Wednesday, December 20, 2006

DeviceMock. Concerned IRP parameters

In general, mock objects are configurable with names of methods are expected to be called by the SUT and parameter values of these methods. A call or series of calls of an expected method and list of the corresponding parameter values is called as an expectation.
In case of the driver, IRPs sent to the device are used instead of methods calls. So expectation will consist of an IRP along with their parameters that is expected to be sent to the device by the SUT.
Thus we need to discover IRP parameters used to resolve whether the considered IRP matches one of the expectations or does not.
The first and obvious candidates to these parameters are the major and minor function codes. These codes are hold by the MajorFunction and MinorFunction fields of the IO_STACK_LOCATION structure. The IO_STACK_LOCATION structure represents an IRP subrequest that contains data are specific to the device handling the IRP among other devices driver stack contains. To obtain the IO_STACK_LOCATION pointer the device invokes the IoGetCurrentIrpStackLocation function.
Besides the function codes, the IO_STACK_LOCATION contains other parameters that are specific to the major function code of the IRP. There are three major functions code whose parameter are to be considered. These are IRP_MJ_READ, IRP_MJ_WRITE and IRP_MJ_DEVICE_CONTROL functions code. An IRP with the IRP_MJ_DEVICE_CONTROL major function code has the Parameters.DeviceIoControl field of the IO_STACK_LOCATION structure. This field is an anonymous structure, which has the IoControlCode field that indicates I/O control code used to select an operation the device should perform.
Other parameters of IRPs with the mentioned functions codes depends on what of the data transfer mechanisms is used. There are three data transfer mechanisms:
  • Buffered I/O
    • For the IRP_MJ_READ and IRP_MJ_WRITE requests this mechanism is used when the Flags field of the DEVICE_OBJECT structure has the DO_BUFFERED_IO flag set. AssociatedIrp.SystemBuffer field of the IRP points to copy of the user-mode buffer. The Parameters.Read.Length and Parameters.Write.Length fields of the of the IO_STACK_LOCATION structure indicate the size in bytes of the buffer.

    • For the IRP_MJ_DEVICE_CONTROL the used mechanism is defined by the Method field of the I/O control code.

      • When this field is set to the METHOD_BUFFERED value, the AssociatedIrp.SystemBuffer field of the IRP points to copy of the user-mode buffer. The Parameters.DeviceIoControl.InputBufferLength or Parameters.DeviceIoControl.OutputBufferLength fields of the IO_STACK_LOCATION structure indicate how many bytes the device may accordingly read or write from or to the buffer depending on the request type.
      • When the Method field is set to the METHOD_OUT_DIRECT value, the AssociatedIrp.SystemBuffer field of the IRP point to copy of user-mode buffer. The Parameters.DeviceIoControl.InputBufferLength field of the IO_STACK_LOCATION structure indicates how many bytes the device may read from the buffer. Transfering data from the device to caller is performed using the Direct I/O mechanism as will be described later.
      • When the Method field is set to the METHOD_IN_DIRECT value, the AssociatedIrp.SystemBuffer field of the IRP point to copy of user-mode buffer. The Parameters.DeviceIoControl.OutputBufferLength field of the IO_STACK_LOCATION structure indicates how many bytes the device may write to the buffer. Transfering data to the device from caller is performed using the Direct I/O mechanism as will be described later.
  • Direct I/O
    • For the IRP_MJ_READ and IRP_MJ_WRITE requests this mechanism is used when the Flags field of the DEVICE_OBJECT structure has the DO_DIRECT_IO flag set. The MdlAddress field of the IRP specifies a MDL that describes memory pages of the user-mode data buffer.
    • For the IRP_MJ_DEVICE_CONTROL the used mechanism is defined by the Method field of the I/O control code.
      • When the Method field is set to the METHOD_OUT_DIRECT value, the MdlAddress field of the IRP specifies a MDL that describes memory pages of the user-mode data buffer. The IRP handler stores data transferred from the device to this buffer.
      • When the Method field is set to the METHOD_IN_DIRECT value, the MdlAddress field of the IRP specifies a MDL that describes memory pages of the user-mode data buffer. The buffer contains data to be transferred by the IRP handler to the device.
  • Neither Buffered nor Direct I/O.
    • For the IRP_MJ_READ and IRP_MJ_WRITE requests this mechanism is used when the Flags field of the DEVICE_OBJECT structure has neither the DO_DIRECT_IO or DO_BUFFERED_IO flag set. The UserBuffer field of the IRP contains address of the user-mode buffer. The Parameters.Read.Length or Parameters.Write.Length fields of the IO_STACK_LOCATION structure indicate how many bytes the device may accordingly read or write from or to the buffer depending on the request type.
    • For the IRP_MJ_DEVICE_CONTROL the used mechanism is used when the Method field of the I/O control code has the METHOD_NEITHER value. The Parameters.DeviceIoControl.Type3InputBuffer field of the IO_STACK_LOCATION structure points to the user-mode input buffer. The Parameters.DeviceIoControl.InputBufferLength field of the IO_STACK_LOCATION structure indicates how many bytes the device may read from the input buffer. The user-mode output buffer is indicated with the UserBuffer field of the IRP. The number of bytes the device may write to this buffer is specified in the Parameters.DeviceIoControl.OutputBufferLength field of the IO_STACK_LOCATION structure.
Besides the IRP parameters themselves it would be useful to add constraints for the data contained in the input buffers the IRP specifies. However, it will be a different task.

Solution of the IoDeleteSymbolicLink issue

Yesterday, I faced with the IoDeleteSymbolicLink issue. Alexander A. Telyatnikov suggested me an idea that helped me to solve that issue.
The cause of the issue consists in the existing of multiple instances of the \DosDevices directory, where I created the symbolic link. Each of the user logged in has a distinct instance. The system operates with its own global instance of the \DosDevices, which is the alias of the \Global?? directory.
I created the symbolic link in the IRP_MJ_DEVICE_CONTROL handler, which is executed in an user process context. So the link was created in one of the users' \DosDevices directories, each of those have the \?? alias. However the DriverUnload routine is executed in the system context, so the link is being deleted from the \Global?? directory. Since there is no links with the specified name the IoDeleteSymbolicLink returns the STATUS_OBJECT_NAME_NOT_FOUND status code.
There is a good article that explains the difference between the local and global instances of the \DosDevices directory.
On the whole, to solve this issue I changed the link path from the \DosDevices to the \DosDevices\Global. Thus, the symbolic link to the device object is always created in the global \DosDevice directory, which is the same for different execution contexts.

Tuesday, December 19, 2006

IoDeleteSymbolicLink in DriverUnload

I have experienced a strange bug. When I am trying to delete an symbolic link to the device object in the DriverUnload function, the IoDeleteSymbolicLink fails with the STATUS_OBJECT_NAME_NOT_FOUND status code. However, the symbolic link being deleting exists. I can assure that with the objdir.exe utility. Moreover, when I am deleting another symbolic link in the same DriverUnload function it is deleted without any problem.
Trying to delete the undeleted link when the driver is being loaded again completes perfectly.
The only difference between the successfully deleted and failed links is that the first one and the device object it references were created within the DriverEntry whereas the failed link was created in the function dispatching the IRP_MJ_DEVICE_CONTROL IRPs.
Certainly, I have been doubly checking that the name of the symbolic link passed to the IoCreateSymbolicLink and the IoDeleteSymbolicLink function is the same. Also I delete the link before the device object. Thus, I have no more idea what I am doing wrong. I am going to investigate this issue with the debugger tomorrow.

Sunday, December 17, 2006

DeviceMock Considerations, part 1

Before starting to develop the DeviceMock I tried to recall and systematize what I know about collaboration between the Win32 API subsystem and a kernel mode driver.
There are five main API functions are intended to interoperate with drivers. These are CreateFile, ReadFile, WriteFile, DeviceIoControl and CloseHandle. All of them except the DeviceIoControl are also used to work with files. Point is that the collaboration between an user mode application and a device is accomplished using the corresponding file object.
The mentioned functions are used by the application as described below:
  • To start working with a device the user mode application should open the corresponding file object first. That is done with the CreateFile function. When the application invokes the CreateFile function the Win32 I/O manager opens the file object and send an IRP with the IRP_MJ_CREATE major function code.
  • To read data from the device the application uses the ReadFile function using the file object handled returned by the CreateFile function. Invoking of the ReadFile function causes sending an IRP with the IRP_MJ_READ major function code to the device.
  • To write data from the device the application invokes the WriteFile function. As a result, the driver receives an IRP with the IRP_MJ_WRITE major function code.
  • When the application needs to sent a custom request to the device, i.e. queries the device for its status or changes the device work mode, it invokes the DeviceIoControl function. An IRP with the IRP_MJ_DEVICE_CONTROL is sent to the device in this case.
  • When the application finishes its work with the device it should close the file object handle using the CloseHandle function. If the closed handle was the last one of the file object, the I/O manager sends an IRP with the IRP_MJ_CLEANUP major function code. On receiving this IRP the driver should complete all pending IRPs correspond to the specified file object and release all resources associated with those requests. When driver completes all IRPs the manager destroys the file object and sends an IRP with the IRP_MJ_CLOSE major function code.
The IRPs are designed to be handled by a stack of devices. In my practice I have never worked with such kind of IRPs. The main scenario I used to is when an user-mode application sends requests to a device and the device handles these requests on its own. In other words, the IRP sent to the device is allocated only with one subrequest. If there were a filter driver that attached another device on the top of mine, the IRP would be allocated with two subrequests and so on. The number of devices attached down to this own is specified in the StackSize field of the DEVICE_OBJECT structure.
Thus, there are two scenarios that the DeviceMock may implement:
  • The first scenario assumes no real devices presented on the system. The following steps are performed in this case:
    1. A unit test requests the DeviceMock to create a mock device, as result the DeviceMock creates a mock device to substitute the real obe and the corresponding file object.
    2. Next, the unit test provides the mock device with IRPs data are expected to be sent to the device by the SUT when the test is being executed. Along with the expected IRPs the corresponding device reaction is to be specified. The possible kinds of device reactions will be described later.
    3. Then, when the SUT is being exercised it invokes the API functions to collaborate with the device. These functions causes sending IRPs with the corresponding function codes to the device. The device matches the received IRPs with the expected ones using either IRPs arrival order or their parameters and reacts accordingly.
    4. On the verification phase the test checks whether all of expected IRPs and only expected IRPs are received by the device.
  • The second scenario differs from the first one in way that the mock devices are filter ones attached above the real devices. So, it is not necessary to specify device reaction for each of the expected IRPs on the step 2 of the previous scenario. Specifying of the device reaction on the IRPs is not mandatory is this case. If no reaction is specified for an expected IRP the IRP will be forwarded to the real device. The same approach may be applied to IRPs are not match to the expected ones.

Saturday, December 16, 2006

Idea of the Win32 Device Mock Object

I am going to develop some kind of a mock object framework that is intended to mock up a Win32 device. This mock should be useful for developers who write user-mode code using Win32 API to collaborate with a kernel device driver and like to use the Test Driven Development or Behaviour Driven Development approaches in their activity. Collaborating with a real device has a lot of drawbacks, so it would be very useful to substitute the device with some kind of Test Double in the code's Unit Tests.

I have the following vision of the concerned framework:
The framework is implemented as a Win32 kernel mode driver. Initially the driver creates an auxiliary DEVICE_OBJECT and corresponding file object used by the Unit Test to setup the mockup withing the Fixture Setup Phase of the Four Phase Test approach. In general, setting up the DeviceMock consists of specifying expected I/O request packets (IRPs) to be sent to the driver from the SUT code and expected reaction of the driver on those IRPs. Specifying of the expected IRPs assumes also the file object that the IRPs are targeted to. So the Unit Test should specify this file object and the DeviceMock will create it and the device object that the file object correspond to.
On the Exercise SUT Phase, the SUT iteracts with the mocked device as it were a real device.
Then, on the Result Verification Phase the driver's verifies that the sent IRPs match to the expected ones. At last, on the Fixture Teardown Phase the driver will deletes all created file and device objects as well as releasing of other resources allocated at the Fixture Setup Phase.

The DeviceMock framework is seen as very low-level mock object. It is not expected that all unit tests of the whole SUT will rely only on the DeviceMock. The DeviceMock will substitute the interaction with the mocked device on very low level. This interaction consist of operating with Win32 OS primitives like file handles, raw memory buffers etc. In upper layers of the SUT this primitives should be wrapped, transformed or aggregated as parts of the application domain objects. Thus for the domain objects themselves it would be better to implement a wrapper of the collaborated device and well as a mock object of this wrapper. In this scenario, the DeviceMock framework will be used only to develop and test the device wrapper. The upper domain object and their unit tests will use the mock of the device wrapper implemented with common Mock Object Frameworks, like the Rhino Mocks.