I'm using some global variables in iokit based driver i.e. outside the main class instance. However, this cause some unexpected panics upon driver startup due to using uninitialize global variables or attempt to double free a global variable upon teardown.
What is the life cycles of global variables in iokit driver lifecycle ? if i set a global variable upon declaration,
For example, if i've got global variable from type lck_grp_t * my_lock_grp
...
Can i assume my global variable is already allocated and ready to be set when my iokit driver reaches the ::start
method ?
(my_lock_grp = lck_grp_alloc_init("my-locks", my_lock_grp_attr);
)
Can i assume my global variable is still valid when I attempt to release it on my iokit ::free
method? (lck_grp_free(my_lock_grp)
)
And the general question is what is the life-cycle of global variables in iokit based driver compared to the driver instance itself.
The lifetime will definitely be the same as the lifetime of the kext. IOKit init/start/stop/free functions on your classes will be happening between the kext start and stop functions (you may not have explicit kext start & stop functions), and global constructors are run before the kext start function, and likewise global destructors are run after the kext stop function. The memory allocation/deallocation for global/static variables is done by the dynamic kernel linker at the same time as the kext's code itself is loaded and unloaded.
There are 3 things I can think of:
The IOService
start()
and free()
functions are not matched - free()
is called even if start()
was never called. So for example if you have a probe()
function, and this is called and returns nullptr
, then start()
is never called, but free()
definitely will be, and it tries to free a lock group that was never allocated. Similarly if an init()
function returned false - start()
will never run, but free()
will. The equivalent of free()
is the init()
family of member functions, so only unconditionally destroy (no nullptr check) in free()
what is unconditionally created in all possible init…
functions.
start()
can be called any number of times on different instances, so if you always run my_lock_grp = lck_grp_alloc_init()
in start()
and 2 instances are created, my_lock_grp
only remembers the last one, so if then both instances of your class are freed, you end up trying to free one lock group twice and the other not at all. This is bad news obviously. For initialising/destroying truly global state, I recommend using kext start and stop functions or global constructors/destructors.
Otherwise I suspect you might be running into a situation where some other part of the running kernel still has a dangling reference past the point where your kext has already been unloaded, for example if you created a new kernel thread and this thread is still running, or if you've not deregistered all callbacks you registered, or if a callback has been deregistered but is not guaranteed to have completed all invocations. (kauth listeners are notorious for this latter situation)
If none of those sound like they might be the problem, I suggest posting the affected code and the panic log, maybe we can make more sense of the problem if we have some hard data.