delphi开发驱动程序.docx
- 文档编号:5221207
- 上传时间:2022-12-14
- 格式:DOCX
- 页数:56
- 大小:57.91KB
delphi开发驱动程序.docx
《delphi开发驱动程序.docx》由会员分享,可在线阅读,更多相关《delphi开发驱动程序.docx(56页珍藏版)》请在冰豆网上搜索。
delphi开发驱动程序
用Delphi编写驱动程序
实现原理
Delphi能不能开发Windows的驱动程序(这里的驱动程序当然不是指VxD了^_^)一直是广大Delphifans关注的问题。
姑且先不说能或者不能,我们先来看看用Delphi开发驱动程序需要解决哪些技术上问题。
Delphi的链接器是无法生成Windows内核模式程序的,因此用delphi无法直接生成驱动程序。
M$的链接器是可以生成Windows内核模式程序的,那么是否可以用Delphi生成目标文件,然后用M$链接呢?
要这么做必须要解决以下的问题:
Delphi生成的目标文件是OMF格式的,而M$link虽然声称支持OMF格式的目标文件,但基本无用。
最好能将OMF格式转换成COFF格式,EliCZ大侠的OMF2D正好可以解决这个问题。
解决了目标格式的问题,一切都OK了吗?
远没这么简单。
继续之前,让我们先来看一下著名的DDDK吧。
DDDK(DelphiDriverDevelopmentKit)是TheHackerDefenderProjectteam发布的一个用Delphi开发Windows驱动程序的工具包,目前最新版是0.0.4版。
DDDK是将常用的驱动API用Delphi做了层包装放在DDDK单元中,就像下面这样:
代码:
unitDDDK;
interface
const
NtKernel='ntoskrnl.exe';
……
procedureIoCompleteRequest(Irp:
PIrp;PriorityBoost:
Integer);stdcall;
……
implementation
procedurekrnlIoCompleteRequest(Irp:
PIrp;PriorityBoost:
Integer);stdcall;externalNtKernelname'IoCompleteRequest';
procedureIoCompleteRequest(Irp:
PIrp;PriorityBoost:
Integer);stdcall;
begin
krnlIoCompleteRequest(Irp,PriorityBoost);
end;
……
然后在每次链接驱动文件之前,用omf2d对dddk.obj中需要引入的驱动API做以下的处理:
omf2dinc\DDDK.obj/U-/CEIoCompleteRequest=_IoCompleteRequest@82>nul将DDDK.obj中的IoCompleteRequest改成_IoCompleteRequest@8,为什么要这样做呢?
那是因为诸如ntoskrnl.lib之类的导入库都是coff格式的,coff格式就是这样命名的。
完成这步以后就可以调用m$link将目标文件链接成驱动文件了。
这样做虽然可以生成正确的驱动文件,但缺点也是明显的。
将驱动API用delphi包装,这些用delphi包装的函数不管是否使用都会被链接到最终生成的驱动文件中,这样会增加驱动文件的尺寸,而且通过delphi的封装函数再去调用驱动API效率也会受影响,还有就是每次链接前都要用omf2dinc\DDDK.obj/U-/CEIoCompleteRequest=_IoCompleteRequest@8去转换delphi的目标文件,既麻烦又容易出错。
有没有更好的办法呢?
omf2d的工作就是将delphi的命名方法转换成coff的_xxxxxxx@xx格式,默认omf2d会去掉前导下划线和@xx后缀,可以用/U_*开关让omf2d不删除前导下划线,如果我们再有没有@xx后缀的导入库,那问题就简单多了。
但m$并没有提供没有@xx后缀的导入库,那就让我们自己做一个吧^_^,其实很简单,比如我们要生成hal.dll的导入库,只需要编辑一个如下内容的hal.def文件:
代码:
LIBRARYHAL.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函数。
代码:
unitbeeper;
interface
useswindows,DDDK,hal;
function_DriverEntry(DriverObject:
PDriverObject;RegistryPath:
PUnicodeString):
NTSTATUS;stdcall;
implementation
const
TIMER_FREQUENCY:
DWORD=1193167;{1,193,167Hz}
OCTAVE:
DWORD=2;{octavemultiplier}
PITCH_C:
DWORD=523;{C-523,25Hz}
PITCH_Cs:
DWORD=554;{C#-554,37Hz}
PITCH_D:
DWORD=587;{D-587,33Hz}
PITCH_Ds:
DWORD=622;{D#-622,25Hz}
PITCH_E:
DWORD=659;{E-659,25Hz}
PITCH_F:
DWORD=698;{F-698,46Hz}
PITCH_Fs:
DWORD=740;{F#-739,99Hz}
PITCH_G:
DWORD=784;{G-783,99Hz}
PITCH_Gs:
DWORD=831;{G#-830,61Hz}
PITCH_A:
DWORD=880;{A-880,00Hz}
PITCH_As:
DWORD=988;{B-987,77Hz}
PITCH_H:
DWORD=1047;{H-1046,50Hz}
{Wearegoingtoplayc-majorchord}
DELAY:
DWORD=$18000000;{formy~800mHzbox}
TONE_1:
DWORD=1141;
TONE_2:
DWORD=905;
TONE_3:
DWORD=1568;{forHalMakeBeep}
STATUS_DEVICE_CONFIGURATION_ERROR:
DWORD=$00C0000182;
procedureMakeBeep1(dwPitch:
DWORD);stdcall;assembler;
asm
cli
moval,10110110b
out43h,al
moveax,dwPitch
out42h,al
moval,ah
out42h,al
{TurnspeakerON}
inal,61h
oral,11b
out61h,al
sti
pusheax
moveax,DELAY
@@1:
deceax
jnz@@1
popeax
cli
{TurnspeakerOFF}
inal,61h
andal,11111100b
out61h,al
sti
end;
procedureMakeBeep2(dwPitch:
DWORD);stdcall;
var
dwPort,i:
DWORD;
begin
asm
cli;
end;
WRITE_PORT_UCHAR(PUCHAR($43),$b6);
WRITE_PORT_UCHAR(PUCHAR($42),dwPitchand$FF);
WRITE_PORT_UCHAR(PUCHAR($42),((dwPitchshr8)and$FF));
dwPort:
=READ_PORT_UCHAR(PUCHAR($61));
dwPort:
=dwPortor3;
WRITE_PORT_UCHAR(PUCHAR($61),dwPort);
asm
sti
end;
fori:
=1toDELAYdo
begin
end;
asm
cli
end;
{TurnspeakerOFF}
dwPort:
=READ_PORT_UCHAR(PUCHAR($61));
dwPort:
=dwPortand$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);
fori:
=1toDELAYdo
begin
end;
HalMakeBeep(0);
Result:
=STATUS_DEVICE_CONFIGURATION_ERROR;
end;
end.
代码:
unithal;
interface
uses
Windows;
const
NtHal='hal.dll';
functionHalMakeBeep(Frequency:
ULONG):
BOOLEAN;stdcall;
functionREAD_PORT_UCHAR(Port:
PUCHAR):
UCHAR;stdcall;
procedureWRITE_PORT_UCHAR(Port:
PUCHAR;Value:
UCHAR);stdcall;
implementation
functionHalMakeBeep(Frequency:
ULONG):
BOOLEAN;stdcall;externalNtHalname'_HalMakeBeep';
functionREAD_PORT_UCHAR(Port:
PUCHAR):
UCHAR;stdcall;externalNtHalname'_READ_PORT_UCHAR';
procedureWRITE_PORT_UCHAR(Port:
PUCHAR;Value:
UCHAR);stdcall;externalNtHalname'_WRITE_PORT_UCHAR';
end.
四
前面提到,如果你访问已经被交换出去的内存时IRQL>=DISPATCH_LEVEL会导致系统瘫痪!
但是事情并不绝对,也许它当时不死机,过一会才死呢!
啥时候死?
就是当你的系统将内存交换了出去,并且你访问它的时候!
千万不要太钟爱不分页内存,太浪费资源了!
它总要占用物理内存!
分配的堆使用完后记得一定要释放,你在系统池里分配了内存,无论你的驱动程序发生了什么事情,这些内存不会被回收,除非ExFreePool被调用。
如果你不用ExFreePool显式地释放内存,即使你的驱动程序卸载了,这些内存还驻留在那里。
所以呢,你就乖乖地显式地释放内存吧!
系统内存池分配的内存不会被自动清零,最后的使用者可能会留下垃圾。
所以呢,使用之前,最好统统置零。
你可以很容易地定义你需要的内存类型了,就两种:
分页,不分页。
如果你的代码要访问IRQL>=DISPATCH_LEVEL,不用说你也知道,你必须使用不分页类型。
代码本身,和涉及的数据都要在不分页内存里。
在缺省情况下,驱动程序以不分页内存加载,除非是INIT节区或者名称以"PAGE"开始的节区。
假如你不做任何动作去改变驱动程序的内存属性(例如:
别去调用MmPageEntireDriver使驱动程序的映像分页),你就不用关心驱动程序了,它总是在内存里。
先前的文章中我们讨论了常用的驱动函数(DriverEntry,DriverUnload,DispatchXxx)被调用时所处的IRQL等级。
DDK给了我们关于每一个函数被调用时的IRQL等级的相关信息。
例如:
在后面的文章中我们会使用IoInitializeTimer函数,该函数的描述这样说的:
该函数执行时,时钟事件发生时的等级IRQL=DISPATCH_LEVEL。
这就意味着:
这个函数访问的所有内存都必须是不分页的。
如果你不能确定到底是哪个IRQL,你写程序时候可以这样调用KeGetCurrentIrql:
代码:
IfKeGetCurrentIrql begin {使用任意内存} Endelse begin {只能使用不分页内存} End; 下面让我们来看一个简单驱动程序例子SystemModules,该例子的主要动作集中在DriverEntry函数里。 我们会分配分页内存(你应该记得DriverEntry运行在IRQL=PASSIVE_LEVEL等级,所以使用分页内存自然是没问题了),然后写进一些信息,再释放,并让系统卸载驱动程序。 代码: {---------------------------------------------------------------- 用Delphi编写驱动程序的例子 ------------------------------------------------------------------} unitSystemModules; interface uses nt_status,ntoskrnl,native; function_DriverEntry(pDriverObject: PDRIVER_OBJECT;pusRegistryPath: PUNICODE_STRING): NTSTATUS;stdcall; implementation function_DriverEntry(pDriverObject: PDRIVER_OBJECT;pusRegistryPath: PUNICODE_STRING): NTSTATUS; var cb: DWORD; p,pTemp: PVOID; dwNumModules: DWORD; pMessage,pModuleName: PCHAR; buffer: array[0..295]ofchar; szModuleName: array[0..100]ofchar; iCnt,iPos: integer; begin DbgPrint('SystemModules: EnteringDriverEntry'); cb: =0; ZwQuerySystemInformation(SystemModuleInformation,@p,0,cb); ifcb<>0then begin p: =ExAllocatePool(PagedPool,cb); ifp<>nilthen begin DbgPrint('SystemModules: %ubytesofpagedmemoryalloctedataddress%08X',cb,p); ifZwQuerySystemInformation(SystemModuleInformation, p,cb,cb)=STATUS_SUCCESSthen begin pTemp: =p; dwNumModules: =DWORD(p^); cb: =(sizeof(SYSTEM_MODULE_INFORMATION)+100)*2; pMessage: =ExAllocatePool(PagedPool,cb); ifpMessage<>nilthen begin DbgPrint('SystemModules: %ubytesofpagedmemoryalloctedataddress%08X',cb,pMessage); memset(pMessage,0,cb); inc(PCHAR(pTemp),sizeof(DWORD)); foriCnt: =1todwNumModulesdo begin iPos: =(PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset; pModuleName: =@((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]); if(_strnicmp(pModuleName,'ntoskrnl.exe',length('ntoskrnl.exe'))=0)or (_strnicmp(pModuleName,'ntice.sys',length('ntice.sys'))=0)then begin memset(@szModuleName,0,sizeof(szModuleName)); strcpy(@szModuleName,pModuleName); _snprintf(@buffer,sizeof(buffer), 'SystemModules: Found%sbase: %08Xsize: %08X', @szModuleName, (PSYSTEM_MODULE_INFORMATION(pTemp))^.Base, (PSYSTEM_MODULE_INFORMATION(pTemp))^._Size); strcat(pMessage,@buffer); end; inc(PCHAR(pTemp),sizeof(SYSTEM_MODULE_INFORMATION)); end; ifpMessage[0]<>#0then begin DbgPrint(pMessage); endelse begin DbgPrint('SystemModules: Foundneitherntoskrnlnorntice'); end; ExFreePool(pMessage); DbgPrint('SystemModules: Memoryataddress%08Xreleased',pMessage); end; end; ExFreePool(p); DbgPrint('SystemModules: Memoryataddress%08Xreleased',p); end; end; DbgPrint('SystemModules: LeavingDriverEntry'); result: =STATUS_DEVICE_CONFIGURATION_ERROR; end; end. 为了写点有用的信息,我们加载了一些模块到系统地址空间(包括以下系统模块: ntoskrnl.exe,hal.dll等,还有设备驱动程序),然后去找ntoskrnl.exe和ntice.sys。 我们用SystemModuleInformation信息类作为参数调用ZwQuerySystemInformation来得到系统模块列表。 读者可以到GarryNebbett的书《winodwsNT/2000内部API参考》去找这些函数的描述。 顺便说一下,ZwQuerySystemInformation是一个独特的函数,它返回大量的系统信息。 这个例子中没有提供驱动控制程序。 你可以使用KmdKit4D工具包中的KmdManager或者类似的工具,还可以使用DebugView或SoftICE控制台来查看调试信息。 现在我们来分析分析这段程序吧…… 代码: cb: =0; ZwQuerySystemInformation(SystemModuleInformation,@p,0,cb); 首先我们要决定我们要使用多少空间。 上面调用对ZwQuerySystemInformation的调用使我们获得STATUS_INFO_LENGTH_MISMATCH(这是正常,因为buffer的尺寸为零。 ),但是cb变量接收到了buffer尺寸(为零或非零)。 于是我们就得到了需要的buffer尺寸。 这里需要地址p是为了ZwQuerySystemInformation函数的正常执行。 代码: ifcb<>0then begin p: =ExAllocatePool(PagedPool,cb); ExAllocatePool从分页内存池分配需要数量的内存。 如果是不分页内存呢,就把第一个参数PagedPool相应地改成NonPagedPool。 ExAllocatePool比用户模式的HeapAlloc简单一些,只有两个参数: 第一个参数是内存池类型(分页、不分页),第二个参数是需要的内存尺寸。 简单吧! 代码: ifp<>nilthen 如果ExAllocatePool返回非零值,那么它就是一个指向分配buffer的指针。 检查调试信息会发现ExAllocatePool分配的buffer地址是页尺寸大小的倍数。 假如请求的内存的大小大于或等于(>=)页尺寸(我们这个例子中,是明显地大了),分配的内存会从页边界开始分配。 代码: ifZwQuerySystemInformation(SystemModuleInformation,p,cb,cb)=STATUS_SUCCESSthen begin pTemp: =p; 我们再次调用ZwQuerySystemInformation,这次使用buffer的指针和尺寸作为参数。 如果返回的是STATUS_SUCCESS,那么buffer之中就包含了系统模块列表,数据以SYSTEM_MODULE_INFORMATION(在native.dcu中定义)结构队列的形式存在。 代码: SYSTEM_MODULE_INFORMATION=packedrecord Reserved: arr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- delphi 开发 驱动程序
![提示](https://static.bdocx.com/images/bang_tan.gif)