手把手跟我学驱动(1)--框架及编译环境
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
大概是一个多星期以前吧,也说不上是出于什么特殊目的,我开始学上驱动来了,这是我第一次写点心得之类的文章,我起步的时候也没少走弯路,现在,至少我可以写一个基本的驱动框架来了,也挺不容易,所以我把我学习的路子写出来,和跟我一样的新手们交流一下,正要学写驱动的同志们可以借鉴一下。前一阵子,不知道是怎么跑到两个专门写驱动的群里面了,在那里面,看到了传说中的大牛,也结识了大牛中的一些中牛,其中也有帮了我不少的不知道是什么样的牛。在这里我要特别感谢文件系统驱动群里的“被剃毛的老鹰”,“哆啦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例程入口,如果必段的话,我们还必须创建必要的D
os连接符号名一样,就和C盘对应于\Harddisk\partition1一样,行,不废话了,一起写一个简单的DriverEntry吧:
#include
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
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
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 例程和相关的内存使用方法,实现驱动的不同版本的读写例程。有兴趣的,等着吧,嘿嘿。