【问题标题】:pthread_create() appears to leak memorypthread_create() 似乎泄漏内存
【发布时间】:2021-10-01 10:52:38
【问题描述】:

我在calloc()free() 上编写了一个简单的wrapper.so 来监视内存调用,并且似乎pthreads_create() 正在泄漏内存。

在使用calloc(17, 16)(大部分时间为calloc(18, 16))进行初始分配后,似乎正在尝试释放内存,但nullptr 被传递给free()

这里发生了什么?

// test.cpp

#include <pthread.h>
#include <cassert>
#include <cstdlib>

void* p_dummy(void*)
{
    return nullptr;
}

int main(void)
{
    void* ptr = calloc(11, 11);
    free(ptr);

    pthread_t thread;
    assert(pthread_create(&thread, nullptr, p_dummy, nullptr) == 0); 
    assert(pthread_join(thread, nullptr) == 0);

    return 0;
}
// wrapper.cpp

#ifndef _GNU_SOURCE
    #define _GNU_SOURCE
#endif

#include <dlfcn.h>
#include <cstdio>

static void  (*real_free)(void* ptr)                   = NULL;
static void* (*real_calloc)(size_t nmemb, size_t size) = NULL;

static bool initializing = false;

static void init()
{
    initializing = true;

    fprintf(stderr,"init()\n");

    real_free = (void (*)(void*))dlsym(RTLD_NEXT, "free");
    real_calloc = (void* (*)(size_t, size_t))dlsym(RTLD_NEXT, "calloc");

    if (!real_free or !real_calloc) {
        fprintf(stderr,"Error in `dlsym()`: %s\n", dlerror());
    }

    initializing = false;
}

extern "C" void free(void *ptr)
{
    fprintf(stderr,"free(%p)\n", ptr);

    if (!real_free) {
        init();
    }

    real_free(ptr);
}

extern "C" void* calloc(size_t nmemb, size_t size)
{
    static char memory[32] { 0 }; // Hack to provide memory to dlsym()

    if (initializing) {
        fprintf(stderr,"calloc(%lu, %lu): %p\n", nmemb, size, &memory);
        return memory;
    }

    if (!real_calloc) {
        init();
    }

    void* ptr = real_calloc(nmemb, size);

    fprintf(stderr,"calloc(%lu, %lu): %p\n", nmemb, size, ptr);

    return ptr;
}
# Makefile

CC = g++
CFLAGS = -std=c++17 -Wall

all: test

test: test.cpp wrapper.so 
    $(CC) $(CFLAGS) -pthread -o test test.cpp -ldl

wrapper.o: wrapper.cpp
    $(CC) $(CFLAGS) -c -fPIC -o wrapper.o wrapper.cpp

wrapper.so: wrapper.o
    $(CC) $(CFLAGS) -shared -o wrapper.so wrapper.o -ldl

clean:
    rm -f *.o *.so test

输出:

$ LD_PRELOAD=./wrapper.so ./test
init()
calloc(1, 32): 0x7f2a02bf1080  -- dlsym() requests memory
calloc(11, 11): 0x7fffd400d260 -- calloc(11, 11) in test.cpp
free(0x7fffd400d260)           -- free() in test.cpp
calloc(17, 16): 0x7fffd400d2f0 -- pthread_create() requests memory
free((nil))                    -- an attempt to free previously allocated memory?

【问题讨论】:

  • 线程有mmap(2)-ed 内存用于它们的call stack。见clone(2),研究GNU libc的源码。它是免费软件。 fprintf 也会调用 malloc
  • fprintf 和朋友们是相对高级的库函数,它们可以自己使用 malloc/free。从劫持的 malloc/free 调用中调用它们可能会产生不稳定的结果。
  • 人们所说的“内存泄漏”至少有两种不同的含义。一个是程序在退出之前不会释放它分配的所有内存。您的程序似乎表现出这种行为,但如果仅此而已,这种泄漏不会产生太大影响。您需要担心的内存泄漏是当程序分配内存然后丢失指向它的指针时,它无法释放该内存。甚至这只是一个重大问题,泄漏单独较大或可能总体较大。
  • 明确一点,我并不是在提倡草率的编程。我只是说问题中展示的行为,如果输出确实准确地反映了程序的行为,还不足以让我有理由担心。
  • nil 触发断点并查看堆栈跟踪。

标签: c++ linux pthreads


【解决方案1】:

有问题的内存区域是 DTV(动态线程向量),在程序终止之前无法释放。

如果您在calloc 上中断,您可以在 GDB 中看到它:

(gdb) bt
#0  __libc_calloc (n=17, elem_size=16) at malloc.c:3366
#1  0x00007ffff7fc52fa in calloc (nmemb=17, size=16) at wrapper.cpp:56
#2  0x00007ffff7fe39cb in allocate_dtv (result=0x7ffff7d86700) at ../elf/dl-tls.c:286
#3  __GI__dl_allocate_tls (mem=mem@entry=0x7ffff7d86700) at ../elf/dl-tls.c:532
#4  0x00007ffff7f8b323 in allocate_stack (stack=<synthetic pointer>, pdp=<synthetic pointer>, attr=0x7fffffffe300) at allocatestack.c:622
#5  __pthread_create_2_1 (newthread=<optimized out>, attr=<optimized out>, start_routine=<optimized out>, arg=<optimized out>) at pthread_create.c:660
#6  0x0000555555555273 in main () at test.cpp:19

这是设计使然(如果您真的很好奇,请查看更多详情 here)。

报告了许多关于allocate_dtv 中“泄漏”的错误,所有这些错误都被拒绝(例如12)。

free(nullptr)的调用与此无关,它是从线程清理函数(__res_thread_freeres)中调用的。

【讨论】:

    【解决方案2】:

    我补充说:

    extern "C" void free(void *ptr)
    {
        if (ptr == NULL) {
            raise(SIGTRAP);
        }
    
         ....
    

    然后启动调试器:

    gdb --args env LD_PRELOAD=./wrapper.so ./test
    

    run 它和bt 显示:

    free((nil))
    
    Thread 2 "test" received signal SIGTRAP, Trace/breakpoint trap.
    [Switching to Thread 0x7ffff7a25640 (LWP 418295)]
    0x00007ffff7c0a702 in raise () from /usr/lib/libpthread.so.0
    (gdb) bt
    #0  0x00007ffff7c0a702 in raise () from /usr/lib/libpthread.so.0
    #1  0x00007ffff7fc1252 in free (ptr=0x0) at wrapper.cpp:39
    #2  0x00007ffff7ab8027 in __libc_thread_freeres () from /usr/lib/libc.so.6
    #3  0x00007ffff7c0027f in start_thread () from /usr/lib/libpthread.so.0
    #4  0x00007ffff7b275e3 in clone () from /usr/lib/libc.so.6
    (gdb) 
    

    一个简单的谷歌搜索导致__libc_thread_freeres 调用__strerror_thread_freeres 调用free(last_value)last_value 用作strerror_l 分配的内存的线程本地值。它是NULL,从未调用过strerror

    【讨论】:

      猜你喜欢
      • 2018-05-19
      • 2011-11-08
      • 2014-09-27
      • 2021-09-14
      • 1970-01-01
      • 2011-09-18
      • 2023-03-05
      • 2013-10-30
      • 1970-01-01
      相关资源
      最近更新 更多