Skip to content

Commit ac6a7e6

Browse files
author
Gonzalo Diaz
committed
[WIP] memory injector to intercept malloc() calls
1 parent a168c25 commit ac6a7e6

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

src/tests/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
find_package(Catch2 3 REQUIRED)
22
find_path(CATCH_INCLUDE_DIR NAMES catch.hpp PATH_SUFFIXES catch2)
33

4+
# # Create a static library for the memory injector utility
5+
file(GLOB_RECURSE TOOLS_SOURCES "tools/*.cpp")
6+
add_library(memory_injector STATIC ${TOOLS_SOURCES})
7+
48
file(GLOB_RECURSE SOURCES "unit/lib/*.cpp")
59
add_executable(unit-tests_combined ${SOURCES})
610

@@ -12,6 +16,14 @@ target_link_libraries(unit-tests_combined PRIVATE Catch2::Catch2WithMain)
1216
# Functionality to test.
1317
target_link_libraries(unit-tests_combined PRIVATE exercises)
1418

19+
target_link_libraries(unit-tests_combined PRIVATE memory_injector)
20+
21+
# GNU ld (Linux) needs --wrap=malloc to redirect calls to __wrap_malloc.
22+
# macOS uses dyld interposition instead, which requires no linker flag.
23+
if(NOT APPLE)
24+
target_link_options(memory_injector INTERFACE "-Wl,--wrap=malloc")
25+
endif()
26+
1527
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
1628
include(CTest)
1729
include(Catch)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include "memory_injector.hpp"
2+
#include <cstdlib>
3+
4+
// Hidden state variables local to this translation unit
5+
static bool force_immediate_error = false;
6+
static int allocation_counter = 0;
7+
static int target_fault_allocation = -1;
8+
9+
namespace MemoryInjector {
10+
// cppcheck-suppress unusedFunction
11+
void enable_oom_fault() {
12+
force_immediate_error = true;
13+
target_fault_allocation = -1;
14+
}
15+
16+
// cppcheck-suppress unusedFunction
17+
void disable_fault() {
18+
force_immediate_error = false;
19+
allocation_counter = 0;
20+
target_fault_allocation = -1;
21+
}
22+
23+
// cppcheck-suppress unusedFunction
24+
void fail_on_allocation_number(int n) {
25+
force_immediate_error = false;
26+
allocation_counter = 0;
27+
target_fault_allocation = n;
28+
}
29+
} // namespace MemoryInjector
30+
31+
// ── Platform-specific interception ──────────────────────────────────────────
32+
33+
#if defined(__APPLE__)
34+
// macOS: use dyld interposition. No extra linker flags are required.
35+
// The DYLD_INTERPOSE macro creates an entry in the __DATA,__interpose section
36+
// that dyld uses to redirect calls to `malloc` at load time.
37+
38+
#include <malloc/malloc.h>
39+
40+
static void *my_malloc(size_t size) {
41+
// Case 1: Forced immediate out-of-memory error
42+
if (force_immediate_error) {
43+
return nullptr;
44+
}
45+
46+
// Case 2: Error on the Nth allocation attempt
47+
if (target_fault_allocation != -1) {
48+
allocation_counter++;
49+
if (allocation_counter == target_fault_allocation) {
50+
return nullptr;
51+
}
52+
}
53+
54+
// Fall through to the real malloc (resolved via the interpose table)
55+
return malloc(size);
56+
}
57+
58+
#define DYLD_INTERPOSE(_replacement, _replacee) \
59+
__attribute__((used)) static struct { \
60+
const void *replacement; \
61+
const void *replacee; \
62+
} _interpose_##_replacee __attribute__((section("__DATA,__interpose"))) = { \
63+
(const void *)(_replacement), (const void *)(_replacee)}
64+
65+
DYLD_INTERPOSE(my_malloc, malloc);
66+
67+
#else
68+
// Linux / GNU ld: use the --wrap=malloc linker flag.
69+
// The linker renames every call to `malloc` → `__wrap_malloc` and makes the
70+
// original symbol available as `__real_malloc`.
71+
72+
extern "C" void *__real_malloc(size_t);
73+
74+
extern "C" void *__wrap_malloc(size_t size) {
75+
// Case 1: Forced immediate out-of-memory error
76+
if (force_immediate_error) {
77+
return nullptr;
78+
}
79+
80+
// Case 2: Error on the Nth allocation attempt
81+
if (target_fault_allocation != -1) {
82+
allocation_counter++; // NOLINT(cppcoreguidelines-avoid-magic-numbers)
83+
if (allocation_counter == target_fault_allocation) {
84+
return nullptr;
85+
}
86+
}
87+
88+
// If it shouldn't fail, call the real system malloc
89+
return __real_malloc(size);
90+
}
91+
#endif
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
#include <cstddef>
3+
4+
namespace MemoryInjector {
5+
// Triggers an immediate malloc failure
6+
void enable_oom_fault();
7+
8+
// Disables the simulator (malloc goes back to normal behavior)
9+
void disable_fault();
10+
11+
// Advanced: Makes malloc succeed N times and fail on the next allocation
12+
void fail_on_allocation_number(int n);
13+
} // namespace MemoryInjector

0 commit comments

Comments
 (0)