diff --git a/inc/usersim/ex.h b/inc/usersim/ex.h
index c0fee2b..a78d1a5 100644
--- a/inc/usersim/ex.h
+++ b/inc/usersim/ex.h
@@ -184,6 +184,77 @@ extern "C"
     USERSIM_API void
     ExRaiseDatatypeMisalignment();
 
+    /**
+     * @brief Allocate memory.
+     * @param[in] pool_type Pool type to use.
+     * @param[in] size Size of memory to allocate.
+     * @param[in] tag Pool tag to use.
+     * @param[in] initialize False to return "uninitialized" memory.
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void* usersim_allocate_with_tag(
+        _In_ __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE pool_type, size_t size, uint32_t tag, bool initialize);
+
+    /**
+     * @brief Allocate memory.
+     * @param[in] size Size of memory to allocate.
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void* usersim_allocate(size_t size);
+
+    /**
+     * @brief Reallocate memory.
+     * @param[in] memory Allocation to be reallocated.
+     * @param[in] old_size Old size of memory to reallocate.
+     * @param[in] new_size New size of memory to reallocate.
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate(
+        _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size);
+
+    /**
+     * @brief Reallocate memory with tag.
+     * @param[in] memory Allocation to be reallocated.
+     * @param[in] old_size Old size of memory to reallocate.
+     * @param[in] new_size New size of memory to reallocate.
+     * @param[in] tag Pool tag to use.
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate_with_tag(
+        _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size, uint32_t tag);
+
+    /**
+     * @brief Free memory.
+     * @param[in] memory Allocation to be freed.
+     */
+    void
+    usersim_free(_Frees_ptr_opt_ void* memory);
+
+    /**
+     * @brief Allocate memory that has a starting address that is cache aligned.
+     * @param[in] size Size of memory to allocate
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    USERSIM_API
+    __drv_allocatesMem(Mem) _Must_inspect_result_
+        _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned(size_t size);
+
+    /**
+     * @brief Allocate memory that has a starting address that is cache aligned with tag.
+     * @param[in] size Size of memory to allocate
+     * @param[in] tag Pool tag to use.
+     * @returns Pointer to memory block allocated, or null on failure.
+     */
+    __drv_allocatesMem(Mem) _Must_inspect_result_
+        _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned_with_tag(size_t size, uint32_t tag);
+
+    /**
+     * @brief Free memory that has a starting address that is cache aligned.
+     * @param[in] memory Allocation to be freed.
+     */
+    void
+    usersim_free_cache_aligned(_Frees_ptr_opt_ void* memory);
+
 #if defined(__cplusplus)
 }
 
@@ -207,4 +278,26 @@ ExRaiseAccessViolationCPP();
 USERSIM_API void
 ExRaiseDatatypeMisalignmentCPP();
 
+void usersim_initialize_ex(bool leak_detector);
+void usersim_clean_up_ex();
+
+#ifdef __cplusplus
+#include <memory>
+namespace usersim_helper {
+
+struct _usersim_free_functor
+{
+    void
+    operator()(void* memory)
+    {
+        usersim_free(memory);
+    }
+};
+
+typedef std::unique_ptr<void, _usersim_free_functor> usersim_memory_ptr;
+
+} // namespace usersim_helper
+
+#endif
+
 #endif
diff --git a/inc/usersim/fwp_test.h b/inc/usersim/fwp_test.h
index 02b87f6..b062609 100644
--- a/inc/usersim/fwp_test.h
+++ b/inc/usersim/fwp_test.h
@@ -7,6 +7,11 @@
 #include <stdint.h>
 #include <ws2def.h>
 
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
 typedef struct _fwp_classify_parameters
 {
     ADDRESS_FAMILY family;
@@ -51,4 +56,8 @@ usersim_fwp_sock_ops_v6(_In_ fwp_classify_parameters_t* parameters);
 
 USERSIM_API void
 usersim_fwp_set_sublayer_guids(
-    _In_ const GUID& default_sublayer, _In_ const GUID& connect_v4_sublayer, _In_ const GUID& connect_v6_sublayer);
\ No newline at end of file
+    _In_ const GUID& default_sublayer, _In_ const GUID& connect_v4_sublayer, _In_ const GUID& connect_v6_sublayer);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/inc/usersim/io.h b/inc/usersim/io.h
index 1c403f1..3b8fa0e 100644
--- a/inc/usersim/io.h
+++ b/inc/usersim/io.h
@@ -25,7 +25,7 @@ extern "C"
 
     typedef struct _DEVICE_OBJECT DEVICE_OBJECT;
 
-    typedef struct _DRIVER_OBJECT DRIVER_OBJECT;
+    typedef struct _DRIVER_OBJECT DRIVER_OBJECT, *PDRIVER_OBJECT;
 
     typedef struct _IO_WORKITEM IO_WORKITEM, *PIO_WORKITEM;
 
@@ -68,7 +68,13 @@ extern "C"
     IoGetFileObjectGenericMapping();
 
     USERSIM_API
-    _IRQL_requires_max_(DISPATCH_LEVEL) NTKERNELAPI PEPROCESS IoGetCurrentProcess(VOID);
+    _IRQL_requires_max_(DISPATCH_LEVEL) PEPROCESS IoGetCurrentProcess(VOID);
+
+    typedef struct _IRP* PIRP;
+
+    USERSIM_API
+    _IRQL_requires_max_(DISPATCH_LEVEL) VOID
+    IofCompleteRequest(_In_ PIRP irp, _In_ CCHAR priority_boost);
 
 #if defined(__cplusplus)
 }
diff --git a/inc/usersim/rtl.h b/inc/usersim/rtl.h
index 3ac8f67..b59e3b3 100644
--- a/inc/usersim/rtl.h
+++ b/inc/usersim/rtl.h
@@ -145,14 +145,16 @@ extern "C"
         _Out_ PSTRING destination_string,
         _In_opt_ __drv_aliasesMem PCSTR source_string);
 
-    _IRQL_requires_max_(DISPATCH_LEVEL) _At_(DestinationString->Buffer, _Post_equal_to_(SourceString))
-    _At_(DestinationString->Length, _Post_equal_to_(_String_length_(SourceString) * sizeof(WCHAR)))
-    _At_(DestinationString->MaximumLength, _Post_equal_to_((_String_length_(SourceString) + 1) * sizeof(WCHAR)))
+    _IRQL_requires_max_(DISPATCH_LEVEL) _At_(destination_string->Buffer, _Post_equal_to_(source_string))
+        _At_(destination_string->Length, _Post_equal_to_(_String_length_(source_string) * sizeof(WCHAR)))
+        _At_(destination_string->MaximumLength, _Post_equal_to_((_String_length_(source_string) + 1) * sizeof(WCHAR)))
     USERSIM_API VOID NTAPI
     RtlInitUnicodeString(
-        _Out_ PCUNICODE_STRING destination_string,
+        _Out_ PUNICODE_STRING destination_string,
         _In_opt_z_ __drv_aliasesMem PCWSTR source_string);
 
+    typedef struct _object_attributes OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+
 // Include Rtl* implementations from ntdll.lib.
 #pragma comment(lib, "ntdll.lib")
 
diff --git a/inc/usersim/wdf.h b/inc/usersim/wdf.h
index 76f504a..dd6744d 100644
--- a/inc/usersim/wdf.h
+++ b/inc/usersim/wdf.h
@@ -5,6 +5,7 @@
 // implemented in usersim.dll, and can be used by unit tests.
 #pragma once
 #include "usersim/common.h"
+#include "usersim/io.h" // For IRP
 #include "usersim/rtl.h" // For UNICODE_STRING
 
 #if defined(__cplusplus)
@@ -13,18 +14,17 @@ extern "C"
 #endif
 
     typedef HANDLE WDFDEVICE;
-    typedef struct _wdfdriver WDFDRIVER;
+    typedef HANDLE WDFDRIVER;
 
-    typedef struct _WDF_OBJECT_ATTRIBUTES WDF_OBJECT_ATTRIBUTES, *PWDF_OBJECT_ATTRIBUTES;
+    typedef struct _WDF_OBJECT_ATTRIBUTES
+    {
+        int SynchronizationScope;
+    } WDF_OBJECT_ATTRIBUTES, *PWDF_OBJECT_ATTRIBUTES;
     typedef struct _WDFDEVICE_INIT WDFDEVICE_INIT, *PWDFDEVICE_INIT;
 
 #define WDF_NO_OBJECT_ATTRIBUTES 0
 #define WDF_NO_HANDLE 0
 
-    typedef struct _wdfdriver WDFDRIVER;
-
-    typedef struct _driver_object DRIVER_OBJECT, *PDRIVER_OBJECT;
-
     typedef NTSTATUS(DRIVER_INITIALIZE)(_In_ PDRIVER_OBJECT driver_object, _In_ PUNICODE_STRING registry_path);
 
     typedef struct _WDFDEVICE_INIT WDFDEVICE_INIT, *PWDFDEVICE_INIT;
@@ -44,10 +44,10 @@ extern "C"
         ULONG DriverPoolTag;
     } WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;
 
-    typedef struct _wdfdriver
+    struct _DRIVER_OBJECT
     {
         WDF_DRIVER_CONFIG config;
-    } WDFDRIVER;
+    };
 
 #define WDF_DRIVER_GLOBALS_NAME_LEN (32)
 
@@ -78,6 +78,113 @@ extern "C"
     void
     WDF_DRIVER_CONFIG_INIT(_Out_ PWDF_DRIVER_CONFIG config, _In_opt_ PFN_WDF_DRIVER_DEVICE_ADD evt_driver_device_add);
 
+    typedef NTSTATUS(WdfDriverCreate_t)(
+        _In_ WDF_DRIVER_GLOBALS* driver_globals,
+        _In_ PDRIVER_OBJECT driver_object,
+        _In_ PCUNICODE_STRING registry_path,
+        _In_opt_ PWDF_OBJECT_ATTRIBUTES driver_attributes,
+        _In_ PWDF_DRIVER_CONFIG driver_config,
+        _Out_opt_ WDFDRIVER* driver);
+
+    typedef NTSTATUS(WdfDeviceCreate_t)(
+        _In_ WDF_DRIVER_GLOBALS* driver_globals,
+        _Inout_ PWDFDEVICE_INIT* device_init,
+        _In_opt_ PWDF_OBJECT_ATTRIBUTES device_attributes,
+        _Out_ WDFDEVICE* device);
+
+    typedef _IRQL_requires_max_(DISPATCH_LEVEL)
+        VOID(WdfDeviceInitFree_t)(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ PWDFDEVICE_INIT device_init);
+
+    typedef _Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) PWDFDEVICE_INIT(WdfControlDeviceInitAllocate_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ WDFDRIVER driver,
+        _In_ CONST UNICODE_STRING* sddl_string);
+
+    typedef _IRQL_requires_max_(DISPATCH_LEVEL) VOID(WdfDeviceInitSetDeviceType_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ PWDFDEVICE_INIT device_init,
+        DEVICE_TYPE device_type);
+
+#define FILE_AUTOGENERATED_DEVICE_NAME 0x00000080
+#define FILE_DEVICE_SECURE_OPEN 0x00000100
+
+    typedef _IRQL_requires_max_(DISPATCH_LEVEL) VOID(WdfDeviceInitSetCharacteristics_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ PWDFDEVICE_INIT device_init,
+        ULONG device_characteristics,
+        BOOLEAN or_in_values);
+
+    typedef _Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS (WdfDeviceInitAssignName_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ PWDFDEVICE_INIT device_init,
+        _In_opt_ PCUNICODE_STRING device_name);
+
+    typedef struct _WDF_FILEOBJECT_CONFIG
+    {
+        int unused;
+    } WDF_FILEOBJECT_CONFIG, *PWDF_FILEOBJECT_CONFIG;
+
+    typedef _IRQL_requires_max_(DISPATCH_LEVEL) VOID (WdfDeviceInitSetFileObjectConfig_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ PWDFDEVICE_INIT device_init,
+        _In_ PWDF_FILEOBJECT_CONFIG file_object_config,
+        _In_opt_ PWDF_OBJECT_ATTRIBUTES file_object_attributes);
+
+
+    typedef NTSTATUS(FN_WDFDEVICE_WDM_IRP_PREPROCESS)(_In_ WDFDEVICE device, _Inout_ IRP* irp);
+    typedef FN_WDFDEVICE_WDM_IRP_PREPROCESS* PFN_WDFDEVICE_WDM_IRP_PREPROCESS;
+
+#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
+
+    typedef _Must_inspect_result_ _IRQL_requires_max_(DISPATCH_LEVEL) NTSTATUS (WdfDeviceInitAssignWdmIrpPreprocessCallback_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ PWDFDEVICE_INIT device_init,
+        _In_ PFN_WDFDEVICE_WDM_IRP_PREPROCESS evt_device_wdm_irp_preprocess,
+        UCHAR major_function,
+        _When_(num_minor_functions > 0, _In_reads_bytes_(num_minor_functions))
+            _When_(num_minor_functions == 0, _In_opt_) PUCHAR minor_functions,
+        ULONG num_minor_functions);
+
+    typedef _Must_inspect_result_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS (WdfDeviceCreateSymbolicLink_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ WDFDEVICE device,
+        _In_ PCUNICODE_STRING symbolic_link_name);
+
+    typedef struct _WDF_IO_QUEUE_CONFIG
+    {
+        int unused;
+    } WDF_IO_QUEUE_CONFIG, *PWDF_IO_QUEUE_CONFIG;
+    typedef HANDLE WDFQUEUE;
+
+    typedef _Must_inspect_result_ _IRQL_requires_max_(DISPATCH_LEVEL) NTSTATUS (WdfIoQueueCreate_t)(
+        _In_ PWDF_DRIVER_GLOBALS driver_globals,
+        _In_ WDFDEVICE device,
+        _In_ PWDF_IO_QUEUE_CONFIG config,
+        _In_opt_ PWDF_OBJECT_ATTRIBUTES queue_attributes,
+        _Out_opt_ WDFQUEUE* queue);
+
+    typedef HANDLE WDFOBJECT;
+
+    typedef _IRQL_requires_max_(DISPATCH_LEVEL)
+        VOID(WdfObjectDelete_t)(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFOBJECT object);
+
+    typedef enum _WDFFUNCENUM
+    {
+        WdfControlDeviceInitAllocateTableIndex = 25,
+        WdfDeviceInitFreeTableIndex = 54,
+        WdfDeviceInitSetDeviceTypeTableIndex = 66,
+        WdfDeviceInitAssignNameTableIndex = 67,
+        WdfDeviceInitSetCharacteristicsTableIndex = 70,
+        WdfDeviceInitSetFileObjectConfigTableIndex = 71,
+        WdfDeviceInitAssignWdmIrpPreprocessCallbackTableIndex = 73,
+        WdfDeviceCreateTableIndex = 75,
+        WdfDeviceCreateSymbolicLinkTableIndex = 80,
+        WdfDriverCreateTableIndex = 116,
+        WdfIoQueueCreateTableIndex = 152,
+        WdfObjectDeleteTableIndex = 208,
+        WdfFunctionTableNumEntries = 444,
+    } WDFFUNCENUM;
+
     void
     usersim_initialize_wdf();
 
diff --git a/src/etw.cpp b/src/etw.cpp
index 3a7ad1b..134e2b0 100644
--- a/src/etw.cpp
+++ b/src/etw.cpp
@@ -3,6 +3,7 @@
 
 #include "platform.h"
 #include "usersim/etw.h"
+#include "usersim/ex.h"
 
 typedef struct
 {
diff --git a/src/ex.cpp b/src/ex.cpp
index b0446be..3c95244 100644
--- a/src/ex.cpp
+++ b/src/ex.cpp
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: MIT
 
 #include "fault_injection.h"
+#include "leak_detector.h"
 #include "platform.h"
 #include "kernel_um.h"
 #include "usersim/ex.h"
@@ -15,6 +16,9 @@
 
 // Ex* functions.
 
+extern "C" size_t usersim_fuzzing_memory_limit = MAXSIZE_T;
+usersim_leak_detector_ptr _usersim_leak_detector_ptr;
+
 /***
  * @brief This following class implements a mock of the Windows Kernel's rundown reference implementation.
  * 1) It uses a map to track the number of references to a given EX_RUNDOWN_REF structure.
@@ -299,7 +303,7 @@ ExAllocatePoolUninitializedCPP(
     if (tag == 0) {
         KeBugCheckExCPP(BAD_POOL_CALLER, 0x9B, pool_type, number_of_bytes, 0);
     }
-    return usersim_allocate_with_tag(number_of_bytes, tag, false);
+    return usersim_allocate_with_tag(pool_type, number_of_bytes, tag, false);
 }
 
 _Ret_maybenull_ void*
@@ -318,7 +322,171 @@ ExAllocatePoolWithTagCPP(
     if (tag == 0) {
         KeBugCheckExCPP(BAD_POOL_CALLER, 0x9B, pool_type, number_of_bytes, 0);
     }
-    return usersim_allocate_with_tag(number_of_bytes, tag, true);
+    return usersim_allocate_with_tag(pool_type, number_of_bytes, tag, true);
+}
+
+#define USERSIM_CACHE_LINE_SIZE 64
+
+typedef struct
+{
+    POOL_TYPE pool_type;
+    uint32_t tag;
+} usersim_allocation_header_t;
+
+__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void*
+usersim_allocate_with_tag(
+    _In_ __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE pool_type, size_t size, uint32_t tag, bool initialize)
+{
+    if (size == 0) {
+        KeBugCheckEx(BAD_POOL_CALLER, 0x00, 0, 0, 0);
+    }
+    if (size > usersim_fuzzing_memory_limit) {
+        return nullptr;
+    }
+
+    if (usersim_fault_injection_inject_fault()) {
+        return nullptr;
+    }
+
+    // Allocate space with a usersim_allocation_header_t prepended.
+    void* memory;
+    if (pool_type == NonPagedPoolNxCacheAligned) {
+        // The pointer we return has to be cache aligned so we allocate
+        // enough extra space to fill a cache line, and put the
+        // usersim_allocation_header_t at the end of that space.
+        // TODO: move logic into usersim_allocate_cache_aligned_with_tag().
+        size_t full_size = USERSIM_CACHE_LINE_SIZE + size;
+        uint8_t* pointer = (uint8_t*)_aligned_malloc(full_size, USERSIM_CACHE_LINE_SIZE);
+        if (pointer == nullptr) {
+            return nullptr;
+        }
+        memory = pointer + USERSIM_CACHE_LINE_SIZE;
+    } else {
+        size_t full_size = sizeof(usersim_allocation_header_t) + size;
+        uint8_t* pointer = (uint8_t*)calloc(full_size, 1);
+        if (pointer == nullptr) {
+            return nullptr;
+        }
+        memory = pointer + sizeof(usersim_allocation_header_t);
+    }
+
+    // Do any initialization.
+    auto header = (usersim_allocation_header_t*)((uint8_t*)memory - sizeof(usersim_allocation_header_t));
+    header->pool_type = pool_type;
+    header->tag = tag;
+    if (!initialize) {
+        // The calloc call always zero-initializes memory.  To test
+        // returning uninitialized memory, we explicitly fill it with 0xcc.
+        memset(memory, 0xcc, size);
+    }
+
+    if (memory && _usersim_leak_detector_ptr) {
+        _usersim_leak_detector_ptr->register_allocation(reinterpret_cast<uintptr_t>(memory), size);
+    }
+
+    return memory;
+}
+
+__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void*
+usersim_reallocate(
+    _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size)
+{
+    return usersim_reallocate_with_tag(memory, old_size, new_size, 0);
+}
+
+__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void*
+usersim_reallocate_with_tag(
+    _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size, uint32_t tag)
+{
+    UNREFERENCED_PARAMETER(tag);
+    UNREFERENCED_PARAMETER(old_size);
+
+    if (new_size > usersim_fuzzing_memory_limit) {
+        return nullptr;
+    }
+
+    if (usersim_fault_injection_inject_fault()) {
+        return nullptr;
+    }
+
+    void* p;
+    auto header = (usersim_allocation_header_t*)((uint8_t*)memory - sizeof(usersim_allocation_header_t));
+    if (header->pool_type == NonPagedPoolNxCacheAligned) {
+        uint8_t* pointer = ((uint8_t*)memory) - USERSIM_CACHE_LINE_SIZE;
+        p = _aligned_realloc(pointer, USERSIM_CACHE_LINE_SIZE + new_size, USERSIM_CACHE_LINE_SIZE);
+    } else {
+        uint8_t* pointer = ((uint8_t*)memory) - sizeof(usersim_allocation_header_t);
+        p = realloc(pointer, sizeof(usersim_allocation_header_t)  + new_size);
+    }
+
+    if (p && (new_size > old_size)) {
+        memset(((char*)p) + old_size, 0, new_size - old_size);
+    }
+
+    if (_usersim_leak_detector_ptr) {
+        _usersim_leak_detector_ptr->unregister_allocation(reinterpret_cast<uintptr_t>(memory));
+        _usersim_leak_detector_ptr->register_allocation(reinterpret_cast<uintptr_t>(p), new_size);
+    }
+
+    return p;
+}
+
+__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void* usersim_allocate(size_t size)
+{
+    return usersim_allocate_with_tag(NonPagedPool, size, 'tset', true);
+}
+
+void
+usersim_free(_Frees_ptr_opt_ void* memory)
+{
+    if (memory == nullptr) {
+        return;
+    }
+    auto header = (usersim_allocation_header_t*)((uint8_t*)memory - sizeof(usersim_allocation_header_t));
+    if (_usersim_leak_detector_ptr) {
+        _usersim_leak_detector_ptr->unregister_allocation(reinterpret_cast<uintptr_t>(memory));
+    }
+    if (header->pool_type == NonPagedPoolNxCacheAligned) {
+        uint8_t* pointer = ((uint8_t*)memory) - USERSIM_CACHE_LINE_SIZE;
+        _aligned_free(pointer);
+    } else {
+        uint8_t* pointer = ((uint8_t*)memory) - sizeof(usersim_allocation_header_t);
+        free(pointer);
+    }
+}
+
+__drv_allocatesMem(Mem) _Must_inspect_result_
+_Ret_writes_maybenull_(size) void*
+usersim_allocate_cache_aligned(size_t size)
+{
+    if (size > usersim_fuzzing_memory_limit) {
+        return nullptr;
+    }
+
+    if (usersim_fault_injection_inject_fault()) {
+        return nullptr;
+    }
+
+    void* memory = _aligned_malloc(size, USERSIM_CACHE_LINE_SIZE);
+    if (memory) {
+        memset(memory, 0, size);
+    }
+    return memory;
+}
+
+__drv_allocatesMem(Mem) _Must_inspect_result_
+_Ret_writes_maybenull_(size) void*
+usersim_allocate_cache_aligned_with_tag(size_t size, uint32_t tag)
+{
+    UNREFERENCED_PARAMETER(tag);
+
+    return usersim_allocate_cache_aligned(size);
+}
+
+void
+usersim_free_cache_aligned(_Frees_ptr_opt_ void* memory)
+{
+    _aligned_free(memory);
 }
 
 _Ret_maybenull_ void*
@@ -409,4 +577,21 @@ void
 ExRaiseDatatypeMisalignment()
 {
     ExRaiseDatatypeMisalignmentCPP();
+}
+
+void
+usersim_initialize_ex(bool leak_detector)
+{
+    if (leak_detector) {
+        _usersim_leak_detector_ptr = std::make_unique<usersim_leak_detector_t>();
+    }
+}
+
+void
+usersim_clean_up_ex()
+{
+    if (_usersim_leak_detector_ptr) {
+        _usersim_leak_detector_ptr->dump_leaks();
+        _usersim_leak_detector_ptr.reset();
+    }
 }
\ No newline at end of file
diff --git a/src/io.cpp b/src/io.cpp
index 914f5df..9d9a8ee 100644
--- a/src/io.cpp
+++ b/src/io.cpp
@@ -3,6 +3,7 @@
 
 #include "platform.h"
 #include "kernel_um.h"
+#include "usersim/ex.h"
 #include "usersim/io.h"
 
 // Io* functions.
@@ -117,3 +118,10 @@ _IRQL_requires_max_(DISPATCH_LEVEL) NTKERNELAPI PEPROCESS IoGetCurrentProcess(VO
 {
     return (PEPROCESS)GetCurrentProcess();
 }
+
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+IofCompleteRequest(_In_ PIRP irp, _In_ CCHAR priority_boost)
+{
+    UNREFERENCED_PARAMETER(irp);
+    UNREFERENCED_PARAMETER(priority_boost);
+}
\ No newline at end of file
diff --git a/src/ke.cpp b/src/ke.cpp
index 0767864..51d456d 100644
--- a/src/ke.cpp
+++ b/src/ke.cpp
@@ -266,7 +266,7 @@ usersim_free_semaphores()
         for (auto handle : *g_usersim_semaphore_handles) {
             ::CloseHandle(handle);
         }
-        usersim_free(g_usersim_semaphore_handles);
+        delete g_usersim_semaphore_handles;
         g_usersim_semaphore_handles = nullptr;
     }
 }
@@ -731,7 +731,7 @@ usersim_free_threadpool_timers()
         for (TP_TIMER* threadpool_timer : *g_usersim_threadpool_timers) {
             CloseThreadpoolTimer(threadpool_timer);
         }
-        usersim_free(g_usersim_threadpool_timers);
+        delete g_usersim_threadpool_timers;
         g_usersim_threadpool_timers = nullptr;
     }
 }
diff --git a/src/kernel_um.h b/src/kernel_um.h
index d2130c7..b87e2e8 100644
--- a/src/kernel_um.h
+++ b/src/kernel_um.h
@@ -42,6 +42,7 @@ extern "C"
 #define STATUS_OBJECT_NAME_EXISTS ((NTSTATUS)0x40000000L)
 #define RPC_NT_CALL_FAILED ((NTSTATUS)0xC002001BL)
 #define STATUS_INVALID_IMAGE_FORMAT ((NTSTATUS)0xC000007BL)
+#define STATUS_DRIVER_INTERNAL_ERROR ((NTSTATUS)0xC0000183L)
 #define STATUS_TOO_MANY_NODES ((NTSTATUS)0xC000020EL)
 #define STATUS_GENERIC_COMMAND_FAILED ((NTSTATUS)0xC0150026L)
 #define STATUS_CONTENT_BLOCKED ((NTSTATUS)0xC0000804L)
diff --git a/src/leak_detector.cpp b/src/leak_detector.cpp
index 8416088..62b827b 100644
--- a/src/leak_detector.cpp
+++ b/src/leak_detector.cpp
@@ -3,6 +3,7 @@
 
 #include "leak_detector.h"
 #include "symbol_decoder.h"
+#include "usersim/ke.h"
 
 #include <iostream>
 #include <sstream>
@@ -30,6 +31,9 @@ void
 _usersim_leak_detector::unregister_allocation(uintptr_t address)
 {
     std::unique_lock<std::mutex> lock(_mutex);
+    if (!_allocations.contains(address)) {
+        KeBugCheck(0);
+    }
     _allocations.erase(address);
 }
 
diff --git a/src/ndis.h b/src/ndis.h
index c27e1aa..2d4f4fd 100644
--- a/src/ndis.h
+++ b/src/ndis.h
@@ -9,6 +9,11 @@
 #include <ndis/objectheader.h>
 #include <ndis/types.h>
 
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
 #define NET_BUFFER_FIRST_MDL(_NB) ((_NB)->MdlChain)
 #define NDIS_STATUS_SUCCESS ((NDIS_STATUS)STATUS_SUCCESS)
 #define NET_BUFFER_LIST_FIRST_NB(_NBL) ((_NBL)->FirstNetBuffer)
@@ -43,41 +48,41 @@ typedef struct _NET_BUFFER_LIST_CONTEXT NET_BUFFER_LIST_CONTEXT, *PNET_BUFFER_LI
 
 typedef struct _NDIS_GENERIC_OBJECT NDIS_GENERIC_OBJECT, *PNDIS_GENERIC_OBJECT;
 
-__declspec(dllexport) PNDIS_GENERIC_OBJECT
+USERSIM_API PNDIS_GENERIC_OBJECT
 NdisAllocateGenericObject(_In_opt_ DRIVER_OBJECT* driver_object, _In_ unsigned long tag, _In_ uint16_t size);
 
-__declspec(dllexport) NDIS_HANDLE
+USERSIM_API NDIS_HANDLE
 NdisAllocateNetBufferListPool(_In_opt_ NDIS_HANDLE ndis_handle, _In_ NET_BUFFER_LIST_POOL_PARAMETERS const* parameters);
 
-__declspec(dllexport) NET_BUFFER_LIST*
+USERSIM_API NET_BUFFER_LIST*
 NdisAllocateCloneNetBufferList(
     _In_ NET_BUFFER_LIST* original_net_buffer_list,
     _In_ NDIS_HANDLE net_buffer_list_pool_handle,
     _In_ NDIS_HANDLE net_buffer_pool_handle,
     ULONG allocate_clone_flags);
 
-__declspec(dllexport) void
+USERSIM_API void
 NdisFreeCloneNetBufferList(_In_ NET_BUFFER_LIST* clone_net_buffer_list, ULONG free_clone_flags);
 
-__declspec(dllexport) PNET_BUFFER_LIST
+USERSIM_API PNET_BUFFER_LIST
 NdisAllocateNetBufferList(_In_ NDIS_HANDLE nbl_pool_handle, _In_ USHORT context_size, _In_ USHORT context_backfill);
 
-__declspec(dllexport) _Must_inspect_result_ __drv_allocatesMem(mem) NET_BUFFER* NdisAllocateNetBuffer(
+USERSIM_API _Must_inspect_result_ __drv_allocatesMem(mem) NET_BUFFER* NdisAllocateNetBuffer(
     _In_ NDIS_HANDLE pool_handle, _In_opt_ MDL* mdl_chain, _In_ unsigned long data_offset, _In_ SIZE_T data_length);
 
-__declspec(dllexport) VOID
+USERSIM_API VOID
 NdisFreeNetBuffer(_In_ __drv_freesMem(mem) NET_BUFFER* net_buffer);
 
-__declspec(dllexport) VOID
+USERSIM_API VOID
 NdisFreeNetBufferList(_In_ __drv_freesMem(mem) NET_BUFFER_LIST* net_buffer_list);
 
-__declspec(dllexport) void
+USERSIM_API void
 NdisFreeNetBufferListPool(_In_ __drv_freesMem(mem) NDIS_HANDLE pool_handle);
 
-__declspec(dllexport) void
+USERSIM_API void
 NdisFreeGenericObject(_In_ PNDIS_GENERIC_OBJECT ndis_object);
 
-__declspec(dllexport) void*
+USERSIM_API void*
 NdisGetDataBuffer(
     _In_ NET_BUFFER* net_buffer,
     _In_ unsigned long bytes_needed,
@@ -85,16 +90,20 @@ NdisGetDataBuffer(
     _In_ unsigned long align_multiple,
     _In_ unsigned long align_offset);
 
-__declspec(dllexport) NDIS_STATUS
+USERSIM_API NDIS_STATUS
 NdisRetreatNetBufferDataStart(
     _In_ NET_BUFFER* net_buffer,
     _In_ unsigned long data_offset_delta,
     _In_ unsigned long data_back_fill,
     _In_opt_ void* allocate_mdl_handler);
 
-__declspec(dllexport) void
+USERSIM_API void
 NdisAdvanceNetBufferDataStart(
     _In_ NET_BUFFER* net_buffer,
     _In_ unsigned long data_offset_delta,
     _In_ BOOLEAN free_mdl,
     _In_opt_ void* free_mdl_handler);
+
+#if defined(__cplusplus)
+}
+#endif
\ No newline at end of file
diff --git a/src/platform.h b/src/platform.h
index 8cf1d10..e178fff 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -99,76 +99,6 @@ extern "C"
     void
     usersim_platform_terminate();
 
-    /**
-     * @brief Allocate memory.
-     * @param[in] size Size of memory to allocate.
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void* usersim_allocate(size_t size);
-
-    /**
-     * @brief Allocate memory.
-     * @param[in] size Size of memory to allocate.
-     * @param[in] tag Pool tag to use.
-     * @param[in] initialize False to return "uninitialized" memory.
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    __drv_allocatesMem(Mem) _Must_inspect_result_
-        _Ret_writes_maybenull_(size) void* usersim_allocate_with_tag(size_t size, uint32_t tag, bool initialize);
-
-    /**
-     * @brief Reallocate memory.
-     * @param[in] memory Allocation to be reallocated.
-     * @param[in] old_size Old size of memory to reallocate.
-     * @param[in] new_size New size of memory to reallocate.
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate(
-        _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size);
-
-    /**
-     * @brief Reallocate memory with tag.
-     * @param[in] memory Allocation to be reallocated.
-     * @param[in] old_size Old size of memory to reallocate.
-     * @param[in] new_size New size of memory to reallocate.
-     * @param[in] tag Pool tag to use.
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    __drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate_with_tag(
-        _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size, uint32_t tag);
-
-    /**
-     * @brief Free memory.
-     * @param[in] memory Allocation to be freed.
-     */
-    void
-    usersim_free(_Frees_ptr_opt_ void* memory);
-
-    /**
-     * @brief Allocate memory that has a starting address that is cache aligned.
-     * @param[in] size Size of memory to allocate
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    USERSIM_API 
-    __drv_allocatesMem(Mem) _Must_inspect_result_
-        _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned(size_t size);
-
-    /**
-     * @brief Allocate memory that has a starting address that is cache aligned with tag.
-     * @param[in] size Size of memory to allocate
-     * @param[in] tag Pool tag to use.
-     * @returns Pointer to memory block allocated, or null on failure.
-     */
-    __drv_allocatesMem(Mem) _Must_inspect_result_
-        _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned_with_tag(size_t size, uint32_t tag);
-
-    /**
-     * @brief Free memory that has a starting address that is cache aligned.
-     * @param[in] memory Allocation to be freed.
-     */
-    void
-    usersim_free_cache_aligned(_Frees_ptr_opt_ void* memory);
-
     typedef enum _usersim_page_protection
     {
         USERSIM_PAGE_PROTECT_READ_ONLY,
@@ -775,22 +705,3 @@ extern "C"
 #ifdef __cplusplus
 }
 #endif
-
-#ifdef __cplusplus
-#include <memory>
-namespace usersim_helper {
-
-struct _usersim_free_functor
-{
-    void
-    operator()(void* memory)
-    {
-        usersim_free(memory);
-    }
-};
-
-typedef std::unique_ptr<void, _usersim_free_functor> usersim_memory_ptr;
-
-} // namespace usersim_helper
-
-#endif
diff --git a/src/platform_user.cpp b/src/platform_user.cpp
index 304ff2c..2fc092e 100644
--- a/src/platform_user.cpp
+++ b/src/platform_user.cpp
@@ -37,12 +37,9 @@ bool _usersim_platform_is_preemptible = true;
 int32_t _usersim_platform_initiate_count = 0;
 
 extern "C" bool usersim_fuzzing_enabled = false;
-extern "C" size_t usersim_fuzzing_memory_limit = MAXSIZE_T;
 
 static EX_RUNDOWN_REF _usersim_platform_preemptible_work_items_rundown;
 
-usersim_leak_detector_ptr _usersim_leak_detector_ptr;
-
 /**
  * @brief Environment variable to enable fault injection testing.
  *
@@ -204,9 +201,9 @@ usersim_platform_initiate()
     try {
         _usersim_platform_maximum_group_count = GetMaximumProcessorGroupCount();
         _usersim_platform_maximum_processor_count = GetMaximumProcessorCount(ALL_PROCESSOR_GROUPS);
-        auto fault_injection_stack_depth =
+        size_t fault_injection_stack_depth =
             _get_environment_variable_as_size_t(USERSIM_FAULT_INJECTION_SIMULATION_ENVIRONMENT_VARIABLE_NAME);
-        auto leak_detector = _get_environment_variable_as_bool(USERSIM_MEMORY_LEAK_DETECTION_ENVIRONMENT_VARIABLE_NAME);
+        bool leak_detector = _get_environment_variable_as_bool(USERSIM_MEMORY_LEAK_DETECTION_ENVIRONMENT_VARIABLE_NAME);
         if (fault_injection_stack_depth || leak_detector) {
             _usersim_symbol_decoder_initialize();
         }
@@ -218,9 +215,7 @@ usersim_platform_initiate()
             usersim_fuzzing_enabled = true;
         }
 
-        if (leak_detector) {
-            _usersim_leak_detector_ptr = std::make_unique<usersim_leak_detector_t>();
-        }
+        usersim_initialize_ex(leak_detector);
 
         result = usersim_initialize_irql();
         if (result != STATUS_SUCCESS) {
@@ -269,10 +264,7 @@ usersim_platform_terminate()
     usersim_clean_up_dpcs();
     usersim_clean_up_irql();
     _clean_up_thread_pool();
-    if (_usersim_leak_detector_ptr) {
-        _usersim_leak_detector_ptr->dump_leaks();
-        _usersim_leak_detector_ptr.reset();
-    }
+    usersim_clean_up_ex();
 
     int32_t count = InterlockedDecrement((volatile long*)&_usersim_platform_initiate_count);
     if (count < 0) {
@@ -294,114 +286,6 @@ usersim_get_code_integrity_state(_Out_ usersim_code_integrity_state_t* state)
     USERSIM_RETURN_RESULT(STATUS_SUCCESS);
 }
 
-__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(size) void* usersim_allocate(size_t size)
-{
-    return usersim_allocate_with_tag(size, 'tset', true);
-}
-
-__drv_allocatesMem(Mem) _Must_inspect_result_
-    _Ret_writes_maybenull_(size) void* usersim_allocate_with_tag(size_t size, uint32_t tag, bool initialize)
-{
-    UNREFERENCED_PARAMETER(tag);
-    if (size == 0) {
-        KeBugCheckEx(BAD_POOL_CALLER, 0x00, 0, 0, 0);
-    }
-    if (size > usersim_fuzzing_memory_limit) {
-        return nullptr;
-    }
-
-    if (usersim_fault_injection_inject_fault()) {
-        return nullptr;
-    }
-
-    void* memory = calloc(size, 1);
-    if (!initialize) {
-        // The calloc call always zero-initializes memory.  To test
-        // returning uninitialized memory, we explicitly fill it with 0xcc.
-        memset(memory, 0xcc, size);
-    }
-
-    if (memory && _usersim_leak_detector_ptr) {
-        _usersim_leak_detector_ptr->register_allocation(reinterpret_cast<uintptr_t>(memory), size);
-    }
-
-    return memory;
-}
-
-__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate(
-    _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size)
-{
-    UNREFERENCED_PARAMETER(old_size);
-    if (new_size > usersim_fuzzing_memory_limit) {
-        return nullptr;
-    }
-
-    if (usersim_fault_injection_inject_fault()) {
-        return nullptr;
-    }
-
-    void* p = realloc(memory, new_size);
-    if (p && (new_size > old_size)) {
-        memset(((char*)p) + old_size, 0, new_size - old_size);
-    }
-
-    if (_usersim_leak_detector_ptr) {
-        _usersim_leak_detector_ptr->unregister_allocation(reinterpret_cast<uintptr_t>(memory));
-        _usersim_leak_detector_ptr->register_allocation(reinterpret_cast<uintptr_t>(p), new_size);
-    }
-
-    return p;
-}
-
-__drv_allocatesMem(Mem) _Must_inspect_result_ _Ret_writes_maybenull_(new_size) void* usersim_reallocate_with_tag(
-    _In_ _Post_invalid_ void* memory, size_t old_size, size_t new_size, uint32_t tag)
-{
-    UNREFERENCED_PARAMETER(tag);
-
-    return usersim_reallocate(memory, old_size, new_size);
-}
-
-void
-usersim_free(_Frees_ptr_opt_ void* memory)
-{
-    if (_usersim_leak_detector_ptr) {
-        _usersim_leak_detector_ptr->unregister_allocation(reinterpret_cast<uintptr_t>(memory));
-    }
-    free(memory);
-}
-
-__drv_allocatesMem(Mem) _Must_inspect_result_
-    _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned(size_t size)
-{
-    if (size > usersim_fuzzing_memory_limit) {
-        return nullptr;
-    }
-
-    if (usersim_fault_injection_inject_fault()) {
-        return nullptr;
-    }
-
-    void* memory = _aligned_malloc(size, USERSIM_CACHE_LINE_SIZE);
-    if (memory) {
-        memset(memory, 0, size);
-    }
-    return memory;
-}
-
-__drv_allocatesMem(Mem) _Must_inspect_result_
-    _Ret_writes_maybenull_(size) void* usersim_allocate_cache_aligned_with_tag(size_t size, uint32_t tag)
-{
-    UNREFERENCED_PARAMETER(tag);
-
-    return usersim_allocate_cache_aligned(size);
-}
-
-void
-usersim_free_cache_aligned(_Frees_ptr_opt_ void* memory)
-{
-    _aligned_free(memory);
-}
-
 struct _usersim_ring_descriptor
 {
     void* primary_view;
diff --git a/src/se.cpp b/src/se.cpp
index 59b7ec2..c4ada13 100644
--- a/src/se.cpp
+++ b/src/se.cpp
@@ -4,6 +4,7 @@
 #include "fault_injection.h"
 #include "kernel_um.h"
 #include "platform.h"
+#include "usersim/ex.h"
 #include "usersim/se.h"
 #include "utilities.h"
 #include <processthreadsapi.h>
diff --git a/src/wdf.cpp b/src/wdf.cpp
index 8195a50..0d71b68 100644
--- a/src/wdf.cpp
+++ b/src/wdf.cpp
@@ -1,10 +1,26 @@
 // Copyright (c) Microsoft Corporation
 // SPDX-License-Identifier: MIT
 
+#include "fault_injection.h"
 #include "framework.h"
+#include "platform.h"
+#include "usersim/ex.h"
 #include "usersim/wdf.h"
 
-static WDFDRIVER g_CurrentDriver = {};
+WDF_DRIVER_GLOBALS g_UsersimWdfDriverGlobals = {0};
+
+static WdfDriverCreate_t _WdfDriverCreate;
+static WdfDeviceCreate_t _WdfDeviceCreate;
+static WdfControlDeviceInitAllocate_t _WdfControlDeviceInitAllocate;
+static WdfDeviceInitFree_t _WdfDeviceInitFree;
+static WdfDeviceInitSetDeviceType_t _WdfDeviceInitSetDeviceType;
+static WdfDeviceInitSetCharacteristics_t _WdfDeviceInitSetCharacteristics;
+static WdfDeviceInitAssignName_t _WdfDeviceInitAssignName;
+static WdfDeviceInitSetFileObjectConfig_t _WdfDeviceInitSetFileObjectConfig;
+static WdfDeviceInitAssignWdmIrpPreprocessCallback_t _WdfDeviceInitAssignWdmIrpPreprocessCallback;
+static WdfDeviceCreateSymbolicLink_t _WdfDeviceCreateSymbolicLink;
+static WdfIoQueueCreate_t _WdfIoQueueCreate;
+static WdfObjectDelete_t _WdfObjectDelete;
 
 static NTSTATUS
 _WdfDriverCreate(
@@ -15,14 +31,19 @@ _WdfDriverCreate(
     _In_ PWDF_DRIVER_CONFIG driver_config,
     _Out_opt_ WDFDRIVER* driver)
 {
-    UNREFERENCED_PARAMETER(driver_globals);
-    UNREFERENCED_PARAMETER(driver_object);
     UNREFERENCED_PARAMETER(registry_path);
     UNREFERENCED_PARAMETER(driver_attributes);
 
-    g_CurrentDriver.config = *driver_config;
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (driver_globals->Driver != nullptr) {
+        return STATUS_DRIVER_INTERNAL_ERROR;
+    }
+    g_UsersimWdfDriverGlobals.Driver = driver_object;
+    driver_object->config = *driver_config;
     if (driver != nullptr) {
-        *driver = g_CurrentDriver;
+        *driver = driver_object;
     }
     return STATUS_SUCCESS;
 }
@@ -30,34 +51,224 @@ _WdfDriverCreate(
 static NTSTATUS
 _WdfDeviceCreate(
     _In_ WDF_DRIVER_GLOBALS* driver_globals,
-    _Inout_ PWDFDEVICE_INIT* device_init, _In_opt_ PWDF_OBJECT_ATTRIBUTES device_attributes, _Out_ WDFDEVICE* device)
+    _Inout_ PWDFDEVICE_INIT* device_init,
+    _In_opt_ PWDF_OBJECT_ATTRIBUTES device_attributes,
+    _Out_ WDFDEVICE* device)
 {
-    UNREFERENCED_PARAMETER(driver_globals);
-    UNREFERENCED_PARAMETER(device_init);
     UNREFERENCED_PARAMETER(device_attributes);
 
-    *device = nullptr;
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (usersim_fault_injection_inject_fault()) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    *device = *device_init;
+    return STATUS_SUCCESS;
+}
+
+#define IRP_MJ_MAXIMUM_FUNCTION 0x1b
+
+typedef struct _WDFDEVICE_INIT
+{
+    DEVICE_TYPE device_type;
+    ULONG device_characteristics;
+    UNICODE_STRING device_name;
+    PWDF_FILEOBJECT_CONFIG file_object_config;
+    PWDF_OBJECT_ATTRIBUTES file_object_attributes;
+    PFN_WDFDEVICE_WDM_IRP_PREPROCESS evt_device_wdm_irp_preprocess;
+    PUCHAR minor_functions[IRP_MJ_MAXIMUM_FUNCTION];
+    ULONG num_minor_functions[IRP_MJ_MAXIMUM_FUNCTION];
+} WDFDEVICE_INIT;
+
+static
+_Must_inspect_result_
+_IRQL_requires_max_(PASSIVE_LEVEL)
+PWDFDEVICE_INIT
+_WdfControlDeviceInitAllocate(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ WDFDRIVER driver,
+    _In_ CONST UNICODE_STRING* sddl_string)
+{
+    UNREFERENCED_PARAMETER(driver_globals);
+    UNREFERENCED_PARAMETER(driver);
+    UNREFERENCED_PARAMETER(sddl_string);
+
+    PWDFDEVICE_INIT device_init = (PWDFDEVICE_INIT)usersim_allocate(sizeof(*device_init));
+    return device_init;
+}
+
+static _IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfDeviceInitFree(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ PWDFDEVICE_INIT device_init)
+{
+    UNREFERENCED_PARAMETER(driver_globals);
+    usersim_free(device_init);
+}
+
+static
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfDeviceInitSetDeviceType(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals, 
+    _In_ PWDFDEVICE_INIT device_init,
+    DEVICE_TYPE device_type)
+{
+    UNREFERENCED_PARAMETER(driver_globals);
+    device_init->device_type = device_type;
+}
+
+static
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfDeviceInitSetCharacteristics(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals, 
+    _In_ PWDFDEVICE_INIT device_init,
+    ULONG device_characteristics,
+    BOOLEAN or_in_values)
+{
+    UNREFERENCED_PARAMETER(driver_globals);
+    if (!or_in_values) {
+        device_init->device_characteristics = 0;
+    }
+    device_init->device_characteristics |= device_characteristics;
+}
+
+static _Must_inspect_result_
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+_WdfDeviceInitAssignName(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ PWDFDEVICE_INIT device_init,
+    _In_opt_ PCUNICODE_STRING device_name)
+{
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (usersim_fault_injection_inject_fault()) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    if (device_name != nullptr) {
+        device_init->device_name = *device_name;
+    }
+    return STATUS_SUCCESS;
+}
+
+static
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfDeviceInitSetFileObjectConfig(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ PWDFDEVICE_INIT device_init,
+    _In_ PWDF_FILEOBJECT_CONFIG file_object_config,
+    _In_opt_ PWDF_OBJECT_ATTRIBUTES file_object_attributes)
+{
+    UNREFERENCED_PARAMETER(driver_globals);
+    device_init->file_object_config = file_object_config;
+    device_init->file_object_attributes = file_object_attributes;
+}
+
+static _Must_inspect_result_
+_IRQL_requires_max_(DISPATCH_LEVEL)
+NTSTATUS
+_WdfDeviceInitAssignWdmIrpPreprocessCallback(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ PWDFDEVICE_INIT device_init,
+    _In_ PFN_WDFDEVICE_WDM_IRP_PREPROCESS evt_device_wdm_irp_preprocess,
+    UCHAR major_function,
+    _When_(num_minor_functions > 0, _In_reads_bytes_(num_minor_functions)) _When_(num_minor_functions == 0, _In_opt_)
+        PUCHAR minor_functions,
+    ULONG num_minor_functions)
+{
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (device_init->minor_functions[major_function] != nullptr) {
+        return STATUS_INVALID_DEVICE_REQUEST;
+    }
+    if (major_function >= IRP_MJ_MAXIMUM_FUNCTION) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (usersim_fault_injection_inject_fault()) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    device_init->evt_device_wdm_irp_preprocess = evt_device_wdm_irp_preprocess;
+    device_init->minor_functions[major_function] = minor_functions;
+    device_init->num_minor_functions[major_function] = num_minor_functions;
     return STATUS_SUCCESS;
 }
 
-WDF_DRIVER_GLOBALS g_UsersimWdfDriverGlobals = {
-    .Driver = g_CurrentDriver,
-};
+static _Must_inspect_result_
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+_WdfDeviceCreateSymbolicLink(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ WDFDEVICE device,
+    _In_ PCUNICODE_STRING symbolic_link_name)
+{
+    UNREFERENCED_PARAMETER(device);
+    UNREFERENCED_PARAMETER(symbolic_link_name);
 
-typedef enum _WDFFUNCENUM
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (usersim_fault_injection_inject_fault()) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    return STATUS_SUCCESS;
+}
+
+static
+_Must_inspect_result_
+_IRQL_requires_max_(DISPATCH_LEVEL)
+NTSTATUS
+_WdfIoQueueCreate(
+    _In_ PWDF_DRIVER_GLOBALS driver_globals,
+    _In_ WDFDEVICE device,
+    _In_ PWDF_IO_QUEUE_CONFIG config,
+    _In_opt_ PWDF_OBJECT_ATTRIBUTES queue_attributes,
+    _Out_opt_ WDFQUEUE* queue)
+{
+    UNREFERENCED_PARAMETER(device);
+    UNREFERENCED_PARAMETER(config);
+    UNREFERENCED_PARAMETER(queue_attributes);
+
+    if (driver_globals != &g_UsersimWdfDriverGlobals) {
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (usersim_fault_injection_inject_fault()) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+    if (queue != nullptr) {
+        memset(queue, 0, sizeof(*queue));
+    }
+    return STATUS_SUCCESS;
+}
+
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfObjectDelete(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFOBJECT object)
 {
-    WdfDeviceCreateTableIndex = 75,
-    WdfDriverCreateTableIndex = 116,
-    WdfFunctionTableNumEntries = 444,
-} WDFFUNCENUM;
+    UNREFERENCED_PARAMETER(driver_globals);
+
+    usersim_free(object);
+}
 
 WDFFUNC g_UsersimWdfFunctions[WdfFunctionTableNumEntries];
 
 void
 usersim_initialize_wdf()
 {
+    g_UsersimWdfFunctions[WdfControlDeviceInitAllocateTableIndex] = (WDFFUNC)_WdfControlDeviceInitAllocate;
+    g_UsersimWdfFunctions[WdfDeviceInitFreeTableIndex] = (WDFFUNC)_WdfDeviceInitFree;
+    g_UsersimWdfFunctions[WdfDeviceInitSetDeviceTypeTableIndex] = (WDFFUNC)_WdfDeviceInitSetDeviceType;
+    g_UsersimWdfFunctions[WdfDeviceInitAssignNameTableIndex] = (WDFFUNC)_WdfDeviceInitAssignName;
+    g_UsersimWdfFunctions[WdfDeviceInitSetCharacteristicsTableIndex] = (WDFFUNC)_WdfDeviceInitSetCharacteristics;
+    g_UsersimWdfFunctions[WdfDeviceInitSetFileObjectConfigTableIndex] = (WDFFUNC)_WdfDeviceInitSetFileObjectConfig;
+    g_UsersimWdfFunctions[WdfDeviceInitAssignWdmIrpPreprocessCallbackTableIndex] =
+        (WDFFUNC)_WdfDeviceInitAssignWdmIrpPreprocessCallback;
     g_UsersimWdfFunctions[WdfDeviceCreateTableIndex] = (WDFFUNC)_WdfDeviceCreate;
+    g_UsersimWdfFunctions[WdfDeviceCreateSymbolicLinkTableIndex] = (WDFFUNC)_WdfDeviceCreateSymbolicLink;
     g_UsersimWdfFunctions[WdfDriverCreateTableIndex] = (WDFFUNC)_WdfDriverCreate;
+    g_UsersimWdfFunctions[WdfIoQueueCreateTableIndex] = (WDFFUNC)_WdfIoQueueCreate;
+    g_UsersimWdfFunctions[WdfObjectDeleteTableIndex] = (WDFFUNC)_WdfObjectDelete;
 }
 
 extern "C"
@@ -69,7 +280,7 @@ extern "C"
 }
 
 WDFDRIVER
-WdfGetDriver() { return g_CurrentDriver; }
+WdfGetDriver() { return g_UsersimWdfDriverGlobals.Driver; }
 
 void
 WDF_DRIVER_CONFIG_INIT(_Out_ PWDF_DRIVER_CONFIG config, _In_opt_ PFN_WDF_DRIVER_DEVICE_ADD evt_driver_device_add)
diff --git a/tests/ex_test.cpp b/tests/ex_test.cpp
index 633f7a2..4de3bd9 100644
--- a/tests/ex_test.cpp
+++ b/tests/ex_test.cpp
@@ -10,12 +10,21 @@
 
 TEST_CASE("ExAllocatePool", "[ex]")
 {
+    // Try an allocation that need not be cache aligned.
     uint64_t* buffer = (uint64_t*)ExAllocatePoolUninitialized(NonPagedPool, 8, 'tset');
     REQUIRE(buffer != nullptr);
     REQUIRE(*buffer != 0);
     *buffer = 0;
     ExFreePool(buffer);
 
+    // Try an allocation that must be cache aligned.
+    buffer = (uint64_t*)ExAllocatePoolUninitialized(NonPagedPoolNxCacheAligned, 8, 'tset');
+    REQUIRE(buffer != nullptr);
+    REQUIRE(*buffer != 0);
+    REQUIRE((((uintptr_t)buffer) % 64) == 0);
+    *buffer = 0;
+    ExFreePool(buffer);
+
     buffer = (uint64_t*)ExAllocatePoolWithTag(NonPagedPool, 8, 'tset');
     REQUIRE(buffer != nullptr);
     REQUIRE(*buffer == 0);
diff --git a/tests/wdf_test.cpp b/tests/wdf_test.cpp
index f93869e..860d61d 100644
--- a/tests/wdf_test.cpp
+++ b/tests/wdf_test.cpp
@@ -7,11 +7,129 @@
 #else
 #include <catch2/catch.hpp>
 #endif
+#include "usersim/wdf.h"
 
 TEST_CASE("DriverEntry", "[wdf]")
 {
     HMODULE module = LoadLibraryW(L"sample.dll");
     REQUIRE(module != nullptr);
 
-    FreeLibrary(module);
+    if (module) {
+        FreeLibrary(module);
+    }
 }
+
+extern "C"
+{
+    __declspec(dllimport) const WDFFUNC* UsersimWdfFunctions;
+    __declspec(dllimport) PWDF_DRIVER_GLOBALS UsersimWdfDriverGlobals;
+}
+
+TEST_CASE("WdfDriverCreate", "[wdf]")
+{
+    DRIVER_OBJECT driver_object = {0};
+    WDF_DRIVER_CONFIG config;
+    WDF_DRIVER_CONFIG_INIT(&config, nullptr);
+
+    WdfDriverCreate_t* WdfDriverCreate = (WdfDriverCreate_t*)UsersimWdfFunctions[WdfDriverCreateTableIndex];
+    WDFDRIVER driver = nullptr;
+    DECLARE_CONST_UNICODE_STRING(registry_path, L"");
+    NTSTATUS status =
+        WdfDriverCreate(UsersimWdfDriverGlobals, &driver_object, &registry_path, nullptr, &config, &driver);
+    REQUIRE(status == STATUS_SUCCESS);
+    REQUIRE(driver != nullptr);
+
+    // Verify that a duplicate create fails.
+    status = WdfDriverCreate(UsersimWdfDriverGlobals, &driver_object, &registry_path, nullptr, &config, &driver);
+    REQUIRE(status == STATUS_DRIVER_INTERNAL_ERROR);
+
+    UsersimWdfDriverGlobals->Driver = nullptr;
+}
+
+NTSTATUS
+driver_query_volume_information(_In_ WDFDEVICE device, _Inout_ IRP* irp)
+{
+    UNREFERENCED_PARAMETER(device);
+    UNREFERENCED_PARAMETER(irp);
+    return STATUS_SUCCESS;
+}
+
+TEST_CASE("WdfDeviceCreate", "[wdf]")
+{
+    // Create driver to hold device.
+    DRIVER_OBJECT driver_object = {0};
+    WDF_DRIVER_CONFIG config;
+    WDF_DRIVER_CONFIG_INIT(&config, nullptr);
+    WdfDriverCreate_t* WdfDriverCreate = (WdfDriverCreate_t*)UsersimWdfFunctions[WdfDriverCreateTableIndex];
+    WDFDRIVER driver = nullptr;
+    DECLARE_CONST_UNICODE_STRING(registry_path, L"");
+    NTSTATUS status = WdfDriverCreate(UsersimWdfDriverGlobals, &driver_object, &registry_path, nullptr, &config, &driver);
+    REQUIRE(status == STATUS_SUCCESS);
+
+    // Allocate control device object.
+    WdfControlDeviceInitAllocate_t* WdfControlDeviceInitAllocate =
+        (WdfControlDeviceInitAllocate_t*)UsersimWdfFunctions[WdfControlDeviceInitAllocateTableIndex];
+    DECLARE_CONST_UNICODE_STRING(security_descriptor, L"");
+    PWDFDEVICE_INIT init = WdfControlDeviceInitAllocate(UsersimWdfDriverGlobals, driver, &security_descriptor);
+    REQUIRE(init != nullptr);
+
+    // Set device type.
+    WdfDeviceInitSetDeviceType_t* WdfDeviceInitSetDeviceType =
+        (WdfDeviceInitSetDeviceType_t*)UsersimWdfFunctions[WdfDeviceInitSetDeviceTypeTableIndex];
+    WdfDeviceInitSetDeviceType(UsersimWdfDriverGlobals, init, FILE_DEVICE_NULL);
+
+    // Set device characteristics.
+    WdfDeviceInitSetCharacteristics_t* WdfDeviceInitSetCharacteristics =
+        (WdfDeviceInitSetCharacteristics_t*)UsersimWdfFunctions[WdfDeviceInitSetCharacteristicsTableIndex];
+    WdfDeviceInitSetCharacteristics(UsersimWdfDriverGlobals, init, FILE_DEVICE_SECURE_OPEN, FALSE);
+
+    // Set device name.
+    UNICODE_STRING device_name;
+    RtlInitUnicodeString(&device_name, L"test device name");
+    WdfDeviceInitAssignName_t* WdfDeviceInitAssignName =
+        (WdfDeviceInitAssignName_t*)UsersimWdfFunctions[WdfDeviceInitAssignNameTableIndex];
+    status = WdfDeviceInitAssignName(UsersimWdfDriverGlobals, init, &device_name);
+    REQUIRE(status == STATUS_SUCCESS);
+
+    // Set file object config.
+    WDF_OBJECT_ATTRIBUTES attributes = {0};
+    WDF_FILEOBJECT_CONFIG file_object_config = {0};
+    WdfDeviceInitSetFileObjectConfig_t* WdfDeviceInitSetFileObjectConfig =
+        (WdfDeviceInitSetFileObjectConfig_t*)UsersimWdfFunctions[WdfDeviceInitSetFileObjectConfigTableIndex];
+    WdfDeviceInitSetFileObjectConfig(UsersimWdfDriverGlobals, init, &file_object_config, &attributes);
+
+    WdfDeviceInitAssignWdmIrpPreprocessCallback_t* WdfDeviceInitAssignWdmIrpPreprocessCallback =
+        (WdfDeviceInitAssignWdmIrpPreprocessCallback_t*)UsersimWdfFunctions[WdfDeviceInitAssignWdmIrpPreprocessCallbackTableIndex];
+    status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+        UsersimWdfDriverGlobals, init, driver_query_volume_information, IRP_MJ_QUERY_VOLUME_INFORMATION, NULL, 0);
+    REQUIRE(status == STATUS_SUCCESS);
+
+    // Create device.
+    WDFDEVICE device = nullptr;
+    WdfDeviceCreate_t* WdfDeviceCreate = (WdfDeviceCreate_t*)UsersimWdfFunctions[WdfDeviceCreateTableIndex];
+    status = WdfDeviceCreate(UsersimWdfDriverGlobals, &init, nullptr, &device);
+    REQUIRE(status == STATUS_SUCCESS);
+    REQUIRE(device != nullptr);
+
+    // Create symbolic link for control object for user mode.
+    UNICODE_STRING symbolic_device_name;
+    RtlInitUnicodeString(&symbolic_device_name, L"symbolic device name");
+    WdfDeviceCreateSymbolicLink_t* WdfDeviceCreateSymbolicLink =
+        (WdfDeviceCreateSymbolicLink_t*)UsersimWdfFunctions[WdfDeviceCreateSymbolicLinkTableIndex];
+    status = WdfDeviceCreateSymbolicLink(UsersimWdfDriverGlobals, device, &symbolic_device_name);
+    REQUIRE(status == STATUS_SUCCESS);
+
+    // Create an I/O queue.
+    WDF_IO_QUEUE_CONFIG io_queue_configuration = {0};
+    WdfIoQueueCreate_t* WdfIoQueueCreate = (WdfIoQueueCreate_t*)UsersimWdfFunctions[WdfIoQueueCreateTableIndex];
+#pragma warning(suppress:28193) // status must be inspected
+    status = WdfIoQueueCreate(
+        UsersimWdfDriverGlobals, device, &io_queue_configuration, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
+    REQUIRE(status == STATUS_SUCCESS);
+
+    // Free device, which will free the control device object.
+    WdfObjectDelete_t* WdfObjectDelete = (WdfObjectDelete_t*)UsersimWdfFunctions[WdfObjectDeleteTableIndex];
+    WdfObjectDelete(UsersimWdfDriverGlobals, device);
+
+    UsersimWdfDriverGlobals->Driver = nullptr;
+}
\ No newline at end of file
diff --git a/usersim_dll_skeleton/dll_skeleton.c b/usersim_dll_skeleton/dll_skeleton.c
index 40b94ae..b105795 100644
--- a/usersim_dll_skeleton/dll_skeleton.c
+++ b/usersim_dll_skeleton/dll_skeleton.c
@@ -21,11 +21,7 @@ __declspec(dllimport) const WDFFUNC* UsersimWdfFunctions;
 
 PWDF_DRIVER_GLOBALS WdfDriverGlobals = NULL;
 const WDFFUNC* WdfFunctions_01015 = NULL;
-
-    struct _driver_object
-    {
-        int dummy;
-    };
+static DRIVER_OBJECT _driver_object = {0};
 
     NTSTATUS
     UsersimStartDriver()
@@ -33,18 +29,18 @@ const WDFFUNC* WdfFunctions_01015 = NULL;
         WdfDriverGlobals = UsersimWdfDriverGlobals;
         WdfFunctions_01015 = UsersimWdfFunctions;
 
-        DRIVER_OBJECT driver_object = {0};
         UNICODE_STRING registry_path = {0};
-        return DriverEntry(&driver_object, &registry_path);
+        return DriverEntry(&_driver_object, &registry_path);
     }
 
     void
     UsersimStopDriver()
     {
-        WDFDRIVER driver = WdfDriverGlobals->Driver;
-        if (driver.config.EvtDriverUnload != NULL) {
-            driver.config.EvtDriverUnload(driver);
+        DRIVER_OBJECT* driver = (DRIVER_OBJECT*)WdfDriverGlobals->Driver;
+        if (driver->config.EvtDriverUnload != NULL) {
+            driver->config.EvtDriverUnload(driver);
         }
+        WdfDriverGlobals->Driver = NULL;
     }
 
     BOOL APIENTRY