My goal is to wrap malloc() with my own implementation and then inject that implementation into executables using LD_PRELOAD trick for educational purposes.
I've made two files, test.c for testing the injection and wrapper.c which wraps malloc() and is compiled as a shared library.
My question is this:
Is it possible for wrapper to detect that it's caller, test is done (at the end of main()) to free it's memory (dynamic_array)?
I can't add something like deinit() to wrapper because I don't necessarily have access to caller's code like I do in the case of test.
I know it might be best to do it using RAII in C++, but what can be done in C?
// test.c
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
(void)malloc(1);
return 0;
}
Built with:
gcc -o test test.c
// wrapper.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <dlfcn.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
static void* (*real_malloc)(size_t size) = NULL;
// Untested pseudocode, but you get the idea
void *dynamic_array = NULL;
unsigned dynamic_array_idx = 0;
size_t dynamic_array_size = 100;
void add_ptr(void* ptr)
{
if (!dynamic_array) {
dynamic_array = real_malloc(dynamic_array_size );
}
if (dynamic_array_idx >= dynamic_array_size) {
dynamic_array_size *= 2;
dynamic_array = realloc(dynamic_array, dynamic_array_size);
}
dynamic_array[dynamic_array_idx++] = ptr;
}
void* malloc(size_t size)
{
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
void* ptr = real_malloc(size);
add_ptr(ptr);
return ptr;
}
Built with:
gcc -c -fPIC -o wrapper.o wrapper.c
gcc -shared -o wrapper.so wrapper.o
Ran with:
LD_PRELOAD=wrapper.so ./test
When real_malloc is initialized, you can use atexit to designate a function to be called when the program exits. You can use this to clean up your memory.
Also, dynamic_array should have type void ** to store an array of void *.
void cleanup(void)
{
free(dynamic_array);
}
void* malloc(size_t size)
{
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
atexit(cleanup); // register cleanup handler
}
void* ptr = real_malloc(size);
add_ptr(ptr);
return ptr;
}
Related
I'm trying to wrap the C library functions malloc and free to detect whether there is memory leakage in my code. I extend the malloc/free functions by adding to them an fprintf to write to a file the address of the malloc/free and the size.
Compiling this code with gcc or clang gives a segmentation fault in the fopen() line.
Here is the command:
gcc -o mainapp main.c -Wall -Wextra
I placed the fopen inside the malloc and free function but also get the same issue: Segmentation fault (core dumped)
I can't find an explanation for the issue.
Here is my complete code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define __USE_GNU
#include <dlfcn.h>
#define TEST_MEM_LEAK 1 // a value of 1 means to join the memory leak detection, and a value of 0 means not to join
#if TEST_MEM_LEAK
typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void *p);
free_t free_f = NULL;
int malloc_flag = 1; // It is used to prevent repeated recursion and cannot exit because the printf function will call malloc for memory allocation
int free_flag = 1;
const char* logFileName = "/home/hammamiw/Documents/HeapMonitor/allocs.log";
FILE* fp = NULL;
void initCheck()
{
fp = fopen("/home/hammamiw/Documents/HeapMonitor/allocs.log", "w");
}
void *malloc(size_t size)
{
if(malloc_flag) {
initCheck();
malloc_flag = 0; // Used to prevent printf from causing an error when calling malloc recursively
void *p = malloc_f(size);
fprintf(fp, "malloc, %lx, %lu\n", (uintptr_t)p, size);
//printf("m\n");
malloc_flag = 1; // It is used to ensure that the initial value of flag flag is consistent when malloc in this file is called again
return p;
}
else {
return malloc_f(size); // Here, the malloc function in the system library obtained by dlsym is called
}
}
void free(void *p)
{
initCheck();
if(free_flag) {
//initCheck();
free_flag = 0;
fprintf(fp, "F, %lx\n", (uintptr_t)p);
//printf("f\n");
free_f(p);
free_flag = 1;
} else {
free_f(p);
}
}
#endif
int main()
{
#if TEST_MEM_LEAK // the part from if to endif can be divided into function calls
malloc_f = dlsym(RTLD_NEXT, "malloc");
if(!malloc_f) {
printf("load malloc failed: %s\n", dlerror());
return 1;
}
free_f = dlsym(RTLD_NEXT, "free");
if(!free_f) {
printf("load free failed: %s\n", dlerror());
return 1;
}
#endif
void *p1 = malloc(10); //The malloc function in this article will be called first
void *p2 = malloc(20);
//Here, p2 is not released and there is a memory leak. Judge by checking whether the number of malloc and free times printed is the same
free(p2);
free(p1);
return 0;
}
NOTE: the code works well if I use printf instead of fprintf -> prints "f" and "m" at each free and malloc call.
Environment: Ubuntu 22.04, C language, GCC compiler version 11.3.0
You need to set malloc_flag before calling fopen(). Then you will protect against recursion not only via fprintf() but also via fopen().
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stddef.h>
static int counter = 0;
void* malloc(size_t size) {
void*(*mymalloc)(size_t) = (void* (*)(size_t)) dlsym(RTLD_NEXT, "malloc");
counter++;
}
void free(void* ptr) {
printf(ptr);
counter--;
}
int main(void) {
int* x = (int*) malloc(sizeof(int));
*x = 5;
printf("%d\n", *x);
free(x);
printf(counter);
When I go to compile the program I get an error such as:
mallocfree.c:2:10: fatal error: dlfcn.h: No such file or directory
I am using Visual Studio Code and was wondering if anyone could help me with the problem. Thanks!
I have a simple program let's call it a victim:
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
int *a = malloc(200);
printf("%p\n", a);
}
And I want to inject a shared object into this program that will log all the calls to the malloc function. So it will works like a memory logger:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void *
malloc(size_t n)
{
static void * (*real_malloc)(size_t) = NULL;
if (real_malloc == NULL) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (real_malloc == NULL) {
#if 0
printf("MEMORY FILTER: can't find the real malloc() function: %s\n", dlerror());
#endif
exit(EXIT_FAILURE);
}
}
void *result = real_malloc(n);
#if 0
if (result != NULL)
printf("MEMORY FILTER: allocated %zu bytes at address %p.\n", n, result);
else
printf("MEMORY FILTER: failed to allocate %zu bytes.\n", n);
#endif
return result;
}
And then I run the victim as follows:
$ LD_LIBRARY_PATH=. LD_PRELOAD=libmemlog.so ./victim
0x55e0747792a0
It works fine but when I uncomment #if 0 ... #endif blocks when the printf function is called then I get a segmentation fault. This is happens because printf calls malloc inside itself and we got an endless recursion which eventually crushes.
How can I get logging work in this case?
I am learning c and trying to build using makefile. I am stuck on the following error and don't know what to do next.
the build command is
gcc -o logfind logfind.o cmdargutils.o filesystem_utils.o file_utils.o strutils.o
If I need both file_utils.o and cmdargutils.o but if I add both I get the following error.
error screenshot
ERROR
file_utils.o:(.rodata+0x0): multiple definition of `MAX_LINE'
logfind.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile:2: recipe for target 'logfind' failed
make: *** [logfind] Error 1
The source is:
Makefile
logfind: clean logfind.o
gcc -o logfind logfind.o cmdargutils.o filesystem_utils.o file_utils.o strutils.o
logfind.o: logfind.c cmdargutils.o file_utils.o filesystem_utils.o strutils.o error_codes.h
gcc -c logfind.c
cmdargutils.o: cmdargutils.c cmdargutils.h
gcc -c cmdargutils.c
file_utils.o: file_utils.c file_utils.h
gcc -c file_utils.c
filesystem_utils.o: filesystem_utils.c filesystem_utils.h
gcc -c filesystem_utils.c
strutils.o: strutils.c strutils.h
gcc -c strutils.c
clean:
rm -f *.o logfind
cmdargutils.h
#ifndef CMD_ARG_UTILS
#define CMD_ARG_UTILS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include "error_codes.h"
#include "strutils.h"
struct Argument {
bool is_and_operation;
int count;
char **search_terms;
};
struct Argument *argument_create(int argc, char **argv, int start, bool is_and_operation);
void argument_destroy(struct Argument *argument);
struct Argument *parse_arguments(int argc, char **argv);
#endif
error_codes.h
#ifndef ERROR_CODES
#define ERROR_CODES
enum error_codes {
MEMORY_ERROR,
INPUT_ERROR
};
#endif
file_utils.h
#ifndef FILE_UTILS
#define FILE_UTILS
#define _GNU_SOURCE
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "cmdargutils.h"
const size_t MAX_LINE = 1024;
bool is_match(char *, struct Argument *);
bool scan_file(char *, struct Argument *);
#endif
filesystem_utils.h
#ifndef FILESYSTEM_UTILS
#define FILESYSTEM_UTILS
#include <glob.h>
#include <string.h>
#include "strutils.h"
struct SearchFiles {
int count;
char **paths;
};
struct SearchFiles *search_files_create(int count, char** paths);
void search_files_destroy(struct SearchFiles *search_files);
struct SearchFiles *scan_directory(char *directory_path, char *pattern);
#endif
strutils.h
#ifndef STRUTILS
#define STRUTILS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error_codes.h"
char *strdup(const char *source);
char **copy_string_array(char **source, int start, int end);
#endif
logfind.c
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include "cmdargutils.h"
#include "filesystem_utils.h"
#include "file_utils.h"
int main(int argc, char **argv) {
struct Argument *argument = parse_arguments(argc, argv);
int i = 0;
struct SearchFiles *search_files = scan_directory(".", "*.*");
for(i = 0; i < search_files->count; i++) {
scan_file(search_files->paths[i], argument);
}
search_files_destroy(search_files);
argument_destroy(argument);
return 0;
}
cmdargutils.c
#include "cmdargutils.h"
struct Argument *argument_create(int argc, char **argv, int start, bool is_and_operation){
struct Argument *argument = (struct Argument *)malloc(sizeof(struct Argument));
if(!argument) {
printf("Could not initialize arguments.\n");
exit(MEMORY_ERROR);
}
argument->count = argc - start;
argument->is_and_operation = is_and_operation;
argument->search_terms = copy_string_array(argv, start, argc);
return argument;
}
void argument_destroy(struct Argument *argument){
int i = 0;
for(i = 0; i < argument->count; i++) {
free(argument->search_terms[i]);
}
free(argument->search_terms);
free(argument);
argument = NULL;
}
struct Argument *parse_arguments(int argc, char **argv) {
struct Argument *argument = NULL;
bool is_and_operation = true;
int start = 0;
if(argc < 2) {
printf("Not enough arguments\n");
exit(INPUT_ERROR);
}
char *operation = argv[1];
if(strcmp(operation, "-o") == 0) {
is_and_operation = false;
if(argc < 3) {
printf("Not enough arguments\n");
exit(INPUT_ERROR);
}
}
start = is_and_operation ? 1 : 2;
argument = argument_create(argc, argv, start, is_and_operation);
return argument;
}
file_utils.c
#include "file_utils.h"
bool is_match(char *line, struct Argument *argument) {
int i = 0;
bool isMatch = false;
for(i = 0; i < argument->count; i++) {
char *found = strcasestr(line, argument->search_terms[i]);
if(!found) {
if(argument->is_and_operation) {
isMatch = false;
break;
} else {
continue;
}
} else {
isMatch = true;
if(argument->is_and_operation) {
continue;
} else {
break;
}
}
}
return isMatch;
}
bool scan_file(char *path, struct Argument *argument) {
FILE *file = fopen(path, "r");
int line_number = 0;
char *line = malloc(MAX_LINE);
while(fgets(line, MAX_LINE - 1, file)!= NULL) {
++line_number;
if(is_match(line, argument)) {
printf("%s:%d\n", path, line_number);
printf("\t%s\n", line);
}
}
free(line);
fclose(file);
}
filesystem_utils.c
#include "filesystem_utils.h"
struct SearchFiles *search_files_create(int count, char** paths) {
struct SearchFiles *search_files = (struct SearchFiles *)malloc(sizeof(struct SearchFiles));
search_files->count = count;
search_files->paths = copy_string_array(paths, 0, count);
return search_files;
}
void search_files_destroy(struct SearchFiles *search_files) {
int i = 0;
for(i = 0; i < search_files->count; i++) {
free(search_files->paths[i]);
}
free(search_files->paths);
free(search_files);
search_files = NULL;
}
struct SearchFiles *scan_directory(char *directory_path, char *pattern) {
glob_t globbuf;
int error = glob(pattern, GLOB_MARK, NULL, &globbuf);
if(!error) {
struct SearchFiles *search_files = search_files_create(globbuf.gl_pathc, globbuf.gl_pathv);
globfree(&globbuf);
return search_files;
}
return NULL;
}
strutils.c
#include "strutils.h"
char *strdup(const char *source) {
char *dest = malloc(strlen(source) + 1);
if(!dest) {
printf("Memory allocation error\n");
exit(MEMORY_ERROR);
}
strcpy(dest, source);
return dest;
}
char **copy_string_array(char **source, int start, int end) {
char **dest = (char **)malloc(sizeof(char *) * (end - start));
int di = 0;
int si = start;
for(di = 0, si = start; si < end;
si++, di++) {
dest[di] = strdup(source[si]);
}
return dest;
}
read documentation!
First, take a few hours to read documentation of GNU make, and read how to invoke GCC. You also need to understand more about the preprocessor, so read documentation of cpp. You want to take advantage of builtin GNU make rules (so run make -p to understand them) and variables. See also this answer. You could use remake (as remake -x) to debug your Makefile. You apparently don't understand how make and how gcc should be used, so you need to read more. Read also a C tutorial, look into some C reference, and glance when needed into the C11 standard n1570. Of course, read the documentation of every function you use (e.g. printf(3) etc..). For Linux system programming, read a book like ALP and relevant man pages from syscalls(2) and intro(3) etc...
Then read How to debug small programs. You certainly want to compile with all warnings and debug info.
a better Makefile
You might try something like:
# a better Makefile
# your C compiler
CC= gcc
# the verbose remove
RM= rm -vf
# your C compilation flags
CFLAGS= -Wall -Wextra -g
# your C source files
MY_CSOURCES= logfind.c cmdargutils.c filesystem_utils.c file_utils.c strutils.c
# the corresponding object files
MY_OBJECTS= $(patsubst %.c, %.o, $(MY_CSOURCES))
# the conventional phony targets
.PHONY: all clean
# the only program is for the default target all
all: logfind
logfind: $(MY_OBJECTS)
$(LINK.c) $< -o $#
# cleaning the mess
clean:
$(RM) logfind *.o *~
Of course, you need dependencies for object files on header files. You could compute them automatically, but it is simpler to explicit them, so add something like:
strutils.o: strutils.c strutils.h
and so on for each other object files.
BTW my HelloWorld/ directory on github is a tutorial example for using make
your multiple definition bug
You are getting multiple definition of MAX_LINE because it is defined in a header file included by several translation units, hence several translation units define it.
So either make it a preprocessor constant #define MAX_LINE 1024 in your header file_utils.h, or put there only a declaration like extern const int MAX_LINE; and define it only once in a single translation unit, as const int MAX_LINE=1024; in file_utils.c
general hints
I strongly recommend doing some iterative and incremental development: code only one or two dozen lines at once, then compile them, improve them to get no warnings, debug them with the GDB debugger and test them. At last repeat all this till satisfied. I do recommend using also a version control system (like git) even for school homework.
You might want to use valgrind to hunt memory leaks and other dynamic memory allocation bugs.
You could also use some static source analyzer like clang-analyzer or even Frama-C.
Once your program is debugged, you might add optimization flags like -O2 into your CFLAGS (in particular if you benchmark it with time(1)).
You could be interested by ntfw(3).
So I have these three files
Main.c
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include "support.h"
int main( void ) {
int* num1 = malloc(100);
printf("num1: %p", &num1);
}
Support.c
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "support.h"
void *malloc(size_t size) {
struct block_meta *block;
if (size <= 0) {
return NULL;
}
if (!global_base) { // First call.
block = request_space(NULL, size);
if (!block) {
return NULL;
}
global_base = block;
} else {
struct block_meta *last = global_base;
block = find_free_block(&last, size);
if (!block) { // Failed to find free block.
block = request_space(last, size);
if (!block) {
return NULL;
}
} else { // Found free block
block->free = 0;
block->magic = 0x77777777;
}
}
return(block+1);
}
void free(void *ptr) {
if (!ptr) {
return;
}
struct block_meta* block_ptr = get_block_ptr(ptr);
assert(block_ptr->free == 0);
assert(block_ptr->magic == 0x77777777 || block_ptr->magic == 0x12345678);
block_ptr->free = 1;
block_ptr->magic = 0x55555555;
}
void *realloc(void *ptr, size_t size) {
if (!ptr) {
// NULL ptr. realloc should act like malloc.
return malloc(size);
}
struct block_meta* block_ptr = get_block_ptr(ptr);
if (block_ptr->size >= size) {
// We have enough space. Could free some once we implement split.
return ptr;
}
// Need to really realloc. Malloc new space and free old space.
// Then copy old data to new space.
void *new_ptr;
new_ptr = malloc(size);
if (!new_ptr) {
return NULL; // TODO: set errno on failure.
}
memcpy(new_ptr, ptr, block_ptr->size);
free(ptr);
return new_ptr;
}
void *calloc(size_t nelem, size_t elsize) {
size_t size = nelem * elsize; // TODO: check for overflow.
void *ptr = malloc(size);
memset(ptr, 0, size);
return ptr;
}
Support.h
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
struct block_meta {
size_t size;
struct block_meta *next;
int free;
int magic; // For debugging only. TODO: remove this in non-debug mode.
};
#define META_SIZE sizeof(struct block_meta)
void *global_base = NULL;
struct block_meta *find_free_block(struct block_meta **last, size_t size) {
struct block_meta *current = global_base;
while (current && !(current->free && current->size >= size)) {
*last = current;
current = current->next;
}
return current;
}
struct block_meta *request_space(struct block_meta* last, size_t size) {
struct block_meta *block;
block = sbrk(0);
void *request = sbrk(size + META_SIZE);
assert((void*)block == request); // Not thread safe.
if (request == (void*) -1) {
return NULL; // sbrk failed.
}
if (last) { // NULL on first request.
last->next = block;
}
block->size = size;
block->next = NULL;
block->free = 0;
block->magic = 0x12345678;
return block;
}
struct block_meta *get_block_ptr(void *ptr) {
return (struct block_meta*)ptr - 1;
}
However when I attempt to compile using
gcc -o asgn2 main.c support.c
I get the error
/tmp/ccscmcbS.o:(.bss+0x0): multiple definition of `global_base'
/tmp/ccyjhjQC.o:(.bss+0x0): first defined here
/tmp/ccscmcbS.o: In function `find_free_block':
support.c:(.text+0x0): multiple definition of `find_free_block'
/tmp/ccyjhjQC.o:main.c:(.text+0x0): first defined here
/tmp/ccscmcbS.o: In function `request_space':
support.c:(.text+0x55): multiple definition of `request_space'
/tmp/ccyjhjQC.o:main.c:(.text+0x55): first defined here
/tmp/ccscmcbS.o: In function `get_block_ptr':
support.c:(.text+0xfe): multiple definition of `get_block_ptr'
/tmp/ccyjhjQC.o:main.c:(.text+0xfe): first defined here
collect2: error: ld returned 1 exit status
I dont believe that I declared those methods more than once, also it is in a much different format than I am usually given. Not quite sure what it means.
The problem is that you have functions and globals defined (as opposed to declared) in your header file. Therefore, those functions are pulled into both main.c and support.c when they are compiled. Then during the linking phase, the linker sees multiple definitions.
Even if you had include guards, it wouldn't help in this case because that only defends against multiple definitions in a single compilation unit, not across multiple units.
Take the definitions of those function out of the header file, replace them with declarations, and put them either in support.c or in a separate .c file.
You can use the -fcommon option for gcc.
Make sure that the header is included only once, so add something like the following to the headers source code:
#ifndef _HAVE_SUPPORT_H
#define _HAVE_SUPPORT_H
// ...
// YOUR HEADER SOURCE CODE
// ...
#endif //_HAVE_SUPPORT_H
As I said this makes sure that the header is included only once, because then it defines _HAVE_SUPPORT_H. If now another source tries to include it, it will not do anything because _HAVE_SUPPRORT_H is already defined.
It also helps if you have only function declarations in the header and your 'real' functions will be in another *.c file.
Edit:
The second parts is the most important for your problem as #kaylum noticed
For me the solution was simple, downgrade to previous GCC version.
Here is comparison gcc installed on two different Ubuntu version.
GCC for ubuntu 20.04: https://packages.ubuntu.com/focal/gcc (gcc 9)
GCC for ubuntu 22.04: https://packages.ubuntu.com/jammy/gcc (gcc 11)
Because on my case, code was legacy code from about 20 years ago, then it makes sense for me to keep using old compiler.