1 //--------------------------------------------------------------------------
2 // Copyright (C) 2016-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18
19 // memory_manager.cc author Joel Cornett <jocornet@cisco.com>
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <cassert>
26 #include <new>
27
28 #include "main/thread.h"
29
30 #include "memory_allocator.h"
31 #include "memory_cap.h"
32
33 #ifdef UNIT_TEST
34 #include "catch/snort_catch.h"
35 #endif
36
37 namespace memory
38 {
39
40 // -----------------------------------------------------------------------------
41 // metadata
42 // -----------------------------------------------------------------------------
43
44 // This structure must be aligned to max_align_t as long as we are prefixing
45 // it to memory allocations so that the returned memory is also aligned.
46 struct alignas(max_align_t) Metadata
47 {
48 #if defined(REG_TEST) || defined(UNIT_TEST)
49 static constexpr size_t SANITY_CHECK_VALUE = 0xabcdef;
50 size_t sanity;
51 #endif
52
53 // number of requested bytes
54 size_t payload_size;
55
56 // total number of bytes allocated, including Metadata header
57 size_t total_size() const;
58 void* payload_offset();
59
60 #if defined(REG_TEST) || defined(UNIT_TEST)
validmemory::Metadata61 bool valid() const
62 { return sanity == SANITY_CHECK_VALUE; }
63 #endif
64
65 Metadata(size_t = 0);
66
67 static size_t calculate_total_size(size_t);
68
69 template<typename Allocator>
70 static Metadata* create(size_t);
71
72 static Metadata* extract(void*);
73 };
74
total_size() const75 inline size_t Metadata::total_size() const
76 { return calculate_total_size(payload_size); }
77
payload_offset()78 inline void* Metadata::payload_offset()
79 { return this + 1; }
80
Metadata(size_t n)81 inline Metadata::Metadata(size_t n) :
82 #if defined(REG_TEST) || defined(UNIT_TEST)
83 sanity(SANITY_CHECK_VALUE),
84 #endif
85 payload_size(n)
86 { }
87
calculate_total_size(size_t n)88 inline size_t Metadata::calculate_total_size(size_t n)
89 { return sizeof(Metadata) + n; }
90
91 template<typename Allocator>
create(size_t n)92 Metadata* Metadata::create(size_t n)
93 {
94 auto meta =
95 static_cast<Metadata*>(Allocator::allocate(calculate_total_size(n)));
96
97 if ( !meta )
98 return nullptr;
99
100 // Trigger metadata ctor
101 *meta = Metadata(n);
102
103 #if defined(REG_TEST) || defined(UNIT_TEST)
104 assert(meta->valid());
105 #endif
106
107 return meta;
108 }
109
extract(void * p)110 Metadata* Metadata::extract(void* p)
111 {
112 assert(p);
113
114 auto meta = static_cast<Metadata*>(p) - 1;
115
116 #if defined(REG_TEST) || defined(UNIT_TEST)
117 assert(meta->valid());
118 #endif
119
120 return meta;
121 }
122
123 // -----------------------------------------------------------------------------
124 // the meat
125 // -----------------------------------------------------------------------------
126
127 class ReentryContext
128 {
129 public:
ReentryContext(bool & flag)130 ReentryContext(bool& flag) :
131 already_entered(flag), flag(flag)
132 { flag = true; }
133
~ReentryContext()134 ~ReentryContext()
135 { flag = false; }
136
is_reentry() const137 bool is_reentry() const
138 { return already_entered; }
139
140 private:
141 const bool already_entered;
142 bool& flag;
143 };
144
145 template<typename Allocator = MemoryAllocator, typename Cap = MemoryCap>
146 struct Interface
147 {
148 static void* allocate(size_t);
149 static void deallocate(void*);
150
151 static THREAD_LOCAL bool in_allocation_call;
152 };
153
154 template<typename Allocator, typename Cap>
allocate(size_t n)155 void* Interface<Allocator, Cap>::allocate(size_t n)
156 {
157 // prevent allocation reentry
158 ReentryContext reentry_context(in_allocation_call);
159 assert(!reentry_context.is_reentry());
160
161 auto meta = Metadata::create<Allocator>(n);
162
163 if ( !meta )
164 return nullptr;
165
166 Cap::allocate(meta->total_size());
167 return meta->payload_offset();
168 }
169
170 template<typename Allocator, typename Cap>
deallocate(void * p)171 void Interface<Allocator, Cap>::deallocate(void* p)
172 {
173 if ( !p )
174 return;
175
176 auto meta = Metadata::extract(p);
177 assert(meta);
178
179 Cap::deallocate(meta->total_size());
180 Allocator::deallocate(meta);
181 }
182
183 template<typename Allocator, typename Cap>
184 THREAD_LOCAL bool Interface<Allocator, Cap>::in_allocation_call = false;
185
186 } //namespace memory
187
188 // -----------------------------------------------------------------------------
189 // new /delete replacements
190 // -----------------------------------------------------------------------------
191
192 // these don't have to be visible to operate as replacements
193
194 #ifdef ENABLE_MEMORY_OVERLOADS
operator new(size_t n)195 void* operator new(size_t n)
196 {
197 auto p = memory::Interface<>::allocate(n);
198 if ( !p )
199 throw std::bad_alloc();
200
201 return p;
202 }
203
operator new[](size_t n)204 void* operator new[](size_t n)
205 { return ::operator new(n); }
206
operator new(size_t n,const std::nothrow_t &)207 void* operator new(size_t n, const std::nothrow_t&) noexcept
208 { return memory::Interface<>::allocate(n); }
209
operator new[](size_t n,const std::nothrow_t &)210 void* operator new[](size_t n, const std::nothrow_t&) noexcept
211 { return memory::Interface<>::allocate(n); }
212
operator delete(void * p)213 void operator delete(void* p) noexcept
214 { memory::Interface<>::deallocate(p); }
215
operator delete[](void * p)216 void operator delete[](void* p) noexcept
217 { ::operator delete(p); }
218
operator delete(void * p,const std::nothrow_t &)219 void operator delete(void* p, const std::nothrow_t&) noexcept
220 { ::operator delete(p); }
221
operator delete[](void * p,const std::nothrow_t &)222 void operator delete[](void* p, const std::nothrow_t&) noexcept
223 { ::operator delete[](p); }
224
operator delete(void * p,size_t)225 void operator delete(void* p, size_t) noexcept
226 { ::operator delete(p); }
227
operator delete[](void * p,size_t)228 void operator delete[](void* p, size_t) noexcept
229 { ::operator delete[](p); }
230 #endif
231
232 // -----------------------------------------------------------------------------
233 // unit tests
234 // -----------------------------------------------------------------------------
235
236 #ifdef UNIT_TEST
237
238 namespace t_memory
239 {
240
241 struct AllocatorSpy
242 {
allocatet_memory::AllocatorSpy243 static void* allocate(size_t n)
244 { allocate_called = true; allocate_arg = n; return pool; }
245
deallocatet_memory::AllocatorSpy246 static void deallocate(void* p)
247 { deallocate_called = true; deallocate_arg = p; }
248
resett_memory::AllocatorSpy249 static void reset()
250 {
251 pool = nullptr;
252 allocate_called = false;
253 allocate_arg = 0;
254 deallocate_called = false;
255 deallocate_arg = nullptr;
256 }
257
258 static void* pool;
259 static bool allocate_called;
260 static size_t allocate_arg;
261 static bool deallocate_called;
262 static void* deallocate_arg;
263 };
264
265 void* AllocatorSpy::pool = nullptr;
266 bool AllocatorSpy::allocate_called = false;
267 size_t AllocatorSpy::allocate_arg = 0;
268 bool AllocatorSpy::deallocate_called = false;
269 void* AllocatorSpy::deallocate_arg = nullptr;
270
271 struct CapSpy
272 {
allocatet_memory::CapSpy273 static void allocate(size_t n)
274 {
275 update_allocations_called = true;
276 update_allocations_arg = n;
277 }
278
deallocatet_memory::CapSpy279 static void deallocate(size_t n)
280 {
281 update_deallocations_called = true;
282 update_deallocations_arg = n;
283 }
284
resett_memory::CapSpy285 static void reset()
286 {
287 update_allocations_called = false;
288 update_allocations_arg = 0;
289
290 update_deallocations_called = false;
291 update_deallocations_arg = 0;
292 }
293
294 static bool update_allocations_called;
295 static size_t update_allocations_arg;
296
297 static bool update_deallocations_called;
298 static size_t update_deallocations_arg;
299 };
300
301 bool CapSpy::update_allocations_called = false;
302 size_t CapSpy::update_allocations_arg = 0;
303
304 bool CapSpy::update_deallocations_called = false;
305 size_t CapSpy::update_deallocations_arg = 0;
306
307 } // namespace t_memory
308
309 TEST_CASE( "memory metadata", "[memory]" )
310 {
311 using namespace t_memory;
312
313 AllocatorSpy::reset();
314 constexpr size_t n = 1;
315 char pool[sizeof(memory::Metadata) + n];
316
317 SECTION( "create" )
318 {
319 AllocatorSpy::pool = pool;
320
321 auto meta = memory::Metadata::create<AllocatorSpy>(n);
322
323 CHECK( (void*)meta == (void*)pool );
324 CHECK( meta->valid() );
325 CHECK( meta->payload_size == n );
326 }
327
328 SECTION( "extract" )
329 {
330 auto meta_pool = reinterpret_cast<memory::Metadata*>(pool);
331 meta_pool[0] = memory::Metadata(n);
332
333 void* p = &meta_pool[1];
334
335 auto meta = memory::Metadata::extract(p);
336
337 CHECK( (void*)meta == (void*)pool );
338 CHECK( meta->payload_offset() == p );
339 }
340 }
341
342 TEST_CASE( "memory manager interface", "[memory]" )
343 {
344 using namespace t_memory;
345
346 AllocatorSpy::reset();
347 CapSpy::reset();
348
349 constexpr size_t n = 1;
350 char pool[sizeof(memory::Metadata) + n];
351
352 using Interface = memory::Interface<AllocatorSpy, CapSpy>;
353
354 SECTION( "allocation" )
355 {
356 SECTION( "allocation failure" )
357 {
358 auto p = Interface::allocate(n);
359
360 CHECK( p == nullptr );
361
362 CHECK( AllocatorSpy::allocate_called );
363 CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
364
365 CHECK_FALSE( CapSpy::update_allocations_called );
366 }
367
368 SECTION( "success" )
369 {
370 AllocatorSpy::pool = pool;
371
372 auto p = Interface::allocate(n);
373
374 CHECK( p > (void*)pool );
375
376 CHECK( AllocatorSpy::allocate_called );
377 CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
378
379 CHECK( CapSpy::update_allocations_called );
380 CHECK( CapSpy::update_allocations_arg == memory::Metadata::calculate_total_size(n) );
381 }
382 }
383
384 SECTION( "deallocation" )
385 {
386 SECTION( "nullptr" )
387 {
388 Interface::deallocate(nullptr);
389
390 CHECK_FALSE( AllocatorSpy::deallocate_called );
391 CHECK_FALSE( CapSpy::update_deallocations_called );
392 }
393
394 SECTION( "success" )
395 {
396 auto meta_pool = reinterpret_cast<memory::Metadata*>(pool);
397 meta_pool[0] = memory::Metadata(n);
398
399 auto p = meta_pool[0].payload_offset();
400
401 Interface::deallocate(p);
402
403 CHECK( AllocatorSpy::deallocate_called );
404 CHECK( AllocatorSpy::deallocate_arg == (void*)pool );
405 CHECK( CapSpy::update_deallocations_called );
406 CHECK( CapSpy::update_deallocations_arg == memory::Metadata::calculate_total_size(n) );
407 }
408 }
409 AllocatorSpy::pool = nullptr;
410 AllocatorSpy::deallocate_arg = nullptr;
411 }
412
413 #endif
414
415