在malloc期间接收信号

时间:2017-10-21 13:09:51

标签: c linux malloc

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

#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的新手,所以感谢任何帮助。

2 个答案:

答案 0 :(得分:2)

backtrace调用malloc的主要原因是它需要使用libgcc_s加载dlopen。您可以先调用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 unwinder仍然不是异步信号安全的,但是glibc认为它(对于线程取消之类的东西),它通常就像是异步信号安全一样。

答案 1 :(得分:1)

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()只是不安全(没有强有力的保证),但在大多数情况下可能效果很好。