极力推荐Android 开发大总结文章:欢迎收藏 程序员Android 力荐 ,Android 开发者须要的必备技能 java
Kernel Exception 在工做中偶尔会遇到,发生此异常时候常常会致使手机死机或重启。那么如何解决这些问题,本篇文章将带你从KE小白变小牛。linux
注: 本文部份内容参考MTK,若有侵权,请告知,立马关闭此文,中止侵权。知识共享,感谢支持!android
本篇文章主要介绍 Android
开发中的部分知识点,经过阅读本篇文章,您将收获如下内容:程序员
基础篇: 经过log分析KE 1.Kernel Exception 2.kernel空间布局 3.了解printk 4.ram console 5.前期异常处理 6.die()流程 7.panic()流程 8.nested panic编程
##1.Kernel Exception数组
Android OS由3层组成,最底层是kernel,上面是native bin/lib,最上层是java层: 微信
任何软件都有可能发生异常,好比野指针,跑飞、死锁等等。框架
异常发生在kernel层,咱们就叫它为KE(kernel exception),同理,发生在native就是NE,java层就是JE。这篇文章仅关注底层的KE。函数
kernel有2种崩溃类别工具
凡是程序就有bug。bug老是出如今预料以外的地方。听说世界上第一个bug是继电器式计算机中飞进一只蛾子,倒霉的飞蛾夹在继电器之间致使了计算机故障。因为这个小虫子,程序中的错误就被称为了bug。
有Bug就须要Debug,而调试是一种很个性化的工做,十我的可能有十种调试方法。但从手段上来说,大体可分为两类,在线调试 (Online Debug) 和离线调试 (Offline Debug).
####在线调试, Online debug, 指的是在程序的运行过程当中监视程序的行为,分析是否符合预期。一般会借助一些工具,如GDB和Trace32等。有时候也会借助一些硬件设备的协助,如仿真器/JTAG,可是准备环境很是困难,并且用起来也很麻烦,除非一些runtime问题须要外不多使用。
####离线调试, Offline debug, 指的是在程序的运行中收集须要的信息,在Bug发生后根据收集到的信息来分析的一种手段。一般也分为两种方式,一种是Logging,一种是Memory Dump。
Logging, 日志或者相关信息的收集,能够比较清晰的看到代码的执行过程,对于逻辑问题是一种有效的分析手段,因为其简单易操做,也是最为重要的一种分析手法。
Memory Dump, 翻译过来叫作内存转储,指的是在异常发生的时刻将内存信息所有转储到外部存储器,即将异常现场信息备份下来以供过后分析。是针对CPU执行异常的一种很是有效的分析手段。在Windows平台,程序异常发生以后能够选择启动调试器来立刻调试。在Linux平台,程序发生异常以后会转储core dump,而此coredump能够用调试器GDB来进行调试。而内核的异常也能够进行相似的转储。 下面咱们由浅入深剖析各类调试方法,先从logging开始吧。
##2.Kernel空间布局
在分析KE前,你要了解kernel内存布局,才知道哪些地址用来作什么,可能会是什么问题。
在内核空间中存在以下重要的段:
vmlinux代码/数据段: 任何程序都有TEXT(可执行代码),RW(数据段),ZI段(未初始化数据段),kernel也有,对应的是.text,.data,.bss
module区域: kernel能够支持ko(模块),所以须要一段空间用于存储代码和数据段。
vmalloc区域: kernel除了能够申请连续物理地址的内存外,还能够申请不连续的内存(虚拟地址是连续的),能够避免内存碎片化而申请不到内存。
io map区域: 留给io寄存器映射的区域,有些版本没有io map区域而是直接用vmalloc区域了。
memmap: kernel是经过page结构体描述内存的,每个页框都有对应的page结构体,而memmap就是page结构体数组。
还有其余段小的段没有列出来,可能根据不一样的版本而差异。
目前智能机已进入64bit,所以就存在32bit布局和64bit布局,下面一一讲解。
ARM64可使用多达48bit物理、虚拟地址(扩充成64bit,高位全为1或0)。对linux kernel来说,目前配置为39bit的kernel空间。
因为多达512GB的空间,所以彻底能够将整个RAM映射进来,0xFFFFFFC000000000以后就是一一映射了,就无所谓high memory了。
vmalloc区域功能除了外设寄存器也直接映射到vmalloc了,就没有32bit布局里的IO map space了。
不一样版本的kernel,布局稍有差异:
这是一张示意图(有些地址可能会有差别)
内核代码开始的地址是0xC0008000,前面放页表(起始地址为0xC0004000),若是支持模块(*.ko)那么地址在0xBF000000。
因为kernel没办法将全部内存都映射进来,毕竟kernel本身只占1G,若是RAM超过1G,就没法所有映射。怎么办呢?只能先映射一部分了,这部分叫low memory。其余的就按需映射,VMALLOC区域就是用于按需映射的。
ARM的外设寄存器和内存同样,都统一地址编码,所以0xF0000000以上的一段空间用于映射外设寄存器,便于操做硬件模块。
0xFFFF0000是特殊地址,CPU用于存放异常向量表,kernel异常绝大部分都是CPU异常(MMU发出的abort/undef inst.等异常)。
以上是粗略的说明,还需查看代码获取完整的分析信息(内核在不停演进,有些部分可能还会变化)
##3.了解printk
最初学编程时,你们必定用过printf(),在kernel里有对应的函数,叫printk()。
最简单的调试方法就是用printk()印出你想知道的信息了,而前面章节讲到oops/panic时,它们就经过printk()将寄存器信息/堆栈信息打印到kernel log buffer里。
了解kernel log对问题的调试将很是重要,这里有专门的课程介绍,请看:
MediaTek On-Line> Quick Start)> Deep in MTK Turnkey Solution Logging Tools> Kernel log。
能够看到kernel log能够经过串口输出,也能够在发生oops/panic后将buffer保存成文件打包到db里,而后拿到串口log或db对kernel进行调试分析了。
一般手机会保留串口测试点,但要抓串口log通常都要拆机,比较麻烦。前面讲到能够将kernel log保存成文件打包在db里,db是什么东西?
db是叫AEE(Android Exception Engine,集成在Mediatek手机软件里)的模块检查到异常并收集异常信息生成的文件,里面包含调试所需的log等关键信息。db有点像飞机的黑匣子。
对于KE来讲,db里包含了以下文件(db能够经过GAT工具解开,请参考附录里的FAQ):
__exp_main.txt:异常类型,调用栈等关键信息。
_exp_detail.txt:详细异常信息
SYS_ANDROID_LOG:android main log
SYS_KERNEL_LOG:kernel log
SYS_LAST_KMSG:上次重启前的kernel log
SYS_MINI_RDUMP:相似coredump,能够用gdb/trace32调试
SYS_REBOOT_REASON:重启时的硬件记录的信息。
SYS_VERSION_INFO:kernel版本,用于和vmlinux对比,只有匹配的vmlinux才能用于分析这个异常。
SYS_WDT_LOG:看门狗复位信息
以上这些文件通常足以调试KE了,除非一些特别的问题须要其余信息,好比串口log等等。
##4.ram console 什么是ram console?
请参考: MediaTek On-Line> Quick Start> Deep in MTK Turnkey Solution Logging Tools
ram console除了保持last kmsg外,还有重要的系统信息,这些很是有助于咱们调试。这些信息保存在ram console的头部ram_console_buffer里。
MediaTek On-Line> Quick Start> 深刻分析看门狗框架> 分析方法> HW reboot> HW reboot调试信息
##5.前期异常处理
CPU异常捕获
对于野指针、跑飞之类的异常会被MMU拦截并报告给CPU,这一系列都是硬件行为,具体请看:
这类问题比较难定位,也是占KE比例的大头,缘由一般是内存被踩坏、指针use atfer free等多种因素,在当时可能不会当即出现异常,而是到使用这块内存才有可能崩溃。
分析问题的手段也是多样化,好比用watch point,MMU protect或加debug code等(请参考附录FAQ)
软件异常捕获
在kernel代码里,通常会经过BUG(),BUG_ON(),panic()来拦截超出预期的行为,这是软件主动回报异常的功能。
这些问题分析一般有固定的套路,请参考后面的:《实例篇: 案例分析》
在内核调用能够用来方便标记bug,提供断言并输出信息。最经常使用的两个是BUG()和BUG_ON()。当被调用的时候,它们会引起oops,致使栈的回溯和错误信息的打印。使用方式以下
if (condition) BUG(); 或者 : BUG_ON(condition); //只是在BUG基础上多层封存而已: #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)
BUG() 的实现采用了埋入未定义指令(0xE7F001F2,记住这个值,log里看到这个值,你就应该知道是调用了BUG()/BUG_ON()了)的方式
原生的kernel,BUG()是直接调用panic()的:
不过Mediatek修改了BUG()的实现,这样有更多的调试信息输出(die()有寄存器等信息输出)
当你看到以下log时,就应该知道是BUG()/BUG_ON()引发的了!
[ 147.234926]<0>-(0)[122:kworker/u8:3]Unable to handle kernel paging request at virtual address 0000dead
##6.die()流程 通过前面的流程,走到了die()函数,该函数主要输出便于调试的寄存器信息/堆栈信息等重要资料,咱们经过log分析KE就是分析这些资料,所以要知道整个流程。die() => panic()的大体流程以下:
在学习这些流程时,建议结合代码和KE的log一块儿看,你就知道log里那些信息在代码哪处打印出来的了。
先从die()入手,看下die()总流程:
若是这个异常是代码里调用BUG()/BUG_ON()引发,那么有额外log说明
绝大部分的关键信息是由__die()函数输出的,流程以下:
开始印出异常类型等信息,看一份kernel log有没有oops,直接搜索关键字Internal error就能够了:
接下来是module信息,不过咱们不建议使用module,这边也不打算介绍了。
而后是重要的CPU寄存器信息(32bit的代码,64bit类同):
有助于咱们分析问题的内存信息,问题极可能就出在里面。
输出的信息大体以下:
有时问题能够直接从调用栈看出来,因而可知调用栈是多么重要。
输出的信息大体以下:
PC附近指令
能够看到PC附近的指令:
输出的信息大体以下:
分析log
到这里die()函数就完成了它的使命,将重要信息输出来了。接下来你要如何调试呢?这个就看我的的功力了,你能够:
##7.panic()流程
流程走到panic()就里死(异常重启)不远了,关键的信息已输出到kernel log。那么panic()作了什么呢?
所以咱们也能够经过搜索关键字Kernel panic查找是否有panic发生。
panic()会调用栈通知链上的回调函数同时感兴趣的模块,好比咱们的aee注册了回调函数,用于保存kernel log/mini dump等关键信息,并将其保存到emmc的expdb分区,等等重启后将其回读并保存成KE db。
重启过程DRAM会丢失,所以信息只能保存在flash上了,在分区表里有一项就是expdb了:
##8.nested panic 有时die()/panic()流程不必定能正常走完,可能走到某一步又发生了异常,则就造成了嵌套,这种状况,咱们通常不会关注后面的异常,而是关注最开始的那个异常。
为了不异常嵌套,在发生第2次异常时,咱们就拦截下来,咱们在3个地方用于拦截nested panic:
不过nested panic能参考的信息不多,不像普通的KE那样丰富。
至此,本篇已结束,若有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!
若有侵权,请联系小编,小编对此深感抱歉,届时小编会删除文章,当即中止侵权行为,请您多多包涵。
既然都看到这里,领两个红包在走吧! 如下两个红包天天均可以领取
1.支付宝搜索 522398497,或扫码支付宝红包海报。
2.微信红包,微信扫一扫便可领取红包