Переопределение "malloc" с использованием механизма LD_PRELOAD
Я пытаюсь написать простую общую библиотеку, которая будет записывать вызовы malloc в stderr (что-то вроде "mtrace", если вы это сделаете).
Однако это не работает.
Вот что я делаю:
/* mtrace.c */
#include <dlfcn.h>
#include <stdio.h>
static void* (*real_malloc)(size_t);
void *malloc(size_t size)
{
void *p = NULL;
fprintf(stderr, "malloc(%d) = ", size);
p = real_malloc(size);
fprintf(stderr, "%p\n", p);
return p;
}
static void __mtrace_init(void) __attribute__((constructor));
static void __mtrace_init(void)
{
void *handle = NULL;
handle = dlopen("libc.so.6", RTLD_LAZY);
if (NULL == handle) {
fprintf(stderr, "Error in `dlopen`: %s\n", dlerror());
return;
}
real_malloc = dlsym(handle, "malloc");
if (NULL == real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
return;
}
}
Я скомпилирую это с помощью:
gcc -shared -fPIC -o mtrace.so mtrace.c
И затем, когда я пытаюсь выполнить ls
:
$ LD_PRELOAD=./mtrace.so ls
malloc(352) = Segmentation fault
Теперь я подозреваю, что dlopen нуждается в malloc, и, поскольку я переопределяю его в общей библиотеке, он использует эту версию с еще не назначенным real_malloc
.
Вопрос... как мне заставить его работать?
P.S. извините за недостаток в тегах, я не смог найти соответствующие теги, и у меня все еще недостаточно репутации для создания новых.
Ответы
Ответ 1
Я всегда так делаю:
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
static void* (*real_malloc)(size_t)=NULL;
static void mtrace_init(void)
{
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (NULL == real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
}
}
void *malloc(size_t size)
{
if(real_malloc==NULL) {
mtrace_init();
}
void *p = NULL;
fprintf(stderr, "malloc(%d) = ", size);
p = real_malloc(size);
fprintf(stderr, "%p\n", p);
return p;
}
Не используйте конструкторы, просто инициализируйте при первом вызове malloc
. Используйте RTLD_NEXT
, чтобы избежать dlopen
. Вы также можете попробовать malloc hooks. Имейте в виду, что все это расширения GNU и, вероятно, не работают в других местах.
Ответ 2
Если вы действительно хотите использовать LD_PRELOAD с malloc и обнаружили, что код в принятом ответе по-прежнему segfaults, у меня есть решение, которое, похоже, работает.
Segfault был вызван dlsym вызовом calloc для 32 байтов, что вызвало рекурсию к концу стека.
Мое решение состояло в том, чтобы создать суперпростой статический распределитель, который заботится о распределениях, прежде чем dlsym вернет указатель функции malloc.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
char tmpbuff[1024];
unsigned long tmppos = 0;
unsigned long tmpallocs = 0;
void *memset(void*,int,size_t);
void *memmove(void *to, const void *from, size_t size);
/*=========================================================
* interception points
*/
static void * (*myfn_calloc)(size_t nmemb, size_t size);
static void * (*myfn_malloc)(size_t size);
static void (*myfn_free)(void *ptr);
static void * (*myfn_realloc)(void *ptr, size_t size);
static void * (*myfn_memalign)(size_t blocksize, size_t bytes);
static void init()
{
myfn_malloc = dlsym(RTLD_NEXT, "malloc");
myfn_free = dlsym(RTLD_NEXT, "free");
myfn_calloc = dlsym(RTLD_NEXT, "calloc");
myfn_realloc = dlsym(RTLD_NEXT, "realloc");
myfn_memalign = dlsym(RTLD_NEXT, "memalign");
if (!myfn_malloc || !myfn_free || !myfn_calloc || !myfn_realloc || !myfn_memalign)
{
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
exit(1);
}
}
void *malloc(size_t size)
{
static int initializing = 0;
if (myfn_malloc == NULL)
{
if (!initializing)
{
initializing = 1;
init();
initializing = 0;
fprintf(stdout, "jcheck: allocated %lu bytes of temp memory in %lu chunks during initialization\n", tmppos, tmpallocs);
}
else
{
if (tmppos + size < sizeof(tmpbuff))
{
void *retptr = tmpbuff + tmppos;
tmppos += size;
++tmpallocs;
return retptr;
}
else
{
fprintf(stdout, "jcheck: too much memory requested during initialisation - increase tmpbuff size\n");
exit(1);
}
}
}
void *ptr = myfn_malloc(size);
return ptr;
}
void free(void *ptr)
{
// something wrong if we call free before one of the allocators!
// if (myfn_malloc == NULL)
// init();
if (ptr >= (void*) tmpbuff && ptr <= (void*)(tmpbuff + tmppos))
fprintf(stdout, "freeing temp memory\n");
else
myfn_free(ptr);
}
void *realloc(void *ptr, size_t size)
{
if (myfn_malloc == NULL)
{
void *nptr = malloc(size);
if (nptr && ptr)
{
memmove(nptr, ptr, size);
free(ptr);
}
return nptr;
}
void *nptr = myfn_realloc(ptr, size);
return nptr;
}
void *calloc(size_t nmemb, size_t size)
{
if (myfn_malloc == NULL)
{
void *ptr = malloc(nmemb*size);
if (ptr)
memset(ptr, 0, nmemb*size);
return ptr;
}
void *ptr = myfn_calloc(nmemb, size);
return ptr;
}
void *memalign(size_t blocksize, size_t bytes)
{
void *ptr = myfn_memalign(blocksize, bytes);
return ptr;
}
Надеюсь, это поможет кому-то.
Ответ 3
Если вы используете glibc, вы должны использовать встроенный механизм подключения malloc - пример на этой странице содержит пример того, как выглядеть вверх по оригинальному malloc. Это особенно важно, если вы добавляете дополнительную информацию отслеживания в распределения, чтобы гарантировать, что функции библиотеки, которые возвращают буферы malloc'd, соответствуют вашей реализации free()
.
Ответ 4
Вот самый простой пример для malloc и свободного перехвата.
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
static void* (*real_malloc)(size_t size);
static void (*real_free)(void *ptr);
__attribute__((constructor))
static void init()
{
real_malloc = dlsym(RTLD_NEXT, "malloc");
real_free = dlsym(RTLD_NEXT, "free");
fprintf(stderr, "init\n");
}
void *malloc(size_t size)
{
void *ptr = real_malloc(size);
fprintf(stderr, "malloc(%zd) = %p\n", size, ptr);
return ptr;
}
void free(void *ptr)
{
real_free(ptr);
fprintf(stderr, "free(%p)\n", ptr);
}
Ответ 5
Вот расширение к приведенным выше примерам, которое позволяет избежать ошибок в dlsym с помощью mmap
до завершения инициализации:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <sys/mman.h>
static void* (*real_malloc)(size_t) = NULL;
static void* (*real_realloc)(void*, size_t) = NULL;
static void* (*real_calloc)(size_t, size_t) = NULL;
static void (*real_free)(void*) = NULL;
static int alloc_init_pending = 0;
/* Load original allocation routines at first use */
static void alloc_init(void)
{
alloc_init_pending = 1;
real_malloc = dlsym(RTLD_NEXT, "malloc");
real_realloc = dlsym(RTLD_NEXT, "realloc");
real_calloc = dlsym(RTLD_NEXT, "calloc");
real_free = dlsym(RTLD_NEXT, "free");
if (!real_malloc || !real_realloc || !real_calloc || !real_free) {
fputs("alloc.so: Unable to hook allocation!\n", stderr);
fputs(dlerror(), stderr);
exit(1);
} else {
fputs("alloc.so: Successfully hooked\n", stderr);
}
alloc_init_pending = 0;
}
#define ZALLOC_MAX 1024
static void* zalloc_list[ZALLOC_MAX];
static size_t zalloc_cnt = 0;
/* dlsym needs dynamic memory before we can resolve the real memory
* allocator routines. To support this, we offer simple mmap-based
* allocation during alloc_init_pending.
* We support a max. of ZALLOC_MAX allocations.
*
* On the tested Ubuntu 16.04 with glibc-2.23, this happens only once.
*/
void* zalloc_internal(size_t size)
{
fputs("alloc.so: zalloc_internal called", stderr);
if (zalloc_cnt >= ZALLOC_MAX-1) {
fputs("alloc.so: Out of internal memory\n", stderr);
return NULL;
}
/* Anonymous mapping ensures that pages are zero'd */
void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (MAP_FAILED == ptr) {
perror("alloc.so: zalloc_internal mmap failed");
return NULL;
}
zalloc_list[zalloc_cnt++] = ptr; /* keep track for later calls to free */
return ptr;
}
void free(void* ptr)
{
if (alloc_init_pending) {
fputs("alloc.so: free internal\n", stderr);
/* Ignore 'free' during initialization and ignore potential mem leaks
* On the tested system, this did not happen
*/
return;
}
if(!real_malloc) {
alloc_init();
}
for (size_t i = 0; i < zalloc_cnt; i++) {
if (zalloc_list[i] == ptr) {
/* If dlsym cleans up its dynamic memory allocated with zalloc_internal,
* we intercept and ignore it, as well as the resulting mem leaks.
* On the tested system, this did not happen
*/
return;
}
}
real_free(ptr);
}
void *malloc(size_t size)
{
if (alloc_init_pending) {
fputs("alloc.so: malloc internal\n", stderr);
return zalloc_internal(size);
}
if(!real_malloc) {
alloc_init();
}
void* result = real_malloc(size);
//fprintf(stderr, "alloc.so: malloc(0x%zx) = %p\n", size, result);
return result;
}
void *realloc(void* ptr, size_t size)
{
if (alloc_init_pending) {
fputs("alloc.so: realloc internal\n", stderr);
if (ptr) {
fputs("alloc.so: realloc resizing not supported\n", stderr);
exit(1);
}
return zalloc_internal(size);
}
if(!real_malloc) {
alloc_init();
}
return real_realloc(ptr, size);
}
void *calloc(size_t nmemb, size_t size)
{
if (alloc_init_pending) {
fputs("alloc.so: calloc internal\n", stderr);
return zalloc_internal(nmemb * size);
}
if(!real_malloc) {
alloc_init();
}
return real_calloc(nmemb, size);
}