47
View our advertisers Advertise with us All Topics, MFC / C++ >> System >> Device Drivers Driver Development Part 2: Introduction to Implementing IOCTLs By Toby Opferman This article will go deeper into the basics of creating a simple driver. Beginner C++, C Windows (Win2003, WinXP) Win32, VS Dev Posted 13 Feb 2005 22:24 Updated 5 Mar 2005 19:36 Articles by this author 51,740 views Search: Help! Articles Message Boards StoreFront Lounge Toolbox Broken Article? VS.NET 2003 for $899 Ultimate Toolbox $499 Print version Send to a friend Sign in / Sign up Email Password Remember me Lost your Password? 50 members have rated this article. Result: Popularity: 8.37. Rating: 4.93 out of 5. Download source code - 14.3 Kb Introduction This is the second tutorial of the Writing Device Drivers series. There seems to be a lot of interest in the topic, so this article will pick up where the first left off. The main focus of these articles will be to build up little by little the knowledge needed to write device drivers. In this article, we will be building on the same example source code used in part one. In this article, we will expand on that code to include Read functionality, Handle Input/Ouput Controls also known as IOCTLs, and learn a bit more about IRPs. F.A.Q. Before we begin with this article, here is a small list of some frequently asked questions that we can clear up. Where do I get the DDK? Microsoft allows MSDN subscribers to download the DDK from their website. If you do

The Code Project - Driver Development Part 2: Introduction to

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: The Code Project - Driver Development Part 2: Introduction to

View our advertisers Advertise with us

All Topics, MFC / C++ >> System >> Device Drivers

Driver Development Part 2: Introduction to Implementing IOCTLsBy Toby Opferman

This article will go deeper into the basics of creating a simple driver.

Beginner

C++, CWindows (Win2003, WinXP)Win32, VSDev

Posted 13 Feb 2005 22:24

Updated 5 Mar 2005 19:36

Articles by this author

51,740 views

Search: Help! Articles Message Boards StoreFront Lounge

Toolbox

Broken Article?

VS.NET 2003 for $899

Ultimate Toolbox $499

Print version

Send to a friend

Sign in / Sign up

Email

Password

Remember me

Lost your Password?

50 members have rated this article. Result:

Popularity: 8.37. Rating: 4.93 out of 5.

● Download source code - 14.3 Kb

Introduction

This is the second tutorial of the Writing Device Drivers series. There seems to be a lot of interest in the topic, so this article will pick up where the first left off. The main focus of these articles will be to build up little by little the knowledge needed to write device drivers. In this article, we will be building on the same example source code used in part one. In this article, we will expand on that code to include Read functionality, Handle Input/Ouput Controls also known as IOCTLs, and learn a bit more about IRPs.

F.A.Q.

Before we begin with this article, here is a small list of some frequently asked questions that we can clear up.

Where do I get the DDK?

Microsoft allows MSDN subscribers to download the DDK from their website. If you do

Page 2: The Code Project - Driver Development Part 2: Introduction to

not subscribe, they sometimes allow new DDKs to be openly downloaded by the public for a certain period of time. At the time of this article, no DDKs are available for download, so if you are not a subscriber, you can request they mail you the DDK CD for the cost of shipping and handling. You can order the DDK from here.

Can I include windows.h in my driver?

You cannot mix the Windows SDK header files with the Windows DDK header files. They have definitions that will conflict and you will have trouble getting the code to compile. Sometimes there are user-mode applications which like to include part of the DDK. These generally will have to take out the types they want to define from the DDK or SDK and put them directly in their file. The other popular approach used where possible is to separate the files into DDK and SDK usage so each .C can include the appropriate headers without conflict.

Can I implement “x” type driver like this?

This is the general generic framework for which mostly all drivers are built upon in Windows. Drivers do not have to implement hardware, and as mentioned in the first tutorial, there is usually a stack of drivers. If you are looking to implement a specific type of driver, this is a starting point to understand in general how drivers work. The difference then becomes how you advertise your device to the system, what IOCTLs you implement, what drivers you communicate to underneath your driver, and any additional pieces you are required to implement such as supporting drivers or even user mode components. If you are looking to implement a specific type of driver, you will want to read information specific to that driver on MSDN, in the DDK and other places. There are sometimes other frameworks which actually encapsulate most of what we are doing here so it’s easier to write for example.

Can I use the C or C++ runtime in a driver?

You should avoid using these in a driver and instead use the equivalent kernel mode APIs. Kernel Run Time Library also includes a subtopic on Safe String Functions. When programming in the kernel, there are some pitfalls you may need to be aware of, and if you never look up the real kernel API, you may never be aware of these since you would never have read the "remarks" section for example. The kernel APIs also tell you at what IRQL you can use each of the functions. It is a lot safer and in your best interest to avoid the standard run time as it will save you time from tracking down

Page 3: The Code Project - Driver Development Part 2: Introduction to

bugs and making simple common mistakes in your code.

Implementing the ReadFile

The first article left this as homework so even if you have not completed your homework, here are the answers. There are three types of I/O as we discussed previously and these are Direct, Buffered and Neither. I have implemented all three of these in the example driver. The difference is that instead of reading the memory, we write to the memory. I will not explain all three types of I/O as they are identical. What I will explain is the new functionality that I have added: return values!

In the WriteFile implementation, we didn’t need to worry about the return value. Proper implementations should always inform the user mode application how much data was “written”, however, I omitted this detail for simplicity at the time. This will become essential with the “ReadFile” implementation if not only for properly informing the user mode application but to let the I/O Manager know as well.

If you recall how “Buffered I/O” works for example, the memory buffer is created in another location and the user mode memory is copied. If we want to read data from the driver, the I/O manager needs to know how much memory to copy from this temporary buffer to the real user mode memory location! If we don’t do this, no memory will be copied and the user mode application will not get any data!

NTSTATUS Example_ReadDirectIO(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS NtStatus = STATUS_BUFFER_TOO_SMALL; PIO_STACK_LOCATION pIoStackIrp = NULL; PCHAR pReturnData = "Example_ReadDirectIO - Hello from the Kernel!"; UINT dwDataSize = sizeof("Example_ReadDirectIO - Hello from the Kernel!"); UINT dwDataRead = 0; PCHAR pReadDataBuffer;

DbgPrint("Example_ReadDirectIO Called \r\n"); /* * Each time the IRP is passed down the driver stack a * new stack location is added * specifying certain parameters for the IRP to the * driver.

Page 4: The Code Project - Driver Development Part 2: Introduction to

*/ pIoStackIrp = IoGetCurrentIrpStackLocation(Irp); if(pIoStackIrp && Irp->MdlAddress) { pReadDataBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); if(pReadDataBuffer && pIoStackIrp->Parameters.Read.Length >= dwDataSize) { /* * We use "RtlCopyMemory" in the kernel instead * of memcpy. * RtlCopyMemory *IS* memcpy, however it's best * to use the * wrapper in case this changes in the future. */ RtlCopyMemory(pReadDataBuffer, pReturnData, dwDataSize); dwDataRead = dwDataSize; NtStatus = STATUS_SUCCESS; } }

Implementing Return Values

The return value is implemented using the IO_STATUS_BLOCK of the IRP. This contains a few data members which vary their use depending on the major function being implemented. In the major functions we are implementing, “Status” is equal to the return code and “Information” contains the number of bytes read or written. Looking at the new code, you also notice that we are now calling “IoCompleteRequest”. What does this all mean?

The IoCompleteRequest is always called by the driver after it completes the IRP. The reason we weren’t doing this in the previous example is that the I/O Manager being a nice guy will in most cases complete this for us. However, it is proper for the driver to complete the IRP where necessary. This location contains a document on “IRP Handling” which can supply more information.

Page 5: The Code Project - Driver Development Part 2: Introduction to

Irp->IoStatus.Status = NtStatus; Irp->IoStatus.Information = dwDataRead;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return NtStatus;}

The second parameter of the IoCompleteRequest specifies the priority boost to give the thread waiting for this IRP to complete. As an example, perhaps the thread has been waiting a long time for a network operation. This boost helps the scheduler re-run this thread sooner than it may have if it simply went back into the ready queue without a boost. To put this quite simply, it's basically a helper being used to inform the scheduler to re-run the thread waiting for this I/O.

Stricter Parameter Validation and Error Checking

The code now implements a little more error checking and parameter validation than it previously did. This is one thing that you want to make sure with your driver, that a user mode application shouldn’t be able to send invalid memory locations, etc. to the driver and blue screen the system. The driver implementation should also do a little better on the errors it returns to the user mode driver instead of just “STATUS_SUCCESS” all the time. We need to inform the user mode process if it needs to send us more data or attempt to determine exactly when wrong. You like APIs which you can call GetLastError to see why they failed or use the return value to determine how to fix your code. If your driver simply returns “failed” or even better “success” all the time, it becomes harder to know how to make your application work properly with the driver.

Input/Output Controls (IOCTL)

The IOCTL is used as more of a communication between the driver and application rather than simply reading or writing data. Generally, the driver exports a number of IOCTLs and defines data structures that would be used in this communication. Generally, these data structures should not contain pointers since the I/O Manager cannot interpret these structures. All data should be contained in the same block. If you want to create pointers, you can do things such as create offsets into the block of

Page 6: The Code Project - Driver Development Part 2: Introduction to

data past the end of the static data so the driver can easily find this information. If you do remember however, the driver does have the ability to read user mode data as long as it’s in the context of the process. So, it is possible to implement pointers to memory and the driver would need to copy the pages or lock the pages in memory (implement basically buffered or direct I/O from within the driver itself, which can be done). The user mode process will use the “DeviceIoControl” API to perform this communication.

Defining the IOCTL

The first thing we need to do is define the IOCTL code to be used between the application and the driver. I will essentially be summarizing this article on MSDN here. First, to relate the IOCTL to something in user mode, you may think of it as a Windows Message. It’s simply a value used by the driver to implement some requested function with predefined input and output values. There is a little more to this value than a Windows Message however. The IOCTL defines the access required in order to issue the IOCTL as well as the method to be used when transferring the data between the driver and the application.

The IOCTL is a 32 bit number. The first two low bits define the “transfer type” which can be METHOD_OUT_DIRECT, METHOD_IN_DIRECT, METHOD_BUFFERED or METHOD_NEITHER.

The next set of bits from 2 to 13 define the “Function Code”. The high bit is referred to as the “custom bit”. This is used to determine user-defined IOCTLs versus system defined. This means that function codes 0x800 and greater are custom defined similar to how WM_USER works for Windows Messages.

The next two bits define the access required to issue the IOCTL. This is how the I/O Manager can reject IOCTL requests if the handle has not been opened with the correct access. The access types are such as FILE_READ_DATA and FILE_WRITE_DATA for example.

The last bits represent the device type the IOCTLs are written for. The high bit again represents user defined values.

There is a macro we can use to define our IOCTLs quickly and it is “CTL_CODE”. I have

Page 7: The Code Project - Driver Development Part 2: Introduction to

used it in “public.h” to define four IOCTLs which implement different types of access transfer methods.

/* * IOCTL's are defined by the following bit layout. * [Common |Device Type|Required Access|Custom|Function Code|Transfer Type] * 31 30 16 15 14 13 12 2 1 0 * * Common - 1 bit. This is set for user-defined * device types. * Device Type - This is the type of device the IOCTL * belongs to. This can be user defined * (Common bit set). This must match the * device type of the device object. * Required Access - FILE_READ_DATA, FILE_WRITE_DATA, etc. * This is the required access for the * device. * Custom - 1 bit. This is set for user-defined * IOCTL's. This is used in the same * manner as "WM_USER". * Function Code - This is the function code that the * system or the user defined (custom * bit set) * Transfer Type - METHOD_IN_DIRECT, METHOD_OUT_DIRECT, * METHOD_NEITHER, METHOD_BUFFERED, This * the data transfer method to be used. * */#define IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO \ CTL_CODE(FILE_DEVICE_UNKNOWN, \ 0x800, \ METHOD_IN_DIRECT, \ FILE_READ_DATA | FILE_WRITE_DATA) #define IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO \ CTL_CODE(FILE_DEVICE_UNKNOWN, \ 0x801, \ METHOD_OUT_DIRECT, \ FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO \ CTL_CODE(FILE_DEVICE_UNKNOWN, \

Page 8: The Code Project - Driver Development Part 2: Introduction to

0x802, \ METHOD_BUFFERED, \ FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_EXAMPLE_SAMPLE_NEITHER_IO \ CTL_CODE(FILE_DEVICE_UNKNOWN, \ 0x803, \ METHOD_NEITHER, \ FILE_READ_DATA | FILE_WRITE_DATA)

The above displays how we defined our IOCTLs.

Implementing the IOCTL

The first thing that simply needs to occur is essentially a switch statement which distributes the IOCTL to the appropriate implementation. This is essentially the same thing a Windows procedure does to dispatch Windows messages. There is no such thing as a "def IOCTL proc" though!

The "Parameters.DeviceIoControl.IoControlCode" of the IO_STACK_LOCATION contains the IOCTL code being invoked. The following code is essentially a switch statement which dispatches each IOCTL to its implementation.

NTSTATUS Example_IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS NtStatus = STATUS_NOT_SUPPORTED; PIO_STACK_LOCATION pIoStackIrp = NULL; UINT dwDataWritten = 0;

DbgPrint("Example_IoControl Called \r\n");

pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);

if(pIoStackIrp) /* Should Never Be NULL! */ { switch(pIoStackIrp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO: NtStatus = Example_HandleSampleIoctl_DirectInIo(Irp, pIoStackIrp, &dwDataWritten);

Page 9: The Code Project - Driver Development Part 2: Introduction to

break;

case IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO: NtStatus = Example_HandleSampleIoctl_DirectOutIo(Irp, pIoStackIrp, &dwDataWritten); break;

case IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO: NtStatus = Example_HandleSampleIoctl_BufferedIo(Irp, pIoStackIrp, &dwDataWritten); break;

case IOCTL_EXAMPLE_SAMPLE_NEITHER_IO: NtStatus = Example_HandleSampleIoctl_NeitherIo(Irp, pIoStackIrp, &dwDataWritten); break; } }

Irp->IoStatus.Status = NtStatus; Irp->IoStatus.Information = dwDataWritten;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return NtStatus;

}

If you understand the ReadFile and WriteFile implementations, these simply implement both in one call. This obviously doesn't have to be the case, IOCTLs can be used to only read data, only write data, or not send any data at all but simply inform or instruct the driver to perform an action.

METHOD_x_DIRECT

The METHOD_IN_DIRECT and METHOD_OUT_DIRECT can essentially be explained at the same time. They are basically the same. The INPUT buffer is passed in using "BUFFERED" implementation. The output buffer is passed in using the MdlAddress as explained in the Read/Write implementations. The difference between "IN" and "OUT" is that with "IN", you can use the output buffer to pass in data! The "OUT" is only used to return data. The driver example we have doesn't use the "IN" implementation

Page 10: The Code Project - Driver Development Part 2: Introduction to

to pass in data, and essentially the "OUT" and "IN" implementations are the same in the example. Since this is the case, I will just show you the "OUT" implementation.

NTSTATUS Example_HandleSampleIoctl_DirectOutIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten){ NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; PCHAR pInputBuffer; PCHAR pOutputBuffer; UINT dwDataRead = 0, dwDataWritten = 0; PCHAR pReturnData = "IOCTL - Direct Out I/O From Kernel!"; UINT dwDataSize = sizeof("IOCTL - Direct Out I/O From Kernel!"); DbgPrint("Example_HandleSampleIoctl_DirectOutIo Called \r\n");

/* * METHOD_OUT_DIRECT * * Input Buffer = Irp->AssociatedIrp.SystemBuffer * Ouput Buffer = Irp->MdlAddress * * Input Size = Parameters.DeviceIoControl.InputBufferLength * Output Size = Parameters.DeviceIoControl.OutputBufferLength * * What's the difference between METHOD_IN_DIRECT && METHOD_OUT_DIRECT? * * The function which we implemented METHOD_IN_DIRECT * is actually *WRONG*!!!! We are using the output buffer * as an output buffer! The difference is that METHOD_IN_DIRECT creates * an MDL for the outputbuffer with * *READ* access so the user mode application * can send large amounts of data to the driver for reading. * * METHOD_OUT_DIRECT creates an MDL * for the outputbuffer with *WRITE* access so the user mode * application can recieve large amounts of data from the driver! * * In both cases, the Input buffer is in the same place, * the SystemBuffer. There is a lot * of consfusion as people do think that * the MdlAddress contains the input buffer and this * is not true in either case. */

Page 11: The Code Project - Driver Development Part 2: Introduction to

pInputBuffer = Irp->AssociatedIrp.SystemBuffer; pOutputBuffer = NULL;

if(Irp->MdlAddress) { pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); }

if(pInputBuffer && pOutputBuffer) { /* * We need to verify that the string * is NULL terminated. Bad things can happen * if we access memory not valid while in the Kernel. */ if(Example_IsStringTerminated(pInputBuffer, pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, &dwDataRead)) { DbgPrint("UserModeMessage = '%s'", pInputBuffer); DbgPrint("%i >= %i", pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength, dwDataSize); if(pIoStackIrp-> Parameters.DeviceIoControl.OutputBufferLength >= dwDataSize) { /* * We use "RtlCopyMemory" in the kernel instead of memcpy. * RtlCopyMemory *IS* memcpy, however it's best to use the * wrapper in case this changes in the future. */ RtlCopyMemory(pOutputBuffer, pReturnData, dwDataSize); *pdwDataWritten = dwDataSize; NtStatus = STATUS_SUCCESS; } else { *pdwDataWritten = dwDataSize; NtStatus = STATUS_BUFFER_TOO_SMALL; }

Page 12: The Code Project - Driver Development Part 2: Introduction to

} }

return NtStatus;}

As homework, see if you can change the "IN" method to work correctly. Pass input data through the output buffer and display it.

METHOD_BUFFERED

The METHOD_BUFFERED implementation does essentially the same thing as the Read and Write implementations. A buffer is allocated and the data is copied from this buffer. The buffer is created as the larger of the two sizes, the input or output buffer. Then the read buffer is copied to this new buffer. Before you return, you simply copy the return data into the same buffer. The return value is put into the IO_STATUS_BLOCK and the I/O Manager copies the data into the output buffer.

NTSTATUS Example_HandleSampleIoctl_BufferedIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten){ NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; PCHAR pInputBuffer; PCHAR pOutputBuffer; UINT dwDataRead = 0, dwDataWritten = 0; PCHAR pReturnData = "IOCTL - Buffered I/O From Kernel!"; UINT dwDataSize = sizeof("IOCTL - Buffered I/O From Kernel!"); DbgPrint("Example_HandleSampleIoctl_BufferedIo Called \r\n");

/* * METHOD_BUFFERED * * Input Buffer = Irp->AssociatedIrp.SystemBuffer * Ouput Buffer = Irp->AssociatedIrp.SystemBuffer * * Input Size = Parameters.DeviceIoControl.InputBufferLength * Output Size = Parameters.DeviceIoControl.OutputBufferLength * * Since they both use the same location

Page 13: The Code Project - Driver Development Part 2: Introduction to

* so the "buffer" allocated by the I/O * manager is the size of the larger value (Output vs. Input) */

pInputBuffer = Irp->AssociatedIrp.SystemBuffer; pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;

if(pInputBuffer && pOutputBuffer) { /* * We need to verify that the string * is NULL terminated. Bad things can happen * if we access memory not valid while in the Kernel. */ if(Example_IsStringTerminated(pInputBuffer, pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, &dwDataRead)) { DbgPrint("UserModeMessage = '%s'", pInputBuffer); DbgPrint("%i >= %i", pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength, dwDataSize); if(pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength >= dwDataSize) { /* * We use "RtlCopyMemory" in the kernel instead of memcpy. * RtlCopyMemory *IS* memcpy, however it's best to use the * wrapper in case this changes in the future. */ RtlCopyMemory(pOutputBuffer, pReturnData, dwDataSize); *pdwDataWritten = dwDataSize; NtStatus = STATUS_SUCCESS; } else { *pdwDataWritten = dwDataSize; NtStatus = STATUS_BUFFER_TOO_SMALL; }

} }

Page 14: The Code Project - Driver Development Part 2: Introduction to

return NtStatus;}

METHOD_NEITHER

This is also the same as implementing neither I/O. The original user mode buffers are passed into the driver.

NTSTATUS Example_HandleSampleIoctl_NeitherIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten){ NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; PCHAR pInputBuffer; PCHAR pOutputBuffer; UINT dwDataRead = 0, dwDataWritten = 0; PCHAR pReturnData = "IOCTL - Neither I/O From Kernel!"; UINT dwDataSize = sizeof("IOCTL - Neither I/O From Kernel!");

DbgPrint("Example_HandleSampleIoctl_NeitherIo Called \r\n");

/* * METHOD_NEITHER * * Input Buffer = Parameters.DeviceIoControl.Type3InputBuffer * Ouput Buffer = Irp->UserBuffer * * Input Size = Parameters.DeviceIoControl.InputBufferLength * Output Size = Parameters.DeviceIoControl.OutputBufferLength * */

pInputBuffer = pIoStackIrp->Parameters.DeviceIoControl.Type3InputBuffer; pOutputBuffer = Irp->UserBuffer;

if(pInputBuffer && pOutputBuffer) {

/* * We need this in an exception handler or else we could trap. */ __try {

Page 15: The Code Project - Driver Development Part 2: Introduction to

ProbeForRead(pInputBuffer, pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, TYPE_ALIGNMENT(char)); /* * We need to verify that the string * is NULL terminated. Bad things can happen * if we access memory not valid while in the Kernel. */ if(Example_IsStringTerminated(pInputBuffer, pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, &dwDataRead)) { DbgPrint("UserModeMessage = '%s'", pInputBuffer); ProbeForWrite(pOutputBuffer, pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength, TYPE_ALIGNMENT(char)); if(pIoStackIrp-> Parameters.DeviceIoControl.OutputBufferLength >= dwDataSize) { /* * We use "RtlCopyMemory" * in the kernel instead of memcpy. * RtlCopyMemory *IS* memcpy, * however it's best to use the * wrapper in case this changes in the future. */ RtlCopyMemory(pOutputBuffer, pReturnData, dwDataSize); *pdwDataWritten = dwDataSize; NtStatus = STATUS_SUCCESS; } else { *pdwDataWritten = dwDataSize; NtStatus = STATUS_BUFFER_TOO_SMALL; }

}

Page 16: The Code Project - Driver Development Part 2: Introduction to

} __except( EXCEPTION_EXECUTE_HANDLER ) {

NtStatus = GetExceptionCode(); }

}

return NtStatus;}

Calling DeviceIoControl

This is a very simple implementation.

ZeroMemory(szTemp, sizeof(szTemp)); DeviceIoControl(hFile, IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO, "** Hello from User Mode Direct IN I/O", sizeof("** Hello from User Mode Direct IN I/O"), szTemp, sizeof(szTemp), &dwReturn, NULL); printf(szTemp); printf("\n");

ZeroMemory(szTemp, sizeof(szTemp)); DeviceIoControl(hFile, IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO, "** Hello from User Mode Direct OUT I/O", sizeof("** Hello from User Mode Direct OUT I/O"), szTemp, sizeof(szTemp), &dwReturn, NULL); printf(szTemp); printf("\n");

ZeroMemory(szTemp, sizeof(szTemp)); DeviceIoControl(hFile, IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO,

Page 17: The Code Project - Driver Development Part 2: Introduction to

"** Hello from User Mode Buffered I/O", sizeof("** Hello from User Mode Buffered I/O"), szTemp, sizeof(szTemp), &dwReturn, NULL); printf(szTemp); printf("\n");

ZeroMemory(szTemp, sizeof(szTemp)); DeviceIoControl(hFile, IOCTL_EXAMPLE_SAMPLE_NEITHER_IO, "** Hello from User Mode Neither I/O", sizeof("** Hello from User Mode Neither I/O"), szTemp, sizeof(szTemp), &dwReturn, NULL); printf(szTemp); printf("\n");

System Memory Layout

This is probably a good time to look at how Windows memory layout looks. To show how this works, we need to first show how Intel processors implement Virtual Memory. I will explain the general implementation as there are a few variations of how this can be implemented. This is basically called the “Virtual Address Translation”. The following is an excerpt from another document that I have been writing on debugging.

Virtual Address Translation

All segment registers become “selectors” in protected mode. To get more familiar with how the x86 operates, we will go over the paging mechanism as an overview and not in detail. This is not a systems programming guide.

There are other registers in the CPU which point to “descriptor tables”. These tables define certain system attributes which we will not go into detail. Instead, we will discuss the process of converting a “virtual” address into a physical address. The descriptor table can define an offset which is then added to the virtual address. If

Page 18: The Code Project - Driver Development Part 2: Introduction to

paging is not enabled, once you add these two addresses, you get the physical address. If paging is enabled, you get instead a “linear” address which is then converted to a physical address using page tables.

There is a paging mechanism that is called “Page Address Extensions” which was originally introduced in the Pentium Pro. This mechanism allows Page Tables to reference up to 36 bit addresses. However, offsets are still 32 bit, so while you can access Physical Ram up to 36 bits, you can only access 4 GB at a time without reloading the page tables. This paging mechanism is not what we will be discussing here, but it is very similar.

Page 19: The Code Project - Driver Development Part 2: Introduction to

The normal 32 bit paging is done using the following. There is a CPU register that points to the base of the Page Directory Table, called CR3. The diagram below displays how the paging mechanism works. Notice that the location of the physical

Page 20: The Code Project - Driver Development Part 2: Introduction to

page does not need to be linear with the virtual address or even with the previous page table entry. The blue lines are involved in the example translation and the black lines are further examples of how the page tables could be setup.

The “Page Directory Table” has entries which each point to a structure of “Page Table Entries”. The entries in the “Page Table Entry” point to the beginning of a page in the physical RAM. While Windows and most other Operating Systems use 4k pages, the CPU actually can support 4k and 2MB pages.

The entire process can be listed in the following steps if the pages are defined as 4k.

1. The selector points to the Descriptor Table Entry. 2. The Descript Table Entry “base offset” is added to the offset of the virtual

address creating a linear address. 3. Bits 31-22 of the Linear Address index into the “Page Directory Table” pointed to

by CR3. 4. The entry in the “Page Directory Table” points to the base of a “Page Entry

Table” which is then indexed by the bits 21 – 12 indexed into this table to retrieve a “Page Table Entry”.

5. The “Page Table Entry” aside from containing information about whether the address is paged to disk points to the base location of the page in Physical Memory.

6. The remaining bits of the Linear Address, bits 11 – 0 are added to the start of the physical page to create the final physical address.

Windows Implementation

If you generally ignore the implementation of the descriptor tables, the address translation should be quite simple to follow. The address is just divided into sections which help index into memory tables that eventually point to the location of a physical page. The last index simply indexes into that physical page.

Windows implements essentially three separate layers of virtual address ranges. The first would be the user-mode addresses. These addresses are essentially unique per-process. That is, each process will have its own memory addresses in this range. Of course, there are optimizations such as different page tables pointing to the same physical memory location in order to share code and not duplicate memory that is essentially static.

Page 21: The Code Project - Driver Development Part 2: Introduction to

The second range of addresses would be those in the session space. If you have used “Fast User Switching” or “Terminal Services”, you know that each user essentially gets their own desktop. There are certain drivers which run in what is called “Session Space” which is memory that is unique per-session. In this memory are things like the display driver, win32k.sys and some printer drivers. This is one reason why Windows does not span sessions, i.e., you cannot do “FindWindow” and see a window on another user’s desktop.

The last is the range of addresses known as “System Space”. This is memory that is shared throughout the entire system and accessible anywhere. This is where our driver lies and where most drivers lie.

So, what happens? Every time a thread is switched, CR3 is reloaded with the appropriate pointer which points to the page tables accessible by that thread. The implementation is that each process has its own page directory pointer and it’s loaded into CR3. This is essentially how Windows isolates each process from one another, they all have their own directory pointer. This directory pointer is implemented in a

Page 22: The Code Project - Driver Development Part 2: Introduction to

way that processes in the same session map the same session space, and all processes on the system map system memory. The only memory ranges implemented unique per-process is essentially the user mode address ranges.

The /PAE Switch

This is called "Physical Address Extensions". It basically means the OS can map 36 bit physical memory into 32 bits. This doesn't mean that you can access > 4 GB of memory at the same time, it means that higher memory addresses can be mapped into 32 bits which means the process can access it. This also means that the OS could use this ability to use machines with > 4 GB of physical memory. So while one process may not access > 4 GB, the OS can manage the memory in a way that it can keep more pages in memory at the same time.

There are also special APIs that an application can use to manage memory itself and use > 4GB of memory. These are called "AWE" or Address Windows Extensions. You can find more information on these at this URL: MSDN.

The /3GB Switch

There is a switch that you may have heard about and it’s called the /3GB switch. This essentially allows user mode to have 3 GB of address space. Normally, the 4 GB range is divided into two. There is 2 GB of address space for user mode and 2 GB of address space for kernel mode. This essentially means that user mode addresses do not have the high bit (bit 31) set while kernel mode addresses have bit 31 set. This means that 0x78000000 is a user mode address while 0x82000000 is a kernel mode address. Setting the /3GB switch will then allow user mode processes to maintain more memory but the kernel will have less memory. There are upsides and downsides to this.

The general upsides of doing this are as follows:

1. Applications requiring a lot of memory will be able to function better if they know to take advantage of this. There would be less swapping to and from disk if they are using user mode memory to cache data.

The general downsides of doing this are as follows:

Page 23: The Code Project - Driver Development Part 2: Introduction to

1. There is not much available kernel mode memory so applications or operations that essentially require a lot of kernel memory will not be able to perform.

2. Applications and drivers that check the high bit (bit 31) and use this to determine kernel mode memory versus user mode memory will not function properly.

Conclusion

In this article, we have learned a bit more about communications with user mode processes. We learned how to implement the ReadFile and DeviceIoControl APIs. We also learned about completing IRPs and returning status to user mode. We also learned about creating IOCTLs, and finally, we saw how memory is mapped in Windows.

In the next article, we may be using this information we just learned to implement something a little more fun, communications between two processes using the driver!

About Toby Opferman

I have programmed in a variety of languages including even more obscure languages such as Natural 2 and Prolog.

Professionally I have worked in PowerBuilder, C, C++, x86 Assembly Language, Natural 2 and CoBOL though majority of my experience has been with C/C++/x86 Assembly.

I have worked with a variety of devices and machines (including MainFrames!) over the years but mostly I have just worked with PCs.

The Operating Systems I have used vary but again these days I basically work almost exclusively with Windows NT based Operating Systems.

I currently work in a research group on a variety of projects.

Click here to view Toby Opferman's online profile.

Page 24: The Code Project - Driver Development Part 2: Introduction to

Other popular articles:

● Driver Development Part 1: Introduction to DriversThis article will go into the basics of creating a simple driver.

● A simple demo for WDM Driver developmentWDM Driver programming introduction with three Pseudo Drivers.

● API hooking revealedThe article demonstrates how to build a user mode Win32 API spying system

● Driver Development Part 5: Introduction to the Transport Device InterfaceIntroduction to TDI Client drivers and more IRP handling.

[Top] Sign in to vote for this article: Poor Excellent

Premium Sponsor

FAQ Noise tolerance Search comments

View Per page

New Message Msgs 1 to 25 of 55 (Total: 55) (Refresh) First Prev Next

Page 25: The Code Project - Driver Development Part 2: Introduction to

Subject Author Date

Beginner question JakeTruman 15:03 27 Jul '05

I was wondering if any of you can answer the following question,

From with in driver, Can we know the name, path etc of the calling application or the application trying perform IO with the device.?

any pointers please..

Thanks, [Reply][Email][View Thread][Get Link] [Modify|Delete]

Connecting 2 Virtual COM ports with IRPs Bill Hase 11:15 31 May '05

I am building a driver for a not-yet-built PCI card that has 3 serial"like" ports. Since I am new to Windows driver development I was goingto try, as a learning exercise, to connect the first 2 ports togetherin the driver. Then I should be able to test down to the kernel driverlevel.

I believe that I should be sending IRPs between the 2 device objects(COM ports) but I am unsure how to do that, especially for asyncronousI/O.

Is there any good examples out there of how to pass IRPs between deviceobjects?

Thanks,

Bill [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Toby Opferman 12:12 31 May '05

Page 26: The Code Project - Driver Development Part 2: Introduction to

The driver tutorials 3, 4, & 5 (especially 5) go into this on various levels. You would essentially do what 5 does, create an instance to the serial device driver and you would really just proxy all calls down. You would then just need to link IRPs so that once the serial device returned the data, put it into your IRP and complete it.

Part 5 also goes into how to handle Asynchronous IRPs.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Bill Hase 14:39 31 May '05

So how would this work?

A async write comes in to Virtual COM1 it then sends off an IRP(what major function? read or write) to COM2. Since COM2 buffer was full what does COM2 do? I guess COM2 returns pending and then waits for a read to clear some data out of buffer. Once a read comes in for COM2 it then looks at its list of queued IRPs and takes COM1s off and writes that data into its queue and completes the original request from COM1?

It still doesnt seem clear the IRPs that are sent between the 2 COM ports. [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Bill Hase 15:01 31 May '05

Maybe another way to handle passing data from COM1 to COM2 is to have a Queue for pending IRPs for both ports and then if a async read comes in for COM1 check COM2s queue for a write if there is one pop it and copy data into read buffer, complete COM2s write and then complete COM1s read.

Does this sound correct? (Of coarse I will have to have spin locks around the IRP queues.) [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Toby Opferman 17:49 31 May '05

Page 27: The Code Project - Driver Development Part 2: Introduction to

In general you really are implementing a proxy:

IRP Arrives for COM1, you create your own IRP and send it to COM2. COM2 returns pending, you mark your COM1 IRP Pending (add to your queue) and return.

COM2 completes IRP, you then complete IRP for COM1 and remove from your queue.

IRP Arrives for COM1, you create your own IRP and send it to COM2. COM2 returns it's completed, you then complete IRP for COM1.

Remember, when you write data you send it out the COM port so transferring the Write IRP to the Read IRP is really a loop back and not communicating through the COM port. The read data comes from the COM port.

There is also a mechanism to wait on a COM event. This event would then notify the application that the COM port has data. You can then read this data from the COM port. In this situation you may never experience a time when a read was pended, however I do know of implementations where this is possible. However, you will probably not run into them.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Bill Hase 18:46 31 May '05

I really dont see how this can work. If both ports are being used bydirectionally how does COM1 distinguish between a read IRP from userland and a read IRP from COM2 (that was actually a write to COM2).

For example:- COM2 gets write request from userland- COM1 gets read IRP from COM2 - Now what does it do? If the request was from userland then it should send off a IRP to COM2. If its from COM2 (write) it should save the data into some buffer and complete the IRP so that COM2s write request can complete.

It seems to me that it would be easier just have some shared data (see http://www.osronline.com/article.cfm?article=177) to queue up the IRP writes in a queue on both (one write queue per port) and return pending (if the other port has no read requests). Then when a read comes in for COM1 it could check if COM2s write queue had data and pop off the first request, copy the data to COM1s read IRP, complete COM2s write IRP and then complete its own read IRP.

Page 28: The Code Project - Driver Development Part 2: Introduction to

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Toby Opferman 19:04 31 May '05

Perhaps I am unclear from what you are attempting to do. My understanding was you essentially wanted to make applications using "COM1" be proxied to "COM2". That would mean that a Read request on COM1 would actually be a Read request on COM2. A write request to COM1 would then be a write request to COM2. So, while the user mode application thinks its using COM1, it's actually using COM2.

This is where my confusion is since what you are describing sounds like an LPC implementation where two processes open different ports and communicate.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Connecting 2 Virtual COM ports with IRPs Toby Opferman 19:10 31 May '05

Ok, I re-read your original post and it seems you are attempting to do an LPC-type implementation. I thought you wanted to write a serial driver that would proxy COM ports to the real serial driver! So, that's my mistake!

Yes, what you describe is correct then. You could also have a small buffer in the kernel to allow writes to complete without any wait. If your buffer is full in the kernel though you probably would want to do something similar to part 5 and pend the IRP in a queue like you mention and then wait until the buffer becomes free.

There is a very simple implementation of this in the 3rd article (Does not include async IRP handling, that's part 5):Poor Man's Pipes[^]

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

WDM PCI Driver Design Bill Hase 1:34 29 May '05

Page 29: The Code Project - Driver Development Part 2: Introduction to

I need to build a PCI driver for a PCI board that has 3 different proprietary input/output ports. I want to make each of these ports look like a regular COM port.

Should I build the driver as a bus driver and internally create 3 different device objects using IoCreateDevice for each channel.

OR

Should I build a Bus driver for the PCI board (like Toaster example) and then have a Functional driver for each of the virtual like COM ports.

Any help would be appreciated.

Thanks, [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: WDM PCI Driver Design Toby Opferman 3:00 29 May '05

First, the definitions of the Bus and Function driver on MSDN:

Bus Drivers[^]

Function Drivers[^]

In summary, the Bus driver is used to enumerate devices on the bus, support PNP and things of that nature while the function driver is generally used to communicate and implement the functionality of the actual device. Given those definitions, the model of creating two drivers would be the route to go if you want to follow the guidelines set forth by MSDN.

That being said however, are you creating your own Bus or is this an expansion card that plugs into the PCI Bus? If so, PCI is an existing bus and should already have a PCI Bus driver installed on the system, so unless I am missing something you really just need to write the function driver.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: WDM PCI Driver Design Bill Hase 10:36 29 May '05

Page 30: The Code Project - Driver Development Part 2: Introduction to

Thanks, that helps.

I am designing a driver for a new PCI card that has 3 propreitary I/O channels that I want to look like 3 COM ports so I guess your right. I only need a functional driver. That simplifies things in my mind.

I believe I then need to create 3 device objects in my driver. For testing I was just going to connect COM1 to COM2 internally.

Is it correct that I will then be passing IRP's between the 2 device objects?

Is it correct that one (for example) READ IRP routine will be called by each device object?

Thanks again, I really appreciate your help,

Bill [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: WDM PCI Driver Design Toby Opferman 0:59 30 May '05

To answer your question you have several options.

1. Multiple device objects, single drver.

As you mention all devices created in a driver share the same function entry points. Read, write, iocontrol, etc. are shared.

2. Multiple device objects, multiple drivers.

This may not be ideal, but it is another option to put eachinto its own driver. If most the code is shared though,this is probably not the best option since it would take up morekernel addresses that could have been shared. Of course youcould do things to minimize this but even still it could make thingsharder to maintain anyway.

3. Single device object, single driver.

So, how is this done? Through the symbolic link.

com3 -> \device\mypci\com3com4 -> \device\mypci\com4com5 -> \device\mypci\com5

Page 31: The Code Project - Driver Development Part 2: Introduction to

Now, if someone opens "com3" your PFILE_OBJECT will point to "\com3"as the file, or whatever you appended to the device object name when you created the symbolic link.

How does windows do com1, com2, etc? They use method #1. Ijust mention the other options to show that there are multiple ways to accomplish the same thing and you can choose what makes sense for you.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: WDM PCI Driver Design Bill Hase 11:19 30 May '05

Well being new to this I dont want to stray to far from the examples I can find so I will probably try 1.

Having said that what about using "mf.sys" as my bus driver (see Supporting Multifunction Devices in DDK) and then writting a functional driver that supports my 3 I/O channels.

I dont understand what the pros/cons are for having a bus driver component or not.

It seems like it would be much easier just to have the one driver object with multiple device objects.

Thanks, you again for your help, there seems to be lots of options,

Bill [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: WDM PCI Driver Design Toby Opferman 11:47 30 May '05

Page 32: The Code Project - Driver Development Part 2: Introduction to

More references from MSDN:

Supporting Multi-Function Devices[^]

Multi-Function Design and Architecture[^]

Designing Multi-Function Devices[^]

Multi-Function PCI Devices[^]

I myself do not write drivers for physical hardware, however the DDK documentation as usual diverges making it look like you need to write a bus driver. Above, if you look at the design documents they specify that if your device supports multiple functions that you need to write a bus driver to enumerate each one. Each of these devices must also be treated as independent devices (This does not mean that they cannot have the same device driver though, just means you need to install them seperately, seperate .INF files, etc.). However, if you read the last document on "PCI Devices" it states as I thought before about using the PCI bus driver:

"If a multifunction PCI device conforms completely to the PCI multifunction standard, the PCI bus driver enumerates the individual functions. The PCI bus driver manages the fact that there is more than one function residing at a single device location. To the rest of the system, the individual functions operate like independent devices."

So, it seems that if your PCI device follows the PCI specification for multiple devices, then you don't have to write a bus driver since their driver can enumerate the devices on the card. If it does not then you will have to write some type of bus driver to enumerate those devices.

Technically speaking though, if you really wanted to you could simply get completely out of this model and just install a device driver that would just talk to your device. Windows PNP manager simply wouldn't know about it and wouldn't show your devices in the device manager. However, it would still work fine. I'm not suggesting this for a final solution but if you get bogged down trying to figure this out you could just get your devices working and conforming to act as a serial device and working, then going back and fitting your devices into the model. This is just an option if you think that this may be bogging you down too much. You would also probably get familar with everything by the end of getting it working and probably plug right into the model pretty easy.

8bc7c0ec02c0e404c0cc0680f7018827ebee

Page 33: The Code Project - Driver Development Part 2: Introduction to

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Example2 Stop driver doesnt work on XP Bill Hase 18:12 27 May '05

I have been trying to use your little program in LoadDriver. The load works fine but when it makes the call "ControlService" it gets a 1052 error saying it cant stop the driver.

I thought since the unload function was provided it should stop?

How else can I unload the driver and reload a new version with changes without rebooting?

Thanks,

Bill

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Toby Opferman 1:59 28 May '05

I was using XP to develop the sample. In order for the driver to unload all handles to any device created by that driver need to be closed.

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Bill Hase 12:36 30 May '05

I tested the example on my Win2000 box and after commenting out "CloseServiceHandle" just before "DeleteService" the driver loads and unloads fine. I then went back to my WinXP SP2 box that is running a checked version of windowsand the "ControlService" call returns "1052 ERROR_INVALID_SERVICE_CONTROL".

If I then try to load the driver again it fails because it is already loaded. So in order to test new changes I have to reboot WinXP. This of course is a pain. I cant find the driver in the device manager since I am bypassing Plug and Play.

Is there another way to unload the driver?

Thanks again,

Bill

Page 34: The Code Project - Driver Development Part 2: Introduction to

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Toby Opferman 13:02 30 May '05

Yes, that is a bug in the code the deleteservice should actually come before the close handle I think it is fixed in the later versions of the code.

You are using the example2 unchanged? NT Style drivers usually are unloaded in this manner where WDM style drivers are unloaded upon device removal.

Try the code from a later example, part 5. My test machine is actually Windows XP SP2 so this should work, although I don't run checked builds of windows.

Here is an example of using the API on MSDN, but for a service:Stopping a Service[^]

The only other method I am aware of is undocumented:

NtUnloadDriver[^]

Are there any handles open to the driver and have you changed any of the start values on the driver?

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Toby Opferman 13:17 30 May '05

I will note one thing, if you changed the driver and added an "AddDevice" entry point you will most likely not be able to unload the driver using this method. That is then where "device removal" and the PNP manager would need to unload you (and actually load you).

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Bill Hase 16:27 31 May '05

Page 35: The Code Project - Driver Development Part 2: Introduction to

I took example5 unmodified and started the kernel driver "netdrv.sys" on a Win2000 machine and it started and stopped without any problem. I then rebuilt the code and reran it on WinXP and it fails (like example2) to stop the device via "ControlService(hService, SERVICE_CONTROL_STOP, &ss)" with error 1052. I did change the makefiles to use the WINDDK build utilities but that was all I changed.

You may not notice the problem with your load.c task since it ignores errors. I added some error checking ( Here is code ).--------------------------------------------------------------------------

/*********************************************************************** * Use this for initial device testing.* **********************************************************************/

#include #include #include

void printUsage( const char* programName ){printf( "\n\nUSAGE: %s 'DriverName' 'c:\\DriverName.sys'\n\n", programName );exit(2);}

/********************************************************** Main Function Entry**********************************************************/int _cdecl main(int argc, char *argv[]){HANDLE hSCManager;HANDLE hService;SERVICE_STATUS ss;char deviceName[255];char deviceFullPath[255];

if(argc < 3) {printUsage( argv[0] );

Page 36: The Code Project - Driver Development Part 2: Introduction to

}

strcpy( deviceName, argv[1] );strcpy( deviceFullPath, argv[2] );

hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

printf("Load Driver Service for driver='%s'.\n", deviceName );

if(hSCManager){printf("Create Device Service for driver='%s'.\n", deviceName );

hService = CreateService(hSCManager, deviceName, deviceName, SERVICE_START | DELETE | SERVICE_STOP,SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, deviceFullPath, NULL, NULL, NULL, NULL, NULL);

if(!hService){if( GetLastError() == 1073 ){printf("Device Service already exists (ERROR_SERVICE_EXISTS). Will try and open it.\n" );}else{printf("Failed to create server try Open device Service. GetLatError=%u\n",GetLastError() );}hService = OpenService(hSCManager, deviceName, SERVICE_START | DELETE | SERVICE_STOP);}

if(hService){printf("Start device Service for driver='%s'.\n", deviceName );

if( StartService(hService, 0, NULL) ){printf("Press Enter to stop device service for driver='%s'.\n", deviceName );

Page 37: The Code Project - Driver Development Part 2: Introduction to

getchar();

if( !ControlService(hService, SERVICE_CONTROL_STOP, &ss) ){printf("Failed to stop device service! GetLatError=%u\n",GetLastError());}}else{printf("Failed to start device service! GetLatError=%u\n",GetLastError() );}

// if( !CloseServiceHandle(hService) )// {// printf("Failed to close service handle! GetLatError=%u\n", GetLastError() );// }

if( !DeleteService(hService) ){printf("Failed to delete device service! GetLatError=%u\n",GetLastError() );}}else{printf("Failed to open device service handle! GetLatError=%u\n",GetLastError() );}

if( !CloseServiceHandle(hSCManager) ){printf("Failed to close service control handle! GetLatError=%u\n",GetLastError() );}}else{printf("Failed to open service control handle! GetLatError=%u\n",GetLastError() );

Page 38: The Code Project - Driver Development Part 2: Introduction to

}

return 0;}

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Toby Opferman 17:45 31 May '05

Yes, I never checked error codes in the code I just verified it was really unloaded through DbgPrint's and the kernel debugger. The DrvUnload is definately called and the driver is unloaded.

What user are you using, is this an administrator?

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Bill Hase 17:52 31 May '05

Yes the account I use for testing has administrator privileges. [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Toby Opferman 19:11 31 May '05

And you copied the driver to C:\Example.sys? Do you have a website or anywhere you could upload your binaries to so I could give them a test. I run XPSP2 and didn't have a problem so I would like to see if it was something with the build or not.

Perhaps you could do a dumpbin and compare the section headers against mine.

Y:\>dumpbin /headers c:\example.sysMicrosoft (R) COFF Binary File Dumper Version 6.00.8447Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file c:\example.sys

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

Page 39: The Code Project - Driver Development Part 2: Introduction to

14C machine (i386)6 number of sections421E5287 time date stamp Thu Feb 24 17:17:43 20050 file pointer to symbol table0 number of symbolsE0 size of optional header10E characteristicsExecutableLine numbers strippedSymbols stripped32 bit word machine

OPTIONAL HEADER VALUES10B magic #6.00 linker version1600 size of codeE00 size of initialized data0 size of uninitialized data1000 RVA of entry point1000 base of code4000 base of data400000 image base1000 section alignment200 file alignment4.00 operating system version0.00 image version4.00 subsystem version0 Win32 version7000 size of image400 size of headersF278 checksum1 subsystem (Native)0 DLL characteristics100000 size of stack reserve1000 size of stack commit100000 size of heap reserve1000 size of heap commit0 loader flags10 number of directories0 [ 0] RVA [size] of Export Directory40BC [ 3C] RVA [size] of Import Directory

Page 40: The Code Project - Driver Development Part 2: Introduction to

0 [ 0] RVA [size] of Resource Directory0 [ 0] RVA [size] of Exception Directory0 [ 0] RVA [size] of Certificates Directory6000 [ 148] RVA [size] of Base Relocation Directory4070 [ 1C] RVA [size] of Debug Directory0 [ 0] RVA [size] of Architecture Directory0 [ 0] RVA [size] of Special Directory0 [ 0] RVA [size] of Thread Storage Directory0 [ 0] RVA [size] of Load Configuration Directory0 [ 0] RVA [size] of Bound Import Directory4000 [ 64] RVA [size] of Import Address Table Directory0 [ 0] RVA [size] of Delay Import Directory0 [ 0] RVA [size] of Reserved Directory0 [ 0] RVA [size] of Reserved Directory

SECTION HEADER #1INIT nameD1 virtual size1000 virtual address200 size of raw data400 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers60000020 flagsCodeExecute Read

SECTION HEADER #2PAGE nameE46 virtual size2000 virtual address1000 size of raw data600 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers60000020 flags

Page 41: The Code Project - Driver Development Part 2: Introduction to

CodeExecute Read

SECTION HEADER #3.text name2A4 virtual size3000 virtual address400 size of raw data1600 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers60000020 flagsCodeExecute Read

SECTION HEADER #4.rdata name350 virtual size4000 virtual address400 size of raw data1A00 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers40000040 flagsInitialized DataRead Only

Debug Directories

Type Size RVA Pointer------ -------- -------- --------cv 49 00000000 2800 Format: NB10, 421e4919, 4, C:\Programming\development\DEBUG\bin\SYMBOLS\example.PDB

SECTION HEADER #5.data name664 virtual size5000 virtual address

Page 42: The Code Project - Driver Development Part 2: Introduction to

800 size of raw data1E00 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbersC0000040 flagsInitialized DataRead Write

SECTION HEADER #6.reloc name168 virtual size6000 virtual address200 size of raw data2600 file pointer to raw data0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers42000040 flagsInitialized DataDiscardableRead Only

Summary

1000 .data1000 .rdata1000 .reloc1000 .text1000 INIT1000 PAGE

8bc7c0ec02c0e404c0cc0680f7018827ebee [Reply][Email][View Thread][Get Link] [Modify|Delete]

Re: Example2 Stop driver doesnt work on XP Bill Hase 12:15 2 Jun '05

Page 43: The Code Project - Driver Development Part 2: Introduction to

It is definately different than mine. I dont have a web site or FTP site. Could you send me a real email address and I could send my compiled version? I also have my 2 virtual COM port thing working ( I want to add support for control lines next so I can connect 2 hyper terminals to each other and type back and forth) and I was wondering if you would like a copy of it to post as a simple example for other newbies. Let me know. My email is [email protected].

Here is my dump:

C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin>dumpbin.exe /headersC:\WINDDK\2600.1106\samples\driverdev_src5\netdrv\objchk_wxp_x86\i386\netdrv.sys

Microsoft (R) COFF/PE Dumper Version 7.10.3077Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file C:\WINDDK\2600.1106\samples\driverdev_src5\netdrv\objchk_wxp_x86\i386\netdrv.sys

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES14C machine (x86)5 number of sections429CC9E5 time date stamp Tue May 31 14:32:37 20050 file pointer to symbol table0 number of symbolsE0 size of optional header10E characteristicsExecutableLine numbers strippedSymbols stripped32 bit word machine

OPTIONAL HEADER VALUES10B magic # (PE32)7.00 linker version3580 size of code400 size of initialized data0 size of uninitialized data34EC entry point (000134EC)

Page 44: The Code Project - Driver Development Part 2: Introduction to

300 base of code1100 base of data10000 image base (00010000 to 00013C7F)80 section alignment80 file alignment5.01 operating system version5.01 image version1.10 subsystem version0 Win32 version3C80 size of image300 size of headers4409 checksum1 subsystem (Native)2000 DLL characteristicsWDM Driver40000 size of stack reserve1000 size of stack commit100000 size of heap reserve1000 size of heap commit0 loader flags10 number of directories0 [ 0] RVA [size] of Export Directory3610 [ 3C] RVA [size] of Import Directory0 [ 0] RVA [size] of Resource Directory0 [ 0] RVA [size] of Exception Directory0 [ 0] RVA [size] of Certificates Directory3A00 [ 1C8] RVA [size] of Base Relocation Directory11A0 [ 1C] RVA [size] of Debug Directory0 [ 0] RVA [size] of Architecture Directory0 [ 0] RVA [size] of Global Pointer Directory0 [ 0] RVA [size] of Thread Storage Directory0 [ 0] RVA [size] of Load Configuration Directory0 [ 0] RVA [size] of Bound Import Directory1100 [ 98] RVA [size] of Import Address Table Directory0 [ 0] RVA [size] of Delay Import Directory0 [ 0] RVA [size] of COM Descriptor Directory0 [ 0] RVA [size] of Reserved Directory

SECTION HEADER #1.text name

Page 45: The Code Project - Driver Development Part 2: Introduction to

DBE virtual size300 virtual address (00010300 to 000110BD)E00 size of raw data300 file pointer to raw data (00000300 to 000010FF)0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers68000020 flagsCodeNot PagedExecute Read

SECTION HEADER #2.rdata name136 virtual size1100 virtual address (00011100 to 00011235)180 size of raw data1100 file pointer to raw data (00001100 to 0000127F)0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers48000040 flagsInitialized DataNot PagedRead Only

Debug Directories

Time Type Size RVA Pointer-------- ------ -------- -------- --------429CC9E5 cv 6A 000011CC 11CC Format: RSDS, {B9A1F795-B165-44F6-8C26-D66AAF6B69E2}, 1, C:\WINDDK\2600~1.110\samples\driverdev_src5\netdrv\objchk_wxp_x86\i386\netdrv.pdb

SECTION HEADER #3PAGE name21B7 virtual size1280 virtual address (00011280 to 00013436)2200 size of raw data

Page 46: The Code Project - Driver Development Part 2: Introduction to

1280 file pointer to raw data (00001280 to 0000347F)0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers60000020 flagsCodeExecute Read

SECTION HEADER #4INIT name550 virtual size3480 virtual address (00013480 to 000139CF)580 size of raw data3480 file pointer to raw data (00003480 to 000039FF)0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbersE2000020 flagsCodeDiscardableExecute Read Write

SECTION HEADER #5.reloc name212 virtual size3A00 virtual address (00013A00 to 00013C11)280 size of raw data3A00 file pointer to raw data (00003A00 to 00003C7F)0 file pointer to relocation table0 file pointer to line numbers0 number of relocations0 number of line numbers42000040 flagsInitialized DataDiscardableRead Only

Summary

Page 47: The Code Project - Driver Development Part 2: Introduction to

180 .rdata280 .relocE00 .text580 INIT2200 PAGE

[Reply][Email][View Thread][Get Link] [Modify|Delete]

Last Visit: 5:08 Tuesday 30th August, 2005 First Prev Next

General comment News / Info Question Answer Joke / Game Admin message

All Topics, MFC / C++ >> System >> Device Drivers Updated: 5 Mar 2005 19:36

Article content copyright Toby Opferman, 2005everything else Copyright © CodeProject, 1999-2005.

Advertise on The Code Project | Privacy

The Ultimate Toolbox • MSDN Communities | ASP Alliance • Developer Fusion • Developersdex • DevGuru • Programmers Heaven • Planet Source Code • Tek-Tips Forums •