起因

C 程序调试钻到“编译器到底生成了什么代码”这一层时,就需要反汇编工具:验证优化结果、确认内联有没有发生、分析没有源码的库、或者把一个地址对应到具体指令。objdump 是 GNU binutils 里的反汇编主力,同族的 addr2line 负责地址→函数名/行号的反查,两篇配合着用见《用 Addr2line 可以将函数地址解析为函数名》

需求

  • 把 .o / 可执行文件 / 静态库里的机器码反汇编成汇编
  • 能与源码、行号交替对照
  • 掌握常用参数组合,不用每次翻 man

技术实现原理

反汇编为什么是可能的

可执行文件的代码段(.text)本来就是一串按指令集编码的字节流,CPU 取指执行时就在不停“解码”它。objdump 做同样的事:通过 BFD 库读出 ELF 的各个 section,从 .text 起点开始按目标架构(x86/ARM/MIPS…)逐条把字节解码回汇编助记符。解码只依赖两样东西——起点正确、架构正确,它大多数“输出一串乱码”的问题也出在这两处(数据被当成指令解、架构选错)。

.o 和可执行文件:查哪个

  • main.o 是未重定位的:模块内函数地址是从 0 编号的临时地址,调用 printf 这类外部符号的位置是占位指令加重定位记录(用 -r 可以看)。在这里反汇编,适合看“编译器为这个函数生成了什么”;
  • main 是重定位完成的:函数地址就是运行时地址。要对照运行期崩溃地址、核对最终链接布局,用它。

参数背后的机制

  • -d 只反汇编标记为“含代码”的 section,-D 连数据一起解——数据被解成“指令”就是乱码,但反过来说,查“这串字节是什么指令”正是 -D 的用法;
  • -S 交替显示源码,依赖 -g 留下的 DWARF 行表,还要能按记录的路径找到源文件(见踩坑);
  • -C demangle:C++ 为了支持重载,把 void f(int) 编码成 _Z1fi 这类符号名,-C 负责翻回可读形式;
  • -j .text 只看指定段,-l 带文件名行号,-s 显示 section 的原始十六进制内容。

AT&T 还是 Intel 语法

x86 上 objdump 默认 AT&T 语法:mov %eax, %ebx源在前、目的在后,寄存器带 % 前缀、立即数带 $——与 Intel 语法(mov ebx, eax)正好相反。看惯 Intel 语法(Windows 工具链/IDA 背景)的,加 -M intel 切换即可。

1 objdump反汇编示例

源文件main.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* main.c */
#include <stdio.h>

void swap(int* first, int* second)
{
    int temp = *first;
    *first = *second;
    *second = temp;
}

int main(void)
{
    int a = 10;
    int b = 20;

    printf("a = %d; b = %d;\n", a, b);
    swap(&a, &b);
    printf("a = %d; b = %d;\n", a, b);

    return 0;
}

1.1 显示main.c的汇编代码

1
gcc -S -o main.s main.c

汇编文件main.s

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    .file   "main.c"
    .text
    .globl  swap
    .type   swap, @function
swap:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -24(%rbp)
    movq    %rsi, -32(%rbp)
    movq    -24(%rbp), %rax
    ...

1.2 目标文件反汇编

1
2
gcc -c -o main.o main.c
objdump -s -d main.o > main.o.txt

目标文件main.o的反汇编结果输出到文件main.o.txt
反汇编同时显示源代码

1
2
gcc -g -c -o main.o main.c
objdump -S -d main.o > main.o.txt

显示源代码同时显示行号

1
objdump -j .text -ld -C -S main.o > main.o.txt

1.3 可执行文件反汇编

1
2
gcc -o main main.c
objdump -s -d main > main.txt

反汇编同时显示源代码

1
2
gcc -g -o main main.c
objdump -S -d main > main.txt

1.4 objdump反汇编常用参数

objdump -d <file(s)>: 将代码段反汇编;
objdump -S <file(s)>: 将代码段反汇编的同时,将反汇编代码与源代码交替显示,编译时需要使用-g参数,即需要调试信息;
objdump -C <file(s)>: 将C++符号名逆向解析
objdump -l <file(s)>: 反汇编代码中插入文件名和行号
objdump -j section <file(s)>: 仅反汇编指定的section

2 objdump帮助信息

输出objdump帮助信息:

objdump –help 或者 man objdump

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Usage: objdump <option(s)> <file(s)>
    Display information from object <file(s)>.
    At least one of the following switches must be given:
    -a, --archive-headers    Display archive header information
    -f, --file-headers       Display the contents of the overall file header
    -p, --private-headers    Display object format specific file header contents
    -P, --private=OPT,OPT... Display object format specific contents
    -h, --[section-]headers  Display the contents of the section headers
    -x, --all-headers        Display the contents of all headers
    -d, --disassemble        Display assembler contents of executable sections
    -D, --disassemble-all    Display assembler contents of all sections
    -S, --source             Intermix source code with disassembly
    -s, --full-contents      Display the full contents of all sections requested
    -g, --debugging          Display debug information in object file
    -e, --debugging-tags     Display debug information using ctags style
    -G, --stabs              Display (in raw form) any STABS info in the file
    -W[lLiaprmfFsoRt] or
    --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index]
                           Display DWARF info in the file
    -t, --syms               Display the contents of the symbol table(s)
    -T, --dynamic-syms       Display the contents of the dynamic symbol table
    -r, --reloc              Display the relocation entries in the file
    -R, --dynamic-reloc      Display the dynamic relocation entries in the file @<file>                  Read options from <file>
    -v, --version            Display this program's version number
    -i, --info               List object formats and architectures supported
    -H, --help               Display this information

 The following switches are optional:
    -b, --target=BFDNAME     Specify the target object format as BFDNAME
    -m, --architecture=MACHINE     Specify the target architecture as MACHINE
    -j, --section=NAME       Only display information for section NAME
    -M, --disassembler-options=OPT Pass text OPT on to the disassembler
    -EB --endian=big         Assume big endian format when disassembling
    -EL --endian=little      Assume little endian format when disassembling
      --file-start-context   Include context from start of file (with -S)
    -I, --include=DIR        Add DIR to search list for source files
    -l, --line-numbers       Include line numbers and filenames in output
    -F, --file-offsets       Include file offsets when displaying information
    -C, --demangle[=STYLE]   Decode mangled/processed symbol names
                             The STYLE, if specified, can be `auto', `gnu',
                                  `lucid', `arm', `hp', `edg', `gnu-v3', `java'
                                  or `gnat'
    -w, --wide               Format output for more than 80 columns
    -z, --disassemble-zeroes       Do not skip blocks of zeroes when disassembling
      --start-address=ADDR   Only process data whose address is >= ADDR
      --stop-address=ADDR    Only process data whose address is <= ADDR
      --prefix-addresses     Print complete address alongside disassembly
      --[no-]show-raw-insn   Display hex alongside symbolic disassembly
      --insn-width=WIDTH     Display WIDTH bytes on a single line for -d
      --adjust-vma=OFFSET    Add OFFSET to all displayed section addresses
      --special-syms         Include special symbols in symbol dumps
      --prefix=PREFIX        Add PREFIX to absolute paths for -S
      --prefix-strip=LEVEL   Strip initial directory names for -S
      --dwarf-depth=N        Do not display DIEs at depth N or greater
      --dwarf-start=N        Display DIEs starting with N, at the same depth
                             or deeper
      --dwarf-check          Make additional dwarf internal consistency checks.      

objdump: supported targets: elf64-x86-64 elf32-i386 elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
objdump: supported architectures: i386 i386:x86-64 i386:x64-32 i8086 i386:intel i386:x86-64:intel i386:x64-32:intel l1om l1om:intel k1om k1om:intel plugin

The following i386/x86-64 specific disassembler options are supported for use
with the -M switch (multiple options should be separated by commas):
    x86-64      Disassemble in 64bit mode
    i386        Disassemble in 32bit mode
    i8086       Disassemble in 16bit mode
    att         Display instruction in AT&T syntax
    intel       Display instruction in Intel syntax
    att-mnemonic
                Display instruction in AT&T mnemonic
    intel-mnemonic
                Display instruction in Intel mnemonic
    addr64      Assume 64bit address size
    addr32      Assume 32bit address size
    addr16      Assume 16bit address size
    data32      Assume 32bit data size
    data16      Assume 16bit data size
    suffix      Always display instruction suffix in AT&T syntax
Report bugs to <http://bugzilla.redhat.com/bugzilla/>.

3 参考资料

1 objdump反汇编用法示例
2 objdump命令的使用
3 GNU Binary Utilities: objdump – Sourceware
4 stackoverflow – objdump

踩坑记录与注意事项

  • -S 只见汇编不见源码:编译时没带 -g,或者源文件不在 DWARF 记录的路径上(在别的机器上编译的)。前者重编,后者用 -I 目录 补源码搜索路径;
  • 地址对不上:拿运行期崩溃地址去查 .o 查不到——.o 还没重定位;要用最终链接产物查,so/PIE 程序还要先减去加载基址;
  • 把数据当代码:-D 解数据段会出现大段“指令”,那是常量表不是程序逻辑,别顺着读;
  • AT&T 顺序反直觉:mov 的源/目的与 Intel 语法相反,加 -M intel 免得读反;
  • 优化后源码交错错位:-O2 下指令重排、内联展开,-S 的对照只是近似,行号顺序不等于执行顺序;
  • 大文件只看一段:用 --start-address/--stop-address-j 指定 section,别整文件硬扫;
  • 配合 -t / -r 一起看:.o 里对 printf 的调用在 -d 输出里是一条空地址指令,加上 -r 的重定位记录、-t 的符号表才看得完整。