在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本文作为《嵌入式系统C编程之堆栈回溯》的补充版。文中涉及的代码运行环境如下:
一 异常信号信号就是软件中断,用于向正在运行的程序(进程)发送有关异步事件发生的信息。Linux应用程序发生异常时,操作系统会产生相应的信号。硬件检测到异常(非法指令、对无效的内存引用等)时也会通知内核,内核将其转换为适当的信号并发给该异常发生时正在运行的进程。 此外,进程可将信号发送给另一进程或进程组(调用kill函数),或向自身发送信号(调用raise函数)。 系统中可产生并发送多种类型的信号。在Linux环境中可通过kill -l命令查看完整的信号清单。 若进程既不忽略也未指定信号处理函数,则操作系统将对信号执行默认动作(通常是终止该进程)。可能导致进程终止的常见信号如下:
应用程序异常终止时,常见的故障定位方法有:1)添加打印语句二分查找,效率低下;2)gdb调试,不适用于没有gdb的环境和软件发布后;3)分析core文件。 对于导致进程终止的信号,系统默认处理是打印出错信息,并在进程当前工作目录下创建core或core.pid文件以复制该进程的存储映像。若未生成core文件,可查看ulimit -c命令结果。当结果为0时通过ulimit -c <Size>或ulimit -c unlimited命令设置core文件大小(不为0时才会产生core文件)。 在编译程序时加上-g –rdynamic选项,当进程异常终止时即可运行gdb ./<Exec> core命令,定位到出错的C语句。若程序运行环境无法运行gdb,可将core文件、程序可执行文件拷贝到支持gdb的主机上调试。 注意,某些异常信号(如SIGPIPE)不会产生core文件,而且有时core文件不能正常调试。 更好的方法是捕获到异常信号后,在信号处理函数里进行堆栈回溯,即《嵌入式系统C编程之堆栈回溯》一文所述。
二 特殊的堆栈回溯本节主要补充两种堆栈回溯的特殊情况。 2.1 未开启-rdynamic -ldl选项未开启-rdynamic选项时(经测-ldl选项可有可无),堆栈回溯将无法显示函数名。 例如,将Func1()函数稍作修改: 1 VOID Func1(VOID){ 2 //SHOW_STACK(); 3 CHAR *p = NULL; 4 *p = 0; 5 return; 6 } 编译链接时开启-g选项,关闭-rdynamic和-ldl选项,执行结果如下: 1 Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>> 2 Process (5663) receive signal 11 3 <Signal Information>: 4 SigNo: 11(SIGSEGV) 5 ErrNo: 0 (Success) 6 SigCode: 1 7 Raised at: (nil)[Unreliable] 8 <Register Content>: 9 00000033 00000000 0000007b 0000007b 10 00000000 00535ca0 bfda20b8 bfda20a8 11 0067eff4 00000000 bfda205c 00000000 12 0000000e 00000006 0804a1c1 00000073 13 00010282 bfda20a8 0000007b 14 <Stack Trace(Customized)>: 15 [ 1] (./OmciExec) [0x0804a1c1] (<STATIC>)+0x804a1c1 16 [ 2] (./OmciExec) [0x0804a1d1] (<STATIC>)+0x804a1d1 17 [ 3] (./OmciExec) [0x0804a1f2] (<STATIC>)+0x804a1fb 18 [ 4] (./OmciExec) [0x0804e348] (<STATIC>)+0x804e35c 19 [ 5] (/lib/libc.so.6) [0x00552e9c] (__libc_start_main)+0xdc 20 [ 6] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001 21 End of Stack Trace<<<<<<<<<<<<<<<<<<<<<<<<<<<< 其中,<STATIC>应作Unknown理解,而非静态函数。 由执行结果可知,错误发生在地址0x0804a1c1处。要定位出错代码的位置,需对该程序的可执行文件进行反汇编。反汇编命令为objdump -dS --start-address 0x804a1c1 OmciExec > dump,表示使用objdump工具反汇编可执行文件OmciExec,并将从出错地址开始的结果写入dump文件。指定参数-S(隐含-d参数)时,将尽可能地反汇编出源代码,通常需结合-g编译选项。 打开dump文件,截取地址0x0804a1c1附近的指令片段如下: 1 VOID Func1(VOID){ 2 //SHOW_STACK(); 3 CHAR *p = NULL; 4 *p = 0; 5 804a1c1: c6 00 00 movb $0x0,(%eax) 6 return; 7 } 8 VOID Func2(VOID){ 9 Func1(); 10 804a1cc: e8 e0 ff ff ff call 804a1b1 <Func1> 11 printf("%s\n", 0x123); 12 804a1d1: c7 44 24 04 23 01 00 movl $0x123,0x4(%esp) 13 return; 14 } 15 VOID BtrTest(VOID){ 16 Func2(); 17 804a1ed: e8 d4 ff ff ff call 804a1c6 <Func2> 18 printf("%d\n", 5/0); 19 804a1f2: ba 05 00 00 00 mov $0x5,%edx 20 return; 21 } 22 INT32S main(VOID) 23 { 24 BtrTest(); 25 804e343: e8 9f be ff ff call 804a1e7 <BtrTest> 26 GlbOverrunTest(); 27 804e348: e8 75 fe ff ff call 804e1c2 <GlbOverrunTest> 可见,函数调用顺序为Func1()->Func2()->BtrTest()->main()。 未开启-g选项时,dump结果只显示函数名和指令地址。但本例中发生的是段错误,通过重点排查出错函数内的空指针引用和数组越界代码,也可轻松定位出错语句。 2.2 忽略帧基指针若忽略帧基指针(开启-fomit-frame-pointer),将无法正确回溯堆栈内容。此时,可借助/proc/pid/maps文件所提供的进程虚拟地址空间信息和栈顶指针SP对堆栈进行尝试性回溯。 通过cat /proc/<pid>/maps 命令可查看指定进程的内存空间分布,如: 1 004c0000-0057e000 rwxp 00000000 00:00 0 [heap] 2 2aaa8000-2aaad000 r-xp 00000000 1f:06 75 /lib/ld-uClibc-0.9.29.so 3 2aaad000-2aaaf000 rw-p 00000000 00:00 0 4 2aabc000-2aabd000 r--p 00004000 1f:06 75 /lib/ld-uClibc-0.9.29.so 5 2aabd000-2aabe000 rw-p 00005000 1f:06 75 /lib/ld-uClibc-0.9.29.so 6 … … … … 7 7ff9b000-7ffb0000 rwxp 00000000 00:00 0 [stack] 显示内容共有6列,分别为: 1) 地址(address):虚拟内存段的起始和终止地址,即该文件所占用的地址空间。 2) 权限(permission):虚拟内存段的访问权限(r为读,w为写,x为执行,s为共享,p为私有)。 3) 偏移量:虚拟内存段在映像文件中的偏移量。 4) 设备(device):映像文件的主设备号和次设备号(可通过cat /proc/devices查看详情)。 5) 节点(inode):映像文件的节点号(0表示没有节点与内存相对应); 6) 路径(name): 映像文件的路径名 通常权限为r-xp时对应代码段(正文段),权限为rw-p时对应数据段。 因此,忽略帧基指针时,函数堆栈回溯的步骤如下(以Intel x86架构为例): 1) 读取/proc/<pid>/maps文件,记录映射到进程虚拟地址空间的可执行代码段的起止位置; 2) 从当前栈顶指针SP出发,向高地址依次取出一个整型值。若该值位于上步所计算的某个地址区间中,则输出该值(可能是函数栈帧中的返回地址,即调用指令的下条地址); 3) 循环步骤2,直至读取完指定数目的整型值。 本实现首先需要增加几个宏定义,如下: 1 #if defined(REG_RIP) 2 #define REG_IP REG_RIP //指令指针(保存返回地址) 3 #define REG_BP REG_RBP //帧基指针 4 #define REG_SP REG_RSP //栈顶指针 5 #define REG_FMT "%016lx" 6 #elif defined(REG_EIP) 7 #define REG_IP REG_EIP 8 #define REG_BP REG_EBP 9 #define REG_SP REG_ESP 10 #define REG_FMT "%08x" 11 #else 12 #warning "Neither REG_RIP nor REG_EIP is defined!" 13 #define REG_FMT "%08x" 14 #endif 15 16 #ifndef TASK_SIZE //用户进程空间大小(基于该值可确定堆栈底部) 17 #define TASK_SIZE (0xbf000000UL) 18 #endif 19 20 #ifndef MAPS_SEG_NUM //解析'/proc/pid/maps'结果时的最大段数(条目数) 21 #define MAPS_SEG_NUM 30 22 #endif 此处TASK_SIZE定义为PAGE_OFFSET(0xc0000000)向低地址偏移16M处,即用户进程空间可用的虚拟内存范围为0~0xbf000000。 然后定义ShowStackContent()函数如下: 1 /****************************************************************************** 2 * 函数名称: ShowStackContent 3 * 功能说明: 显示堆栈内容 4 ******************************************************************************/ 5 static VOID ShowStackContent(INT32U dwStkPtr) 6 { 7 fprintf(gpStraceFd, "<Current Thread Maps>:\n"); 8 CHAR szMapsCmd[sizeof("/proc/65535/maps")] = {0}; 9 sprintf(szMapsCmd, "/proc/%d/maps", getpid()); 10 FILE *pFile = fopen(szMapsCmd, "r"); 11 if(NULL == pFile) 12 { 13 fprintf(gpStraceFd, "Open File '%s' Error(%s)!\n", szMapsCmd, strerror(errno)); 14 return; 15 } 16 17 INT32U dwSegIdx = 0, dwSegNum = 0; 18 INT32U dwStartAddr = 0, dwEndAddr = 0; 19 INT32U aAddrSeg[MAPS_SEG_NUM*2] = {0}; 20 CHAR szMapsBuf[256] = {0}; 21 while(fgets(szMapsBuf, sizeof(szMapsBuf)-1, pFile) != NULL) 22 { 23 CHAR cAccess; 24 CHAR szMisc[128]; //杂项,不必关注 25 INT32S dwRet = sscanf(szMapsBuf, "%08x-%08x %*c%*c%c %[^\n]%*c", &dwStartAddr, &dwEndAddr, &cAccess, szMisc); 26 if(-1 == dwRet || 0 == dwRet) 27 break; 28 29 if(cAccess == 'x'/*Executable*/ && dwSegIdx < MAPS_SEG_NUM*2 && dwEndAddr != TASK_SIZE) 30 { 31 fprintf(gpStraceFd, "\t%s", szMapsBuf); 32 aAddrSeg[dwSegIdx++] = dwStartAddr; 33 aAddrSeg[dwSegIdx++] = dwEndAddr; 34 } 35 } 36 dwSegNum = dwSegIdx; 37 fclose(pFile); 38 39 //从当前ESP出发检查高地址处的dwDwordNum个堆栈单位(双字) 40 INT32U dwDwordNum = ((TASK_SIZE-dwStkPtr) > 512) ? 512 : (TASK_SIZE-dwStkPtr); 41 dwDwordNum >>= 2; 42 fprintf(gpStraceFd, "<Possible Call Trace>:\n\t"); 43 44 INT32U dwIdx = 0, dwIdx2 = 0; 45 for(; dwIdx < dwDwordNum; dwIdx++) 46 { 47 INT32U dwStkCont = *((INT32U*)dwStkPtr + dwIdx); 48 for(dwSegIdx = 0; dwSegIdx < dwSegNum; dwSegIdx+=2) 49 { 50 if(dwStkCont >= aAddrSeg[dwSegIdx] && dwStkCont <= aAddrSeg[dwSegIdx+1]) 51 { 52 fprintf(gpStraceFd, "[%8x] ", dwStkCont); 53 if(0 == ((++dwIdx2)%4)) //每行输出4个堆栈内容 54 fprintf(gpStraceFd, "\n\t"); 55 break; 56 } 57 } 58 } 59 fprintf(gpStraceFd, "\n"); 60 return; 61 } SigHandler()函数输出Customized堆栈回溯(仅首行有参考意义)后,再调用ShowStackContent()函数: ShowStackContent(ptContext->uc_mcontext.gregs[REG_SP]); 以2.1节Func1()函数为例。编译链接时开启-g和-fomit-frame-pointer选项,可选关闭-rdynamic和-ldl选项,执行结果如下: 1 Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>> 2 Process (15207) receive signal 11 3 <Signal Information>: 4 SigNo: 11(SIGSEGV) 5 ErrNo: 0 (Success) 6 SigCode: 1 7 Raised at: (nil)[Unreliable] 8 <Register Content>: 9 00000033 00000000 0000007b 0000007b 10 00000000 00535ca0 bfe17428 bfe1735c 11 0067eff4 00000001 bfe173d0 00000000 12 0000000e 00000006 0804a373 00000073 13 00010286 bfe1735c 0000007b 14 <Stack Trace(Customized)>: 15 [ 1] (./OmciExec) [0x0804a373] (<STATIC>)+0x804a373 16 [ 2] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001 17 <Current Thread Maps>: 18 0051b000-00535000 r-xp 00000000 fd:00 28871142 /lib/ld-2.5.so 19 00535000-00536000 r-xp 00019000 fd:00 28871142 /lib/ld-2.5.so 20 00536000-00537000 rwxp 0001a000 fd:00 28871142 /lib/ld-2.5.so 21 0053d000-0067c000 r-xp 00000000 fd:00 28871143 /lib/libc-2.5.so 22 0067c000-0067d000 --xp 0013f000 fd:00 28871143 /lib/libc-2.5.so 23 0067d000-0067f000 r-xp 0013f000 fd:00 28871143 /lib/libc-2.5.so 24 0067f000-00680000 rwxp 00141000 fd:00 28871143 /lib/libc-2.5.so 25 00680000-00683000 rwxp 00680000 00:00 0 26 00685000-006aa000 r-xp 00000000 fd:00 28871150 /lib/libm-2.5.so 27 006aa000-006ab000 r-xp 00024000 fd:00 28871150 /lib/libm-2.5.so 28 006ab000-006ac000 rwxp 00025000 fd:00 28871150 /lib/libm-2.5.so 29 006ae000-006b0000 r-xp 00000000 fd:00 28871144 /lib/libdl-2.5.so 30 006b0000-006b1000 r-xp 00001000 fd:00 28871144 /lib/libdl-2.5.so 31 006b1000-006b2000 rwxp 00002000 fd:00 28871144 /lib/libdl-2.5.so 32 006b4000-006c8000 r-xp 00000000 fd:00 28871145 /lib/libpthread-2.5.so 33 006c8000-006c9000 r-xp 00013000 fd:00 28871145 /lib/libpthread-2.5.so 34 006c9000-006ca000 rwxp 00014000 fd:00 28871145 /lib/libpthread-2.5.so 35 006ca000-006cc000 rwxp 006ca000 00:00 0 36 00a68000-00a6f000 r-xp 00000000 fd:00 28871146 /lib/librt-2.5.so 37 00a6f000-00a70000 r-xp 00006000 fd:00 28871146 /lib/librt-2.5.so 38 00a70000-00a71000 rwxp 00007000 fd:00 28871146 /lib/librt-2.5.so 39 00b13000-00b42000 r-xp 00000000 fd:00 4096074 /usr/lib/libreadline.so.5.1 40 00b42000-00b46000 rwxp 0002f000 fd:00 4096074 /usr/lib/libreadline.so.5.1 41 00b46000-00b47000 rwxp 00b46000 00:00 0 42 00d10000-00d11000 r-xp 00d10000 00:00 0 [vdso] 43 04e6a000-04eaa000 r-xp 00000000 fd:00 22226947 /usr/lib/libncurses.so.5.5 44 04eaa000-04eb2000 rwxp 00040000 fd:00 22226947 /usr/lib/libncurses.so.5.5 45 04eb2000-04eb3000 rwxp 04eb2000 00:00 0 46 08048000-08052000 r-xp 00000000 08:11 86278170 /sdb1/wangxiaoyuan/linux_test/DCLinkedList/OmciExec 47 <Possible Call Trace>: 48 [ 8052000] [ 804a382] [ 804a3a2] [ 804eac1] 49 [ 67eff4] [ 67d204] [ 804f2e9] [ 568e25] 50 [ 67eff4] [ 529600] [ 804f2d0] [ 552e9c] 51 [ 535ca0] [ 804f2d0] [ 552e9c] [ 536810] 52 [ 67eff4] [ 535ca0] [ 52e4f0] [ 552dcd] 53 [ 535fc0] [ 8048fe0] [ 8049001] [ 804eaae] 54 [ 804f2d0] [ 804f2c0] [ 529600] [ 53202b] 55 [ d10400] [ d10000] [ 8048034] [ 8048fe0] 56 57 End of Stack Trace<<<<<<<<<<<<<<<<<<<<<<<<<<<< 通过objdump -dS OmciExec > dump命令反汇编可执行文件OmciExec。 打开dump文件,根据<Stack Trace(Customized)>首行的返回地址和<Possible Call Trace>的堆栈内容,分析和摘取位于OmciExec内存段的地址,匹配dump文件中的指令地址(若匹配极有可能为出错代码的下条指令)。 截取部分指令片段如下: 1 VOID Func1(VOID){ 2 //SHOW_STACK(); 3 CHAR *p = NULL; 4 *p = 0; 5 804a373: c6 00 00 movb $0x0,(%eax) 6 return; 7 } 8 VOID Func2(VOID){ 9 Func1(); 10 804a37d: e8 e2 ff ff ff call 804a364 <Func1> 11 printf("%s\n", 0x123); 12 804a382: c7 44 24 04 23 01 00 movl $0x123,0x4(%esp) 13 return; 14 } 15 VOID BtrTest(VOID){ 16 Func2(); 17 804a39d: e8 d8 ff ff ff call 804a37a <Func2> 18 printf("%d\n", 5/0); 19 804a3a2: ba 05 00 00 00 mov $0x5,%edx 20 return; 21 } 22 INT32S main(VOID) 23 { 24 BtrTest(); 25 804eabc: e8 d9 b8 ff ff call 804a39a <BtrTest> 26 GlbOverrunTest(); 27 804eac1: e8 8a fe ff ff call 804e950 <GlbOverrunTest> 可见,函数调用顺序为Func1()->Func2()->BtrTest()->main()。 注意,当出错语句调用库函数时,本实现很难有效地回溯。例如,删去Func1()函数中对*p的赋值语句,执行结果如下所示: 1 Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>> 2 Process (28854) receive signal 11 3 <Signal Information>: 4 SigNo: 11(SIGSEGV) 5 ErrNo: 0 (Success) 6 SigCode: 1 7 Raised at: 0x123[Unreliable] 8 <Register Content>: 9 00000033 00000000 0000007b 0000007b 10 00000123 bfff6004 bfff5fdc bfff59bc 11 0067eff4 00579999 00000003 00000123 12 0000000e 00000004 005ad1ab 00000073 13 00010206 bfff59bc 0000007b 14 <Stack Trace(Customized)>: 15 [ 1] (/lib/libc.so.6) [0x005ad1ab] (strlen)+0x0b 16 [ 2] (/lib/libc.so.6) [0x00582e83] (_IO_printf)+0x33 17 [ 3] (./OmciExec) [0x0804a381] (<STATIC>)+0x804a381 18 [ 4] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001 19 <Current Thread Maps>: 20 003bb000-003bc000 r-xp 003bb000 00:00 0 [vdso] 21 0051b000-00535000 r-xp 00000000 fd:00 28871142 /lib/ld-2.5.so 22 00535000-00536000 r-xp 00019000 fd:00 28871142 /lib/ld-2.5.so 23 00536000-00537000 rwxp 0001a000 fd:00 28871142 /lib/ld-2.5.so 24 0053d000-0067c000 r-xp 00000000 fd:00 28871143 /lib/libc-2.5.so 25 0067c000-0067d000 --xp 0013f000 fd:00 28871143 /lib/libc-2.5.so 26 0067d000-0067f000 r-xp 0013f000 fd:00 28871143 /lib/libc-2.5.so 27 0067f000-00680000 rwxp 00141000 fd:00 28871143 /lib/libc-2.5.so 28 00680000-00683000 rwxp 00680000 00:00 0 29 00685000-006aa000 r-xp 00000000 fd:00 28871150 /lib/libm-2.5.so 30 006aa000-006ab000 r-xp 00024000 fd:00 28871150 /lib/libm-2.5.so 31 006ab000-006ac000 rwxp 00025000 fd:00 28871150 /lib/libm-2.5.so 32 006ae000-006b0000 r-xp 00000000 fd:00 28871144 /lib/libdl-2.5.so 33 006b0000-006b1000 r-xp 00001000 fd:00 28871144 /lib/libdl-2.5.so 34 006b1000-006b2000 rwxp 00002000 fd:00 28871144 /lib/libdl-2.5.so 35 006b4000-006c8000 r-xp 00000000 fd:00 28871145 /lib/libpthread-2.5.so 36 006c8000-006c9000 r-xp 00013000 fd:00 28871145 /lib/libpthread-2.5.so 37 006c9000-006ca000 rwxp 00014000 fd:00 28871145 /lib/libpthread-2.5.so 全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论