驱动编写的全过程(上) 2008-04-05 12:04
目录:
☆ 概述 ☆ 编写hello.c文件 ☆ 编写dirs文件 ☆ 编写sources文件 ☆ 编写makefile文件 ☆ 编译产生hello.sys文件 ☆ 编写hello.inf文件 ☆ 安装hello.inf文件 ☆ 卸载hello.sys及相关设置 ☆ 察看KdPrint输出 ☆ 使用DriverMonitor ☆ 参考资源
--------------------------------------------------------------------------
☆ 概述
在<<MSDN系列(1)--学习使用"/Device/PhysicalMemory">>中,我首次接触了Windows内核,并演习了通过调用门从Ring 3进入Ring 0,进而获取一些内核空间的信息。这种方式利弊岂有之,总的来说弊大于利。
听说现在Windows驱动和Unix下LKM、KLD一样,允许动态加载/卸载。过去我们在Unix下常利用类似技术进行Kernel Hacking,今天我来学习如何在Windows下做同样的事。
拣空看了三本书([1]、[2]、[3])的个别章节,感觉举步维艰,想说"Hello World"不容易。
我安装了EWindows XP SP1、VC 7、Windows DDK 2600.1106、ActivePerl 5.6.1、Compuware SoftICE Release 2.7、VMware Workstation 3.2,以方便此次学习过程。如果你碰巧也有类似环境,并且也是此道初学者的话,后面的许多步骤将相当容易重现。
下面是学习笔记。为了简捷起见,并没有时刻指明某项操作是在开发机还是在测试机进行,但你务必清楚区分开发机、测试机上应该进行的操作。
☆ 编写hello.c文件
--------------------------------------------------------------------------/** For x86/EWindows XP SP1 & VC 7 & Windows DDK 2600.1106* build -cZ -x86*/
/************************************************************************* ** Head File ** *************************************************************************/
#include <wdm.h>
/************************************************************************* ** Macro ** *************************************************************************/
#define PRIVATEDRIVERNAME "PRIVATE_HELLO_WORLD"
/************************************************************************* ** Function Prototype ** *************************************************************************/
/************************************************************************* ** Static Global Var ** *************************************************************************/
/************************************************************************/
/** DriverEntry is the first routine called after a driver is loaded, and* is responsible for initializing the driver.** you'll find a list of NTSTATUS status codes in the DDK header * ntstatus.h (/WINDDK/2600.1106/inc/ddk/wxp/)*/NTSTATUS DriverEntry ( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ){ CHAR privatedrivername[] = PRIVATEDRIVERNAME; /* * 这是无效的用户空间地址 */ PVOID p = ( PVOID )1;
/* * kernel-mode functions and the functions in your driver use the * __stdcall calling convention when compiled for an x86 computer. * This shouldn't affect any of your programming, but it's something * to bear in mind when you're debugging */
/* * This routine is defined in ntddk.h, wdm.h, and ndis.h. * A call to this macro requires double parentheses. */ KdPrint(( "%s - Entering DriverEntry()/n", privatedrivername )); __try { KdPrint(( "%s - You should see this message[1]/n", privatedrivername )); /* * 由于1未对齐在4字节边界上,引发SYSTEM_DATATYPE_MISALIGNMENT异常。 * Do not use this routine on kernel-mode addresses. */ ProbeForWrite( p, 4, 4 ); KdPrint(( "%s - You shouldn't see this message[1]/n", privatedrivername )); } __except ( EXCEPTION_EXECUTE_HANDLER ) { KdPrint(( "%s - __except{}/n", privatedrivername )); } KdPrint(( "%s - Kept control after exception/n", privatedrivername )); __try { KdPrint(( "%s - You should see this message[2]/n", privatedrivername )); __leave; KdPrint(( "%s - You shouldn't see this message[2]/n", privatedrivername )); } __finally { KdPrint(( "%s - __finally{}/n", privatedrivername )); } KdPrint(( "%s - Exiting DriverEntry()/n", privatedrivername )); /* * 故意失败返回 */ return( STATUS_UNSUCCESSFUL );} /* end of DriverEntry */
/************************************************************************/
--------------------------------------------------------------------------
用户空间编程,main()、WinMain()是总入口点。与之类似,DriverEntry()是驱动程序总入口点,它的两个形参对于目前阶段的我来说并不重要。其最后故意失败返回,使我可以不考虑绝大多数复杂情况,而专注于如何编译驱动程序、加载驱动程序,观察驱动程序调试输出信息。同样的技巧在Unix系统使用过。
与用户空间的结构化异常处理(SEH)相比,内核空间的SEH主要致力于捕捉内核代码访问无效用户空间地址产生的异常,它无法捕捉除以零、内核代码访问无效内核空间地址产生的异常。
hello.c中由于1未对齐在4字节边界上,ProbeForWrite( p, 4, 4 )引发相应异常。SEH机制使程序继续保持控制权。对于__try{}/__finally{},若因return、continue、break、goto等语句提前离开__try{},将导致一次开销可观的"局部展开"。__leave语句用于避免不必要的"局部展开"。
KdPrint(())的用法与printf()类似,注意调用该宏时必须指定两层圆括号。
☆ 编写dirs文件
与用户空间编程不同,只有hello.c不足以产生hello.sys,至少还需要三个文件:
dirs、sources、makefile
DDK文档"Running the Build Utility"小节对此有详细解释。build根据dirs文件遍历目录树,在子目录中发现dirs时继续遍历,发现sources时build开始为调用nmake做准备。nmake使用makefile,最终调用cl进行真正的编译。
假设将来的目录/文件布局是这样的:
hello/ --+-- dirs | +-- code/ --+-- hello.c | +-- sources | +-- makefile
这里只列举了手工创建的目录/文件,不包括编译过程产生的目录/文件。
此时dirs文件内容很简单,就一行:
--------------------------------------------------------------------------DIRS=code--------------------------------------------------------------------------
☆ 编写sources文件
--------------------------------------------------------------------------## Use the TARGETNAME macro to specify the name of the library to be built.# Do not include the file name extension#TARGETNAME=hello
## All build products (such as .exe, .dll, and .lib files) will be placed # in this directory#TARGETPATH=obj
## Use the TARGETTYPE macro to specify the type of product being built. # TARGETTYPE gives the Build utility clues about some of the input files# that it should expect. You must include this macro in your sources file.#TARGETTYPE=DRIVER
## Use the USE_PDB macro if your debug symbolic files will use a VC4 PDB. # This is the default in the Windows XP build environment.#USE_PDB=1
# # Use the INCLUDES macro to indicate the location of the headers to be # included in your build#INCLUDES=
## Use the MSC_WARNING_LEVEL macro to set the warning level to use on the # compiler. The default is /W3.## After your code builds without errors, you might want to change # MSC_WARNING_LEVEL to /W3 /WX. Setting this value causes warnings to show# as errors.#MSC_WARNING_LEVEL=-W3 -WX
## The SOURCES macro specifies the files to be compiled. The SOURCES macro# is required by the Build utility. This macro must be placed in your # sources file. All files specified by this macro must reside in the # directory containing the sources file.#SOURCES=hello.c--------------------------------------------------------------------------
必要的解释我都放在sources文件里了。单独多解释一下TARGETPATH,BUILD_ALT_DIR的值会被追加在TARGETPATH之后。假设进入了"Win XP Checked Build Environment":
> set BUILD_ALT_DIRBUILD_ALT_DIR=chk_wxp_x86
此时在hello/下执行build -cZ -x86,产生的目录/文件布局如下:
hello/ --+-- buildchk_wxp_x86.log | +-- dirs | +-- code/ --+-- hello.c | +-- hello.inf | +-- sources | +-- makefile | +-- objchk_wxp_x86/ --+-- _objects.mac | +-- i386/ --+-- hello.sys | +-- hello.obj | +-- hello.pdb
如果你嫌BUILD_ALT_DIR对于目前阶段太碍眼,可以删除该环境变量:
> set BUILD_ALT_DIR=
此时在hello/下执行build -cZ -x86,产生的目录/文件布局如下:
hello/ --+-- build.log | +-- dirs | +-- code/ --+-- hello.c | +-- hello.inf | +-- sources | +-- makefile | +-- obj/ --+-- _objects.mac | +-- i386/ --+-- hello.sys | +-- hello.obj | +-- hello.pdb
☆ 编写makefile文件
--------------------------------------------------------------------------!INCLUDE $(NTMAKEENV)/makefile.def--------------------------------------------------------------------------
J:/source/driver/hello> set NTMAKEENVNTMAKEENV=J:/WINDDK/2600~1.110/bin
☆ 编译产生hello.sys文件
在dirs文件所在目录里执行"build -cZ -x86":
J:/source/driver/hello> build -cZ -x86BUILD: Adding /Y to COPYCMD so xcopy ops won't hang.BUILD: Compile and Link for i386BUILD: Examining j:/source/driver/hello directory tree for files to compile.BUILD: Compiling j:/source/driver/hello/code directoryCompiling - code/hello.c for i386BUILD: Linking j:/source/driver/hello/code directoryLinking Executable - code/obj/i386/hello.sys for i386BUILD: Done
2 files compiled 1 executable built
J:/source/driver/hello>
驱动编写的全过程(下) 2008-04-05 12:24
☆ 编写hello.inf文件
; ------------------------------------------------------------------------
;; Copyright to satisfy the CHKINF utility;
[Version]Signature = "$Windows NT$"Class = %ClassName%;; For a new device setup class, the INF must specify a newly generated; ClassGuid value;; 用/WINDDK/2600.1106/tools/other/i386/guidgen.exe生成;ClassGuid = {EABDB9DF-F09D-44ba-81E5-551C2ABC1FA9}Provider = %INFCreator%DriverVer = 06/10/2003,1.00.1993.9
; ------------------------------------------------------------------------
[ClassInstall32.ntx86] AddReg = ClassInstall32Reg[ClassInstall32Reg]HKR,,,,%ClassName%
; ------------------------------------------------------------------------
[Manufacturer]%INFCreator% = HelloWorldSection
; ------------------------------------------------------------------------
[HelloWorldSection]