Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память — Архив WASM.RU

Все статьи

Драйверы режима ядра: Часть 9: Базовая техника: Работа с памятью. Разделяемая память — Архив WASM.RU



В предыдущем примере SharedSection, где мы совместно использовали раздел, драйвер был жестко привязан к адресному контексту конкретного процесса, т.к. виртуальный адрес имевшийся у драйвера указывал в адресное пространство этого процесса. Метод, который мы используем в этом примере лишен этого недостатка. Для драйверов этот метод намного более естественен.


9.1 Исходный текст драйвера SharingMemory

Сначала разберёмся с тем, что происходит в драйвере.


 ;@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


             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, должны проходить в адресном контексте определенного процесса, что вполне естественно.



9.2 Исходный текст программы управления драйвером SharingMemory


 ;@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 можно заменить системный обработчик исключений на свой собственный.



     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 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 планирования потоков не происходит, пользовательский поток не может прервать системный в момент записи текущего времени на разделяемую страницу. Т.о. на однопроцессорной машине никаких проблем с синхронизацией возникнуть не должно. На многопроцессорной машине возможна одновременная работа этих потоков. Поэтому в подобных ситуациях требуется подумать о синхронизации. В данном случае мы не предпринимаем никаких усилий в этом направлении, т.к. это тема для одной из следующих статей. При стечении самых неблагоприятных обстоятельств в одну из секунд в диалоге отобразится неверное значение времени. В данном случае, больше нам это ничем не грозит.

Исходный код драйвера в архиве.

2002-2013 (c) wasm.ru