Derive-C
Loading...
Searching...
No Matches
memory_tracker.h
Go to the documentation of this file.
1
7#pragma once
8
10
11#include <stddef.h>
12#include <stdint.h>
13#include <stdio.h>
14
15#if defined CUSTOM_MEMORY_TRACKING
16 // For clang, we detect asan with feature
17 // - See: https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension
18 #if defined __has_feature
19 #if __has_feature(address_sanitizer)
20 #define ASAN_ON
21 #endif
22 #if __has_feature(memory_sanitizer)
23 #define MSAN_ON
24 #endif
25 #endif
26
27 // For detection under gcc
28 // - See: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/cpp.pdf
29 // - No support for msan as of GCC 15.2.0
30 #if defined __SANITIZE_ADDRESS__
31 #define ASAN_ON
32 #endif
33#endif
34
35#if defined ASAN_ON
36 #if defined MSAN_ON
37 #error "cannot support asan and msan simultaneously"
38 #endif
39 #include <sanitizer/asan_interface.h>
40#elif defined MSAN_ON
41 #include <sanitizer/msan_interface.h>
42#endif
43
50
51// JUSTIFY: Different tracker levels
52// - So we can disable container, or allocator level tracking when testing/debugging tracking
53// itself.
58typedef enum { // NOLINT(performance-enum-size)
63
64#if defined CUSTOM_MEMORY_TRACKING
66 (memory_tracker_level)CUSTOM_MEMORY_TRACKING;
67#else
68// JUSTIFY: Default as container
69// - Library users assume correct library implementation, so expect tracking from the container
70// level
72#endif
73
75 const volatile void* addr, size_t size) {
76 if (level <= memory_tracker_global_level) {
77#if defined(MSAN_ON)
78 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
79 switch (cap) {
82 __msan_poison(addr, size);
83 return;
84 }
86 __msan_unpoison(addr, size);
87 return;
88 }
89 }
90 UNREACHABLE("Invalid capability");
91#elif defined(ASAN_ON)
92 switch (cap) {
94 __asan_poison_memory_region(addr, size);
95 return;
96 }
99 __asan_unpoison_memory_region(addr, size);
100 return;
101 }
102 }
103 UNREACHABLE("Invalid capability");
104#else
105 (void)addr;
106 (void)size;
107 (void)cap;
108#endif
109 }
110}
111
113 const void* addr, size_t size) {
114 if (level <= memory_tracker_global_level) {
115#if defined(MSAN_ON)
116 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
117 switch (cap) {
120 if (__msan_test_shadow((void*)addr, size) != -1) {
121 PANIC("Memory region %p (%zu bytes) is not uninitialised, but should be", addr,
122 size);
123 }
124 return;
125 }
127 if (__msan_test_shadow((void*)addr, size) == -1) {
128 PANIC("Memory region %p (%zu bytes) is not uninitialised, but should be", addr,
129 size);
130 }
131 return;
132 }
133 }
134 UNREACHABLE("Invalid capability");
135#elif defined(ASAN_ON)
136 bool const region_is_poisoned = __asan_region_is_poisoned((void*)addr, size);
137 switch (cap) {
139 if (!region_is_poisoned) {
140 bool const is_at_end_of_granule = is_aligned_pow2_exp((char*)addr - 7, 3);
141 bool const is_next_byte_poisoned =
142 __asan_region_is_poisoned((void*)((char*)addr + 1), 1);
143
144 // JUSTIFY: panic conditionally
145 // - Asan tracks poisoning at the granule/8 byte level, with the number of bytes
146 // poisoned from the end of the granule
147 // - Hence if we have a poisoned byte, in the middle of a granule, with the rest
148 // unpoisoned, asan will mark that byte as unpoisoned
149 // Therefore, we only throw if we are certain the poisoning is wrong (e.g. it would
150 // be continuing contiguous poisoned part of a granule, or its at the start of a
151 // granule)
152 if (is_at_end_of_granule || is_next_byte_poisoned) {
153
154 PANIC("Memory region %p (%zu bytes) is not poisoned, but should be", addr,
155 size);
156 }
157 }
158 return;
159 }
162 if (region_is_poisoned) {
163 PANIC("Memory region %p (%zu bytes) is poisoned, but should be accessible", addr,
164 size);
165 }
166 return;
167 }
168 }
169 UNREACHABLE("Invalid capability");
170#else
171 (void)addr;
172 (void)size;
173 (void)cap;
174#endif
175 }
176}
177
178static void memory_tracker_debug(FILE* stream, const void* addr, size_t size) {
179 fprintf(stream, "memory tracker debug (%zu bytes) at %p ", size, addr);
180#if defined(MSAN_ON)
181 fprintf(stream, "[MSAN]: ")
182 // msan tracks the initialised state, so for none & write we want poisoned / unreadable.
183 for (size_t i = 0; i < size; i++) {
184 fprintf(stream, "\n%p: ", (char*)addr + i);
185 if (__msan_test_shadow((char*)addr + i, 1) == -1) {
186 fprintf(stream, "U [%02x]", *((unsigned char*)addr + i));
187 } else {
188 fprintf(stream, "I [%02x]", *((unsigned char*)addr + i));
189 }
190 }
191#elif defined(ASAN_ON)
192 // Each shadow memory entry covers 8 bytes of memory, aligned to 8 bytes, so we print this.
193 char* addr_start = (char*)addr;
194 char* addr_end = addr_start + size;
195 char* granule_base = (char*)((uintptr_t)addr & ~0x7); // NOLINT(performance-no-int-to-ptr)
196 char* granule_end =
197 (char*)(((uintptr_t)addr + size + 7) & ~0x7); // NOLINT(performance-no-int-to-ptr)
198
199 fprintf(stream, "[ASAN]:");
200 fprintf(stream,
201 "\ndisplaying each 8 byte grandule (asan tracks poisoning as 0-8 bytes from the end)");
202 fprintf(stream, "\n");
203 fprintf(stream, "\n ");
204 for (size_t b = 0; b < 8; b++) {
205 fprintf(stream, " %lu ", b);
206 }
207 fprintf(stream, "\n");
208 for (char* p = granule_base; p < granule_end; p += 8) {
209 fprintf(stream, "%p: ", p);
210 for (char* b = p; b < p + 8; b++) {
211 bool const poisoned = __asan_region_is_poisoned(b, 1);
212 bool const in_selected = (b >= addr_start && b < addr_end);
213 uint8_t value;
214 if (poisoned) {
215 __asan_unpoison_memory_region(b, 1);
216 value = *b;
217 __asan_poison_memory_region(b, 1);
218 } else {
219 value = *b;
220 }
221
222 char const status = poisoned ? 'P' : 'U';
223
224 if (in_selected) {
225 fprintf(stream, "[%c|%02x] ", status, value);
226 } else {
227 fprintf(stream, "|%c|%02x| ", status, value);
228 }
229 }
230 fprintf(stream, "\n");
231 }
232#else
233 fprintf(stream, "[NO TRACKING]");
234 (void)addr;
235 (void)size;
236#endif
237 fprintf(stream, "\n");
238}
static INDEX_TYPE size(SELF const *self)
Definition template.h:275
#define UNREACHABLE(...)
Definition macros.h:49
#define PANIC(...)
Definition macros.h:34
static bool INLINE CONST is_aligned_pow2_exp(const void *ptr, unsigned exp)
Definition math.h:31
static const memory_tracker_level memory_tracker_global_level
static void memory_tracker_check(memory_tracker_level level, memory_tracker_capability cap, const void *addr, size_t size)
memory_tracker_level
@ MEMORY_TRACKER_LVL_NONE
@ MEMORY_TRACKER_LVL_CONTAINER
@ MEMORY_TRACKER_LVL_ALLOC
static void memory_tracker_set(memory_tracker_level level, memory_tracker_capability cap, const volatile void *addr, size_t size)
memory_tracker_capability
a wrapper over asan & msan Containers and allocators can use this for custom asan & msan poisoning,...
@ MEMORY_TRACKER_CAP_NONE
@ MEMORY_TRACKER_CAP_WRITE
@ MEMORY_TRACKER_CAP_READ_WRITE
static void memory_tracker_debug(FILE *stream, const void *addr, size_t size)
static FILE * stream(SELF *self)
Opens a file for.
Definition template.h:112