资讯动态

高通ARM64 ramdump解析:crash工具隐藏参数与KASLR偏移实战

发布时间:2026/9/28 20:44:24 来源:尧图企业网站定制
把高通平台的ramdump.bin直接丢给crash工具回车然后看着它吐出一堆crash: read error或者WARNING: cannot access vmallocd address——这个场景过去两年里我在8155、8295、8550这几个大平台上遇到过不下十次。网上很多教程把crash解析ramdump说得跟crash vmlinux ramdump.bin一行命令一样简单实际上真正决定成败的往往是那些藏在crash初始化流程里的隐藏参数phys_offset、kaslr_offset、vmalloc_start、RAMDUMP_OFFSET这类。这篇文章就是要把这些参数的作用、获取方式和翻车现场一次讲清楚主要围绕ARM64平台尤其是高通新老平台的ramdump解析。如果你在做内核BSP、驱动稳定性调试、或者FAE现场问题分析这篇文章应该能帮你省下好几个通宵。1. 拿到ramdump.bin后别急着敲crash命令1.1 你拿到的是高通私有dump格式不是标准ELF core很多人第一次接触高通ramdump时下意识把它当成一个普通的内存镜像文件于是执行crash vmlinux ramdump.bin结果crash直接报file format not recognized或者干脆乱读一气。这其实不是crash太弱而是高通平台的ramdump文件格式本身就不是通用ELF core。高通的ramdump.bin在文件开头有一段平台私有的头信息里面记录了各个dump segment的加载地址、大小、内存类型等元数据。真正有用的内存数据是从某个偏移之后才开始排列的。上游原版crash工具不认识这套私有头部所以你必须先经过一层“翻译”高通release包里的ramdump_parse.py脚本可以把带头的ramdump.bin解析成标准ELF core文件高通维护的crash分支一般从codeaurora或qcom release里带出来内置了ramdump解析能力能直接吃原始ramdump也可以用手工方式读取头部segment表按加载地址逐个dd出来再拼接成内存镜像但这种方式只适合在没有脚本的紧急情况下用。以线上release里最常见的做法为例先用高通的转换脚本拿到core文件# 高通平台常用的转换示例实际脚本名/参数以release包为准 python ramdump_parse.py -i ramdump.bin -o ramdump.elf --vmlinux vmlinux转换完再交给crashcrash vmlinux ramdump.elf如果你拿到的是高通crash分支也可以直接crash --ramdump vmlinux ramdump.bin这一步的核心是先确认你的工具链到底支不支持这份ramdump格式不要拿着任何一份文件都硬上。1.2 符号“三件套”vmlinux、System.map、kallsyms各管一摊crash工具解析内核崩溃转储本质上是把内存里的数据跟符号表做映射。ARM64平台这一套尤其依赖符号文件的质量所以先说清楚这三样东西的定位文件作用能否替代其他vmlinux未压缩的内核镜像包含符号表、调试段、BSS段布局信息是crash的主依赖不可替代System.map纯文本符号地址表只提供地址到名称的映射不能替代vmlinux无法提供结构体布局kallsyms内核运行时展开的符号表通常残留在ramdump内存里可以作为偏移修正的参考实际使用中vmlinux是必须的。而且这个vmlinux必须和出问题的内核是同一次编译出来的连编译时间对不上都可能导致符号行号错位。System.map虽然也能单独加载进crash但它没有类型信息你想用struct task_struct去读一个进程结构体没有vmlinux是读不出来的。如果你拿到手的是一个Image.gz或者Image先别高兴这些是自解压镜像不是最终带符号表的vmlinux。真正要的是编译目录下那个ELF格式的vmlinux或者从编译产物里找到带CONFIG_DEBUG_INFO的那个版本。检查一下file vmlinux # 期望输出类似ELF 64-bit LSB executable, ARM aarch64, ...如果这个文件输出是MSB或者not stripped都没有那就趁早回去找编译机吧。我在项目里见过几次用zImage硬加载crash的crash倒是没拒但符号全是错的后面分析完全没法看。2. 隐藏参数之一phys_offset——crash找不到内存的根源2.1 crash在ARM64上是怎么找物理内存的ARM64 Linux的地址转换关系可以用一个粗略公式表达虚拟地址 物理地址 - PHYS_OFFSET PAGE_OFFSETcrash在初始化阶段需要建立“虚拟地址→ramdump里物理内存数据”的映射关系。对于标准kdump转储ELF core文件头里的program header会直接标出每个内存段的物理加载地址和虚拟地址crash照着读就行。但ramdump不是这样它提供的往往是原始物理内存内容甚至是从某个固定物理地址开始的一段连续数据没有ELF头那样丰富的信息。这时crash需要知道一个关键值物理内存的起始地址也就是phys_offset。如果你不告诉它它会尝试从vmlinux内部的某些符号、mem_map数组或启动信息里去猜。一旦猜错后面所有符号地址的换算全都会偏表现出来就是bt回溯栈时函数名要么全是unknown要么地址看起来“很合理但明显不对”用struct命令读结构体出来的字段值要么全0要么是0xdeadd00d这类垃圾值log命令读出来的dmesg时间线断断续续甚至乱码。我见过最坑的一次是符号地址解析出来看起来完全正常栈回溯的前几帧也像模像样但读到第5层之后全部进入了一个不存在的地址区间。折腾了两小时才发现是phys_offset偏了4KB导致栈上的某个链表指针被错位解读。2.2 怎么把正确的phys_offset找出来获取phys_offset有几种途径按可靠性排序方法一从dmesg启动日志里直接读如果设备还能启动或者ramdump里的log段是完整的可以这样找dmesg | grep -i memory # 或者 dmesg | grep -i Physical memory map有时候启动日志会有这样一行Memory: 6230016K/8388608K available (14336K kernel code, ...)这只是总容量。更直接的是看/proc/iomem第一行通常就是00000000-7fffffff : System RAM这种情况下phys_offset就是0x00000000。但高通平台未必都从0开始有些平台DDR基地址是0x80000000之类所以一定要以实际平台为准。方法二从vmlinux的ELF段里推算用readelf看vmlinux的LOAD段能拿到内核镜像链接期认为的物理加载地址readelf -l vmlinux | grep LOAD对比-m参数里想设置的phys_offset看看推算出的虚拟地址段是否和vmlinux符号表里的_text地址吻合。这个方法适合手头没有dmesg的离线场景。方法三从ramdump内容里反推这种方法最土但也最常救命。ARM64内核镜像的头部有固定的魔数字节MZ或ARM\x64你在ramdump里搜索这个特征找到内核镜像在物理内存里的实际加载位置再对比vmlinux里_text的链接虚拟地址就能算出物理偏移。我习惯先跑一两条命令做粗定位# 在ramdump里搜索ARM64镜像头部特征 grep -abo $\x41\x52\x4d\x64 ramdump.bin | head -20一旦找到特征偏移再用dd把对应字节抠出来看或者直接在crash里配合rd验证。这种方法在KASLR场景下也同样有效因为虽然虚拟地址随机了但内核镜像在物理内存里的存放位置还是有规律可循的。2.3 这个参数最容易翻车的地方phys_offset的坑不在于“不知道原理”而在于“你以为你设对了”。有个细节经常被忽略高通的ramdump头部里每个segment标出的地址可能已经包含了phys_offset的信息也可能只给了一个相对DDR基址的偏移。具体来说有些平台的ramdump解析工具会在转换时自动把物理基地址加进segment地址里导致转换出的ELF core里的物理地址范围和你手动传入的phys_offset叠加出现“二次偏移”。我自己的习惯是在转换之前先解析一遍ramdump头部把segment表导出来看一遍确认每个段的地址范围是物理地址还是相对偏移再决定后续要传什么参数。另外不同平台DDR基址可能不一样别把8155上拿到的phys_offset直接套到8550kalama上。高通的SoC迭代很频繁每次新平台bringup时都要重新确认一次这个值。3. 隐藏参数之二KASLR偏移——所有符号地址都在撒谎3.1 KASLR开启后vmlinux里的符号地址已经不作数了KASLR内核地址空间布局随机化在ARM64平台默认是开启的至少在较新的内核版本里是这样。它会把内核镜像整体加载到一个随机化的虚拟地址上而不是vmlinux链接脚本里写死的那个固定地址。这意味着vmlinux文件里_text符号写的是0xffff000010080000但实际运行时它可能被加载到了0xffff800010080000或者别的什么位置。两者之间差的那个值就是KASLR偏移。crash工具在解析ramdump时如果拿不到这个偏移它按vmlinux里的链接地址去算找到的内存位置自然就是错的。有人会问为什么crash直接从ramdump里找_text的实际地址不行理论上它可以通过kallsyms或者通过搜索内核镜像特征可以做到但实际初始化时受限于ramdump内容的完整性和可读性crash经常无法自动推导。所以这个偏移往往需要人工确认后传进去。怎么知道内核开没开KASLR看启动cmdlinecat /proc/cmdline如果里面有nokaslr说明关闭了否则大概率开着。另外dmesg里如果有Kernel Offset这一行也说明KASLR生效了。3.2 从ramdump和log里反推KASLR偏移方法一直接抄log里的Kernel Offset这是最省事的方式。在能开机的情况下开机完成后执行dmesg | grep Kernel Offset输出大概是这样的Kernel Offset: 0x44000000 from 0xffff000010000000 (relocation range: 0xffff000010000000-0xffff0000bfffffff)这个0x44000000就是KASLR偏移。如果你已经有crash在分析一个设备崩溃设备开机期间没抓到这条log那可以从ramdump里的log_buf里找。不过log_buf本身的地址也是受KASLR影响的这就有点鸡生蛋蛋生鸡。方法二对比kallsyms和vmlinux符号如果ramdump里的kallsyms数据可以访问你可以找出某个符号在运行时的实际地址然后和vmlinux里该符号的链接地址做差grep _text /proc/kallsyms # 设备上执行如果权限允许如果没法在设备上执行可以从ramdump里直接搜索符号名对应的地址数据但这个过程比较手工适合脚本化处理。方法三从内核镜像头的特征推算这种方法跟前文说phys_offset的方法类似在ramdump里找到ARM64内核镜像的头部魔数确认镜像实际加载的物理位置再对比vmlinux里_text的链接地址和链接物理地址算出虚拟偏移。这种方法离线也能做而且KASLR偏移、phys_offset可以一起推算出来互相印证。3.3 把KASLR偏移应用到crashcrash工具传入KASLR偏移的参数在不同版本里不一样有的用-m kaslr_offset0x...有的通过--kaslr参数直接传高通crash分支可能还支持其他扩展名称。我建议先跑一下crash -m help确认当前版本支持哪些参数名。一个典型的加载命令长这样crash vmlinux ramdump.elf -m kaslr_offset0x44000000 -m phys_offset0x80000000加载成功后用mach命令检查crash mach重点看KERNELOFFSET字段是不是你传的值以及PAGE_OFFSET、VMALLOC_START等是否合理。如果KERNELOFFSET显示不对你传参的方式可能不对或者参数命名不对先查版本。验证符号是否对位我习惯用这种方式crash sym _text crash sym _stext crash sym init_task如果这几个符号地址落在预期范围内比如0xffff...高地址段而且bt能看到人类可读的函数名说明符号基本对位了。3.4 顺带把printk/log_buf偏移的坑也讲一下很多人在crash里敲log命令期望看到完整的崩溃前内核日志有时候却只看到一部分或者日志时间线从某个点开始就乱掉了。这往往也是KASLR偏移没有完全处理好的表现。log命令读取的是__log_buf里的环形缓冲区如果__log_buf的地址因为KASLR偏移没设对而解析错了crash就会从一个错误的内存地址读数据。读取出来的内容有时候恰好是内存中的其他数据看起来像日志但根本不是完整的时间线。处理方式还是先确认KASLR偏移正确然后用crash log -T-T会打印带时间戳的日志行如果时间戳连续、环形缓冲区首尾衔接正常说明log读取是可靠的。如果日志还是不对我偶尔会用rd直接去看__log_buf符号指向的内存crash p (char *)__log_buf crash rd __log_buf -p但这只能作为辅助验证手段核心还是把KASLR偏移搞对。4. 隐藏参数之三vmalloc区域参数、页表参数和其他标量4.1 vmalloc_start / vmalloc_end栈回溯走到模块区就断ARM64平台的vmalloc区域在PAGE_OFFSET之上、vmemmap之下模块第三方内核模块、vmalloc分配的栈、ioremap内存都集中在这一带。crash初始化时会计算vmalloc区间的起止地址用于判断某个虚拟地址是否落在可访问的内存区间内。如果这个区间设置不正确最典型的表现是栈回溯的早期帧都在内核image区域走到某个ko模块的函数时就断了显示cannot access vmallocd address。这是因为crash认为那个地址不属于它的可访问内存范围实际上数据明明就在ramdump里。处理方式是在-m参数里显式指定-crash vmlinux ramdump.elf -m vmalloc_start0xffff800080000000 -m vmalloc_end0xffff8000bfffffff这个值从哪来最简单的方法是看设备上的/proc/vmallocinfo或者/proc/kallsyms里的vmalloc区域边界也可以从vmlinux链接脚本里推算。还有一招是从crash的mach命令输出里读取它算出来的VMALLOC范围如果和实际的差太多再手动覆盖。4.2 页表和arm64_kernel_vmalloc_base这类细节高通平台较新的内核里CONFIG_ARM64_VA_BITS通常配置为39、48或者52对应的PAGE_OFFSET、VMALLOC_START也会不一样。crash在解析arm64内核时会尝试从vmlinux的符号和配置里推断这些值但某些场景下需要手动确认。我实际遇到过一次某个平台的ramdump解析出来整个swapper_pg_dir里的页表项看起来全是对的但虚拟地址翻译出来的物理地址始终落在ramdump文件范围之外。排查到最后发现是CONFIG_ARM64_VA_BITS48的情况下crash把PAGE_OFFSET算到了4-level页表的默认值而实际内核启用了5-level页表。这种情况下光是传phys_offset和kaslr_offset是不够的还需要确认页表级别和VA_BITS是否匹配。简单说arm64_kernel_vmalloc_base、idmap_pg_dir、tramp_pg_dir这些符号在正常加载时不用管但如果你用vtop命令翻译一个虚拟地址时得到的结果跟ramdump里的数据对不上就得回头检查页表相关配置了。我的习惯是加载完先随机挑一个内核符号做vtop验证如果翻译结果落在ramdump实际包含的物理地址范围内才认为页表层面的设置是OK的。4.3 RAMDUMP_OFFSET / 二次偏移的混淆陷阱这部分我想专门提一下因为很多人在转换脚本里见过--offset或者RAMDUMP_OFFSET之类的变量但搞不清楚它跟phys_offset的区别导致参数叠加出错。phys_offset描述的是DDR物理内存的起始地址而ramdump解析脚本里的offset往往指的是“ramdump文件内部数据相对于segment起始位置的偏移”或者“某些段需要额外平移的偏移量”。这两个概念一个影响地址转换的基地址一个影响文件数据读取的位置混在一起很容易出问题。我的建议是转换ramdump时先把header解析出来把每个segment的加载地址、文件偏移写到一个文本文件里确认清楚再决定传什么参数。转换后的ELF core如果本身已经把所有segment的物理地址都标对了那crash这边就不需要再叠加RAMDUMP_OFFSET只要传phys_offset、kaslr_offset这些关键参数就够了。很多人在crash里反复调不通其实不是crash的问题而是转换环节就已经引入了偏移误差。5. 完整实操高通8550(kalama)平台ramdump从加载到出栈5.1 一步一步来从ramdump.bin到crash提示符以kalamaSM8550平台为例我整理了一套固定流程每拿到一个新的ramdump都会按这个顺序走一遍。第一步确认ramdump的头信息。高通release包的解析工具里通常会附带一个ramdump_header文件或者你可以在解析环境里用Python解析头部# 示意代码读取ramdump头部segment表信息 import struct with open(ramdump.bin, rb) as fp: magic fp.read(16) print(magic:, magic) # 具体字段布局以平台release为准这里只做思想演示第二步用高通解析脚本转换成ELF corepython ramdump_parse.py -i ramdump.bin -o kalama_ramdump.elf --vmlinux vmlinux第三步确认符号文件和版本一致性。用modinfo和strings交叉验证甚至直接对比vmlinux和ramdump里提取出的linux_banner字符串strings ramdump.bin | grep Linux version strings vmlinux | grep Linux version这两个字符串里的版本号、编译时间必须完全一致。第四步加载crashcrash vmlinux kalama_ramdump.elf \ -m phys_offset0x80000000 \ -m kaslr_offset0x44000000第五步进crash后先跑一遍基础检查。检查项命令合格标准架构与偏移mach显示KERNELOFFSET正确VA_BITS正确符号定位sym _stext地址在高地址段且和预期一致栈回溯bt -a或foreach bt每个CPU都有可读栈回溯日志log -T时间戳连续内容与设备侧日志吻合5.2 加载成功后先做三件事第一件事是看mach输出确认所有内存布局参数都正确。这一步不要省哪怕你只是换了一个ramdump参数也可能变了。第二件事是bt选一个CPU的栈回溯看是否有符号、是否连续。如果第一帧就是cpu_do_idle或者某个中断处理函数并且后面的调用链能对应上平台的idle流程说明基础状态是好的。第三件事是log看崩溃前的内核日志是不是完整的。高通的ramdump里一般会包含完整的log_buf只要KASLR偏移正确log -T能看到从开机到panic的完整时间线。这一步同时也是对kaslr偏移的二次验证——如果日志开头有乱码或者全是NULL多半是偏移没设置对。三件事都通过了再开始具体的崩溃现场分析。5.3 排查崩溃时的命令组合真正分析崩溃原因时我常用的命令组合是这样crash bt # 当前CPU回溯 crash foreach bt # 所有CPU回溯适合死锁/panic多核现场 crash log -T # 时间线日志 crash ps -m # 进程列表及内核线程 crash struct task_struct addr -x # 读进程结构体 crash rd 地址 -p # 物理内存读取 crash mod -s # 已加载模块列表及符号 crash sym 函数名 # 符号定位 crash dis 地址 # 反汇编如果你怀疑崩溃和内存故障相关可以在设备侧提前用memtester跑一轮内存压力测试确认硬件层面的DDR是否稳定。这在高通平台问题分析里挺常见——RAM故障和软件错误有时候在crash里的表现几乎一样都是随机地址的栈回溯和寄存器异常值。高通平台分析内存问题还有一个特点是ramdump本身如果出现大段全0或者全0xCC的数据可能就是DDR读回异常需要跟硬件同事确认采样点。另外我在反汇编某些关键代码路径时会用到qemu-system-aarch64做一个辅助验证环境把vmlinux丢进qemu启动一个最小系统把ramdump里的某个内存段load进guest的对应物理地址然后让crash或者gdb去分析。这样做的目的是绕开某些crash版本对高通私有ramdump格式的兼容性问题单独验证某段内存内容的语义。虽然操作起来多几步但在面对“crash说这地址读不到但ramdump里明明有数据”的情况时非常管用。6. 踩坑回顾最值得写进团队wiki的三条经验6.1 一定要先对内核版本和编译ID这是所有坑里最冤枉的。目标设备跑的内核和手头vmlinux根本不是同一个编译产物轻则符号偏移错误重则结构体成员完全对不上甚至崩溃现场的任务列表都是乱的。crash虽然不会主动提示“你的vmlinux和ramdump不匹配”但只要你做一些深一点的检查比如看init_task的comm字段或者某个驱动结构的成员值就会发现明显的错位。所以我现在拿到ramdump的第一件事就是对比linux_banner字符串和build-idreadelf -n vmlinux | grep Build ID如果vmlinux带build-id这个值最好和设备的/sys/kernel/notes一致。没有build-id的老内核至少把版本、编译时间、编译器信息都对一遍。这一步多花一分钟后面省几小时。6.2 别迷信“高通开箱即用”的crash分支高通release包里的crash版本确实能直接解析自家ramdump但缺点也很明显版本往往偏老对较新内核特性的支持滞后。比如某些新平台开启CONFIG_ARM64_VA_BITS52或者新页表格式后老版本crash可能计算错误或者不能识别某些新的section类型。我的做法是自己从上游编译一份crash作为主力再保留高通的crash分支做交叉验证。遇到两边行为不一致的情况通常是上游crash对ramdump格式支持不足或者高通的crash有私有补丁这时候优先确认内存布局参数而不是急着换工具。6.3 先解决“能不能读内存”再分析“符号对不对”很多新手在crash里遇到符号无法解析第一反应是“符号文件坏了”或者“vmlinux不对”。我的排查顺序永远是rd一个已知物理地址确认ramdump数据能否被正确读取vtop翻译一个内核符号的虚拟地址看物理地址是否落在ramdump范围内sym验证符号表是否有解析最后才去纠结构体字段值为什么不对。这个顺序的核心逻辑是物理内存都读不到符号再对都没用。反过来物理内存能读了符号错位的问题通常可以通过phys_offset、kaslr_offset这类参数修正。把定位顺序理顺排障效率会高很多。我自己现在分析每份高通ramdump之前都会先写一个固定的加载脚本把已知的phys_offset、kaslr_offset、vmalloc参数固化进去同时把ramdump头部信息导出一份存档。每次拿到新平台第一件事不是找代码问题而是先更新这个脚本里的平台参数表。等参数表稳定下来crash分析就是一条流水线后面再复杂的崩溃现场只要先能进去总会找到线索。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑