Delphi研究之驱动开发篇_第1页
Delphi研究之驱动开发篇_第2页
Delphi研究之驱动开发篇_第3页
Delphi研究之驱动开发篇_第4页
Delphi研究之驱动开发篇_第5页
已阅读5页,还剩39页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Delphi 研究之驱动开发篇(一)Delphi 能不能开发 Windows 的驱动程序(这里的驱动程序当然不是指 VxD 了_)一直是广大 Delphi fans 关注的问题。姑且先不说能或者不能,我们先来看看用 Delphi 开发驱动程序需要解决哪些技术上问题。 Delphi 的链接器是无法生成 Windows 内核模式程序的,因此用 delphi 无法直接生成驱动程序。M$的链接器是可以生成 Windows 内核模式程序的,那么是否可以用 Delphi 生成目标文件,然后用 M$链接呢?要这么做必须要解决以下的问题: Delphi 生成的目标文件是 OMF 格式的,而 M$ link 虽然声称支持 OMF 格式的目标文件,但基本无用。最好能将 OMF 格式转换成 COFF 格式,EliCZ 大侠的OMF2D 正好可以解决这个问题。解决 了目标格式的问题,一切都 OK 了吗?远没这么简单。继续之前,让我们先来看一下著名的 DDDK 吧。 DDDK(Delphi Driver Development Kit)是 The Hacker Defender Project team 发布的一个用 Delphi 开发 Windows 驱动程序的工具包,目前最新版是 0.0.4 版。DDDK 是将常用的驱动 API 用 Delphi 做了层包 装放在 DDDK 单元中,就像下面这样: Copy codeunit DDDK; interface const NtKernel=ntoskrnl.exe; procedure IoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall; implementation procedure krnlIoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall; external NtKernel name IoCompleteRequest; procedure IoCompleteRequest(Irp:PIrp;PriorityBoost:Integer); stdcall; begin krnlIoCompleteRequest(Irp,PriorityBoost); end; 然后在每次链接驱动文件之前,用 omf2d 对 dddk.obj 中需要引入的驱动 API 做以下的处理: omf2d incDDDK.obj /U- /CEIoCompleteRequest=_IoCompleteRequest8 2nul将 DDDK.obj 中的 IoCompleteRequest 改成_IoCompleteRequest8,为什么要这样做呢?那是因为 诸如 ntoskrnl.lib 之类的导入库都是 coff 格式的,coff 格式就是这样命名的。完成这步以后就可以调用 m$ link 将目标文件链接成驱动文件了。这样做虽然可以生成正确的驱动文件,但缺点也是明显的。将驱动 API 用delphi 包装,这些用 delphi 包装的函数不管是否使用都会被链接到最终生成 的驱动文件中,这样会增加驱动文件的尺寸,而且通过 delphi 的封装函数再去调用驱动 API 效率也会受影响,还有就是每次链接前都要用 omf2d incDDDK.obj /U- /CEIoCompleteRequest=_IoCompleteRequest8 去转换 delphi 的目标文件,既麻烦又容易出错。有没有更好的办 法呢? omf2d 的工作就是将 delphi 的命名方法转换成 coff 的_xxxxxxxxx 格式,默认omf2d 会去掉前导下划线和xx 后缀,可以用 /U_*开关让 omf2d 不删除前导下划线,如果我们再有没有xx 后缀的导入库,那问题就简单多了。但 m$并没有提供没有xx 后缀的导入库,那就让我 们自己做一个吧_,其实很简单,比如我们要生成 hal.dll 的导入库,只需要编辑一个如下内容的 hal.def 文件: Copy codeLIBRARY HAL.DLL EXPORTS ExAcquireFastMutex ExReleaseFastMutex ExTryToAcquireFastMutex HalAcquireDisplayOwnership HalAdjustResourceList HalAllProcessorsStarted 然 后用 LINK /LIB /MACHINE:IX86 /DEF:hal.def /OUT:hal.lib 命令就可以生成我们需要的没有xx 后缀的导入库文件了。有了这个文件,事情就好办多了。下面就让我们开始用 delphi 来开发 一个简单的驱动程序 beeper 吧。 这个驱动程序是从 Four-F 的 KmdKit 里的 beeper 转换过来的,程序的目标就是通过访问端口让 PC 的扬声器发声,程序通过三种方法让扬声器发 声,一种是直接访问端口,一种是调用 hal.dll 的 READ_PORT_UCHAR 和WRITE_PORT_UCHAR 访问端口,第三种方法则是调用 hal.dll 的HalMakeBeep 函数。 Copy codeunit beeper; interface uses windows, DDDK, hal; function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall; implementation const TIMER_FREQUENCY:DWORD = 1193167; 1,193,167 Hz OCTAVE:DWORD = 2; octave multiplier PITCH_C:DWORD = 523; C - 523,25 Hz PITCH_Cs:DWORD = 554; C# - 554,37 Hz PITCH_D:DWORD = 587; D - 587,33 Hz PITCH_Ds:DWORD = 622; D# - 622,25 Hz PITCH_E:DWORD = 659; E - 659,25 Hz PITCH_F:DWORD = 698; F - 698,46 Hz PITCH_Fs:DWORD = 740; F# - 739,99 Hz PITCH_G:DWORD = 784; G - 783,99 Hz PITCH_Gs:DWORD = 831; G# - 830,61 Hz PITCH_A:DWORD = 880; A - 880,00 Hz PITCH_As:DWORD = 988; B - 987,77 Hz PITCH_H:DWORD = 1047; H - 1046,50 Hz We are going to play c-major chord DELAY:DWORD = $18000000; for my 800mHz box TONE_1:DWORD = 1141; TONE_2:DWORD = 905; TONE_3:DWORD = 1568; for HalMakeBeep STATUS_DEVICE_CONFIGURATION_ERROR:DWORD = $00C0000182; procedure MakeBeep1(dwPitch: DWORD); stdcall; assembler; asm cli mov al, 10110110b out 43h, al mov eax, dwPitch out 42h, al mov al, ah out 42h, al Turn speaker ON in al, 61h or al, 11b out 61h, al sti push eax mov eax, DELAY 1: dec eax jnz 1 pop eax cli Turn speaker OFF in al, 61h and al, 11111100b out 61h, al sti end; procedure MakeBeep2(dwPitch: DWORD); stdcall; var dwPort, i: DWORD; begin asm cli; end; WRITE_PORT_UCHAR(PUCHAR($43), $b6); WRITE_PORT_UCHAR(PUCHAR($42), dwPitch and $FF); WRITE_PORT_UCHAR(PUCHAR($42), (dwPitch shr 8) and $FF); dwPort := READ_PORT_UCHAR(PUCHAR($61); dwPort := dwPort or 3; WRITE_PORT_UCHAR(PUCHAR($61), dwPort); asm sti end; for i := 1 to DELAY do begin end; asm cli end; Turn speaker OFF dwPort := READ_PORT_UCHAR(PUCHAR($61); dwPort := dwPort and $FC; WRITE_PORT_UCHAR(PUCHAR($61), dwPort); asm sti end; end; function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall; var i: integer; begin MakeBeep1(TONE_1); MakeBeep2(TONE_2); HalMakeBeep(TONE_3); for i := 1 to DELAY do begin end; HalMakeBeep(0); Result := STATUS_DEVICE_CONFIGURATION_ERROR; end; end. unit hal; interface uses Windows; const NtHal = hal.dll; function HalMakeBeep(Frequency: ULONG):BOOLEAN; stdcall; function READ_PORT_UCHAR(Port:PUCHAR):UCHAR; stdcall; procedure WRITE_PORT_UCHAR(Port: PUCHAR; Value: UCHAR); stdcall; implementation function HalMakeBeep(Frequency: ULONG):BOOLEAN; stdcall; external NtHal name _HalMakeBeep; function READ_PORT_UCHAR(Port:PUCHAR):UCHAR; stdcall; external NtHal name _READ_PORT_UCHAR; procedure WRITE_PORT_UCHAR(Port: PUCHAR; Value: UCHAR); stdcall; external NtHal name _WRITE_PORT_UCHAR; end. 1. 用 dcc32 U .include -B -CG -JP -$A-,C-,D-,G-,H-,I-,L-,P-,V-,W+,Y- beeper.pas生成目标文件(此处的.inc 是我保存相关 delphi 单元文件的目录,你的可能不是这个目录哟) 2. 用 omf2d beeper.obj /U_*转换目标文件,使其能被 m$ link 链接 3. 用 link /NOLOGO /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /DRIVER /FORCE:UNRESOLVED /FORCE:MULTIPLE /ENTRY:DriverEntry .libhal.lib beeper.obj /OUT:beeper.sys 生成最终的驱动文件。 (注意这里用/FORCE:UNRESOLVED 是因为 dcc32 会在 delphi 的目标文件中加入 一些单元的初始化及销毁代码,这些东东在驱动程序中是不需要的,所以强行忽略之,还会出现一堆链接警告,也不用理会) 。 执行完以上的步骤,在你的目录下就会生成一个 beeper.sys 文件了。把它拷贝到 KmdKit 的 beeper 目录中,用它的 SCP 文件加载,PC 的喇 叭果然发出的清脆的声音,证明我们的 delphi 驱动是正确的。用此种方法生成的 beeper.sys 只有 1376 字节,只比用 KmdKit 的汇编代码的 beeper.sys 大几百个字节,而用DDDK 生成的 beeper.sys 则要超过 3K。 打完这么多字真不容易,这篇教程就到这里吧,下一篇我们再来用 delphi 做一个更有趣的东东。 Delphi 研究之驱动开发篇(二)上篇教程主要是讲解了用 Delphi 开发 Windows 驱动程序需要解决的一些技术上的问题,虽然啰嗦了一大堆,也不知道讲清楚了没有_。本篇我们开始讲述用Delphi 构建驱动开发环境。用 Delphi 开发驱动程序所必须的工具: Dcc32.exe Delphi 编译器,我用的是 Delphi 2007 的 dcc32Omf2d - Delphi 目标文件转换工具 Link.exe - microsoft 链接器,不要使用 7.1xx 版的,似乎有 bugDDK 相关结构、APIs 的Delphi 声明文件(我已经完成部分结构、APIs 的声明转换,放在我的 KmdKit4D 工具包里)有上面的东东就可以开发 Windows 驱动程序了,下面就让我们来写一个最简单的驱动程序:Copy codeunit driver;interfaceuses nt_status, ntoskrnl;function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;implementationprocedure DriverUnload(DriverObject:PDriverObject); stdcall;beginDbgPrint(DriverUnload(DriverObject:0x%.8X),DriverObject);DbgPrint(DriverUnload(-),);end;function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;beginDbgPrint(DriverEntry(DriverObject:0x%.8X;RegistryPath:0x%.8X),DriverObject,RegistryPath);DriverObject.DriverUnload:=DriverUnload;Result:=STATUS_SUCCESS;DbgPrint(DriverEntry(-):0x%.8X,Result);end;end.以上就是一个最简单的驱动程序,就像其他的可执行程序一样,每个驱动程序也有一个入口点,这是当驱动被装载到内存中时首先被调用的,驱动的入口点是 DriverEntry 过程(注:过程也就是子程序) , DriverEntry 这个名称只是一个标记而已,你可以把它命名为其他任何名字-只要它是入口 点就行了。DriverEntry 过程用来对驱动程序的一些数据结构进行初始化,它的函数原型定义如下:Copy codefunction _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;当然你也可以不用 DriverEntry 这个名字,任意的名字都可以,不过前面的下划线是必需的。nt_status 和 ntoskrnl 两个单元包含了常用的数据结构和 APIs 的声明。由于我常开发 Unix 下的程序,所以我习惯使用 make 编译程序,个人感觉make 比较智能和方便,因此在推荐大家使用 make 编译程序。我用的是 borland make 5.2 版。Makefile 的写法可以参考/showthread.php?t=56912,以下是编译这个程序的 makefile:Copy codeNAME=driverDCC=dcc32INCLUDE=d:mickeylanKmdKit4DincludeLIB_PATH=d:mickeylanKmdKit4DlibDCCFLAGS=-U$(INCLUDE) -B -CG -JP -$A-,C-,D-,G-,H-,I-,L-,P-,V-,W+,Y-LIBS=ntoskrnl.lib hal.lib win32k.lib ntdll.libLINKFLAGS=/NOLOGO /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /DRIVER /LIBPATH:$(LIB_PATH) /FORCE:UNRESOLVED /FORCE:MULTIPLE /ENTRY:DriverEntryall : $(NAME).sys$(NAME).sys : $(NAME).obj omf2d $(NAME).obj /U_*link $(LINKFLAGS) $(LIBS) /out:$(NAME).sys $(NAME).obj$(NAME).obj : $(NAME).pas$(DCC) $(DCCFLAGS) $(NAME).pasclean : del *.objdel *.dcudel *.sys在命令行下执行 make 即可编译生成驱动文件,是不是很简单_。此程序的源码放在 KmdKit4D 的 samplebasic 目录下,该目录下还有一 个 loaddriver.bat,执行此批处理文件即可加载驱动,并且可以在 DbgView 的窗口里看见驱动程序输出的调试信息。到这里,你应该对用 Delphi 开发驱动程序有了个大体的了解了,下面让我们再来写一个很有趣的驱动程序以加深了解。这个程序是从 Four-F 的 KmdKit 的giveio 转换来的(我比较懒,不想写新的_) ,写个驱动程序让用户模式下的进程能通过读取端口来访问电脑的 CMOS。大家都知道,端口是被 Windows 保护起来的,正常情况下,用户模式下的程序是无法直接操作端口的,通过我们的驱动程序修改 I/O 许可位图(I/O permission bit map,IOPM) ,这样用户模式下的相应进程就被允许自由地存取I/O 端口,这方面详细资料见/design/intarch/techinfo/pentium/PDF/inout.pdf。 每个进程都有自己的 I/O 许可位图,每个单独的 I/O 端口的访问权限都可以对每个进程进行单独授权,如果相关的位被设置的话,对对应端口的访问就是被禁止 的,如果相关的位被清除,那么进程就可以访问对应的端口。既然 I/O 地址空间由64K 个可单独寻址的 8 位 I/O 端口组成,IOPM 表的最大尺寸就是 2000h 字节(注:每个端口的权限用 1 个 bit 表示,64K 个端口除以 8 得到的就是 IOPM 的字节数,也就是 65536/88192 字节2000h 字节) 。TSS 的设计意图是为了在任务切换的时候保存处理器状态,从执行效率的考虑出发,Windows NT 并没有使用这个特征,它只维护一个 TSS 供多个进程共享,这就意味着 IOPM 也是共享的,因此某个进程改变了 IOPM 的话,造成的影响是系统范围的。ntoskrnl.exe 中有些未公开的函数是用来维护 IOPM 的,它们是Ke386QueryIoAccessMap 和 Ke386SetIoAccessMap 函数。Copy codefunction Ke386QueryIoAccessMap(dwFlag:DWORD;pIopm:PVOID): NTSTATUS; stdcall;Ke386QueryIoAccessMap 函数从 TSS 中拷贝 2000h 字节的当前 IOPM 到指定的内存缓冲区中,缓冲区指针由 pIopm 参数指定。各参数描述如下: dwFlag-0 表示将全部缓冲区用 0FFh 填写,也就是所有的位都被设置,所有的端口都被禁止访问;1 表示从 TSS 中将当前 IOPM 拷贝到缓冲区中 pIopm-用来接收当前 IOPM 的缓冲区指针,注意缓冲区的大小不能小于2000h 字节如果函数执行成功的话会在返回值的低 8 位返回非 0 值;如果执行失败则返回零。Copy codefunction Ke386SetIoAccessMap(dwFlag:DWORD;pIopm:PVOID): NTSTATUS; stdcall;Ke386SetIoAccessMap 函数刚好相反,它从 pIopm 参数指定的缓冲区中拷贝2000h 字节的 IOPM 到 TSS 中去。各参数描述如下: dwFlag-这个参数只能是 1,其他任何值函数都会返回失败 pIopm-指向包含 IOPM 数据的缓冲区,缓冲区的尺寸不能小于 2000h 字节如果函数执行成

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论