应用层打印PC指针LR指针解决段错误总结

6 篇文章 0 订阅
3 篇文章 1 订阅

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编译选项;

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DBX260中文说明书,DBX260的使用(一) 1.1 后面板连接(260) IEC电源线插座 260采用电压范围为100V-240V,频率为50-60Hz的国际性电源来供电,它使用的是IEC电缆线。 MIDI输入,MIDI输出和转接接口 这些接口为260 DriveRackÔ提供了MIDI功能,输入输出和转接插孔可让260 DriveRackÔ接在 MIDI链路中的任意处。 RS485控制母线输入(DB-9接口型) 该输入网络连接用来接收在 DriveRackÔ网络链中其它单元送来的信息。 RS485控制转 接母线(DB-9接口型) 该转接网络连接用于转接 DriveRackÔ网络链中其它单元来的信息。 RS485控制母线输入(RJ-45接口型) 该输入网络连接用来接收来自 DriveRackÔ网络链中其它单元的信息。 RS485控制转换母线(RJ-45接口型) 该转接网络连接用来转接 DriveRackÔ网络链中其它单元的信息。 端接LED 这些LED在网络被正确端接时会加以表示。绿色LED表示网络已经被正确端接。 遥控器输入连接 该DB-9型输入连接用来从260R遥控单元送出和接收信息。 PC连接 该DB-9型连接用来向GUI接口,或从GUI接口送出和接收信息。 输出1-6 260 DriveRackÔ的输出部分有6个电子平衡式XLR接口。 输入1-2 260 DriveRackÔ的输入部分有2个电子平衡XLR接口。并提供线路/RTA开关,可让用户将进行实时声频分析话筒直接接到260 DriveRackÔ的输入上,260 DriveRackÔ的2个XLR输 入还有一个脚1浮地开关,当它按下时所选的XLR输入对的地浮起。 忠告:要想正确使用RTA话筒,必须要按下RTA按钮,并且将接地/浮地开关置于接地位置。当后面板的RTA按钮按下时,在XLR接口的2和3脚上加上48V幻象电源。要维持幻象电源有正确的接地回路,接地/浮地开关必须处在接地位置上。这样可避免电击的潜在危险。 1.2 前面板(260) LCD显示 260 DriveRackÔ的LCD显示为用户提供了DriveRackÔ全部的重要处理信息,其中包括:信号路由分配,配置方式,效果块编辑和RTA显示。显示的左上角表示的是网络设备的ID号码。反白的数字表示的是受控从机的号码,而Mst表示单元是作为主机工作的。 功能按钮 260 DriveRackÔ的功能按钮允许访问260 DriveRackÔ的所有编辑和导航功能。 输入仪表 260 DriveRackÔ为用户提供了2个独立的12段LightpipeÔ输入仪表,其量程范围为-30~ +20dBu。注意:这些仪表可以被校准,对应于增益跳线器的+22dBu设定。 阈值仪表 阈值仪表表示的阈值电平已经超出了动态部分的阈值(压缩器/限制器),以及在特定的输出信道上的增益下降量。 输出仪表 260 DriveRackÔ为用户提供了6个独立的12段LightpipeÔ输出仪表,其量程范围为-30~ +22dBu。注:这些仪表可校准成对应增益跳线器的+22dBu设定。 输出哑音 6个输出哑音按钮分别用来哑掉260 DriveRackÔ的6个输出的每一个。 电源开关 用来开闭260 DriveRackÔ。注:dbx专业产品推荐接至 DriveRackÔ的功率放大器,应该在环接 DriveRackÔ之前将功率降低下来。 DBX260的使用(二) 2.1 基本导航方法 260 DriveRack的导航部分清晰、简便,更重要的是具有更大的自由度。当进行程序编辑时,DriveRack可以提供3种不同的基本导航方法。1.FX钮。¾这个由12个FX钮组成的数组是找任何效果模式的首选方法。2.下一页(NEXTPG)及上一页(PREVPG)钮-按动NEXTPG或PREVPG钮可以在一个效果栏中进行翻页。3. Data Wheel数据轮,用来移动DriveRackTM 260的全部程序菜单。数据轮还用来改变选择参数的值。按下数据轮触发当前被选效果模块任何页上可得到的参数。 2.2 FX钮数组的概况 在接下来的部分里将介绍260 DriveRack FX 钮精确导航功能的具体内容。每一个图指示的是每一个FX 钮的功能,及其在每个操作菜单中指导用户的能力。 上一页(PREVIOUS 260GE)-翻至当前所选效果菜单的上一页。 下一页(NEXT 260GE)-翻至当前所有选效果菜单的下一页。 EQ-选择EQ效果菜单。按动此钮将在各种EQ模式中滚动。 XOVER-选择分频菜单。按动它将在各种分频

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值