一、为什么要用crash

在手机领域常用trace32或crash来解析内核dump,选择适宜的工具可以大大提高问题定位的效率,但有时候我们不得不该用crash,例如下面场景:

php中dump怎么写crash实战手把手教你应用crash剖析内核dump React

trace32太占用资源

在手机领域中,当机器崩溃时常日会将全体内存现场全部转储下来,利用trace32工具剖析这些转储信息时,首先须要将这些转储文件全部加载到本地PC真个内存中。
目前市情上手机的内存容量一样平常为16GB或24GB,利用trace32来剖析这些dump文件对PC机器的性能哀求就很高了。
下图展示了在一个32GB内存的PC上加载一个16GB手机产生的dump文件时的内存利用情形。
此时,trace32占用了约17GB的内存,加上系统本身利用的一些软件占用的资源,险些耗尽了PC的所有内存资源。
因此PC真个性能变得非常拉胯,变得巨卡无比,这时候也险些不可能同时启动多个trace32终端。

而crash工具的事情事理并不哀求将全体dump文件全部加载到内存中。
它利用“用时读取”的技能,即只有在须要利用某一段dump文件时才将该段加载到内存中,因此对内存的花费较小。
笔者的crash环境搭建在一个6GB内存的VMware虚拟机中,在剖析16GB的dump文件时,常日可以同时启动3至4个终端。
因此,在须要剖析大型dump文件时,考虑到PC真个性能,crash工具可能是一个最佳的选择,特殊是在资源受限的环境中。

trace32可能看不到调用

在利用trace32解析dump时,有时候纵然是已经把崩溃现场的各个cpu寄存器全部规复,也可能会涌现看不到任务调用栈的情形,如下图所示。
这时候可以通过crash手动还原任务的调用栈,详见本文第四章内容。

trace32对变量的解析可能会失落败

trace32对局部变量的解析可能会失落败,要么是完备没有对要关注的局部变量进行解析,要么是对局部变量的解析是缺点的。
如下图所示,在multi_cpu_stop这一层调用栈中,完备没有对newstate和flags变量进行解析,并且对msdata变量的解析出的值是缺点的。
此时可以通过crash工具从函数的栈帧中规复局部变量的值,详情请拜会本文第五章内容。

在面对上面的问题时,就不得不该用crash工具来解析dump了。

二、dump文件加载

本文利用的工具源码和插件信息如下:

源码: https://github.com/crash-utility/crash

插件: https://crash-utility.github.io/extensions.html

本文紧张先容crash在手机领域中的运用,目前海内安卓手机紧张以MTK和高通为主,这两个平台的dump转储文件格式不同,利用crash加载的办法也不同。

MTK平台dump加载命令如下:

高通平台dump加载命令如下:

MTK平台的加载命令很大略,这里大略先容一下高通平台的加载命令中各个部分的含义,我们把命令大致分为下面3个部分。

第一部分:指定转储文件对应的物理地址

Kdump在转储文件时,会把物理内存中的所有内容保存为对应的二进制文件,在通过crash加载dump文件时,须要见告crash某个bin文件对应的物理内存是在哪里。

这段命令指示了不同的转储文件对应的物理内存的地址,信息来源于dump_info.txt,该文件在dump导出的时候就天生,你也可以通过\公众grep DDRCS dump_info.txt -nr\"大众命令来快速获取。

第二部分:指定kaslr

KASLR是kernel address space layout randomization的缩写,即内核地址空间布局随机化,作为Linux内核的一个主要安全机制,它旨在防止黑客对内核数据进行修正。
KASLR技能可以让kernel运行的地址和vmlinux中的链接地址有个偏移量,并且每次reboot后该偏移量都不一样,使得内核加载和运行的地址每次都不同。
因此,在利用crash进行dump剖析时,我们须要获取相应的kimage_voffset偏移量,并将其通报给crash工具进行解析。

kaslr值保存在OCIMEM.BIN中,利用下面的命令可以从OCIMEM.BIN中快速提取出kaslr,直接关注4ead dead这一行,后面的0000 21a0 0028 0000即为kaslr。
须要把稳的是,由于arm采取little-endian,因此须要倒序读取,拼接后kaslr为:0x0000002821a00000

第三部分:打算vabits_actual

在64位系统中,用64位地址线来描述一个虚拟地址空间,但是实际上并不是每一个bit都有效的,vabits_actual就像是一个mask,用于表示这64位地址中有多少位是有效的。

虚拟地址有效位数常常是在记录的内核配置项中,可利用利用内核源码自带的extract-ikconfig工具直接从vmlinux中解析出CONFIG_ARM64_VA_BITS的值,命令格式如下:

在本例中,vabits_actual为39

高通平台打算完上面的参数后,便可启动crash加载dump文件了。
下面示例中笔者已经把命令写进crash.sh脚本,这样就不用每次都打算一次了。

三、根本信息搜集

如果你是新手的话,dump加载起来之后可能也是一头雾水,不知道从何处开始剖析,没有打破口。
这时候可以考试测验搜集下面根本信息,在搜集的过程中说不定你就找到打破口了。

系统信息查询;

内存利用情形;

中断状态;

runq上各个线程韶光;

dmesg信息;

trace信息;

3.1 系统信息查询

命令如下:

示例如下,通过该命令你可以获取下面信息

cpu的个数;

已经开机多永劫光了;

内核版本;

内存大小;

panic的大致缘故原由;

3.2 内存利用情形

命令如下:

示例如下,通过该命令可以确定问题产生时的内存利用情形,是否处于低内存的场景下。

3.3 中断状态

命令如下:

示例如下,确定各个cpu上中断产生的情形。
笔者之前就碰着过一例,由于外设非常造成的中断风暴问题,终极导致这个cpu频繁的处理中断,cpu的runqueue上的线程永劫光获取不到cpu资源而终极触发hungtask进dump的问题。
以是作为根本信息,先瞥一眼各个cpu上的中断处理情形还是很有必要的。

3.4 runq上各个线程韶光

命令如下:

示例如下,通过该命令可以获取到各个cpu上正在运行的线程,以及运行了的韶光。
须要把稳的是:对付非idle线程:这里获取到的运行韶光是指这个线程在本次运行机遇中的已经运行了多永劫光,当线程进入就寝或者壅塞状态从cpu的runqueue摘除时,这个韶光会清零。
对付idle线程:这里获取到的韶光表示系统已经开机运行了多永劫光。
详见请拜会内核关于sched_info的统计信息。

如下图所示,可以确定机器进dump时各个cpu上正在运行的线程是什么,并且可知cpu4上的migration/4线程运行韶光是最长的,共运行了4秒,那么这个线程很可能是触发dump的罪魁罪魁,后面在剖析的过程中须要重点关注。

下面示例中cpu3~cpu7上的idle线程运行韶光为20小时44分41秒,表示系统已经开机这么永劫光了。
另一方面,我们看到migration/0线程已经在cpu上19.867秒了,要知道调度一样平常因此ms级别为单位的,而这里连续运行19+秒,肯定是存在问题了,在后面的剖析过程中也要重点关注。

3.5 dmesg信息

命令如下:

示例如下,这没什么好阐明的,dmesg用于查看内核日志,它可以显示一些与系统运行干系的主要信息,包括崩溃时的一些关键信息。
笔者一样平常喜好搭配tail命令或者直接通过\公众>\"大众将其直接重定向到指定的文件中后再查看。

3.6 trace信息

命令如下,利用trace命令须要先安装trace.so插件,下面第一句的浸染是先安装插件,第二句才是查看trace信息。

示例如下,通过该命令可以查看设备触发dump时的trace信息,但是这依赖于设备在进dump时trace-event系统正在往表面吐数据的,如果彼时trace-event根本就没有使能的话,也就没有trace信息输出了。

四、查看任务的调用栈信息

如果一个任务处于壅塞、就寝状态,或者是处于runnable状态(在cpu的runqueue上等待运行,但是还没有获取到cpu资源),这类任务可以直接通过bt命令查看任务的调用栈,通过空格分隔多个pid可一次查看多个线程的调用栈。

注:crash命令基本用法不是本文的重点信息,如果你对crash命令的利用方法以及各种参数含义感兴趣,可以参考本文末了的参考文档。

但是对付正在cpu上running的任务的调用栈该当怎么查看呢?如下图所示,这时候直策应用bt命令无法查看任务的调用栈,提示找不到任务的栈帧信息。

对付正在running的任务,有下面三种方法可以规复其调用栈,下面依次先容。

4.1 方法一:通过cpu_context中获取sp的值

首先通过下面命令从任务的高下文中读出崩溃时sp指针。
下面命令中54为线程pid,你可以更换为任何必要查看的任务,多个任务pid用空格隔开。

然后通过bt -S指定sp地址,这样就能查看任务的调用栈了。

把稳,下面传给bt的地址实际上是sp+8,缘故原由在本文4.3章节阐明。

须要把稳的是,该方法依赖于内核崩溃时已经将真正running的高下文保存进task_struct::thread::cpu_context中去,一样平常只有那些由于去世锁或者永劫光获取不到资源而触发看门狗的dump才会对高下文进行保存。
以是该方法不一定每次都能成功。

另一方面,由于每个任务都有自己的任务栈,而中断栈则是percpu维度的,是这个cpu上的所有任务共享的,以是通过该方法无法看到中断栈中的内容。

4.2 方法二:从解析好的cmm中获取sp_el1的值

该方法须要依赖于芯片厂商的供应的dump解析工具。
当系统触发dump时会将各个cpu的高下文信息(即各个寄存器的值)保存进dump文件的实行位置,芯片厂商供应的解析工具可以从dump文件中解析出系统奔溃时候各个cpu的现场,并保存进一个cmm脚本中。
例如MTK对应的脚本名称为debug.cmm,高通平台对应的脚本名称为corevcpuN_regs.cmm。
下图以高通为例,在corevcpu6_regs.cmm中记录了sp_el1的值。
然后通过bt -S命令指定栈帧地址即可查看任务的调用栈。

命令实行效果如下:

同理,该方法同样存不才面两个缺陷:

一方面,该方法严重依赖厂商供应的解析工具,并且有时候并不一定能完全的解析出dump的内容,可能根本获取不到cmm脚本;

另一方面,利用该方法只能看到任务栈的内容,无法看到中断栈内容,缘故原由同上,不再赘述。

4.3 方法三:手动规复栈帧信息

上面两个方法有一个共同的缺陷,那便是看不到中断栈中的信息,并且对工具或者崩溃时的环境又有哀求,以是并不是每次都能成功规复出调用栈。
以是上述两个方案不是本文重点,理解即可。
本文重点是要先容下面的方法,直接在任务栈或中断栈中找出最顶层的栈帧,然夹帐动规复各层调用栈信息。
该方法对工具或者奔溃时的环境无任何哀求,成功率极高(笔者至今没有碰着过规复失落败的情形),强烈建议get该技能。

在先容本方法之前我们还须要科普一些打算机中的八股文信息。

4.3.1 ATPCS规则

首先是函数调用时各个寄存器的利用规则,下表是笔者罗列ARM64平台中的一些常用规则,更多详细详细大家可以google一下ATPCS(ARM-Thumb Produce Call Standard)规则,或者参考本文后面的参考文档,此处不再赘述。

4.3.2 栈帧格式

其次是函数调用时的栈帧中的内容,在ARM64平台中,函数栈帧中的内容如下:

本函数用到的局部变量;

本函数即将毁坏的“所有”寄存器(X19~X28);

本函数实行完毕后要返回到哪里LR(X30);

父函数的栈帧起始地址FP(X29);

举个栗子,以下面程序为例:

各级函数栈帧中的内容如下,个中:

FP(X29)指向上一个函数调用的栈帧的结束地址;

LR(X30)保存在FP+8的位置(这也是为什么上面在利用bt -S命令时要加8的缘故原由),指向本级函数返回后要实行的下一条指令的地址;

上面的示例程序只是空想的情形,函数沿着main -> func1 -> func2 -> func3的路径一贯调用下去,返回时沿着func3 -> func2 -> func1 -> main的方向原路返回,中间没有“分叉”,此时任务栈中的栈帧信息还是比较简洁的。

但是实际情形中函数之间的调用关系每每错综繁芜,如下,在func1返回之后又急速调用和其他函数func4,那么此时的任务栈帧中的内容又该当是怎么的呢?

示例程序如下,假设当前PC指针在fun4内部。

这时候栈帧中的内容取决于func4这一层栈帧的大小,分下面三种情形:

场景1:func4的栈帧和func1的栈帧完备重叠

这种情形下func2的栈帧中的FP在func4调用前后依然指向同一个地址,并且在func4调用前后该地址中保存的内容都表示FP,并且他俩的值是一样的。
在func4调用之前,该地址保存的是func1的FP,指向main函数的栈帧的起始地址;在func4调用之后,该地址保存的是func4的FP,也指向main函数的栈帧的起始地址。
这时候我们从最底层栈帧(即main函数对应的栈帧)沿着图中的赤色箭头反方向回溯时,得到的调用栈内容该当是main -> func4 -> func2 -> func3。
实际上由于当前PC指针是在func4中,以是实际的调用栈该当是main -> func4,只有后面的func2 -> func3只是历史实行过程中留下的“影子”,这紧张是由于栈的“退出不清空”的特性造成的。
碰着这种情形时,须要你对代码的逻辑比较熟习,或者结合任务cpu_context或解析好的cmm脚本中记录的PC或SP指针,从回溯出的调用栈中精准的判断出哪些是真正的调用栈,哪些是残留的影子,本文4.3.4章节有一个很好的示例。
好在完备重叠这种情形是小概率事宜,正常情形下都是后面将先容的两种场景。

然而,事物总是有两面性的,虽然这个“影子”具有一定的迷惑性,但是由于从这个“影子”中可以推断出代码在过去的韶光里实行过哪些逻辑,这对付哪些系统崩溃时Kdump没有抓到第一现场的情形下切实其实便是一根救命稻草,后面有机会笔者会补充一篇利用栈帧中的“影子”剖析奔溃问题的一篇案例,本文我们还是先节制常规操作吧。

场景二:func4的栈帧小于func1的栈帧

这种情形下func2的栈帧中的FP在func4调用前后依然指向同一个地址,在func4调用之前,该地址保存的是func1的FP,指向main函数的栈帧的起始地址;由于func4的栈帧小于func1的栈帧,以是在func4调用之后并不会覆盖该地址的内容,该地址依然指向main函数的栈帧的起始地址,如下面图中赤色虚线部分。
这时候我们从最底层栈帧(即main函数对应的栈帧)沿着图中的赤色箭头反方向回溯时,会得到下面两条调用栈,分别是:

a) 调用栈1:main -> func4

b) 调用栈2:main -> func1 -> func2 -> func3

到底那一条才是真正的调用栈呢?这时候须要结合代码逻辑、任务cpu_context或解析好的cmm脚本中记录的PC或SP指针、报错时dmesg中的信息,末了不难找出真正的调用栈。

场景三:func4的栈帧大于func1的栈帧

这种情形下func2的栈帧中的FP在func4调用前后依然指向同一个地址,在func4调用之前,该地址保存的是func1的FP,指向main函数的栈帧的起始地址;但是由于func4的栈帧大于func1的栈帧,以是在func4调用之后该地址的内容会被覆盖,有可能保存的是func4中某一个局部变量的值,在本例中该地址保存的是func4的LR,如下面图中赤色虚线部分。
这时候我们从最底层栈帧(即main函数对应的栈帧)沿着图中的赤色箭头反方向回溯时,只会得到一条调用栈:main -> func4

至此,八股文科普完毕,下面来实战剖析一下

4.3.4 实战

首先,规复任务栈中的内容,步骤如下:

第一步:获取任务栈的内容

在查看某个线程的调用栈之前,须要通过set指令先将crash的事情环境到这个线程的高下文中。

只管任务栈的起始地址会保存在task_struct::stack中,但是笔者一样平常不从该成员中获取,而是直接通过bt -S指定一个缺点的地址,接着crash会直接见告你这个任务合法的任务栈范围。
通过rd命令直接将这段内存空间中的值读取到文本中,以便进行下一步剖析。

第二步:沿着FP找到最深的栈帧

上一步我们已经将pid=54的任务栈导出到mm.txt文本中,下面我们用Notepad++打开,利用NotePad++的好处是双击指定的地址可以高亮显示,这样我们可以很快定位出下一个栈帧的FP地址指向哪里。

如下图所示,下图最左侧的为栈地址,中间两列为栈内容。
从高地址向低地址,沿着赤色箭头反向回溯。
详细方法是利用鼠标双击最左侧的栈地址,如果在栈内容区域中找到一处高亮的值(在Notepad++中,只有相同的两个值才会高亮),则表示该高亮的位置是下一个函数栈帧的起始地址;如果没有创造高亮的值则连续查找下一个地址;如果创造有多处高亮的值,则须要根据上面4.3.2章节先容的三种调用栈格式,判断那一条才是真实的栈帧。
一样平常碰着存在多条高亮的值时候,笔者会直接找出最深的栈帧,然后不才面第三步去判断到底哪些层调用栈是真实的,哪些层是“影子”。

第三步:通过bt -S查看任务栈中的调用关系

须要把稳的是,bt -S后面接的参数该当是LR的值,以是须要在上面找出高亮的地址的根本上加8。

把稳:由下面的任务栈中的内容可知,当前这个任务该当是被中断打断了。
在Linux内核中,每一个任务都有自己的任务栈空间,但是中断栈空间是这个cpu上的所有任务共享的。
既然任务已经被中断打断,那么后面还须要看一下中断栈中的调用关系。

第四步:查看中断栈的范围

直接通过help -m命令,拉到末了面可以看到系统为各个cpu分配的中断栈的起始地址和大小,然后通过rd命令将中断栈的范围导出到文本等分析。

第五步:沿着FP找到最深的栈帧

该步骤同上面第二步,直接找到最顶层的FP即可,此处不再赘述。

第六步:查看完全的任务栈+中断栈中的内容

下面通过bt -S命令就可以查看完全的任务栈和中断栈中的内容了。

实际上上面获取到的调用栈,从第0~19层全部是“影子”,可以从下面两点证明:

pid=54的任务task_struct::thread::cpu_context中记录的sp和fp的值为0xffffffc00847bd60,解释真实的栈帧到0xffffffc00847bd60这一层就结束了,之后的内容是影子。

另一方面,在corevcpu6_regs.cmm脚本中记录了系统崩溃时的pc指针为0xffffffe95af5b7a0,通过crash中的sym命令可以查看该地址对应的符号为rcu_momentary_dyntick_idle+60,通过这个也可以确定真实的调用栈是第20层一下的内容。

通过sym命令可查看指定地址对应的符号

以是,pid=54任务真实的调用栈该当是这样的

五、查看全局变量/局部变量的值

5.1 普通的全局变量

普通的全局变量的值很好查看,直接p后面接函数名即可。

5.2 percpu的全局变量

对付percpu类型的全局变量查看办法也比较大略。
在Linux内核中每个cpu都对应一个struct rq构造,下面命令是查看指定的cpu上rq构造里面的指定成员。
须要把稳的是下面的runqueues为全局变量的名称,冒号后面表示查看指定的cpu上的值。

注:本文不打算对crash的根本命令进行讲解,关于crash的根本命令的利用方法,请拜会本文末了的参考文档。

percpu变量runqueues定义如下

命令实行效果如下:

5.3 局部变量

普通全局变量和percpu全局变量的查看还是比较大略的,但是要想查看局部变量的值就没那么大略了,本文重点是规复指定局部变量的值。
例如不才面调用栈中,我们想剖析在multi_cpu_stop这一层的调用栈局部变量的值。

multi_cpu_stop函数实现如下,本文我们以newstate、curstate和msdata这几个局部变量为例,剖析局部变量的规复过程。

newstate和msdata这两个局部变量正好利用trace32无法解析出,这时候就轮到crash上场了。

通过对multi_cpu_stop函数实现的剖析,我们可以把newstate、curstate和msdata这几个局部分为下面两类,这两类的局部变量的规复方法类似,下面分别先容:

msdata来源于函数通报的参数;

newstate、curstate是函数中的普通变量;

5.3.1 通过函数传参的局部变量: msdata

确定函数传参的步骤如下:

第一步:确定调用关系,找出父函数

由上面multi_cpu_stop的实现可知,局部变量msdata的值来源于multi_cpu_stop函数的第一个传参,由上面4.3.1章节先容的ATPCS规则可知:函数调用时通报的参数,当参数数量小于即是8个时,利用x0~x7通报,大于8个时,多余的参数利用栈通报;函数返回时返回值保存在x0中。

以是在本例中我们要想知道msdata局部变量的值,只须要知道在cpu_stopper_thread中调用multi_cpu_stop时通报的参数即可,也就x0的值。

第二步:对父函数进行反汇编,确定参数所在的寄存器

利用下面命令对父函数cpu_stopper_thread函数进行反汇编,个中\"大众/s\"大众参数表示显示汇编在C文件中的行数。

找出调用cpu_stopper_thread的地方,确定通报的参数x0的来源。
在本例中,x0来源于x21。

第三步:对子函数进行反汇编,确定sp和x21保存的位置

上一步已经剖析出调用multi_cpu_stop函数通报的参数data保存在x21中。
由上面4.3.1章节先容的ATPCS规则可知,x19~x28这几个寄存器是须要被调用者保存的临时寄存器,子程序若利用到这些寄存器,须要将其保存到自己的栈帧中,返回时从栈中规复。
那么在multi_cpu_stop函数对应的汇编代码中,如果要利用x21寄存器,就须要在自己的栈帧中对x21的值进行保存,在函数退出的时候再进行规复。

下面对multi_cpu_stop函数进行反汇编,只关注最前面的压栈操作部分。
下面第一个红框中的汇编指令实现的是将x29(也便是父函数cpu_stopper_thread的栈帧地址)保存进sp-96的位置,感叹号\公众!\公众的含义表示这一条store指令实行完毕之后sp=sp-96。
下面第二个红框表示将x21保存进[sp+64+8]的位置,须要把稳的是,此时的sp已经在实行上一条指令的时候被改为sp-96了。
以是,x21的保存位置该当为sp-96+64+8的位置。

第四步:结合结合栈帧中的内容打算出x21的值

如下图所示,bt命令加上-f参数可以查看各层栈帧中的内容,由下面赤色箭头和红框中的内容可知cpu_stopper_thread的栈帧FP保存在ffffffc00847bd70地址处,也便是说sp-96=ffffffc00847bd70,进一步得到sp = 0xffffffc00847bd70 + 96 = 0xffffffc00847bdd0,须要把稳的是,这里得到的sp是还没有实行感叹号\公众!\"大众操作之前的值。

由上一步的剖析可知,x21保存的地址为sp - 96 +64 + 8 = 0xffffffc00847bdd0 - 96 + 64 + 8 = 0xffffffc00847bdb8。

接着,结合下面栈帧中的内容可知,0xffffffc00847bdb8地址处保存的值为ffffffc00e6eb778,也便是说x21(msdata)的值为ffffffc00e6eb778

第五步:查看局部变量msdata的值

上面已经剖析出msdata的值,结合代码中可知msdata的数据类型为multi_stop_data,则通过下面struct命令即可查看msdata的值。

5.3.2 查看普通局部变量的值: newstate、curstate

如果关注的局部变量并不是通过函数传参传进来的,而是函数自己定义的局部变量,此时我们则须要换个姿势。
在本例中我们看一下系统奔溃时newstate、curstate这两个局部变量的值。

第一步:对利用变量所在的行进行反汇编,确定变量保存在哪两个寄存器中

结合上面的C函数实现,创造在C文件的第231~233行有对着两个变量进行访问的地方,下面对multi_cpu_stop函数进行反汇编,重点关注231~233这几行反汇编。
通过对这几行反汇编进行剖析可知,msdata中保存在x19中,w26中记录着newstate的值,w27中记录着curstate的值。

第二步:查看子函数汇编,是否对w26,w27保存

寄存器w26和w27实际就只x26和x27的低32位。
由4.3.1章节先容的ATPCS规则可知,x19~x28这几个寄存器是须要被调用者保存的临时寄存器,子程序若利用到这些寄存器,须要将其保存到自己的栈帧中,返回时从栈中规复。
以是如果rcu_momentary_dyntick_idle对应的汇编代码中须要利用x26或x27这两个寄存器,则会在最前面的压栈操作中对这两个寄存器的值进行保存。

下面对rcu_momentary_dyntick_idle进行反汇编,创造在该函数中并没有利用到这两个寄存器,因此也就不会对这两个寄存器进行保存。

第三步:从cmm脚本中获取x26和x27的值

这时候怎么办呢?由上面4.3.4章节剖析的结果可知,54号线程的真实的调用栈的最顶层栈帧便是rcu_momentary_dyntick_idle,不存不才一层函数了,此时要获取x26或x27这两个寄存器的值就只能依赖高通或者MTK解析好的cmm脚本。
这是由于系统崩溃后,如果已经没有函数调用了,就不可能再将崩溃现场的寄存器保存在某个函数的栈帧中了。
好在高通和MTK厂商应对这个问题时都采取类似的方法:即在网络Kdump转储信息的时候,都会将系统奔溃时各个cpu上的各个寄存器中的值保存进dump文件的某个地址中,后期再利用解析工具从这些地址中读取出各个寄存器的值并保存在cmm脚本中。
以是我们只须要从cmm脚本中获取x26和x27这两个寄存器的值即可。

如下,在高通corevcpu6_regs.cmm脚本中记录的x26=x27=2,因此系统奔溃时newstate、curstate这两个局部变量的值为2。

5.3.3 扩展:tick中断产生时newstate、curstate这两个局部变量的值

由上面4.3.4章节剖析可知,在过去某个时候,migration/6在实行过程中曾经被tick中断打断过,完全的调用栈如下,当时我们还说从第0~19层调用栈实际上是一个“影子”,栈帧中的内容记录的也是那次tick中断产生时的现场。

为了深入理解crash解析局部变量的方法,我们在此处扩展一下,连续剖析在这次tick中断产生时,彼时newstate和curstate这两个局部变量的值。

这样我们就能够接着上面5.3.2章节的第二步剖析连续了,由于彼时rcu_momentary_dyntick_idle并不是调用栈的最顶层,以是纵然rcu_momentary_dyntick_idle的汇编代码中没有利用到x26和x27这两个寄存器,但是我们可以连续查看下一层,也便是el1h_64_irq这一层。

下面对el1h_64_irq函数进行反汇编,终于创造该函数中对x26和x27这两个寄存器保存的地方。
由下面两点可知:

x29有多处保存,分别在sp+224+8处和sp+304处;

x26, x27分别保存在sp+208和sp+208+8处;

下面查看通过bt -f参数查看栈帧中的内容,由于x29有多处保存,分别在sp+224+8处和sp+304处我们此处以高地址为例,可知sp+304 = 0xffffffc00847bd40,那么sp和x26,x27保存的地址就可以打算出来了,如下:

sp = 0xffffffc00847bd40-304 = 0xffffffc00847bc10

&x26 = sp+208= 0xffffffc00847bc10+208 = 0xffffffc00847bce0

&x27 = sp+208+8= 0xffffffc00847bc10+208+8 = 0xffffffc00847bce8

由下面栈帧中的内容可知newstate=curstate=1。

须要把稳的是,这是彼时tick中断产生时的瞬市价,并不是系统奔溃时的值。

六、哪些线程正在访问/访问过指定的变量

再先容末了一个实用技巧,有时候我们在剖析dump的时候常常会有这样的需求:

已经知道某一把锁在内存中的地址,我们想知道有哪些线程在等这把锁;

已经知道某个全局变量的地址或名称,我们想知道有哪些任务正在访问这个全局变量;

已经知道某个函数名称,我们想知道有哪些任务的调用栈中会调用到这个函数;

这时候我们就可以利用下面命令格式,来对系统中所有的任务的调用栈进行搜索了,个中-t是对所有的任务进行搜索,不区分任务的状态。
而-T则表示只对系统中正在running的任务的调用栈进行搜索。

前面已经得知msdata的值为0xffffffc00e6eb778,在本例中,我们看一下系统中有哪些线程在访问msdata变量,命令如下:

创造pid=943的线程也在访问msdata变量,下面通过bt命令看一下这个线程在干啥。

七、结语

本文从一个实例入手,先容了利用crash工具剖析dump的方法。
重点先容了如何利用crash查看系统崩溃时cpu上正在running的任务的调用栈的方法,并从任务的栈帧中规复局部变量的方法。

crash工具非常强大,它可以填补trace32的不敷,当trace32无法规复任务调用栈或者无法查看某些局部变量的值时,crash tool是一个不错的选择。
此外,crash中还集成了许多实用的命令,例如search, foreach, list等命令,如果要在trace32上实现这些命令的功能,须要编写繁芜的脚本实现,而在crash中只须要一条命令即可实现。

八、参考文档

http://www.dumpstack.cn/index.php/2022/02/23/395.html

http://www.dumpstack.cn/index.php/2022/02/25/398.html

http://www.dumpstack.cn/index.php/2022/02/23/370.html

https://www.xiaoyeshiyu.com/post/58be.html

https://crab2313.github.io/post/kdump/

https://blog.csdn.net/Luckiers/article/details/124612386

https://www.cnblogs.com/aiwz/p/6333276.html

https://blog.csdn.net/wylfengyujiancheng/article/details/70403184

http://www.tjtech.me/how-to-build-redhat-crash-for-arm-under-x86_64-ubuntu.html

https://blog.csdn.net/daocaokafei/article/details/113779430

https://blog.csdn.net/kouxi1/article/details/123039106