阅读:9556回复:28
手把手跟我学驱动(1)--框架及编译环境文:柯小三(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/) |
|
沙发#
发布于:2009-02-28 11:19
拜读了,受益匪浅,看到了一点希望
|
|
板凳#
发布于:2009-03-02 14:05
学习了 谢谢大牛!
|
|
|
地板#
发布于:2009-06-02 20:41
奇怪,例子程序我这里通不过
|
|
地下室#
发布于:2009-07-04 21:42
初学者,谢了
|
|
5楼#
发布于:2009-07-06 04:00
拜读
|
|
6楼#
发布于:2009-07-10 13:53
|
|
7楼#
发布于:2009-08-07 00:40
o , 还行把。。呵呵
|
|
8楼#
发布于:2009-08-07 11:34
写的很好,收藏下来慢慢看!
|
|
|
9楼#
发布于:2009-08-16 09:29
学习,但是每一次配置 VS2008 好像有一点麻烦,LZ 还有没有更速度的方法?
|
|
10楼#
发布于:2009-09-07 10:46
谢谢楼主!
|
|
11楼#
发布于:2009-09-27 23:44
好贴。。。
|
|
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; |
|
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; |
|
14楼#
发布于:2009-09-29 18:21
新人 完全看不懂说的什么~
用VC++6.0不行吗? 哪个更适合新手一些? |
|
15楼#
发布于:2009-09-29 21:34
|
|
16楼#
发布于:2009-09-29 21:36
|
|
17楼#
发布于:2009-09-29 21:37
|
|
18楼#
发布于:2010-01-06 17:58
这样的帖子还发。。。。。
|
|
19楼#
发布于:2010-01-08 21:50
看来我要去下个vs2008了
|
|
|
上一页
下一页