想象你是一位考古学家,在黄沙之下挖出一卷被封印的“天书”。书页上全是杂乱的花纹,其实那是远古“编译器工匠”用 CPU 的母语——机器码——刻下的工程图纸。编译器日复一日把 C、C++ 甚至 AI 算子库翻译成这种 CPU 唯一听得懂的密文,一旦落笔,人类便再难直读。 幸运的是,考古队里有两位专家:一位档案管理员只翻看封面、目录和索引,从不改动正文——它就是 readelf;一位文字翻译官则逐行解读花纹,把密文还原成可读的汇编语言——它就是 objdump。两者合称“反汇编工具链”,是读懂二进制天书的两把钥匙。本文从零开始,带你认识它们的职能、原理与实战心法。

岗位关联

掌握 objdump 与 readelf,等于拿到二进制世界的放大镜:定位崩溃、核对性能、审计依赖都从读懂这两条命令开始。对 AI 工程师而言,这是模型部署与推理调优路上不可绕过的底层基本功。

目录
  1. 可视化演示
  2. 反汇编与工具链概述
  3. ELF文件结构解析
  4. readelf核心用法
  5. objdump核心技术
  6. 调试与动态分析
  7. 工程实践案例
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

反汇编与工具链概述

想象一位考古学家在戈壁滩发现一卷天书:纸张上满是看似杂乱的花纹,其实那是远古工匠用“机器语”写下的工程图纸。编译器就是那位工匠——它把 C、C++ 乃至 AI 算子库翻译成 CPU 唯一听得懂的机器码,也就是那一个个由字节构成的指令花纹。天书一旦写成,人眼便无法直接阅读。

于是考古队需要两种专家。第一种是档案管理员,负责整理书的封面、装帧、目录和索引,他从不改动内容——这就是 readelf;第二种是翻译官,负责把每一页的花纹逐行解读成可读的文字——这就是 objdump。二者的专业合称“反汇编与系统工具”。

实际工程中,编译产物可能是 .o 目标文件、.so 动态库或最终可执行程序,它们在 Linux 上统一使用 ELF(Executable and Linkable Format)封装。objdump 负责“反汇编”——将 ELF 中 .text 节区的机器码反向还原为汇编指令;readelf 负责“读结构”——展示 ELF 头、节区表、程序头、符号表等元数据。二者都来自 GNU binutils 工具包,常用于崩溃定位、逆向分析和性能核对。

有了“结构管理员 + 翻译官”的组合,考古队才谈得上深入解读天书。接下来的章节,我们先看看天书的整体装帧(ELF 结构),再分别认识两位专家的看家本领。

源码→编译汇编→汇编器机器码→反汇编汇编\text{源码} \xrightarrow{\text{编译}} \text{汇编} \xrightarrow{\text{汇编器}} \text{机器码} \xrightarrow{\text{反汇编}} \text{汇编}
这条链从左到右是“写天书”:编译器把人类语言逐级翻译成 CPU 母语;从右往左是“读天书”:objdump 做最后一步逆翻译,把机器码还原成汇编。
  1. 编译编译器(如 gcc)将 C 源码转换为汇编语言,完成从人类语言到半机器语的第一次翻译。
    C 源码→汇编指令\text{C 源码} \rightarrow \text{汇编指令}
  2. 汇编器把汇编助记符编码成 CPU 能执行的字节码,写入 ELF 文件。
    汇编指令→机器码字节\text{汇编指令} \rightarrow \text{机器码字节}
  3. 反汇编objdump 逆着编码方向,把字节流还原成可读的汇编指令。
    机器码字节→汇编指令\text{机器码字节} \rightarrow \text{汇编指令}

ELF文件结构解析

一本完整的古卷,通常有封面、扉页、目录、正文和章节页眉。ELF 文件同样如此:最前面的 ELF 头是“封面与版权页”,节区表是“目录”,程序头表是“加载须知”,符号表则是“人名索引”。理解了这个类比,readelf 的各类输出就都有了归宿。

当 readelf -h 打印 ELF 头时,我们读到的是封面信息:开头的 4 个魔数 7f 45 4c 46(即 ASCII 的 ELF)相当于防伪标记;e_entry 字段记录程序第一条指令的入口地址;e_shoff 记录节区表在整个文件中的偏移——相当于“目录在第几页”。

节区(Section)是链接器视角下的区划:.text 存放代码,.data 存放已初始化数据,.bss 存放未初始化数据,.symtab 存放符号表。程序头(Program Header)则是加载器视角下的“段”(Segment),描述运行时要把哪些区域映射进内存。两者一个服务链接,一个服务加载,不可混淆。

理解了装帧,考古队才能知道哪些章节是正文(.text)、哪些是注记(.strtab)、哪些是索引(.symtab)。下一步,档案管理员 readelf 就要正式上岗。

sh_off(i)=e_shoff+i×e_shentsize\text{sh\_off}(i) = \text{e\_shoff} + i \times \text{e\_shentsize}
节区表就像天书的目录页:从 e_shoff 指向的位置起,每条记录占 e_shentsize 字节,第 i 条记录的位置 = 目录起点 + 记录序号 × 单条记录高度。
  1. e_shoff 字段ELF 头中的“节区表起始偏移”,相当于天书封面上标注的目录页码。
    e_shoff\text{e\_shoff}
  2. e_shentsize 字段每条节区头记录的字节数,64 位 ELF 下固定为 64 字节,像目录每行有固定高度。
    e_shentsize\text{e\_shentsize}
  3. 线性定位第 i 条记录的位置 = 起始偏移 + i × 单条记录大小,readelf 正是靠这个公式逐条读出目录。
    sh_off(i)=e_shoff+i×e_shentsize\text{sh\_off}(i) = \text{e\_shoff} + i \times \text{e\_shentsize}

readelf核心用法

档案管理员的工作原则是“只看不动”:绝不修改天书内容,只把封面的防伪线、目录的章节、索引的人名一一登记造册。这正是 readelf 的定位——一个只读的 ELF 结构浏览器。用它查看任何二进制都无需担心改变文件内容。

常用参数要记牢:-h 打印 ELF 头;-l 查看程序头(段);-S 列出节区表;-s 显示符号表;-r 显示重定位表。其中 -s 是排查崩溃和逆向最常用的:它既能列出 .symtab 静态符号,也能列出 .dynsym 动态符号,告诉我们每个函数、全局变量的名字和地址。

符号表本身也是一个节区,节区头里记录了它的总字节数和单个符号条目大小。因此符号个数不需要一个个数,做一次除法即可。这提醒我们:readelf 输出中的每一列几乎都能在 ELF 头或节区头里找到出处,结构决定视图。

档案管理员把目录和索引交到翻译官手上后,真正的硬仗开始了:把天书正文逐行翻译出来。这就是 objdump 的主场。

nsym=sh_sizesh_entsizen_{\text{sym}} = \frac{\text{sh\_size}}{\text{sh\_entsize}}
sh_size 是符号表节区占用的总字节数,sh_entsize 是单个符号记录的长度,两者相除即是符号条数——这就像用索引页总行高除以每行高度,得到行数。
  1. sh_size符号表节区占用的总字节数,相当于索引页的总高度。
    sh_size\text{sh\_size}
  2. sh_entsize单个符号记录的长度,64 位下通常为 24 字节。
    sh_entsize\text{sh\_entsize}
  3. 相除求条数用总大小除以单条大小,得到符号数量,一条除法省去逐条数数的麻烦。
    nsym=sh_sizesh_entsizen_{\text{sym}} = \frac{\text{sh\_size}}{\text{sh\_entsize}}

objdump核心技术

翻译官拿到档案管理员的索引,翻开正文,把每一行的花纹逐字念成人类语言。objdump 的核心动作是“反汇编”:读取 .text 节区的机器码字节,按照 CPU 指令集把每个字节解码成汇编助记符与操作数。

最常用的命令是 objdump -d 目标文件,它自动找到可执行节区进行反汇编;objdump -S 会在汇编中间穿插源码行(前提是编译时加了 -g);-M intel 可以切换成 Intel 语法;-s 则直接以十六进制转储字节,适合检查数据区。反汇编架构默认与文件一致,跨架构时需要 -m 参数或交叉工具链。

翻译官采用的策略叫“线性扫描”——从节区头开始,一条挨一条解码。但 x86 指令长度不固定(1 到 15 字节),如果碰到数据夹杂在代码里(如跳转表),很容易“串行”译错后继指令,这正是所有反汇编工具面临的基本难题。

翻译官逐页翻译的效率虽高,却可能被书中的“插图”(数据区)带偏。所幸现代 ELF 里还有批注和行号——下一节我们看看如何利用调试信息与动态链接,让译文更精准。

addrk+1=addrk+len(insnk)\text{addr}_{k+1} = \text{addr}_k + \text{len}(\text{insn}_k)
线性扫描反汇编的核心递推:当前地址加上当前指令的字节长度,就是下一条指令的地址;翻译官正是这样一页接一页地翻译下去。
  1. 当前地址addr_k 是第 k 条指令在内存中的起始地址,翻译官正读到这一页。
    addrk\text{addr}_k
  2. 指令长度len(insn_k) 表示当前指令占几个字节,x86 指令长度从 1 到 15 不等,必须先解码才能知道。
    len(insnk)\text{len}(\text{insn}_k)
  3. 地址累加把长度加到当前地址上,即得到下一条指令的位置,循环往复直到节区末尾。
    addrk+1=addrk+len(insnk)\text{addr}_{k+1} = \text{addr}_k + \text{len}(\text{insn}_k)

调试与动态分析

有些天书在修订时,工匠会在页边留下批注:标注这段文字出自原稿第几行、这个变量原来叫什么。这样的批注在 ELF 中就是调试信息,通常以 DWARF 格式存放在 .debug_* 节区里。保留批注的天书才能让翻译官对照原文,这也解释了为什么 -g 编译的产物更容易读懂。

实际发布的可执行文件常被 strip 去掉符号和调试信息,但动态链接的符号(.dynsym 与 PLT/GOT 表)必须保留——因为程序运行时还要靠它们把外部函数地址“填”进跳转表。readelf -d 能查看动态段,objdump -T 能反汇编动态符号对应的桩代码。

对 AI 工程师而言,这一步通常意味着“性能核对”:用 objdump -S 确认热路径是否被编译器向量化,用 readelf -s 确认符号是否被内联或 stripped,再结合基地址重定位理解加载时地址变化的规律。

批注、行号、动态符号就像考古队的定位系统。有了它们,最后一步——从崩溃现场还原真相——才能水到渠成。

actual_addr=base+reloc_offset\text{actual\_addr} = \text{base} + \text{reloc\_offset}
PIE/ASLR 下,模块每次装载的内存基址都不同;运行时真实地址 = 加载基址 + 模块内偏移。崩溃日志里的 pc 正是这样换算回函数内的。
  1. 加载基址base 是程序被装载进内存时的起始地址,在 ASLR 机制下每次运行都不同。
    base\text{base}
  2. 模块内偏移reloc_offset 是函数或数据在模块内的位置,由重定位表给出,相当于书内的页码编号。
    reloc_offset\text{reloc\_offset}
  3. 地址合成基址与偏移相加得到运行时真实地址,动态链接器正是这样修正 GOT 表项的。
    actual_addr=base+reloc_offset\text{actual\_addr} = \text{base} + \text{reloc\_offset}

工程实践案例

考古队的完整工作流通常是这样:先用档案管理员摸清墓葬结构(ELF 布局),再用翻译官解读碑文(反汇编),最后把碎片拼成事故时间线。工程上的三项典型任务分别是逆向分析、崩溃定位、安全审计,而它们都依赖工具链协作。

崩溃定位是最常见的实战场景。程序崩溃时内核留下的 core dump 会记录崩溃时刻的程序计数器 pc。拿到 pc 后,减去模块基址得到模块内偏移,再在符号表中找到包含该偏移的函数,然后用 objdump -d 反汇编该函数附近代码,最终定位到导致 SIGSEGV 的指令。

安全审计则是“找茬”:readelf -s 列出所有导出符号,objdump -d 抽查可疑函数体,看看是否有访问网络、读文件等越权行为;逆向分析更接近“补脑”——没有文档的第三方库,只能靠反汇编推断调用约定与数据流。

从结构到符号再到指令,考古队的“读天书”方法论已经闭环。反汇编工具链不是魔法,而是把二进制这块“黑盒”一层层打开的钥匙——只要你肯像考古学家一样耐心。

offset=pc−module_base ⇒ sym=symtab[offset]\text{offset} = \text{pc} - \text{module\_base} ~\Rightarrow~ \text{sym} = \text{symtab}[\text{offset}]
把崩溃地址减去模块基址得到模块内偏移,再在符号表里查找该偏移属于哪个函数区间,就能锁定出错的函数——这是“崩溃地址 → 函数名 → 源码行”链路的地基。
  1. 程序计数器pc 记录崩溃瞬间 CPU 正在执行的指令地址,是事故现场最关键的定位信标。
    pc\text{pc}
  2. 减基址pc 减去模块基址,把全局地址换算成模块内偏移,才能与符号表对齐。
    offset=pc−module_base\text{offset} = \text{pc} - \text{module\_base}
  3. 查符号表在符号表中查找包含该偏移的函数区间,锁定函数名后再用 objdump 反汇编还原现场。
    sym=symtab[offset]\text{sym} = \text{symtab}[\text{offset}]

AI 应用场景

推理框架性能调优

阶段:模型优化与部署

解决的问题:AI 推理引擎(TensorRT / ONNX Runtime)会把算子编译成高度优化的二进制,源码级调优无从下手。传统做法靠反复试参数,而用 objdump -d 可以直接检查目标平台的向量指令(AVX/NEON/SVE)是否真正生成、热点循环是否按预期展开。

具体案例:某团队将 transformer 部署到 ARM 服务器,延迟超预期。用 objdump -d 检查卷积热路径,发现编译选项未加 -march=armv8-a,编译器退化为标量循环;再用 readelf -S 确认 .text 段中缺少向量化常数表,重新编译后吞吐提升 31%。

端侧模型崩溃定位

阶段:测试与线上运维

解决的问题:手机端 AI SDK 崩溃时,日志只有 pc 地址和一串十六进制 backtrace。传统做法是盲查日志反复复现,而正确做法是结合 readelf 找模块基址、objdump 反汇编 pc 附近指令、addr2line 换算源码行,一次定位。

具体案例:人脸检测 App 偶发 SIGSEGV,平台日志给出 pc=0x7abc……。工程师用 readelf -l 找到模块基址,objdump -d 反汇编崩溃函数,发现代码对空 cv::Mat 调用了 .data 成员;NDK 侧补判空后崩溃清零。

供应链安全审计

阶段:模型安全与合规

解决的问题:从第三方下载的预编译推理库(.so / wheel)看不到源码,是否夹带私货?传统做法只比对哈希值,而用 readelf -s 列出导出符号、objdump -d 抽查可疑函数体,可以直接发现未文档化的越权行为。

具体案例:安全工程师审计某开源 OCR 库的 .so,readelf -s 发现一个未文档化的导出函数 network_check;objdump -d 反汇编确认其内部调用了 socket 与 connect,判定为可疑外联后替换该依赖。

Python 演示

python98 行
import structimport sys ELF_HEADER_FMT = '<16sHHIQQQIHHHHHH'  def read_elf_header(f):    f.seek(0)    data = f.read(64)    fields = struct.unpack(ELF_HEADER_FMT, data)    return {        'magic': fields[0][:4],        'elfclass': fields[0][4],        'type': fields[1],        'entry': fields[4],        'phoff': fields[5],        'shoff': fields[6],        'shentsize': fields[11],        'shnum': fields[12],        'shstrndx': fields[13],    }  def read_sections(f, hdr):    f.seek(hdr['shoff'])    sections = []    for i in range(hdr['shnum']):        sh = struct.unpack('<IIQQQQIIQQ', f.read(64))        sections.append({            'name_off': sh[0],            'offset': sh[4],            'size': sh[5],            'entsize': sh[9],        })    return sections  def section_names(f, hdr, sections):    shstr = sections[hdr['shstrndx']]    f.seek(shstr['offset'])    blob = f.read(shstr['size'])    names = []    for s in sections:        end = blob.find(b'\x00', s['name_off'])        names.append(blob[s['name_off']:end].decode('ascii', 'replace'))    return names  OPCODE_MAP = {    0x55: 'push %ebp',    0x5d: 'pop %ebp',    0x90: 'nop',    0xc3: 'ret',}  def disasm(byte_list):    lines = []    i = 0    while i < len(byte_list):        b = byte_list[i]        if b == 0x83 and i + 2 < len(byte_list):            imm = byte_list[i + 2]            lines.append((i, 'sub $%d, %%esp' % imm))            i += 3        else:            lines.append((i, OPCODE_MAP.get(b, 'db 0x%02x' % b)))            i += 1    return lines  def main(path):    with open(path, 'rb') as f:        hdr = read_elf_header(f)        if hdr['magic'] != b'\x7fELF':            print('not an ELF file')            return        if hdr['elfclass'] != 2:            print('only 64-bit ELF is supported')            return        print('entry=0x%x shoff=0x%x shnum=%d' % (hdr['entry'], hdr['shoff'], hdr['shnum']))        if hdr['shnum'] == 0:            return        sections = read_sections(f, hdr)        names = section_names(f, hdr, sections)        for n, s in zip(names, sections):            print('%-12s off=0x%-8x size=0x%-7x' % (n, s['offset'], s['size']))        print('--- mini disasm ---')        demo = [0x55, 0x83, 0xec, 0x08, 0xc3]        for addr, text in disasm(demo):            print('0x%04x: %s' % (addr, text))  if __name__ == '__main__':    if len(sys.argv) < 2:        print('usage: python mini_readelf.py <elf-file>')        sys.exit(1)    main(sys.argv[1])
目的:用纯 Python 实现 mini_readelf——解析 ELF 头、节区表和节区名称,并演示一个微型线性扫描反汇编器,把 5 个字节还原成 3 条汇编指令。
  1. fields = struct.unpack(ELF_HEADER_FMT, data)按固定格式一次性拆解 64 字节 ELF 头,就像翻开封面读取版权页。
  2. 'magic': fields[0][:4],取前 4 字节魔数,校验文件是否真是 ELF 天书。
  3. f.seek(hdr['shoff'])跳到节区表目录所在偏移,让读取指针就位。
  4. sh = struct.unpack('<IIQQQQIIQQ', f.read(64))拆解一条节区头记录,拿到 name/offset/size 等字段。
  5. end = blob.find(b'\x00', s['name_off'])在字符串表中找到节区名的结束位置。
  6. if b == 0x83 and i + 2 < len(byte_list):识别 0x83 开头的“立即数减法”指令,并检查剩余字节是否够长。
  7. lines.append((i, 'sub $%d, %%esp' % imm))把操作数拼进汇编文本,注意 %% 在格式化中表示字面量 %。
  8. for addr, text in disasm(demo):打印反汇编结果,地址从 0 开始随指令长度推进。

常见误区

误区“objdump -d 反汇编出来的代码,就是编译器生成、和我写的源码一一对应的汇编。”

正解编译经过优化后,汇编与源码不再一一对应。objdump 处理的是链接后最终机器码,包含重定位、PLT 跳转和指令调度,只能反映 CPU 实际执行的指令。就像翻译官翻译的是定稿印刷的天书,而不是作者原始草稿。

为何常见:初学者从“汇编课程”过渡时,默认 O0 级别的简单映射关系,没有意识到编译器优化会彻底改变指令序列。

误区“readelf 能看符号,objdump 也能看符号,它们功能差不多,选一个就可以。”

正解两者分工明确:readelf 专注 ELF 结构(头部、节区表、程序头、动态段),不负责反汇编;objdump 是通用二进制查看器,侧重反汇编和字节转储。档案管理员不翻译正文,翻译官也不整理目录,各司其职才是正确用法。

为何常见:两个命令同属 binutils,且部分输出字段相似(地址、大小),容易让人误以为等价。

误区“只要加上 -S 就能看到原始 C 源码,等于源码泄露。”

正解-S 只是把调试信息中的源码行号与汇编交错显示,前提是编译时用了 -g 且文件未被 strip。优化后源码行号并不精确,源码内容的完整还原更不可能。批注只写了“大概在第几页”,并没有把草稿也印进天书。

为何常见:教程动画常展示 -S 的惊艳效果,却忽略了“必须带调试信息”这一前提。

误区“可执行文件里一定有函数名和符号表,否则程序无法运行。”

正解符号表是给调试和链接用的元数据;strip 后 .symtab 会被移除,但 .text 机器码一个字节都不会少,程序照常运行。若连 .dynsym 都被清空(少见),就只能全靠反汇编硬读。撕掉人名索引,正文一字不少,天书照样能读,只是查人费劲。

为何常见:在 debug 版上养成的习惯,没接触过 release 版的 strip 流程。

面试题

Q1. 下面哪个十六进制序列是 ELF 文件的魔数(Magic Number)?

单选题 · easy

  • A.0x7F 0x45 0x4C 0x46← 正确答案
  • B.0x4D 0x5A 0x90 0x00
  • C.0xFF 0xD8 0xFF 0xE0
  • D.0xCA 0xFE 0xBA 0xBE

答案:A。ELF 文件首字节是 0x7F,后跟 ASCII 的 'E'(0x45)、'L'(0x4C)、'F'(0x46)。0x4D 5A 是 DOS PE 格式的魔数,FF D8 是 JPEG,CA FE BA BE 是 Java class 文件。魔数就像天书的防伪水印,读到它 readelf 才会承认“这是本 ELF 天书”。

Q2. 想查看 ELF 文件中有哪些节区(Section),readelf 应使用哪个参数?

单选题 · easy

  • A.readelf -h 查看 ELF 头
  • B.readelf -S 查看节区表← 正确答案
  • C.readelf -s 查看符号表
  • D.readelf -l 查看程序头

答案:B。-h 看 ELF 头,-S 列节区,-s 看符号表,-l 看程序头(段)。题干要“列出有哪些节区”对应 -S。类比档案管理员打开目录页,逐条登记章节名、偏移和大小。

Q3. objdump 中哪个参数可对目标文件进行反汇编,输出汇编指令?

单选题 · easy

  • A.objdump -r 显示重定位信息
  • B.objdump -s 转储节区字节
  • C.objdump -d 反汇编机器码← 正确答案
  • D.objdump -x 输出全部头部

答案:C。objdump -d 会找到 .text 等可执行节区并反汇编成汇编指令;-s 是十六进制字节转储,-r 看重定位,-x 打印全部头部。翻译官 objdump 正式开译用的就是 -d。

Q4. readelf 显示符号表后,符号数量如何从节区头中计算?

单选题 · mid

  • A.sh_size 乘以 sh_entsize
  • B.sh_size 除以 sh_entsize← 正确答案
  • C.sh_offset 减去 sh_addr
  • D.sh_link 加上 sh_info

答案:B。符号表是 ELF 里的一个普通节区。sh_size 是总字节数,sh_entsize 是单个符号记录大小,条目数 = 总大小 ÷ 单条大小。这就像用索引页总高度除以每行高度得到行数,其余选项没有物理意义。

Q5. 关于 objdump -d 和 objdump -S 的区别,下列说法正确的有?

多选题 · mid

  • A.-d 不穿插源码,只输出汇编← 正确答案
  • B.-S 会在汇编中穿插 C 源码行← 正确答案
  • C.-S 无需调试信息即可还原源码
  • D.-d 反汇编的是优化后的机器码← 正确答案

答案:ABD。-d 只反汇编机器码;-S 需要在编译时带 -g 才能穿插源码,且优化后行号可能错位,因此 C 错误;-d 输出的是链接后真实指令,当然反映优化结果。天书的 -S 是“带批注版”,没有批注(调试信息)就无从穿插。

Q6. 可执行文件经 strip 之后,下列哪种信息通常会消失?

单选题 · mid

  • A.机器码指令 .text 内容
  • B.ELF 头中的魔数字段
  • C.用于调试的符号表信息← 正确答案
  • D.节区数据段里的字符串

答案:C。strip 删除的是调试符号表(.symtab 等),机器码 .text、ELF 头和数据段都不受影响。撕掉天书的人名索引,正文一字不少;失去符号表后 objdump -d 仍能反汇编,只是地址处不再显示函数名。

Q7. 某 32 位 ELF 无法用 64 位 objdump 正常解读,最可能的原因是?

单选题 · hard

  • A.文件在传输过程中损坏了
  • B.架构字长不匹配,需用对应工具链← 正确答案
  • C.当前磁盘剩余空间不足
  • D.二进制缺少调试信息段

答案:B。64 位工具默认按 ELFCLASS64 解析,遇到 32 位 ELF 会解析失败或输出乱码。解决方法是安装 ia32 交叉工具链或用 -m 指定架构。这就像让只懂现代汉语的翻译官去读古篆——不是书坏了,而是需要对应的字典。

Q8. 关于 ELF 程序头(Program Header)与节区头(Section Header),说法正确的有?

多选题 · hard

  • A.程序头描述运行时加载所需的段← 正确答案
  • B.节区头描述链接视角下的区划← 正确答案
  • C.两者的作用完全等价可以互换
  • D.节区头是加载器运行时的必需

答案:AB。程序头描述段(segment),供加载器把文件映射进内存;节区头描述节(section),供链接器组织内容。二者视角不同,加载器运行时主要看程序头,节区头通常不是必需的,故 C、D 错误。一个是“搬运须知”,一个是“编目手册”。

Q9. 线性扫描反汇编算法中,下一条指令地址如何计算?

单选题 · hard

  • A.当前地址加上当前指令字节长度← 正确答案
  • B.当前地址乘以固定常数二
  • C.直接跳到函数起始地址处
  • D.取当前指令的操作数字段值

答案:A。线性扫描从节区起始地址开始,每解码完一条指令,就把指令长度累加到地址上,得到下一条地址。这与天书翻译逐行推进同理。固定乘以常数或跳函数开头都会跳乱整段指令流。

Q10. PIE 可执行文件加载时,动态重定位条目 R_X86_64_RELATIVE 填入的地址是?

单选题 · extreme

  • A.编译期就确定的绝对虚拟地址
  • B.加载基地址与重定位加数之和← 正确答案
  • C.符号表条目的字符串偏移量
  • D.节区头部记录的文件偏移量

答案:B。PIE 程序编译期只知道相对布局,装载时由动态链接器把“加载基地址 + 加数”填入 GOT 或变量地址。A 是静态链接的旧思路;C、D 分别属于字符串表和节区头,与重定位地址无关。这正是考古队换算崩溃地址时使用的同一套“基址 + 偏移”法则。