Kernel Mode Drivers

Part 9: Basic Technique: Working with Memory

Shared memory



In the previous SharedSection example, where we shared a section, the driver was hardcoded to the address context of a particular process, since the virtual address that the driver had was pointing to the address space of this process. The method we are using in this example does not have this drawback. For drivers, this method is much more natural.


9.1 SharingMemory Driver Source Code

First, let's figure out what's going on in the driver.


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


             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

In addition to the usual IRP_MJ_CREATE, IRP_MJ_CLOSE and IRP_MJ_DEVICE_CONTROL requests, we will also handle IRP_MJ_CLEANUP. When the user mode code calls CloseHandle , an IRP_MJ_CLEANUP is first sent to the driver, signaling that the driver's device handle will now be closed. After the handle is actually closed, the driver receives an IRP_MJ_CLOSE. In this example, it is desirable to free resources as early as possible. Therefore, IRP_MJ_CLEANUP processing was required.



9.1.2 DispatchControl procedure


             invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
             .if eax != NULL
                 mov g_pSharedMemory, eax

Having received the control code IOCTL_GIVE_ME_YOUR_MEMORY, we allocate one page of non-paged memory. Why do we need non-swapped memory and why exactly one page will be clear along the way.

The driver will have to map this memory to the address space of the user process in the context of which the request is received, i.e. to the address space of our driver control program.

ExAllocatePool will return an address from the system range, i.e. The driver can access it regardless of the current context. Now we need to map this memory into the address space of the process with which we want to share this memory. Our driver is single-level, so when processing IRP_MJ_DEVICE_CONTROL we are in the address context of our control program. Before we map the allocated memory page into its address space, it is necessary to form an MDL (Memory Descriptor List. I do not know the translation of this term into Russian).



9.1.3 Memory Descriptor List

MDL is represented by the structure of the same name and contains a description of the physical pages of the memory region.


 MDL STRUCT
     Next            PVOID       ?
     _Size           SWORD       ?
     MdlFlags        SWORD       ?
     Process         PVOID       ?
     MappedSystemVa  PVOID       ?
     StartVa         PVOID       ?
     ByteCount       DWORD       ?
     ByteOffset      DWORD       ?
 MDL ENDS
 PMDL typedef PTR MDL

More precisely, the MDL structure is a header. Following the title is an array of Pages double words, each of which is a page frame number (PFN). Although the memory region described by MDL is contiguous in the virtual address space, the physical pages it occupies can be located in physical memory in any order. That is why the Pages array is needed, containing a list of all the physical pages occupied by the memory region. It is also required for organizing Direct Memory Access (DMA). That. The MDL contains all the information the memory manager needs. In our case, there is only one page.


                 invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
                 .if eax != NULL
                     mov g_pMdl, eax

The first two parameters of the IoAllocateMdl functiondetermine the virtual address and the size of the memory block for which the MDL should be generated. If MDL is not associated with an IRP (which is exactly what it is in our case), then the third parameter is FALSE. The fourth parameter determines whether the process quota should be reduced, and is applicable only to drivers located at the highest level in the driver chain or to single-level drivers (this is exactly what our driver is). Each process receives resource quotas from the system. When a process allocates a resource to itself, the quota is reduced. If the quota ends, then the corresponding resource is no longer allocated. We do not want to reduce the process quota for allocated memory, so the fourth parameter will be FALSE. The last parameter specifies an optional pointer to the IRP with which the MDL is associated. For example, for direct I / O, the I / O manager creates an MDL for the user buffer and passes its address to IRP.MdlAddress. We are forming MDL not for I / O operation, so we have no IRP, and the last parameter will be NULL.

That. the IoAllocateMdl function allocates memory for the MDL and initializes its header.


                     invoke MmBuildMdlForNonPagedPool, g_pMdl

MmBuildMdlForNonPagedPool populates an array of physical page numbers and updates some of the MDL header fields.


                     _try

If the function AccessMode MmMapLockedPagesSpecifyCache , we are going to call the equals UserMode and its call to end in failure, the system throws an exception (this is clearly stated in the DDK), which we will be able to handle, putting SEH-frame.


                     invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
                                         NULL, FALSE, NormalPagePriority

We map the memory described by MDL to the address space of our control program.

The first parameter points to an MDL describing the memory region to be mapped. The second parameter determines whether this memory can be accessed from user mode. The third defines the policy for caching this memory by the processor. If the fourth parameter is NULL, then the system will choose the virtual address in user space by itself. The fifth parameter determines whether BSOD will appear if the system suddenly fails to satisfy the request, but only if the second parameter is equal to KernelMode. However, we pass FALSE in this parameter, since we do not want to destroy the system under any circumstances. The last parameter determines how important it is for the call to MmMapLockedPagesSpecifyCache to succeed.

The MmMapLockedPagesSpecifyCache function is not implemented on Windows NT4 . Use MmMapLockedPages instead, like this:


 invoke MmMapLockedPages, g_pMdl, UserMode

MmMapLockedPages is also available in future releases of Windows and is a simple wrapper around MmMapLockedPagesSpecifyCache , but the last four parameters cannot be controlled.

With MDL, only locked ones can be mapped to the user address space, i.e. non-paged memory pages (anyway, I don't know how to do this for paged memory). This is the first reason we needed non-swapped memory.

You cannot display less than one page. Therefore, we need an entire page, although only a few bytes will actually be used.


                     .if eax != NULL
                         mov g_pUserAddress, eax

                         mov eax, [esi].AssociatedIrp.SystemBuffer
                         push g_pUserAddress
                         pop dword ptr [eax]

MmMapLockedPagesSpecifyCache will return the address from the custom range where it rendered our page. We transfer it to the control program. That. from this moment, the memory page becomes shared, and the driver will be able to access it regardless of the current address context, and the user process will refer to it at the address available to it.


                         invoke UpdateTime

For clarity, the UpdateTime procedure will place the current system time on the shared page.


 UpdateTime proc

 local SysTime:LARGE_INTEGER

     invoke KeQuerySystemTime, addr SysTime
     invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory

     ret

 UpdateTime endp

KeQuerySystemTime reports Greenwich Mean Time. ExSystemTimeToLocalTime adjusts it for the time zone.


                         invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext

We initialize a timer that will be associated with a driver-managed device object. For this, the DEVICE_OBJECT structure has a Timer field, which is a pointer to the IO_TIMER structure. The first parameter of the IoInitializeTimer function determines which device object the timer should be associated with. The second is a pointer to a procedure called by the system when the timer expires. The TimerRoutine procedure will update the system time on the shared page by calling UpdateTime . TimerRoutine is executed with IRQL = DISPATCH_LEVEL (this is clearly stated in the DDK). This is the second and main reason why we need non-swapped memory. The last parameter of the IoInitializeTimer function- pointer to arbitrary data. This pointer will be passed to TimerRoutine . We don't need any additional data, so we just have dwContext as a dummy variable.


                         .if eax == STATUS_SUCCESS

                             invoke IoStartTimer, pDeviceObject
                             inc g_fTimerStarted

We start the timer. The TimerRoutine procedure will now be called approximately once a second. This interval cannot be changed.


     .if [esi].IoStatus.Status != STATUS_SUCCESS
         invoke Cleanup, pDeviceObject
     .endif

If at one of the previous stages there were problems, we clean up the resources.



9.1.4 Cleanup Procedure


 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

Everything should be clear here without explanation. The only subtlety is that the memory mapping into user space and the reverse operation performed using the MmUnmapLockedPages function must take place in the address context of a certain process, which is quite natural.



9.2 SharingMemory driver control source code


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

Each thread in user processes is placed by the system in an SEH frame in order to handle any exceptions that occur in this thread. If the thread does not install additional exception handlers, then when an exception occurs, the system handler calls the famous dialog and connects the debugger, if any. By calling the SetUnhandledExceptionFilter function, you can replace the system exception handler with your own.


     invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter

That. without actually setting any SEH frames, we will be able to perform the necessary cleanup of resources in this case when any exception occurs. We 'll look at the MyUnhandledExceptionFilter handler a little later.


                     invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, \
                                 addr g_pSharedMemory, sizeof g_pSharedMemory, \
                                 addr dwBytesReturned, NULL

If the driver has started successfully, we pass it the control code IOCTL_GIVE_ME_YOUR_MEMORY. In response, the driver will return us in the g_pSharedMemory variable the address at which it mapped the shared memory buffer. In this case, we are not interested in its size, since it is obviously more than our needs. The first 8 bytes contain the current time, which is updated by the driver every second.


                     .if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )

                         invoke GetModuleHandle, NULL
                         mov g_hInstance, eax
                         invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0

If everything went well, start the dialogue. Then everything is elementary.


     .elseif eax == WM_INITDIALOG
         . . .
         invoke UpdateTime
         invoke SetTimer, hDlg, TIMER_ID, 1000, NULL

When processing the WM_INITDIALOG message, we call the UpdateTime procedure . This is necessary in order to display the current time immediately after the appearance of the dialog. Then we start a timer that will fire once a second.


     .if eax == WM_TIMER
         invoke UpdateTime

When processing the WM_TIMER message, we also call UpdateTime to update the time.


 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

The task of the UpdateTime procedure is to format and output the current time in the generally accepted format Hours: Minutes: Seconds.

Rice. 9-1. The result of the SharingMemory.exe program

That. once a second, the driver places the current time on the shared page, referring to the virtual address in the system address space, and the control program, also once a second, takes this information, referring to the virtual address in the user address space. But one page of memory is physically shared. That. the clock "ticks" every second. By the way, the function KeQuerySystemTime gets the current time as addressing shared between the kernel and user mode page, which is the kernel mode is projected at 0FFDF0000h, and user at 7FFE0000h mode (user-defined function GetSystemTime read the same bytes as the function of the nucleus KeQuerySystemTime) and is described by the KUSER_SHARED_DATA structure (see ntddk.inc). Even the name of this structure shows that it is shared by the kernel and user mode.


 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

If an exception is thrown at any point in the control program, the system will call our handler, MyUnhandledExceptionFilter . All we can do is release all allocated resources. The most important thing is to close the device handle. Then the driver will receive IRP_MJ_CLEANUP, and then IRP_MJ_CLOSE and also carry out a cleanup, the most important of which is to unmap the memory region from the user address space. In fact, you can even do without an exception handler. If the control program crashes, the system itself will close all open descriptors, including the device descriptor. We disable our shared page when handling IRP_MJ_CLEANUP simply out of a desire to clean up resources as early as possible. In this case, you can do this when processing IRP_MJ_CLOSE. AnywayMmUnmapLockedPages must be called before the user process expires .

Unlike the previous example with a shared section, here we already have two threads accessing a shared memory resource. Those. you need to think about synchronization. The reading stream operates in user mode, which means it is always executed with IRQL = PASSIVE_LEVEL. The writing thread belongs to the system process and executes the TimerRoutine procedure , the address of which we specified in the IoInitializeTimer call . TimerRoutine Procedurecalled by the system with IRQL = DISPATCH_LEVEL (this is clearly written in the DDK) and executed by the idle process thread, in any case, this thread worked in my experiments. Because its priority is lower than that of the user thread, then it cannot interrupt the driver control program while reading data from the shared page. Because with IRQL = DISPATCH_LEVEL, no thread scheduling occurs, the user thread cannot interrupt the system thread at the moment of writing the current time to the shared page. That. on a uniprocessor machine, there should be no problems with synchronization. Simultaneous operation of these threads is possible on a multiprocessor machine. Therefore, in such situations, you need to think about synchronization. In this case, we are not making any efforts in this direction, because this is a topic for one of the following articles. In the event of the most unfavorable circumstances, in one of the seconds, the dialog will display an incorrect time value. In this case, this does not threaten us anymore.

The source code of the driver in the archive .