分析Linux kernel exception-基础篇

转载自MTKFAQ:

KE概念

Android OS由3层组成,最底层是kernel,上面是native bin/lib,最上层是java层:


任何软件都有可能发生异常,比如野指针,跑飞、死锁等等。

异常发生在kernel层,我们就叫它为KE(kernel exception),同理,发生在native就是NE,java层就是JE。这篇文章仅关注底层的KE。

KE类别

kernel有2中崩溃类别,

 

oops (类似assert,有机会恢复)

  • oops是美国人比较常有的口语。就是有点意外,吃惊,或突然的意思。内核行为表现为通知感兴趣模块,打印各种信息,如寄存器值,堆栈信息
  • 当出现oops时,我们就可以根据寄存器等信息调试并解决问题。
  • /proc/sys/kernel/panic_on_oops为1时导致panic。我们默认设置为1,即oops会发生panic。

panic

  • Panic – 困惑,恐慌,它表示Linux kernel遇到了一个不知道该怎么继续的情况。内核行为表现为通知感兴趣模块,死机或者重启
  • 在kernel代码里,有些代码加了错误检查,发现错误可能直接调用了panic(),并输出信息提供调试。

其实不管分类几种,都表示kernel出现故障,需要修复。那如何调试呢?就要看在发生异常时留了哪些信息帮我们定位问题了。

常用调试方法

凡是程序就有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开始吧。


kernel space

在分析KE前,你要了解kernel内存布局,才知道哪些地址用来做什么,可能会是什么问题。目前智能机已进入64bit,因此就存在32bit布局和64bit布局,下面一一讲解。

ARM32bit kernel布局

这是一张示意图(有些地址可能会有差异)




整个地址空间是4G,kernel被配置为1G,程序占3G。

任何程序都有TEXT(可执行代码),RW(数据段),ZI段(未初始化数据段),kernel也有,对应的是.text,.data,.bss。而内核代码开始的地址是0xC0008000,前面放页表(起始地址为0xC0004000),如果支持模块(*.ko)那么地址在0xBF000000。

由于kernel没办法将所有内存都映射进来,毕竟kernel自己只占1G,如果RAM超过1G,就无法全部映射。怎么办呢?只能先映射一部分了,这部分叫low memory。其他的就按需映射,VMALLOC区域就是用于按需映射的。

ARM的外设寄存器和内存一样,都统一地址编码,因此0xF0000000以上的一段空间用于映射外设寄存器,便于操作硬件模块。

0xFFFF0000是特殊地址,CPU用于存放异常向量表,kernel异常绝大部分都是CPU异常(MMU发出的abort/undef inst.等异常)。

ARM64bit kernel布局


这是39bit的kernel空间,由于多达512GB的空间,因此完全可以将整个RAM映射进来,0xFFFFFFC000000000之后就是一一映射了,就无所谓high memory了。

vmalloc还是存在,因为可以将不连续的物理内存拼接成连续的虚拟内存,可以解决部分内存碎片问题。而且外设寄存器也直接映射到vmalloc了,就没有32bit布局里的IO map space了。

modules对应的就是*.ko内核模块了。

以上是粗略的说明,还需查看代码获取完整的分析信息(内核在不停演进,有些部分可能还会变化)。


kernel log

最初学编程时,大家一定用过printf(),在kernel里有对应的函数,叫printk()。

最简单的调试方法就是用printk()印出你想知道的信息了,而前面章节讲到oops/panic时,它们就通过printk()将寄存器信息/堆栈信息打印到kernel log buffer里。

可以看到kernel log可以通过串口输出,也可以在发生oops/panic后将buffer保存成文件打包到db里,然后拿到串口log或db对kernel进行调试分析了。

通常手机会保留串口测试点,但要抓串口log一般都要拆机,比较麻烦。前面讲到可以将kernel log保存成文件打包在db里,db是什么东西?

AEE 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等等。


什么是ram console?


系统重启时关键信息

    ram console除了保持last kmsg外,还有重要的系统信息,这些非常有助于我们调试。这些信息保存在ram console的头部ram_console_buffer里。

struct ram_console_buffer
{
    uint32_t sig;
    /* for size comptible */
    uint32_t off_pl;
    uint32_t off_lpl; /* last preloader: struct reboot_reason_pl*/
    uint32_t sz_pl;
    uint32_t off_lk;
    uint32_t off_llk; /* last lk: struct reboot_reason_lk */
    uint32_t sz_lk;
    uint32_t padding[3];
    uint32_t sz_buffer;
    uint32_t off_linux; /* struct last_reboot_reason */
    uint32_t off_console;

    /* console buffer*/
    uint32_t log_start;
    uint32_t log_size;
    uint32_t sz_console;
};


这个结构体里的off_linux指向了struct last_reboot_reason,里面保存了重要的信息:

struct last_reboot_reason
{
    uint32_t fiq_step;
    uint32_t exp_type; /* 0xaeedeadX: X=1 (HWT), X=2 (KE), X=3 (nested panic) */
    uint32_t reboot_mode;

    uint32_t last_irq_enter[NR_CPUS];
    uint64_t jiffies_last_irq_enter[NR_CPUS];

    uint32_t last_irq_exit[NR_CPUS];
    uint64_t jiffies_last_irq_exit[NR_CPUS];

    uint64_t jiffies_last_sched[NR_CPUS];
    char last_sched_comm[NR_CPUS][TASK_COMM_LEN];

    uint8_t hotplug_data1[NR_CPUS], uint8_t hotplug_data2;
    uint64_t hotplug_data3;

    uint32_t mcdi_wfi, mcdi_r15, deepidle_data, sodi_data, spm_suspend_data;
    uint64_t cpu_dormant[NR_CPUS];
    uint32_t clk_data[8], suspend_debug_flag;

    uint8_t cpu_dvfs_vproc_big, cpu_dvfs_vproc_little, cpu_dvfs_oppidx, cpu_dvfs_status;

    uint8_t gpu_dvfs_vgpu, gpu_dvfs_oppidx, gpu_dvfs_status;

    uint64_t ptp_cpu_big_volt, ptp_cpu_little_volt, ptp_gpu_volt, ptp_temp;
    uint8_t ptp_status;

    uint8_t thermal_temp1, thermal_temp2, thermal_temp3, thermal_temp4, thermal_temp5;
    uint8_t thermal_status;

    void *kparams;
};



以上重要的信息在重启后将被打包到db里的SYS_REBOOT_REASON文件里。对这只文件的各个栏位解读请查看:

  • HW reboot调试信息


什么是Crash?

   当linux系统内核发生崩溃的时候,可以通过KEXEC+KDUMP等方式收集内核崩溃之前的内存,生成一个转储文件vmcore。内核开发者通过分析该vmcore文件就可以诊断出内核崩溃的原因,从而进行操作系统的代码改进。那么Crash就是一个被广泛使用的内核崩溃转储文件分析工具。

    前面讲过gdb调试方法,但gdb始终是调试native的工具,不支持kernel信息显示,比如task信息之类的。crash补足了这个短板,由Dave Anderson开发和维护的一个内存转储分析工具,是基于GDB开发的 (GDB适用于用户进程的coredump,而Crash扩展了GDB,使其适用于linux kernel coredump),目前它的最新版本是7.0.5。在没有统一标准的内存转储文件的格式的情况下,Crash工具支持众多的内存转储文件格式,包括:

  • Live linux系统
  • kdump产生的正常的和压缩的内存转储文件
  • 由makedumpfile命令生成的压缩的内存转储文件
  • 由Netdump生成的内存转储文件
  • 由Diskdump生成的内存转储文件
  • 由Kdump生成的Xen的内存转储文件
  • IBM的390/390x的内存转储文件
  • LKCD生成的内存转储文件
  • Mcore生成的内存转储文件

而我们前面讲到的SYS_COREDUMP,则可以用crash来调试。

安装/使用方法

搭建crash分析kernel ramdump平台

常用命令

crash使用gdb作为它的内部引擎,crash中的很多命令和语法都与gdb相同。如果曾经使用过gdb,就会发现crash并不是很陌生。如果想获得crash更多的命令和相关命令的详细说明,可以使用crash的内部命令help来获取:

命令说明例子
*指针的快捷方式,用于代替struct/union*page 0xc02943c0:显示0xc02943c0地址的page结构体
 files 显示已打开的所有文件的信息 files 462:显示进程462的已打开文件信息
 mach 显示与机器相关的参数信息 mach:显示CPU型号,核数,内存大小等
 sys 显示特殊系统的数据 sys config:显示CONFIG_xxx配置宏状态
 timer 无参数。按时间的先后顺序显示定时器队列的数据 timer:显示详细信息
 mod 显示已加载module的详细信息 mod:列出所有已加载module信息
 runq 显示runqueue信息 runq:显示所有runqueue里的task
 tree 显示基数树/红黑树结构 tree -t rbtree -o vmap_area.rb_node vmap_area_root:显示所有红黑树vmap_area.rb_node节点地址
 fuser 显示哪些task使用了指定的文件/socket fuser /usr/lib/libkfm.so.2.0.0:显示使用了该文件的所有进程
mount显示已挂载的文件系统信息mount:当前已挂载的文件系统信息
ipcs显示System V IPC信息ipcs:显示系统中System V IPC信息
ps显示进程状态ps:类似ps命令
struct显示结构体的具体内容struct vm_area_struct c1e44f10:显示c1e44f10结构
union显示联合体的具体内容,用法与struct一致union bdflush_param:显示bdflush_param结构
waitq列出在等待队列中的所有task。参数可以指定队列的名称、内存地址等waitq buffer_wait:显示buffer_wait等待队列信息
irq显示中断编号的所有信息irq 18:显示中断18的信息
list显示链表的内容list task_struct.p_pptr c169a000:显示c169a000地址所指task里p_pptr链表
log显示内核的日志,以时间的先后顺序排列log -m:显示kernel log
dev显示数据关联着的块设备分配,包括端口使用、内存使用及PCI设备数据dev:显示字符/块设备相关信息
sig
显示一个或者多个task的signal-handling数据
sig 8970:显示进程8970的信号处理相关信息
task显示指定内容或者进程的task_struct的内容task -x:显示当前进程task_struct等内容
swap无参数。显示已配置好的交换设备信息swap:交换设备信息
search在给定范围的用户、内核虚拟内存或者物理内存搜索值search -u deadbeef:在用户内存搜索0xdeadbeef
bt显示调用栈信息bt:显示当前调用栈
net显示各种网络相关的数据net:显示网络设备列表
vm显示task的基本虚拟内存信息vm:类似于/proc/self/maps
btop把一个16进制地址转换成它的分页号N/A
ptob该命令与btop相反,是把一个分页号转换成地址N/A
vtop显示用户或内核虚拟内存所对应的物理内存N/A
ptov该命令与vtop相反。把物理内存转换成虚拟内存N/A
pte16进制页表项转换为物理页地址和页的位设置N/A
alias显示或建立一个命令的别名alias kp kmem -p:以后用kp命令相当于kmem -p
foreach用指定的命令枚举foreach bt:显示所有进程的调用栈
repeat循环执行指定命令repeat -1 p jiffies:每个1s执行p jiffies
ascii把16进制表示的字符串转化成ascii表示的字符串ascii 62696c2f7273752f:结果为/usr/lib
set设置要显示的内容,内容一般以进程为单位,也可以设置当前crash的内部变量set -p:切换到崩溃进程的上下文环境
pprint的缩写,打印表达式的值。表达式可以为变量,也可以为结构体N/A
disdisassemble的缩写。把一个命令或者函数分解成汇编代码dis sys_signal:反汇编sys_signal函数
whatis搜索数据或者类型的信息whatis linux_binfmt:显示linux_binfmt结构体
eval计算表达式的值,及把计算结果或者值显示为16、10、8和2进制N/A
kmem显示当前kernel使用内存状况kmem -i:显示kernel使用内存状况
sym显示符号所在的虚拟地址,或虚拟地址对应的符号sym jiffies:显示jiffies地址
rd显示指定内存的内容。缺少的输出格式是十六进制输出rd -a linux_banner:显示linux_banner内容
wr根据参数指定的写内存。在定位系统出错的地方时,一般不使用该命令wr my_debug_flag 1:修改my_debug_flag值为1
gdb执行GDB原生命令gdb help:执行gdb的help命令
extend动态装载或卸载crash额外的动态链接库N/A
q退出N/A
exit同q,退出N/A
help帮助命令N/A

参考

Crash工具主页:http://people.redhat.com/anderson/



到这里,基本上对KE调试有基本的了解,剩下的就是对kernel的熟悉程度了。越熟悉,调试起来越容易,也可以根据问题对症下药。

kernel内容非常庞大,可能不知道如何下手,建议先看Unix/Linux内核相关的书籍,了解内核的经典实现方法,然后再结合源码去研究Linux内核。这样做的原因是避免从一开始就陷入细节。

内核重点关注这几个部分:进程管理及调度,内存管理,文件及文件系统,Cache,I/O,SMP(多CPU)。
参考的书籍有(最好是看英文原版):
  • 《Linux内核设计与实现》
  • 《Linux内核源代码情景分析》
  • 《深入理解Linux内核》

等等。

另外要注意,linux kernel发展很快,有些模块/结构可能被移除或没有使用了,基本就不用关注了。



  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值