kcynice
驱动牛犊
驱动牛犊
  • 注册日期2008-03-03
  • 最后登录2010-08-28
  • 粉丝1
  • 关注0
  • 积分43分
  • 威望138点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分2分
阅读:9558回复:28

手把手跟我学驱动(1)--框架及编译环境

楼主#
更多 发布于:2009-02-28 10:01


文:柯小三(kcynic@163.com),引自:http://kcynic.blog.163.com/blog/static/84324655200911803938893/

未经许可,转载请注明原文出处并保持完整性

大概是一个多星期以前吧,也说不上是出于什么特殊目的,我开始学上驱动来了,这是我第一次写点心得之类的文章,我起步的时候也没少走弯路,现在,至少我可以写一个基本的驱动框架来了,也挺不容易,所以我把我学习的路子写出来,和跟我一样的新手们交流一下,正要学写驱动的同志们可以借鉴一下。前一阵子,不知道是怎么跑到两个专门写驱动的群里面了,在那里面,看到了传说中的大牛,也结识了大牛中的一些中牛,其中也有帮了我不少的不知道是什么样的牛。在这里我要特别感谢文件系统驱动群里的“被剃毛的老鹰”,“哆啦B梦”,LookSail,qi等,正是因为他们,我才有了今天的进步,尽管这只是一个小小的进步而已。

一、生产工具:

首先,得准备好微软的驱动开发包,我这里用的是WDK(Windows Driver Kit)6001,因为它里面的编译环境我不会用,所以,我选用了集成环境,这里我使用的VS2008.这两个工具在网上都有下,至于下载和安装,都是你自己的事情了。

二、开工了:

还记得我们当初第一次写C程序吗?每一个C程序都有一个必须的东西,即main主函数,它是程序执行的起点,后来我们熟悉了,我们会在编译器编译的时候指定另一个函数入口为主函数起点。这里所要说的驱动程序也是有一个类似的入口,一般我们都会把这个名字叫 DriverEntry,和一般的C程序一样,这个函数入口也可以设置成为另外一个名字,如果是在命令行的话,给个参数 -entry: NewEntryName即可,后面的部分,我们可以在VS里面设置这个入口。

一般的Windows应用程序在主入口函数完成相关的初始化工作以后,就开始一个消息循环,进行消息处理。驱动程序也有点像像了,驱动程序在主入口函数中完成相关的初始化之后便处于睡眠状态,开始等待外围请求。驱动程序的主要消息都被Windows打包成IRP包,相关的知识如果你都还不明白的话,我想你可以参考MSDN或者是百度一下,网络应该成为你的朋友。

我们就以DriverEntry为例来说明吧,这个函数原型如下:

NTSTATUS  DriverEntry(PDEVICE_OBJECT pDrvObj,PUNICODE_STRING pRegPath)

第一个参数是驱动模块对象,由操作系统内核分配好传来,以我的理解吧,就和我们的Win32应用程序的第一个参数HINSTANCE一样,第二个参数是操作系统传来的驱动在注册表中路径。和以前的main函数一样,DriverEntry函数也是做一些初始化的工作,下面一起来看看一个基本的最简单的驱动程序框架。

一个基本的驱动程序框架里面,我们做的最主要的事情就是创建设备对象,还有初始化一些必要的IRP例程入口,如果必段的话,我们还必须创建必要的Dos连接符号名一样,就和C盘对应于\Harddisk\partition1一样,行,不废话了,一起写一个简单的DriverEntry吧:

#include <wdm.h>

void Example1Unload(PDRIVER_OBJECT pDrvObj)

{

UNICODE_STRING usDosDevName;//Dos符号链接

DbgPrint("Example1: Driver is being unloaded.\n");

//首先我们要删除的是一个Dos链接名,否则,这个链接名便不再可用,直到系统重启

//不过,这个Dos链接名应该和我们在DriverEntry里创建的一样,千万记得了

RtlInitUnicodeString(&usDosDevName,L"\\DosDevices\\Example1");

IoDeleteSymbolicLink(&usDosDevName);

//接下来,我们再删除驱动程序创建的设备对象,在此之后,系统将会把我们的驱动从内核移除

//我们的驱动便是被卸载了

IoDeleteDevice(pDrvObj->DeviceObject);

//OK,所有的事情已经解决

}

NTSTATUS Example1IrpRoutine(PDRIVER_OBJECT pDev,PIRP pIrp)

{

//在调试器中输出一个字符串,你还记得Win32下的TRACE系列宏吗

DbgPrint("An driver routine is called.\n");

return STATUS_SUCCESS;//简单地返回成功而已

}

//驱动入口函数

NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj,PUNICODE_STRING pUsRegPath)

{

NTSTATUS status = STATUS_UNSUCCESSFUL;//初始化为不成功

UNICODE_STRING usDevName;//我们的设备名

UNICODE_STRING usDosDevName;//Dos符号链接

PDEVICE_OBJECT pDevObj = NULL;//设备对象

unsigned int nIndex;//一个计数器,循环的时候可以用到

DbgPrint("Example1: Driver entry is called.\n");

//__try{

//初始化两个Unicodestring

RtlInitUnicodeString(&usDevName,L"\\Device\\Example1");

RtlInitUnicodeString(&usDosDevName,L"\\DosDevices\\Example1");

//现在我们创建设备

status = IoCreateDevice(pDrvObj,0,&usDevName,FILE_DEVICE_UNKNOWN,

FILE_DEVICE_SECURE_OPEN,FALSE,&pDevObj);

//只有成功创建设备我们才有必要继续

if(NT_SUCCESS(status)){//测试成功与否一般用这个宏,而不是让它直接与STATUS_SUCCESS比 //较,查看一下这个宏定义就知道了

//成功创建设备之后,我们要作的一件事就是初使化驱动的IRP例程,现在,我们的驱动什么 都不干,所以,每个例程也还是什么都不做

//每个设备最多有IRP_MJ_MAXIMUM_FUNCTION个IRP例程,现在,我们都把它们初始 //化成一个相同的入口

for(nIndex=0;nIndex<IRP_MJ_MAXIMUM_FUNCTION;++nIndex)

pDrvObj->MajorFunction[nIndex] = Example1IrpRoutine;

//接下来,我再安装一个卸载例程,从而,使我们的驱动可以动态的卸载,这就是传说中的 //热插拨

pDrvObj->DriverUnload = Example1Unload;

//把创建的设备保存起来吧,否则以后便不能引用啦

pDrvObj->DeviceObject = pDevObj;

//好了,创建符号链接,正是由于有了符号链接,我们可以用C:来访问第一个硬盘分区……

status = IoCreateSymbolicLink(&usDosDevName,&usDevName);

if(!NT_SUCCESS(status)){

//在这里定义了如果创建Dos链接失败将做的事情,如果我们不需要一个Dos符号链接, //这一步便不是必须的,更不必检验了

IoDeleteDevice(pDevObj);//删除创建的设备对象

//这个时候,status肯定就代表失败了,如果入口函数返回一个失败的状态,系统会自动把 //创建的这个Driver删除的,我们不用担心

}

}

//现在,我们测试一下成功与否

//}__except(EXCEPTION_EXECUTE_HANDLER){

//如果出现一般的异常,执行流程会走到这儿来,除了一些SEH都解决不了的异常除外,这个我们以 //后再讨论

//}

return status;//

}

至此,一个简单的设备驱动程序框架已经完成了,难道不是吗?在这个驱动里面,我们创建的设备\Device\Example1,这里,Device是设备对象的所属名字空间,Example1才是它的名字。事实上,它只是一个需拟设备驱动程序,并没有一个实际的设备与之相对应。

但是,这里只是介绍了这么一个框架程序代码,还得编译链接呢!

三、编译链接:

我说过,我水平有限,不会使用WDK(或是DDK)集成的编译链接环境,所以我选择使用WDK+VS2008.

1.建立工程。VS里面没有为现成的驱动工程向导,所以,我一般都建立一个Win32工程(我常会用Console),只是,建立一个空项目即可,你可别指望向导生成的代码文件在你的驱动里面有用!

2.添加代码文件。添加新项,选择源程序代码文件,不过,这里一般用C文件,至于原因,我不想多解释,现在只是一个简单的代码,所以,我都在一个文件里面写完。就是把我刚才在上面的代码复制到你刚新建的源文件里就行了。

3.设置编译选项。还记得安装了WDK吧,既然如果,就要用到它了,再说了,以前还没用到过#include <wdm.h>之类的语句吧?好了,在附加包含路径设置成WDK中的inc路径,一般如下,

WDKROOT\inc\api

WDKROOT\inc\crt

WDKROOT\inc\ddk

另外,同于驱动是接近硬件的程序,它可以执行绝大多数CPU指令,从而,我们还得为我们的驱动程序指定目标CPU平台,这里,我选X86.需要在预定义里定义添加一个_X86_的定义,否则会收到一个需要指定目标平台(target architecture)的编译错误。

4.链接先项。现在的设置的话,生成的文件还是exe后缀呢,但驱动都是sys后缀,所以,你得改,在什么地方改,我不说了。另外,得添加一个输入库,就是附加依赖项:ntoskrnl.lib这个时候,又得设置附加库目录了,设置成 WDKROOT\lib\wxp\i386

别忘了,我们现在的工程还是个console项目呢,得改,找到链接选项卡,System(子系统)一项选择Native,还有驱动选/Driver.

5.OK,现在试着生成一下吧。如果没有语法错误的话,它还是会有一大堆错误的,好,我当初写的时候也是这样,那,我现在把所有的设置一股脑告诉你,你就别走弯路了,遇到其它别的问题再说,主要精力别放在这儿就行了。

①C/C++->Preprocessor: Ignor Standard include path  YES

②C/C++->Code generation: Basic Runtime Check  Default;  Buffer security check  No(GS-)

③C/C++->Advanced: Calling convertion   __stdcall(/Gz)

③Linker->Input: Ignor All default libraries   YES

④linker->Manifest file: Generate manifest    No

⑤Linker->Advaced:  Entry point  DriverEntry;   Base address   0x100000;  Ramdomized base address  Default;   Data execution prevention   Default;

四.启动驱动程序。

把生成的example1.sys复制到一个地方,我这里是C:\然后,使用以下程序加载程序加载驱动:

int _cdecl main(void)

{

    HANDLE hSCManager;

    HANDLE hService;

    SERVICE_STATUS ss;

    hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

    

    printf("Load Driver\n");

    if(hSCManager)

    {

        printf("Create Service\n");

        hService = CreateService(hSCManager, "Example1",

                                 "Example1 Driver",

                                  SERVICE_START | DELETE | SERVICE_STOP,

                                  SERVICE_KERNEL_DRIVER,

                                  SERVICE_DEMAND_START,

                                  SERVICE_ERROR_IGNORE,

                                  "C:\\example1.sys",

                                  NULL, NULL, NULL, NULL, NULL);

        if(!hService)

        {

            hService = OpenService(hSCManager, "Example1",

                       SERVICE_START | DELETE | SERVICE_STOP);

        }

        if(hService)

        {

            printf("Start Service\n");

            StartService(hService, 0, NULL);

            printf("Press Enter to close service\r\n");

            getchar();

            ControlService(hService, SERVICE_CONTROL_STOP, &ss);

            DeleteService(hService);

            CloseServiceHandle(hService);

            

        }

        CloseServiceHandle(hSCManager);

    }

    

    return 0;

}

这段代码是我直接从其它地方复制过来的,经过我的试验,并不总是好使的,但是,只要执行一次就好办了,以后就不再需要它啦。因为,在执行一次 CreateService之后,它就会在注册表的HKLM\CurrentControlSet\System\Services\下创建一个子键 Example1,在以后我们的驱动的例子,就不再使用上面的加载程序,而是直接修改这个注册表键了(当然,并不是最终目标,在此,只是图个方便,以后,有一天,会使用标准安装文件inf)。Example1子键下面有个ImagePath子项指定了这个服务的目标路径。第一次,如果上面的程序执行成功,再按以下回车就卸载了。以后,将使用Dos命令来启动和停止它,启动: net start example1,停止:net stop example.是不是很简单了?

五、测试:

现在好了,既然我们的驱动能加载了,我们就试一下,写一个一般的Win32程序,使用如下语句:

CreateFile("\\\\.\\Example1",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,NULL,OPEN_EXISTING,0,NULL)试一下。是不是能成功返回一个句柄了?

六、总结:

本文主要记录了我怎么使用VS2008+WDK创建我的第一个驱动程序的过程,这个程序我保证绝对是我的第一个程序,上个周末写的。哈哈。虽然我的介绍没有楚狂人或是wowocock的那么独到,那么吸引人,它只是绍了构建一个驱动框架的基本方法。相关的系统知识得参考MSDN。在下一篇中我将介绍几种IO 例程和相关的内存使用方法,实现驱动的不同版本的读写例程。有兴趣的,等着吧,嘿嘿。

好不容易,记下了我学习的过程,有兴趣一起学习交流的或是有问题的欢迎与我联系,有错请指正,谢谢,QQ:178041876

特别声明:未经许可,转载请注明原文出处并保持完整性(本文引自http://kcynic.blog.163.com/blog/static/84324655200911803938893/


最新喜欢:

greenpeacegreenp... SystemNeckSystem... sherlinlovesherli...
qingwen
驱动牛犊
驱动牛犊
  • 注册日期2009-02-20
  • 最后登录2010-07-11
  • 粉丝0
  • 关注0
  • 积分43分
  • 威望381点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2009-02-28 11:19
拜读了,受益匪浅,看到了一点希望
Peter Pan
驱动牛犊
驱动牛犊
  • 注册日期2009-02-26
  • 最后登录2009-03-02
  • 粉丝0
  • 关注0
  • 积分12分
  • 威望121点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2009-03-02 14:05
学习了 谢谢大牛!  
逆流而上
lofrank
驱动牛犊
驱动牛犊
  • 注册日期2008-09-28
  • 最后登录2010-04-26
  • 粉丝0
  • 关注0
  • 积分13分
  • 威望102点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地板#
发布于:2009-06-02 20:41
奇怪,例子程序我这里通不过
yaoayao111
驱动牛犊
驱动牛犊
  • 注册日期2004-03-09
  • 最后登录2009-09-25
  • 粉丝0
  • 关注0
  • 积分3分
  • 威望30点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2009-07-04 21:42
初学者,谢了
liuganchao
驱动牛犊
驱动牛犊
  • 注册日期2009-06-22
  • 最后登录2012-04-20
  • 粉丝0
  • 关注0
  • 积分17分
  • 威望151点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2009-07-06 04:00
    拜读
ly830817
驱动牛犊
驱动牛犊
  • 注册日期2009-07-10
  • 最后登录2010-04-20
  • 粉丝0
  • 关注0
  • 积分10分
  • 威望101点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2009-07-10 13:53
     
xum2008
驱动牛犊
驱动牛犊
  • 注册日期2009-05-08
  • 最后登录2014-08-10
  • 粉丝0
  • 关注0
  • 积分75分
  • 威望741点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2009-08-07 00:40
o , 还行把。。呵呵
hhn9918
驱动牛犊
驱动牛犊
  • 注册日期2009-08-07
  • 最后登录2009-08-07
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望11点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
8楼#
发布于:2009-08-07 11:34
写的很好,收藏下来慢慢看!
Good Good Study,Day Day Up!
yangqi123
驱动牛犊
驱动牛犊
  • 注册日期2008-07-22
  • 最后登录2010-05-11
  • 粉丝0
  • 关注0
  • 积分20分
  • 威望171点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分1分
9楼#
发布于:2009-08-16 09:29
学习,但是每一次配置 VS2008 好像有一点麻烦,LZ 还有没有更速度的方法?
dengyueping
驱动牛犊
驱动牛犊
  • 注册日期2009-09-07
  • 最后登录2009-09-07
  • 粉丝0
  • 关注0
  • 积分7分
  • 威望71点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
10楼#
发布于:2009-09-07 10:46
谢谢楼主!
Dr.king
驱动牛犊
驱动牛犊
  • 注册日期2009-09-15
  • 最后登录2009-09-28
  • 粉丝0
  • 关注0
  • 积分4分
  • 威望41点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
11楼#
发布于:2009-09-27 23:44
好贴。。。
Dr.king
驱动牛犊
驱动牛犊
  • 注册日期2009-09-15
  • 最后登录2009-09-28
  • 粉丝0
  • 关注0
  • 积分4分
  • 威望41点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
12楼#
发布于:2009-09-28 23:16
1>errors in directory c:\winddk\7600.16385.0\project\example2
1>c:\winddk\7600.16385.0\project\example2\example2.c(42) : error C4028: formal p
arameter 1 different from declaration
怎么回事啊?
是这一句
for(nIndex=0;nIndex<IRP_MJ_MAXIMUM_FUNCTION;++nIndex)
pDrvObj->MajorFunction[nIndex] = Example1IrpRoutine;
Dr.king
驱动牛犊
驱动牛犊
  • 注册日期2009-09-15
  • 最后登录2009-09-28
  • 粉丝0
  • 关注0
  • 积分4分
  • 威望41点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
13楼#
发布于:2009-09-28 23:17
1>errors in directory c:\winddk\7600.16385.0\project\example2
 1>c:\winddk\7600.16385.0\project\example2\example2.c(42) : error C4028: formal p
 arameter 1 different from declaration
 怎么回事啊?
 是这一句
 for(nIndex=0;nIndex<IRP_MJ_MAXIMUM_FUNCTION;++nIndex)
pDrvObj->MajorFunction[nIndex] = Example1IrpRoutine;
swlilike
驱动牛犊
驱动牛犊
  • 注册日期2009-09-29
  • 最后登录2010-04-04
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望11点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
14楼#
发布于:2009-09-29 18:21
新人  完全看不懂说的什么~
用VC++6.0不行吗?  哪个更适合新手一些?
kcynice
驱动牛犊
驱动牛犊
  • 注册日期2008-03-03
  • 最后登录2010-08-28
  • 粉丝1
  • 关注0
  • 积分43分
  • 威望138点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分2分
15楼#
发布于:2009-09-29 21:34
引用第14楼swlilike于2009-09-29 18:21发表的  :
新人  完全看不懂说的什么~
用VC++6.0不行吗?  哪个更适合新手一些?


①VC++6.0不被新版DDK所支持
②如果你已经习惯VC6,并且一定要用VC6,那么新版本的DDK环境下,你只能把VC6当做文件编译器(多了些语法高亮之类的).你得写MakeFile借用DDK build. 或者设置自定义编译编译(rules)使用DDK的编译器
kcynice
驱动牛犊
驱动牛犊
  • 注册日期2008-03-03
  • 最后登录2010-08-28
  • 粉丝1
  • 关注0
  • 积分43分
  • 威望138点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分2分
16楼#
发布于:2009-09-29 21:36
引用第12楼Dr.king于2009-09-28 23:16发表的  :
1>errors in directory c:\winddk\7600.16385.0\project\example2
1>c:\winddk\7600.16385.0\project\example2\example2.c(42) : error C4028: formal p
arameter 1 different from declaration
怎么回事啊?
是这一句
.......


这是语法问题,你适当改一下以适应你的编译器
kcynice
驱动牛犊
驱动牛犊
  • 注册日期2008-03-03
  • 最后登录2010-08-28
  • 粉丝1
  • 关注0
  • 积分43分
  • 威望138点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分2分
17楼#
发布于:2009-09-29 21:37
引用第9楼yangqi123于2009-08-16 09:29发表的  :
学习,但是每一次配置 VS2008 好像有一点麻烦,LZ 还有没有更速度的方法?


有,写项目模板.
可以用Javascript来完成(网上好像有DDK Wizard下载,不过我没试过)
我没学习怎么写模板
disahacker
驱动牛犊
驱动牛犊
  • 注册日期2008-11-11
  • 最后登录2013-05-01
  • 粉丝0
  • 关注0
  • 积分21分
  • 威望201点
  • 贡献值1点
  • 好评度0点
  • 原创分0分
  • 专家分0分
18楼#
发布于:2010-01-06 17:58
这样的帖子还发。。。。。
zenzc
驱动牛犊
驱动牛犊
  • 注册日期2010-01-08
  • 最后登录2010-08-17
  • 粉丝0
  • 关注0
  • 积分2分
  • 威望21点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
19楼#
发布于:2010-01-08 21:50
看来我要去下个vs2008了
是非在乎实力
上一页
游客

返回顶部