2017-10-21 81 views
0

我希望程序在malloc()期间收到信号后,将堆栈内容写入文件。为此,我尝试使用backtrace()backtrace_symbols_fd()函数,但后来发现它们不是异步信号安全的。我编写了下面的代码来测试,看起来该程序在大多数运行中都挂起了。malloc期间接收信号

#include <execinfo.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/wait.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <signal.h> 
#include <stdlib.h> 
#include <stdio.h> 

typedef int bool; 
#define true 1 
#define false 0 

static void signal_handler_child(int sig) 
{ 
    char error_msg_buffer[4096]; 

    int fd = open("./backtrace_log.txt", O_RDWR | O_TRUNC | O_CREAT, 0777); 

    strcpy(error_msg_buffer, "entered signal_handler_child()"); 
    write(fd, error_msg_buffer, strlen(error_msg_buffer)); 

    void* buffer[1024]; 
    const int size = backtrace(buffer, 1024); 

    if(size <= 0) 
    { 
     strcpy(error_msg_buffer, "unable to dump call stack trace: backtrace() returned bad size"); 
     write(fd, error_msg_buffer, strlen(error_msg_buffer)); 
     return ; 
    } 

    backtrace_symbols_fd(buffer, size, fd); 

    close(fd); 

    _exit(EXIT_SUCCESS); 
} 

int main(int argc, char *argv[]) 
{ 
    pid_t pid = fork(); 
    if(pid == 0) 
    { 
     signal(SIGSEGV, signal_handler_child); 
     while(true) 
     { 
      void *pointer = malloc(1000000); 
      free(pointer); 
     } 
    } 
    else if(pid == -1) 
    { 
     printf("fork() error\n"); 
    } 
    else 
    { 
     sleep(3); 

     if(kill(pid, SIGSEGV) == -1) 
      printf("kill() error\n"); 

     wait(NULL); 
    } 
} 

那么,怎样才能安全地我写堆栈内容到在这样一种情况文件?一般情况下,backtrace()可以使用malloc()吗?

而且该名男子页说

backtrace_symbols_fd()不调用malloc(3),因此可以在使用的情况,其中 后者的功能可能会失败。

但是,什么是功能backtrace_symbols_fd()点,如果backtrace()实际上是malloc()影响?

我是linux api的新手,所以任何帮助表示赞赏。

+0

你的信号处理器有多远?你的输出文件是否被创建?你看到文件中的任何输出吗?我在[backtrace()'源代码](https://code.woboq.org/userspace/glibc/debug/backtrace.c.html)中没有看到任何会导致死锁的东西。你可以尝试添加一些东西来写从'backtrace()'返回的指针到文件中。您可以从命令行运行'pstack PID'来对付死锁进程,以查看它挂起的位置。另外,您可能希望在写入文件的每个字符串的末尾添加一个“\ n”字符。 –

+0

'malloc'不会比其他任何东西更容易受到信号或干扰处理程序。为什么要专门测试它? –

+0

@Peach:默认情况下,许多现代系统[将'ptrace'限制为父进程](https://www.kernel.org/doc/Documentation/security/Yama.txt)。如果可以的话,使用'sudo'。 –

回答

2

backtrace调用malloc的主要原因是它需要使用dlopen加载libgcc_s。通过首先调用backtrace来初始化自己,可以获得一些额外的可靠性。如以下示例所示,后续对backtrace的呼叫不应触发对malloc的呼叫。

#define _GNU_SOURCE 
#include <dlfcn.h> 
#include <execinfo.h> 
#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 

void * 
malloc (size_t size) 
{ 
    const char *message = "malloc called\n"; 
    write (STDOUT_FILENO, message, strlen (message)); 
    void *next = dlsym (RTLD_NEXT, "malloc"); 
    return ((__typeof__ (malloc) *) next) (size); 
} 

int 
main (void) 
{ 
    /* This calls malloc. */ 
    puts ("First call to backtrace."); 
    void *buffer[10]; 
    backtrace (buffer, 10); 
    /* This does not. */ 
    puts ("Second call to backtrace."); 
    backtrace (buffer, 10); 
} 

的libgcc的开卷仍然不是异步信号安全的其他原因,但glibc的假设是(对于像取消线程),它通常工作,好像它是异步信号安全。

+0

我只是在思考回调“第一次”的效果。 当以这种方式使用SIGSEGV处理程序(如OP中所示)时,在“正常”情况下(即程序永远不会获取SIGSEGV),只有在发生SIGSEGV时才会加载libgcc_s。但是通过这种方式,在使其更加可靠的同时,即使SIGSEGV没有发生,libgcc_s的加载也会完成。加载libgcc_s时是否还有额外的开销?或者它只是一次发生的可忽略的开销? – usr

+0

你只需要调用'backtrace'一次,所以开销很小。加载'libgcc_s'只需要一点额外的内存,它仅仅存在于过程映像中不会减慢程序的速度。 –

0

backtrace()backtrace_symbols()是asyc信号不安全的,backtrace_symbols_fd()是异步信号安全的。您可以在GNU documentation中阅读详细信息。

但是,如果backtrace()实际上受malloc影响,那么函数backtrace_symbols_fd()的作用是什么?

回溯机制不何时发生的信号越来越回溯 - 即使是在正常情况下,它可能会被使用。除了手册页不说backtrace()受到malloc()的影响。它说约有backtrace_symbols()函数。从man page引述全段:

backtrace_symbols_fd()接受,但代替返回字符串数组给调用者相同的缓冲液和尺寸参数作为backtrace_symbols(),它写入的字符串,每行一个到文件描述符fd。 backtrace_symbols_fd()不会调用malloc(3),因此可用于后一种功能可能失败的情况。

但是GNU文档标记为backtrace无论如何asyn-csignal-unsafe。所以即使它不受malloc()影响,从信号处理程序调用它也是不安全的。但是当你处于一个SIGSEGV处理程序中时,很有可能你已经处于一个可怕的境地(你正在处理的SIGSEGV可能是由一些未定义的行为引起的)。总之,从信号处理程序使用backtrace()没有安全的方法。拨打backtrace()仅仅是不安全的(没有强有力的保证),但在大多数情况下可能效果很好。