該項目在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異常,它將錯誤地檢查 - 它不會在堆棧上放棄對象。
該存儲庫借鑒了一些已經存在的工作。學分歸功於作者。