backtrace输出任意线程栈信息

4 篇文章 0 订阅

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>//SIGQUIT /usr/include/bits/signum.h
#include <errno.h>// ESRCH  /usr/include/asm-/error-bash.h
#include <execinfo.h>
#include <sys/syscall.h> // 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 头文件

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BackTrack Linux(现在已经更名为Kali Linux)是一款基于Debian的Linux发行版,主要用于网络安全和渗透测试。它集成了大量的渗透测试工具和安全审计工具,方便安全专业人员进行各种网络安全测试和攻击模拟。 安装BackTrack Linux(Kali Linux)可以按照以下步骤进行操作: 1. 下载BackTrack Linux(Kali Linux)的ISO镜像文件。您可以前往官方网站或者其他可靠的下载来源获取ISO镜像文件。 2. 创建一个可启动的USB闪存驱动器或者光盘。您可以使用软件如Rufus、UNetbootin或者Etcher等工具,将ISO镜像文件写入USB闪存驱动器或者刻录到光盘上。 3. 将USB闪存驱动器插入您的计算机或者将光盘放入光驱,并重启计算机。 4. 进入计算机的BIOS设置界面。您需要将计算机的启动顺序设置为从USB闪存驱动器或者光盘启动。 5. 保存设置并重新启动计算机。计算机将从BackTrack Linux(Kali Linux)的安装介质启动。 6. 在启动菜单中选择安装BackTrack Linux(Kali Linux)。您可以选择图形化安装或者文本安装,根据自己的喜好和需求进行选择。 7. 按照安装向导的提示,进行分区设置、用户创建、安装源选择等配置。 8. 等待安装程序完成,然后重新启动计算机。 9. 完成安装后,您可以登录到BackTrack Linux(Kali Linux)系统,根据需要进行进一步的配置和使用。 请注意,BackTrack Linux(Kali Linux)是针对网络安全专业人员和渗透测试人员设计的操作系统,使用时需遵循法律和道德规范,仅在合法授权和合规的情况下使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值