Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
MallocGuard.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <atomic>
7#include <exception>
8#if qStroika_Foundation_Common_Platform_Linux
9#include <malloc.h>
10#endif
11
12#if qStroika_Foundation_Common_Platform_POSIX
13#include <unistd.h>
14#endif
15
16#include "Stroika/Foundation/Memory/Common.h"
17
18#include "Trace.h"
19
20#include "MallocGuard.h"
21
22using namespace Stroika::Foundation;
23
24#if qStroika_Foundation_Debug_MallocGuard
25namespace {
26 constexpr array<byte, 16> kMallocGuardHeader_BASE_{
27 0xf3, 0xfa, 0x0b, 0x93, 0x48, 0x50, 0x46, 0xe6, 0x22, 0xf1, 0xfa, 0xc0, 0x9a, 0x0b, 0xeb, 0x23,
28 };
29 constexpr array<byte, 16> kMallocGuardFooter_BASE_{
30 0x07, 0x41, 0xa4, 0x2b, 0xba, 0x97, 0xcb, 0x38, 0x46, 0x1e, 0x3c, 0x42, 0x3c, 0x5f, 0x0c, 0x80,
31 };
32
33 using GuradBytes_ = array<byte, qStroika_Foundation_Debug_MallocGuard_GuardSize>;
34#if qStroika_Foundation_Debug_MallocGuard_GuardSize == 16
35 constexpr GuradBytes_ kMallocGuardHeader_{kMallocGuardHeader_BASE_};
36 constexpr GuradBytes_ kMallocGuardFooter_{kMallocGuardFooter_BASE_};
37#else
38 const GuradBytes_ kMallocGuardHeader_;
39 const GuradBytes_ kMallocGuardFooter_;
40 struct DoInit_ () {
41 DoInit_ ()
42 {
43 size_t fromI = 0;
44 for (size_t i = 0; i < NEltsOf (kMallocGuardHeader_); ++i) {
45 kMallocGuardHeader_[i] = kMallocGuardHeader_BASE_[fromI];
46 kMallocGuardFooter_[i] = kMallocGuardFooter_[fromI];
47 ++fromI;
48 if (fromI >= NEltsOf (kMallocGuardHeader_BASE_)) {
49 fromI = 0;
50 }
51 }
52 }
53 }
54}
55sDoInit_x_;
56#endif
57 constexpr byte kDeadMansLand_[] = {
58 0x1d, 0xb6, 0x20, 0x27, 0x43, 0x7a, 0x3d, 0x1a, 0x13, 0x65,
59 };
60
61 struct alignas (alignof (long double)) Header_ {
62 size_t fRequestedBlockSize;
63 GuradBytes_ fGuard;
64 };
65 struct alignas (alignof (long double)) Footer_ {
66 GuradBytes_ fGuard;
67 size_t fRequestedBlockSize;
68 };
69
70 void OhShit_ (const char* why)
71 {
72 static bool sDone_{false}; // doing terminate MIGHT allocate more memory ... just go with the flow if that happens - and don't re-barf (e.g. allow backtrace if possible)
73 if (not sDone_) {
74 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wunused-result\"")
75 sDone_ = true;
76 const char kMsg_[] = "Fatal Error detected in Stroika Malloc Guard\n";
77 ::write (2, kMsg_, NEltsOf (kMsg_));
78 DbgTrace ("%s", kMsg_);
79 {
80 ::write (2, why, ::strlen (why));
81 ::write (2, "\n", 1);
82 DbgTrace ("%s", why);
83 }
84 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wunused-result\"")
85 terminate ();
86 }
87 }
88
89 void* ExposedPtrToBackendPtr_ (void* p)
90 {
91 if (p == nullptr) {
92 OhShit_ ("unexpected nullptr in ExposedPtrToBackendPtr_");
93 }
94 return reinterpret_cast<Header_*> (p) - 1;
95 }
96 void* BackendPtrToExposedPtr_ (void* p)
97 {
98 if (p == nullptr) {
99 OhShit_ ("unexpected nullptr in BackendPtrToExposedPtr_");
100 }
101 return reinterpret_cast<Header_*> (p) + 1;
102 }
103 size_t AdjustMallocSize_ (size_t s)
104 {
105 // Header_ before 's' and after 's'
106 return sizeof (Header_) + s + sizeof (Footer_);
107 }
108
109 bool IsDeadMansLand_ (const byte* s, const byte* e)
110 {
111 // NYI cuz not clear if/how/where to use...
112 return false;
113 }
114 void SetDeadMansLand_ (byte* s, byte* e)
115 {
116 const byte* pBadFillStart = begin (kDeadMansLand_);
117 const byte* pBadFillEnd = end (kDeadMansLand_);
118 const byte* badFillI = pBadFillStart;
119 for (byte* oi = s; oi != e; ++oi) {
120 *oi = *badFillI;
121 ++badFillI;
122 if (badFillI == pBadFillEnd) {
123 badFillI = pBadFillStart;
124 }
125 }
126 }
127 void SetDeadMansLand_ (void* p)
128 {
129 const Header_* hp = reinterpret_cast<const Header_*> (p);
130 SetDeadMansLand_ (reinterpret_cast<byte*> (p), reinterpret_cast<byte*> (p) + AdjustMallocSize_ (hp->fRequestedBlockSize));
131 }
132
133 /*
134 * Not 100% threadsafe, but probably OK.
135 *
136 * If we add something and it doesn't really get added, we just miss the opporuntity to detect a bug, but we don't create one.
137 *
138 * Assure we flush any entries CLEARED with ClearFromFreeList_ (why we use atomic_store_explicit)
139 *
140 * Also FreeList alway uses BackendPtr - not ExternalPtr
141 */
142 volatile void* sFreeList_[100];
143 volatile void** sFreeList_NextFreeI_ = &sFreeList_[0];
144 void Add2FreeList_ (void* p)
145 {
146 *sFreeList_NextFreeI_ = p;
147 volatile void** next = sFreeList_NextFreeI_ + 1;
148 if (next >= end (sFreeList_)) {
149 next = begin (sFreeList_);
150 }
151 sFreeList_NextFreeI_ = next; // race in that we could SKIP recording a free element, but thats harmless - just a missed opportunity to detect an error
152 }
153 void* MyAtomicLoad_ (volatile void** p)
154 {
155 //unclear why this doesn't work....return std::atomic_load_explicit (p, memory_order_acquire);
156 // unclear that this is safe (but we do in Thread.cpp code too)
157 return atomic_load_explicit (reinterpret_cast<volatile atomic<void*>*> (p), memory_order_acquire);
158 }
159 void MyAtomicStore_ (volatile void** p, void* value)
160 {
161 //unclear why this doesn't work....std::atomic_store_explicit (p, nullptr, memory_order_release);
162 // unclear that this is safe (but we do in Thread.cpp code too)
163 atomic_store_explicit (reinterpret_cast<volatile atomic<void*>*> (p), value, memory_order_release);
164 }
165 void ClearFromFreeList_ (void* p)
166 {
167 // not a race because you cannot free and allocate the same pointer at the same time
168 for (volatile void** i = begin (sFreeList_); i != end (sFreeList_); ++i) {
169 if (MyAtomicLoad_ (i) == p) {
170 MyAtomicStore_ (i, nullptr);
171 }
172 }
173 }
174 bool IsInFreeList_ (const void* p)
175 {
176 for (volatile void** i = begin (sFreeList_); i != end (sFreeList_); ++i) {
177 if (MyAtomicLoad_ (i) == p) {
178 return true;
179 }
180 }
181 return false;
182 }
183
184 void Validate_ (const Header_& header, const Footer_& footer)
185 {
186 if (::memcmp (&header.fGuard, &kMallocGuardHeader_, sizeof (kMallocGuardHeader_)) != 0) {
187 OhShit_ ("Invalid leading header guard");
188 }
189 if (::memcmp (&footer.fGuard, &kMallocGuardFooter_, sizeof (kMallocGuardFooter_)) != 0) {
190 OhShit_ ("Invalid trailing footer guard");
191 }
192 if (header.fRequestedBlockSize != footer.fRequestedBlockSize) {
193 OhShit_ ("Mismatch between header/trailer block sizes");
194 }
195 // OK
196 }
197 void ValidateBackendPtr_ (const void* p)
198 {
199 if (IsInFreeList_ (p)) {
200 // check FIRST because if freed, the header will be all corrupted
201 OhShit_ ("Pointer already freed (recently)");
202 }
203 const Header_* hp = reinterpret_cast<const Header_*> (p);
204 const Footer_* fp = reinterpret_cast<const Footer_*> (reinterpret_cast<const byte*> (hp + 1) + hp->fRequestedBlockSize);
205 Footer_ footer; //tmporary so aligned
206 (void)::memcpy (&footer, fp, sizeof (footer)); // align access
207 Validate_ (*hp, footer);
208 }
209
210 void PatchNewPointer_ (void* p, size_t requestedSize)
211 {
212 Header_* hp = reinterpret_cast<Header_*> (p);
213 (void)::memcpy (begin (hp->fGuard), begin (kMallocGuardHeader_), kMallocGuardHeader_.size ());
214 hp->fRequestedBlockSize = requestedSize;
215 Footer_* fp = reinterpret_cast<Footer_*> (reinterpret_cast<byte*> (hp + 1) + hp->fRequestedBlockSize);
216 (void)::memcpy (begin (fp->fGuard), begin (kMallocGuardFooter_), kMallocGuardFooter_.size ());
217 fp->fRequestedBlockSize = requestedSize;
218 }
219}
220#endif
221
222#if qStroika_Foundation_Debug_MallocGuard
223
224extern "C" void __libc_free (void* __ptr);
225extern "C" void* __libc_malloc (size_t __size);
226extern "C" void* __libc_realloc (void* __ptr, size_t __size);
227extern "C" void* __libc_calloc (size_t __nmemb, size_t __size);
228extern "C" void __libc_free (void* __ptr);
229
230extern "C" void* calloc (size_t __nmemb, size_t __size)
231{
232 size_t n = __nmemb * __size;
233 void* p = malloc (n);
234 (void)::memset (p, 0, n);
235 return p;
236}
237
238extern "C" void cfree (void* __ptr)
239{
240 free (__ptr);
241}
242
243extern "C" void free (void* __ptr)
244{
245 if (__ptr == nullptr) {
246 // according to http://linux.die.net/man/3/free
247 // "if ptr is NULL, no operation is performed." - and glibc does call this internally
248 return;
249 }
250 void* p = ExposedPtrToBackendPtr_ (__ptr);
251 ValidateBackendPtr_ (p);
252 SetDeadMansLand_ (p);
253 Add2FreeList_ (p);
254 __libc_free (p);
255}
256
257extern "C" void* malloc (size_t __size)
258{
259 void* p = __libc_malloc (AdjustMallocSize_ (__size));
260 PatchNewPointer_ (p, __size);
261 ClearFromFreeList_ (p);
262 ValidateBackendPtr_ (p);
263 if (p != nullptr) {
264 p = BackendPtrToExposedPtr_ (p);
265 }
266 return p;
267}
268
269extern "C" void* realloc (void* __ptr, size_t __size)
270{
271 if (__ptr == nullptr) {
272 // from http://linux.die.net/man/3/realloc
273 // If ptr is NULL, then the call is equivalent to malloc(size),
274 return malloc (__size);
275 }
276 if (__ptr != nullptr and __size == 0) {
277 // from http://linux.die.net/man/3/realloc
278 // if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr).
279 free (__ptr);
280 return nullptr;
281 }
282 void* p = ExposedPtrToBackendPtr_ (__ptr);
283 ValidateBackendPtr_ (p);
284 size_t n = AdjustMallocSize_ (__size);
285 void* newP = __libc_realloc (p, n);
286 if (newP != nullptr) {
287 PatchNewPointer_ (newP, __size);
288 if (newP != p) {
289 //Already been freed, so not safe to set at this point!!! - SetDeadMansLand_ (p);
290 Add2FreeList_ (p);
291 ClearFromFreeList_ (newP);
292 }
293 ValidateBackendPtr_ (newP);
294 newP = BackendPtrToExposedPtr_ (newP);
295 }
296 return newP;
297}
298
299extern "C" void* valloc (size_t __size)
300{
301 // http://linux.die.net/man/3/valloc "OBSOLETE"
302 OhShit_ ("valloc not supported in qStroika_Foundation_Debug_MallocGuard (valloc is OBSOLETE)");
303 return nullptr;
304}
305
306extern "C" void* pvalloc (size_t __size)
307{
308 // http://linux.die.net/man/3/valloc "OBSOLETE"
309 OhShit_ ("pvalloc not supported in qStroika_Foundation_Debug_MallocGuard (pvalloc is OBSOLETE)");
310 return nullptr;
311}
312
313extern "C" void* memalign (size_t __alignment, size_t __size)
314{
315 // http://linux.die.net/man/3/valloc "OBSOLETE"
316 OhShit_ ("memalign not supported in qStroika_Foundation_Debug_MallocGuard (memalign is OBSOLETE)");
317 return nullptr;
318}
319
320extern "C" size_t malloc_usable_size (void* ptr)
321{
322 if (ptr == nullptr) {
323 return 0;
324 }
325 void* p = ExposedPtrToBackendPtr_ (ptr);
326 ValidateBackendPtr_ (p);
327 const Header_* hp = reinterpret_cast<const Header_*> (p);
328 return hp->fRequestedBlockSize;
329}
330
331extern "C" int posix_memalign (void** memptr, size_t alignment, size_t size)
332{
333 // Probably SHOULD implement ... but so far not running into trouble cuz anything I link to calling this...
334 OhShit_ ("posix_memalign () not supported in qStroika_Foundation_Debug_MallocGuard (it should be)");
335 return 0;
336}
337#endif
#define DbgTrace
Definition Trace.h:309