3

我正在尝试执行以下操作 - 为 pthreads 库编写一个包装器,该包装器将在它调用的每个 API 时记录一些信息。我想记录的一条信息是堆栈跟踪。

以下是可以按原样编译和运行的原始代码的最小片段。

初始化(文件libmutex.c):

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <dlfcn.h>

static int (*real_mutex_lock)(pthread_mutex_t *) __attribute__((__may_alias__));
static void *pthread_libhandle;

#ifdef _BIT64
#define PTHREAD_PATH      "/lib64/libpthread.so.0"
#else
#define PTHREAD_PATH      "/lib/libpthread.so.0"
#endif 

static inline void load_real_function(char* function_name, void** real_func) {
  char* msg;
  *(void**) (real_func) = dlsym(pthread_libhandle, function_name);
  msg = dlerror();
  if (msg != NULL)
    printf("init: real_%s load error %s\n", function_name, msg);
}

void __attribute__((constructor)) my_init(void) {
   printf("init: trying to dlopen '%s'\n", PTHREAD_PATH);
   pthread_libhandle = dlopen(PTHREAD_PATH, RTLD_LAZY);
   if (pthread_libhandle == NULL) {
     fprintf(stderr, "%s\n", dlerror());
     exit(EXIT_FAILURE);
  }
  load_real_function("pthread_mutex_lock", (void**) &real_mutex_lock);
}

包装器和对回溯的调用。我已经尽可能多地从方法中删除了,所以是的,我知道我从不调用原始的 pthread_mutex_lock 例如。

void my_backtrace(void) {
    #define SIZE 100
    void *buffer[SIZE];
    int nptrs;

    nptrs = backtrace(buffer, SIZE);
    printf("backtrace() returned %d addresses\n", nptrs);
}

int pthread_mutex_lock(pthread_mutex_t *mutex) {
  printf("In pthread_mutex_lock\n"); fflush(stdout);
  my_backtrace();
  return 0;
}

为了测试这一点,我使用了这个二进制文件(文件tst_mutex.c):

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int main (int argc, char *argv[]) {
  pthread_mutex_t x;

  printf("Before mutex\n"); fflush(stdout);
  pthread_mutex_lock(&x);
  printf("after  mutex\n");fflush(stdout);

  return 0;
}

这是所有这些的编译方式:

rm -f *.o *.so tst_mutex

cc -Wall -D_BIT64 -c -m64 -fPIC libmutex.c
cc -m64 -o libmutex.so -shared -fPIC -ldl -lpthread libmutex.o

cc -Wall -m64 tst_mutex.c  -o tst_mutex

并运行

LD_PRELOAD=$(pwd)/libmutex.so ./tst_mutex

这会在 Linux x86 上因分段错误而崩溃。在 Linux PPC 上,一切都完美无缺。我尝试了几个版本的 GCC 编译器、GLIBC 库和 Linux 发行版 - 都失败了。

输出是

init: trying to dlopen '/lib64/libpthread.so.0'
Before mutex
In pthread_mutex_lock
In pthread_mutex_lock
In pthread_mutex_lock
...
...
./run.sh: line 1: 25023 Segmentation fault      LD_PRELOAD=$(pwd)/libmutex.so ./tst_mutex

暗示这里有递归。我查看了源代码backtrace()- 其中没有调用锁定机制。它所做的只是简单地遍历堆栈帧链表。我也用 objdump 检查了库代码,但这并没有发现任何异常。

这里发生了什么?任何解决方案/解决方法?

哦,也许是最重要的事情。这只发生在 pthread_mutex_lock 函数中!!从任何其他重写的 pthread_* 函数打印堆栈都可以正常工作...

4

1 回答 1

0

这是一个堆栈溢出,由无限递归引起(正如@Chris Dodd 所说)。backtrace() 函数运行不同的系统调用,这些系统调用是从使用 pthread 库编译的程序调用的,而没有使用 pthread 库。即使程序没有显式调用 pthread 函数。

这是一个使用 backtrace() 函数且不使用任何 pthread 函数的简单程序。

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>

int main(void)
{
 void* buffer[100];
 int num_ret_addr;

 num_ret_addr=backtrace(buffer, 100); 
 printf("returned number of addr %d\n", num_ret_addr);

 return 0;
}

让我们在不链接到 pthread 的情况下编译它,并使用 strace 实用程序检查程序系统调用。输出中没有出现与互斥锁相关的系统调用。

$ gcc -o backtrace_no_thread backtrace.c
$ strace -o backtrace_no_thread.out backtrace_no_thread

不,让我们编译将其链接到 pthread 库的相同代码,运行 strace 并查看其输出。

$ gcc -o backtrace_with_thread backtrace.c -lpthread
$ strace -o backtrace_with_thread.out backtrace_with_thread

这次输出包含与互斥锁相关的系统调用(它们的名称可能取决于平台)。这是在 X86 Linux 机器上获得的 strace 输出文件的片段。

futex(0x3240553f80, FUTEX_WAKE_PRIVATE, 2147483647) = 0
futex(0x324480d350, FUTEX_WAKE_PRIVATE, 2147483647) = 0
于 2014-01-23T08:40:47.123 回答