04 cores - drive object


Drive object

Driver: is a .sysmodule,

Drive object: it is .sysbeing loaded into the kernel instances of objects out, for indicating the drive module.

Windows kernel using the DRIVER_OBJECTstructure to describe a drive object.

Although the Windows kernel source code written in C, but also the use of object-oriented thinking. In object-oriented thinking, there is the parent class, abstract class, the concept of a pure virtual function, pure virtual function is a class without defining the virtual function. this function has the class is called abstract classes, subclasses an abstract class must implement the pure virtual functions.

In DRIVER_OBJECTthis structural body, which corresponds to a class. There are several fields in the class is a function pointer, the function pointer can be stored certain functions of the system calls.

Drive object is similar to a class object, the object class is directly operated by the operating system kernel frame, when a drive object to be loaded, the system calls DriverEntrya function to construct the object, the first parameter is a function DRIVER_OBJECT*pointer structure, this structure is similar to the pointer thispointer (C language without this pointer, just analogy here), in this function, the need to write code to initialize the necessary drive object field

  • PDRIVER_INITIALIZE DriverInit- driving object initialization function pointer, the function pointer is initialized to I / O manager, pointing DriverEntryfunction

  • PDRIVER_STARTIO DriverStartIo - driving the object handling function pointer for I / O, this function pointer may be set toNULL

  • PDRIVER_UNLOAD DriverUnload- Uninstall function pointer drive object, when the drive is unloaded will be system calls this function pointer must be assigned , or when the drive is unloaded, the field is NULL cause system blue screen

  • PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]- sending the drive object array of function pointers in the array, different subscripts holds a different function, the following table lists the subscript of each element (macro) stored function pointers ( function pointer in the array can be set to NULL ):

Name
calling the timing of
the trigger API calls

IRP_MJ_CLEANUP
When the present driving device object handle to the object is closed (and the reference count is 0), this function is called, but still I / O request has not been completed, can be cleaned outstanding I / O requests in this function.
CloseHandle

IRP_MJ_CLOSE
When the present driving device object handle to the object is closed (and the reference count is 0), and I / O request has been completed or has been abolished, this function is called. This function is equivalent to the device object destructor
CloseHandle

IRP_MJ_CREATE
When the device object of the drive object is opened (by CreateFile/ ZwCreateFile), this function is called. This function is equivalent to the device object constructor
CreateFile

IRP_MJ_DEVICE_CONTROL
Control device for reading / writing device objects
DeviceIoControl

IRP_MJ_FILE_SYSTEM_CONTROL

IRP_MJ_FLUSH_BUFFERS
Output buffer write input buffer or discarded
FlushFileBuffers

IRP_MJ_INTERNAL_DEVICE_CONTROL

IRP_MJ_PNP

IRP_MJ_POWER
Power Manager requests emanating

IRP_MJ_QUERY_INFORMATION
Get the length of the device object
GetFileSize

IRP_MJ_READ
Read the contents of the device object
ReadFile

IRP_MJ_SET_INFORMATION
Length setting device object

IRP_MJ_SHUTDOWN

IRP_MJ_SYSTEM_CONTROL

IRP_MJ_WRITE
Write data to the device object
WriteFile

These functions are optional, when a user level or kernel level device through symbolic links operating device (open, read, write, close), the function is called.

That is, the previously used user-level API: CreateFile, GetFileSize, ReadFile, WriteFile, CloseHandlethe API operation is a device object in the kernel, a file belonging to the device object.

The same function, capable of operating different object, which is the core C by the language of the polymorphism.

Device Object

Device objects are typically driven out by a (non-plug and play drive) drive can hold various objects dispatch function, however, these dispatch function is called by a general I / O manager when the dispatch function is called, I/Oadditional information Manager will be packaged into a structure and passed to the dispatch function. this general structure is referred to as a IRPstructure and only the object to receive the device I/Omanager I/Orequests (what I / O request? for example, when a device object file is opened ( CreateFileafter), read (device object of this file ReadFile) and write ( WriteFile) is I/Orequested).

But the device can not exist independently of the object, the object apparatus is capable of receiving I/Oa request, but does not address I/Othe dispatch function requests, the process I/Odispatch function requests stored in the drive object.

Thus, in a drive project, there must exist two kinds of objects:

  1. Driving the object, the processing can be saved I/Oto send requested function.

  2. Device object, capable of receiving the I/Orequest.

Further, the drive object can not be accessed by the user-level code, the device may be subject.

Device object although at the kernel level, but created DOSafter the symbolic link, the user layer can pass CreateFileto open the device object. And through ReadFile, WriteFile, DeviceIOControl, GetFileSizefunctions to indirectly call the save dispatch function in the drive object, etc. They FIG belongs to the following relationship:

1566986967836

Device object creation and destruction

  • IoCreateDevice - create a device object

  • IoDeleteDevice - destruction device object

Symbolic link

Is a symbolic link name, \DosDevices\D:\\this is a letter, but can in fact be seen as a symbolic link. Its role is to be able to make use of the API layer issue IO requests, and the ability to specify a device when a request to deal with this issue IO IO request:

When the application layer user issuing an IO request, the object manager by the symbolic link names to find the corresponding device, the object manager is able to resolve the names of symbolic links, in order to determine the destination of the IO request.

A symbolic link is to use the device object, the object is not symbolic link to the default device, no symbolic links device objects can not be used by the code user layer. Can then create a symbolic link to the device object.

In the kernel, there are two kinds of symbolic links:

  • NT device name - Device General format "\Device\自定义设备名", this format is generally the name of the transfer function for IoCreateDevicethe device name given by a desired device name can be used in the kernel, but the user can not use the application layer.

  • DOS device name - equipment General format: "\DosDevices\自定义设备名"This format is generally the user name passed to the function IoCreateSymbolLinkNameparameters, function behind this function is to create a symbolic link of a user layer can be used as an NT device name.

    Symbolic link creation and destruction

  • IoCreateSymbolicLink - NT device name is a link to a DOS device names, DOS device names for user-level programs.

  • IoDeleteSymbolicLink - Delete a DOS device name.

The user opens the device layer

When the drive object creates a device object, and also established a DOS device object symbolic link:

// driver entry function 
NTSTATUS the DriverEntry (the DRIVER_OBJECT * Driver, the UNICODE_STRING * path) {
    driver-> the DriverUnload = NULL;     the UNICODE_STRING ntDeviceName;     the RtlInitUnicodeString (& ntDeviceName, L "\\ \\ dev_test_1 Device");     the DEVICE_OBJECT * Device;     NTSTATUS RET;     // create a device object     ret = IoCreateDevice (driver, / * create a device object for driving the object * /                          0, / * size of the extended data * /                          & ntDeviceName, / * NT device name to the object * /                          FILE_DEVICE_UNKNOWN, / * the type of device objects * /                          0, / ** /                          0, / ** /                          & device / * device object is created * /);     IF (NT_SUCCESS (RET!)) {
















        RET return;
   }     UNICODE_STRING dosDeviceName;     RtlInitUnicodeString (& dosDeviceName, L "\\ \\ dev_test_1 DosDevice");     // create a symbolic link to NT DOS device name.     IoCreateSymbolicLink (& dosDeviceName, & ntDeviceName); }





The above code creates DOS device name: dev_test_1, ( \\DosDevice\\just a prefix) then in three-ring, you can open the DOS device in the following ways:

file = CreateFileW (L "\\\\ \\ dev_test_1.", / * device name * / 
                   GENERIC the READ | GENERIC WRITE, / * authority to operate the equipment open * /
                   0,
                   NULL,
                   OPEN_EXISTING,
                   0,
                   NULL);

IRP dispatch functions and

When the function is actually sending device receives the IO request handler is invoked to IO requests.

E.g:

file = CreateFileW (L "\\\\ \\ dev_test_1.", / * device name * / 
                   GENERIC the READ | GENERIC WRITE, / * authority to operate the equipment open * /
                   0,
                   NULL,
                   OPEN_EXISTING,
                   0,
                   NULL);

The code to open a device object, then save the dispatch function array drive object in MajorFunctionthe first IRP_MJ_CREATEterm will be called, if this element is set to NULL, it will not be invoked, however, CreateFilethe call will fail. This function is called , the system will be CreateFilepassed as a parameter passed to the dispatcher dispatch function but the function prototype (parameter list) but not the parameter:

typedef
NTSTATUS DRIVER_DISPATCH (
   _In_ struct _DEVICE_OBJECT *DeviceObject, /*设备对象*/
   _Inout_ struct _IRP *Irp /*IRP*/
   );

The above code is all dispatch functions of the prototype, only one device object, and IRP structure pointer two parameters. Then the passed parameters in the user layer where the transfer?

In fact, the system has these parameters stored in the IRPand IO_STACK_LOCALTIONstructures.

Therefore, IRP is actually a user to save the parameter passed in the user level. There are more of these parameters, but for different IO requests, have different parameters, and no matter what the IO request, how many arguments, only through this structure to keep them, so the structure is relatively large.

In the MSDN documentation, there are different special presentation IO request, parameters are stored in the IRP structure in which field:

1566987279551

1566987305231

IO_STACK_LOCATION

任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序。 IRP的头部有一个当前IO_STACK_LOCATION的数组索引,同时也有一个指向该IO_STACK_LOCATION的指针。索引是从1开始,没有0。当驱动程序准备向次低层驱动程序传递IRP时可以调用IoCallDriver例程,它其中的一个工作是递减当前IO_STACK_LOCATION的索引,使之与下一层的驱动程序匹配。但该索引不会设置成0,如果设置成0,系统将会崩溃。就是说,最底层的驱动程序不会调用IoCallDriver例程。

这个数组一般是紧随IRP结构体之后. 通过IoGetCurrentIrpStackLocation 函数就能过获取到当前设备的IO栈.

IRP处理过程

IRP处理概览(例子)

下图是在用户层中打开文件时的过程(例如打开D:\\1.txt)

1566987350673

  1. 在用户层通过子系统调用I/O系统服务来打开命名文件

  2. 进入到内核层. 由IO管理器调用对象管理器去查找和解析文件对象的符号链接, 它会调用安全引用监视器来检查子系统是否具有正确的访问权限来打开文件对象.

  3. 如果卷尚未挂载,I/O管理器将暂时挂起打开的请求,并调用一个或多个文件系统,直到其中一个文件系统将文件对象识别为文件系统使用的大容量存储设备之一上存储的对象。当文件系统已安装卷时,I/O管理器恢复请求。

  4. I/O管理器为打开请求分配内存并初始化IRP, 同时也会分配IO_STACK_LOCATION数组。对于驱动程序,打开等同于“创建”请求。

  5. I/O管理器调用文件系统驱动程序,将IRP传递给它。文件系统驱动程序访问其在IRP中的I/O堆栈位置,以确定它必须执行什么操作,检查参数,确定所请求的文件是否在高速缓存中,如果没有,则在IRP中设置下一级驱动程序的I/O堆栈位置。

  6. 两个驱动程序都处理IRP并完成所请求的I/O操作,调用由I/O管理器和其他系统组件提供的内核模式支持例程(在前面的图中未示出)。

  7. 驱动程序将IRP返回到I/O管理器,并在IRP中设置I/O状态块,以指示所请求的操作是否成功或为什么失败。

  8. I/O管理器从IRP获取I/O状态,因此它可以通过受保护的子系统向原始调用者返回状态信息。

  9. 释放已经处理完成的IRP

  10. 如果打开操作成功,I/O管理器将文件对象句柄返回到子系统。如果存在错误,则返回适当的状态给子系统。

在子系统成功打开表示数据文件、设备或卷的文件对象之后,子系统使用返回的句柄在设备I/O操作的后续请求(通常是读、写或设备I/O控制请求)中标识文件对象。为了做出这样的请求,子系统调用I/O系统服务。I/O管理器将这些请求路由到发送给适当驱动程序的IRP。

IRP请求处理的详细过程(例子)

下图是在用户层中打开文件时的过程(例如打开D:\\1.txt)

1566987394266

  1. I/O管理器调用文件系统驱动程序(FSD), 并传递已分配给子系统的读/写请求的IRP。FSD访问在IRP中的I/O栈,以确定它应该执行什么操作。

  2. FSD通过调用I/O函数(IoAllocateIrp)一次或多次来分配额外的IRP,可以将原始请求分解为更小的请求(可能针对多个设备驱动程序)。附加的IRP返回到FSD(就是IRP栈),并为下级驱动器用零填充的I/O堆栈位置。FSD可以自行决定,通过在原始IRP中设置下一级驱动程序的I/O堆栈位置,并将其传递给较低级驱动程序,从而重用原始IRP,而不是像上图所示那样分配额外的IRP。

  3. 对于每个驱动程序分配的IRP,上一图中的FSD调用I/O支持例程来注册FSD提供的完成例程;在完成例程中,FSD可以确定较低驱动程序是否满足请求,并在较低驱动程序具有comp时释放每个驱动程序分配的IRP。让它过去吧。I/O管理器将调用FSD提供的完成例程,无论每个驱动程序分配的IRP是否成功完成、错误状态完成或取消。更高级别的驱动程序负责释放其分配的任何IRP,并为其自身设置低级别驱动程序。I/O管理器释放在所有驱动程序完成之后分配的IRP。

    接下来,FSD调用I/O支持例程(IoGetNextIrpStackLocation)来访问下一级驱动程序的I/O堆栈位置,以便设置对下一级驱动程序的请求。(在前面的图中,下一个较低级别的驱动程序碰巧是最低级别的驱动程序。)FSD然后调用I/O支持例程(IoCallDriver)将该IRP传递给下一个较低级别的驱动程序。

  4. 当用IRP调用它时,最低级别的驱动程序检查它的I/O堆栈位置,以确定应该在目标设备上执行什么操作(由IRP_MJ_XXX函数代码指示)。目标设备由设备对象在其指定的I/O堆栈位置中表示,并与IRP一起传递给驱动程序。最低级别的驱动程序可以假设I/O管理器已经将IRP路由到为IRP_MJ_XXX操作(这里是IRP_MJ_READIRP_MJ_WRITE)定义的驱动程序的入口点,并且较高级别的驱动程序已经检查了请求的其他参数的有效性。

    如果没有更高级别的驱动程序,那么最低级别的驱动程序将检查IRP_MJ_XXX操作的输入参数是否有效。如果是,则驱动程序通常调用I/O支持例程来告诉I/O管理器设备操作正在IRP上挂起,或者对IRP进行排队,或者将其传递到访问目标设备(这里是物理或逻辑设备:磁盘或分区)的另一个驱动程序提供的例程。在磁盘上)。

  5. I/O管理器确定驱动程序是否已经忙于处理目标设备的另一个IRP,如果是,则对IRP排队并返回。否则,I/O管理器将IRP路由到在其设备上启动I/O操作的驱动程序提供的例程。(在此阶段,前一个图中的两个驱动程序和I/O管理器都返回控制)。

  6. 当设备中断时,驱动程序的中断服务例程(ISR)所做的工作与它必须停止设备中断和保存有关操作的必要上下文所做的工作一样多。然后,ISR调用具有IRP的I/O支持例程(IoRequestDpc)来对驱动程序提供的DPC(延迟过程调用)例程进行排队,以比ISR更低的硬件优先级完成所请求的操作。

  7. 当驱动程序的DPC得到控制时,它使用上下文(在ISR调用IoRequestDpc中传递的)来完成I/O操作。DPC调用一个支持例程来取消下一个IRP(如果有的话)的队列,并将该IRP传递给在设备上启动I/O操作的驱动程序提供的例程(参见步骤5)。然后,DPC在IRP的I/O状态块中设置关于刚刚完成的操作的状态,并将其返回到具有IoCompleteRequest的I/O管理器。

  8. I/O管理器将IRP中最低层驱动程序的I/O堆栈位置置零,并用FSD分配的IRP调用文件系统的注册完成例程(参见步骤3)。此完成例程检查I/O状态块,以确定是重试请求还是更新关于原始请求维护的任何内部状态,并释放其驱动程序分配的IRP。文件系统可以收集它发送给低级驱动程序的所有驱动程序分配IRP的状态信息,以便它可以设置I/O状态并完成原始IRP。当文件系统已经完成原始IRP时,I/O管理器将向I/O操作的原始请求者(子系统的本机函数)返回和NTSTATUS值。

IRP处理流程中的IO栈(IO_STACK_COMPLATE)

I/O管理器为每个驱动程序提供一组分层驱动程序,为每个IRP建立一个I/O堆栈位置。每个I/O堆栈位置由 IO_STACK_LOCATION结构组成。

I/O管理器为每个IRP创建I/O栈数组,其中数组元素对应于分层驱动程序链中的每个驱动程序。每个驱动程序都拥有包中的一个栈位置,并调用IoGetCurrentIrpStackLocation以获得关于I/O操作的驱动程序特定信息。

这样的链中的每个驱动程序负责调用IoGetNextIrpStackLocation,然后设置下一个驱动程序的I/O堆栈位置。任何高级驱动程序的I/O堆栈位置也可以用于存储关于操作的上下文,以便驱动程序的IoCompletion例程可以执行其清理操作。

分层驱动程序中的处理IRP图显示了原始IRP中的两个I/O堆栈位置,因为它显示了两个驱动程序,一个文件系统驱动程序和一个大容量存储设备驱动程序。分层驱动程序中的处理IRP中的驱动程序分配IRP图没有创建它们的FSD的堆栈位置。为低级驱动程序分配IRP的任何高级驱动程序还根据下一级驱动程序的设备对象的StackSize值确定新IRP应该具有多少I/O堆栈位置。

下图更详细地显示了IRP的内容。

1566987412372

IRP处理流程总结

  1. IRP被IO管理器所创建

  2. 一个IRP被发出来,可以被多个驱动对象所处理, 每个驱动对象都能做出不同的处理.

    IO管理器创建IRP时, 会找出所有能够处理此IRP的设备对象, 并为每一个设备对象建立一个IRP栈元素,(一个驱动对象所创建的设备可以挂载到另一个驱动的设备对象链中,IoAttatchDeviceToDeviceStack)

    于是便有了IRP栈,

    通过函数IoGetCurrentIrpStackLocation能够获取到本驱动的IRP栈,

  3. 一个IRP被处理完成之后, 使用IoCompleteRequest来设置IRP的完成状态, 设置时,主要设置以下内容

    1. Irp.IoStatus.Status - 将完成的状态设置到此字段(成功了失败了, 总之得有一个状态码) , 这个状态码可以使用STATUS_XXXX这系列的宏.

    2. Irp.IoStatus.Information - 设置完成的字节数(如读取了多少字节, 写入了多少字节等等)

IRQL

IRQL即 :中断请求级别(Interrupt ReQuest Level,IRQL) .

内核实际就是一个进程(ntoskrnl.exe) , 里面有非常多的全局变量, 而且有大量的线程在运行. 当几个线程同时操作一个全局变量时, 就会出现问题, 为了解决这个问题, 微软这才提出了IRQL的概念. 这套东西主要是为了保证代码执行的优先级,原子级的, 它分为以下级别

  • Dispatch:所有运行在Dispatch级的代码都是会被进行原子操作的,且不能访问分页内存,也就是说操作系统中在一个时间内只能运行一段Dispatch级的代码,且必须将其完全执行完毕后才会发生线程切换

  • APC :比Dispatch低的一个级别,可以访问分页内存

  • Passive : 的优先级,大多数代码所运行的级别

MDL

typedef struct _MDL {
   struct _MDL *Next; //用于挂入到一个队列中,如插入到驱动程序的IRP的MDL队列中。
   CSHORT Size;//指定这个MDL所占的空间大小=MDL结构体的大小+sizeof(PFN_NUMBER)*映射需要的页面数。
   CSHORT MdlFlags; //指明MDL的映射方式
   struct _EPROCESS *Process; //指明此MDL属于哪个进程。
   PVOID MappedSystemVa; //所描述的内存如果有映射到系统空间并锁定。那么这个成员指定了MDL在系统空间内的地址
   PVOID StartVa; //所描述的内存映射后的虚拟地址的开始页面地址,这个地址总是页面对齐的地址
   ULONG ByteCount; //此MDL所描述的内存块有多少个字节
   ULONG ByteOffset; //MDL映射的虚拟地址的首地址在StartVa页面中的偏移值。
} MDL, *PMDL;

一个连续的虚拟内存地址范围可能是由多个分布(spread over)在不相邻的物理页所组成的。系统使用MDL(内存描述符表)结构体来表明虚拟内存缓冲区的物理页面布局。我们应该避免直接访问MDL。我们可以使用MS-Windows提供的宏,他们提供了对这个结构体基本 的访问。

  • MmGetMdlVirtualAddress 获取缓冲区的虚拟内存地址 ·

  • MmGetMdlByteCount 获取缓冲区的大小(字节数)

  • MmGetMdlByteOffset 获取缓冲区开端的物理页的大小(字节数)

  • MmGetMdlPfnArray 获取记录物理页码的一个数组指针。

我们可以用IoAllocateMdl函数来分配一个MDL。如果要取消分配,可是使用IoFreeMdl函数。或者,可以使用MmInitializeMdl来把一个之前定义的缓冲区定制成一个MDL。但是以上两种方式都不能初始化物理页码数组。

对于在非分页池中分配的缓冲区,可以用MmBuidlMdlForNonpagedPool函数来初始化页码数组。对于可分页的内存,虚拟内存和物理内存之间的联系是暂时的,所以MDL的页码数组只在特定的环境和时间段有效,因为很可能其他的程序对它们进行重新分配,为了使其他的程序无法对他们进行修改和重新分配(在我们释放之前),我们就需要把这段内存锁定,防止其他程序修改,我们可以用MmProbeAndLockPages来实现,这个函数同时还为当前的布局初始化了页码数组。当我们用MmUnlockPages来释放被锁定的内存时,页码数组也会随之无效。

假如MDL指定的是映射一块内核级别的虚拟地址空间,那么我们要用MmGetSystemAddressForMdlSafe ,这样我们就能防止映射目标是来自用户模式的空间,而来自用户模式空间的物理页只能在用户模式上下文环境中使用,并且随时可能被清空。用函数进行申明后,就可以防止以上情况发生了。

总结:MDL就是描述一块虚拟内存的结构体,里面有个成员记录了多个页码,这些页码即处于各个不同物理地址的物理块的页号。

所以要对一块受系统保护的区域进行写操作的话,可以这样来修改它的保护属性: 1.创建一个MDL,显然里面的物理页号数组没有初始化 IoAllocateMdl 2.初始化页码数组,使之成为实际有效的MDL MmBuildMdlForNonPagedPool 3.进行锁定,并且重新赋值新的保护属性为可读 MmProbeAndLockPages 4.获得我们所映射后的实际内存区域的虚拟地址 MmMapLockedPagesSpecifyCache

使用示例

   const wchar_t * pStr = L "123456789abcdefg0 ";. // constant strings, immutable 
   // Create a memory descriptor list
   PMDL mdl = IoAllocateMdl (pStr, 17 * number / byte * /, 0, 0, 0);
   // create a virtual memory page of the memory descriptor list
   MmBuildMdlForNonPagedPool (MDL);    // virtual memory to physical memory to load, modify memory descriptor to be written paging properties, and return to the virtual memory page address    wchar_t * p = (wchar_t * ) MmMapLockedPagesSpecifyCache (MDL, KernelMode or, MmWriteCombined, 0, 0, 0);    P [. 1] = 'a'; // not otherwise modified can now modify the memory.    @ unlock and mapping    MmUnmapLockedPages (p, mdl) ;    // release the memory descriptor list    IoFreeMdl (mdl);








Guess you like

Origin www.cnblogs.com/ltyandy/p/11425709.html
04