当前位置:首页 » 操作系统 » linuxbacktrace

linuxbacktrace

发布时间: 2023-03-22 15:05:20

1. c语言 linux 获取调用者函数名称

可以做到的,参考一下backtrace_symbols的实现以及液隐相关原理,这里面比较复杂,也比神埋粗较底层,一两句话说不清楚
你要是想通过_FUNCTION_这种游镇方式来搞的话,可以通过如下方式:
#define m_fun(args) fun(__FILE__,__LINE__, args)
进行相应的替换即可

2. linux记录死机前的函数调用

线上环境进程崩溃,运维为了不背锅,要求崩溃之后立马将进程拉起。然而发现有个问题:一旦运维将进程拉起之后,之后使用崩溃的 core 文件来进行分析时,符号信息都丢失,看到的都是问号。

但是,如果崩溃之后未被拉起,可以正常的看到符号。

后来发现,贺闷是运维启动进程的 shell 脚本,每次启动之前,会将需要加载的部分业务相关的 so 文件,文件名字修改(名称里加上了时间戳,类似 lib20200423002608_xxxx.so 这种)。名称被修改之后,gdb 自然没法加载加载这个 so 文件。

info shared
在 gdb 里使用 info shared,可以看到这个 so 文件无对应的地址,因为没有对应的 so 文件被加载。线上环境的 gdb版本是 7.2,启动时没有与 so 文件不存在相关的提示。

当然这是后话。

那么在奔溃时,如何将奔溃时的调用栈记录到日志里呢。

可以借助 backtrace 相关的 3 个函数来实现。

#include <execinfo.h>

int backtrace(void *symaddr[], int size);

char **backtrace_symbols(void *const symaddr[], int size);

void backtrace_symbols_fd(void *const *buffer, int size, int fd);
参数和返回值说明:

backtrace 传入一个数组 symaddr,用来保存符号的地址;size 为数组的大小。size 应该足够大,不然会有部分符号丢失。返回值为实际保存的地址数量。

backtrace_symbols 用来根据符号的地址,得到对应的符号。size 为 backtrace 的返回值,表示腔耐实际需要处理的符号数量。

返回的是一个 malloc 得到的字符串数组的起始地址(C 语言中不太严谨的讲,char* 就是字符串),所以最后需要调用者释放内存。

#include <stdlib.h>
#include <string>
#include <execinfo.h>
#include <unistd.h>

void getCallStackInfo(std::string &stackInfo)
{
static const int size = 100; //符号数量,100足够
int nptrs;

void *buffer[size];
char **syms;

nptrs = backtrace(buffer, size); //返回当前调伍拍春用栈实际的符号数量

syms = backtrace_symbols(buffer, nptrs);

if (syms == nullptr)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for(int i = 0; i < nptrs; ++i)
{
stackInfo.append(syms[i]);
stackInfo.append("\n");
}

free(syms);
}

void say(int &n)
{
static int call_count = 0;
++n;
++call_count;
printf("call count %d\n", call_count);

if(call_count == 6)
{
std::string stack_info;
getCallStackInfo(stack_info);

printf("%s\n", stack_info.c_str());
return;
}

say(n);
}

int main()
{
int n = 3;
say(n);
return 0;
}

编译运行,clang++ main.cpp -rdynamic -o main.out && ./main.out

call count 1
call count 2
call count 3
call count 4
call count 5
call count 6
./main.out(_Z16getCallStackInfoRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x23) [0x400d53]
./main.out(_Z3sayRi+0x6a) [0x400e8a]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(_Z3sayRi+0xc1) [0x400ee1]
./main.out(main+0x1f) [0x400f0f]
/lib64/libc.so.6(__libc_start_main+0xf3) [0x7fe51b555873]
./main.out(_start+0x2e) [0x400c6e]

看到调用栈已经被记录下来,当然符号都是 name mangling 之后的,使用 c++filt _Z3sayRi 可以看到原始名字。

回到记录奔溃时的调用栈到日志里的主题上。通常的奔溃都是由于内存问题,那么可以捕获 SIGSEGV 信号,在信号处理函数中将当前的调用栈记录到日志中就行。写文件可能需要一个 sleep 延时等待日志线程处理完毕。

void sig_log_stack_handler(int sig)
{
std::string stackInfo;
getCallStackInfo(stackInfo);
abort();
}
当然严格的来说,在信号处理器函数里处理 IO 是不符合标准的,会 UB.

注意编译时一定要带上 -rdynamic 选项才有用。如果使用的是 qt creator,这个 -rdynamic 参数时需要传给链接器的,需要在 .pro 文件里加上,加到 QMAKE_CXXFLAGS 是没得用的。

QMAKE_LFLAGS += -rdynamic

3. Linux mutex为什么不能用在中断函数

Linux mutex不能用在中断函数原因:Backtrace来看,应该是i2c_transfer中调用mutex_lock导致schele调用。

pthread_mutex_lock(&qlock);表示尝试去把qlock上锁,它会先判断qlock是否已经上锁,如果已经上锁这个线程就会停在这一步直到其他线程把锁解开。它才继续运行。所以代码中要么是线程1先执行完后执行线程2,要么就是线程2先执行,再执行线程1.而线程3一开始就执行了。

中断函数防止方法:

要防止中断冲突,其实就是要知道什么设备容易产生中断冲突,只要知道了这点,在使用这些设备时稍微注意一下就可以了。下面我列出一些容易冲突的设备,希望对读者有用。

1、声卡:一些早期的ISA型声卡,系统很有可能不认,就需要用户手动设置(一般为5)。

2、内置调制解调器和鼠标:一般鼠标用COM1,内置调制解调器使用COM2的中断(一般为3),这时要注意此时COM2上不应有其它设备。

4. 安装linux提示“loader exited unexpectedly!backtrace……”

安装盘坏了(ISO文件有问题了),重新下载一个再试

5. linux c开发: 在程序退出时进行处理

有时候,希望程序退出时能进行一些处理,比如保存状态丛和,释放一些资源。c语言开发的linux程序,有可能正常退出(exit),有可能异常crash,而异常crash可能是响应了某信号的默认处理。这里总结一下这些情况,如何获取拆敏一个统一的退出处理的点,说白了就是写一个回调函数,让他在程序正常或异常退出时调用。

这个例子里面其实是将异常退出处理和正常退出处理结合起来了旅郑枝。对于SIGTERM(即kill进程)和SIGINT(即ctrl-c结束前台进程),我们当做是正常退出,在其信号处理函数里面,直接调用了exit(0),而exit(0)又会被server_on_exit捕获到。对于异常退出也是类似,只是调用了exit(-1)表示是异常的。同时异常退出我们会打印出当前的进程堆栈信息,server_backtrace的实现下一篇再说。另外注意的是SIGKILL信号是无法捕获的。而调用abort导致的退出,也是通过SIGABRT信号捕获到进行处理了。其他几种异常退出的信号也是比较常见,一并捕获到进行处理。这样对于异常退出,我们即可统一的log堆栈信息,又可直接继续正常退出时的处理流程了。

6. Linux下 C崩溃了!谁能帮忙看看打印出来的 Backtrace 信息

王者荣耀去衣图

7. 如何linux内核报告问题

Linux Kernel BUG:soft lockup CPU#1 stuck分析
1.线上内核bug日志
kernel: Deltaway too big! 18428729675200069867 ts=18446743954022816244 write stamp =18014278822746377
kernel:------------[ cut here ]------------
kernel:WARNING: at kernel/trace/ring_buffer.c:1988 rb_reserve_next_event+0x2ce/0x370()(Not tainted)
kernel:Hardware name: ProLiant DL360 G7
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel: Pid:5483, comm: master Not tainted 2.6.32-220.el6.x86_64 #1
kernel: CallTrace:
kernel:[<ffffffff81069b77>] ? warn_slowpath_common+0x87/0xc0
kernel:[<ffffffff81069bca>] ? warn_slowpath_null+0x1a/0x20
kernel:[<ffffffff810ea8ae>] ? rb_reserve_next_event+0x2ce/0x370
kernel:[<ffffffff810eab02>] ? ring_buffer_lock_reserve+0xa2/0x160
kernel:[<ffffffff810ec97c>] ? trace_buffer_lock_reserve+0x2c/0x70
kernel:[<ffffffff810ecb16>] ? trace_current_buffer_lock_reserve+0x16/0x20
kernel:[<ffffffff8107ae1e>] ? ftrace_raw_event_hrtimer_cancel+0x4e/0xb0
kernel:[<ffffffff81095e7a>] ? hrtimer_try_to_cancel+0xba/0xd0
kernel:[<ffffffff8106f634>] ? do_setitimer+0xd4/0x220
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
kernel: ---[end trace 4d0a1ef2e62cb1a2 ]---
abrt-mp-oops: Reported 1 kernel oopses to Abrt
kernel: BUG: softlockup - CPU#11 stuck for 4278190091s! [qmgr:5492]
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel: CPU 11
kernel:Moles linked in: fuse ipv6 power_meter bnx2 sg microcode serio_raw iTCO_wdtiTCO_vendor_support hpilo hpwdt i7core_edac edac_core shpchp ext4 mbcache jbd2sd_mod crc_t10dif hpsa radeon ttm drm_kms_helper drm i2c_algo_bit i2c_coredm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan]
kernel:
kernel: Pid:5492, comm: qmgr Tainted: G W ---------------- 2.6.32-220.el6.x86_64 #1 HPProLiant DL360 G7
kernel: RIP:0010:[<ffffffff8106f730>] [<ffffffff8106f730>]do_setitimer+0x1d0/0x220
kernel: RSP:0018:ffff88080a661ef8 EFLAGS: 00000286
kernel: RAX:ffff88080b175a08 RBX: ffff88080a661f18 RCX: 0000000000000000
kernel: RDX:0000000000000000 RSI: 0000000000000082 RDI: ffff88080c8c4c40
kernel: RBP:ffffffff8100bc0e R08: 0000000000000000 R09: 0099d7270e01c3f1
kernel: R10:0000000000000000 R11: 0000000000000246 R12: ffffffff810ef9a3
kernel: R13:ffff88080a661e88 R14: 0000000000000000 R15: ffff88080a65a544
kernel: FS:00007f10b245f7c0(0000) GS:ffff88083c4a0000(0000) knlGS:0000000000000000
kernel: CS:0010 DS: 0000 ES: 0000 CR0: 000000008005003b
kernel: CR2:00007ff955977380 CR3: 000000100a80b000 CR4: 00000000000006e0
kernel: DR0:0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
kernel: DR3:0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
kernel:Process qmgr (pid: 5492, threadinfo ffff88080a660000, task ffff880809577500)
kernel: Stack:
kernel:00007f10b323def0 00007f10b248ead0 00007f10b26d0f78 00007f10b248ede0
kernel:<0> ffff88080a661f68 ffffffff8106f88a 0000000000000000 0000000000000000
kernel:<0> 000000000000014c 00000000000f423d 0000000000000000 0000000000000000
kernel: CallTrace:
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
kernel: Code:89 ef e8 74 66 02 00 83 3d 15 69 b5 00 00 75 37 49 8b 84 24 70 07 00 00 48 0508 08 00 00 66 ff 00 66 66 90 fb 66 0f 1f 44 00 00 <31> c0 e9 64 fe ff ff49 8b 84 24 68 07 00 00 48 c7 80 d0 00 00
kernel: CallTrace:
kernel:[<ffffffff8106f769>] ? do_setitimer+0x209/0x220
kernel:[<ffffffff8106f88a>] ? alarm_setitimer+0x3a/0x60
kernel:[<ffffffff8107c27e>] ? sys_alarm+0xe/0x20
kernel:[<ffffffff8100b308>] ? tracesys+0xd9/0xde
abrt-mp-oops: Reported 1 kernel oopses to Abrt

2.内核软死锁(soft lockup)bug原因分析
Soft lockup名称解释:所谓,soft lockup就是说,这个bug没有让系统彻底死机,但是若干个进程(或者kernel thread)被锁死在了某个状态(一般在内核区域),很多情况下这个是由于内核锁的使用的问题。
Linux内核对于每一个cpu都有一个监控进程,在技术界这个叫做watchdog(看门狗)。通过ps –ef | grep watchdog能够看见,进程名称大概是watchdog/X(数字:cpu逻辑编号1/2/3/4之类的)。这个进程或者线程每一秒钟运行一次,否则会睡眠和待机。这个进程运行会收集每一个cpu运行时使用数据的时间并且存放到属于每个cpu自己的内核数据结构。在内核中有很多特定的中断函数。这些中断函数会调用soft lockup计数,他会使用当前的时间戳与特定(对应的)cpu的内核数据结构中保存的时间对比,如果发现当前的时间戳比对应cpu保存的时间大于设定的阀值,他就假设监测进程或看门狗线程在一个相当可观的时间还没有执。Cpu软锁为什么会产生,是怎么产生的?如果linux内核是经过精心设计安排的CPU调度访问,那么怎么会产生cpu软死锁?那么只能说由于用户开发的或者第三方软件引入,看我们服务器内核panic的原因就是qmgr进程引起。因为每一个无限的循环都会一直有一个cpu的执行流程(qmgr进程示一个后台邮件的消息队列服务进程),并且拥有一定的优先级。Cpu调度器调度一个驱动程序来运行,如果这个驱动程序有问题并且没有被检测到,那么这个驱动程序将会暂用cpu的很长时间。根据前面的描述,看门狗进程会抓住(catch)这一点并且抛出一个软死锁(soft lockup)错误。软死锁会挂起cpu使你的系统不可用。
如果是用户空间的进程或线程引起的问题backtrace是不会有内容的,如果内核线程那么在soft lockup消息中会显示出backtrace信息。
3.根据linux内核源码分析错误
根据我们第一部分内核抛出的错误信息和call trace(linux内核的跟踪子系统)来分析产生的具体原因。
首先根据我们的centos版本安装相应的linux内核源码,具体步骤如下:
(1)下载源码的rpm包kernel-2.6.32-220.17.1.el6.src.rpm
(2)安装相应的依赖库,命令:yuminstall rpm-build redhat-rpm-config asciidoc newt-devel
(3)安装源码包:rpm -ikernel-2.6.32-220.17.1.el6.src.rpm
(4)进入建立源码的目录:cd~/rpmbuild/SPECS
(5)建立生成源码目录:rpmbuild-bp --target=`uname -m` kernel.spec

下面开始真正的根据内核bug日志分析源码:
(1)第一阶段内核错误日志分析(时间在Dec 4 14:03:34这个阶段的日志输出代码分析,其实这部分代码不会导致cpu软死锁,主要是第二阶段错误日志显示导致cpu软死锁)
我们首先通过日志定位到相关源代码:看下面日志:Dec 4 14:03:34 BP-YZH-1-xxxx kernel: WARNING: atkernel/trace/ring_buffer.c:1988 rb_reserve_next_event+0x2ce/0x370() (Not tainted)
根据日志内容我们可以很容易的定位到kernel/trace/ring_buffer.c这个文件的1988行代码如下:WARN_ON(1)。
先简单解释一下WARN_ON的作用:WARN_ON只是打印出当前栈信息,不会panic。所以会看到后面有一大堆的栈信息。这个宏定义如下:
#ifndef WARN_ON
#defineWARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN(); \
unlikely(__ret_warn_on); \
})
#endif
这个宏很简单保证传递进来的条件值为0或者1(两次逻辑非操作的结果),然后使用分支预测技术(保证执行概率大的分支紧邻上面的指令)判断是否需要调用__WARN()宏定义。如果满足条件执行了__WARN()宏定义也接着执行一条空指令;。上面调用WARN_ON宏是传递的1,所以会执行__WARN()。下面继续看一下__WARN()宏定义如下:
#define __WARN() warn_slowpath_null(__FILE__,__LINE__)
从接下来的call trace信息中我们也确实发现调用了warn_slowpath_null这个函数。通过在linux内核源代码中搜索这个函数的实现,发现在panic.c(内核恐慌时的相关功能实现)中实现如下:
voidwarn_slowpath_null(const char *file, int line)
{
warn_slowpath_common(file, line,__builtin_return_address(0),
TAINT_WARN, NULL);
}
EXPORT_SYMBOL(warn_slowpath_null);//都出这个符号,让其他模块可以使用这个函数
同样的我们看到了warn_slowpath_common这个函数,而在call trace当中这个函数在warn_slowpath_null函数之前打印出来,再次印证了这个流程是正确的。同样在panic.c这个文件中我发现了warn_slowpath_common这个函数的实现如下:
static voidwarn_slowpath_common(const char *file, int line, void *caller,
unsigned taint, struct slowpath_args *args)
{
const char *board;

printk(KERN_WARNING "------------[ cut here]------------\n");
printk(KERN_WARNING "WARNING: at %s:%d %pS()(%s)\n",
file, line, caller, print_tainted());
board = dmi_get_system_info(DMI_PRODUCT_NAME);//得到dmi系统信息
if (board)
printk(KERN_WARNING "Hardware name:%s\n", board);//通过我们的日志信息可以发现我们硬件名称是ProLiant DL360 G7

if (args)
vprintk(args->fmt, args->args);

print_moles();//打印系统模块信息
mp_stack();//mp信息输出(call trace开始)
print_oops_end_marker();//打印oops结束
add_taint(taint);
}
分析这个函数的实现不难发现我们的很多日志信息从这里开始输出,包括打印一些系统信息,就不继续深入分析了(请看代码注释,里面调用相关函数打印对应信息,通过我分析这些函数的实现和我们的日志信息完全能够对应,其中mp_stack是与cpu体系结构相关的,我们的服务器应该是属于x86体系)。这里在继续分析一下mp_stack函数的实现,因为这个是与cpu体系结构相关的,而且这个函数直接反应出导致内核panic的相关进程。这个函数实现如下:
/*
* The architecture-independent mp_stackgenerator
*/
void mp_stack(void)
{
unsigned long stack;

printk("Pid: %d, comm: %.20s %s %s %.*s\n",
current->pid, current->comm,print_tainted(),
init_utsname()->release,
(int

8. memorymappingsegment发生段错误,如何回溯

Linux应用中用backtrace和memory map信息定位段错误代码的方法 原创
2020-03-27 14:33:21

川渝小神丢
码龄11年
关注
前言:

在Linux应用软件中,段错误(segmentation fault)是开发过程中比较棘手的问题,在博文(Linux环境下段错误的产生原因及调试方法小结)中简要介绍了段错误的产生原因和简单的调试方法。

本文主要描述面对大型程序,比如多线程,调用多个动态库的情况下发生段错误分析方法。这个时候如果使用gdb和gcc,由于程序过于复杂,gdb将很难处理,并且对于那些偶尔出现段错误的情况gdb基本上无法定位。

另外,还可以使用dmesg,nm,ldd,objmp等工具结合分析汇编代码调试段错误,但是对于大型程序一般都有编译优化,分析汇编地址将不是那么容易,另外需要一定汇编基础,对于庞大程序分析汇编代码也是噩梦。

最后,使用printf。printf是个最方便方式,如果对于必现的段错误,使用printf再结合二分法,一般都能定位到。但是就算段错误必现,对于那些多线程大型程序,使用printf就需要对代码有较深入的了解,凭经验找到可能触发异常的地方,并且每次加了调试语句后需要重新编译烧写,效率非常低下。对于那些很难复现的段错误,printf更难入手。

综上所述,单单使用上述某一种方法都不能很好地分析满足如下条件的段错误:

1) 大型项目,代码量几十万行以上。

2) 多线程,多动态库。

3) 出现段错误不一定必现(偶尔出现)。

本文将使用backtrace和memory map信息定位出发时异常的大致位置(往往是某个函数名,如果此函数层数非常多,那么还需要printf定位最终位置),然后通过printf二分法精确到具体位置,针对偶尔出现的段错误,使用applog日志记录的方式。下面将详细描述具体方法。

一、段错误信号

在某些大型程序中,程序初始化时首先会安装SIGSEGV信号及指定处理函数,在处理函数中完成特殊段仿册指定的处理后,最后在处理函数中调用longjmp函数回到主进程报错并退出主进程。backtrace和memory map信息就是在上述信号处理函数中写入系统日志中的,前面的主进程退出后,系统日志仍然保留在flash上供后续分析。在系统日志中,backtrace提供了触发异常的堆栈调用点地址序列,memory map信息提供各目标文件在内存中的地址分配。先通过backtarce中记录的目标文件和地址数据,结合memory map中目标文件代码段起始地址,计算偏移量。

使用此偏移量和目标文件用addr2line命令可查得对应此地址的代码信息。下面先描述linux下段错误信号描述(注意Unix系统和linux系统中的signal函数有差异)。

1. 如果没有安装SIGSEGV信握宏号和并指定处理函数,系统将对这个信号进行默认处理。通常是杀掉当前进程,并在终端输出Segmentation fault信息。

2. 如果已经安装SIGSEGV信号和并指定处理函数,产生这个信号后,系统先运行信号处理函数,信号处理函数运行完后当前进程继续运行。通过实验,在发生了这个信号后,大核并且当前进程没有退出,那么系统会多次进入信号处理函数。

3. 当安装了SIGSEGV信号和并指定处理函数后,发生段错误的线程将调用处理函数,因此在处理函数中可以获取哪个线程发生了段错误(一般通过线程ID)。即发生段错误线程的线程ID和段错误处理函数中获取的线程ID是一样的。 (也就是说,当线程A发生段错误,CPU的PC指针指向Linux内核,运行内核程序,Linux内核捕捉到段错误信号,这个时候要运行用户空间中的信号处理函数,因此记录下内核空间相关地址后切换到用户空间,把PC指针指向段错误处理函数并运行。PC指针在进入内核空间之前运行的线程A,因此当时PC处于线程A的地址空间内,因此当段错误发生后,PC指针指向信号处理函数也应该处于线程A的地址空间内,因此在信号处理函数内获取的线程ID就是出现问题的线程的线程ID,在信号处理函数中backtrace信息就是出现问题线程问题点相关地址信息)通过上述分析,已经知道段错误发生线程,为了进一步缩小范围,确定发生段错误线程中到底哪个函数有问题,就是下面即将描述backtrace和memory map信息。

4. 同中断类似,内核也为每个进程准备了一个信号向量表,信号向量表中记录着每个信号所对应的处理机制,默认情况下是调用默认处理机制。当进程为某个信号注册了信号处理程序后,发生该信号时,内核就会调用注册的函数。

5. 详细参考http://www.spongeliu.com/165.html

二、backtrace()

1. int backtrace(void **buffer,int size)

该函数用于获取当前线程的调用堆栈(即获取程序中当前函数的栈回溯信息,即一系列的函数调用关系),获取的信息将会被存放在buffer中,它是 一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小。在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。

注意:某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会导致无法正确解析堆栈内容.

2. char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。

函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和函数的实际返回地址。比如backtrace信息中,./main-test(SEGV_Test+0x10)[0x8bc8],main-test为程序名称,SEGV_Test函数名,0x10为函数的偏移地址,0x8bc8为此函数实际返回地址。经过翻译后的函数回溯信息放到backtrace_symbols()的返回值中,如果失败则返回NULL。

现在,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的符号给链接器,以能支持函数名功能(比如,在使用GNU ld链接器的系统中,需要传递(-rdynamic), -rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果链接器支持-rdynamic,建议将其加上)。

该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针。注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL。

3. void backtrace_symbols_fd (void *const *buffer, int size, int fd)

backtrace_symbols_fd()的buffer和size参数和backtrace_symbols()函数相同,只是它翻译后的函数回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd对应的文件中。

4. 在段错误信号处理函数中调用backtrace()和backtrace_symbols()函数用于确定出现问题线程的调用堆栈。

由于发生段错误后,内核才会把pc寄存器值设为信号处理函数地址,继而执行应用空间的信号处理函数,因此在信号处理函数中调用backtrace栈回溯函数即可知道段错误发生的地方附近相关函数调用关系,间接找到段错误发生地点。虽然在信号处理函数中调用backtrace等函数并不能保证是一个安全的信号处理函数,但是用于调试完全可以忽略这一点。

5. 使用上述几个函数需要注意:

1)backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数- fomit-frame-pointer后多将不能正确得到程序栈信息。

2)backtrace_symbols的实现需要符号名称的支持,在gcc编译过程中需要加入-rdynamic参数。

3)内联函数没有栈帧,它在编译过程中被展开在调用的位置。

4)未调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。

三、memory map信息

1. /proc/PID/maps

Proc/pid/maps显示进程映射了的内存区域和访问权限。

[root@ES_Controller:/var]#cat /proc/1/maps
00008000-0006a000 r-xp 00000000 1f:03 27 /bin/busybox
00071000-00072000 rw-p 00061000 1f:03 27 /bin/busybox
00072000-00095000 rwxp 00072000 00:00 0 [heap]
40000000-40002000 rw-p 40000000 00:00 0
412d8000-412f4000 r-xp 00000000 1f:03 396 /lib/ld-2.5.so
412fb000-412fd000 rw-p 0001b000 1f:03 396 /lib/ld-2.5.so
41300000-41416000 r-xp 00000000 1f:03 405 /lib/libc-2.5.so
41416000-4141d000 ---p 00116000 1f:03 405 /lib/libc-2.5.so
4141d000-41420000 rw-p 00115000 1f:03 405 /lib/libc-2.5.so
41420000-41423000 rw-p 41420000 00:00 0
beb71000-beb86000 rw-p befeb000 00:00 0 [stack]
内核每进程的vm_area_struct项/proc/pid/maps中的项

在其他进程(如控制台)执行/proc/pid/maps,会显示进程号为pid对应的memory map信息;如果在本进程中想获取本身的memory map信息,需要执行/proc/self/maps。

2. /proc/self/maps

我对/proc/self/maps的理解如下:

如果一个进程正在运行,那么可以在终端执行/proc/进程PID/maps查看到memory map,但在当前进程代码运行时需要memory map信息时,可以在代码中执行/proc/self/maps查看memory map信息,也就是说在哪个程序中执行/proc/self/maps,那么memory map信息就是对应进程的内存布局信息。

3. memory map

(1) 首先是映像文件(静态编译)

00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
映像文件名为main-test,第一行只读可执行,说明为代码段,00008000为代码段的起始地址,如果用backtrace信息中出现问题点栈回溯中的某个函数的实际返回地址减去这个程序代码段起始地址,那么就刚好得到了出现问题点相对于整个程序代码中的位置,再借用addr2line工具就可 以得到出现问题点所在源代码对应行。

上述信息中第二行为可读写,那么一般为程序的数据段。数据段对应信息的下一行如下,即堆(heap),当且仅当malloc调用时存在,是由kernel把匿名内存map到虚存空间,堆则在程序中没有调用malloc的情况下不存在;

00012000-00033000 rwxp 00012000 00:00 0 [heap]
另外就是静态编译下的动态链接库,最后是堆栈[stack]。

(2) 然后是共享库(.SO,动态编译)

40b58000-40bb7000 r-xp 00000000 1f:04 22773 /application/example.so
40bb7000-40bbe000 ---p 0005f000 1f:04 22773 /application/example.so
40bbe000-40bbf000 rw-p 0005e000 1f:04 22773 /application/example.so
同样,第一行对应共享库的代码段;第三行(rw-p)为共享库的数据段,第二行即不读;也不可写,且不可执行,不知道是什么。共享库在一个大型程序中,一般都有个入口函数,如果创建一个线程时,把这个入口函数作为线程函数,那么这个共享库实际上被加载到内存后,在一个线程中运行。

四、addr2line工具定位出错点

1. addr2line定义

Addr2line (它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。

命令参数如下:

Usage: addr2line [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
2. 用addr2line定位

(1) 静态链接情况

执行编译命令:arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test

即把所有源程序和库全部连接成一个可执行文件main-test。

backtrace信息如下:

./main-test(pthread_create+0xac4)[0x95ec]
/lib/libc.so.6(__default_rt_sa_restorer_v2+0x0)[0x4132b240]
./main-test(test1+0x44)[0x8cdc]
./main-test(SEGV_Test+0x10)[0x8d5c]
./main-test(thread3+0x40)[0x92ec]
/lib/libpthread.so.0[0x41444858]
memory map信息如下:

00008000-0000a000 r-xp 00000000 00:0d 16281360 /mnt/nfs/main-test/main-test
00011000-00012000 rw-p 00001000 00:0d 16281360 /mnt/nfs/main-test/main-test
00012000-00033000 rwxp 00012000 00:00 0 [heap]
40000000-40001000 rw-p 40000000 00:00 0
40001000-400df000 r-xp 00000000 1f:03 446 /lib/preloadable_libiconv.so
400df000-400e7000 ---p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e7000-400e8000 rw-p 000de000 1f:03 446 /lib/preloadable_libiconv.so
400e8000-400ea000 rw-p 400e8000 00:00 0
从Memory map信息第一行可以知道静态编译的程序main-test代码段地址空间为0x8000-0xa000,而backtrace信息中0x8cdc等地址也在这个地址空间范围内(这种实际地址不用backtrace地址减去memory map地址计算偏移,然后再用addr2line命令定位)。因此执行如下命令:

addr2line 0x8cdc -e main-test -f
在使用时,用 -e 选项来指定可执行映像是main-test。通过使用 -f 选项,可以告诉工具输出函数名。上述命令将会输出在程序main-test中,指令地址为0x8cdc对应源代码文件、源代码文件中的函数名、地址对应行号。执行输出信息如下:

[root@test main-test]$ addr2line 0x8cdc -e main-test -f
test1
/home/mytest/main-test/main.c:355
上述信息中,说明问题出现在在main.c中的355行附近(test1函数中)。

(2) 动态链接情况

然而,调试的程序往往没有这么简单,通常会加载用到各种各样的动态链接库。如果错误是发生在动态链接库中那么处理将变得困 难一些。下面另外一个例子:

backtrace信息如下:

./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
部分memory map信息如下:

41c5f000-41cbe000 r-xp 00000000 1f:04 22773 /application/example.so
41cbe000-41cc5000 ---p 0005f000 1f:04 22773 /application/example.so
41cc5000-41cc6000 rw-p 0005e000 1f:04 22773 /application/example.so
用backtrace中的地址0x41c68b48做实验,即执行命令 addr2line 0x41c68b48 -e example.so -f 后,addr2line输出信息如下:

[root@test main-test]$ addr2line 0x41c68b48 -e example.so -f
??
??:0
出现这种情况是由于动态链接库是程序运行时动态加载的,因此其加载地址每次可能不一样(关于与位置无关映像,用file命令查看elf格式文件,如果是shared object,那么表示此程序支持PIE与位置无关;如果是executable表示编译和连接分别没有加-fPIE和-pie选项。与位置无关编译和链接增加参数:-fPIE for compiler,-pie for linker),可见0x41c68b48是一个非常大的地址, 和能得到正常信息的地址如0xac00相差甚远,0xac00其也不是一个实际的物理地址(用户空间的程序无法直接访问物理地址),而是经过MMU(内存管理单元)映射过的。

因此,只需要得到此次example.so的加载地址后(动态库代码段起始地址,在memory map信息中第一行的最左面那个地址),再用backtrace中的实际地址0x41c68b48减去example.so的加载地址得到的结果再利用addr2line命令就可以正确的得到出错的地方。

另外在backtrace信息中,(testquery+0x28)其实也是在描述出错的地方,这里表示的是发生在符号testquery(函数名)偏移0x28处的地方,也就是说如果我们能得到符号testquery也就是函数testquery在程序中的入口地址再加上偏移量0x28也能得到正常的出错地址。

0x41c68b48-0x41c5f000 = 0x9b48

addr2line 0x9b48 -e example.so -f

执行上述命令后,addr2line输出信息如下,从而能定位到问题所在行:

[root@test main-test]$ addr2line 0x9b48 -e example.so -f
example_delay
/home/example/example.c:1883
注意:就算是动态链接,如果段错误出现在非动态库中,即应用程序中,这个时候执行addr2line命令时,可执行程序backtrace地址不需要 减去memory map信息中的起始地址。

3. 使用addr2line注意

为了在使用addr2line工具后能得到准确的结果,在编译程序时应该加上-g选项通知编译器生成调试符号。

五、总结

1. 为了正确地获取backtrace信息,编译时需要加上-rdynamic选项,为了正确地利用addr2line输出信息,编译时需要加上-g选项。完整命令如下:

arm-linux-gcc -rdynamic -lpthread -g main.c -o main-test

2. 如果是静态编译程序,只需要backtrace信息和addr2line工具即获取出错点。

3. 如果是动态编译程序,且问题出现在动态链接库时(因为动态编译时,动态链接库是运行时才加载的,加载地址就是在memory map信息中第一行的最左面那个地址),那么就需要使用backtrace信息中问题点实际地址减去memory map信息中代码段其实地址,得出地址偏移,然后使用addr2line工具定位到行。

4. 在大型程序中,backtrace信息往往会出现多个目标地址的情况,如下:

./application(InitialConfigTest+0x6ec) [0xac00].
/lib/libc.so.6(__default_rt_sa_restorer_v2+0) [0x4132b240].
/application/example.so [0x41c68b48].
/application/example.so(testquery+0x28) [0x41c68f6c].
./application [0x19f58].
./application [0x1a660].
/application/lib/libpub.so [0x401d6a68].
/lib/libpthread.so.0 [0x41444858].
上述例子属于动态编译,application为应用程序,example.so为动态链接库。不管是应用程序还是动态链接库,都出现了多个目标地址的情况。

如:

/application/example.so [0x40b61b48].
/application/example.so(testquery+0x28) [0x41c68f6c].

一般选择第一个比较准确(没有地址偏移的那个)

六、偶尔出现段错误分析经验

未完待续...

七、其他相关资料:

1. http://blog.csdn.net/jxgz_leo/article/details/53458366
2. http://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html
3. http://blog.csdn.net/guoping16/article/details/6583957
4. Linux内核出现段错误,会打印出栈信息(dmesg命令可以看到这些信息)。
5. linux中Oops信息的调试及栈回溯(sù):http://blog.csdn.net/u012839187/article/details/78963443。
6. Linux core 文件介绍:https://www.cnblogs.com/dongquan/archive/2012/01/20/2328355.html

9. linux backtrace如何分析

是伏局念指kernel mp的backtrace分析吗?
先google,如果google不到也就没有啥捷径,得系统学习下,主要是kernel代码和驱动的原理。
经腊激验也很重要,有些问题crash的点和实际发生错误的地方不在一个地方,如果没经验很难出结果。
大多都是驱动或者硬件固件的问题,如果问缺困题影响面较大,就收集信息提交给服务器售后或者操作系统的售后去分析吧。

10. 如何快速定位Linux Panic出错的代码行

内核Panic时,一般会打印回调,并打印出当前出错的地址:
kernel/panic.c:panic():

#ifdef CONFIG_DEBUG_BUGVERBOSE
/*
* Avoid nested stack-mping if a panic occurs ring oops processing
*/
if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)
mp_stack();
#endif

而mp_stack()调用关系如下:

mp_stack() --> __mp_stack() --> show_stack() --> mp_backtrace()

mp_backtrace()会打印整个回调,例如:

[<001360ac>] (unwind_backtrace+0x0/0xf8) from [<00147b7c>] (warn_slowpath_common+0x50/0x60)
[<00147b7c>] (warn_slowpath_common+0x50/0x60) from [<00147c40>] (warn_slowpath_null+0x1c/0x24)
[<00147c40>] (warn_slowpath_null+0x1c/0x24) from [<0014de44>] (local_bh_enable_ip+0xa0/0xac)
[<0014de44>] (local_bh_enable_ip+0xa0/0xac) from [<0019594c>] (bdi_register+0xec/0x150)

通常,上面的回调会打印出出错的地址。
解决方案
通过分析,要快速定位出错的代码行,其实就是快速查找到出错的地址对应的代码?
相应的工具有addr2line, gdb, objmp等,这几个工具在How to read a Linux kernel panic?都有介绍,我们将针对上面的实例做更具体的分析。
需要提到的是,代码的实际运行是不需要符号的,只需要地址就行。所以如果要调试代码扮悄,必须确保调试符号已经编译到内核中,不然,回调里头打印的是一堆地址,根本看不到符号,那么对于上面提到的情况二缓誉而言,将无法准确定位问题。
情况一
在代码编译连接时,每个函数都有起始地址和长度,这个地址是程序运行时的地址,而函数内部,每条指令相对于函数开始地址会有偏移。那么有了地址以后,就可以定位到该地址落在哪个函数的区间内,然后找到该函数,进而通过计算偏移,定位到代码行。
情况二
但是,如果拿到的日志文件所在的系统版本跟当前的代码版本不一致,那么编译后的地址就会有差异。那么简单地直接通过地厅哪渣址就可能找不到原来的位置,这个就可能需要回调里头的函数名信息。先通过函数名定位到所在函数,然后通过偏移定位到代码行。

热点内容
安卓手机236开发者选项在哪里 发布:2024-05-06 04:11:13 浏览:258
sql过滤条件 发布:2024-05-06 04:05:18 浏览:562
ifconfiglinux 发布:2024-05-06 03:47:59 浏览:533
c语言开发集成环境 发布:2024-05-06 03:47:06 浏览:607
脚本uzi比赛视频 发布:2024-05-06 03:46:19 浏览:823
php给文本框赋值 发布:2024-05-06 03:21:24 浏览:26
androidjsonkey 发布:2024-05-06 03:07:31 浏览:732
python主线程子线程 发布:2024-05-06 03:07:20 浏览:764
android系统截屏 发布:2024-05-06 02:57:51 浏览:777
android居左 发布:2024-05-06 02:40:26 浏览:45