在前一个例子SharedSection中,我们共享内存区通讯。这个驱动紧紧关联到用户模式进程的地址空间,也就是驱动所用的虚拟地址在进程空间地址中。这个例子中我们用的这个方法,没有这个缺点,对于驱动来说这个方法更适合。
9.1 SharingMemory
驱动的源码
首先,驱动的功能。
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; SharingMemory - How to share memory between kernel-mode driver and its user-mode client
;
; This method is applicable only for highest-level or monolithic driver
; because of while processing IRP such driver's type is in the context
; of the requested user process which address space driver maps the memory buffer into.
;
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N C L U D E F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include /masm32/include/w2k/ntstatus.inc
include /masm32/include/w2k/ntddk.inc
include /masm32/include/w2k/ntoskrnl.inc
include /masm32/include/w2k/hal.inc
includelib /masm32/lib/w2k/ntoskrnl.lib
includelib /masm32/lib/w2k/hal.lib
include /masm32/Macros/Strings.mac
include ../common.inc
include seh0.inc
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O N S T A N T S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.const
CCOUNTED_UNICODE_STRING "//Device//SharingMemory", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "//DosDevices//SharingMemory", g_usSymbolicLinkName, 4
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; U N I N I T I A L I Z E D D A T A
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.data?
g_pSharedMemory PVOID ?
g_pMdl PVOID ?
g_pUserAddress PVOID ?
g_fTimerStarted BOOL ?
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; N O N D I S C A R D A B L E C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; UpdateTime
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
UpdateTime proc
; This routine is called from TimerRoutine at IRQL DISPATCH_LEVEL !
; The routine itself and all memory it touches must be in nonpaged memory.
; The memory pointed by g_pSharedMemory and the driver's code (except INIT or PAGED sections)
; is in nonpaged memory. KeQuerySystemTime and ExSystemTimeToLocalTime can be called at any IRQL.
; So, no problem here.
local SysTime:LARGE_INTEGER
invoke KeQuerySystemTime, addr SysTime
invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory
ret
UpdateTime endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; TimerRoutine
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
TimerRoutine proc pDeviceObject:PDEVICE_OBJECT, pContext:PVOID
; This routine is called at IRQL DISPATCH_LEVEL !
invoke UpdateTime
ret
TimerRoutine endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Cleanup
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Cleanup proc pDeviceObject:PDEVICE_OBJECT
.if g_fTimerStarted
invoke IoStopTimer, pDeviceObject
invoke DbgPrint, $CTA0("SharingMemory: Timer stopped/n")
.endif
.if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
; If the call to MmMapLockedPages or MmMapLockedPagesSpecifyCache specified user mode,
; the caller must be in the context of the original process before calling MmUnmapLockedPages.
; Cleanup routine is called either from DispatchCleanup or DispatchControl.
; So we always in appropriate process context.
invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X unmapped/n"), g_pUserAddress
and g_pUserAddress, NULL
.endif
.if g_pMdl != NULL
invoke IoFreeMdl, g_pMdl
invoke DbgPrint, $CTA0("SharingMemory: MDL at address %08X freed/n"), g_pMdl
and g_pMdl, NULL
.endif
.if g_pSharedMemory != NULL
invoke ExFreePool, g_pSharedMemory
invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X released/n"), g_pSharedMemory
and g_pSharedMemory, NULL
.endif
ret
Cleanup endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DispatchCleanup
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DispatchCleanup proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
; We MUST unmap the memory mapped into the user process before it exits
; It's better to do it as early as possible.
; The driver recieves IRP_MJ_CLEANUP while user mode app just calls CloseHandle.
invoke DbgPrint, $CTA0("/nSharingMemory: Entering DispatchCleanup/n")
invoke Cleanup, pDeviceObject
mov eax, pIrp
mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
and (_IRP PTR [eax]).IoStatus.Information, 0
fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT
invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchCleanup/n")
mov eax, STATUS_SUCCESS
ret
DispatchCleanup endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DispatchCreateClose
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
mov eax, pIrp
mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
and (_IRP PTR [eax]).IoStatus.Information, 0
fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT
mov eax, STATUS_SUCCESS
ret
DispatchCreateClose endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DispatchControl
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DispatchControl proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
local dwContext:DWORD
invoke DbgPrint, $CTA0("/nSharingMemory: Entering DispatchControl/n")
mov esi, pIrp
assume esi:ptr _IRP
mov [esi].IoStatus.Status, STATUS_UNSUCCESSFUL
and [esi].IoStatus.Information, 0
IoGetCurrentIrpStackLocation esi
mov edi, eax
assume edi:ptr IO_STACK_LOCATION
.if [edi].Parameters.DeviceIoControl.IoControlCode
== IOCTL_GIVE_ME_YOUR_MEMORY
.if [edi].Parameters.DeviceIoControl.OutputBufferLength >= sizeof PVOID
invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
.if eax != NULL
mov g_pSharedMemory, eax
invoke DbgPrint, /
$CTA0("SharingMemory: %X bytes of nonpaged memory allocated at address %08X/n"), /
PAGE_SIZE, g_pSharedMemory
; The memory g_pSharedMemory points to contains garbage
; because of the memory allocated in kernel doesn't zeroed out
; So, if you want to do some string operations in such buffer
; it may be better to fill it with the zeroes before.
; In this example it's not required
invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
.if eax != NULL
mov g_pMdl, eax
invoke DbgPrint, /
$CTA0("SharingMemory: MDL allocated at address %08X/n"), g_pMdl
invoke MmBuildMdlForNonPagedPool, g_pMdl
; If AccessMode is UserMode and the specified pages cannot be mapped,
; the routine raises an exception. Callers that specify UserMode
; must wrap the call to MmMapLockedPagesSpecifyCache in a try/except block.
_try
; Under NT4 use MmMapLockedPages instead of MmMapLockedPagesSpecifyCache
; invoke MmMapLockedPages, g_pMdl, UserMode
invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, /
NULL, FALSE, NormalPagePriority
.if eax != NULL
mov g_pUserAddress, eax
invoke DbgPrint, /
$CTA0("SharingMemory: Memory mapped into user space at address %08X/n"), g_pUserAddress
mov eax, [esi].AssociatedIrp.SystemBuffer
push g_pUserAddress
pop dword ptr [eax]
invoke UpdateTime
invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext
.if eax == STATUS_SUCCESS
; Our TimerRoutine routine will be called once per second.
invoke IoStartTimer, pDeviceObject
inc g_fTimerStarted
invoke DbgPrint, $CTA0("SharingMemory: Timer started/n")
mov [esi].IoStatus.Information, sizeof PVOID
mov [esi].IoStatus.Status, STATUS_SUCCESS
.endif
.endif
_finally
.endif
.endif
.else
mov [esi].IoStatus.Status, STATUS_BUFFER_TOO_SMALL
.endif
.else
mov [esi].IoStatus.Status, STATUS_INVALID_DEVICE_REQUEST
.endif
assume edi:nothing
; If something went wrong do cleanup
.if [esi].IoStatus.Status != STATUS_SUCCESS
invoke DbgPrint, $CTA0("SharingMemory: Something went wrong/:/n")
invoke Cleanup, pDeviceObject
.endif
fastcall IofCompleteRequest, esi, IO_NO_INCREMENT
invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchControl/n")
mov eax, [esi].IoStatus.Status
assume esi:nothing
ret
DispatchControl endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DriverUnload
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverUnload proc pDriverObject:PDRIVER_OBJECT
invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
mov eax, pDriverObject
invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject
ret
DriverUnload endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; D I S C A R D A B L E C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code INIT
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
local status:NTSTATUS
local pDeviceObject:PDEVICE_OBJECT
mov status, STATUS_DEVICE_CONFIGURATION_ERROR
; Explicity initialize global variables
and g_pSharedMemory, NULL
and g_pMdl, NULL
and g_pUserAddress, NULL
and g_fTimerStarted, FALSE
; Create exclusive device
invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, addr pDeviceObject
.if eax == STATUS_SUCCESS
invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
.if eax == STATUS_SUCCESS
mov eax, pDriverObject
assume eax:ptr DRIVER_OBJECT
mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)], offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)], offset DispatchCleanup
mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],
offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)], offset DispatchControl
mov [eax].DriverUnload, offset DriverUnload
assume eax:nothing
mov status, STATUS_SUCCESS
.else
invoke IoDeleteDevice, pDeviceObject
.endif
.endif
mov eax, status
ret
DriverEntry endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end DriverEntry
:make
set drv=SharingMemory
/masm32/bin/ml /nologo /c /coff %drv%.bat
/masm32/bin/link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj
del
%drv%.obj
move %drv%.sys ..
echo.
Pause
9.1.1
DriverEntry
例程
mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)], offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)], offset DispatchCleanup
mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)], offset DispatchCreateClose
mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)], offset DispatchControl
除了通常的请求
IRP_MJ_CREATE, IRP_MJ_CLOSE IRP_MJ_DEVICE_CONTROL
外,还处理
IRP_MJ_CLEANUP
请求。当一个用户模式代码调用
CloseHandle
,驱动初始化例程发送
IRP_MJ_CLEANUP
请求,表明驱动将要关闭。然后收到
IRP_MJ_CLOSE
请求,驱动才真正关闭。在这个例子中,为了尽可能快的释放资源,因此发送
IRP_MJ_CLEANUP
并处理。
9.1.2
DispatchControl
例程
invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
.if eax != NULL
mov g_pSharedMemory, eax
收到
IOCTL_GIVE_ME_YOUR_MEMORY
控制代码,分配一页大小的内存,当请求收到的时候驱动会把这一页空间映射到驱动管理程序地址空间。为什么必须使用非分页内存而且这页内存在用户模式进程地址空间中是可见的。
不管驱动的当前环境
ExAllocatePool
返回系统范围内存地址,可以在进程地址空间范围内访问共享的内存。当驱动处理
IRP_MJ_DEVICE_CONTROL
请求在驱动管理程序的地址空间中。在映射合适的内存页前需要
MDL
(
Memory Descriptor List
。我不知道如何把他翻译为俄语。注:原作者是俄国人)。
9.1.3
Memory Descriptor List
MDL
是个结构用来描述物理内存页区域。
MDL STRUCT
Next PVOID ?
_Size SWORD ?
MdlFlags SWORD ?
Process PVOID ?
MappedSystemVa PVOID ?
StartVa PVOID ?
ByteCount DWORD ?
ByteOffset DWORD ?
MDL ENDS
PMDL typedef PTR MDL
特别指出
MDL
结构的头部是指向内存页数组的双字,每一个代表物理内存页的编号(
page frame number, PFN
)。
MDL
描述的虚拟地址空间是连续的而他所占的物理页是随机分布的。这就是为什么所需要的页数组包含在整个物理内存页区域的页组成的链表。还需要组成直接内存访问(
Direct Memory Access, DMA
),因此,
MDL
包含所有内存控制必须的信息。在这个例子中只有一页。
invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
.if eax != NULL
mov g_pMdl, eax
前两个参数分别标识
IoAllocateMdl
函数分配的虚拟地址和想要创建
MDL
内存块的大小。如果没有涉及到
IRP MDL
(这个例子中就是这样),第三个参数是
FALSE
,第四个参数说明是否必须降低进程限额并且驱动出于驱动链的顶层,或者出于中间层(这个例子中是这样)。系统为每个进程分配限额资源。当一个进程分配一个资源,限额降低。如果限额为零,相关的资源不在有效。我们不想让进程分配内存是降低限额,因此第四个参数的值为
FALSE
。最后一个参数标识与
MDL
相关的
IRP
请求指针。例如
I/O
控制器为客户缓冲创建
MDL
并且地址发送到
IRP.MdlAddress
。我们没有用到
I/O
操作控制
MDL
,所以
IRP
指针为空,最后一个参数的值为
NULL
。
IoAllocateMdl
函数为
MDL
分配内存并且初始化他的标题。
invoke MmBuildMdlForNonPagedPool, g_pMdl
MmBuildMdlForNonPagedPool
函数填充一组空的物理页并且跟新
MDL
头部。
_try
如果在用户模式调用
MmMapLockedPagesSpecifyCache
函数失败会发生系统异常(
DDK
中有明确的解释),通过
SHE
来处理这个异常。
invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, /
NULL, FALSE, NormalPagePriority
映射内存,在用户驱动管理程序中描述
MDL
。第一个参数标识内存映射
MDL
的区域。第二个参数说明内存应用在用户模式还是内核模式。第三个参数标识内存缓冲的类型。如果第四个参数为空系统会自动确定一个虚拟地址在用户地址空间中。第五个参数定义当系统突然不能对请求进行安全处理是是否产生蓝屏(
BSOD
),只有在第二个参数选折内核模式才行。然而这个参数为
FALSE
,因为我们不想在任何情况下破坏系统。最后一个参数说明MmMapLockedPagesSpecifyCache函数成功返回的重要性。
在Windows NT4中没有
MmMapLockedPagesSpecifyCache
函数,而是用MmMapLockedPages函数代替。
invoke MmMapLockedPages, g_pMdl, UserMode
MmMapLockedPages函数在后面版本的windows中很少用,被这个MmMapLockedPagesSpecifyCache函数取代。但是后面四个参数是不可用的。根据MDL的帮助文件地址空间规定映射仅一个块,在非分页内存中(到现在我还不知道如何利用他在分页内存中)。这就是我们为什么需要非分页内存的第一个原因。
映射不能小于一页,因此,我们需要一整页,实际上只用了一些字节。
.if eax != NULL
mov g_pUserAddress, eax
mov eax, [esi].AssociatedIrp.SystemBuffer
push g_pUserAddress
pop dword ptr [eax]
MmMapLockedPagesSpecifyCache
从用户范围内返回地址,打印这个页,传送给驱动管理程序。因此从现在起这页内存变为共享内存。驱动无论在什么进程环境中都可以访问他,而用户进程将提供给他地址。
invoke UpdateTime
UpdateTime
例程将把当前系统时间通知到共享内存上。
UpdateTime proc
local SysTime:LARGE_INTEGER
invoke KeQuerySystemTime, addr SysTime
invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory
ret
UpdateTime endp
KeQuerySystemTime
函数告诉系统时间。
ExSystemTimeToLocalTime
将当前时间转换为当地时间。
invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext
初始化定时器,驱动根据定时器来控制设备。
DEVICE_OBJECT
时钟结构,是一个指针指向
IO_TIMER
结构。
IoInitializeTimer
函数的第一个参数标识设备对象相连的时钟,第二个参数指向一个时钟回调函数,
TimerRoutine
例程将要在共享页面上跟新系统时间,
TimerRoutine
执行在
IRQL = DISPATCH_LEVEL
级别(
DDK
中有清楚的描述)。这就是需要用非分页内存的第二个也是主要的原因。这个函数的最后一个参数指向一个额外数据,是
TimerRoutine
的索引,我们并不需要额外数据所以这个变量是虚构的。
.if eax == STATUS_SUCCESS
invoke IoStartTimer, pDeviceObject
inc g_fTimerStarted
定时器开始,每秒
TimerRoutine
例程会被调用一次,间隔值没变。
.if [esi].IoStatus.Status != STATUS_SUCCESS
invoke Cleanup, pDeviceObject
.endif
如果状态返回错误,释放分配的资源。
9.1.4
Cleanup
例程
Cleanup proc pDeviceObject:PDEVICE_OBJECT
.if g_fTimerStarted
invoke IoStopTimer, pDeviceObject
.endif
.if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
and g_pUserAddress, NULL
.endif
.if g_pMdl != NULL
invoke IoFreeMdl, g_pMdl
and g_pMdl, NULL
.endif
.if g_pSharedMemory != NULL
invoke ExFreePool, g_pSharedMemory
and g_pSharedMemory, NULL
.endif
ret
Cleanup endp
不用做跟多的解释,将会释放所有的资源。在定义的内存映射空间,
MmUnmapLockedPages
函数的翻转操作在特定的进程地址空间中是很合适的。
9
.
2
SharingMemory
驱动管理程序源码
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; SharingMemory.asm
;
; Client of SharingMemory.sys driver
;
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N C L U D E F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
include /masm32/include/advapi32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib
includelib /masm32/lib/advapi32.lib
include /masm32/include/winioctl.inc
include /masm32/Macros/Strings.mac
include ../common.inc
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; E Q U A T E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
IDD_MAIN equ 1000
IDC_TIME equ 1001
IDI_ICON equ 1002
TIMER_ID equ 100
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O N S T A N T S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.const
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N I T I A L I Z E D D A T A
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.data
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; U N I N I T I A L I Z E D D A T A
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.data?
g_hDevice HANDLE ?
g_hInstance HINSTANCE ?
g_hDlg HWND ?
g_pSharedMemory LPVOID ?
g_hSCManager HANDLE ?
g_hService HANDLE ?
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; MyUnhandledExceptionFilter
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
MyUnhandledExceptionFilter proc
; Just cleanup every possible thing
local _ss:SERVICE_STATUS
invoke KillTimer, g_hDlg, TIMER_ID
; The most important thing here is CloseHandle
; If something went wrong we must close device handle
; to let the driver know it should unmap memory.
; The driver should do it before application exits
; otherwise the system may crash!
; So, in driver we unmap memory by processing IRP_MJ_CLEANUP not IRP_MJ_CLOSE
; because of IRP_MJ_CLEANUP is processed before CloseHandle exits.
invoke CloseHandle, g_hDevice
invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
invoke DeleteService, g_hService
invoke CloseServiceHandle, g_hService
invoke CloseServiceHandle, g_hSCManager
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
MyUnhandledExceptionFilter endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; UpdateTime
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
UpdateTime proc
local stime:SYSTEMTIME
local buffer[64]:CHAR
.if g_pSharedMemory != NULL
; It can't be zero but who cares...
invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
movzx eax, stime.wHour
movzx ecx, stime.wMinute
movzx edx, stime.wSecond
invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx
invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
.endif
ret
UpdateTime endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; D I A L O G P R O C E D U R E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DlgProc proc uses esi edi hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
mov eax, uMsg
.if eax == WM_TIMER
invoke UpdateTime
.elseif eax == WM_INITDIALOG
push hDlg
pop g_hDlg
invoke LoadIcon, g_hInstance, IDI_ICON
invoke SendMessage, hDlg, WM_SETICON, ICON_BIG, eax
invoke SetWindowText, hDlg, $CTA0("Kernel Timer")
invoke UpdateTime
invoke SetTimer, hDlg, TIMER_ID, 1000, NULL
.elseif eax == WM_COMMAND
mov eax, wParam
.if ax == IDCANCEL
invoke EndDialog, hDlg, 0
.endif
.elseif eax == WM_DESTROY
invoke KillTimer, hDlg, TIMER_ID
.else
xor eax, eax
ret
.endif
xor eax, eax
inc eax
ret
DlgProc endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; start
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
start proc uses esi edi
local acModulePath[MAX_PATH]:CHAR
local _ss:SERVICE_STATUS
local dwBytesReturned:DWORD
; explicity set for sure
and g_pSharedMemory, NULL
; The very first thing we have to do is to install exception handler
invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
.if eax != NULL
mov g_hSCManager, eax
push eax
invoke GetFullPathName, $CTA0("SharingMemory.sys"), sizeof acModulePath, addr acModulePath, esp
pop eax
invoke CreateService, g_hSCManager, $CTA0("SharingMemory"), $CTA0("Another way how to share memory"), /
SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, /
SERVICE_ERROR_IGNORE, addr acModulePath, NULL, NULL, NULL, NULL, NULL
.if eax != NULL
mov g_hService, eax
invoke StartService, g_hService, 0, NULL
.if eax != 0
invoke CreateFile, $CTA0("////.//SharingMemory"), GENERIC_READ, /
0, NULL, OPEN_EXISTING, 0, NULL
.if eax != INVALID_HANDLE_VALUE
mov g_hDevice, eax ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, /
addr g_pSharedMemory, sizeof g_pSharedMemory, /
addr dwBytesReturned, NULL
.if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )
; Here g_pSharedMemory contains the pointer
; to mapped by the driver memory buffer
invoke GetModuleHandle, NULL
mov g_hInstance, eax
invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0
.else
invoke MessageBox, NULL, $CTA0("Can't send control code to device."), /
NULL, MB_OK + MB_ICONSTOP
.endif
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
invoke CloseHandle, g_hDevice
.else
invoke MessageBox, NULL, $CTA0("Device is not present."), NULL, MB_ICONSTOP
.endif
invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
.else
invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_OK + MB_ICONSTOP
.endif
invoke DeleteService, g_hService
invoke CloseServiceHandle, g_hService
.else
invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_OK + MB_ICONSTOP
.endif
invoke CloseServiceHandle, g_hSCManager
.else
invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), NULL, MB_OK + MB_ICONSTOP
.endif
invoke ExitProcess, 0
start endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end start
:make
set exe=SharingMemory
if exist ../%scp%.exe del ../%scp%.exe
if exist rsrc.obj goto final
/masm32/bin/rc /v rsrc.rc
/masm32/bin/cvtres /machine:ix86 rsrc.res
if errorlevel 0 goto final
pause
exit
:final
if exist rsrc.res del rsrc.res
/masm32/bin/ml /nologo /c /coff %exe%.bat
/masm32/bin/link /nologo /subsystem:windows %exe%.obj rsrc.obj
del
%exe%.obj
move %exe%.exe ..
if exist %exe%.exe del %exe%.exe
echo.
pause
为了处理出现的异常,每个过程都注册自机的
SHE
处理函数。如果没有定义自己的异常处理函数系统会默认弹出一个错误对话框并调用调试器。调用这个
SetUnhandledExceptionFilter
函数用自己的异常处理函数取代系统默认的异常处理。
invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
因此,实际上在这个例子中如果出现任何资源异常将会执行释放资源,稍后看
MyUnhandledExceptionFilter
异常处理函数。
invoke DeviceIoControl, g_hDevice,
IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, /
addr g_pSharedMemory, sizeof g_pSharedMemory, /
addr dwBytesReturned, NULL
如果传给驱动
IOCTL_GIVE_ME_YOUR_MEMORY
控制代码正确执行,驱动会返回给用户共享内存地址到变量
g_pSharedMemory
,这个例子中我们不用考虑共享内存大小,因为他明显大于我们的所需要的。前
8
个字节就是驱动每秒跟新的时间。
.if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )
invoke GetModuleHandle, NULL
mov g_hInstance, eax
invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0
创建对话框,
.elseif eax == WM_INITDIALOG
. . .
invoke UpdateTime
invoke SetTimer, hDlg, TIMER_ID, 1000, NULL
在
WM_INITDIALOG
消息中调用
UpdateTime
函数,是为了确保对话框出现后显示的是当前时间,然后设置时钟定时器,每秒触发一次。
.if eax == WM_TIMER
invoke UpdateTime
在
WM_TIMER
消息中调用
UpdateTime
函数跟新时间。
UpdateTime proc
local stime:SYSTEMTIME
local buffer[64]:CHAR
.if g_pSharedMemory != NULL
invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
movzx eax, stime.wHour
movzx ecx, stime.wMinute
movzx edx, stime.wSecond
invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx
invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
.endif
ret
UpdateTime endp
这是时间格式化例程,将当前时间转换为
Hours: Minutes: Second
格式显示。
驱动每隔一秒把当前时间存入共享内存中,涉及到的虚拟地址在系统地址空间中,驱动管理程序每隔一秒从用户模式地址空间中获得时间信息。没有一个共享的物理内存页。
KeQuerySystemTime
函数获得当前系统时间在用户模式和内核模式之间共享,共享物理页在内核模式地址在
0FFDF0000h
,用户模式地址在
7FFE0000h
,用户模式函数
GetSystemTime
和内核模式函数获得的时间是相同的类型。
KUSER_SHARED_DATA
结构的标题显示内核模式和用户模式共享数据。
MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS
local _ss:SERVICE_STATUS
invoke KillTimer, g_hDlg, TIMER_ID
invoke CloseHandle, g_hDevice
invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
invoke DeleteService, g_hService
invoke CloseServiceHandle, g_hService
invoke CloseServiceHandle, g_hSCManager
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
MyUnhandledExceptionFilter endp
如果在程序中任何地方出现异常系统就会调用我们定义的
MyUnhandledExceptionFilter
异常处理函数处理。我们要做的就是释放所有的资源,最重要的是关闭描述符设备。驱动将会依次收到
IRP_MJ_CLEANUP
和
IRP_MJ_CLOSE
控制代码,并执行释放,最重要的是解除用户地址空间中内存映射。实际上,甚至可以不用异常处理,因为当程序崩溃系统会关闭所有的描述符包括设备描述符。当收到
IRP_MJ_CLEANUP
请求系统会尽快的释放资源。在这个例子中当收到
IRP_MJ_CLEANUP
请求就执行这样的操作。在任何情况下,在进程停止前必须调用
MmUnmapLockedPages
函数。
与前一个共享内存区的例子相比,这个例子中有两个流操作共享内存资源,因此需要考虑同步问题。读操作在用户模式中,意味着
IRQL = PASSIVE_LEVEL
,写操作在系统进程中进行并调用
TimerRoutine
例程,并且我们已经调用
IoInitializeTimer
进行初始化。在
IRQL=DISPATCH_LEVEL
级别下调用
TimerRoutine
例程(
DDK
中有清楚说明),在任何情况下运行在空闲进程(
idle process
)中
,
因为他的执行权限低于用户权限,当他从共享页中读数据是不能挂起驱动管理程序,由于当
IRQL = DISPATCH_LEVEL
,用户可以中断将当前时间写入共享内存的操作。因此出问题时单处理器机器定时器不能被唤醒,在多处理器机器上能够同步执行这些操作,因此在这样的情况下必须考虑同步问题。在这个例子中我们并没有考虑,因为这是我们后面的文章中要写的主题。幸运的是大多数环境下对话框会显示出时间。