Skip to content

Commit e8f73f6

Browse files
committed
Added tests and updated READMD.md
1 parent 67cc531 commit e8f73f6

37 files changed

+1592
-40
lines changed

Diff for: README.md

+105-17
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,36 @@ integers : { size=10 } [Type: std::vector<int,jxy::details::allo
134134

135135
Below is table of functionality under the `jxy` namespace:
136136

137-
| jxylib | STL equivalent | Notes |
138-
| ------ | -------------- | ----- |
139-
| `jxy::allocator` | `std::allocator` | |
140-
| `jxy::default_delete` | `std::default_delete` | |
141-
| `jxy::unique_ptr` | `std::unique_ptr` | |
142-
| `jxy::shared_ptr` | `std::shared_ptr` | |
143-
| `jxy::basic_string` | `std::basic_string` | |
144-
| `jxy::string` | `std::string` | |
145-
| `jxy::wstring` | `std::wstring` | |
146-
| `jxy::vector` | `std::vector` | |
147-
| `jxy::map` | `std::map` | |
148-
| `jxy::mutex` | `std::mutex` | Uses `KGUARDED_MUTEX` |
149-
| `jxy::shared_mutex` | `std::shared_mutex` | Uses `EX_PUSH_LOCK` |
150-
| `jxy::unique_lock` | `std::unique_lock` | |
151-
| `jxy::shared_lock` | `std::shared_lock` | |
152-
| `jxy::scope_resource` | None | Similar to `std::experimental::unique_resource` |
153-
| `jxy::scope_exit` | None | Similar to `std::experimental::scope_exit` |
137+
| jxylib | STL equivalent | Include | Notes |
138+
| ------ | -------------- | ------- | ----- |
139+
| `jxy::allocator` | `std::allocator` | `<jxy/memory.hpp>` | |
140+
| `jxy::default_delete` | `std::default_delete` | `<jxy/memory.hpp>` | |
141+
| `jxy::unique_ptr` | `std::unique_ptr` | `<jxy/memory.hpp>` | |
142+
| `jxy::shared_ptr` | `std::shared_ptr` | `<jxy/memory.hpp>` | |
143+
| `jxy::basic_string` | `std::basic_string` | `<jxy/string.hpp>`| |
144+
| `jxy::string` | `std::string` | `<jxy/string.hpp>` | |
145+
| `jxy::wstring` | `std::wstring` | `<jxy/string.hpp>` | |
146+
| `jxy::vector` | `std::vector` | `<jxy/vector.hpp>` | |
147+
| `jxy::map` | `std::map` | `<jxy/map.hpp>` | |
148+
| `jxy::multimap` | `std::miltimap` | `<jxy/map.hpp>` | |
149+
| `jxy::mutex` | `std::mutex` | `<jxy/locks.hpp>` | Uses `KGUARDED_MUTEX` |
150+
| `jxy::shared_mutex` | `std::shared_mutex` | `<jxy/locks.hpp>` | Uses `EX_PUSH_LOCK` |
151+
| `jxy::unique_lock` | `std::unique_lock` | `<jxy/locks.hpp>` | |
152+
| `jxy::shared_lock` | `std::shared_lock` | `<jxy/locks.hpp>` | |
153+
| `jxy::scope_resource` | None | `<jxy/scope.hpp>` | Similar to `std::experimental::unique_resource` |
154+
| `jxy::scope_exit` | None | `<jxy/scope.hpp>` | Similar to `std::experimental::scope_exit` |
155+
| `jxy::thread` | `std::thread` | `<jxy/thread.hpp>` | |
156+
| `jxy::deque` | `std::deque` | `<jxy/deque.hpp>` | |
157+
| `jxy::queue` | `std:queue` | `<jxy/queue.hpp>` | |
158+
| `jxy::priority_queue` | `std::priority_queue` | `<jxy/queue.hpp>` | |
159+
| `jxy::set` | `std::set` | `<jxy/set.hpp>` | |
160+
| `jxy::multiset` | `std::multiset` | `<jxy/set.hpp>` | |
161+
| `jxy::stack` | `std::stack` | `<jxy/stack.hpp>` | |
162+
163+
## Tests - `stltest.sys`
164+
165+
The `stltest` project implements a driver that runs some tests against jxystl,
166+
usage of STL, and exceptions in the Windows Kernel.
154167

155168
## Practical Usage - `stlkrn.sys`
156169
The `stlkrn` project is a Windows Driver that uses `jxylib` to implement
@@ -227,6 +240,75 @@ use cases. `x86` has *not* been tested. There is functionality under the
227240
I would like to continue this work over time, if any issues/bugs are found
228241
feel free to open issues against this repo.
229242

243+
## Related Work
244+
245+
This project provides STL support in the Windows Kernel by using as much of the
246+
STL facility as possible. There are other solutions for use of STL in kernel
247+
development. This section will outline alternatives, first I will summarize
248+
this work:
249+
250+
This Project:
251+
- Uses the STL directly. Does **not** reimplement any STL functionality unless absolutely necessary.
252+
- Requires pool types and tags. No global `new` or `delete` is implemented.
253+
- Forbid moving data between objects of different pools or tags.
254+
- Avoids CRT initialization and `atexit` functionality. CRT initialization order
255+
is non-obvious, driver initialization and teardown *should be obvious*. `atexit` functionality
256+
may introduce data races for kernel code, `atexit` is not implemented.
257+
258+
[Bareflank Hypervisor][github.bareflank]:
259+
260+
Bareflank implements support for running C++ in their hypervisor. They have full STL and CRT
261+
support. This is a comprehensive project that enables a plethora features of the standard in
262+
kernel mode (including exceptions). As I understand their solution forces `NonPagedPool` on global
263+
`new`/`delete` allocations. I have to commend Bareflank with their implementation, it's well
264+
thought out and cross platform. However the Windows implementation builds through cygwin and
265+
"shims" in support for the Windows kernel. In comparison, this project aims to be considerate to
266+
the Windows kernel. It enables specifying pool tags and types (paged vs non-paged) and hopes
267+
to minimize "sharp edges" associated with using C++ and the STL in kernel mode. All that said,
268+
Bareflank is impressive for what is does. For an excellent presentation on Bareflank's support of
269+
C++ I highly recommend watching [Dr. Rian Quinn's presentation at cppcon 2016][channel9.bareflank].
270+
271+
[Win32KernelSTL][github.Win32KernelSTL]:
272+
273+
The Win32KernelSTL project does allow you to use STL functionality directly in the kernel. The project
274+
implements global `new`/`delete` and forces `NonPagedPool`, it implements CRT initialization support,
275+
and bugchecks when a cpp exception is thrown. It makes no attempt to do cpp exception unwinding. Due
276+
to the assumptions it makes I find it unpractical for any serious use cases. The code is reasonably
277+
clear and documented, I recommend giving this project a browse for educating around C++ support in the
278+
kernel. One note, the CRT code in Win32KernelSTL does implement `atexit` but keep in mind there is no
279+
synchronization emitted by the compiler here (as opposed to user mode). So a local static requiring
280+
insertion of an entry in the `atexit` list may race causing a double-init or double-free.
281+
282+
[Driver Plus Plus][github.dxx]:
283+
284+
This project implements necessary C++ facility for pulling in a number of C++ solutions into
285+
kernel mode (`EASTL`, `msgpack`, etc.). Driver Plus Plus implements CRT initialization and global
286+
`new`/`delete` support (which forces `NonPagedPool`). Again this is counter to the goals of this
287+
project. However, this project does enable a lot of great C++ facility for use in kernel mode. It
288+
does make modifications to the C++ solutions it pulls in to shim in support for it's use cases.
289+
Driver Plus Plus also makes the assumption around `atexit` as mentioned previously.
290+
291+
[KTL][github.ktl]:
292+
293+
KTL (Windows Kernel Template Library) reimplements a good amount of modern C++ functionality for
294+
use in the Windows Kernel. It also implements global `new`/`delete` but does a decent job
295+
at providing facility for specifying pool tags and types where possible. However this does mean
296+
the global allocator might hide an allocation in a non-obvious pool. Further the template
297+
allocators in this project carry the cost of two points for an allocator and deallocator object,
298+
I am also concerned that conversion between the allocator types may allow for cross pool/tag
299+
allocs/frees. Overall I'm impressed by the amount of facility that is implemented here.
300+
Reimplementation of STL functionality and the global allocators are counter to the ideologies of
301+
this project.
302+
303+
[Kernel-Bridge][github.KernelBridge]:
304+
305+
Kernel-Bridge implements some great facility for Windows Kernel development. The library provides
306+
wrappers for registering for Windows callbacks using C++ objects. I would like to find more time
307+
to use and investigate this solution. It does implement CRT support. The `atexit` functionality
308+
implemented is not dynamic - it uses a static array, if it runs out of slots, it fails. The
309+
default `new`/`delete` forces `NonPagedPool`. It does not have full exception support, it will
310+
bugcheck if a cpp exception is thrown - it will not unwind objects on the stack.
311+
230312
## Credits
231313
This repository draws from some preexisting work. Credits to their authors.
232314

@@ -242,3 +324,9 @@ symbol files, as well as a lot of reverse engineering and guessing.
242324
[github.vcrtl]: https://github.com/avakar/vcrtl
243325
[github.vcrtl.x64]: https://github.com/avakar/vcrtl/tree/master/src/x64
244326
[github.phnt]: https://github.com/processhacker/phnt
327+
[github.bareflank]: https://github.com/Bareflank/hypervisor
328+
[channel9.bareflank]: https://channel9.msdn.com/Events/CPP/CppCon-2016/CppCon-2016-Rian-Quinn-Making-C-and-the-STL-Work-in-the-Linux--Windows-Kernels
329+
[github.Win32KernelSTL]: https://github.com/DragonQuestHero/Win32KernelSTL
330+
[github.dxx]: https://github.com/sidyhe/dxx
331+
[github.ktl]: https://github.com/MeeSong/KTL
332+
[github.KernelBridge]: https://github.com/HoShiMin/Kernel-Bridge

Diff for: include/jxy/alloc.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// the pool type and tags for all allocations.
1010
//
1111
#pragma once
12-
#include <wdm.h>
12+
#include <fltKernel.h>
1313
#include <cstddef>
1414

1515
void* __cdecl operator new(size_t Size, POOL_TYPE PoolType, ULONG PoolTag) noexcept(false);

Diff for: include/jxy/locks.hpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class shared_mutex
2727
{
2828
public:
2929

30+
using native_handle_type = PEX_PUSH_LOCK;
31+
3032
shared_mutex() noexcept;
3133
~shared_mutex() noexcept;
3234
void lock() noexcept;
@@ -35,6 +37,7 @@ class shared_mutex
3537
void lock_shared() noexcept;
3638
bool try_lock_shared() noexcept;
3739
void unlock_shared() noexcept;
40+
native_handle_type native_handle() noexcept;
3841

3942
private:
4043

@@ -47,11 +50,14 @@ class mutex
4750
{
4851
public:
4952

53+
using native_handle_type = PKGUARDED_MUTEX;
54+
5055
mutex() noexcept(false)
5156
{
52-
m_GuardedMutex = ExAllocatePoolWithTag(NonPagedPoolNx,
53-
sizeof(*m_GuardedMutex),
54-
t_PoolTag);
57+
m_GuardedMutex = static_cast<PKGUARDED_MUTEX>(
58+
ExAllocatePoolWithTag(NonPagedPoolNx,
59+
sizeof(*m_GuardedMutex),
60+
t_PoolTag));
5561
if (!m_GuardedMutex)
5662
{
5763
throw std::bad_alloc();
@@ -83,6 +89,11 @@ class mutex
8389
KeReleaseGuardedMutex(m_GuardedMutex);
8490
}
8591

92+
native_handle_type native_handle() noexcept
93+
{
94+
return m_GuardedMutex;
95+
}
96+
8697
private:
8798

8899
PKGUARDED_MUTEX m_GuardedMutex = nullptr;

Diff for: include/jxy/memory.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// jxy::shared_ptr std::shared_ptr
2121
//
2222
#pragma once
23-
#include <wdm.h>
23+
#include <fltKernel.h>
2424
#include <jxy/alloc.hpp>
2525
#include <memory>
2626

Diff for: include/jxy/scope.hpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ class scope_resource
2828
using this_type = scope_resource<T, TReleaseFunctor>;
2929
using value_type = T;
3030

31-
scope_resource() = default;
32-
3331
~scope_resource() noexcept
3432
{
3533
TReleaseFunctor()(m_Resource);
3634
}
3735

36+
scope_resource() = default;
37+
38+
scope_resource(T Resource) : m_Resource(Resource)
39+
{
40+
}
41+
3842
value_type get() const noexcept
3943
{
4044
return m_Resource;
File renamed without changes.

Diff for: include/jxy/thread.hpp

+17-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// jxy::thread std::thread
1111
//
1212
#pragma once
13-
#include <ntddk.h>
13+
#include <fltKernel.h>
1414
#include <thread>
1515
#include <jxy/memory.hpp>
1616

@@ -38,6 +38,8 @@ class thread
3838
static constexpr POOL_TYPE pool_type = PagedPool;
3939
static constexpr ULONG pool_tag = 'TYXJ';
4040

41+
~thread();
42+
4143
thread() noexcept = default;
4244

4345
template <typename TFunc,
@@ -94,7 +96,7 @@ class thread
9496
std::invoke(std::move(std::get<t_Indices>(tuple))...);
9597

9698
//
97-
// Check that no matter what the invoked routine does that it returned
99+
// Check that no matter what the invoked routine does it returns
98100
// to passive.
99101
//
100102
NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
@@ -134,13 +136,19 @@ class thread
134136
}
135137
else
136138
{
137-
NT_VERIFY(ObReferenceObjectByHandle(threadHandle,
138-
THREAD_ALL_ACCESS,
139-
*PsThreadType,
140-
KernelMode,
141-
reinterpret_cast<void**>(&m_Thread),
142-
nullptr));
143-
NT_VERIFY(ObCloseHandle(threadHandle, KernelMode));
139+
NT_VERIFY(NT_SUCCESS(
140+
ObReferenceObjectByHandle(threadHandle,
141+
THREAD_ALL_ACCESS,
142+
*PsThreadType,
143+
KernelMode,
144+
reinterpret_cast<void**>(&m_Thread),
145+
nullptr)));
146+
NT_VERIFY(NT_SUCCESS(ObCloseHandle(threadHandle, KernelMode)));
147+
148+
//
149+
// Release the memory to the invoke routine.
150+
//
151+
params.release();
144152
}
145153
}
146154

Diff for: include/jxy/vector.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ template <typename T,
2222
typename TAllocator = jxy::allocator<T, t_PoolType, t_PoolTag>>
2323
using vector = std::vector<T, TAllocator>;
2424

25+
template <typename T,
26+
ULONG t_PoolTag,
27+
typename TAllocator = jxy::allocator<T, PagedPool, t_PoolTag>>
28+
using paged_vector = std::vector<T, TAllocator>;
29+
30+
template <typename T,
31+
ULONG t_PoolTag,
32+
typename TAllocator = jxy::allocator<T, NonPagedPoolNx, t_PoolTag>>
33+
using non_paged_vector = std::vector<T, TAllocator>;
34+
2535
}

Diff for: jxystl/jxystl.vcxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
<ClInclude Include="..\include\jxy\queue.hpp" />
145145
<ClInclude Include="..\include\jxy\scope.hpp" />
146146
<ClInclude Include="..\include\jxy\set.hpp" />
147-
<ClInclude Include="..\include\jxy\stach.hpp" />
147+
<ClInclude Include="..\include\jxy\stack.hpp" />
148148
<ClInclude Include="..\include\jxy\string.hpp" />
149149
<ClInclude Include="..\include\jxy\thread.hpp" />
150150
<ClInclude Include="..\include\jxy\unordered_map.hpp" />

Diff for: jxystl/jxystl.vcxproj.filters

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<ClInclude Include="..\include\jxy\list.hpp" />
1919
<ClInclude Include="..\include\jxy\deque.hpp" />
2020
<ClInclude Include="..\include\jxy\set.hpp" />
21-
<ClInclude Include="..\include\jxy\stach.hpp" />
21+
<ClInclude Include="..\include\jxy\stack.hpp" />
2222
<ClInclude Include="..\include\jxy\queue.hpp" />
2323
<ClInclude Include="..\include\jxy\unordered_map.hpp" />
2424
<ClInclude Include="..\include\jxy\unordered_set.hpp" />

Diff for: jxystl/locks.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ void jxy::shared_mutex::unlock_shared() noexcept
4848
{
4949
FltReleasePushLockEx(&m_PushLock, 0);
5050
}
51+
52+
jxy::shared_mutex::native_handle_type jxy::shared_mutex::native_handle() noexcept
53+
{
54+
return &m_PushLock;
55+
}

Diff for: jxystl/msvcfill.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// This is done intentionally as to not introduce a path which may be unsafe
1313
// for the kernel.
1414
//
15-
#include <wdm.h>
15+
#include <fltKernel.h>
1616
#include <stdexcept>
1717
#include <system_error>
1818
#include <intrin.h>
@@ -56,7 +56,6 @@ void __cdecl _Throw_Cpp_error(int Code)
5656
// FIXME: This should throw a std::system_error but it comes with global
5757
// allocator requirements. For now we'll throw a faked "system error" as
5858
// a runtime error.
59-
// Maybe need a non-standard jxy::error_code and jxy::system_error?
6059
//
6160
UNREFERENCED_PARAMETER(Code);
6261
throw std::runtime_error("jxy: Faked system error!");

Diff for: jxystl/thread.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
//
88
#include <jxy/thread.hpp>
99

10+
jxy::thread::~thread()
11+
{
12+
NT_ASSERT(m_Thread == nullptr);
13+
}
14+
1015
jxy::thread::thread(thread&& Other) noexcept
1116
: m_Thread(std::exchange(Other.m_Thread, nullptr))
1217
{
@@ -53,6 +58,8 @@ void jxy::thread::join()
5358
{
5459
std::_Throw_Cpp_error(std::_NO_SUCH_PROCESS);
5560
}
61+
62+
ObDereferenceObject(m_Thread);
5663
m_Thread = nullptr;
5764
}
5865

@@ -63,6 +70,7 @@ void jxy::thread::detach()
6370
std::_Throw_Cpp_error(std::_INVALID_ARGUMENT);
6471
}
6572

73+
ObDereferenceObject(m_Thread);
6674
m_Thread = nullptr;
6775
}
6876

Diff for: stlkrn.sln

+17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stlkrn", "stlkrn\stlkrn.vcx
1010
EndProject
1111
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jxystl", "jxystl\jxystl.vcxproj", "{D7E96B42-E040-46B7-A15C-29F0CC9DD469}"
1212
EndProject
13+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stltest", "stltest\stltest.vcxproj", "{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}"
14+
ProjectSection(ProjectDependencies) = postProject
15+
{D7E96B42-E040-46B7-A15C-29F0CC9DD469} = {D7E96B42-E040-46B7-A15C-29F0CC9DD469}
16+
EndProjectSection
17+
EndProject
1318
Global
1419
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1520
Debug|x64 = Debug|x64
@@ -42,6 +47,18 @@ Global
4247
{D7E96B42-E040-46B7-A15C-29F0CC9DD469}.Release|x86.ActiveCfg = Release|Win32
4348
{D7E96B42-E040-46B7-A15C-29F0CC9DD469}.Release|x86.Build.0 = Release|Win32
4449
{D7E96B42-E040-46B7-A15C-29F0CC9DD469}.Release|x86.Deploy.0 = Release|Win32
50+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x64.ActiveCfg = Debug|x64
51+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x64.Build.0 = Debug|x64
52+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x64.Deploy.0 = Debug|x64
53+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x86.ActiveCfg = Debug|Win32
54+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x86.Build.0 = Debug|Win32
55+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Debug|x86.Deploy.0 = Debug|Win32
56+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x64.ActiveCfg = Release|x64
57+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x64.Build.0 = Release|x64
58+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x64.Deploy.0 = Release|x64
59+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x86.ActiveCfg = Release|Win32
60+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x86.Build.0 = Release|Win32
61+
{19BE23CA-600E-4B26-A1A9-1E4878BF9AD1}.Release|x86.Deploy.0 = Release|Win32
4562
EndGlobalSection
4663
GlobalSection(SolutionProperties) = preSolution
4764
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)