在Linux 64位系统中,从信号处理程序中进行回溯并使用调用堆栈上的malloc/free函数。

4
以下是我想在运行“Red Hat Enterprise Linux 5.5(Tikanga)Kernel 2.6.18-194.el5xen x86_64”操作系统的机器上使用的源代码示例。
总的想法是我想要某些线程的回溯,因此我会为该线程引发SIGUSR1信号,并且处理程序会调用backtrace()函数。
在我的场景中,FrameTwo函数在循环中调用malloc和free。每当为该特定线程引发信号并且在调用堆栈中有free或malloc时,当信号处理程序调用backtrace()时,程序会崩溃。
(gdb) where (stack from gdb)
0  0x0000003e67207638 in ?? () 
1  0x0000003e672088bb in _Unwind_Backtrace
2  0x00000037ba0e5fa8 in backtrace () 
3  0x000000000040071a in handler ()
4  <signal handler called>
5  0x00000037ba071fac in _int_free () 
6  0x0000000a33605000 in ?? ()
7  0x000000004123b130 in ?? ()
8  0x00000000004007d4 in ThreadFunction ()
9  0x000000001f039020 in ?? ()
10 0x000000004123b940 in ?? ()
11 0x0000000000000001 in ?? ()
12 0x0000000000000000 in ?? ()

我从其他来源得知,在信号处理程序中不应调用backtrace,因此我为此情况编写了自己的函数grok_and_print_thread_stack()。
它使用RBP寄存器来导航堆栈(RBP包含当前帧的基指针,指向前一个帧的基指针),但是这种算法在这种情况下也无法工作:当调用堆栈上有_int_free ()时,RBP寄存器导航算法会断裂,因为_int_free的RBP是一些值,例如0x20,它不是有效帧的基指针。
有人知道如何从寄存器中导航调用堆栈吗?或者我该如何使用backtrace实现我的目的?
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "signal.h"
#include "syscall.h"
#include "string.h"
#include "inttypes.h"

//####################################################################

//gcc BacktraceTestProgram.c -o backtracetest -lpthread
//./backtracetest
//gdb -c core backtracetest

//####################################################################
volatile sig_atomic_t flag = 1;
int thlist[6] = {0};
int cnt = 0;
int *memory = NULL;

//####################################################################

void raiseUserSignal(int tid)
{
    union sigval value;
    value.sival_int = 1;
    sigqueue(tid,SIGUSR1, value);
}

//####################################################################

int grok_and_print_thread_stack()
{
    int ret = 0;
    register uint64_t* rbp asm("rbp");
    /*if buffer was built before, add separator */
    uint64_t *previous_bp;

    /*save pointers*/
    previous_bp = rbp;

    /* stack Traversal */
    while(previous_bp)
    {
        uint64_t *next_bp;

        next_bp = (uint64_t*)*previous_bp;
        printf("Read BP: %lx \n", next_bp);

        if ( NULL == (void*)next_bp )
        {
            printf("Reached the top of the stack\n");
            fflush(stdout);
            break;
        }

        previous_bp = next_bp;
    }
    return ret;
}

//####################################################################

void handler(int signum, siginfo_t *info, void *context)
{

    int nptrs = 0 ;
    void *buffer[100] = {NULL};
    char **strings = NULL;

    nptrs = backtrace(buffer, 100);

    flag = 1;
}

//####################################################################

void FrameTwo(const char A)
{
    do{
        if( memory == NULL)
            memory = (int *)malloc(sizeof(int) *5);

        if(memory != NULL) {
            free(memory);
            memory = NULL;
        }
    }while(1);
}

//####################################################################

void FrameOne(int no)
{
    FrameTwo('A');
}

//####################################################################

void *ThreadFunction( void *ptr )
{
    int tid = syscall(SYS_gettid);
    thlist[cnt++] = tid;

    FrameOne(10);
}

//####################################################################

void RegisterSignalHandler()
{
    /* Register a Signal Handler */
    struct sigaction usrsig_action;
    usrsig_action.sa_flags = SA_SIGINFO;
    usrsig_action.sa_sigaction = &handler;
    sigaction (SIGUSR1, &usrsig_action, NULL);
}

//####################################################################

int main(int no , char *argc[] )
{
    int iret1;
    pthread_t thread1;
    RegisterSignalHandler();

    /* Create independent threads each of which will execute function */
    iret1 = pthread_create( &thread1, NULL, ThreadFunction, NULL);

    while(cnt == 0);

    while(1) {
        if(flag == 1){
            flag = 0;
            raiseUserSignal(thlist[0]);
        }
    }

    pthread_join( thread1, NULL);
    return 0;
}

如果我没记错的话,信号处理程序有它自己的堆栈,所以你看到的不是你程序的主堆栈,而是你信号处理程序的堆栈。 - DarkDust
@DarkDust 在大多数情况下,我能够看到主堆栈..实际上,在崩溃分析中,人们建议实现崩溃信号处理程序,并从该处理程序调用回溯()以查看主堆栈。 - sandeep
2个回答

1
一般来说,x86_64程序在构建时可能使用了-fomit-frame-pointer,因为它是默认开启优化时的选项。
这意味着RBP不能用于展开堆栈,您需要使用DWARF展开信息(如果有调试信息可用)或异常展开表。

在大多数情况下,使用RBP(调用堆栈中的帧指针寄存器)的算法可以正常工作,但在很少的情况下会失败,例如free和malloc...如果我从我的堆栈中删除free和malloc,则此实现将正常工作。 "异常展开表"是在执行期间还是仅在崩溃时可用?我如何使用DWARF展开信息?有任何参考资料吗? - sandeep
你的代码可能已经使用了帧指针进行构建,但系统库例程(如malloc和free)不会这样做。因此,在您的代码中使用RBP回溯将有效,但在跟踪C库时则不会。 - TomH

1
你可能需要查看libunwind项目。

[libunwind]的主要目标是定义一个可移植和高效的C编程接口(API)来确定程序的调用链。因此,该API在许多应用程序中非常有用。一些示例包括:

  • 调试器
    libunwind API使得调试器能够轻松生成运行程序线程的调用链(回溯)。

特别是,看看文档中的本地解缠部分,其中包含解释以及以下代码示例(您需要链接-lunwind),打印当前函数的回溯:

#define UNW_LOCAL_ONLY
#include <libunwind.h>

void show_backtrace (void) {
  unw_cursor_t cursor; unw_context_t uc;
  unw_word_t ip, sp;

  unw_getcontext(&uc);
  unw_init_local(&cursor, &uc);
  while (unw_step(&cursor) > 0) {
    unw_get_reg(&cursor, UNW_REG_IP, &ip);
    unw_get_reg(&cursor, UNW_REG_SP, &sp);
    printf ("ip = %lx, sp = %lx\n", (long) ip, (long) sp);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接