ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

crash工具解析高通ARM64 ramdump:KASLR与vmemmap参数实战指南

crash工具解析高通ARM64 ramdump:KASLR与vmemmap参数实战指南 做高通平台的BSP或者稳定性调试总会遇到这种场景一大早测试扔过来一台机器“挂死了ramdump抓出来了”。你拿到一个几GB甚至十几GB的ramdump文件然后开始跟crash工具较劲。如果是在x86平台上我闭着眼都能把vmcore解出来但一到ARM64尤其是高通的SoC上crash工具突然就“不配合”了要么bt打印出来的调用栈全是乱码要么一启动就报错说“cannot determine vmemmap”。这篇文章就是把我这些年用crash工具解析高通ramdump踩过的坑以及那些文档里语焉不详的隐藏参数一次说清楚。适合刚接手高通平台稳定性工作、或者正被ramdump折磨得够呛的Linux内核开发、驱动工程师参考。1. 先说清楚ARM64和高通ramdump的组合到底难在哪1.1 同样是崩溃转储ARM64和x86的差别不是一点半点在x86平台上用crash工具分析vmcore基本套路是crash vmlinux vmcore运气好顶多加一个--kaslr偏移很快就能出结果。但到了ARM64尤其是高通平台上这套经验百分之百失灵。原因主要有三个层面。第一个层面是内存布局不同。x86的物理内存映射相对规整而ARM64的线性映射区direct mapping、vmalloc区、vmemmap区都有独立的虚拟地址范围这些范围由内核编译时的VA_BITS、PAGE_OFFSET、VMEMMAP_START等宏决定一旦crash工具拿不到这些信息它连“虚拟地址转物理地址”这一步都做不了。第二个层面是内核地址随机化KASLR这个最让人头疼。ARM64平台从内核4.x开始默认开启KASLR高通在bootloader阶段会往内核里传入硬件随机数种子也就是说每次启动内核的虚拟地址基址都会变而且这个偏移量不会写进ramdump的固定位置crash工具没法直接从dump文件里“一眼”看到偏移值。你带着x86的习惯直接启动crash它默认认为内核加载在编译时的链接地址结果就是所有符号都对不上bt命令输出一堆歪七扭八的地址。第三个层面是ramdump文件的格式。高通平台的ramdump不是标准的ELF core dump也不是Linux标准的kdump格式而是各个内存段DDR、OCIMEM等的裸内存镜像打包。crash工具本身不认这种格式必须先经过一个格式转换步骤把它变成crash能识别的ELF文件。很多人上来就把整个ramdump目录丢给crash报错之后一脸懵问题就出在这里。1.2 crash工具解析高通ramdump的本质是什么说到底crash工具要做的事就三件第一把内核虚拟地址转换成物理地址第二把物理地址对应到ramdump文件中的具体偏移第三根据vmlinux中的符号表和调试信息把内存里的二进制数据翻译成人能看懂的调用栈、结构体、变量值。这三件事环环相扣。虚拟地址转物理地址依赖crash对ARM64内核内存布局的认知这个认知来自PAGE_OFFSET和vmemmap如果这两个基础值错了后面一切全错物理地址转文件偏移依赖ramdump解析器生成的段表PT_LOAD段段表缺失或者地址范围不对crash读内存就会直接报错符号翻译则依赖vmlinux是否与正在解析的内核完全匹配版本不一致、编译选项不同都会导致函数名错位。所以当我看到有人拿着crash工具对着高通ramdump干瞪眼我第一反应就是问他你的vmlinux哪来的ramdump转换了没kaslr偏移设了没这三个问题解决了百分之九十的解析问题都能化解。这篇文章接下来就把这三条线串起来讲明白。2. 开工之前环境准备以及理解你拿到的ramdump到底是什么2.1 两个前提同一次构建的vmlinux和正确的解析工具先聊vmlinux。高通平台的内核编译产物里vmlinux是未压缩、未strip的内核ELF带有完整的符号表和调试信息。这是crash工具解析的“字典”没有它crash只能看到一堆裸地址毫无意义。关键点在于这个vmlinux必须和你正在解析的ramdump来自同一次构建一个字节的差异都可能让符号解析出错。实践里面我见过不少新人直接拿out/target/product/xxx/obj/KERNEL_OBJ/vmlinux去解析另一台机器上抓的ramdump结果函数名全部偏移排查半天发现版本都对不上。验证vmlinux是否匹配有个快速办法用crash启动后执行log命令看看内核日志里的Linux version字符串再对比ramdump对应系统的版本号。如果log命令直接报错“read error”说明vmlinux和ramdump之间已经存在根本性错位。再聊解析工具。高通平台官方的ramdump解析脚本叫作ramdump_parser.py通常位于内核源码树的scripts/ramdump/ramdump_parser.py路径下。这个脚本的作用是把裸的ramdump内存镜像重新打包成标准的ELF格式让crash、gdb这类工具能够按段读取。另外高通还有一套图形化工具QCAPQualcomm Crash Analysis Program也能做类似的事情不过大部分人还是习惯命令行流程。我的建议是两条腿走路QCAP出报告快适合快速定位crash灵活度高适合深入分析某一条调用栈或者某个结构体。2.2 高通ramdump并不是标准ELF需要先转换高通平台的ramdump严格来说是一堆内存段的集合体。抓取ramdump时高通bootrom或者HLOS里的dump工具会把DDR、OCIMEM、DDR_0等各个内存区域的内容读出来分别存成文件放在一个目录下面。整个目录的原始形态既不是ELF也不是core dump只是内存的“快照”。要让crash工具能读它第一步就是用ramdump_parser.py做转换。典型命令如下python3 ramdump_parser.py -i ramdump/ -o ramdump.elf -k vmlinux其中-i指定ramdump所在的目录-o指定输出的ELF文件名-k指定同一次构建的vmlinux。脚本会自动识别ramdump目录下的DDR段文件通常是DDR_0、ddr.bin这类名字然后把它们打包成ELF的PT_LOAD段。转换完成后可以用file命令快速验证file ramdump.elf # ELF 64-bit LSB core file, ARM aarch64, version 1 (SYSV)看到“core file”字样基本就稳了。如果file命令显示的不是core file或者转换过程中报错说找不到DDR段多半是-i指定的目录不对或者ramdump本身不完整。2.3 确认平台基线8550kalama这类新平台的内存布局从哪查很多人拿到ramdump就直接莽结果栽在不知道平台物理内存起始地址上。高通的SoC型号不同DDR物理起始地址可能不同比如某些老平台是0x80000000而8550平台kalama整体布局又不一样。虽然是ARM64架构但具体到每一个SoCcrash工具并不会自动知道内存布局它需要依赖vmlinux里的符号和常量来推断但如果推断失败你就得手动告诉它。查找平台内存基线最靠谱的来源有三个。第一同构建内核的System.map文件里面能看到_text、swapper_pg_dir、vmemmap、page_offset_base等符号的链接地址这些是内核编译期确定的值第二设备树中的memory节点里面声明了DDR的起始地址和大小高通平台通常在arch/arm64/boot/dts/qcom/kalama.dtsi中能找到第三如果手头有设备抓ramdump之前的串口日志dmesg里也会打印内存布局信息。这些工作看起来繁琐但绝对值得做。因为接下来所有参数设置都要以这份平台基线为依据。3. 那些隐藏参数到底怎么用core解析的“正确姿势”3.1 --kaslrARM64上绕不开的“地址漂移”KASLR是crash解析高通ramdump时最常见也最坑的一个参数。先解释一下它到底在干什么为了让内核镜像在内存中的地址随机化ARM64内核启动时会从启动参数或者硬件随机数源获取一个随机偏移整个内核镜像的虚拟地址都基于这个偏移重新定位。这就导致一个现象编译vmlinux时_text符号的链接地址是0xffff000000000000附近某个值但实际运行时代码和数据可能落在0xffff00003a800000这样的位置中间差的那个0x3a800000就是kaslr偏移。crash工具如果不加--kaslr就直接拿链接地址去解析ramdump那当然什么都对不上。解决办法是在启动crash时手工指定这个偏移或者让crash尝试自动检测crash vmlinux ramdump.elf --kaslr0x3a800000 # 或者自动检测 crash vmlinux ramdump.elf --kaslrauto怎么知道这个偏移值常规做法是看内核日志dmesg里有一行长这样Kernel Offset: 0x3a800000 from 0xffff000000000000 (relocation range: 0xffff000000000000-0xffff0000bffff000)如果你手里只有ramdump还没拿到串口日志也不要慌。先启动crash不加kaslr参数然后执行log命令尝试读取内核log缓冲区有时候能直接翻到“Kernel Offset”这一行再把偏移带出来重新启动解析。我还没见过哪一次高通ramdump完全拿不到Kernel Offset的无非是找起来费点劲。另外一个实用技巧如果ramdump里包含了启动阶段的内存内容kaslr偏移还可以通过搜索vmlinux里的linux_banner字符串在ramdump中的实际位置来反推。crash工具的相当一部分自动检测逻辑就是基于这个原理。不过自动检测毕竟有失败概率手动指定最保险。3.2 --machdepARM64专属的底层配置开关如果说--kaslr解决的是“地址漂移”那--machdep解决的就是“内存布局认知缺失”。在ARM64平台上crash工具启动时往往需要知道以下几个底层的机器依赖参数子参数作用典型值page_offset线性映射区起始虚拟地址0xffff000000000000vmemmapstruct page数组的虚拟地址基址0xffff7dffffe00000kernphysbase内核镜像的物理加载地址0x80000000依平台而定kernbase内核虚拟地址基址0xffff000000000000这些参数在crash工具官方文档里也有说明但ARM64平台上使用者普遍不重视所以一直有种“隐藏参数”的感觉。实际用法是在命令行上通过--machdep一次性传入多个子参数crash vmlinux ramdump.elf --machdep page_offset0xffff000000000000 vmemmap0xffff7dffffe00000 kernphysbase0x80000000需要强调一点--machdep不是每次都要手工设。crash工具自带一些自动侦测逻辑遇到较老或较常见的ARM64内核布局时不加这些参数也能正常启动。但在高通新平台、新内核上自动侦测失败的频率很高报错信息往往就是“cannot determine vmemmap”“cannot determine page_offset”。这个时候不想被卡住就得手动指定。参数值从哪里来最简单的方式用同一次构建的System.map查。page_offset一般对应内核符号page_offset_base或者编译选项CONFIG_PAGE_OFFSETvmemmap对应符号vmemmap。注意vmlinux编译时这些符号都有一个“链接地址”加上KASLR偏移之后才是运行时的真实地址但--machdep里的page_offset和vmemmap要填的是“链接地址”一类的基线值吗这里有个容易混淆的点对于page_offset它表示的是线性映射区的起始虚拟地址这个地址在KASLR打开时本身不会随机化随机化的是内核镜像的基址所以按编译期配置填即可vmemmap同理它通常固定在0xffff7dffffe00000这样的值附近属于编译期确定的内存布局的一部分。3.3 --page_offset 与 --vmemmap内存映射的“坐标轴”这两个参数值得单独拉出来讲因为crash工具做内存地址翻译时它们就是“横纵坐标轴”。page_offset决定了线性映射区的起始虚拟地址。ARM64 Linux内核对物理内存的直接映射是把物理地址加上一个固定偏移得到虚拟地址这个固定偏移就是page_offset。假设page_offset0xffff000000000000那么物理地址0x80000000对应的虚拟地址就是0xffff000080000000。crash工具要访问物理内存里的某个数据结构必须先通过这个偏移把虚拟地址换算成物理地址再去ramdump的ELF段里找对应的文件偏移。如果page_offset设错后续所有地址换算都会产生系统性偏差。vmemmap则对应Linux内核里struct page数组的起始虚拟地址。内核为每一个物理页帧维护一个struct page结构体这些结构体组成一个数组数组的起始地址就是vmemmap。crash工具处理很多命令时比如kmem -s查看slab信息、vm查看内存管理结构都需要通过物理页帧号PFN去索引struct page索引过程完全依赖vmemmap的地址。没有它所有涉及内存管理的数据结构都没法解析。不同内核配置下这两个值不完全一样我整理了一个常见参考表VA_BITSPAGE_OFFSET 常见值vmemmap 常见值480xffff0000000000000xffff7dffffe00000390xffff0000000000000xffff7dffe0000000依配置高通现代平台8550/kalama这类基本都是48位虚拟地址PAGE_OFFSET用0xffff000000000000这个值居多。不过具体还是要以内核源码里的arch/arm64/include/asm/memory.h和生成的autoconf.h为准。3.4 容易被忽略的--image、--phys_base等参数除了上面几个明星参数还有几个在特定场景下能救命的小众参数可能不算“核心”但既然聊到隐藏参数就一起说了。第一个是--phys_base。这个参数和kernphysbase有一定关联但侧重点不同。有时候crash提示找不到物理内存基址尤其当ramdump里没有包含低地址区域时你需要在启动命令行里显式给出DDR物理基址crash vmlinux ramdump.elf --phys_base0x80000000第二个是--image。这个参数用于指定vmlinux的替代文件。个别场景下手头的vmlinux被strip过或者不完整但你有另一个完整的内核镜像文件比如Image可以配合使用。实际用得不算多但如果遇到了会非常解渴。第三个是--zero_excluded。高通ramdump转换出来的ELF文件有些内存段可能并没有完整的物理内存镜像未包含的部分在段表里可能被标记成zero-fill或者excluded。crash默认遇到这些区域的读取请求会报错加上--zero_excluded可以强制把未包含的内存当作全零来处理。这个参数在分析某些不完整的ramdump时很有用因为很多内存区域本身就是保留区或者没有实际被使用全零处理不影响主线分析。这些参数平时用不上但一旦遇到crash启动失败它们就是你手里的一张张底牌。4. 高通ramdump解析完整实操从原始文件到第一份调用栈4.1 第一步用ramdump_parser.py生成可解析的ELF假设你现在拿到一个ramdump目录结构大概是这样的ramdump/ ├── DDR_0 ├── DDR_1 ├── OCIMEM ├── ... └── index.xml第一步先把vmlinux和ramdump对一下版本。怎么快速对直接看vmlinux的build ID和ramdump目录里有没有对应的编译信息或者先转换完用crash的log命令验证。如果发现版本对不上立刻停下回去找正确的vmlinux不要浪费时间。确认无误后执行转换python3 scripts/ramdump/ramdump_parser.py -i ramdump/ -o ramdump.elf -k vmlinux转换过程中脚本会打印识别到的内存段信息。如果提示找不到DDR段可以尝试显式指定DDR镜像文件有些平台DDR段文件名不叫DDR_0可能是ddr.binpython3 scripts/ramdump/ramdump_parser.py -i ramdump/ -o ramdump.elf -k vmlinux -d ramdump/ddr.bin转换完成后用file看一下file ramdump.elf # ELF 64-bit LSB core file, ARM aarch64, version 1 (SYSV)这里有个经验生成的ELF文件越大越好。正常情况下ramdump.elf的大小应该和DDR段原始大小差不多如果小得离谱说明转换时漏掉了核心内存段这种ramdump.elf拿给crash解析即使参数全对也读不出完整数据。4.2 第二步带参数启动crash拿到了可解析的ELF文件先不要急着裸启动crash。我个人的习惯是先把三个信息准备好kaslr偏移、page_offset、vmemmap。kaslr偏移优先从内核日志获取page_offset和vmemmap从System.map或者内核配置获取。然后组合完整的启动命令crash vmlinux ramdump.elf --kaslr0x3a800000 --machdep page_offset0xffff000000000000 vmemmap0xffff7dffffe00000crash正常启动后会打印类似这样的信息KERNEL: vmlinux DUMPFILE: ramdump.elf [Partition 0]: start: 0xffff000000000000, size: ... [Partition 1]: start: 0xffff7dffffe00000, size: ... CRASH: 64-bit ARM64 (little-endian)看到CRASH: 64-bit ARM64 (little-endian)并且没有报错说明起点顺利。如果这个时候直接报“cannot determine vmemmap”就是--machdep没设置对如果报“cannot access vmallocd memory”多半是--kaslr偏移有问题。如果在启动阶段没有手动设置kaslrcrash也会尝试探测但结果不一定准。我的建议是能手动指定就手动指定准确性优先。4.3 第三步交互式命令组合拳进入crash交互环境后第一个必做的命令是log把内核日志翻出来。这一步能快速确认kaslr偏移是否设置正确——如果日志最后几条能看到系统panic或者watchdog超时相关的输出说明地址解析工作正常如果log命令一执行就read error说明地址映射不对赶紧退出调整参数。确认log可读之后下一步就是核心的bt命令crash bt -a-a表示打印所有CPU的调用栈。高通平台多核场景下系统挂死往往不止一个核在忙其他核可能在某个锁上自旋等待或者处于中断上下文。把所有核的栈都拉出来一眼就能看到哪个核是“第一案发现场”。如果想看更详细的栈帧信息可以加bt -f只想看某个CPU用bt -c 3指定CPU编号。接下来是进程维度的分析。ps命令可以列出所有线程及其状态crash ps -m如果系统是死锁或者看门狗超时重点关注处于Rrunning或者Duninterruptible sleep状态的线程。再看线程栈上正在执行的函数往往就能锁定问题模块。设备状态可以用dev命令查看尤其是DMA、中断控制器等驱动的状态在某些稳定性问题里是关键线索。再进一步如果怀疑某个驱动出问题可以直接用struct命令按地址读取结构体内容crash struct module 0xffffff8000123456这在排查驱动访问越界、状态标志异常时非常有价值。需要注意的是kmem -s这类命令依赖vmemmap如果之前vmemmap参数设置不对执行时会报错所以前面参数设置的功夫不能省。4.4 用脚本批量出报告一条命令搞定重复劳动实际工作中ramdump解析不可能只看一次就万事大吉。同一批稳定性的问题可能一天要解析好几个dump每次都手工执行那几条命令太浪费时间。crash工具支持从脚本文件读取命令我一般会准备一个crash_cmds.txtlog bt -a ps -m dev -p quit然后一条命令直接跑完crash vmlinux ramdump.elf --kaslr0x3a800000 --machdep page_offset0xffff000000000000 vmemmap0xffff7dffffe00000 crash_cmds.txt report.txt生成的report.txt就是一份完整的初步分析报告可以直接丢给团队其他成员去看。如果有对比排查的需求也可以用rd命令按物理地址读取特定内存区域把几个dump的数据并排比较能发现一些蛛丝马迹。5. 常见问题快查表我踩过的坑都在这里5.1 bt输出为空或者调用栈乱码这是最高频的问题。现象是crash能启动log也能看但bt命令要么什么都不打要么打出来的函数名明显不对头比如地址值异常或者符号完全对不上。排查思路先确认kaslr偏移。我遇到过一个案例系统日志里Kernel Offset是0x3a800000但启动crash时没加--kaslr结果bt输出的函数名整体偏了同一个值看起来就像模块名加了一截。加上--kaslr之后再重启crash一切正常。另外一个不那么显眼的原因是内核日志被覆盖或截断导致你拿到的kaslr偏移其实是上一次启动的值这种情况务必多核对几行日志里的时间戳。5.2 “cannot determine vmemmap/phys_base”报错crash启动阶段直接报错通常意味着自动探测内存布局失败。这时候需要手工注入参数。我的做法是先去System.map里翻出vmemmap符号的地址再根据内核配置确定PAGE_OFFSET然后通过--machdep一次性传给crash。有种情况需要特别留意高通新平台早期bringup阶段内核调试信息可能不完整System.map里的某些符号被优化掉了或者编译选项和release版本不一致。这种情况下建议多备几份不同构建的vmlinux和System.map做交叉验证确认当前ramdump对应的是哪个版本的代码。5.3 vmlinux符号错位所有函数名偏移一个固定值如果bt出来的函数名错位规律一致比如每个函数的地址都比实际值大或者小一个固定数值那基本就是vmlinux和ramdump不匹配或者kaslr偏移设置错了。先排除kaslr偏移是否正确如果偏移没问题那就是vmlinux拿错了。实践中我见过最折磨人的一种情况同一套代码有人用GCC编译有人用Clang编译生成的内核符号地址完全不同。所以拿vmlinux的时候一定要确认来源最好是直接从对应系统的编译产物目录里拷不要从别的工程师手里转一道很容易串版本。5.4 读取内存失败ramdump本身不完整有时候crash能启动但执行某些命令时报“read error: Cannot access user memory”甚至log只能翻出一部分内容。这种情况多数不是参数问题而是ramdump本身不完整。ramdump抓取时如果DDR段没有完全覆盖内核对内存的使用范围转换出来的ELF里就缺少某些物理页。先执行dev -p查看当前ELF里的段信息确认DDR的物理地址范围。如果发现范围比实际平台内存布局小就得回头检查抓取ramdump的环节。另外前面提到的--zero_excluded参数也能缓解一部分问题但它是“掩盖”缺页不是“修复”缺页关键数据如果真的丢了加什么参数都救不回来。5.5 新旧工具的差异QCAP与crash如何互补高通官方这些年一直在推QCAP它能把ramdump直接解析成一份带调用栈、线程状态、内核日志的HTML报告很多场景下确实比crash方便尤其适合给测试或非内核背景的同事看结论。但QCAP毕竟是“报告生成器”你想自己深入读某个结构体、验证某个驱动的状态标志它就不如crash灵活了。我的习惯是用QCAP快速定位可疑线程再用crash做深入分析。两者配合效率和深度都能兼顾。如果你所在团队还停留在“拿到ramdump不知道从哪下手”的阶段建议先让大家跑一遍QCAP熟悉整体流程再用crash挖细节。写在最后的一点体会解析ramdump这件事表面上是在跟工具和参数较劲本质上是在跟内核的内存管理、编译链接、硬件初始化这些底层机制打交道。那些参数之所以看起来“隐藏”是因为大多数时候crash帮你把细节都藏起来了一旦平台变得陌生这些细节就会变成拦路虎。我个人的经验是不要死记参数而是搞懂每个参数修正的是哪一段映射关系。KASLR修的是“链接地址到运行地址”的平移page_offset修的是“虚拟地址到物理地址”的基准vmemmap修的是“物理页到struct page”的索引。这三个坐标系的任意一个对不上解析结果就是错的。搞懂了这些换任何平台、任何新SoC你都不会慌。
返回列表