Hello,今天又是Q哥来给大家进行分享了~
当屏幕弹出“Segmentation fault”时,程序员该怎么办?之前文章已经教给大家一个通用的方法就是利用backtrace函数和addr2line命令定位问题,没看过上一篇文章的小伙伴可以戳这里查看如何轻松搞定“Segmentation fault”,看这篇就够了!
如果你熟悉gdb,当程序跑飞,我们可以执行backtrace(简写bt)命令,可以dump出异常调用栈。如果你在大厂呆过,你应该知道,商用的定位方法是不允许使用gdb的,只能通过命令行和日志定位问题。
所以,今天我们一起分析下gdb源码中backtrace怎么实现的,然后活学活用,把这个功能在任意硬件平台(ARM/MIPS/X64/X32...)上移植出来。
首先回顾下,利用gdb在程序异常后执行bt命令的打印。
先从gdb官网下载gdb-8.3源码,把backtrace命令当做字符串,可以查到其调用函数,主要功能是获取异常时调用栈地址信息,然后对地址进行解析成文件名、函数名和行号。对代码一层层分析,可以看到最核心的代码段(地址解析),先贴出来,如下:
是不是看不懂?没关系,很简单的,一起分析下呗。
首先代码最后一行pfilename, pfunctionname, plineno一眼就看出是出参,所以地址解析最核心的函数是bfd_find_nearest_line,该函数参数mypc就是待解析的地址,那么下面的任务就变成给bfd_find_nearest_line函数的参数赋值就行了。
bfd_find_nearest_line函数入参主要包括,bfd节点、所属section、符号表和偏移地址,出参则是文件名、函数名和行号。
Bfd节点的获取,即current_bfd,直接可以调用bfd模块open函数即可。
通过上面代码可以知道Code_section,是通过遍历所有section段,找到text这个section段赋值给Code_section,并把text section段的基地址给code_base。例如用readelf读取section信息,code_base值就是光亮的0X4005C0值。
有小伙伴会问,什么是section?
这是Linux ELF相关的基础知识,ELF相关的基础知识之前文章已经讲过,可查阅。
符号表的获取,方法有很多,例如bfd_canonicalize_symtab、bfd_read_minisymbols函数都可以获取到,可以参考上面的源码。
最后需要说的是bfd_find_nearest_line依赖于bfd库,如果你需要把这个异常调用栈功能移植到特定平台(arm/mips),你需要自己编译bfd库。
特定平台的bfd库哪里来呢?
需要下载binutils源码,可以到官网下载最新的binutils-2.32.tar.gz。
先配置CC=XX ./configure --host==XX,再make生成libbfd.a和libiberty.a。
现在开始对之前文章中介绍的backtrace代码进行优化,主要是重构backtrace_symbols函数,利用backtrace和dump_backtrace实现异常调用栈的打印。
我们已经知道调用backtrace函数可以获取调用栈地址,考虑到可执行文件包含动态库,下面的分析,是只dump不包含动态库的异常调用栈,这样,如果调用栈地址非本模块本身,则不会做解析。
核心代码如下,首先以可执行文件本身作为file,获取fd,然后根据addr,调用dump_current_pc_location函数,传入fd和addr,解析出文件名、函数名和行号。
通过gcc example.c -L./lib -lbfd -liberty -g -ldl -lz -o example 命令编译,执行example后,可以看到异常调用栈信息。
好了,至此,我们已经把gdb的backtrace功能移植出来了。
但是如果异常调用栈中还包括动态库中的函数,这个我们目前代码是没有做解析的,这个代码能不能做?
答案是肯定的,只要对现有代码做两个地方改动就行了。
怎么搞?
不急,慢慢来,先消化,后面再深聊。
如果想直接获取本文完整代码,可关注公众号,回复”bt”即可下载。
欢迎扫码关注,一起学习Linux