1.需求的产生 写程序难免会出现段错误的情况,这时候很想知道,到底在什么地方崩溃了,对于代码很少,或者你很有把握的时候,或许用二分法配合printf就可以搞定了;而对于非常复杂的代码,比如像Xserver这样的程序,可能就不太好定位了; (本文讨论的情况都是针对arm环境,并且gdb不方便使用的情况) 2. 解决思路 思路其实很简单,对于用户态段错误的原因,大约可以分为两种, a) 没有权限访问这个地址; b) 访问的地址没有映射,比如NULL地址; 当出现这两种情况的时候,linux内核都会向用户态的程序发送SIGSEGV的信号,于是程序执行默认的信号处理函数,就退出了; 所以有两个解决办法: A) 在内核里面把这些寄存器打印出来; B) 在上层程序里面把寄存器打印出来; 下面来分别说明: 3. 内核信息打印 内核的执行路径如下: 我们只需要在__do_user_fault的时候把打印信息打开就可以了,如下: #ifdef CONFIG_DEBUG_USER if (user_debug & UDBG_SEGV) { printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); show_pte(tsk->mm, addr); show_regs(regs); } #endif 改成 printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); show_pte(tsk->mm, addr); show_regs(regs); 里面会打印出pc寄存器的值,有了这个就可以定位了。 4. 用户态信息打印 这个做法的主要思路就是先拦截SIGSEGV信号,然后在信号处理函数里面打印信息,信号拦截代码如下: static void catch_sigsegv() { struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_sigaction = sigsegv_handler; action.sa_flags = SA_SIGINFO; if(sigaction(SIGSEGV, &action, NULL) < 0) { perror("sigaction"); } } 只需要在main函数里面加入这个函数就可以了: main(…) { …. catch_sigsegv(); … } 下面来看看这个处理函数sigsegv_handler是怎么写的,代码如下: static void sigsegv_handler(int signum, siginfo_t* info, void*ptr) { static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"}; int i; ucontext_t *ucontext = (ucontext_t*)ptr; void *bt[100]; char **strings; printf("Segmentation Fault Trace:\n"); printf("info.si_signo = %d\n", signum); printf("info.si_errno = %d\n", info->si_errno); printf("info.si_code = %d (%s)\n", info->si_code, si_codes[info->si_code]); printf("info.si_addr = %p\n", info->si_addr); /*for arm*/ printf("the arm_fp 0x%3x\n",ucontext->uc_mcontext.arm_fp); printf("the arm_ip 0x%3x\n",ucontext->uc_mcontext.arm_ip); printf("the arm_sp 0x%3x\n",ucontext->uc_mcontext.arm_sp); printf("the arm_lr 0x%3x\n",ucontext->uc_mcontext.arm_lr); printf("the arm_pc 0x%3x\n",ucontext->uc_mcontext.arm_pc); printf("the arm_cpsr 0x%3x\n",ucontext->uc_mcontext.arm_cpsr); printf("the falut_address 0x%3x\n",ucontext->uc_mcontext.fault_address); **/*backtrace函数有的系统不支持,如hisi平台/FH平台等不支持*/** printf("Stack trace (non-dedicated):"); int sz = backtrace(bt, 20); printf("the stack trace is %d\n",sz); strings = backtrace_symbols(bt, sz); for(i = 0; i < sz; ++i) { printf("%s\n", strings[i]); } _exit (-1); } 测试代码如下: void test_segv() { char *i=0; *i=10;//产生段错误的位置 } void cause_segv() { printf("this is the cause_segv\n"); test_segv();//调用函数 } int main(int argc,char **argv) { catch_sigsegv();//初始化注册捕捉函数 cause_segv(); return 0; } 编译方法: gcc segment_trace.c -g –rdynamic –o segment_trace 执行: ./segment_trace 输出如下: this is the catch_sigsegv Segmentation Fault Trace: info.si_signo = 11 info.si_errno = 0 info.si_code = 1 (SEGV_MAPERR) info.si_addr = (nil) the arm_fp 0xb7f8a3d4 the arm_ip 0xb7f8a3d8 the arm_sp 0xb7f8a3c0 the arm_lr 0x8998 the arm_pc 0x8974 the arm_cpsr 0x60000010 the falut_address 0x 0 Stack trace (non-dedicated):the stack trace is 5 ./segment_trace(backtrace_symbols+0x1c8) [0x8844] /lib/libc.so.6(__default_rt_sa_restorer+0) [0xb5e22230] ./segment_trace(cause_segv+0x18) [0x8998] ./segment_trace(main+0x20) [0x89c0] /lib/libc.so.6(__libc_start_main+0x108) [0xb5e0c10c] 5. 输出信息分析 根据上面的输出可以看出一些端倪: 根据栈信息,可以看出是在cause_segv里面出了问题,但是最后一层栈信息是看不到的,另外需要根据pc寄存器的值来定位: addr2line -f -e segment_trace 0x8974 test_segv /home/wf/test/segment_trace.c:55 可以看到说是在55行,一看: 刚好是 *i=10; 这一行, 而且可以看出,函数名是test_segv, 所以基本上不需要打印栈信息,也可以定位了。 6. 注意 这个方法最好不要用在多线程环境里面; 如果打印不出栈信息,需要在内核中去掉: -fomit-frame-pointer编译选项; ———————————————— 版权声明:本文为CSDN博主「sdsszk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/sdsszk/article/details/109765180