Skip to content

Commit 148c8c0

Browse files
committed
Detours implementation of overriding CRT
This commit adds an override to Windows for replacing the CRT malloc/free etc routines.
1 parent 012138e commit 148c8c0

File tree

10 files changed

+349
-1
lines changed

10 files changed

+349
-1
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ jobs:
403403
build-type: [ Release, Debug ]
404404
arch: [ Win32, x64 ]
405405
toolchain: [ "", "-T ClangCL" ]
406-
extra-cmake-flags: [ "" ]
406+
extra-cmake-flags: [ "", "-S src/windows" ]
407407
# Add an extra check for the Windows 8 compatible PAL
408408
include:
409409
- os: windows-2022

src/snmalloc/pal/pal_windows.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
* be destroyed when the program exits or the DLL is
4242
* unloaded.
4343
*/
44+
# pragma warning(push)
4445
# pragma warning(disable : 4075)
4546
# pragma init_seg(".CRT$XCB")
47+
# pragma warning(pop)
4648

4749
namespace snmalloc
4850
{

src/windows/CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
include(FetchContent)
3+
4+
FetchContent_Declare(
5+
vcpkg
6+
GIT_REPOSITORY https://github.com/microsoft/vcpkg
7+
GIT_TAG 2025.06.13
8+
GIT_SHALLOW TRUE
9+
)
10+
11+
FetchContent_MakeAvailable(vcpkg)
12+
13+
set(CMAKE_TOOLCHAIN_FILE
14+
${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake
15+
CACHE STRING "vcpkg toolchain file")
16+
17+
project(snmallocdetours CXX)
18+
19+
add_subdirectory(../.. src/snmalloc EXCLUDE_FROM_ALL)
20+
21+
# On Windows, we need to use the detours library to override the malloc
22+
# functions.
23+
find_path(DETOURS_INCLUDE_DIRS "detours/detours.h")
24+
find_library(DETOURS_LIBRARY detours REQUIRED)
25+
26+
add_library(snmallocdetours OBJECT override/detours.cc)
27+
28+
target_include_directories(snmallocdetours PRIVATE ${DETOURS_INCLUDE_DIRS})
29+
target_link_libraries(snmallocdetours PUBLIC snmalloc)
30+
target_link_libraries(snmallocdetours PRIVATE ${DETOURS_LIBRARY})
31+
32+
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
33+
add_library(snmallocdetourslib SHARED override/lib.cc)
34+
target_include_directories(snmallocdetourslib PRIVATE ${DETOURS_INCLUDE_DIRS})
35+
target_link_libraries(snmallocdetourslib PRIVATE snmallocdetours)
36+
37+
add_executable(snmallocdetoursexample test/main.cc)
38+
target_link_libraries(snmallocdetoursexample snmallocdetourslib)
39+
# Check if 32 bit windows.
40+
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
41+
target_link_options(snmallocdetoursexample PRIVATE "/INCLUDE:_is_snmalloc_detour")
42+
else()
43+
target_link_options(snmallocdetoursexample PRIVATE "/INCLUDE:is_snmalloc_detour")
44+
endif()
45+
46+
add_test(NAME snmallocdetours COMMAND snmallocdetoursexample)

src/windows/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Windows snmalloc override
2+
3+
This directory contains an implementation of the snmalloc allocator that
4+
overrides the default Windows allocator for the C-runtime (CRT).
5+
The implementation uses the detours library to intercept calls to the CRT
6+
and redirects them to snmalloc.
7+
8+
# Building
9+
10+
Build the cmake projects in this directory. From the root of the repository, do
11+
12+
```bash
13+
cmake -B build_detours -S src/windows
14+
cmake --build build_detours
15+
```
16+
17+
This will install the detours library using vcpkg and then build the snmalloc
18+
static library, and a DLL that automatically load snmalloc as the CRT allocator.
19+
20+
# Using
21+
22+
## DLL
23+
To use the snmalloc allocator in your Windows application, you need to link against the `snmallocdetourslib.dll` that was built in the previous step.
24+
You can do this by adding the following line to your CMakeLists.txt:
25+
26+
```cmake
27+
target_link_libraries(your_target_name PRIVATE snmallocdetourslib)
28+
target_link_options(snmallocdetoursexample PRIVATE "/INCLUDE:is_snmalloc_detour")
29+
```
30+
31+
The second line is necessary to ensure that the linker does not optimize away the DLL import.
32+
33+
34+
## Static Library
35+
36+
To use the snmalloc allocator as a static library, you can link against the `snmallocdetourslib.lib` file that was built in the previous step.
37+
You can do this by adding the following line to your CMakeLists.txt:
38+
39+
```cmake
40+
target_link_libraries(your_target_name PRIVATE snmallocdetourslib_static)
41+
```
42+
43+
Then you need to cause the detours routine to be run by adding the following lines to a C++ source file:
44+
45+
```cpp
46+
#include "detours.h"
47+
#pragma warning(push)
48+
#pragma warning(disable : 4075)
49+
#pragma init_seg(".CRT$XCV")
50+
static SnmallocDetour snmalloc_detour;
51+
#pragma warning(pop)
52+
```
53+
54+
This causes the detours code to be run early in the program startup.
55+
56+
## Locally scoped detour
57+
58+
Finally, you can use the detour in a locally scoped manner.
59+
This requires linking against the `snmallocdetourslib.lib`, and then can be used as:
60+
61+
```cpp
62+
#include "detours.h"
63+
64+
65+
void my_function()
66+
{
67+
SnmallocDetour snmalloc_detour;
68+
// snmalloc is now the CRT allocator for this function
69+
// ...
70+
} // snmalloc_detour goes out of scope and the CRT allocator is restored
71+
```
72+
73+
Upon exiting the scope of `snmalloc_detour`, the CRT allocator will be restored to its original state.
74+
75+
76+
# Status
77+
78+
This implementation is currently in an alpha state, and is not yet suitable for production use.
79+
80+
Significant testing is required to ensure that it works for a range of applications.
81+
If you sucessfully use this in your application, please let us know by commenting on #700.

src/windows/override/detours.cc

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* This file provides a Windows-specific overriding of the malloc,
3+
* free, calloc, realloc, and msize functions using the Detours library.
4+
*/
5+
#ifdef _DEBUG
6+
# include <crtdbg.h>
7+
#endif
8+
9+
#include "detours.h"
10+
11+
#include <new>
12+
#include <stdlib.h>
13+
14+
// Symbols for the original malloc, free, calloc, realloc, and msize functions
15+
auto* original_malloc = malloc;
16+
auto* original_calloc = calloc;
17+
auto* original_realloc = realloc;
18+
auto* original_free = free;
19+
auto* original_msize = _msize;
20+
21+
void* (*original_new)(size_t) = operator new;
22+
void* (*original_new2)(size_t, const std::nothrow_t&) = operator new;
23+
24+
void* (*original_new_array)(size_t) = operator new[];
25+
void* (*original_new_array2)(size_t, const std::nothrow_t&) = operator new[];
26+
27+
void (*original_delete)(void*) = operator delete;
28+
void (*original_delete2)(void*, size_t) = operator delete;
29+
void (*original_delete3)(void*, const std::nothrow_t&) = operator delete;
30+
31+
void (*original_delete_array)(void*) = operator delete[];
32+
void (*original_delete_array2)(void*, size_t) = operator delete[];
33+
34+
#include <snmalloc/snmalloc_core.h>
35+
// Provides the global configuration for the snmalloc implementation.
36+
#include <snmalloc/backend/globalconfig.h>
37+
38+
#define SNMALLOC_PROVIDE_OWN_CONFIG
39+
40+
namespace snmalloc
41+
{
42+
class WindowsHeapAsSecondaryAllocator
43+
{
44+
public:
45+
// This flag is used to turn off checks on fast paths if the secondary
46+
// allocator does not own the memory at all.
47+
static constexpr inline bool pass_through = false;
48+
49+
SNMALLOC_FAST_PATH
50+
static void initialize() {}
51+
52+
// We always use snmalloc for allocation.
53+
template<class SizeAlign>
54+
SNMALLOC_FAST_PATH static void* allocate(SizeAlign&&)
55+
{
56+
return nullptr;
57+
}
58+
59+
// If the memory was not deallocated by snmalloc, then try the
60+
// original free.
61+
SNMALLOC_FAST_PATH
62+
static void deallocate(void* pointer)
63+
{
64+
if (pointer == nullptr)
65+
return;
66+
67+
original_free(pointer);
68+
}
69+
70+
SNMALLOC_FAST_PATH
71+
static size_t alloc_size(const void* p)
72+
{
73+
return original_msize(const_cast<void*>(p));
74+
}
75+
};
76+
77+
// Root failed deallocations and msize requests to the Windows heap.
78+
using Config = snmalloc::StandardConfigClientMeta<
79+
NoClientMetaDataProvider,
80+
WindowsHeapAsSecondaryAllocator>;
81+
using Alloc = snmalloc::Allocator<Config>;
82+
} // namespace snmalloc
83+
84+
#define SNMALLOC_STATIC_LIBRARY_PREFIX snmalloc_
85+
#include "detours/detours.h"
86+
87+
#include <snmalloc/override/malloc.cc>
88+
#include <stdio.h>
89+
#include <windows.h>
90+
91+
// This name is not provided by malloc.cc above, so we define it here.
92+
size_t snmalloc_msize(void* ptr)
93+
{
94+
// Call the snmalloc function to get the allocation size.
95+
// This is not accurate as it rounds up, whereas the original msize
96+
// function returns the exact size of the allocation.
97+
return snmalloc::alloc_size(ptr);
98+
}
99+
100+
SnmallocDetour::SnmallocDetour()
101+
{
102+
// Initilialize snmalloc.
103+
snmalloc_free(snmalloc_malloc(1));
104+
105+
DetourTransactionBegin();
106+
DetourAttach(&(PVOID&)original_free, snmalloc_free);
107+
DetourAttach(&(PVOID&)original_delete, snmalloc_free);
108+
DetourAttach(&(PVOID&)original_delete2, snmalloc_free);
109+
DetourAttach(&(PVOID&)original_delete3, snmalloc_free);
110+
DetourAttach(&(PVOID&)original_delete_array, snmalloc_free);
111+
DetourAttach(&(PVOID&)original_delete_array2, snmalloc_free);
112+
DetourAttach(&(PVOID&)original_malloc, snmalloc_malloc);
113+
DetourAttach(&(PVOID&)original_calloc, snmalloc_calloc);
114+
DetourAttach(&(PVOID&)original_realloc, snmalloc_realloc);
115+
DetourAttach(&(PVOID&)original_msize, snmalloc_msize);
116+
DetourAttach(&(PVOID&)original_new, snmalloc_malloc);
117+
DetourAttach(&(PVOID&)original_new2, snmalloc_malloc);
118+
DetourAttach(&(PVOID&)original_new_array, snmalloc_malloc);
119+
DetourAttach(&(PVOID&)original_new_array2, snmalloc_malloc);
120+
121+
DetourTransactionCommit();
122+
}
123+
124+
SnmallocDetour::~SnmallocDetour()
125+
{
126+
// Detours performs allocation so during this some data structures will
127+
// be allocated with snmalloc. These cannot be handled by the Windows heap
128+
// so leave snmalloc::free in place to handle these allocations.
129+
130+
DetourTransactionBegin();
131+
DetourDetach(&(PVOID&)original_calloc, snmalloc_calloc);
132+
DetourDetach(&(PVOID&)original_realloc, snmalloc_realloc);
133+
DetourDetach(&(PVOID&)original_malloc, snmalloc_malloc);
134+
DetourDetach(&(PVOID&)original_msize, snmalloc_msize);
135+
DetourDetach(&(PVOID&)original_new, snmalloc_malloc);
136+
DetourDetach(&(PVOID&)original_new2, snmalloc_malloc);
137+
DetourDetach(&(PVOID&)original_new_array, snmalloc_malloc);
138+
DetourDetach(&(PVOID&)original_new_array2, snmalloc_malloc);
139+
DetourTransactionCommit();
140+
141+
// This transaction's allocation will come from the Windows heap, so it is
142+
// safe to use the Windows heap's free during teardown.
143+
DetourTransactionBegin();
144+
DetourDetach(&(PVOID&)original_free, snmalloc_free);
145+
DetourDetach(&(PVOID&)original_delete, snmalloc_free);
146+
DetourAttach(&(PVOID&)original_delete2, snmalloc_free);
147+
DetourDetach(&(PVOID&)original_delete3, snmalloc_free);
148+
DetourDetach(&(PVOID&)original_delete_array, snmalloc_free);
149+
DetourDetach(&(PVOID&)original_delete_array2, snmalloc_free);
150+
DetourTransactionCommit();
151+
}
152+
153+
extern "C" bool is_snmalloc_detour(void* ptr)
154+
{
155+
return snmalloc::is_owned(ptr);
156+
}

src/windows/override/detours.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
class SnmallocDetour
4+
{
5+
public:
6+
SnmallocDetour();
7+
~SnmallocDetour();
8+
};
9+
10+
// Used to check that a pointer is from the snmalloc detour.
11+
extern "C" __declspec(dllexport) bool is_snmalloc_detour(void* ptr);

src/windows/override/lib.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "detours.h"
2+
3+
#pragma warning(push)
4+
#pragma warning(disable : 4075)
5+
// This pragma uses a segment that is alphabetically later than the
6+
// one used in pal_windows.h. This is required to ensure that the
7+
// global function pointers have been initialized before we attempt to
8+
// detour them.
9+
#pragma init_seg(".CRT$XCV")
10+
static SnmallocDetour snmalloc_detour;
11+
#pragma warning(pop)

src/windows/test/main.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include "../override/detours.h"
2+
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
// This is done in CMake, but could be done here as well
7+
// #pragma comment(linker, "/include:is_snmalloc_detour")
8+
9+
int main()
10+
{
11+
auto p_old = malloc(16);
12+
13+
if (p_old != nullptr && !is_snmalloc_detour(p_old))
14+
{
15+
printf("Detouring malloc and free failed...\n");
16+
}
17+
18+
free(p_old);
19+
20+
printf("Test passed: Detouring malloc and free succeeded.\n");
21+
return 0;
22+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"default-registry": {
3+
"kind": "git",
4+
"baseline": "1a66c32c6f90c2f646529975c3c076ed3dbdae0c",
5+
"repository": "https://github.com/microsoft/vcpkg"
6+
},
7+
"registries": [
8+
{
9+
"kind": "artifact",
10+
"location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
11+
"name": "microsoft"
12+
}
13+
]
14+
}

src/windows/vcpkg.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": [
3+
"detours"
4+
]
5+
}

0 commit comments

Comments
 (0)