在前一個例子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.4Cleanup 例程
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,用戶可以中斷將當前時間寫入共享內存的操作。因此出問題時單處理器機器定時器不能被喚醒,在多處理器機器上能夠同步執行這些操作,因此在這樣的情況下必須考慮同步問題。在這個例子中我們並沒有考慮,因爲這是我們後面的文章中要寫的主題。幸運的是大多數環境下對話框會顯示出時間。