深入 Linux C/C++ 调试工具之 objdump(反汇编一眼看懂)

目录

一、前言——objdump 工具简介

1.1 什么是 objdump

1.2 与相关工具对比

二、使用场景——何时使用 objdump

1、确认编译器到底生成了什么指令

2、崩溃 / dmesg 只有地址时对照汇编

3、源码 + 汇编对照(需 -g)

4、查看段布局与文件头

5、静态库 / 目标文件里「谁调用了谁」

6、与 nm 分工:nm 找符号,objdump 看身体

7、内核模块 / 固件粗看

三、核心原理——工作原理深度解析

3.1 读的是什么

3.2 -d 与 -D 的区别

3.3 地址:VMA vs 文件偏移

3.4 与「可执行视图」

3.5 反汇编质量依赖什么

四、命令参数——常用选项详解

4.1 高频选项

4.2 常用组合

4.3 交叉与指定机器

4.4 输出怎么读(x86_64 AT&T 示例)

五、使用实战——实际案例分析

示例:objdump_demo.c

Makefile

5.1 案例一:看某个函数的汇编

5.2 案例二:-O0 vs -O2 对比

5.3 案例三:源码混排 -S

5.4 案例四:PLT 与外部调用

5.5 案例五:只看 .rodata 字符串

5.6 案例六:Intel 语法与地址窗

5.7 案例七:目标文件与重定位

5.8 案例八:strip 前后对比

5.9 与 gdb / nm / readelf 联动手势

六、常见问题——疑难解答

6.1 中文乱码 / 看不懂 AT&T

6.2 -S 没有源码

6.3 地址和运行时对不上

6.4 反汇编「数据段」出现奇怪指令

6.5 交叉架构反汇编失败

6.6 很大的文件刷屏

6.7 与 llvm-objdump 的差异

七、总结——要点回顾

7.1 三句话

7.2 推荐工作流

7.3 最小命令清单

7.4 调试工具链位置


一、前言——objdump 工具简介

1.1 什么是 objdump

objdump 是 GNU Binutils 中的 目标文件分析与反汇编工具,可读 ELF 可执行文件、.o、.a、.so、.ko 等,输出 段头、符号、反汇编、重定位、源码混排 等信息。

一句话定位:

objdump 是二进制的 「解剖刀」 —— 不用跑起来,就能看清段怎么排、函数汇编长什么样、调用落在哪。

1.2 与相关工具对比

工具

侧重

典型场景

objdump

反汇编 + 段/重定位总览

看汇编、对地址、查调用

nm

符号表一览

「有没有这个符号」

readelf

ELF 结构字段

段表、动态节、解释器

gdb

运行时调试

断点、变量、core

addr2line

地址→源码行

崩溃地址反查

perf annotate

热点旁注汇编

性能热点指令

链接报错 / 行为怪异 / 崩溃地址
  ├─ nm / readelf     →  符号、段、依赖在不在
  ├─ objdump -d/-S    →  函数汇编与调用关系
  ├─ addr2line        →  地址对应哪一行
  └─ gdb              →  运行时验证

二、使用场景——何时使用 objdump

1、确认编译器到底生成了什么指令

优化开没开、inline 有没有真正内联、分支是否被优化掉:

objdump -d ./app | less
objdump -d ./app | grep -A40 '<hot_func>:'

2、崩溃 / dmesg 只有地址时对照汇编

objdump -d ./app | grep -n '1234:'    # 粗查
# 更好:gdb list *addr 或 addr2line;objdump 用于看附近指令序列

3、源码 + 汇编对照(需 -g

gcc -g -O2 -c foo.c
objdump -S -d foo.o
# 或
objdump -S -d ./app | less

4、查看段布局与文件头

objdump -h ./app          # section headers
objdump -f ./app          # file header 摘要
objdump -p ./app          # private headers(程序头等,视格式)

5、静态库 / 目标文件里「谁调用了谁」

objdump -d libfoo.a
objdump -r foo.o          # 重定位项
objdump -T ./lib.so       # 动态符号(类似动态 nm)

6、与 nm 分工:nm 找符号,objdump 看身体

nm ./app | grep my_func
objdump -d ./app | sed -n '/<my_func>:/,/^$/p'

7、内核模块 / 固件粗看

objdump -d mymod.ko | less
objdump -h mymod.ko

注意:.ko 未加载时许多地址为 0,重定位后才是运行时地址。

三、核心原理——工作原理深度解析

3.1 读的是什么

objdump 解析目标文件格式(Linux 上主要是 ELF):

ELF File
  ├─ ELF Header          → -f
  ├─ Program Headers     → -p(加载视图)
  ├─ Section Headers     → -h(链接视图:.text/.data/.rodata…)
  ├─ .symtab / .dynsym   → -t / -T
  ├─ .rela*              → -r / -R
  └─ .text 字节流        → -d / -D 反汇编成指令

反汇编时:按机器架构(x86_64、ARM…)把 机器码 译成 助记符;若有调试信息,-S 可把源码行插入汇编旁。

3.2 -d-D 的区别

选项

行为

-d / --disassemble

只反汇编 期望含代码的段(通常 .text 等)

-D / --disassemble-all

试图反汇编 更多段(含数据段,结果可能「假指令」)

日常看函数用 -d;怀疑代码落在非常规段再用 -D

3.3 地址:VMA vs 文件偏移

objdump -h ./app
# 列: VMA / LMA / File off / Algn

概念

含义

VMA

加载后虚拟地址(反汇编左边一列通常是这个)

File off

在文件中的偏移

PIE/ASLR

运行时基址会变;objdump 显示的是 链接时/文件中的地址

因此:线上 dmesgip 要先减掉加载基址,再和 objdump -d 里的地址对齐;或直接用 gdb/addr2line 更省事。

3.4 与「可执行视图」

objdump -d 展示的是 静态镜像;动态链接的外部调用常表现为:

call   401030 <printf@plt>

真正跳进 libc 要经 PLT/GOT,运行时由动态链接器填好。objdump 看到的是 PLT 桩,不是最终 libc 指令流。

3.5 反汇编质量依赖什么

  • 正确的 架构--architecture / 交叉 objdump)

  • 是否有 符号(strip 后函数名变 addr

  • 是否有 调试信息-S 才好看)

  • 编译优化等级(-O0-O2 判若两人)

四、命令参数——常用选项详解

4.1 高频选项

选项

含义

-d

反汇编代码段

-D

反汇编所有相关段

-S

反汇编并混排源码(需 -g

-h

section headers

-f

文件头摘要

-x

几乎所有头信息

-t

符号表(类似 nm)

-T

动态符号表

-r

重定位信息

-R

动态重定位

-s

以十六进制转储各段内容

-j .text

只处理指定段

-C / --demangle

C++ 名字还原

-l

行号信息(与源码相关)

-M intel

x86 用 Intel 语法(默认 AT&T)

-M att

AT&T 语法

--start-address= / --stop-address=

限制反汇编地址范围

-w / --wide

宽行,少折行

4.2 常用组合

objdump -d ./app                          # 基础反汇编
objdump -dC ./app                         # + C++ demangle
objdump -S -d ./app                       # 源码交叉
objdump -M intel -d ./app                 # Intel 语法
objdump -d --start-address=0x401000 --stop-address=0x401200 ./app
objdump -h -j .text -j .data ./app
objdump -s -j .rodata ./app | head
objdump -t ./app | grep ' F .text'

4.3 交叉与指定机器

aarch64-linux-gnu-objdump -d firmware.elf
objdump -m aarch64 -d something.bin       # 原始二进制需指定 arch(慎用)

4.4 输出怎么读(x86_64 AT&T 示例)

0000000000401136 <main>:
  401136: 55                      push   %rbp
  401137: 48 89 e5                mov    %rsp,%rbp
  40113a: e8 f1 fe ff ff          call   401030 <puts@plt>
  40113f: b8 00 00 00 00          mov    $0x0,%eax
  401144: 5d                      pop    %rbp
  401145: c3                      ret

含义

左侧地址

指令 VMA

中间机器码

原始字节

右侧助记符

反汇编文本

<name>:

符号入口

五、使用实战——实际案例分析

示例:objdump_demo.c

#include <stdio.h>

volatile int g_sink;

/* 简单算术:看寄存器传参与 add */
__attribute__((noinline)) int add_ints(int a, int b)
{
	return a + b;
}

/* 故意保留循环体,便于 O0/O2 对比 */
__attribute__((noinline)) int hot_loop(int n)
{
	int i, s = 0;
	for (i = 0; i < n; i++)
		s += i * 3 + 1;
	return s;
}

__attribute__((noinline)) void leaf(int x)
{
	g_sink = x;
}

__attribute__((noinline)) void call_chain(int x)
{
	leaf(add_ints(x, 1));
}

static const char k_msg[] = "objdump_demo: hello rodata";

int main(void)
{
	int r = hot_loop(100);
	call_chain(r);
	printf("%s sink=%d sum=%d\n", k_msg, g_sink, add_ints(2, 3));
	return 0;
}

Makefile

CC      ?= gcc
CFLAGS  ?= -g -O0 -Wall -Wextra -fno-omit-frame-pointer
TARGET  := objdump_demo

.PHONY: all clean demo disasm intel source rodata reloc strip-cmp help

all: $(TARGET)

$(TARGET): objdump_demo.c
	$(CC) $(CFLAGS) -o $@ $<

objdump_demo.o: objdump_demo.c
	$(CC) $(CFLAGS) -c -o $@ $<

disasm: $(TARGET)
	objdump -d $(TARGET) | sed -n '/<add_ints>:/,/^$$/p'

intel: $(TARGET)
	objdump -M intel -d $(TARGET) | sed -n '/<add_ints>:/,/^$$/p'

source: $(TARGET)
	objdump -S -d $(TARGET) | sed -n '/<call_chain>:/,/^$$/p'

rodata: $(TARGET)
	objdump -s -j .rodata $(TARGET)

reloc: objdump_demo.o
	objdump -r objdump_demo.o
	@echo "----"
	objdump -d objdump_demo.o | head -50

strip-cmp: $(TARGET)
	cp $(TARGET) $(TARGET).nostrip
	strip -o $(TARGET).stripped $(TARGET).nostrip
	@echo "=== with symbols ==="
	objdump -d $(TARGET).nostrip | grep -E '<add_ints>:' || true
	@echo "=== stripped (name may disappear) ==="
	objdump -d $(TARGET).stripped | grep -E '<add_ints>:' || echo "(no add_ints symbol label)"

demo: all
	@echo "objdump -d ./$(TARGET) | less"
	@echo "make disasm | intel | source | rodata | reloc | strip-cmp"

clean:
	$(RM) $(TARGET) $(TARGET).o $(TARGET).nostrip $(TARGET).stripped objdump_demo.o

help: demo

5.1 案例一:看某个函数的汇编

objdump -d ./objdump_demo | sed -n '/<add_ints>:/,/^$/p'
# 或
objdump -d ./objdump_demo | awk '/<add_ints>:/{p=1} p; /^$/{if(p&&++n>0) exit}'

期望: 看到参数传递(System V AMD64:edi/esi 等)、addret

5.2 案例二:-O0 vs -O2 对比

make clean && make CFLAGS='-g -O0'
objdump -d ./objdump_demo | sed -n '/<hot_loop>:/,/^$/p' > /tmp/o0.txt

make clean && make CFLAGS='-g -O2'
objdump -d ./objdump_demo | sed -n '/<hot_loop>:/,/^$/p' > /tmp/o2.txt

diff -u /tmp/o0.txt /tmp/o2.txt | less

期望: -O2 循环更紧、可能少存栈、指令更短或向量化痕迹。

5.3 案例三:源码混排 -S

make CFLAGS='-g -O1'
objdump -S -d ./objdump_demo | sed -n '/<call_chain>:/,/^$/p'

期望: C 源码行与汇编交错,便于对照「这一行变成了哪些指令」。

5.4 案例四:PLT 与外部调用

objdump -d ./objdump_demo | grep -E 'printf@plt|puts@plt|call'
objdump -R ./objdump_demo | head    # 动态重定位

期望: 对 libc 的调用经 @plt-R 可见 JUMP_SLOT 等。

5.5 案例五:只看 .rodata 字符串

objdump -s -j .rodata ./objdump_demo
# 或
objdump -s ./objdump_demo | sed -n '/Contents of section \.rodata/,/^Contents/p'

期望: 可见字符串字面量十六进制/ASCII。

5.6 案例六:Intel 语法与地址窗

objdump -M intel -d ./objdump_demo | sed -n '/<add_ints>:/,/^$/p'
ADDR=$(nm ./objdump_demo | awk '/ add_ints$/{print $1}')
objdump -d --start-address=0x$ADDR ./objdump_demo | head -20

5.7 案例七:目标文件与重定位

make objdump_demo.o
objdump -r objdump_demo.o
objdump -d objdump_demo.o | head -40

期望: .o 中对全局/外部符号的 R_X86_64_* 重定位;链接后地址被钉死或改走 PLT。

5.8 案例八:strip 前后对比

cp objdump_demo objdump_demo.nostrip
strip objdump_demo -o objdump_demo.stripped
objdump -d objdump_demo.nostrip   | grep '<add_ints>'
objdump -d objdump_demo.stripped  | grep '<add_ints>' || echo "symbol name gone"
objdump -t objdump_demo.stripped | head

期望: strip 后反汇编还在,但函数名符号大量消失,只剩地址。

5.9 与 gdb / nm / readelf 联动手势

nm ./app | grep foo          # 符号在不在、地址多少
objdump -d ./app | ...       # 该地址指令序列
readelf -S ./app             # 段权限、对齐
gdb ./app -ex "disassemble foo" -batch   # 运行时/带调试的另一种反汇编

六、常见问题——疑难解答

6.1 中文乱码 / 看不懂 AT&T

objdump -M intel -d ./app     # 改 Intel 语法
# 或学习 AT&T:src/dst 顺序与 Intel 相反,立即数带 $,寄存器带 %

6.2 -S 没有源码

需编译加 -g,且源文件路径仍可访问;剥离调试信息后无效。

6.3 地址和运行时对不上

PIE + ASLR。用:

gdb -q ./app -ex "start" -ex "info proc mappings" -ex quit
# 或
readelf -h ./app | grep Type   # DYN=PIE

objdump 地址是链接视图;运行时基址另算。

6.4 反汇编「数据段」出现奇怪指令

那是 -D 把数据当代码解了。改用 -d,或只 -j .text

6.5 交叉架构反汇编失败

换对应三元组的 *-objdump,或 -m <arch>

6.6 很大的文件刷屏

objdump -d ./app --start-address=... --stop-address=...
objdump -d ./app | less
llvm-objdump -d --symbolize-operands   # 若更习惯 LLVM 工具

6.7 与 llvm-objdump 的差异

选项大体类似,细节与默认语法可能不同;团队统一一种即可。


七、总结——要点回顾

7.1 三句话

  1. objdump 把 ELF 的代码与元数据变成可读的段表和汇编。

  2. 日常:objdump -d 看函数;-S 对照源码;-h/-r/-R 看布局与重定位。

  3. 和 nm/readelf/gdb 分工:nm 找名,objdump 看指令,gdb 看运行时。

7.2 推荐工作流

符号在哪 → nm / objdump -t
函数咋编译 → objdump -d / -S
外部咋调 → 看 @plt,objdump -R
崩溃地址 → 换算后对照 objdump,或直接 gdb/addr2line
优化效果 → 同函数 O0/O2 的 objdump diff

7.3 最小命令清单

objdump -d ./app
objdump -dC ./app | less
objdump -S -d ./app
objdump -M intel -d ./app
objdump -h ./app
objdump -t ./app
objdump -T ./lib.so
objdump -r ./foo.o
objdump -R ./app
objdump -s -j .rodata ./app
objdump -d --start-address=0x... --stop-address=0x... ./app

7.4 调试工具链位置

工具

回答的问题

objdump

这段二进制指令/段长什么样?

nm

有哪些符号?

readelf

ELF 头/段/动态节细节?

addr2line

地址对应源码行?

gdb

运行时栈与变量?

perf annotate

热点落在哪些指令?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值