Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память — Архив WASM.RU
В предыдущем примере SharedSection, где мы совместно использовали раздел, драйвер был жестко привязан к адресному контексту конкретного процесса, т.к. виртуальный адрес имевшийся у драйвера указывал в адресное пространство этого процесса. Метод, который мы используем в этом примере лишен этого недостатка. Для драйверов этот метод намного более естественен.
9.1 Исходный текст драйвера SharingMemory
Сначала разберёмся с тем, что происходит в драйвере.
Код (Text):
;@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 push [esi].IoStatus.Status assume esi:nothing fastcall IofCompleteRequest, esi, IO_NO_INCREMENT invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchControl\n") pop eax 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
Код (Text):
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
Код (Text):
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 представляется одноименной структурой и содержит описание физических страниц региона памяти.
Код (Text):
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 содержит всю необходимую диспетчеру памяти информацию.
Код (Text):
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 и инициализирует его заголовок.
Код (Text):
invoke MmBuildMdlForNonPagedPool, g_pMdlMmBuildMdlForNonPagedPool заполняет массив номеров физических страниц и обновляет некоторые поля заголовка MDL.
Код (Text):
_tryЕсли параметр AccessMode функции MmMapLockedPagesSpecifyCache, которую мы сейчас будем вызывать, равен UserMode и её вызов окончится неудачей, система вбрасывает исключение (это явно указано в DDK), которое мы сможем обработать, поставив SEH-фрейм.
Код (Text):
invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \ NULL, FALSE, NormalPagePriorityОтображаем память, описываемую MDL в адресное пространство нашей программы управления.
Первый параметр указывает на MDL, описывающий регион памяти подлежащий отображению. Второй параметр определяет, можно ли будет обращаться к этой памяти из режима пользователя. Третий определяет политику кэширования этой памяти процессором. Если четвертый параметр равен NULL, то система сама выберет виртуальный адрес в пользовательском пространстве. Пятый параметр определяет, появится ли BSOD, если вдруг система не сможет удовлетворить запрос, но только если второй параметр равен KernelMode. Тем не менее, передаем в этом параметре FALSE, т.к. не хотим рушить систему ни при каких обстоятельствах. Последний параметр определяет насколько важно, чтобы вызов MmMapLockedPagesSpecifyCache прошёл успешно.
В Windows NT4 функция MmMapLockedPagesSpecifyCache не реализована. Вместо неё используйте MmMapLockedPages таким образом:
Код (Text):
invoke MmMapLockedPages, g_pMdl, UserModeMmMapLockedPages имеется и в последующих выпусках Windows и является простой оболочкой вокруг MmMapLockedPagesSpecifyCache, но управлять последними четырьмя параметрами возможности нет.
С помощью MDL в пользовательское адресное пространство можно отобразить только заблокированные, т.е. находящиеся в неподкачиваемой памяти страницы (во всяком случае, я не знаю, как это сделать для подкичиваемой памяти). Это первая причина, по которой нам потребовалась неподкачиваемая память.
Отобразить меньше одной страницы нельзя. Поэтому нам нужна целая страница, хотя реально будет использовано всего несколько байт.
Код (Text):
.if eax != NULL mov g_pUserAddress, eax mov eax, [esi].AssociatedIrp.SystemBuffer push g_pUserAddress pop dword ptr [eax]MmMapLockedPagesSpecifyCache вернет адрес из пользовательского диапазона, по которому она отобразила нашу страницу. Передаем его в программу управления. Т.о. с этого момента страница памяти становится совместно используемой, причём драйвер сможет обращаться к ней независимо от текущего адресного контекста, а пользовательский процесс будет обращаться к ней по доступному ему адресу.
Код (Text):
invoke UpdateTimeДля наглядности процедура UpdateTime будет помещать на разделяемую страницу текущее системное время.
Код (Text):
UpdateTime proc local SysTime:LARGE_INTEGER invoke KeQuerySystemTime, addr SysTime invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory ret UpdateTime endpKeQuerySystemTime сообщает системное время по Гринвичу. ExSystemTimeToLocalTime корректирует его с учётом часового пояса.
Код (Text):
invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContextИнициализируем таймер, который будет ассоциирован с управляемым драйвером объектом "устройство". Для этого в структуре DEVICE_OBJECT имеется поле Timer, являющееся указателем на структуру IO_TIMER. Первый параметр функции IoInitializeTimer определяет, с каким объектом "устройство" надо связать таймер. Второй - указатель на процедуру вызываемую системой при срабатывании таймера. Процедура TimerRoutine будет обновлять системное время на разделяемой странице, вызывая UpdateTime. TimerRoutine выполняется при IRQL = DISPATCH_LEVEL (об этом явно написано в DDK). Это вторая причина, по которой нам требуется неподкачиваемая память. Последний параметр функции IoInitializeTimer - указатель на произвольные данные. Этот указатель будет передан в TimerRoutine. Нам никакие дополнительные данные не нужны, поэтому dwContext у нас просто фиктивная переменная.
Код (Text):
.if eax == STATUS_SUCCESS invoke IoStartTimer, pDeviceObject inc g_fTimerStartedЗапускаем таймер. Теперь процедура TimerRoutine будет вызываться примерно раз в секунду. Изменить этот интервал нельзя.
Код (Text):
.if [esi].IoStatus.Status != STATUS_SUCCESS invoke Cleanup, pDeviceObject .endifЕсли на одном из предыдущих этапов возникли проблемы, проводим очистку ресурсов.
9.1.4 Процедура Cleanup
Код (Text):
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
Код (Text):
;@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 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 ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; 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 можно заменить системный обработчик исключений на свой собственный.
Код (Text):
invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilterТ.о. фактически не устанавливая никаких SEH-фреймов, мы будем иметь возможность провести необходимую в данном случае очистку ресурсов при возникновении любого исключения. На обработчик MyUnhandledExceptionFilter посмотрим чуть позже.
Код (Text):
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 байтах содержится текущее время, которое обновляется драйвером каждую секунду.
Код (Text):
.if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory ) invoke GetModuleHandle, NULL mov g_hInstance, eax invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0Если все прошло удачно, запускаем диалог. Дальше всё элементарно.
Код (Text):
.elseif eax == WM_INITDIALOG . . . invoke UpdateTime invoke SetTimer, hDlg, TIMER_ID, 1000, NULLПри обработке сообщения WM_INITDIALOG вызываем процедуру UpdateTime. Это нужно для того, чтобы сразу после появления диалога отобразить текущее время. Затем запускаем таймер, который будет срабатывать один раз в секунду.
Код (Text):
.if eax == WM_TIMER invoke UpdateTimeПри обработке сообщения WM_TIMER также вызываем UpdateTime для обновления времени.
Код (Text):
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). Даже по названию этой структуры видно, что она разделяется ядром и режимом пользователя.
Код (Text):
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 и также проведет очистку, самой главной из которых является отключение (unmap) региона памяти от пользовательского адресного пространства. На самом деле, можно даже обойтись без обработчика исключений. Если программа управления и рухнет, система сама закроет все открытые описатели, в том числе и описатель устройства. Мы отключаем нашу разделяемую страницу при обработке IRP_MJ_CLEANUP просто из желания сделать очистку ресурсов как можно раньше. В данном случае можно сделать это и при обрабтке IRP_MJ_CLOSE. В любом случае MmUnmapLockedPages должна быть вызвана до того, как пользовательский процесс прекратит своё существование.
В отличие от предыдущего примера с разделяемым разделом, здесь у нас уже два потока обращающихся к разделяемому ресурсу памяти. Т.е. необходимо подумать о синхронизации. Читающий поток работает в режиме пользователя, а значит всегда выполняется при IRQL = PASSIVE_LEVEL. Пишущий поток принадлежит системному процессу и выполняет процедуру TimerRoutine, адрес которой мы определили в вызове IoInitializeTimer. Процедура TimerRoutine вызывается системой при IRQL = DISPATCH_LEVEL (об этом недвусмысленно написано в DDK) и выполняется потоком процесса простоя (idle process), во всяком случае, в моих экспериментах работал именно этот поток. Т.к. его приоритет ниже, чем приоритет пользовательского потока, то он не может прервать программу управления драйвером в момент считывания данных с разделяемой страницы. Т.к. при IRQL = DISPATCH_LEVEL планирования потоков не происходит, пользовательский поток не может прервать системный в момент записи текущего времени на разделяемую страницу. Т.о. на однопроцессорной машине никаких проблем с синхронизацией возникнуть не должно. На многопроцессорной машине возможна одновременная работа этих потоков. Поэтому в подобных ситуациях требуется подумать о синхронизации. В данном случае мы не предпринимаем никаких усилий в этом направлении, т.к. это тема для одной из следующих статей. При стечении самых неблагоприятных обстоятельств в одну из секунд в диалоге отобразится неверное значение времени. В данном случае, больше нам это ничем не грозит.
Исходный код драйвера в архиве.
© Four-F
Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память
Дата публикации 27 ноя 2003