В предыдущем примере SharedSection, где мы совместно использовали раздел, драйвер был жестко привязан к адресному контексту конкретного процесса, т.к. виртуальный адрес имевшийся у драйвера указывал в адресное пространство этого процесса. Метод, который мы используем в этом примере лишен этого недостатка. Для драйверов этот метод намного более естественен.
Сначала разберёмся с тем, что происходит в драйвере.
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; SharingMemory - Пример того, как драйвер может передать в пользовательский процесс
; используемую им область памяти
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; В К Л Ю Ч А Е М Ы Е Ф А Й Л Ы
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
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
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Н Е И З М Е Н Я Е М Ы Е Д А Н Н Ы Е
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.const
CCOUNTED_UNICODE_STRING "\\Device\\SharingMemory", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "\\DosDevices\\SharingMemory", g_usSymbolicLinkName, 4
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е Д А Н Н Ы Е
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.data?
g_pSharedMemory PVOID ?
g_pMdl PVOID ?
g_pUserAddress PVOID ?
g_fTimerStarted BOOL ?
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; К О Д
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; UpdateTime
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
UpdateTime proc
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
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 )
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
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 status:NTSTATUS
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
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
_try
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
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 [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
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; В Ы Г Р У Ж А Е М Ы Й П Р И Н Е О Б Х О Д И М О С Т И К О Д
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code INIT
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
local status:NTSTATUS
local pDeviceObject:PDEVICE_OBJECT
mov status, STATUS_DEVICE_CONFIGURATION_ERROR
and g_pSharedMemory, NULL
and g_pMdl, NULL
and g_pUserAddress, NULL
and g_fTimerStarted, FALSE
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 это заголовок. Сразу за заголовком идет массив двойных слов Pages, каждое из которых представляет собой номер физической страницы (page frame number, PFN). Хотя регион памяти описываемый MDL является непрерывным в виртуальном пространстве адресов, занимаемые им физические страницы могут располагаться в физической памяти в произвольном порядке. Именно поэтому и нужен массив Pages, содержащий список всех физических страниц занимаемых регионом памяти. Также он требуется для организации прямого доступа к памяти (Direct Memory Access, DMA). В нашем случае страница всего одна. Т.о. MDL содержит всю необходимую диспетчеру памяти информацию.
invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
.if eax != NULL
mov g_pMdl, eax
Первые два параметра функции IoAllocateMdl определяют виртуальный адрес и размер блока памяти, для которого надо сформировать MDL. Если MDL не ассоциируется с IRP (именно так и есть в нашем случае), то третий параметр равен FALSE. Четвертый параметр определяет, нужно ли уменьшить квоту процесса, и применим только для драйверов находящихся на самом верхнем уровне в цепочке драйверов или для одноуровневых драйверов (именно таковым наш драйвер и является). Каждый процесс получает от системы квоты на ресурсы. Когда процесс выделяет себе ресурс, квота уменьшается. Если квота кончается, то и соответствующий ресурс больше не выделяется. Мы не хотим уменьшать квоту процесса на выделяемую память, поэтому четвертый параметр будет равен FALSE. Последний параметр определяет необязательный указатель на IRP, с которым ассоциируется MDL. Например, при прямом вводе-выводе диспетчер ввода-вывода создает MDL для пользовательского буфера и передает его адрес в IRP.MdlAddress. Мы формируем MDL не для операции ввода-вывода, поэтому никакого IRP у нас нет, и последний параметр будет равен NULL.
Т.о. функция IoAllocateMdl выделяет память для MDL и инициализирует его заголовок.
invoke MmBuildMdlForNonPagedPool, g_pMdl
MmBuildMdlForNonPagedPool заполняет массив номеров физических страниц и обновляет некоторые поля заголовка MDL.
_try
Если параметр AccessMode функции MmMapLockedPagesSpecifyCache, которую мы сейчас будем вызывать, равен UserMode и её вызов окончится неудачей, система вбрасывает исключение (это явно указано в DDK), которое мы сможем обработать, поставив SEH-фрейм.
invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
NULL, FALSE, NormalPagePriority
Отображаем память, описываемую MDL в адресное пространство нашей программы управления.
Первый параметр указывает на MDL, описывающий регион памяти подлежащий отображению. Второй параметр определяет, можно ли будет обращаться к этой памяти из режима пользователя. Третий определяет политику кэширования этой памяти процессором. Если четвертый параметр равен NULL, то система сама выберет виртуальный адрес в пользовательском пространстве. Пятый параметр определяет, появится ли BSOD, если вдруг система не сможет удовлетворить запрос, но только если второй параметр равен KernelMode. Тем не менее, передаем в этом параметре 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 имеется поле Timer, являющееся указателем на структуру IO_TIMER. Первый параметр функции IoInitializeTimer определяет, с каким объектом "устройство" надо связать таймер. Второй - указатель на процедуру вызываемую системой при срабатывании таймера. Процедура TimerRoutine будет обновлять системное время на разделяемой странице, вызывая UpdateTime. TimerRoutine выполняется при IRQL = DISPATCH_LEVEL (об этом явно написано в DDK). Это вторая причина, по которой нам требуется неподкачиваемая память. Последний параметр функции IoInitializeTimer - указатель на произвольные данные. Этот указатель будет передан в TimerRoutine. Нам никакие дополнительные данные не нужны, поэтому dwContext у нас просто фиктивная переменная.
.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, должны проходить в адресном контексте определенного процесса, что вполне естественно.
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; Клиент драйвера SharingMemory
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; В К Л Ю Ч А Е М Ы Е Ф А Й Л Ы
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
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
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е Д А Н Н Ы Е
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.data?
g_hDevice HANDLE ?
g_hInstance HINSTANCE ?
g_hDlg HWND ?
g_pSharedMemory LPVOID ?
g_hSCManager HANDLE ?
g_hService HANDLE ?
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; К О Д
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; MyUnhandledExceptionFilter
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
MyUnhandledExceptionFilter proc
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
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; 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
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; 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
and g_pSharedMemory, NULL
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 )
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
Каждый поток в пользовательских процессах помещается системой в SEH-фрейм, для того чтобы обрабатывать любые исключения в этом потоке возникающие. Если поток не устанавливает дополнительных обработчиков исключений, то при возникновении исключения системный обработчик вызывает знаменитый диалог и подключает отладчик, при наличии такового. Вызовом функции SetUnhandledExceptionFilter можно заменить системный обработчик исключений на свой собственный.
invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter
Т.о. фактически не устанавливая никаких SEH-фреймов, мы будем иметь возможность провести необходимую в данном случае очистку ресурсов при возникновении любого исключения. На обработчик 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
Задачей процедуры UpdateTime является форматирование и вывод текущего времени в общепринятом формате Часы:Минуты:Секунды.
Рис. 9-1. Результат работы программы SharingMemory.exe
Т.о. раз в секунду драйвер помещает на разделяемую страницу текущее время, обращаясь при этом по виртуальному адресу в системном адресном пространстве, а программа управления, также раз в секунду, забирает эту информацию, обращаясь при этом по виртуальному адресу в пользовательском адресном пространстве. Но физически разделяется одна страница памяти. Т.о. часы "тикают" каждую секунду. Кстати, функция KeQuerySystemTime получает текущее время также обращаясь к разделяемой между ядром и режимом пользователя странице, которая в режиме ядра спроецирована по адресу 0FFDF0000h, а в режиме пользователя по адресу 7FFE0000h (пользовательская функция GetSystemTime читает те же самые байты что и функция ядра KeQuerySystemTime) и описывается структурой KUSER_SHARED_DATA (см. ntddk.inc). Даже по названию этой структуры видно, что она разделяется ядром и режимом пользователя.
MyUnhandledExceptionFilter proc
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 и также проведет очистку, самой главной из которых является отключение (unmap) региона памяти от пользовательского адресного пространства. На самом деле, можно даже обойтись без обработчика исключений. Если программа управления и рухнет, система сама закроет все открытые описатели, в том числе и описатель устройства. Мы отключаем нашу разделяемую страницу при обработке IRP_MJ_CLEANUP просто из желания сделать очистку ресурсов как можно раньше. В данном случае можно сделать это и при обрабтке IRP_MJ_CLOSE. В любом случае MmUnmapLockedPages должна быть вызвана до того, как пользовательский процесс прекратит своё существование.
В отличие от предыдущего примера с разделяемым разделом, здесь у нас уже два потока обращающихся к разделяемому ресурсу памяти. Т.е. необходимо подумать о синхронизации. Читающий поток работает в режиме пользователя, а значит всегда выполняется при IRQL = PASSIVE_LEVEL. Пишущий поток принадлежит системному процессу и выполняет процедуру TimerRoutine, адрес которой мы определили в вызове IoInitializeTimer. Процедура TimerRoutine вызывается системой при IRQL = DISPATCH_LEVEL (об этом недвусмысленно написано в DDK) и выполняется потоком процесса простоя (idle process), во всяком случае, в моих экспериментах работал именно этот поток. Т.к. его приоритет ниже, чем приоритет пользовательского потока, то он не может прервать программу управления драйвером в момент считывания данных с разделяемой страницы. Т.к. при IRQL = DISPATCH_LEVEL планирования потоков не происходит, пользовательский поток не может прервать системный в момент записи текущего времени на разделяемую страницу. Т.о. на однопроцессорной машине никаких проблем с синхронизацией возникнуть не должно. На многопроцессорной машине возможна одновременная работа этих потоков. Поэтому в подобных ситуациях требуется подумать о синхронизации. В данном случае мы не предпринимаем никаких усилий в этом направлении, т.к. это тема для одной из следующих статей. При стечении самых неблагоприятных обстоятельств в одну из секунд в диалоге отобразится неверное значение времени. В данном случае, больше нам это ничем не грозит.
Исходный код драйвера в архиве.
[C] Four-F