Derive-C
Loading...
Searching...
No Matches
memory_tracker.h
Go to the documentation of this file.
1
7#pragma once
8
10#include <derive-c/core/math.h>
11
12#include <stddef.h>
13#include <stdint.h>
14#include <stdio.h>
15
16#if defined CUSTOM_MEMORY_TRACKING
17 // For clang, we detect asan with feature
18 // - See: https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension
19 #if defined __has_feature
20 #if __has_feature(address_sanitizer)
21 #define DC_ASAN_ON
22 #endif
23 #if __has_feature(memory_sanitizer)
24 #define DC_MSAN_ON
25 #endif
26 #endif
27
28 // For detection under gcc
29 // - See: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/cpp.pdf
30 // - No support for msan as of GCC 15.2.0
31 #if defined __SANITIZE_ADDRESS__
32 #define DC_ASAN_ON
33 #endif
34#endif
35
36#if defined DC_ASAN_ON
37 #if defined DC_MSAN_ON
38 #error "cannot support asan and msan simultaneously"
39 #endif
40 #include <sanitizer/asan_interface.h>
41#elif defined DC_MSAN_ON
42 #include <sanitizer/msan_interface.h>
43#endif
44
51
52// JUSTIFY: Different tracker levels
53// - So we can disable container, or allocator level tracking when testing/debugging tracking
54// itself.
64
65#if defined CUSTOM_MEMORY_TRACKING
67 (dc_memory_tracker_level)CUSTOM_MEMORY_TRACKING;
68#else
69// JUSTIFY: Default as container
70// - Library users assume correct library implementation, so expect tracking from the container
71// level
74#endif
75
78 const volatile void* addr, size_t size) {
79 if (level <= dc_memory_tracker_global_level) {
80#if defined(DC_MSAN_ON)
81 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
82 switch (cap) {
85 __msan_poison(addr, size);
86 return;
87 }
89 __msan_unpoison(addr, size);
90 return;
91 }
92 }
93 DC_UNREACHABLE("Invalid capability");
94#elif defined(DC_ASAN_ON)
95 switch (cap) {
97 __asan_poison_memory_region(addr, size);
98 return;
99 }
102 __asan_unpoison_memory_region(addr, size);
103 return;
104 }
105 }
106 DC_UNREACHABLE("Invalid capability");
107#else
108 (void)addr;
109 (void)size;
110 (void)cap;
111#endif
112 }
113}
114
116 dc_memory_tracker_capability cap, const void* addr,
117 size_t size) {
118 DC_ASSERT(size > 0, "Cannot check zero sized region");
119 if (level <= dc_memory_tracker_global_level) {
120#if defined(DC_MSAN_ON)
121 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
122 switch (cap) {
125 for (size_t offset = 0; offset < size; offset++) {
126 const unsigned char* p = (const unsigned char*)addr + offset;
127
128 // For size == 1:
129 // - return -1 => unpoisoned (initialized) -> ERROR here
130 // - return 0 => poisoned (uninitialized) -> OK
131 if (__msan_test_shadow(p, 1) == -1) {
132 DC_PANIC("Memory region %p (%zu bytes) is not fully uninitialised: "
133 "byte %zu (%p) is unpoisoned",
134 addr, size, offset, p);
135 }
136 }
137 return;
138 }
140 for (size_t offset = 0; offset < size; offset++) {
141 const unsigned char* p = (const unsigned char*)addr + offset;
142 if (__msan_test_shadow(p, 1) != -1) {
143 DC_PANIC("Memory region %p (%zu bytes) is not initialised: "
144 "byte %zu (%p) is poisoned",
145 addr, size, offset, p);
146 }
147 }
148 return;
149 }
150 }
151 DC_UNREACHABLE("Invalid capability");
152#elif defined(DC_ASAN_ON)
153 bool const region_is_poisoned = __asan_region_is_poisoned((void*)(uintptr_t)addr, size);
154 switch (cap) {
156 if (!region_is_poisoned) {
157 const char* addr_char = (const char*)addr;
158 bool const is_at_end_of_granule = dc_math_is_aligned_pow2(addr_char - 7, 8);
159 bool const is_next_byte_poisoned =
160 __asan_region_is_poisoned((void*)(uintptr_t)(addr_char + 1), 1);
161
162 // JUSTIFY: panic conditionally
163 // - Asan tracks poisoning at the granule/8 byte level, with the number of bytes
164 // poisoned from the end of the granule
165 // - Hence if we have a poisoned byte, in the middle of a granule, with the rest
166 // unpoisoned, asan will mark that byte as unpoisoned
167 // Therefore, we only throw if we are certain the poisoning is wrong (e.g. it would
168 // be continuing contiguous poisoned part of a granule, or its at the start of a
169 // granule)
170 if (is_at_end_of_granule || is_next_byte_poisoned) {
171
172 DC_PANIC("Memory region %p (%zu bytes) is not poisoned, but should be", addr,
173 size);
174 }
175 }
176 return;
177 }
180 if (region_is_poisoned) {
181 DC_PANIC("Memory region %p (%zu bytes) is poisoned, but should be accessible", addr,
182 size);
183 }
184 return;
185 }
186 }
187 DC_UNREACHABLE("Invalid capability");
188#else
189 (void)addr;
190 (void)size;
191 (void)cap;
192#endif
193 }
194}
195
196DC_PUBLIC static void dc_memory_tracker_debug(FILE* stream, const void* addr, size_t size) {
197 fprintf(stream, "memory tracker debug (%zu bytes) at %p ", size, addr);
198#if defined DC_MSAN_ON
199 fprintf(stream, "[MSAN]:");
200 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
201 for (size_t i = 0; i < size; i++) {
202 char const* ptr = (char const*)addr + i;
203 fprintf(stream, "\n%p: ", ptr);
204 if (__msan_test_shadow(ptr, 1) != -1) {
205 __msan_unpoison(ptr, 1);
206 fprintf(stream, "U [%02x]", *((unsigned char*)ptr));
207 __msan_poison(ptr, 1);
208 } else {
209 fprintf(stream, "I [%02x]", *((unsigned char*)ptr));
210 }
211 }
212#elif defined DC_ASAN_ON
213 // Each shadow memory entry covers 8 bytes of memory, aligned to 8 bytes, so we print this.
214 const char* addr_start = (const char*)addr;
215 const char* addr_end = addr_start + size;
216 const char* granule_base =
217 (const char*)((uintptr_t)addr & ~(uintptr_t)0x7U); // NOLINT(performance-no-int-to-ptr)
218 const char* granule_end = (const char*)(((uintptr_t)addr + size + 7U) &
219 ~(uintptr_t)0x7U); // NOLINT(performance-no-int-to-ptr)
220
221 fprintf(stream, "[ASAN]:");
222 fprintf(stream,
223 "\ndisplaying each 8 byte grandule (asan tracks poisoning as 0-8 bytes from the end)");
224 fprintf(stream, "\n");
225 fprintf(stream, "\n ");
226 for (size_t b = 0; b < 8; b++) {
227 fprintf(stream, " %lu ", b);
228 }
229 fprintf(stream, "\n");
230 for (const char* p = granule_base; p < granule_end; p += 8) {
231 fprintf(stream, "%p: ", p);
232 for (const char* b = p; b < p + 8; b++) {
233 bool const poisoned = __asan_region_is_poisoned((void*)(uintptr_t)b, 1);
234 bool const in_selected = (b >= addr_start && b < addr_end);
235 uint8_t value;
236 if (poisoned) {
237 __asan_unpoison_memory_region((void*)(uintptr_t)b, 1);
238 value = (uint8_t)*b;
239 __asan_poison_memory_region((void*)(uintptr_t)b, 1);
240 } else {
241 value = (uint8_t)*b;
242 }
243
244 char const status = poisoned ? 'P' : 'U';
245
246 if (in_selected) {
247 fprintf(stream, "[%c|%02x] ", status, value);
248 } else {
249 fprintf(stream, "|%c|%02x| ", status, value);
250 }
251 }
252 fprintf(stream, "\n");
253 }
254#else
255 fprintf(stream, "[NO TRACKING]");
256 (void)addr;
257 (void)size;
258#endif
259 fprintf(stream, "\n");
260}
static DC_PUBLIC size_t size(SELF const *self)
Definition template.h:252
static DC_PUBLIC bool DC_INLINE DC_CONST dc_math_is_aligned_pow2(const void *ptr, unsigned alignment)
Definition math.h:67
static DC_PUBLIC void dc_memory_tracker_set(dc_memory_tracker_level level, dc_memory_tracker_capability cap, const volatile void *addr, size_t size)
static const dc_memory_tracker_level dc_memory_tracker_global_level
static DC_PUBLIC void dc_memory_tracker_check(dc_memory_tracker_level level, dc_memory_tracker_capability cap, const void *addr, size_t size)
static DC_PUBLIC void dc_memory_tracker_debug(FILE *stream, const void *addr, size_t size)
dc_memory_tracker_level
@ DC_MEMORY_TRACKER_LVL_CONTAINER
@ DC_MEMORY_TRACKER_LVL_ALLOC
@ DC_MEMORY_TRACKER_LVL_NONE
dc_memory_tracker_capability
a wrapper over asan & msan Containers and allocators can use this for custom asan & msan poisoning,...
@ DC_MEMORY_TRACKER_CAP_WRITE
@ DC_MEMORY_TRACKER_CAP_NONE
@ DC_MEMORY_TRACKER_CAP_READ_WRITE
#define DC_PUBLIC
Definition namespace.h:25
#define DC_PANIC(str,...)
Definition panic.h:29
#define DC_UNREACHABLE(...)
Definition panic.h:44
#define DC_ASSERT(expr,...)
Definition panic.h:37
static DC_PUBLIC FILE * stream(SELF *self)
Definition template.h:108