Linux调试工具 | Addr2line
来源:一口Linux发布时间:2023-06-17729浏览
询问 AIaddr2line translates addresses into file names and line numbers. Given an address in an executable or an offset in a section of a relocatable object, it uses the debugging information to figure out which file name and line number are associated with it.
描述:addr2line将地址转换为文件名和行号。给定可执行文件中的地址或可重定位对象部分中的偏移量,它会使用调试信息来确定与之相关的文件名和行数。
一、前言
通过addr2line的描述可知,在使用addr2line将地址转换为函数、文件名或行号时,其有两种使用方法:即对于可执行文件,addr2line后直接跟十六进制的地址值;对于可重定位对象文件,addr2line后直接跟十六进制的地址偏移量。从而实现正确输出需要的信息,否则会导致地址无法解析,输出??:0。
提示:可通过file命令区分可执行文件与可重定位对象
#输出executable,可知文件是可执行文件
[root@localhost~]#filetest
test:ELF64-bitLSBexecutable,x86-64,version1(SYSV),dynamicallylinked,interpreter/lib64/ld-linux-x86-64.so.2,forGNU/Linux3.2.0,BuildID[sha1]=6bae72d43422a7a048ecf8bce55723aff816820b,withdebug_info,notstripped
#输出relocatable,可知文件是可重定位对象
[root@localhost~]#filehelloworld.ko
helloworld.ko:ELF64-bitLSBrelocatable,x86-64,version1(SYSV),BuildID[sha1]=2f5b2cbb443e05489dcf0272dbb0b72875172411,withdebug_info,notstripped
二、用法
addr2line用于得到程序指令地址所对应的函数,以及函数所在的源文件名和行号。如果没有在命令行中给出地址,就从标准输入中读取它们。
基本用法:addr2line [选项] [地址]
| 选项 | 描述 |
|---|---|
| -a | --addresses 显示地址 |
| -b | --target=设置二进位文件格式 |
| -e | --exe=设置输入文件名称(默认为 a.out) |
| -i | --inlines 解开内联函数 |
| -j | --section=读取相对于段的偏移而非地址 |
| -p | --pretty-print 让输出对人类更可读 |
| -s | --basenames 去除目录名 |
| -f | --functions 显示函数名 |
| -C | --demangle[=style] 解码函数名 |
| -h | --help 显示本帮助 |
三、程序崩溃场景
1、用户态普通程序
使用方法:addr2line -e 进程名 IP指令地址 -f
用户态程序有时可能因为各种原因导致崩溃,发生段错误,比如空指针等。如果没有靠谱的工具,我们就只能靠猜哪里的代码可能存在问题,这里通过Linux自带的addr2line工具调试程序,能够快速直接帮我们准确定位到文件、异常函数名以及行号。
segfault.c源文件:
#include<stdio.h>
intmain()
{
int*p=NULL;
*p=0;
return0;
}
使用gcc进行编译,如下:
[root@localhost68]#gccsegfault.c-osegfault-g
[root@localhost68]#ls
segfaultsegfault.c
[root@localhost68]#./segfault
Segmentationfault(coredumped)
dmesg查看报错信息,如下:
[root@localhost~]#dmesg
[134563.793925]segfault[53791]:segfaultat0ip0000000000400546sp00007fff7956af70error6insegfault[400000+1000]
[134563.793946]Code:015dc390c366662e0f1f8400000000000f1f4000f30f1efaeb8a554889e548c745f800000000488b45f8<c7>0000000000b8000000005dc3662e0f1f8400000000
[root@localhost68]#addr2line-esegfault0000000000400546-f
main
/tmp/68/segfault.c:5
该例子说明addr2line能够直观程序发生段错误的函数以及文件和行号。
注:如果编译程序时没有加上-g参数(即程序不含调试信息),就只能显示出函数名,显示不出具体所在文件的位置,如下:
[root@localhost68]#addr2line-esegfault0x0000000000400546-f
main
??:?
提示:如何区分程序是否还有调试信息,可通过file命令,如果输出结果中显示with debug_info则表示程序含有调试信息,否则表示不含有调试信息。
#不含有调试信息
[root@localhost68]#filesegfault
segfault:ELF64-bitLSBexecutable,x86-64,version1(SYSV),dynamicallylinked,interpreter/lib64/ld-linux-x86-64.so.2,forGNU/Linux3.2.0,BuildID[sha1]=135f8128f721d829b9318da6105cc47223b14de9,notstripped
#含有调试信息
[root@localhost68]#filesegfault
segfault:ELF64-bitLSBexecutable,x86-64,version1(SYSV),dynamicallylinked,interpreter/lib64/ld-linux-x86-64.so.2,forGNU/Linux3.2.0,BuildID[sha1]=c94635b3ef107e7ecc19f4c1a16e16f031d9ca51,withdebug_info,notstripped
2、动态库程序
在动态库中发生段错误,也可以使用addr2line进行定位。
使用方法:addr2line -e 动态库名 IP指令地址-基地址 -f
test.c源文件:
#include"foo.h"
intmain(void)
{
foo();
return0;
}
foo.h:
#ifndef__FOO_LIB_H__
#define__FOO_LIB_H__
intfoo(void);
#endif
foo.c:
#include"foo.h"
intfoo()
{
int*p=0;
*p=0;
return0;
}
先编译动态库, 再编译主程序, 让它链接动态库, 最后运行之:
[root@localhost69]#gcc-O3-g-olibfoo.so-shared-fPICfoo.c
[root@localhost69]#gcc-O3-g-otesttest.c-L.-lfoo
[root@localhost69]#exportLD_LIBRARY_PATH=.
[root@localhost69]#./test
Segmentationfault(coredumped)
查看dmesg日志,如下:
[root@localhost~]#dmesg
[70567.416655]test[27722]:segfaultat0ip00007ffa1f588580sp00007fffa964e698error6inlibfoo.so[7ffa1f588000+1000]
[70567.427374]Code:ffe864ffffffc605bd0a2000015dc30f1f00c30f1f8000000000f30f1efae977ffffff0f1f8000000000<c7>042500000000000000000f0b000000f30f1efa4883
根据日志可知,段错误发生的位置是在test进程调用的libfoo.so库里,我们先使用ldd找到动态库的位置,如下:
[root@localhost69]#lddtest
linux-vdso.so.1(0x00007ffd15b24000)
libfoo.so=>./libfoo.so(0x00007f8c01879000)
libc.so.6=>/lib64/libc.so.6(0x00007f8c014b4000)
/lib64/ld-linux-x86-64.so.2(0x00007f8c01a7b000)
00007ffa1f588580为程序崩溃点ip指令地址,使用dmesg消息中ip指令地址减去动态库基址值(00007ffa1f588580 -7ffa1f588000=580), 差值0x580为错误点在动态库的地址,调用addr2line, 注意-e参数后文件名改为动态库名:
[root@localhost69]#addr2line-elibfoo.so580-f-p
fooat/tmp/69/foo.c:6
动态库基址值(模块载入地址)即dmesg消息libfoo.so[7ffa1f588000+1000]语句中的7ffa1f588000值。
3、内核模块程序
使用方法:addr2line -e xxx.ko 地址偏移量 -f
当内核模块程序异常时,可能会导致机器直接死机重启,这种情况下定位bug可能就比较麻烦,dmesg日志在机器重新启动后,新的日志会覆盖掉原来的报错日志,从而对定位内核异常问题造成麻烦。常见
提示:Linux内核错误
1、panic 当内核遇到严重错误的时候,内核
panic,立马崩溃。死机。2、Oops
Oops是内核遇到错误时发出的提示“声音”,Oops有时候会触发panic,有时候不会,而是直接杀死当前进程,系统可以继续运行。比如说内核态下的段错误,当内核设置了
panic_on_oops=1的时候,Oops会触发panic。【panic_on_oops的值在内核编译的时候配置,可以在/proc/sys/kernel/panic_on_oops查看值,同时可以使用sysctl修改】当
panic_on_oops=0的时候,如果错误发生在中断上下文,Oops也会触发panic。如果错误只是发生在进程上下文,这个时候只需要kill当前进程。【中断上下文包括以下情况:硬中断、软中断、NMI】。Oops的时候内核还可以运行,只是可能不稳定,这个时候,内核会调用printk打印输出内核栈的信息和寄存器的信息。
本人所用主机即属于一旦发生Oops,就会触发panic,因此总是无法查看Oops时的dmesg日志,经查阅资料,发现是内核参数panic_on_oops的原因导致的,因为该参数被设置为1,所以Oops会触发panic,从而导致机器总是死机重启,无法查看Oops时的dmesg日志。下面提供两种方法修改Oops内核参数,使其不会在Oops的时候触发panic导致死机重启。
方法一:修改/proc下内核参数文件内容,临时生效,重启后失效。
echo0>/proc/sys/kernel/panic_on_oops
方法二:修改/etc/sysctl.conf文件的内核参数来永久更改。
[root@localhost~]#vi/etc/sysctl.conf
[root@localhost~]#cat/etc/sysctl.conf
#sysctlsettingsaredefinedthroughfilesin
#/usr/lib/sysctl.d/,/run/sysctl.d/,and/etc/sysctl.d/.
#
#Vendorssettingslivein/usr/lib/sysctl.d/.
#Tooverrideawholefile,createanewfilewiththesamein
#/etc/sysctl.d/andputnewsettingsthere.Tooverride
#onlyspecificsettings,addafilewithalexicallylater
#namein/etc/sysctl.d/andputnewsettingsthere.
#
#Formoreinformation,seesysctl.conf(5)andsysctl.d(5).
kernel.panic_on_oops=0
[root@localhost~]#cat/proc/sys/kernel/panic_on_oops
1
[root@localhost~]#sysctl-p
kernel.panic_on_oops=0
[root@localhost~]#
[root@localhost~]#cat/proc/sys/kernel/panic_on_oops
0
设置好Oops内核参数以后,让我们来构造生成Oops的程序,源代码如下:
oops.c源文件
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
staticnoinlinevoiddo_oops(void)
{
*(int*)0=0;
}
staticintmy_oops_init(void)
{
pr_info("my_oops_init\n");
do_oops();
return0;
}
staticvoidmy_oops_exit(void)
{
pr_info("my_oopsexit\n");
}
module_init(my_oops_init);
module_exit(my_oops_exit);
MODULE_DESCRIPTION("KernelOOPSTesting");
MODULE_LICENSE("GPL");
编译内核模块的Makefile文件
ifneq($(KERNELRELEASE),)
EXTRA_CFLAGS=-Wall-g
obj-m:=oops.o
else
PWD:=$(shellpwd)
KVER:=$(shelluname-r)
KDIR:=/lib/modules/$(KVER)/build
all:
$(MAKE)-C$(KDIR)M=$(PWD)modules
clean:
rm-rf.*.cmd*.o*.order*.symvers*.mod.c*.ko.tmp_versions
endif
编译以及安装oops.ko模块,出现killed,由于错误未发生在中断上下文,未导致Kernel panic死机重启。
[root@localhostoops]#make
make-C/lib/modules/4.18.0-394.el8.x86_64/buildM=/tmp/oopsmodules
make[1]:Enteringdirectory'/usr/src/kernels/4.18.0-394.el8.x86_64'
CC[M]/tmp/oops/oops.o
Buildingmodules,stage2.
MODPOST1modules
CC/tmp/oops/oops.mod.o
LD[M]/tmp/oops/oops.ko
make[1]:Leavingdirectory'/usr/src/kernels/4.18.0-394.el8.x86_64'
[root@localhostoops]#
[root@localhostoops]#ls
dump.logMakefilemodules.orderModule.symversoops.coops.kooops.mod.coops.mod.ooops.o
[root@localhostoops]#insmod./oops.ko
Killed
[root@localhostoops]#lsmod|grepoops
oops163841
加载模块将生成以下Kernel Oops消息,dmesg查看信息如下:
[root@localhost~]#dmesg
[1039.918606]my_oops_init
[1039.918616]BUG:unabletohandlekernelNULLpointerdereferenceat0000000000000000
[1039.926442]PGD0P4D0
[1039.928979]Oops:0002[#1]SMPNOPTI
[1039.932637]CPU:34PID:3843Comm:insmodKdump:loadedTainted:GOE-----------4.18.0-394.el8.x86_64#1
[1039.943756]Hardwarename:NewH3CTechnologiesCo.,Ltd.H3CUniServerR4950G5/RS45M2C9SB,BIOS5.3709/30/2021
[1039.954000]RIP:0010:do_oops+0x5/0x11[oops]
[1039.958364]Code:UnabletoaccessopcodebytesatRIP0xffffffffc02e6fdb.
[1039.965231]RSP:0018:ffffb9d40a8c7cb0EFLAGS:00010246
[1039.970449]RAX:000000000000000cRBX:0000000000000000RCX:0000000000000000
[1039.977573]RDX:0000000000000000RSI:ffff98942ee96758RDI:ffff98942ee96758
[1039.984697]RBP:ffffffffc02e7011R08:0000000000000000R09:c0000000ffff7fff
[1039.991822]R10:0000000000000001R11:ffffb9d40a8c7ad8R12:ffffffffc02e9000
[1039.998944]R13:ffffffffc02e9018R14:ffffffffc02e91d0R15:0000000000000000
[1040.006069]FS:00007f1b8d93b740(0000)GS:ffff98942ee80000(0000)knlGS:0000000000000000
[1040.014145]CS:0010DS:0000ES:0000CR0:0000000080050033
[1040.019884]CR2:ffffffffc02e6fdbCR3:0000000145c02000CR4:0000000000350ee0
[1040.027008]CallTrace:
[1040.029454]my_oops_init+0x16/0x19[oops]
[1040.033550]do_one_initcall+0x46/0x1d0
[1040.037390]?do_init_module+0x22/0x220
[1040.041318]?kmem_cache_alloc_trace+0x142/0x280
[1040.046023]do_init_module+0x5a/0x220
[1040.049777]load_module+0x14ba/0x17f0
[1040.053530]?__do_sys_finit_module+0xb1/0x110
[1040.058059]__do_sys_finit_module+0xb1/0x110
[1040.062411]do_syscall_64+0x5b/0x1a0
[1040.066077]entry_SYSCALL_64_after_hwframe+0x65/0xca
[1040.071130]RIP:0033:0x7f1b8c8509bd
[1040.074701]Code:ffc3662e0f1f84000000000090f30f1efa4889f84889f74889d64889ca4d89c24d89c84c8b4c24080f05<48>3d01f0ffff7301c3488b0d9b543800f7d864890148
[1040.093446]RSP:002b:00007ffc4df0a968EFLAGS:00000246ORIG_RAX:0000000000000139
[1040.101004]RAX:ffffffffffffffdaRBX:00005653fb1997d0RCX:00007f1b8c8509bd
[1040.108126]RDX:0000000000000000RSI:00005653f980c8b6RDI:0000000000000003
[1040.115251]RBP:00005653f980c8b6R08:0000000000000000R09:00007f1b8cbd9760
[1040.122375]R10:0000000000000003R11:0000000000000246R12:0000000000000000
[1040.129498]R13:00005653fb1997b0R14:0000000000000000R15:0000000000000000
[1040.136623]Moduleslinkedin:oops(OE+)binfmt_miscxt_CHECKSUMipt_MASQUERADExt_conntrackipt_REJECTnf_reject_ipv4nft_compatnft_counternft_chain_natnf_natnf_conntracknf_defrag_ipv6nf_defrag_ipv4nf_tablesnfnetlinkrpcsec_gss_krb5auth_rpcgssnfsv4dns_resolvernfslockdgracefscachebridgestpllcintel_rapl_msrintel_rapl_commonamd64_edac_modedac_mce_amdamd_energykvm_amdkvmirqbypassipmi_ssifpcspkrcrct10dif_pclmulcrc32_pclmulghash_clmulni_intelrapljoydevccpsp5100_tcoi2c_piix4k10tempptdmaacpi_ipmiipmi_sisunrpcvfatfatxfslibcrc32csd_modt10_pisgcrc32c_intelastdrm_vram_helperdrm_kms_helpersyscopyareasysfillrectsysimgbltfb_sys_fopsdrm_ttm_helperttmahcidrmlibahcinfp(OE)igblibatadcai2c_algo_bitdm_mirrordm_region_hashdm_logdm_modipmi_devintfipmi_msghandler
[1040.208357]CR2:0000000000000000
[1040.211668]---[endtraceb69c1e8998070273]---
[1040.230185]RIP:0010:do_oops+0x5/0x11[oops]
[1040.234540]Code:UnabletoaccessopcodebytesatRIP0xffffffffc02e6fdb.
[1040.241409]RSP:0018:ffffb9d40a8c7cb0EFLAGS:00010246
[1040.246626]RAX:000000000000000cRBX:0000000000000000RCX:0000000000000000
[1040.253750]RDX:0000000000000000RSI:ffff98942ee96758RDI:ffff98942ee96758
[1040.260876]RBP:ffffffffc02e7011R08:0000000000000000R09:c0000000ffff7fff
[1040.267998]R10:0000000000000001R11:ffffb9d40a8c7ad8R12:ffffffffc02e9000
[1040.275124]R13:ffffffffc02e9018R14:ffffffffc02e91d0R15:0000000000000000
[1040.282247]FS:00007f1b8d93b740(0000)GS:ffff98942ee80000(0000)knlGS:0000000000000000
[1040.290323]CS:0010DS:0000ES:0000CR0:0000000080050033
[1040.296061]CR2:ffffffffc02e6fdbCR3:0000000145c02000CR4:0000000000350ee0
消息解析:
[1039.928979]Oops:0002[#1]SMPNOPTI
[1039.932637]CPU:34PID:3843Comm:insmodKdump:loadedTainted:GOE-----------4.18.0-394.el8.x86_64#1Oops: 0002 -- 错误码
Oops: [#1] -- Oops发生的次数
CPU: 34 -- 表示Oops是发生在CPU34上
关键信息如下,这里提示在操作函数do_oops的时候出现异常,地址偏移量0x5:
[1039.954000]RIP:0010:do_oops+0x5/0x11[oops]
为什么这条信息关键,因为其含有指令指针
RIP;指令指针IP/EIP/RIP的基本功能是指向要执行的下一条地址。在80808位微处理器上的寄存器名称是PC(program counter,程序计数器),从8086起,被称为IP(instruction pointer,指令指针)。主要区别在与PC指向正在执行的指令,而IP指向下一条指令。在64位模式下,指令指针是RIP寄存器。这个寄存器保存着下一条要执行的指令的64位地址偏移量。64位模式支持一种新的寻址模式,被称为RIP相对寻址。使用这个模式,有效地址的计算方式变为RIP(指向下一条指令)加上位移量。
由此可以看出内核执行到do_oops+0x5/0x11这个地址的时候出现异常,我们只需要找到这个地址对应的代码即可。
打印格式:
do_oops+0x5/0x11 [oops]即:symbol+offset/size [module]symbol: 符号offset:地址偏移量size:函数的长度module: 所属内核模块
do_oops指示了是在do_oops函数中出现的异常,0x5表示出错的地址偏移量,0x11表示do_oops函数的大小。使用file查看内核模块文件类型:
[root@localhostoops]#fileoops.ko
oops.ko:ELF64-bitLSBrelocatable,x86-64,version1(SYSV),BuildID[sha1]=4b5b58c4aaf63d65d338be17399bfd3c480504c3,withdebug_info,notstripped
上述结果显示该内核模块文件为可重定位对象文件,因此使用addr2line直接跟十六进制的地址偏移量定位文件名、行号和行数,如下:
[root@localhostoops]#addr2line-eoops.ko0x5-f-p
do_oopsat/tmp/oops/oops.c:7
可以看到异常代码在oops.c文件第7行,根据源代码,可知该行代码访问非法内存地址。
四、总结
Addr2line 工具(它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。这在应用程序和内核程序执行过程中出现崩溃时,可用于快速定位出出错的位置,进而找出代码的bug。一般适用于 debug 版本或带有 symbol 信息的库。
新闻来源:一口Linux,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。

