• 嵌入式系统coredump设计


    阴沟翻船,马失前蹄,说明凡事皆有可能。自然,程序设计的再好,也会有crash的时候。开发期还还说,正式交付的系统crash自然更是难以承受的。无论何时,死一次就够了,得有方法查个水落石出。

    几年前哥去广州的一家民企呆过些日子。刚到那,就碰上系统毫无线索的crash。咋办?哥想静下心来,花点时间做个工具去定位,但无奈硬件出生的领导天天赶着大家守在机房。唉,无知啊,天天守在机房,面对crash,哥想到的只有我儿子常常念的诗--来如春梦不多时,去如朝霞无觅处。嗯,crash,哥只能数数又crash了几次。

    再后来,哥被请去了另外家公司,挂了个闲职,要求就一个,帮忙解决嵌入式系统开发碰到的,解决不了的问题。在那还是蛮幸福,因为哥那些日子确实需要时间,大把大把时间,来照顾家人。

    这当中,就碰到三个最典型的问题:memory leak, system crash, system halt。内存泄漏以后再讲,今天只说crash,当然,halt跟crash的解决方法有很大重叠,明天有空就简单的说一说。

    首先,简单定义下讨论对象:嵌入式操作系统,使用ucos,vxworks,nucleus等操作系统。Linux自带coredump功能,不在讨论范围。

    接下来,普及下CPU的寄存器知识。以ARM为例,处理器存在几个不同的寄存器组,对应不同的模式:用户模式,中断模式,数据访问终止模式,未定义指令模式undef等。系统crash,除去硬件原因(比如电不足等),都是先触发异常:数据异常,取指异常,或者未定义异常。未定义异常一般来说是没有的,除非故意改掉指令的opcode,比如野指针,咱不谈它,那太难了!

    有兴趣了解更多,可以读一读ARM的文档:
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344f/Beiibjca.html

    当Data aboat或是Prefetch abort出现后,CPU即切换到数据访问终止模式。我们要做的,就是抢在系统开始处理前,把用户寄存器信息保存下来。以及abort模式下LR寄存器内容。接下来,把控制权还给系统的正常流程。当然也不是绝对,哥碰到的系统,总是可以在异常里挂coredump处理函数。万一系统没给你留这个钩子,那就麻烦些,需要修改cpsr到用户模式,调用coredump处理函数;完成后,再返回abort模式,将处理权交还系统。当然,你不想交权也可以,coredump完了,就直接复位CPU。反正你该收集的信息都收集了,系统接下来的异常处理也没什么价值。

    Coredump处理函数做的事,也很简单,首先确定当前运行的task和tcb。操作系统一定会提供这个全局变量,或是获取接口。然后,就是把堆栈信息保存起来。很多时候,堆栈会很大,很难保存所有数据。这时,就有几个策略:遍历堆栈,找出所有函数调用;提取栈顶的一段数据。其中原理等下再讲。

    系统复位了,刚刚存的数据还在么?当然在,我们用SRAM来保存这些数据。CPU复位但不掉电的情况下,SRAM数据会完整的保留着。不过,系统要带SRAM。当然,没SRAM也可以,只不过做起来有点难度,需要flash驱动做些特别支持。数据需要先保存到内存中,然后在coredump处理函数中写入flash。写flash需要时间,因此要记得喂狗和写flash两不误;还有就是flash一般不能是文件系统,应该是直接的地址映射。flash的驱动必须配这种特俗的写操作。

    然后,系统重新启动运行。这时候,我们先赶紧把刚才存在SRAM或是flash的数据复制出来,写入到coredump文件,免得夜长梦多。这个过程就很简单了!

    接下来,解释几个技术问题。

    怎么遍历堆栈,找到所有函数调用?
    很简单。用readelf,objdump一类工具打开编译出来的软件包,你就能发现,函数地址总是在某个区间内。如果是动态加载的系统,则会提供代码段的区间范围。提前找到这个范围就好了,硬编码在你的程序里,且不需要精确,多一点没关系,毕竟相对于32位系统的地址空间来说,这段地址占的比例总是可以忽略不计,误抓取的信息也就非常有限。记得,宁可错杀一千一万!

    为什么要保存栈顶数据?
    一般来说,最后的一些列操作有更大的嫌疑。从分析角度来说,较远位置的错误,堆栈数据分析的难度非常大。其实,就经验来看,只要能找到函数调用的层次关系,再加上代码走读,基本上都能确定故障原因。堆栈数据分析,也就是不得已才做的,尤其是面对编译使用了优化选项的故障。

    如何使用coredump文件?
    coredump文件包含三个内容:寄存器信息,函数调用信息,堆栈数据。函数调用信息可以在线翻译,只要利用cshell里的符号表就可以直接查询那些地址,并翻译成可读的函数名。当然,最好的办法是离线处理。这里需要明确,编译时必须带-g参数用以产生addr2line需要的符号表内容。正式发布的软件包可以额外使用strip方法去掉符号表,但需要同时保存带符号表的原始编译软件包和发布软件包。利用addr2line对coredump保存的疑似地址检查,可以梳理出完整的系统调用层次。记得用Perl,或是Python等写个脚本来做这个!

    很遗憾,哥找了半天,也没找到以前做的那段搞寄存器的汇编代码段,也没那段异常处理的函数代码,更也没找打coredump分析的perl脚本。早知道就该在给上家做这个的时候,偷偷复制一份供日后参考的。当然,也没什么,在弄清楚具体系统的工作原理后,相关实现估计只要一两个工作日来完成,只是没SRAM的方式,蛮复杂的,需要时间也多点,尤其测试会麻烦很多。

  • 相关阅读:
    随机森林算法参数调优
    BAYES和朴素BAYES
    阿里云 金融接口 token PHP
    PHP mysql 按时间分组 表格table 跨度 rowspan
    MySql按周,按月,按日分组统计数据
    PHP 获取今日、昨日、本周、上周、本月的等等常用的起始时间戳和结束时间戳的时间处理类
    thinkphp5 tp5 会话控制 session 登录 退出 检查检验登录 判断是否应该跳转到上次url
    微信 模板消息
    php 腾讯 地图 api 计算 坐标 两点 距离 微信 网页 WebService API
    php添加http头禁止浏览器缓存
  • 原文地址:https://www.cnblogs.com/hhao020/p/5013595.html
Copyright © 2020-2023  润新知