Skip to content
Snippets Groups Projects
Commit cf18a62b authored by chrg's avatar chrg
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
build/
cmake_minimum_required(VERSION 3.16)
project(ShamAlloc LANGUAGES C CXX)
add_library(shamalloc SHARED
shamalloc.c
include/shamalloc.h
)
target_include_directories(shamalloc PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:>
)
target_compile_options(shamalloc
PRIVATE -fPIC -D_GNU_SOURCE -Wall -W
)
target_link_libraries(shamalloc
PUBLIC dl
)
add_library(shamallocpp SHARED
shamalloc.cpp
)
target_link_libraries(shamallocpp
PUBLIC
shamalloc
)
target_compile_options(shamallocpp
PUBLIC -g
)
add_subdirectory(test)
# ShamAlloc
A dynamic library for making `malloc`, `calloc`, and `realloc` return `NULL`
during tests:
```c
#include <stdio.h>
#include <shamalloc.h>
int
main () {
void *a; void *b;
// Break malloc, calloc, and realloc after 0 allocations.
break_alloc(0);
a = malloc(8);
// Remember to unbreak malloc again, otherwise printf might fail.
break_alloc(-1);
printf("%p\n", a); // Prints "(null)"
// Break malloc, calloc, and realloc after 1 allocations.
break_alloc(1);
a = malloc(8);
b = malloc(8);
break_alloc(-1);
printf("%p, %p\n", a, b); // Prints "(null), <pointer>"
return 0;
}
```
or in c++:
```cpp
#include <iostream>
#include <shamalloc.h>
int
main(int argc, char ** argv) {
break_alloc(0);
try {
int * ptr = new int;
} catch (const std::bad_alloc& e) {
std::cout << "Allocation failed: " << e.what() << "\n";
}
break_alloc(-1);
int * ptr = new int;
std::cout << "Allocation succeded: " << ptr << "\n";
}
```
*Why, would I ever do such a thing?* Well, most people forget to check if
`malloc` returns `NULL`, or that `new` can throw an exception. By using this
library you can put a ticking time-bomb under your tests, because it better
to fail early than in production.
## Usage
Either, include in compilation:
```sh
clang -o main main.c libshamalloc.so -I<pathto-shamalloc>/include -ldl
```
Or if you use the `CMake` build system, you can add the code
as as subdirectory, in the `CMakeLists.txt` file.
```cmake
add_subdirectory(thirdparty/shamalloc)
... some where later ...
target_link_libraries(my-target
shamalloc
)
```
## Limitations
Currently, when used with [Valgrind](https://valgrind.org/) or
[Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html), Valgrind
and Address Sanitizer will overload the mallocs instead of using the code
of this library.
### Valgrind
To avoid this with Valgrind, use the `--soname-synonyms=somalloc` flag.
```
valgrind --soname-synonyms=somalloc <binary>
```
However, this will not overload `new` operators right now.
#ifndef SHAMALLOC_H
#define SHAMALLOC_H
#include <stdlib.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int32_t shamalloc_time_until_broken = -1;
void break_alloc(int32_t times);
#ifdef __cplusplus
}
#endif
#endif // SHAMALLOC_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <dlfcn.h>
#include "include/shamalloc.h"
void
break_alloc(int32_t until_broken) {
shamalloc_time_until_broken = until_broken;
}
#define COUNTDOWN_OR_BREAK\
if (shamalloc_time_until_broken == 0)\
return NULL;\
if (shamalloc_time_until_broken > 0)\
shamalloc_time_until_broken -= 1;\
// Might be to little on some systems.
#define BUFFER_SIZE (1024)
void *
malloc(size_t size) {
COUNTDOWN_OR_BREAK
static void *(*fptr)(size_t) = NULL;
if (fptr == NULL) {
// To bootstrap malloc we need to allow dlsym to allocate
// some memory. On my system only a single call is needed.
static uint8_t is_recusive_call = 0;
if (is_recusive_call++) {
static uint8_t bootstrap_memory[BUFFER_SIZE];
// Check that we don't bootstrap the same memory twice, or
// that we try to allocate too much memory
if (is_recusive_call > 1 || size > BUFFER_SIZE) abort();
return bootstrap_memory;
}
fptr = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc");
is_recusive_call = 0;
}
if (fptr == NULL) abort();
return (*fptr)(size);
}
void *
calloc(size_t nmemb, size_t size) {
COUNTDOWN_OR_BREAK
static void *(*fptr)(size_t, size_t) = NULL;
fptr = fptr ? fptr
: ((void *(*)(size_t, size_t)) dlsym(RTLD_NEXT, "calloc"));
if (fptr == NULL) abort();
return (*fptr)(nmemb, size);
}
void *
realloc(void * ptr, size_t size) {
COUNTDOWN_OR_BREAK
static void *(*fptr)(void*, size_t) = NULL;
fptr = fptr ? fptr
: ((void *(*)(void*, size_t)) dlsym(RTLD_NEXT, "realloc"));
if (fptr == NULL) abort();
return (*fptr)(ptr, size);
}
#import "include/shamalloc.h"
#include <cstdio>
#include <cstdlib>
#include <dlfcn.h>
#include <new>
// A hacky override to get new working with Valgrind
void* operator new(std::size_t size) {
static void *(*fptr)(size_t) = NULL;
if (shamalloc_time_until_broken == 0) {
throw std::bad_alloc{};
} else {
if (shamalloc_time_until_broken > 0)
shamalloc_time_until_broken -= 1;
// Load the orginal new
// TODO: is _Znwm global across all platforms?
fptr = fptr ? fptr
: ((void *(*)(size_t)) dlsym(RTLD_NEXT, "_Znwm"));
if (fptr == NULL) std::abort();
return (*fptr)(size);
}
}
add_executable(shamalloc-test main.c)
target_link_libraries(shamalloc-test
shamalloc
)
target_compile_options(shamalloc-test
PRIVATE
-Wall
)
add_executable(shamalloc-test-cpp main.cpp)
target_compile_options(shamalloc-test-cpp
PRIVATE
-Wall
-g
)
target_link_libraries(shamalloc-test-cpp
shamallocpp
)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <shamalloc.h>
int
main () {
void* a; void* b; void* c;
// break after zero allocations
break_alloc(0);
a = malloc(124);
b = calloc(1, 124);
c = realloc(a, 124);
break_alloc(-1);
printf("%16p, %16p, %16p\n", a, b, c);
// break after one allocation.
break_alloc(1);
a = malloc(124);
b = calloc(1, 124);
c = realloc(a, 124);
break_alloc(-1);
printf("%16p, %16p, %16p\n", a, b, c);
// malloc still works
a = malloc(124);
b = calloc(1, 124);
c = realloc(a, 124);
printf("%16p, %16p, %16p\n", a, b, c);
return 0;
}
#include <iostream>
#include <shamalloc.h>
int
main() {
break_alloc(0);
try {
int * ptr = new int;
std::cout << "Allocation (unexpectedly) succeded: " << ptr << "\n";
} catch (const std::bad_alloc& e) {
std::cout << "Allocation failed: " << e.what() << "\n";
}
break_alloc(-1);
int * ptr = new int;
std::cout << "Allocation succeded: " << ptr << "\n";
delete ptr;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment