It takes about a year to learn WDM. However, due to the intense research courses and the horizontal pressure on the teaching and research sections of the next phase, I have little time to calm down and read books and read the code. It can be said that it is not as good as fishing for two days. After more than half a year, Walter oney's "Windows Driver Model" had a hard time reading five chapters. After reading a few chapters, he began to invest in practice. In fact, I don't know when to learn it because it will only get increasingly busy in the future. So I wrote my first driver, which is very simple. It is equivalent to the "Hello World" program we just learned when learning C language. The program is simple, but through this process, I have a deeper and more intuitive understanding of what I have learned in the previous book. Many vague concepts have become clearer through this practice. In addition, by writing this program, I am familiar with the overall architecture and programming process of the WDM program. Therefore, this program is a stepping stone. Well, there is so much nonsense. Let's start with the question below. WDM, the Windows Driver Model, is a powerful tool used to develop drivers in a Windows environment. When I was a beginner at WMD, I thought it was a little difficult. Although I can't say I have learned it all now, I finally got into that threshold. In retrospect, I think the main reason is that I know too little about Windows systems. Therefore, to learn about WDM, you must first have a deep understanding of the system structure. At least, you must understand how the system calls the hardware device step by step from the user application program through the driver. The following is a description of the Windows 2000 System Structure in WDM:
In this figure, the "device driver" is where our driver is located. It is not enough to understand this structure. We must also understand the structure between drivers and how they interact. A driver is a layered structure. A hardware device is not managed by only one driver. There are many filter drivers on its associated physical device drivers. The filter device object associated with these filter drivers is the filter device object of the physical device object. Therefore, a user-mode request must pass down through the filter device object on the upper layer to reach the physical device object. This is a bit like the TCP/IP layered structure model. A data packet at the application layer must be transmitted to the physical layer and transmitted to the network through the transmission layer and the network layer. The purpose of designing such a layered model is to facilitate expansion. For example, if you want to add new management operations to a device, you do not need to modify the drivers and filter drivers of your physical devices. Instead, you only need to add new filter device objects and corresponding drivers, add a new operation here. Below, we use a graph to represent this layered structure model: the PDO, fdo, and Fido on the left of this graph refer to the device object. The IRP--IO request package is the request sent by the device object on the previous layer, that is, the information of interaction between them. In addition, in many kernel-mode programming, drivers do not have to be associated with a physical device, it can create only one virtual device object, which is not associated with any actual physical device. In many cases, the purpose of writing a driver is to make your code run in the kernel state of the system. With the necessary knowledge above, let's take a look at how to write a WDM driver. As we did when learning C language, we should first start from the program entry point. In the driver, this entry point is the DriverEntry function (equivalent to the main function in C), which is called when the driver is loaded into the memory. The DriverEntry function has two parameters. The first parameter pdriver_object pdriverobj is the pointer to the driver object corresponding to the driver. In the DriverEntry function, an important task is to set several function pointers of the driver object. In this way, when the device object associated with the driver object receives the upper-layer IRP, the corresponding function will be found through the function pointer set in the driver object for processing: pdriverobj-> driverunload = driverunload; pdriverobj-> majorfunction [irp_mj_create] = pdriverobj-> majorfunction [irp_mj_close] = pdriverobj-> majorfunction [handler] = driverdispatch; in addition, the DriverEntry function has an important task, it is to create a device object and establish a symbolic connection for it. (The adddevice function is used to create a device object in a standard WDM program. This function is located by a function pointer of the driver object. In such a standardized WDM program, once new hardware is added, the system will automatically find the adddevice function through the function pointer of the driver object and call it to create the device object. However, I am not writing a driver for an existing hardware, but writing a program in kernel mode, therefore, you only need to create a device object in the DriverEntry function .) Iocreatedevice (pdriverobj, 0, & devicename, file_device_unknown, file_device_secure_open, true, & pdeviceobj); // create a device object iocreatesymboliclink (& linkname, & devicename ); // establish a symbolic connection from the above parameters that call the iocreatedevice function, you can also see that the device object is associated with the driver object, which can explain why the device receives the IRP, the corresponding processing functions are located by the function pointer in the driver object. To establish a symbolic connection, you can call a device object in user mode. As you can see from the function pointer in the previously set driver object, there are two main functions: the unload function driverunload and the dispatch function driverdispatch. The driverunload function should be easy to understand. It is called when the driver is detached from the memory, mainly to release the memory. In my program, all IRPs are processed in the same function. This is the dispatch function driverdispatch (in fact, many WDM programs do this ). The two functions are described below. The main task of the driverunload function is to delete the created device object and symbol connection. Of course, if other memory needs to be released in the program, this is also done here. Iodeletesymboliclink (& linkname); iodeletedevice (pdriverobj-> deviceobject); the dispatch function driverdispatch is mainly used to process the upper-layer IRPs. Here, we need to mention that Each IRP is associated with two data structures, namely, the IRP itself and the IRP Stack -- io_stack_location structure. These two structures contain the information that all the upper layers pass to the device objects of the current layer. The most important information is that the io_stack_location structure contains the IRP Function Code majorfunction and minorfunction (the IRP Function Code identifies the request specific to the IRP, for example, the majorfunction value of the read request is irp_mj_read ). The processing process of the driverdispatch function is generally as follows: First, obtain the IPR stack through the IRP; then, obtain the primary function code majorfunction of the IRP from the IRP stack, determine the primary function code, and perform corresponding processing; after the request is processed, complete the request based on the actual situation or pass the IRP to the next layer of Device objects. It is easy to obtain the IRP stack. You only need to call the iogetcurrentirpstacklocation function: pio_stack_location pirpstack = iogetcurrentirpstacklocation (pirp ); this step is generally implemented by a switch-case statement: Switch (pirpstack-> majorfunction) {Case irp_mj_create: dbuplint ("info: create! /N "); break; Case irp_mj_close: dbuplint (" info: Close! /N "); break; Case irp_mj_device_control: {Switch (pirpstack-> parameters. deviceiocontrol. iocontrolcode) {Case ioctl_get_info: {rtlcopymemory (pirp-> userbuffer, "this is a test driver! ", 23); Information = 23; break;} default: break;} in the last step, if you need to complete the request, set the iostatus field in the IRP structure and call the iocompleterequest: pirp-> iostatus function. status = STATUS_SUCCESS; pirp-> iostatus. information = information; iocompleterequest (pirp, io_no_increment, first, you need to initialize the corresponding IRP stack (you can directly copy the current IRP stack to the lower IRP stack), and then call the iocalldriver function to pass the IRP: iocopycurrentirpstacklocationtone XT (pirp); status = iocalldriver (plowerdeviceobj, pirp); The above is a simple analysis of the driver test program I wrote, it is also a preliminary understanding of the structure of the WDM driver framework. As I am only a beginner in WDM, there must be many mistakes or omissions. Next, I will briefly introduce how to call the driver from a user-Mode Program. To call a driver, you must first open the device, that is, the device object created in the driver. This can be done by calling the createfile function. The createfile function is used to open a file. Its first parameter is the file name. Here, we pass in the device name as its first parameter, then the function opens the device. The device name mentioned here is actually the symbolic connection name created for the device object in the driver. For example, if the device name in user mode is "//./mydevice", the I/O manager automatically converts "//./" to "/?" before executing the name search "/?? /", Then it becomes "/?? /Mydevice ", which is the symbolic connection name established in the driver. After the device is turned on, the user-mode program can call functions such as readfile, writefile, and deviceiocontrol to send requests to the driver. Finally, the source code of my test program is provided. // Driver part: # ifdef _ cplusplusextern "C" {# endif # include "ntddk. H "# define device_name l" // device // mydevice "# define link_name l "//?? // Mydevice "# define response/ctl_code (response, 0x802, method_neither, file_any_access) void driverunload (pdriver_object pdriverobj); ntstatus driverdispatch (pdevice_object pdeviceobj, pirpirp ); // The DriverEntry routine is called when the driver is loaded: ntstatus DriverEntry (pdriver_object pdriverobj, punicode_string pregistrystring) {dbuplint ("DriverEntry! /N "); ntstatus status; pdevice_object pdeviceobj; unicode_string devicename; rtlinitunicodestring (& devicename, device_name); status = iocreatedevice (pdriverobj, 0, & devicename, file_device_unknown, success, true, & pdeviceobj); If (! Nt_success (Status) {dbuplint ("error: Create device failed! /N "); Return status;} unicode_string linkname; rtlinitunicodestring (& linkname, link_name); status = iocreatesymboliclink (& linkname, & devicename); If (! Nt_success (Status) {dbuplint ("error: Create Symbolic Link failed! /N "); iodeletedevice (pdeviceobj); Return status;} pdriverobj-> driverunload = driverunload; pdriverobj-> majorfunction [irp_mj_create] = pdriverobj-> majorfunction [callback] = pdriverobj-> majorfunction [callback] = driverdispatch; return STATUS_SUCCESS;} void driverunload (pdriver_object pdriverobj) {dbuplint ("driverunload! /N "); If (pdriverobj-> deviceobject! = NULL) {unicode_string linkname; rtlinitunicodestring (& linkname, link_name); callback (& linkname); iodeletedevice (pdriverobj-> deviceobject);} return;} ntstatus driverdispatch (pdevice_object pdeviceobj, pirp) {ulong information = 0; pio_stack_location pirpstack = iogetcurrentirpstacklocation (pirp); Switch (pirpstack-> majorfunction) {Case irp_mj_create: dbuplint ("info: Create! /N "); break; Case irp_mj_close: dbuplint (" info: Close! /N "); break; Case irp_mj_device_control: {Switch (pirpstack-> parameters. deviceiocontrol. iocontrolcode) {Case ioctl_get_info: {rtlcopymemory (pirp-> userbuffer, "this is a test driver! ", 23); Information = 23; break;} default: break;} pir-> iostatus. status = STATUS_SUCCESS; pirp-> iostatus. information = information; iocompleterequest (pirp, io_no_increment); return STATUS_SUCCESS;} # ifdef _ cplusplus} # endif // user mode program part: # include "stdafx. H "# include" stdio. H "# include" windows. H "# define device_name "////. // mydevice "# define ctl_code (devicetype, function, Method, Access) (/(devicetype) <16) | (ACCESS) <14) | (function) <2) | (method )/) # define defaults 0x00000022 # define method_neither 3 # define file_any_access 0 # define ioctl_get_info/ctl_code (file_device_unknown, 0x802, method_neither, file_any_access) int main (INT argc, char * argv []) {handle hdevice = createfile (device_name, generic_read | generic_write, file_share_read, null, O Pen_existing, file_attribute_normal, null); If (hdevice = invalid_handle_value) {printf ("error: Can't open the device! /N "); Return-1;} unsigned long numofbytesreturned; char info [32] = {0}; If (deviceiocontrol (hdevice, ioctl_get_info, null, 0, info, 32, & numofbytesreturned, null) = true) printf ("information: % s/n", Info); closehandle (hdevice); sleep (3000); Return 0 ;}