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

Initial commit

parents
Branches
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