前言
使用backtrace
等工具将程序异常运行的堆栈打印出来,然后再结合使用addr2line
将堆栈地址转为文件行,将对于定位故障非常有帮助,但有时使用addr2line
却输出为??:0
!
从addr2line的man手册中看确实没有分C和C++语言的区别,而且还有特殊的选项-C
针对于C++编译器的函数重命名,所以,输出??:0
,应该是找不到对应行号了,特别是使用地址信息的不正确。
结合个人实践,将对backtrace
输出的地址信息进行分类!
backtrace显示地址分类
/lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7faf347b3133]
- **()**中携带的地址信息为模块内地址,分为
+0x0ffset
和symbol+0x0ffset
两种情况,其中+0x0ffset
可以直接作为addr2line
的地址参数;symbol+0x0ffset
地址在多几步处理后,可以作为addr2line
的地址参数,特别是结合objdump -D
工具。
- **[]**中携带的地址为函数运行时地址,所以,一般是不能直接用的
在没有**()内地址信息时,[]**的地址可以作为地址参数
如果您确定堆栈信息与运行程序和依赖库是一致的话,且程序携带-g
的调试信息,那么可以深入使用objdump -D
工具进行人工分析。
-rdynamic
-rdynamic
编译选项,从个人实践来看,作用被夸大了。携带此参数后,显示出来的symbol+0ffset
的地址信息,无法直接作为addr2line
的地址参数,去掉选项后,反而得到了能够直接使用的(+0xOffset)
的地址
对于动态库的适应
如果SO库本身携带有调试信息的话,同样可以用addr2line -e /path/to/so addr
,定位到SO库某文件中的某行。
[]地址的特殊性
从一个很小的青蛙例子,可以backtrace
打印出**类[]的地址,多运行几次发现类[]**地址存在动态变化,所以,不能直接作为addr2line
进行使用。反而是(+0xOffset)
或(Symbol+0xOffset)
的地址非常稳定,比较适合作为addr2line
的参数进行使用!
(+0xOffset)中的0xOffset可以直接作为addr2line
的地址参数来使用
symbol+0xffset地址结合objdump
纯手工计算
# template
objdump -D /path/to/relocatable_object | grep -A20 ‘symbol’
# example
objdump -D a.out |grep -A20 'main'
# output
...
0000000000003920 <main>:
3920: f3 0f 1e fa endbr64
3924: 55 push %rbp
3925: 48 89 e5 mov %rsp,%rbp
3928: 53 push %rbx
# 对于解析不出来的symbol+0xOffset进行特殊处理,特别关注<symbol>的位置
# 对于无法识别的`addr2line -e a.out main+0x1fc`
addr2line -e a.out $(printf '%x' $((0x3920 + 0x1fc)))
-
objdump -D
将程序或库进行汇编输出,可以看到符号在模块内的地址
-
grep -A
选项打印匹配行后多少行;另外,-C
为匹配行的上下多少行,-B
为匹配行前多少行,在监控动态输出日志时非常有帮助
固化
#!/bin/bash
# file: queryRowNo.sh
addr="$2"
name=${addr%+*}
offset=${addr#*+}
base=$(objdump -D $1 | grep "<$name>" | awk '{print $1}')
if [ -z "$base" ]; then
echo "can't get the base addr of $name"
exit
fi
base="0x$base"
addr2line -e "$1" $(printf '%x' $((base + offset)))
# example using shell script file
./queryRowNo.sh /lib/libACE.so.6.5.19 _ZN15ACE_Sig_Handler8dispatchEiP9siginfo_tP10ucontext_t+0x55
参考