2.2. Binutils
GNU Binutils 是 GNU 项目下的二进制工具集合,用于处理、分析和转换二进制文件 (如目标文件、可执行文件、库文件等),是 Linux/Unix 系统开发流程中的核心工具链组件之一。它涵盖了从汇编、链接到二进制文件分析的完整环节, 支撑着 GCC 等编译器的底层运作,也是嵌入式开发、逆向工程、性能调优等场景的基础工具。
2.2.1. GNU Binutils的相关说明
2.2.1.1. 核心定位与背景
GNU Binutils 的设计目标是为 GNU 系统提供二进制文件处理能力,最初是为了配合 GCC(GNU 编译器集合)使用, 解决目标文件的汇编、链接、符号管理等问题。随着发展,其功能逐渐扩展到二进制文件分析、转换、优化等多个领域, 成为 Linux 开发环境中不可或缺的工具集。
Binutils 的核心依赖是BFD(Binary File Descriptor)库,该库提供了统一的接口来处理多种二进制文件格式(如 ELF、COFF、a.out 等),使得 Binutils 中的工具能跨格式工作。
2.2.1.2. 重要组成部分与功能
GNU Binutils 包含十余种工具,可分为核心工具(汇编、链接)、辅助工具(文件处理、符号分析)、 跨平台工具(Windows 兼容)三大类,以下是关键工具的详细说明:
2.2.1.2.1. 核心工具:汇编与链接
as(GNU 汇编器):
将汇编语言源文件(如.s、.asm)转换为目标文件(.o),是 GCC 编译流程中的第二步(第一步是预处理,第三步是链接)。as支持多种架构(x86、ARM、RISC-V、LoongArch 等),其输出为目标文件中的机器码,供链接器使用。ld(GNU 链接器):
将多个目标文件(.o)或库文件(.a、.so)组合成可执行文件或共享库。 链接器的主要作用是解决符号引用(如函数调用、全局变量访问),分配内存地址,并处理重定位(Relocation)。ld支持自定义链接脚本(Link Script),用于控制输出文件的布局(如代码段、数据段的位置)。后续章节我们会详细的介绍使用的例子。
gold(快速链接器):
ld的替代工具,专为ELF 格式(Linux 下的标准可执行文件格式)设计,链接速度比传统ld快数倍。它优化了符号解析和数据布局,适合大型项目(如内核、浏览器)的快速构建,但目前仍处于 beta 测试阶段。
2.2.1.2.2. 辅助工具:二进制文件分析与处理
objdump(目标文件信息显示):
显示目标文件或可执行文件的详细信息,包括反汇编代码、段结构(如.text、.data、.bss)、 符号表、重定位条目等。objdump是逆向工程和调试的常用工具,例如通过-d选项可查看程序的机器码对应的汇编指令。nm(符号表列举):
列出目标文件或库文件中的符号信息(如函数名、全局变量名),包括符号的地址、类型 (如T表示代码段符号、D表示数据段符号)。nm常用于定位未定义符号 (如链接错误中的undefined reference)或分析库文件的导出符号。objcopy(目标文件转换):
将目标文件从一种格式转换为另一种格式(如 ELF 转二进制、二进制转 S-record), 或修改目标文件的段属性(如将.text段设置为只读)。例如,嵌入式开发中常用objcopy将 ELF 可执行文件转换为裸二进制文件(.bin),用于烧录到 Flash 中。readelf(ELF 文件信息显示):
专门用于显示ELF 格式文件的信息(如 ELF 头、程序头表、节头表、符号表),比objdump更聚焦于 ELF 结构的细节。readelf常用于分析 ELF 文件的布局(如代码段的位置、动态链接信息), 是理解 Linux 可执行文件格式的关键工具。strip(符号剥离):
从目标文件或可执行文件中移除符号表、调试信息(如.debug_*段),减小文件体积。strip常用于发布版本的程序,避免泄露内部符号信息,或提高加载速度。ar(归档工具):
创建、修改或提取静态库(.a文件),静态库是多个目标文件的归档集合。
例如,ar rv libfoo.a foo1.o foo2.o可将foo1.o和foo2.o打包成libfoo.a,供链接器使用。ranlib(归档索引生成):
为静态库生成符号索引(.symtab段),加速链接器对静态库的符号查找。ranlib通常与ar配合使用,例如ar rv libfoo.a foo1.o && ranlib libfoo.a。addr2line(地址转行号):
将程序中的虚拟地址转换为源文件行号,常用于调试(如 coredump 分析)。
例如,addr2line -e a.out 0x400520可显示地址0x400520对应的源文件和行号。c++filt(C++ 符号反混淆):
将 C++ 编译器生成的混淆符号(如_ZN3Foo3barEv) 转换为人类可读的形式(如Foo::bar()),便于分析 C++ 程序的符号表。size(段大小列举):
列出目标文件或库文件的段大小(如.text、.data、.bss),帮助开发者了解程序的内存占用情况。
例如,size a.out可显示各段的大小之和。strings(可打印字符串列举):
列出文件中的可打印字符串(如 ASCII 字符串),常用于分析二进制文件中的隐藏信息(如错误信息、版权信息)。
2.2.1.2.3. 跨平台工具:Windows 兼容
dlltool(DLL 工具):
创建 Windows 动态链接库(.dll)的依赖文件(如.lib导入库),或生成 DLL 的导出表。
例如,dlltool -e exports.o -l dll.lib dll.o可从dll.o生成dll.lib,供 Windows 程序链接使用。windmc(Windows 消息编译器):
编译 Windows 消息资源文件(.mc),生成消息表和资源头文件,用于国际化(i18n)开发。windres(Windows 资源编译器):
编译 Windows 资源文件(.rc),生成目标文件(.o),用于嵌入图标、对话框等资源到可执行文件中。
2.2.1.3. 应用场景
GNU Binutils 的应用场景覆盖软件开发的全流程,以下是典型案例:
嵌入式开发:
嵌入式设备的资源有限(如 ROM/RAM 小),需用objcopy将 ELF 可执行文件转换为裸二进制文件(.bin), 再用烧录工具写入 Flash;用nm分析静态库的符号,确保没有冗余代码。逆向工程:
用objdump反汇编可执行文件,分析其机器码逻辑;用readelf查看 ELF 文件的动态链接信息(如依赖的共享库); 用strings查找二进制文件中的敏感字符串(如密码、URL)。性能调优:
用gprof(性能分析工具)分析程序的调用图,找出性能瓶颈;用strip移除调试信息,减小程序体积,提高加载速度。库开发:
用ar和ranlib创建静态库(.a),用ld生成共享库(.so);用nm检查库的导出符号,确保符合 API 设计规范。
下面我们针对不同的工具进行详细的案例说明使用。
2.2.2. ld链接器
链接器就是将多个目标文件(.o)或库文件(.a、.so)组合成可执行文件或共享库。
链接器的主要作用是解决符号引用(如函数调用、全局变量访问),分配内存地址,并处理重定位(Relocation)。
ld 支持自定义链接脚本(Link Script),用于控制输出文件的布局(如代码段、数据段的位置)。
例如: 我们先有一个main.c 和 hello.c
// main.c
void call_say_hello();
void exit();
void main()
{
call_say_hello();
exit();
}
void _start(){
main();
}
// hello.c
char *ptr = "hello LoongArch\n";
void call_say_hello() {
__asm__ (
"li.d $a7, 64 \t\n"
"li.d $a0, 1 \t\n"
"move $a1, %[wptr] \t\n"
"li.d $a2, %[count] \t\n"
"syscall 0x0"
: /*Output*/
: [wptr] "r" (ptr), /*Input*/
[count] "I" (16)
:"memory"
);
}
void exit() {
__asm__(
"li.d $a7, 93 \t\n"
"li.d $a0, 0 \t\n"
"syscall 0x0 \t\n"
:::"memory"
);
}
其中hello.c中的内嵌汇编,我们在后面的章节会详细的介绍。
shell> loongarch64-linux-musl-gcc main.c -o main.o -c
shell> loongarch64-linux-musl-gcc hello.c -o hello.o -c
shell> loongarch64-linux-musl-ld main.o hello.o -o hello
我们使用file hello可以查看生成ELF的信息
shell> file hello
hello: ELF 64-bit LSB executable, LoongArch, version 1 (SYSV), statically linked, not stripped
然后可以执行
shell> ./hello
Hello LoongArch!
2.2.2.1. LD的参数说明
下面我们详细说下ld链接器常使用的命令说明:
ld -o output hello.o -lc
将hello.o文件,生成output文件,hello.o中用到了C库中的函数,因此需要指定C库-lc
具体的参数如下说明:
-e entry 或者
–entry=entry:
使用entry显式的作为程序的入口地址,而不是使用默认的地址。
在LoongArch中默认的地址是0000000120000000
默认的输入数字是按照十进制来输入,比如--entry=10000是指十进制的10000。
如果想用十六进制或者八进制,需要使用相应的前缀。
比如:
--entry=0x10000 #指的是16进制的0x10000
--entry=010000 #指的是8进制的10000
-l namespec 或者 –library=namespec: 增加一个.a或者.so按照namespec指定的列表。典型的通过库搜索路劲,去查找libnamespec.a或者libnamespec.so文件
-L searchdir –library-path=searchdir Add path searchdir to the list of paths that ld will search for archive libraries and ld control scripts. You may use this option any number of times. The directories are searched in the order in which they are specified on the command line. Directories specified on the command line are searched before the default directories. All -L options apply to all -l options, regardless of the order in which the options appear. -L options do not affect how ld searches for a linker script unless -T option is specified.
-M –print-map 打印在链接过程中,指定符号的内存排布
```bash
loongarch64-linux-musl-ld main.o hello.o -o hello -M=link.map
```
```
There are no discarded input sections
Memory Configuration
Name Origin Length Attributes
*default* 0x0000000000000000 0xffffffffffffffff
Linker script and memory map
LOAD main.o
LOAD hello.o
[!provide] PROVIDE (__executable_start = SEGMENT_START ("text-segment", 0x120000000))
0x00000001200000e8 . = (SEGMENT_START ("text-segment", 0x120000000) + SIZEOF_HEADERS)
.note.gnu.build-id
*(.note.gnu.build-id)
.text 0x00000001200000e8 0xb4
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT_BY_NAME(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
.text 0x00000001200000e8 0x54 main.o
0x00000001200000e8 main
0x0000000120000114 _start
.text 0x000000012000013c 0x60 hello.o
0x000000012000013c call_say_hello
0x0000000120000174 exit
*(.gnu.warning)
```
2.2.3. 如何正确的使用readelf
readelf 是 Linux 下用于分析 ELF(Executable and Linkable Format)格式文件的核心工具,能够显示 ELF 文件的头部信息、节区结构、符号表、重定位信息等。以下是其常用用法及关键选项解析:
2.2.3.1. 基础用法与核心选项
2.2.3.1.1. 查看全部信息
readelf -a <elf文件>
等效选项:
-h -l -S -s -r -d -V -A -I作用:综合显示文件头、程序头、节头、符号表、重定位表等所有关键信息。
适用场景:快速全面分析未知 ELF 文件结构。
2.2.3.1.2. 查看 ELF 文件头
readelf -h <elf文件>
关键字段:
Magic:ELF 文件标识(0x7F 45 4C 46)。Class:32 位(ELF32)或 64 位(ELF64)。Machine:目标架构(如Intel 80386、ARM)。Entry point address:程序入口地址。
示例输出片段:
ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: LoongArch Version: 0x1 Entry point address: 0x120000540 Start of program headers: 64 (bytes into file) Start of section headers: 67672 (bytes into file) Flags: 0x43, DOUBLE-FLOAT, OBJ-v1 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 8 Size of section headers: 64 (bytes) Number of section headers: 24 Section header string table index: 23
2.2.3.1.3. 查看节区头表(Sections)
readelf -S <elf文件>
输出内容:节区名称、类型、大小、地址、权限等。
关键节区类型:
.text:代码段(可执行指令)。.data:已初始化全局变量。.bss:未初始化全局变量。.debug_info:调试信息。
示例:
There are 24 section headers, starting at offset 0x10858: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000120000200 00000200 000000000000001e 0000000000000000 A 0 0 1 [ 2] .hash HASH 0000000120000220 00000220 0000000000000038 0000000000000004 A 4 0 8 [ 3] .gnu.hash GNU_HASH 0000000120000258 00000258 000000000000001c 0000000000000000 A 4 0 8 [ 4] .dynsym DYNSYM 0000000120000278 00000278 00000000000000d8 0000000000000018 A 5 1 8 [ 5] .dynstr STRTAB 0000000120000350 00000350 0000000000000090 0000000000000000 A 0 0 1 [ 6] .rela.dyn RELA 00000001200003e0 000003e0 0000000000000090 0000000000000018 A 4 0 8 [ 7] .rela.plt RELA 0000000120000470 00000470 0000000000000060 0000000000000018 AI 4 17 8 [ 8] .plt PROGBITS 00000001200004d0 000004d0 0000000000000060 0000000000000010 AX 0 0 16 [ 9] .text PROGBITS 0000000120000540 00000540 0000000000000200 0000000000000000 AX 0 0 32 [10] .rodata PROGBITS 0000000120000740 00000740 000000000000000b 0000000000000000 A 0 0 8 [11] .eh_frame_hdr PROGBITS 000000012000074c 0000074c 0000000000000014 0000000000000000 A 0 0 4 [12] .eh_frame PROGBITS 0000000120000760 00000760 000000000000003c 0000000000000000 A 0 0 8 [13] .init_array INIT_ARRAY 000000012001fdf0 0000fdf0 0000000000000008 0000000000000008 WA 0 0 8 [14] .fini_array FINI_ARRAY 000000012001fdf8 0000fdf8 0000000000000008 0000000000000008 WA 0 0 8 [15] .dynamic DYNAMIC 000000012001fe00 0000fe00 00000000000001b0 0000000000000010 WA 5 0 8 [16] .got PROGBITS 000000012001ffb0 0000ffb0 0000000000000040 0000000000000008 WA 0 0 8 [17] .got.plt PROGBITS 000000012001fff0 0000fff0 0000000000000030 0000000000000008 WA 0 0 8 [18] .sdata PROGBITS 0000000120020020 00010020 0000000000000008 0000000000000000 WA 0 0 8 [19] .bss NOBITS 0000000120020028 00010028 0000000000000038 0000000000000000 WA 0 0 8 [20] .comment PROGBITS 0000000000000000 00010028 0000000000000012 0000000000000001 MS 0 0 1 [21] .symtab SYMTAB 0000000000000000 00010040 0000000000000570 0000000000000018 22 42 8 [22] .strtab STRTAB 0000000000000000 000105b0 00000000000001ed 0000000000000000 0 0 1 [23] .shstrtab STRTAB 0000000000000000 0001079d 00000000000000bb 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), p (processor specific)
2.2.3.1.4. 查看符号表
readelf -s <elf文件>
输出内容:符号名称、地址、大小、绑定类型(如
GLOBAL、STATIC)。选项扩展:
--dyn-syms:显示动态符号表(动态链接库相关)。--use-dynamic:优先使用动态符号表而非静态符号表。
示例:
Symbol table '.symtab' contains 16 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000001200000e8 0 SECTION LOCAL DEFAULT 1 .text 2: 00000001200001a0 0 SECTION LOCAL DEFAULT 2 .rodata 3: 00000001200001b8 0 SECTION LOCAL DEFAULT 3 .eh_frame 4: 0000000120010000 0 SECTION LOCAL DEFAULT 4 .data 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .comment 6: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c 7: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c 8: 0000000120010000 8 OBJECT GLOBAL DEFAULT 4 ptr 9: 000000012000013c 56 FUNC GLOBAL DEFAULT 1 call_say_hello 10: 0000000120000114 40 FUNC GLOBAL DEFAULT 1 _start 11: 0000000120010008 0 NOTYPE GLOBAL DEFAULT 4 __bss_start 12: 00000001200000e8 44 FUNC GLOBAL DEFAULT 1 main 13: 0000000120010008 0 NOTYPE GLOBAL DEFAULT 4 _edata 14: 0000000120010008 0 NOTYPE GLOBAL DEFAULT 4 _end 15: 0000000120000174 40 FUNC GLOBAL DEFAULT 1 exit
2.2.3.1.5. 5. 查看重定位信息
readelf -r <elf文件>
作用:显示程序中需要重定位的符号地址(如未定义函数或变量的占位符)。
典型场景:调试链接错误(如
undefined reference)。示例:
Relocation section '.rela.text' at offset 0x328 contains 4 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000000c 001000000047 R_LARCH_PCALA_HI2 0000000000000000 ptr + 0 00000000000c 000000000064 R_LARCH_RELAX 0 000000000010 001000000048 R_LARCH_PCALA_LO1 0000000000000000 ptr + 0 000000000010 000000000064 R_LARCH_RELAX 0 Relocation section '.rela.data' at offset 0x388 contains 1 entry: Offset Info Type Sym. Value Sym. Name + Addend 000000000000 000d00000002 R_LARCH_64 0000000000000000 .LC0 + 0 Relocation section '.rela.eh_frame' at offset 0x3a0 contains 8 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000001c 000600000063 R_LARCH_32_PCREL 0000000000000000 L0^A + 0 000000000020 000900000032 R_LARCH_ADD32 000000000000003c L0^A + 0 000000000020 000600000037 R_LARCH_SUB32 0000000000000000 L0^A + 0 00000000002f 000800000069 R_LARCH_ADD6 0000000000000034 L0^A + 0 00000000002f 00070000006a R_LARCH_SUB6 000000000000000c L0^A + 0 00000000003c 000a00000063 R_LARCH_32_PCREL 000000000000003c L0^A + 0 000000000040 000b00000032 R_LARCH_ADD32 0000000000000064 L0^A + 0 000000000040 000a00000037 R_LARCH_SUB32 000000000000003c L0^A + 0
2.2.3.2. 二、高级用法
下面介绍一些在调试ELF文件和操作系统中常用的命令。
2.2.3.2.1. 查看程序头表(Segments)
readelf -l <elf文件>
输出内容:程序加载时内存映射的段(如
LOAD、DYNAMIC)。关键字段:
Offset:文件中的偏移量。Virtual Address:加载到内存的虚拟地址。Flags:权限(R可读、W可写、X可执行)。
示例:
Elf file type is EXEC (Executable file) Entry point 0x120000540 There are 8 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000120000040 0x0000000120000040 0x00000000000001c0 0x00000000000001c0 R 0x8 INTERP 0x0000000000000200 0x0000000120000200 0x0000000120000200 0x000000000000001e 0x000000000000001e R 0x1 [Requesting program interpreter: /lib/ld-musl-loongarch64.so.1] LOAD 0x0000000000000000 0x0000000120000000 0x0000000120000000 0x000000000000079c 0x000000000000079c R E 0x10000 LOAD 0x000000000000fdf0 0x000000012001fdf0 0x000000012001fdf0 0x0000000000000238 0x0000000000000270 RW 0x10000 DYNAMIC 0x000000000000fe00 0x000000012001fe00 0x000000012001fe00 0x00000000000001b0 0x00000000000001b0 RW 0x8 GNU_EH_FRAME 0x000000000000074c 0x000000012000074c 0x000000012000074c 0x0000000000000014 0x0000000000000014 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x000000000000fdf0 0x000000012001fdf0 0x000000012001fdf0 0x0000000000000210 0x0000000000000210 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .hash .gnu.hash .dynsym .dynstr .rela.dyn .rela.plt .plt .text .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .got.plt .sdata .bss 04 .dynamic 05 .eh_frame_hdr 06 07 .init_array .fini_array .dynamic .got
2.2.3.2.2. 查看调试信息
readelf --debug-dump=info <elf文件>
选项扩展:
-w:显示 DWARF 调试信息(如行号、变量位置)。--dwarf-depth=N:限制调试信息显示的嵌套深度。
适用场景:逆向工程或调试崩溃问题。
示例:
readelf --debug-dump=info test_main Contents of the .debug_info section: Compilation Unit @ offset 0: Length: 0xa6 (32-bit) Version: 5 Unit Type: DW_UT_compile (1) Abbrev Offset: 0 Pointer Size: 8 <0><c>: Abbrev Number: 4 (DW_TAG_compile_unit) <d> DW_AT_producer : (indirect string, offset: 0x25): GNU C23 15.2.0 -mabi=lp64d -march=la64v1.0 -mfpu=64 -msimd=none -mcmodel=normal -mtune=generic -g <11> DW_AT_language : 29 (C11) <12> Unknown AT value: 90: 3 <13> Unknown AT value: 91: 0x31647 <17> DW_AT_name : (indirect line string, offset: 0x2f): test_main.c <1b> DW_AT_comp_dir : (indirect line string, offset: 0): /home/airxs/user/doc/loong64_os_works_doc/test <1f> DW_AT_low_pc : 0x120000704 <27> DW_AT_high_pc : 0x3c <2f> DW_AT_stmt_list : 0 <1><33>: Abbrev Number: 1 (DW_TAG_base_type) <34> DW_AT_byte_size : 8 <35> DW_AT_encoding : 7 (unsigned) <36> DW_AT_name : (indirect string, offset: 0x13): long unsigned int <1><3a>: Abbrev Number: 1 (DW_TAG_base_type) <3b> DW_AT_byte_size : 8 <3c> DW_AT_encoding : 5 (signed) <3d> DW_AT_name : (indirect string, offset: 0x5): long int <1><41>: Abbrev Number: 1 (DW_TAG_base_type) <42> DW_AT_byte_size : 1 <43> DW_AT_encoding : 6 (signed char) <44> DW_AT_name : (indirect string, offset: 0x87): char <1><48>: Abbrev Number: 5 (DW_TAG_const_type) <49> DW_AT_type : <0x41> <1><4d>: Abbrev Number: 1 (DW_TAG_base_type) <4e> DW_AT_byte_size : 8 <4f> DW_AT_encoding : 5 (signed) <50> DW_AT_name : (indirect string, offset: 0): long long int <1><54>: Abbrev Number: 1 (DW_TAG_base_type) <55> DW_AT_byte_size : 8 <56> DW_AT_encoding : 4 (float) <57> DW_AT_name : (indirect string, offset: 0x8c): double <1><5b>: Abbrev Number: 6 (DW_TAG_subprogram) <5c> DW_AT_external : 1 <5c> DW_AT_name : (indirect string, offset: 0xe): main <60> DW_AT_decl_file : 1 <61> DW_AT_decl_line : 5 <62> DW_AT_decl_column : 5 <63> DW_AT_prototyped : 1 <63> DW_AT_type : <0x98> <67> DW_AT_low_pc : 0x120000704 <6f> DW_AT_high_pc : 0x3c <77> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa) <79> DW_AT_call_all_tail_calls: 1 <79> DW_AT_sibling : <0x98> <2><7d>: Abbrev Number: 2 (DW_TAG_formal_parameter) <7e> DW_AT_name : (indirect string, offset: 0x93): argc <82> DW_AT_decl_file : 1 <82> DW_AT_decl_line : 5 <82> DW_AT_decl_column : 14 <83> DW_AT_type : <0x98> <87> DW_AT_location : 2 byte block: 91 6c (DW_OP_fbreg: -20) <2><8a>: Abbrev Number: 2 (DW_TAG_formal_parameter) <8b> DW_AT_name : (indirect string, offset: 0x98): argv <8f> DW_AT_decl_file : 1 <8f> DW_AT_decl_line : 5 <8f> DW_AT_decl_column : 32 <90> DW_AT_type : <0x9f> <94> DW_AT_location : 2 byte block: 91 60 (DW_OP_fbreg: -32) <2><97>: Abbrev Number: 0 <1><98>: Abbrev Number: 7 (DW_TAG_base_type) <99> DW_AT_byte_size : 4 <9a> DW_AT_encoding : 5 (signed) <9b> DW_AT_name : int <1><9f>: Abbrev Number: 3 (DW_TAG_pointer_type) <a0> DW_AT_byte_size : 8 <a0> DW_AT_type : <0xa4> <1><a4>: Abbrev Number: 3 (DW_TAG_pointer_type) <a5> DW_AT_byte_size : 8 <a5> DW_AT_type : <0x48> <1><a9>: Abbrev Number: 0
#### 十六进制/字符串转储
```bash
# 十六进制查看指定节区内容
readelf -x .text <elf文件>
示例:提取
.text节的机器码:Hex dump of section '.text': 0x1200000e8 63c0ff02 6120c029 76000027 7640c002 c...a .)v..'v@.. 0x1200000f8 00440054 00780054 00004003 6120c028 .D.T.x.T..@.a .( 0x120000108 76000026 6340c002 2000004c 63c0ff02 v..&c@.. ..Lc... 0x120000118 6120c029 76000027 7640c002 ffc7ff57 a .)v..'v@.....W 0x120000128 00004003 6120c028 76000026 6340c002 ..@.a .(v..&c@.. 0x120000138 2000004c 63c0ff02 7620c029 7640c002 ..Lc...v .)v@.. 0x120000148 ccf50718 8c010026 0b008103 04048003 .......&........ 0x120000158 85011500 06448003 00002b00 00004003 .....D....+...@. 0x120000168 7620c028 6340c002 2000004c 63c0ff02 v .(c@.. ..Lc... 0x120000178 7620c029 7640c002 0b748103 04001500 v .)v@...t...... 0x120000188 00002b00 00004003 7620c028 6340c002 ..+...@.v .(c@.. 0x120000198 2000004c ..L
# 字符串形式查看节区内容
readelf -p .rodata <elf文件>
示例:提取
.rodata节的字符串(一般都是字符串):shell> readelf -p .rodata hello String dump of section '.rodata': [ 0] Hello LoongArch!\n
2.2.3.2.3. 4. 架构特定信息
readelf -A <elf文件>
输出内容:CPU 架构扩展信息(如 SIMD 指令支持)。
2.2.3.3. 三、实战示例
2.2.3.3.1. 1. 分析可执行文件
# 查看入口地址和程序头
readelf -h /bin/ls
readelf -l /bin/ls
# 检查是否包含调试信息
readelf -S /bin/ls | grep debug
2.2.3.3.2. 2. 检查共享库依赖
# 查看动态段中的依赖库
readelf -d libc.so.6 | grep NEEDED
2.2.3.3.3. 3. 定位符号地址
# 查找函数 `printf` 的地址
readelf -s libc.so.6 | grep printf
2.2.3.4. 四、与其他工具对比
工具 |
优势 |
局限性 |
|---|---|---|
|
专注 ELF 结构,不依赖 BFD 库 |
功能较单一(如无反汇编能力) |
|
支持反汇编、多架构解析 |
依赖 BFD 库,可能受其缺陷影响 |
2.2.4. objdump
objdump 是 GNU Binutils 工具集中的核心命令行工具,用于解析和显示二进制文件(如 ELF、COFF 等格式)的详细信息,支持反汇编、符号表分析、段信息查看等功能。以下是其核心用法及典型场景:
2.2.4.1. 一、基础用法与核心选项
2.2.4.1.1. 1. 查看文件头信息
objdump -f <二进制文件>
作用:显示文件格式、入口地址、架构等元信息。
示例输出:
test: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0000000000401160
2.2.4.1.2. 2. 反汇编代码
objdump -d <二进制文件> # 反汇编可执行代码段(.text)
objdump -D <二进制文件> # 反汇编所有段(包括数据段)
objdump -d -j .text <文件> # 仅反汇编指定段(如 .text)
示例输出:
0000000000401160 <main>: 401160: 55 push %rbp 401161: 48 89 e5 mov %rsp,%rbp 401164: 48 83 ec 10 sub $0x10,%rsp
2.2.4.1.3. 3. 查看符号表
objdump -t <二进制文件> # 静态符号表(类似 nm -s)
objdump -T <二进制文件> # 动态符号表(仅共享库)
输出字段:地址、符号类型(如
T表示代码段符号)、绑定方式(g全局,l局部)。
2.2.4.1.4. 4. 查看段信息
objdump -h <二进制文件> # 显示节头信息(Size、VMA、Offset 等)
关键字段:
Idx:节索引。Name:节名称(如.text,.data)。Size:节大小(字节)。VMA:虚拟内存地址。
2.2.4.1.5. 5. 显示完整内容
objdump -s <二进制文件> # 以十六进制/ASCII 显示节内容
objdump -s -j .rodata <文件> # 查看只读数据段(如字符串常量)
示例:提取
.rodata中的字符串:Contents of section .rodata: 402000 48656c6c6f20576f726c6421 Hello World!
2.2.4.2. 二、高级用法
2.2.4.2.1. 1. 结合源代码反汇编
objdump -S <二进制文件> # 混合显示源代码与汇编指令(需编译时加 -g)
输出示例:
0000000000401160 <main>: #include <stdio.h> int main() { 401160: 55 push %rbp 401161: 48 89 e5 mov %rsp,%rbp printf("Hello World!\n"); 401164: 48 8d 3d e5 ff ff ff lea 0xffffffffffffffe5(%rip),%rdi }
2.2.4.2.2. 2. 指定架构与字节序
objdump -d --architecture=arm <ARM二进制文件> # 分析 ARM 架构代码
objdump -d -EB <文件> # 指定大端模式(默认小端)
2.2.4.2.3. 3. 重定位信息分析
objdump -r <二进制文件> # 显示重定位条目(如未解析的函数地址)
输出示例:
RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000012 R_X86_64_PC32 add-0x0000000000000004
2.2.4.2.4. 4. 动态链接信息
objdump -T <共享库> # 查看动态符号表(如依赖的库函数)
示例输出:
DYNAMIC SYMBOL TABLE: 0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 printf
2.2.4.2.5. 5. 地址范围过滤
objdump -d --start-address=0x400500 --stop-address=0x400520 <文件>
作用:仅显示指定地址范围内的反汇编代码。
2.2.4.3. 三、典型应用场景
2.2.4.3.1. 1. 逆向工程
分析恶意软件:通过
-d反汇编可疑程序,定位关键函数。提取 Shellcode:使用
-s导出.text段的原始机器码。
2.2.4.3.2. 2. 编译器调试
验证汇编优化:对比
-O0和-O2编译选项生成的汇编代码差异。调试链接错误:通过
-r检查未解析的重定位符号。
2.2.4.3.3. 3. 嵌入式开发
验证交叉编译结果:分析 ARM 目标文件的
.text段是否符合预期。内存布局分析:通过
-h检查.bss和.data段的大小。
2.2.4.3.4. 4. 安全研究
检测漏洞利用:通过
-d分析缓冲区溢出攻击的触发点。绕过反调试:解析混淆代码中的真实指令。
2.2.4.4. 四、与其他工具对比
工具 |
优势 |
局限性 |
|---|---|---|
|
支持多架构、反汇编与符号分析一体化 |
无调试功能(如断点、单步执行) |
|
动态调试能力强 |
无法直接查看二进制结构 |
|
专注 ELF 文件解析 |
不支持反汇编 |
2.2.4.5. 五、注意事项
调试信息依赖:使用
-S或-l需编译时添加-g选项。架构匹配:分析非 x86 文件时需指定
--architecture(如arm、riscv)。性能问题:对大型文件(如内核镜像)使用
-D可能耗时较长。权限要求:分析系统文件(如
/lib下的库)可能需要sudo。
2.2.4.6. 六、实战示例
2.2.4.6.1. 示例 1:分析崩溃地址
objdump -d myprogram | grep -A20 "0x400526" # 定位崩溃地址附近的代码
2.2.4.6.2. 示例 2:提取加密密钥
objdump -s -j .data myapp | grep "ENCRYPT_KEY" # 从数据段提取硬编码密钥
2.2.4.6.3. 示例 3:自动化脚本
#!/bin/bash
for file in *.so; do
echo "Checking $file..."
objdump -T $file | grep "GLIBC_2.28" # 检测 glibc 版本依赖
done
通过灵活组合选项,objdump 可以成为二进制分析的瑞士军刀。对于开发者,掌握其核心功能能显著提升调试和逆向工程效率。
2.2.5. objcopy
objcopy 是 GNU Binutils 中的核心工具,用于复制、转换和修改二进制文件(如 ELF、COFF、SREC 等格式),支持跨格式转换、节区操作、符号处理等功能。以下是其核心用法及典型场景:
2.2.5.1. 一、基础用法与核心选项
2.2.5.1.1. 1. 格式转换
将 ELF 文件转换为纯二进制文件(去除符号和重定位信息):
objcopy -O binary input.elf output.bin
关键选项:
-O <格式>指定输出格式(如binary、srec、elf32-i386)。应用场景:生成固件烧录文件或嵌入资源到代码中。
2.2.5.1.2. 2. 提取或删除节区
提取特定节(如
.text):objcopy -j .text -O binary input.elf output.text.bin
删除冗余节(如
.comment和.note):objcopy -R .comment -R .note input.elf output.cleaned.elf
适用场景:减小文件体积或提取关键代码段。
2.2.5.1.3. 3. 剥离符号信息
完全剥离符号:
objcopy -S input.elf output.stripped.elf
保留特定符号:
objcopy -K main -K global_var input.elf output.elf
应用场景:保护代码隐私或优化调试信息。
2.2.5.2. 二、高级功能
2.2.5.2.1. 1. 修改节属性
重命名节:
objcopy --rename-section .data=.mydata input.elf output.elf
调整段地址:
objcopy --change-section-address .text=0x8000 input.elf output.elf
应用场景:兼容不同硬件地址映射或修复段布局。
2.2.5.2.2. 2. 符号操作
重命名符号:
objcopy --redefine-sym old_func=new_func input.elf output.elf
弱化符号(避免链接冲突):
objcopy --weaken-symbol global_var input.elf output.elf
应用场景:解决符号命名冲突或适配动态链接库。
2.2.5.2.3. 3. 填充与对齐
填充段间空隙:
objcopy --gap-fill=0xFF input.elf output.elf
填充到指定地址:
objcopy --pad-to=0x1000 input.elf output.elf
应用场景:ROM 编程或内存对齐优化。
2.2.5.2.4. 4. 处理二进制数据
反转字节序:
objcopy --reverse-bytes=4 input.bin output.bin
截取特定字节:
objcopy -b 1 -i 4 input.bin output.bin # 每4字节取第1字节
应用场景:处理特定硬件协议的二进制数据。
2.2.5.3. 三、参数详解
选项 |
功能 |
|---|---|
|
指定输入文件格式(如 |
|
指定输出文件格式(如 |
|
强制输入/输出格式一致(不转换) |
|
仅处理指定节(等效于 |
|
删除指定节 |
|
设置程序入口地址 |
|
修改段的加载地址(LMA) |
|
仅保留重定位需要的符号 |
|
显示详细操作日志 |
2.2.5.4. 四、典型应用场景
2.2.5.4.1. 1. 嵌入式开发
生成二进制固件:
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
提取 Bootloader:
objcopy -j .bootloader -O binary input.elf bootloader.bin
2.2.5.4.2. 2. 逆向工程
分析可执行文件结构:
objcopy -O srec program.exe program.srec # 转换为 SREC 格式便于分析
修复损坏文件:
objcopy --remove-section .corrupted input.exe output.exe
2.2.5.4.3. 3. 安全加固
移除调试符号:
objcopy -S -g input.elf output.elf
混淆符号名称:
objcopy --redefine-sym _start=main input.elf output.elf
2.2.5.5. 五、常见问题与解决
2.2.5.5.1. 问题1:转换后文件无法运行
原因:删除了关键节(如
.text或.data)。解决:避免使用
-R删除必要节,或通过objdump -h检查节依赖。
2.2.5.5.2. 问题2:格式转换失败
原因:输入/输出格式不兼容(如 ELF 转 Windows PE)。
解决:使用中间格式(如
elf32-littlearm→srec→hex)。
2.2.5.5.3. 问题3:符号重命名无效
原因:未指定全局符号或格式不支持。
解决:使用
--globalize-symbol或检查目标格式是否允许符号修改。
2.2.5.6. 六、与其他工具对比
工具 |
优势 |
局限性 |
|---|---|---|
|
支持跨格式转换和节级操作 |
无法反汇编或调试 |
|
专注 ELF 文件分析 |
不支持格式转换 |
|
仅剥离符号和调试信息 |
功能单一,无法修改节内容 |
2.2.5.7. 七、总结
objcopy 是二进制文件处理的瑞士军刀,适用于:
嵌入式开发:生成固件、提取代码段。
安全加固:移除敏感信息、混淆符号。
逆向工程:分析文件结构、修复损坏文件。
通过灵活组合选项(如 -O binary -R .comment -S),开发者可以高效完成二进制文件的转换与优化。