下面是我想在一台运行"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 ?? ()
我从其他来源了解到,不应该从信号处理程序中调用反向跟踪,所以我为这种情况编写了自己的函数grok_and_print_thread_stack()。
它使用RBP寄存器来导航堆栈(RBP包含当前帧的基指针指向前一帧的基指针),但该算法在这种情况下也不起作用:当_int_free()在调用堆栈上时,RBP寄存器导航算法中断,因为_int_free的RBP是一些值,如0x20,这不是一个有效的帧的基指针。
有人知道如何从寄存器中导航调用堆栈吗?或者我如何使用回溯来达到我的目的?
#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 stackn");
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;
}
一般来说,x86_64程序很可能是用-fomit-frame-pointer
构建的,因为它是优化时的默认值。
这意味着RBP
不能用于展开堆栈,您将需要使用DWARF展开信息(如果您有可用的调试信息)或异常展开表。
你可能想看看libunwind项目。
[libwind]的主要目标是定义一个可移植和高效的C编程接口(API)来确定程序的调用链。[…因此,该API在许多应用程序中都很有用。一些例子包括:
- 调试器
libwind API使得调试器在运行的程序中生成线程的调用链(backtrace)变得非常简单/
特别地,看一下他们文档的本地展开部分,它包含解释和下面的代码示例(您需要链接到-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 = %lxn", (long) ip, (long) sp);
}
}