Confuse at Device Context and ioctl in WDF driver

Hello OSR community, of course i’m newbie in kernel programming. I have some question after tried a lot:
I have to write some code to solve a simple task: take communication between User-Client and Driver (Non PnP Driver), no device plug in.
I referenced sample “nonpnp” from MS’s git (1) and try to rewrite it but only support BUFFERED_IO method. And… it have an error from client: 0x1 (ERROR_INVALID_FUNCTION).
After compare with MS’s codes, i found that my codes do not have “Device Context” variable, does it the main reason?. So, i wanna ask you:
_
1.What is extractly “Device Context”? Does it useful in my project? I just know that is something like “WDM Extension” but have no clues how to use it and its difference on every samples.:frowning:
2.KMDF is strong when programming in C++? I find many samples and source code, but they use only C and not C++.
_
(1)link sample:
https://github.com/microsoft/Windows-driver-samples/tree/master/general/ioctl

thank you for reading my gg trans post. :smiley:
(My codes is in attached files)

You’re using KMDF (which is the correct thing to do). KMDF handles the device context for you. It’s wrapped up in the WDFDEVICE.

What did you do that got ERROR_INVALID_FUNCTION? I may be wrong, but it’s possible you have a misunderstanding. Remember that DeviceIoControl does not return an error code. It returns 1 for success, 0 for failure. If it returns failure, THEN you call GetLastError() to find the error code. So, if you get a 1 back from DeviceIoControl, it means everything worked fine, NOT that you had ERROR_INVALID_FUNCTION.

What is the device context? The device context holds all of the global data that a driver needs in order to service its hardware. Remember that each driver can support multiple pieces of hardware. If you should happen to have 5 identical network cards, for instance, the network driver will only be loaded once. But as each device comes on line, it gets a new device context. The driver will store the register addresses and all the current state in the device context. So, when an ioctl comes in, the ioctl handler is passed the corresponding device context, so it knows which device it is working on.

The debate of C vs C++ in the kernel is a long one. For a long time, C++ was officially “discouraged”, even though several of the device classes always used it. Streaming drivers have ALWAYS been in C++, for example. These days, C++ is officially supported. KMDF itself is actually written in C++, but they expose a C API to support all of the C programmers. You should use whatever language makes you the most comfortable. I always use C++ for my drivers.

1 Like

Wow, i got a lot information from you @Tim_Roberts .
Yes, i got error code from GetLastError() when DeviceIoControl return false. I wanna ask you more:
=> Does Device Context is a “must have” in my Project? Even it is NonPnP task?
i mean a driver can run properly without it?. I dont have any hardware to work with it, so…

You are thinking in totally the wrong direction here. A device context is not some burden to be regretted. It is a fundamental part of the driver model. Don’t waste your time trying to avoid it.

Is it theoretically possible to write a driver without one? Yes, but that means you have to put all of your state data in global variables, and that is extremely bad programming practice. If I found a consultant writing a driver like that, I’d fire them.

The device context has nothing to do with hardware. As some point, you will probably want to have two instances of your driver. If you have done things correctly, by keeping all of your state in a device context, that change will be seamless. If you don’t, that change will be very painful.

1 Like

thank you,
still wanna ask you more:

  • In a NonPnP Driver, does Device Context will be treat as “static struct”?
    Because without device plug in, so driver do not need to create new Device Context, and the contexts was assigned with Device will be like a “global static struct” for that driver?
  • Could i put sensitive data in Device Context (like strings)?
    sorry, my knowledge is bad. :neutral:

The lifetime of any Context in WDF is identical to the lifetime of the object with which it is associated. So, the lifetime of your Device Context is exactly the same as the lifetime of your WDF Device.

It’s not exactly like a static struct in global memory, because (presumably) your driver is unloadable… and when you unload it you destruct your WDF Device, and when your WDF Device is destroyed your Device context is deallocated.

Peter

1 Like

There is no difference in device context handling between a PnP and non-PnP driver. There is one device object per instance (no matter what kind of driver), and the device object points to the device context (called “extension” in the device object). The context is allocated when the device object is created, and freed when the device object is deleted. Drivers of all kinds have multiple instances, and the device object and device context are what keeps them separate.

The device context lives in non-paged kernel memory. Whether that’s sufficient for “sensitive data” is up to you. I certainly don’t know where else you’d put it.

1 Like

:smiley:
I’m working on a simple project that notify when new process creating, and use Device Context to store some data, i’m using PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE) in my project.
The problem is: in OnProcessNotify callback function, there is no way i can get Device Context to get/set my data.
Some solutions i can think of:

  1. Do not use Device Context anymore, use a global data struct to store data instead. It can access easily.
  2. Set WDFDevice hDevice (use for WdfDeviceCreate() function) as a global variable, so i can get Device Context via WdfObjectGetTypedContext() and hDevice as a parameter. Then, my data is accessible (sound not a good solution :().

Or have a better idea that i dont know yet? Please share your thinks, thanks you.

If your target is Win 10, you can use PsSetCreateProcessNotifyRoutineEx2, which does have a context parameter.

If you need to target older systems, then yes, you’re stuck with storing your WDFDEVICE in a global variable, or in a WDFDRIVER context, which is equivalent. That was a kernel design flaw. Callbacks should always have a context parameter.

:slight_smile:
I’m still curious (for production drivers):

  1. I should use at less as possible global variables in driver, right? Like this case, only one global variable (static hDevice) is enough?
  2. What problems if i use all data as global variables like class, struct for my driver tasks?
  3. How about private data in driver? They should be place in class, struct Or in context areas (Device, Queue, Request…) to processing? (data requests from user doesn’t count, only privates).
    thank you. :smiley:

We’re getting a little bit into programming religion here. Father Tim of the Church of the Holy Semicolon.

The rule is, “avoid global variables unless absolutely necessary”. This is a case, because of a poorly design API, where a global is absolutely necessary.

This is a generalization of the rule that data should only be visible to components that need it. If a variable is only used in one function, make it a local or a static, not a global or a class member. If it’s only needed in one file, make it a file static, not a global extern.

And why is this? Because it helps to prevent accidental state changes. If filea.cpp has a file static, then nothing in fileb.cpp can accidentally change that variable. It forces you to think about how your state data is managed and how access is controlled. Overuse of globals just makes it too easy for functions to have unexpected side effects, and that makes proving correctness vastly more difficult.

Like most programming rules, these aren’t intended to prevent useful work. It should just make you uncomfortable when you have to violate them, like a nagging itch. Violating them should never be the first choice.

To answer your other questions, yes, I would put the WDFDEVICE in a global, and store the rest of my state data in a context structure attached to that WDFDEVICE, just like any other KMDF driver. That lets you develop good habits. The context structures in KMDF are one of its best features.

State data needed by multiple handlers in your driver should be in the WDFDEVICE context. I have often used WDFREQUEST contexts for data that is specific to each request. I have often used WDFFILEOBJECT contexts for data that’s specific to a file handle. I have very occasionally used a WDFQUEUE context or a WDFDMATRANSACTION context as well. Again, it’s the principle of least visibility: data should be visible to the objects that need to use it, and not any more than that.

1 Like

thank you so much for all your replies, i got some new problems, and want to hear consult from you:

  1. Does contexts have a max size value? I mean, put all data in a context with huge size isn’t a good idea, right? I need some contexts with their own data for some specified purposes, but how about put all data in one context? Does it have disadvantage?.
  2. Should i combine many features in one driver file (like working with process, thread, network, memory…) ? Or i need to split them to other drivers by their functionality like drvnet.sys, drvproc.sys, drvmem.sys… etc?
    Waiting for your reply. :smiley:

On the question of multiple drivers what are you trying to do? You say process, thread and from your questions I am assume you are use PsSetCreateXXXNotifyRoutine calls to get information. Not sure what you think you can monitor on memory. On network are you trying to monitor network traffic? Now beyond this are you going to be looking at other I/O?

If you are just tracking process/thread creation and something on network you can make it all one driver, but as soon as you go off beyond that it is better to be multiple drivers since a network filter is different from a storage filter and different from a file system filter driver. You need to identify what you really want to do here, before defining the architecture.

1 Like

The context is allocated at device creation time from non-paged pool. If you need the data, it doesn’t matter one whit who does the allocation. It’s all the same memory. Now, if you have information that is specific to each request, then it’s better to put that in a request context so it gets freed when the request is completed. But if it’s device-global info, it doesn’t matter.

The decision on whether to have a monolithic driver or a series of small ones is entirely up to you. It is a fact that smaller drivers are easier to debug than larger drivers, but if they share some common code base, then it might make sense to combine them

1 Like

@Tim_Roberts said:
The context is allocated at device creation time from non-paged pool. If you need the data, it doesn’t matter one whit who does the allocation. It’s all the same memory. Now, if you have information that is specific to each request, then it’s better to put that in a request context so it gets freed when the request is completed. But if it’s device-global info, it doesn’t matter.

The decision on whether to have a monolithic driver or a series of small ones is entirely up to you. It is a fact that smaller drivers are easier to debug than larger drivers, but if they share some common code base, then it might make sense to combine them

hi Tim_Roberts , then how shoud we dynamically allocate memory in device context?
base on allocating-system-space-memory
ExAllocatePoolWithTag with PoolType = NonPagedPool for any objects or resources not stored in a device extension or controller extension,
and for other routines, looks like not so Economically.

You use ExAllocatePoolXxxx or WdfMemoryCreate. If you aren’t working with hardware, then you probably don’t need very much NonPagedPool – PagedPool will work just fine.

I mean, put all data in a context with huge size isn’t a good idea, right?

It depends on what your definition of “huge” is… right?

Peter