私は、stderrへのmalloc呼び出しをログに記録する単純な共有ライブラリ(ある場合は「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が必要だと思います。共有ライブラリ内でdlopenを再定義しているので、未割り当てのreal_malloc
。
問題は...どうやってそれを機能させるのですか?
追伸タグが少ないため申し訳ありませんが、適切なタグを見つけることができませんでした。それでも、新しいタグを作成するのに十分な評判がありません。
私はいつもこのようにしています:
#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=拡張機能であり、おそらく他の場所では機能しないことに注意してください。
MallocでLD_PRELOADを本当に使用したい場合、受け入れられた回答のコードがまだsegfaultsであることがわかった場合は、動作するように見える解決策があります。
Segfaultは、dlsymが32バイトのcallocを呼び出し、スタックの最後に再帰を引き起こしたことが原因でした。
私の解決策は、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;
}
これが誰かを助けることを願っています。
Glibcを使用している場合は、 mallocフックメカニズムに組み込まれています を使用する必要があります。このページの例には、元のmallocを検索する方法の例が含まれています。これは、割り当てに追跡情報を追加する場合に特に重要であり、mallocされたバッファーを返すライブラリー関数がfree()
の実装と整合していることを確認します。
Mallocとfreeフッキングの最も簡単な例を次に示します。
#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);
}
以下は、初期化が完了するまでmmap
を使用してdlsymのsegfaultsを回避する上記の例の拡張です。
#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);
/* Be aware of integer overflow in nmemb*size.
* Can only be triggered by dlsym */
return zalloc_internal(nmemb * size);
}
if(!real_malloc) {
alloc_init();
}
return real_calloc(nmemb, size);
}