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

backtracelinux

发布时间: 2024-04-17 04:19:03

Ⅰ 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

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

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

Ⅲ linux core 怎么打开

core文件是由应用程序收到系统信号后崩溃产生的,该文件中记录了程序崩溃的原因(例如收到那种信号),调用堆栈和崩溃时的内存及变量值等等的信息。
打开core文件与编译时使用的编译器有关,但绝大多数linux程序是使用gcc编译器编译的,因此可使用对应gdb调试器打开,命令格式如下:
$ gdb 应用程序文件名 core文件名
举例:
$ gdb /usr/bin/gedit ~/core ------ 查看由gedit崩溃产生的core文件
(gdb) bt ------ 或者backtrace, 查看程序运行到当前位置之前所有的堆栈帧情况)
(gdb) quit ------ 退出

如果不知道core文件由哪个文件产生的,可使用file命令显示
$ file core

Ⅳ linux gdb backtrace 怎么实现的

一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。

在glibc头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈。

[cpp] view plain print?
int backtrace(void **buffer,int size)
该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小
在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址
注意:某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会导致无法正确解析堆栈内容

[cpp] view plain print?
char ** backtrace_symbols (void *const *buffer, int size)
backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)

函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址
现在,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的符号给链接器,以能支持函数名功能(比如,在使用GNU ld链接器的系统中,你需要传递(-rdynamic), -rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果你的链接器支持-rdynamic的话,建议将其加上!)

该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针.

注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL

[cpp] view plain print?
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况

下面是glibc中的实例(稍有修改):
[cpp] view plain print?
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to @code{stdout}. */
void print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
if (NULL == strings)
{
perror("backtrace_synbols");
Exit(EXIT_FAILURE);
}

printf ("Obtained %zd stack frames.\n", size);

for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);

free (strings);
strings = NULL;
}

/* A mmy function to make the backtrace more interesting. */
void mmy_function (void)
{
print_trace ();
}

int main (int argc, char *argv[])
{
mmy_function ();
return 0;
}
输出如下:
[cpp] view plain print?
Obtained 4 stack frames.
./execinfo() [0x80484dd]
./execinfo() [0x8048549]
./execinfo() [0x8048556]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x70a113]

我们还可以利用这backtrace来定位段错误位置。
通常情况系,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function);函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。
举例如下:
[cpp] view plain print?
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>

void mp(int signo)
{
void *buffer[30] = {0};
size_t size;
char **strings = NULL;
size_t i = 0;

size = backtrace(buffer, 30);
fprintf(stdout, "Obtained %zd stack frames.nm\n", size);
strings = backtrace_symbols(buffer, size);
if (strings == NULL)
{
perror("backtrace_symbols.");
exit(EXIT_FAILURE);
}

for (i = 0; i < size; i++)
{
fprintf(stdout, "%s\n", strings[i]);
}
free(strings);
strings = NULL;
exit(0);
}

void func_c()
{
*((volatile char *)0x0) = 0x9999;
}

void func_b()
{
func_c();
}

void func_a()
{
func_b();
}

int main(int argc, const char *argv[])
{
if (signal(SIGSEGV, mp) == SIG_ERR)
perror("can't catch SIGSEGV");
func_a();
return 0;
}

编译程序:
gcc -g -rdynamic test.c -o test; ./test
输出如下:
[cpp] view plain print?
Obtained6stackframes.nm
./backstrace_debug(mp+0x45)[0x80487c9]
[0x468400]
./backstrace_debug(func_b+0x8)[0x804888c]
./backstrace_debug(func_a+0x8)[0x8048896]
./backstrace_debug(main+0x33)[0x80488cb]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]
(这里有个疑问: 多次运行的结果是/lib/i368-Linux-gnu/libc.so.6和[0x468400]的返回地址是变化的,但不变的是后三位, 不知道为什么)
接着:
objmp -d test > test.s
在test.s中搜索804888c如下:

[cpp] view plain print?
8048884 <func_b>:
8048884: 55 push %ebp
8048885: 89 e5 mov %esp, %ebp
8048887: e8 eb ff ff ff call 8048877 <func_c>
804888c: 5d pop %ebp
804888d: c3 ret
其中80488c时调用(call 8048877)C函数后的地址,虽然并没有直接定位到C函数,通过汇编代码, 基本可以推出是C函数出问题了(pop指令不会导致段错误的)。
我们也可以通过addr2line来查看
[cpp] view plain print?
addr2line 0x804888c -e backstrace_debug -f
输出:
[cpp] view plain print?
func_b
/home/astrol/c/backstrace_debug.c:57

以下是简单的backtrace原理实现:

Ⅳ 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上不应有其它设备。

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

Ⅶ 1 linux下调试core的命令,察看堆栈状态命令

比方说,你要调试的core文件是 core.xxx,原始可执行文件是 a.exe

先用 gdb a.exe 进入 gdb,在gdb命令行下 执行

core-file /path/to/core.xxx
然后即可调试core mp文件了,比如用 bt 等

热点内容
上传日上限 发布:2024-05-16 22:26:08 浏览:858
c语言基本概念 发布:2024-05-16 21:44:16 浏览:190
sqlserver跨库查询 发布:2024-05-16 21:36:56 浏览:164
sql多表连接语句 发布:2024-05-16 21:21:53 浏览:301
sqlscope 发布:2024-05-16 21:17:37 浏览:966
存储器通常有 发布:2024-05-16 21:17:35 浏览:379
云数据库概念 发布:2024-05-16 21:17:32 浏览:819
铺地板编程 发布:2024-05-16 21:07:36 浏览:459
苹果手机缓存删除 发布:2024-05-16 21:04:35 浏览:507
scratch编程电子书 发布:2024-05-16 21:02:54 浏览:227