ARTICLE DETAIL

资讯详情

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

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

ARM64高通ramdump解析实战:crash工具参数与避坑指南 凌晨两点客户的量产机在实验室里突然死机。接上EDL口把ramdump抓回来解压、找到DDR主镜像满怀期待敲下那行已经背熟的命令crash vmlinux ddr.img结果屏幕上蹦出来一行让人瞬间清醒的报错crash: vmlinux and ddr.img do not match!又或者更常见的crash: cannot determine kernel base address。这一卡就是大半夜。说实话ARM64平台加高通ramdump这个组合我见过太多人把时间耗在crash工具的参数上而不是真正的崩溃点上。很多坑官方文档根本不会写清楚论坛里的答案又零零散散甚至互相矛盾。这篇指南就是把我这几年在ARM64平台、特别是高通8550这类新平台kalama上解析ramdump踩过的坑、试出来的参数、以及最后沉淀下来的固定操作流程一次性整理出来。适合正在做BSP、内核驱动、稳定性分析或者刚接触crash工具还摸不着门道的朋友。看完你可以直接照着操作至少能把“工具能启动、数据能看对”这关过了不用再反复折腾。1. 为什么ARM64平台让ramdump解析变得更折腾1.1 从“能跑就行”到“符号对齐”的思维切换早年在32位ARM平台内核映像加载地址相对固定vmlinux和ramdump文件拿来就能对上去crash工具一启动基本就能用。但ARM64时代不一样了地址空间扩大、多级页表、内核地址随机化KASLR这些机制叠加在一起crash工具没法再靠“猜”来把虚拟地址、物理地址和文件偏移三者对应起来。你给的参数不对它要么直接拒绝启动要么启动后显示的符号全是乱的栈回溯出来的地址一看就是错的。这里关键在于理解一条链vmlinux里存的符号地址是链接时决定的虚拟地址ramdump文件里存的是物理内存内容而crash工具需要知道“虚拟地址到物理地址”的映射关系才能在你输入log或bt的时候从dump文件里找到对应的数据来解析。32位平台通常映射简单甚至固定偏移就行ARM64则必须显式告知工具物理基址和内核映像偏移少一个都不行。1.2 高通平台的ramdump到底长什么样高通平台的ramdump最常见的是通过EDLEmergency Download模式抓出来的。设备进9008端口后用QDLoder/QPST之类的工具把整个DDR内容导出来。导出结果通常不是一个单一文件而是一个目录或压缩包里面包含多个文件命名五花八门有的叫DDRCS0.BIN有的叫bootdump有的叫amss_dump。你需要的是主DDR镜像简单说就是包含内核代码段、数据段和页表等内存内容的那个大块文件。这里有个很容易犯的错把整个ramdump压缩包直接丢给crash。crash要的是一个线性的物理内存镜像不是多个分区文件的集合。拿到手之后先解压找到真正的DDR主镜像最好用file命令看一眼类型确认不是压缩包或者稀疏文件。部分平台还会在镜像前面加一段头部信息解析前得先处理掉。另外MTK平台的ramdump组织方式又不一样别拿高通的经验硬套。1.3 为什么选crash工具而不是其他方案有人会问用gdb读镜像行不行用IDA逆向行不行可以但都不如crash合适。crash是专门为Linux内核内存转储分析设计的工具它理解内核数据结构认得task_struct、page、module这些内核对象一条ps命令就能列出进程状态一条log就能扒出内核打印缓冲区。用gdb你得自己算偏移、自己猜结构效率完全不是一个量级。还有一点crash支持脚本化可以把常用分析步骤写成.crash命令脚本一键执行。这对量产问题分析特别重要拿到一个新ramdump先跑一套固定的脚本基本能判断出大概方向再深入细看。再加上crash是免费开源的社区活跃遇到问题还能看源码排查比起商业工具更灵活。2. 动手前必做的三件事镜像、符号、地址假设2.1 vmlinux匹配度决定成败很多人拿到ramdump就开始敲命令敲不下去才发现vmlinux版本对不上。这里的“对不上”不单指uname -r里的版本号一样就行内核编译时间、代码提交点、配置文件差异都会导致符号表偏移。最靠谱的办法是拿设备上实际运行的内核对应的vmlinux最好连编译机器和编译器都一致。检查vmlinux是否匹配有一个硬指标build ID。用readelf -n vmlinux可以看到这个ID设备上运行的kernel同样可以算出来。如果拿不到设备里的kernel image至少把客户固件里带的System.map和vmlinux版本对一下确认符号地址能对应上。另外vmlinux必须是带调试信息的完整版本如果被strip过crash虽然能启动但符号会大幅缺失分析起来很痛苦。注意编译内核时一定要打开CONFIG_DEBUG_INFO。如果工程因为体积原因压缩了调试信息至少保留函数符号。别问我怎么知道的我试过拿着一个精简过符号的vmlinux折腾了整晚连崩溃函数都定位不出来。2.2 理解物理地址、虚拟地址和文件偏移三者的关系这是解析ramdump最核心的概念没有之一。物理地址是DDR上的实际地址虚拟地址是内核运行时使用的地址文件偏移是ramdump文件里数据所在的位置。crash工具解析时大致是这么个链路给你一个虚拟地址 - 查内核页表或已知映射关系 - 换成物理地址 - 再减去镜像起始物理地址 - 得到文件偏移 - 从文件里读数据。举个简单例子假设某个DDR镜像文件从物理地址0x80000000开始导出文件头偏移0x0对应物理地址0x80000000那么物理地址0x90000000对应文件偏移0x10000000。如果文件前面还带了0x2000字节的头部信息实际读数据的文件偏移还要再加上这个头部长度。这个换算关系搞错crash会一直报读内存错误或者读到错位的数据栈回溯全是乱码。2.3 KASLR与物理基址的坑ARM64平台默认内核开启了地址随机化CONFIG_RANDOMIZE_BASEy这意味着vmlinux里记录的链接地址和设备实际运行时内核所在地址并不一致。解析ramdump时crash需要知道随机化偏移量是多少否则所有符号地址都是错的。这个值在高通平台上可以从内核日志的Kernel Offset行看到但问题是你都死机了常规手段拿不到日志。好在ramdump里有整个DDR内容日志缓冲区也在里面所以可以先用未修正偏移的crash强行启动从log命令里看到Kernel Offset信息再退出重启crash把这个偏移通过-m kimage_voffset0x...传进去。这是个笨办法但实测有效。除了随机化偏移另一个必须明确的参数是物理基址phys_base它表示内核映像被加载到的物理地址。大部分ARM64平台在高通的内存映射里内核都放在DDR的某个固定基址上比如0x80000000或者更高这取决于SoC的内存编址得看具体平台的memory map。3. crash工具里的“隐藏参数”一次成功的启动3.1 从默认命令到正确参数很多人以为crash启动就是crash vmlinux ramdump.bin在高通ARM64平台上这样大概率失败。我自己常用的启动命令长这样crash -m phys_base0x80000000 -m kimage_voffset0x0 ./vmlinux ./ddr.img这里的-m是--machdep参数的简写意思是“机器相关参数”专门用来告诉crash目标平台的硬件特性。phys_base告诉它内核映像在物理内存中的加载基址kimage_voffset告诉它KASLR产生的虚拟地址偏移量。没有随机化的时候kimage_voffset就是0开了随机化就得填实际值。不同平台的这两个值不一样高通8450、8650和8550kalama之间可能都有差异。如果启动报错说找不到内核可以加-d1参数打开crash自己的调试输出它会打印出它尝试解析的物理地址和镜像布局这些信息对排查地址参数特别有用。3.2 通过文件偏移修正ramdump头部高通的ramdump有的带固定头部有的不带有的还是从DDR中间某个地址开始导出的。判断方法很简单用十六进制编辑器或者xxd看一眼文件头如果开头一堆看似杂乱的指针和字符串很可能就是带了元信息的格式得先剥掉。剥头部可以用Python写个几行的小脚本import sys with open(sys.argv[1], rb) as f: data f.read() # 假设头部长度为0x2000字节按实际平台配置修改 header_len 0x2000 with open(sys.argv[2], wb) as out: out.write(data[header_len:])值得注意的是有的平台DDR镜像不是简单剥个头就能用它可能把多个DDR通道分文件导出每个文件对应不同的物理地址区间需要按平台的内存映射表重新拼接成一个线性镜像。这类情况建议先去查对应平台的技术参考手册或者高通的ramdump解析脚本别自己瞎拼拼错了crash一样不认。3.3 借qemu造一个“假ramdump”练手没真实设备的时候想练crash工具怎么办qemu可以模拟一个ARM64环境制造崩溃并导出内存镜像这个思路对团队培训特别实用。基本流程是这样用qemu启动一个ARM64的内核在guest里执行echo c /proc/sysrq-trigger触发内核panic然后在qemu的monitor里用pmemsave命令把物理内存保存下来。qemu虚拟机的内存起始物理地址通常和高通不一样很多virt平台的起始地址是0x40000000正好可以拿来当反面教材练习如何根据平台差异调整crash参数。qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 4 -m 2G \ -kernel arch/arm64/boot/Image \ -append consolettyAMA0 panic-1 \ -monitor telnet:127.0.0.1:5555,server,nowait启动后在monitor里执行pmemsave 0 0x80000000 /tmp/qemu_ramdump.bin导出前2GB物理内存。然后拿这台机器的vmlinux和这个镜像去练手核心逻辑和真实高通ramdump完全一致只是参数值不同。我在团队内部就是这么带新人的比干讲理论有效得多。4. 实战解析从日志到崩溃点的四步流程4.1 第一眼用log和bt建立现场crash启动成功后我习惯先跑两条命令log和bt -a。log直接打印内核日志缓冲区里的内容在这里你能看到panic时的内核消息包括异常类型、PC值、调用栈以及可能的内核报错信息。有时候光看日志就能定位问题比如明显的内存越界、空指针解引用都会在日志里留下痕迹。bt -a是所有CPU的栈回溯比单独敲bt信息量大很多。默认的bt只显示当前CPU的栈但很多死机是多核交互问题别的核上的任务状态和栈信息同样关键。ARM64平台上看bt -a的输出重点关注panic CPU是哪一个以及各核上正在跑什么任务往往能看出是哪个核触发了整机崩溃。提示如果bt显示某个task的栈回溯完全为空先别急着怀疑crash坏了很可能是该CPU的寄存器上下文信息没有正确落在ramdump对应的区域。这时候用dis -r从PC值附近反汇编读LR寄存器所指的返回地址手动梳理调用链比傻等命令输出可靠。4.2 深入现场task、struct、rd验证定位到崩溃函数后通常要结合代码看具体是哪一行出的问题。手头没有带行号调试信息时可以用dis -r 地址反汇编该函数再配合rd命令直接读内存里的参数值。比如一个函数接收一个指针参数实际调用时是空指针你可以在栈上找到这个参数的值然后看它哪里被写坏的。crash里查看结构体字段也很方便比如想看某个进程的完整状态直接用struct task_struct 地址它会按数据结构定义自动展开所有字段。这样你就能确认某些标志位、引用计数、链表指针是否合法进一步推断崩溃时的资源状态。另外ps命令能列出所有进程及其状态task可以切换当前上下文到特定任务dev -d能查看设备树信息。这些命令组合起来足以在不开源码IDE的情况下完成一次相对完整的内核态问题排查。4.3 当符号不完整时怎么续命最糟糕的情况是vmlinux符号不完整连函数名都解析不出来。这时候crash里看到的是一个个十六进制地址但也不是完全没办法。先确认模块符号有没有加载mod -s或者mod -S可以尝试加载模块符号如果ramdump里有模块的内存影像很多驱动函数就能恢复名字。符号彻底没救的情况下还有两条路一是纯汇编硬啃。崩溃现场有PC值和LR值反汇编之后看代码逻辑配合对内核源码的熟悉程度还是有希望定位到具体函数的。二是对比分析。用编译出的vmlinux在同一个地址范围内找相似函数特征或者用strings在dump文件里搜索与崩溃日志相关的字符串再反向查找引用它的代码位置。这些都是没办法的办法但确实救过我的急。5. 常见报错速查与避坑技巧实录5.1 速查表crash启动常见报错报错信息原因分析解决办法crash: vmlinux and ddr.img do not match!vmlinux与固件版本不对应或者加载地址参数错误检查build ID确认版本匹配核对phys_base参数crash: cannot determine kernel base address缺少物理基址或KASLR偏移信息用-m phys_base0x...指定物理基址必要时通过日志提取Kernel Offsetread error: physical address: ...文件偏移映射错误crash读取了镜像以外的区域确认镜像起始地址和文件头部长度换用修正后的镜像invalid kernel virtual address地址换算错误或者访问的内核虚拟地址本身不合法检查页表映射信息确认是不是把物理地址当虚拟地址使了bt: cannot determine starting stack frame寄存器上下文缺失或者sp/lr地址异常尝试dis -r反汇编PC周边手动回溯栈帧检查是否选了错误的CPU上下文表格里的情况我基本都遇到过尤其是第一行vmlinux版本不对是最常见的但也是最容易被忽视的。很多研发人员习惯于从服务器上随便拿个vmlinux来用完全没想过build ID不一样会导致符号表错位。5.2 我踩过的三个隐藏坑第一个坑vmlinux被strip过。某个项目为了控制固件体积发布的vmlinux把调试符号裁掉了。表面上看crash能启动版本信息也能显示但只要一敲log或者bt要么输出空要么报符号无法解析。排查了很久才发现是strip的“杰作”。解决办法只能是重新找一个完整的、带调试信息的vmlinux做匹配。这个坑的隐蔽之处在于报错不是“文件不匹配”而是“能跑但解析很烂”很容易让人误判成参数问题。第二个坑ramdump大文件截断。高通的ramdump动辄好几个GB如果在32位环境或者旧版本crash上解析长文件偏移处理不好crash会静默地只读取前面一段数据所有栈回溯看起来都是截断的。排查方法很简单启动crash后先看它的版本和启动日志确认它识别出的内存大小和实际ramdump大小一致。另外shell的ulimit -f如果限制过文件大小也会导致读取不完整这个坑不是crash本身的问题但坑人效果一样。第三个坑多核栈回溯全是零地址。有次在8450平台解析一个死机dumpbt -a发现除了panic CPU之外其他CPU的栈全是零地址看起来就像那些核根本没有运行。后来发现是ramdump抓取方式的问题——EDL导出DDR时某些CPU的寄存器上下文存放位置没有包含进来或者标记位没对。这种情况下crash无法重建其他核的执行现场栈回溯自然就是空的。解决思路是退回到panic CPU单核分析同时从日志里找其他核最后打印的信息拼凑出多核交互过程。这个经验提醒我ramdump解析不光是工具问题还要理解平台的dump抓取机制。最后分享一个我自己的习惯拿到一个新平台的ramdump我从来不急着定位崩溃点。我会先花五分钟做一次“环境自检”确认vmlinux的build ID、确认镜像文件大小和文件头、敲一遍带-d1的启动命令、看crash启动日志里识别的内存布局。这套流程走完工具层面的问题基本都排掉了后面再进入真正的问题分析。虽然听起来浪费时间但实际帮我省掉的排查时间远大于五分钟。在这个领域工具本身的问题往往比崩溃本身更难缠先把环境变量全部校正好后面才能安心地跟内核代码较劲。
返回列表