该项目在Windows内核驱动程序中使用MSVC C ++ STL。在此解决方案中, jxystl.lib被实现为内核调整,池类型/标签Aware,Template Library和MSVC实现。在引擎盖下使用MSVC C ++ STL。
# include < wdm.h >
# include < jxy/string.hpp >
extern " C "
NTSTATUS DriverEntry (
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath)
{
jxy::wstring<PagedPool, ' 0GAT ' > helloWorld;
try
{
helloWorld. assign ( L" Hello, World! " );
}
catch ( const std::bad_alloc&)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
return STATUS_SUCCESS;
} 1: kd> dv
DriverObject = 0xffffca83`5380d300 Driver "Driverstlkrn"
RegistryPath = 0xffffca83`5227f000 "REGISTRYMACHINESYSTEMControlSet001Servicesstlkrn"
helloWorld = "Hello, World!"
| 标准 | 支持 | 笔记 |
|---|---|---|
| CPP11 | 不 | 未经测试,你的里程有所不同 |
| CPP14 | 是的 | |
| CPP17 | 是的 | |
| CPP20 | 是的 |
该解决方案中的测试驱动器stdtest.sys包含该项目的单元测试。使用驱动程序验证者在内核中进行单位测试。单位测试框架是裸露的骨头,但足以锻炼jxystl.lib 。
该解决方案中实现的另一个驱动程序stdkrn.sys将jxystl.lib放在实用方案中使用。它使用各种std名称空间设施和容器(包裹在jxy名称空间下)。该驱动程序注册以获取流程,线程和图像通知;然后使用现代C ++跟踪过程上下文,线程上下文和模块上下文。
vcrtl异常处理使C ++对象在抛出异常时可以放松。这是C ++的核心功能,对内核驱动程序几乎没有关注。 Microsoft本质上不支持内核驱动程序的C ++例外。
C ++例外处理是由Avakar的VCRTL Libraray做出的。如果没有阿瓦卡(Avakar)的出色贡献,这个项目将是更多的工作。有关Windows驱动程序中例外处理的信息,请前往Avakar的VCRTL GitHub。另外,此页面提供了有关AMD64上例外处理的出色详细信息。
jxystlWindows内核分配与内存池相关联。此外,Windows内核内置了池标签。泳池标签有助于跟踪驾驶员进行的分配。这种标记设施可以调试和监视分配。
jxy名称空间在此解决方案中,使用带有池键入和标记的std名称空间对象的Windows驱动程序的开发。
该库选择不实现“全局” new / delete操作员。它仅通过池键入和标记功能实现new / delete运算符。这需要指定池类型和标签。如果使用某些功能需要“全局分配器”,则不会链接。这是一个有意的设计决策,因此不使用全局分配器,所有分配都必须指定池类型和标签。
jxy名称空间实现了符合标准用于模板容器的标准的分配器和删除器。这些分配器和删除器是池类型/标签。他们需要指定池类型和标签,并防止跨工具类型和标签进行转换/重新插入 - 应代替STL分配器。
jxy::allocator<T, PagedPool, ' 0GAT ' >;
jxy::default_delete<T, PagedPool, ' 0GAT ' >; jxystl.lib实现了使用MSVC STL容器的必要“填充”功能。实现(在msvcfill.cpp中)对内核进行了体贴。此功能使MSVC STL容器能够链接到适合内核的功能。这也意味着,如果使用了某些std容器功能,而其背后没有“填充”功能,则链接器将失败。这是一个有意的设计决策,使任何实现都可以在内核中使用。
CRT初始化和高估功能不受任何支持。 CRT初始化的顺序尚不清楚且不明显。当内核驱动程序加载全局数据时,应清楚地设置全局数据并在驱动程序负载和卸载过程中拆除。全球CRT初始化以非明显的方式“掩盖”此初始化。此外,不支持CRT ABITIT功能。编译器未完成必要同步的排放,从而实现C ++对象的局部静态初始化。并会在内核中引入非明显的同步。缺乏CRT初始化和AtexIT支持是有意的设计决定。我强烈建议在开发内核驱动程序时避免使用它。
例如, jxy名称空间“包装” std::vector和池类型和标签的强制使用:
namespace jxy
{
template < typename T,
POOL_TYPE t_PoolType,
ULONG t_PoolTag,
typename TAllocator = jxy::allocator<T, t_PoolType, t_PoolTag>>
using vector = std::vector<T, TAllocator>;
}
jxy::vector< int , PagedPool, ' 0GAT ' > integers; stlkrn!DriverEntry+0xea:
0: kd> dx integers
integers : { size=10 } [Type: std::vector<int,jxy::details::allocator<int,1,809976148> >]
[<Raw View>] [Type: std::vector<int,jxy::details::allocator<int,1,809976148> >]
[capacity] : 10
[allocator] : {...} [Type: std::_Compressed_pair<jxy::details::allocator<int,1,809976148>,std::_Vector_val<std::_Simple_types<int> >,1>]
[0] : 1 [Type: int]
[1] : 2 [Type: int]
[2] : 3 [Type: int]
[3] : 4 [Type: int]
[4] : 5 [Type: int]
[5] : 6 [Type: int]
[6] : 7 [Type: int]
[7] : 8 [Type: int]
[8] : 9 [Type: int]
[9] : 10 [Type: int]
以下是jxy名称空间下的功能表:
| JXY | STL等效 | 包括 | 笔记 |
|---|---|---|---|
jxy::allocator | std::allocator | <jxy/memory.hpp> | |
jxy::default_delete | std::default_delete | <jxy/memory.hpp> | |
jxy::unique_ptr | std::unique_ptr | <jxy/memory.hpp> | |
jxy::shared_ptr | std::shared_ptr | <jxy/memory.hpp> | |
jxy::basic_string | std::basic_string | <jxy/string.hpp> | |
jxy::string | std::string | <jxy/string.hpp> | |
jxy::wstring | std::wstring | <jxy/string.hpp> | |
jxy::vector | std::vector | <jxy/vector.hpp> | |
jxy::map | std::map | <jxy/map.hpp> | |
jxy::multimap | std::miltimap | <jxy/map.hpp> | |
jxy::mutex | std::mutex | <jxy/locks.hpp> | 使用KGUARDED_MUTEX |
jxy::shared_mutex | std::shared_mutex | <jxy/locks.hpp> | 使用EX_PUSH_LOCK |
jxy::unique_lock | std::unique_lock | <jxy/locks.hpp> | |
jxy::shared_lock | std::shared_lock | <jxy/locks.hpp> | |
jxy::scope_resource | 没有任何 | <jxy/scope.hpp> | 类似于std::experimental::unique_resource |
jxy::scope_exit | 没有任何 | <jxy/scope.hpp> | 类似于std::experimental::scope_exit |
jxy::thread | std::thread | <jxy/thread.hpp> | |
jxy::deque | std::deque | <jxy/deque.hpp> | |
jxy::queue | std:queue | <jxy/queue.hpp> | |
jxy::priority_queue | std::priority_queue | <jxy/queue.hpp> | |
jxy::set | std::set | <jxy/set.hpp> | |
jxy::multiset | std::multiset | <jxy/set.hpp> | |
jxy::stack | std::stack | <jxy/stack.hpp> |
stltest.sys stltest项目实施了针对JXYSTL,使用STL的使用以及Windows内核中例外的驱动程序。
stlkrn.sys stlkrn项目是一个Windows驱动程序,它使用jxystl.lib在Windows内核中实现进程,线程和模块跟踪。
使用ntoskrnl导出的功能,用于处理,线程和图像通知的stlkrn.sys寄存器。使用这些回调,它会在各种对象中跟踪进程,线程和图像加载,这些对象使用jxy::map , jxy::shared_mutex , jxy::wstring等。
驾驶员有两个单身人士。 jxy::ProcessMap和jxy::ThreadMap ,当驱动程序加载( DriverEntry )并在驱动程序卸载( DriverUnload )时拆除时,它们是构造的。值得注意的是,在此处跟踪的每个过程jxy::ProcessMap (以jxy::ProcessContext实现)也管理jxy::ThreadMap 。每个“上下文”( jxy::ProcessContext , jxy::ThreadContext和jxy::ModuleContext )都是共享(引用)对象( jxy::shared_ptr )。因此,线程映射中存在的线程上下文是与过程上下文相关的上下文。
stlkrn.sys的关键组成部分:
| 目的 | 目的 | 来源 | 笔记 |
|---|---|---|---|
jxy::ProcessContext | 在系统上运行的过程的信息。 | process_context.hpp/cpp | 使用jxy::wstring 。具有线程( jxy::ThreadMap )和模块( jxy::ModuleMap )地图成员。 |
jxy::ThreadContext | 在系统上运行的线程的信息。 | thread_context.hpp/cpp | 使用std::atomic 。 |
jxy::ModuleContext | 在给定过程中加载的图像的信息。 | module_context.hpp/cpp | 使用jxy::wstring和jxy::shared_mutex 。 |
jxy::ProcessMap | Singleton,地图将jxy::ProcessContext对象分享到PID。 | process_map.hpp/cpp | Singleton可通过jxy::GetProcessMap访问。使用jxy::shared_mutex和jxy::map 。 |
jxy::ThreadMap | 地图共享jxy::ThreadContext对象到tid。 | thread_map.hpp/cpp | 通过jxy::GetThreadMap访问全局线程表(Singleton)。每个jxy::ProcessContext也都有一个线程映射,该线程映射可以通过jxy::ProcessContext::GetThreads访问。使用jxy::shared_mutex和jxy::map 。 |
jxy::GetModuleMap | 地图共享jxy::ModuleContext到已加载的图像扩展(基本和终端地址)。 | module_map.hpp/cpp | 每个过程上下文都有一个模块地图成员。使用此对象跟踪给定过程的加载图像。使用jxy::shared_mutex和jxy::map |
std::unordered_map对于对象映射的有序树( std::map )将是一个更好的选择。这是有原因不使用的(请参见TODO部分)。
stlkrn!jxy::nt::CreateProcessNotifyRoutine+0xa6:
3: kd> dx proc
proc : {...} [Type: std::shared_ptr<jxy::ProcessContext>]
[<Raw View>] [Type: std::shared_ptr<jxy::ProcessContext>]
[ptr] : 0xffffaa020d73cf70 [Type: jxy::ProcessContext *]
[control block] : custom deleter, custom allocator [Type: std::_Ref_count_resource_alloc<jxy::ProcessContext *,jxy::details::default_delete<jxy::ProcessContext,1,1668307018>,jxy::details::allocator<jxy::ProcessContext,1,1668307018> > (derived from std::_Ref_count_base)]
[+0x000] m_ProcessId : 0x2760 [Type: unsigned int]
[+0x004] m_SessionId : 0x2 [Type: unsigned int]
[+0x008] m_ParentProcessId : 0xcc4 [Type: unsigned int]
[+0x010] m_FileName : "DeviceHarddiskVolume4WindowsSystem32cmd.exe" [Type: std::basic_string<unsigned short,std::char_traits<unsigned short>,jxy::details::allocator<unsigned short,1,1852856394> >]
[+0x030] m_FilePart : "cmd.exe" [Type: std::basic_string<unsigned short,std::char_traits<unsigned short>,jxy::details::allocator<unsigned short,1,1886410826> >]
[+0x050] m_CreatorProcessId : 0x1b08 [Type: unsigned int]
[+0x054] m_CreatorThreadId : 0x26a0 [Type: unsigned int]
[+0x058] m_Threads [Type: jxy::ThreadMap]
[+0x070] m_Modules [Type: jxy::ModuleMap]
我想最初想包括std::unordered_map ,但是它使用ceilf 。 Windows内核中的浮点算术遇到了一些挑战。因此,目前省略它,直到设计适当的解决方案为止。
该解决方案是一个激情项目。目前,它不打算用于生产代码。 x64经过良好的测试和稳定, stlkrn.sys通过完整的驱动程序验证器选项(包括随机的低资源模拟)。在调度或以上进行的例外处理已进行了测试,但在实际用例中没有进行测试。 x86尚未测试。在jxy名称空间下有功能不完整/未使用/未经测试。您的里程可能会有所不同- 如果发现任何问题/错误可以随时与此回购打开问题,我想继续这项工作。
该项目通过尽可能多地使用STL功能,在Windows内核中提供STL支持。在内核开发中使用STL的其他解决方案。本节将概述替代方案,首先我将总结这项工作:
这个项目:
new或delete 。atexit功能。 CRT初始化顺序是非很明显的,驱动器初始化和拆卸应该是显而易见的。 atexit功能可能会引入内核代码的数据竞赛, atexit未实施。Bareflank管理程序:
Bareflank在其管理程序中实现了对运行C ++的支持。他们有完整的STL和CRT支持。这是一个综合项目,可以在内核模式下具有该标准的大量功能(包括例外)。据我了解,他们的解决方案迫使NonPagedPool在全球new / delete分配上。我必须赞扬Bareflank的实施,这是经过深思熟虑的跨平台。但是,Windows实现通过Cygwin和“ Shims”来构建,以支持Windows内核。相比之下,该项目的目标是对Windows内核进行体贴。它启用指定池标签和类型(分页与非页面),并希望最大程度地减少与使用C ++和在内核模式下使用STL相关的“尖锐边缘”。综上所述,Bareflank对做什么给人留下了深刻的印象。有关Bareflank对C ++的支持的出色演讲,我强烈建议您在CPPCON 2016上观看Rian Quinn博士的演讲。
win32kernelstl:
Win32KernElstl项目确实允许您直接在内核中使用STL功能。该项目实现了全局new / delete ,并强迫NonPagedPool ,它实现了CRT初始化支持,并且在抛出CPP异常时会进行BugCheck。它没有尝试做CPP例外。由于假设,因此我发现在任何严重用例中都不彻底。该代码是相当清楚和记录的,我建议给该项目进行浏览,以围绕内核中的C ++支持进行教育。一个请注意,Win32KernElstl中的CRT代码确实实现了atexit但请记住,此处的编译器没有发出同步(与用户模式相反)。因此,需要在atexit列表中插入条目的本地静态可能会导致双定位或双重竞赛。
驾驶员加上:
该项目实现了必要的C ++设施,以将许多C ++解决方案拉入内核模式( EASTL , msgpack等)。 Driver Plus Plus实现CRT初始化和全局new / delete支持(这迫使NonPagedPool )。同样,这与该项目的目标背道而驰。但是,该项目确实可以在内核模式下使用许多出色的C ++设施。它确实对它所拉入的C ++解决方案进行了修改,以支持其用例。如前所述,Driver Plus Plus还可以使atexit进行假设。
KTL -Dymok93
KTL(DyMok93的Windows内核模板库)重新形成了大量现代C ++,并且正在积极地为Windows内核中使用更多的支持。它还在许多内核原始图中提供了对RAII的支持,提供了本机ANSI_STRING和UNICODE_STRING支持,它为注册内核回调提供了一些有用的包装器,以及Windows内核周围的更多便利性功能。它实现了全局new / delete ,并具有预处理器定义( KTL_USING_NON_PAGED_NEW_AS_DEFAULT ),用于在默认分页或非分页的情况下切换,这很好。但是,它使用一个池标签( KTL_HEAP_TAG )。此外,现有的分配模板不能使开发人员可以指定池标签,因此使用此库AS-IS会导致所有分配都使用同一池标签标记。也就是说,实施一个自定义分配器可以赋予分配标签的能力是合理的。该库确实有例外支持,尽管仅x64。 KTL中的例外支持是基于Avakar的,并具有增强功能和修复程序。我赞扬这里的工作,对存在的设施数量印象深刻,它具有合理的包装和积极发展。我想将来探索更多使用它,并有可能在stlkrn和KTL的更好的异常支持下进行合作。 STL功能的重新实现,缺乏本机池标记支持以及全球分配器与该项目的意识形态相反。
KTL -Meesong:
KTL(Meesong的Windows内核模板库)重新形成了大量现代C ++功能,可用于Windows内核。它还实现了全局new / delete但在提供可能在可能的情况下指定池标签和类型的设施方面做得不错。但是,这确实意味着全球分配器可能会隐藏在一个不太明显的池中。此外,该项目中的模板分配器的成本为分配器和Deallocator对象的两个点,我还担心分配器类型之间的转换可能允许交叉池/标签同级/折痕。总的来说,这里实施的设施数量给我留下了深刻的印象。 STL功能和全球分配器的重新实现与该项目的意识形态背道而驰。
内核桥:
内核桥梁为Windows内核开发实施了一些重要的设施。该库提供了用于使用C ++对象注册Windows回调的包装器。我想寻找更多时间使用和调查此解决方案。它确实实现了CRT支持。实现的atexit功能不是动态的 - 它使用静态数组,如果它用完了插槽,则会失败。默认的new / delete力NonPagedPool 。它没有完整的异常支持,如果抛出了CPP异常,它将错误地检查 - 它不会在堆栈上放弃对象。
该存储库借鉴了一些已经存在的工作。学分归功于作者。