acktrace多用在程序运行出现异常时打印异常线程的调用栈信息,如:在Linux中如何利用backtrace信息解决程序崩溃的问题_gongmin856的博客-CSDN博客_backtrace。backtrace相关函数说明如:在Linux中如何利用backtrace信息解决程序崩溃的问题_gongmin856的博客-CSDN博客_backtrace。
backtrace可输出当前线程的调用栈信息,在一个多线程的架构中,如果某一个线程出现死锁或者卡死现象,如何找出问题线程的异常点?
下面先简要说下线程崩溃异常(内存访问越界,除0等错误异常,非死锁或卡死异常)的方法。
1. 首先需要注册signal函数(应用程序调试-signal和backtrace_vector_s的博客-CSDN博客_backtrace返回值一直是0)如:signal(SIGSEGV, SigSegv_handler),这个是当内存访问异常时会执行到SigSegv_handler,在SigSegv_handler函数内可调用如下print_backtrace代码输出问题线程调用栈信息
backtrace输出调用栈示例代码如下:
void print_backtrace(int signum)
{
#define BACKTRACE_SIZE 30
void* buffer[BACKTRACE_SIZE] = {0};
int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
char** string_buffer = backtrace_symbols(buffer, pointer_num);
printf("[%s:%d] signal received num:%u\n", __func__, __LINE__, signum);
printf("print backtrace begin\n");
if(string_buffer != NULL)
{
for(int i = 0; i < pointer_num; i++)
{
printf("%s\n", string_buffer[i]);
}
}
else
{
printf("print backtrace null\n");
}
printf("print backtrace end\n");
free(string_buffer);
return;
}
2. 编译时加入-funwind-tables(-ffunction-sections可不加),否则backtrace返回值会是0,无法输出调用栈信息。也可以在链接时加入-rdynamic,这样在输出的调用栈中可以看到函数信息。-g选项可以加进去。
3. 当出现问题时,根据输出的调用栈信息,在PC上输入:arm-oe-linux-gnueabi-addr2line -e bin address -a -f -p -C
其中:bin为目标程序,该目标程序可为非strip目标文件(没有加strip生成的目标文件信息更多,可查询strip作用。若程序被strip又没有加-rdynamic选项,则查找的地址可能不准确)。address为backtrace输出的十六进制地址,通过输出可以看出异常地址。
这个监测原理是当程序运行出现异常时,由系统发起注册信号给异常线程,从而输出异常线程时的调用栈。
基于以上信息,输出相关运行线程时的调用栈信息步骤如下:
1. 在主线程中安装信号signal(SIGUSR1 SigSegv_handler),注册信号函数SigSegv_handler函数可以与其他异常共用或者另写;(使用用户自定义信号SIGUSR1,也可以使用其它信号)
2. 需要建立一个类似看门狗线程,被监测线程设定超时时间,周期更新看门狗,同时被监测线程需要将线程自身id(通过pthread_self获取)传送给看门狗线程;
3. 如果看门狗线程发现某一个线程未及时喂狗时,则通过pthread_kill(thread_id, SIGUSR1)给问题线程发送信号,在注册信号函数内输出调用栈信息。若长时间未喂狗,还可以给问题线程发送退出信号来退出整个进程;
4. 基于以上设置也可以在需要时输出相关线程栈。
示例代码如下,在函数test_fun1内死循环调用while_a函数。
#include
#include
#include
#include
#include //SIGQUIT /usr/include/bits/signum.h
#include // ESRCH /usr/include/asm-/error-bash.h
#include
#include // syscall(SYS_gettid)
int while_a(int a)
{
return a*a;
}
void test_fun1(void)
{
int a = 1;
while(1)
{
a += while_a(a);
}
}
void* thread_fun1(void* arg)
{
pid_t pid;
pthread_t self;
pid = getpid();
self = pthread_self(); // 与pthread_create创建的相同
printf("[%s:%d] pid:%d tid:%ld self:%lu\n", __func__, __LINE__, pid, syscall(SYS_gettid), self); // syscall(SYS_gettid) 与ps -T 查看的线程ID相同
test_fun1();
return (void*)0;
}
void* thread_fun2(void* arg)
{
pid_t pid;
pthread_t self;
pid = getpid();
self = pthread_self();
printf("[%s:%d] pid:%d tid:%ld self:%lu\n", __func__, __LINE__, pid, syscall(SYS_gettid), self);
while(1)
{
printf("%s.\n", __func__);
sleep(1);
}
return (void*)0;
}
void print_backtrace(int signum)
{
#define BACKTRACE_SIZE 30
void* buffer[BACKTRACE_SIZE] = {0};
int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
char** string_buffer = backtrace_symbols(buffer, pointer_num);
printf("[%s:%d] signal received num:%u\n", __func__, __LINE__, signum);
printf("print backtrace begin\n");
if(string_buffer != NULL)
{
for(int i = 0; i < pointer_num; i++)
{
printf("%s\n", string_buffer[i]);
}
}
else
{
printf("print backtrace null\n");
}
printf("print backtrace end\n");
free(string_buffer);
return;
}
int main(int argc ,char *argv[])
{
pthread_t tid1, tid2;
int err1, err2;
int res_kill;
struct sigaction sa_usr;
sa_usr.sa_flags = 0;
sa_usr.sa_handler = print_backtrace; //信号处理函数
sigaction(SIGUSR1, &sa_usr, NULL);
err1 = pthread_create(&tid1, NULL, thread_fun1, NULL);
err2 = pthread_create(&tid2, NULL, thread_fun2, NULL);
printf("[%s:%d]tid:%lu,err1:%d. tid2:%lu,err2:%d.\n", __func__, __LINE__, tid1, err1, tid2, err2);
if(err1!=0 || err2!=0)
{
return 0;
}
sleep(1);
res_kill = pthread_kill(tid1,0); // sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着
printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
if(res_kill == ESRCH)
{
printf("the specified thread did not exists or already quit\n");
}
else if(res_kill == EINVAL)
{
printf("signal is invalid\n");
}
else
{
printf("the specified thread is alive\n");
}
sleep(1);
res_kill = pthread_kill(tid1, SIGUSR1); // SIGUSR1 用户自定义,进入自定义函数
printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
sleep(3);
res_kill = pthread_kill(tid1, SIGQUIT); // SIGQUIT未定义信号, 进程退出
printf("[%s:%d]pthread_kill ret:%d\n", __func__, __LINE__, res_kill);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("[%s:%d]main thread end\n", __func__, __LINE__);
return 0;
}
使用gcc -pthread -funwind-tables -o backtrace_test backtrace_test.c,运行程序后,输出的调用栈信息看不到函数名,只能看到相关地址。使用addr2line -e backtrace_test 0x4009f0 -a -f -p -C查看地址,对应处的函数显示出来。如下图:
使用ida pro 7.6打开backtrace_test目标文件,Jump to address:4009f0处,按F5后转换的伪代码如下,通过伪代码可以更清楚的看到代码问题处。
加入-rdynamic选项,gcc -pthread -rdynamic -funwind-tables -o backtrace_test backtrace_test.c后,在backtrace内部即可打印出调用栈时的函数信息。
不加-rydnamic,加入-g选项,gcc -pthread -funwind-tables -g -o backtrace_test backtrace_test.c,addr2line后可看到出问题的行号。
使用strip去掉符号表(减小程序大小,strip 命令从 XCOFF 对象文件中有选择地除去行号信息、重定位信息、调试段、typchk 段、注释段、文件头以及所有或部分符号表。),使用addr2line后看不到函数位置,ida也无法定到。当出问题后,需要使用未strip的目标程序进行问题查找。
使用file backtrace_test能看到该文件被strip了。
当程序中有库函数时可参考:c语言 backtrace_学术马的博客-CSDN博客_backtrace 头文件