2017-10-21 86 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()僅僅是不安全的(沒有強有力的保證),但在大多數情況下可能效果很好。