分享

手把手跟我学驱动(1)

 创科之龙 2010-11-20

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

特别声明:转载请注明出处

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多