Linux GNU 二进制实用程序 Binutils 命令和示例
GNU Binary Utilities,通常称为 binutils,是处理汇编文件、目标文件和库的开发工具的集合。
如果您是在 Linux / UNIX 平台上工作的开发人员,那么了解作为 GNU 开发工具的一部分可用的各种命令是必不可少的。
以下是本教程中介绍的 12 个不同的 binutils 命令。
- as – GNU 汇编器命令
- ld – GNU 链接器命令
- ar – GNU 归档命令
- nm - 列出目标文件符号
- objcopy – 复制和翻译目标文件
- objdump – 显示对象文件信息
- size - 列出部分大小和总大小
- strings – 显示文件中的可打印字符
- strip – 从目标文件中丢弃符号
- c++filt - Demangle 命令
- addr2line - 将地址转换为文件名和数字
- readelf - 显示 ELF 文件信息
这些工具将帮助您有效地操作二进制、对象和库文件。
在这 12 个实用程序中,as 和 ld 是最重要的,它们是 GNU Compiler Collection (gcc) 的默认后端。GCC 只做从 C/C++ 编译成汇编语言的工作,它的 as 和 ld 的工作是输出可执行的二进制文件。
准备示例代码
要了解所有这些命令是如何工作的,首先,让我们使用 gcc -S 从 C 代码准备一些示例汇编代码。这里显示的所有实验都是在 x86 64 位 linux 机器上完成的。
下面是 C 代码,它只是使用外部函数的返回值作为返回码。没有输入/输出,所以如果要检查程序是否按预期执行,请检查返回状态(echo $?)。我们有三个函数,main、func1 和 func2,每个函数都有一个文件。
// func1.c file:
int func1() {
return func2();
}
// func2.c file:
int func2() {
return 1;
}
// main.c file:
int main() {
return func1();
}
GCC 具有 C 运行时库支持,因此 main 函数被视为普通函数。为了简化演示,我们不想在编译和链接这些 .s 文件时涉及 C 库。因此,对 main.s 进行了两项修改:
第一个修改是为链接阶段添加标签_start。
_start标签是App的入口点,如果没有定义,运行ld时会报如下警告。
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
第二个修改是 ret 被系统退出调用替换。
我们应该手动引发系统退出中断。%eax 用于保存函数的返回值,但系统退出调用将其保存在 %ebx 中。因此,将其从 %eax 复制到 %ebx
下面是重新编辑版本的 gcc 汇编代码。
func1.s 文件:
.file "func1.c"
.text
.globl func1
.type func1, @function
func1:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
call func2
leave
func2.s 文件:
.file "func2.c"
.text
.globl func2
.type func2, @function
func2:
pushq %rbp
movq %rsp, %rbp
movl $1, %eax
leave
ret
main.s 文件:
.file "main.c"
.text
.globl main
.globl _start
.type main, @function
_start:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
call func1
movl %eax, %ebx
movl $1, %eax
int $0x80
leave
1. as – GNU 汇编器命令
as 将汇编文件作为输入并输出目标文件。目标文件只是一种内部格式,它将作为 ld 的输入,用于生成最终的可执行文件。
对 main.s 文件执行 as 命令得到 main.o 目标文件,如下图所示。
as main.s -o main.o
文件 main.o(由“as main.s -o main.o”生成),我们可以得到以下信息。
main.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped
目标文件采用 ELF 格式,这是 Linux 发行版中使用最广泛的文件格式。
请注意,“as”命令还具有对预处理、符号、约束、表达式、伪操作/指令和注释的语法支持。
GNU Assembler 可以支持大量机器,但通常在编译或交叉编译时只选择一个机器/架构系列。
2. ld - GNU 链接器命令
对象文件通常包含对不同库/对象中外部函数的引用,链接器 (ld) 的工作是组合最终二进制文件所需的所有对象/库文件,重新定位部分并解析引用。
ld 的实际行为在链接描述文件中定义,描述了可执行文件的内存布局。
如果我们只链接main.o(ld main.o -o main),会出现未定义的引用错误:
main.o: In function `_start':
main.c:(.text+0xa): undefined reference to `func1'
如果不链接所有三个反对文件(ld main.o func1.o func2.o -o main),我们将无法获得可执行文件。
# file main
main: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), statically linked, not stripped
与目标文件不同,这里我们得到一个静态链接的可执行文件。
as 和 ld 适用于特定的目标/架构。但是有一些工具可以处理 binutils 中定义的 BFD 对象。
从 objcopy -h 输出的最后几行,我们可以得到支持目标。
objcopy: supported targets: elf64-x86-64 elf32-i386 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
需要说的是,verilog、ihex 是不被真实操作系统支持的,但它在处理文本格式的对象内容时非常有用。它们广泛用于内存/ROM初始化的芯片模拟环境。
3. ar/ranlib – GNU 归档命令
ar 可用于生成和操作静态库,静态库是由多个对象组成的归档文件。
ar 的行为可以通过命令行参数(unix 风格)或脚本文件来控制。ranlib 可以将符号索引添加到存档中,这样可以加快链接速度,也可以方便例程的调用。ar -s 和ranlib 做同样的事情。
对于我的测试,无论有没有 -s,ar 都将始终输出存档索引。
Test1,没有 -s 的 ar。
# ar -r extern.a func1.o func2.o && nm -s extern.a
ar: creating extern.a
Archive index:
func1 in func1.o
func2 in func2.o
func1.o:
0000000000000000 T func1
U func2
func2.o:
0000000000000000 T func2
有关 ar 命令的完整详细信息,请阅读:Linux ar 命令示例:如何创建、查看、提取、修改 C 存档文件 (*.a)
测试 2,带有 -s 的 ar。
# ar -r -s externS.a func1.o func2.o && nm -s externS.a
ar: creating externS.a
Archive index:
func1 in func1.o
func2 in func2.o
func1.o:
0000000000000000 T func1
U func2
func2.o:
0000000000000000 T func2
测试3,再次运行ranlib。
# cp extern.a externR.a && ranlib externR.a && nm -s externR.a
Archive index:
func1 in func1.o
func2 in func2.o
func1.o:
0000000000000000 T func1
U func2
func2.o:
0000000000000000 T func2
可以看出,每次测试都输出相同的结果。
4. nm - 列出目标文件符号
nm 可以列出目标文件中的符号。我们已经在上面的部分展示了它的用法。
nm 命令提供有关在目标文件或可执行文件中使用的符号的信息。
nm 命令提供的默认信息如下:
- 符号的虚拟地址
- 描述符号类型的字符。如果字符是小写,则符号是本地的,但如果字符是大写的,则符号是外部的
- 符号名称
$ nm -A ./*.o | grep func
./hello2.o:0000000000000000 T func_1
./hello3.o:0000000000000000 T func_2
./hello4.o:0000000000000000 T func_3
./main.o: U func
./reloc.o: U func
./reloc.o:0000000000000000 T func1
./test1.o:0000000000000000 T func
./test.o: U func
阅读更多:10 个实用的 Linux nm 命令示例
5. objcopy – 复制和翻译目标文件
objcopy 可以将一个对象文件的内容复制到另一个对象文件,输入/输出对象可以是不同的格式。
有时您需要将可用于一种平台(如 ARM 或 x86)的目标文件移植到另一种平台。
如果源代码可用,事情就相对容易了,因为它可以在目标平台上重新编译。
但是,如果源代码不可用并且您仍然需要将目标文件从平台类型移植到其他平台怎么办?好吧,如果您使用的是 Linux,那么命令 objcopy 完全符合要求
该命令的语法是:
objcopy [options] infile [outfile]...
阅读更多:用于复制和翻译目标文件的 Linux Objcopy 命令示例
6. objdump - 显示对象文件信息
objdump 可以显示从目标文件中选择的信息。我们可以使用 objdump -d 将反汇编应用到 main。
# objdump -d main
main: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <main>:
400078: 55 push %rbp
400079: 48 89 e5 mov %rsp,%rbp
40007c: b8 00 00 00 00 mov $0x0,%eax
400081: e8 0a 00 00 00 callq 400090 <func1>
400086: c9 leaveq
400087: 89 c3 mov %eax,%ebx
400089: b8 01 00 00 00 mov $0x1,%eax
40008e: cd 80 int $0x80
0000000000400090 <func1>:
400090: 55 push %rbp
400091: 48 89 e5 mov %rsp,%rbp
400094: b8 00 00 00 00 mov $0x0,%eax
400099: e8 02 00 00 00 callq 4000a0 <func2>
40009e: c9 leaveq
40009f: c3 retq
00000000004000a0 <func2>:
4000a0: 55 push %rbp
4000a1: 48 89 e5 mov %rsp,%rbp
4000a4: b8 01 00 00 00 mov $0x1,%eax
4000a9: c9 leaveq
4000aa: c3 retq
阅读更多:Linux Objdump 命令示例(反汇编二进制文件)
7. size - 列出部分大小和总大小
size 可以显示目标文件中section的大小信息。
# size main
text data bss dec hex filename
51 0 0 51 33 main
8. 字符串——显示文件中的可打印字符
string 可以显示来自目标文件的可打印字符序列。默认情况下,它只在 .data 部分搜索。使用 -a 开关,可以搜索所有部分。
# strings -a main
.symtab
.strtab
.shstrtab
.text
main.c
func1.c
func2.c
func1
_start
__bss_start
main
func2
_edata
_end
阅读更多:Linux 字符串命令示例(在 UNIX 二进制文件中搜索文本)
9. strip——从目标文件中丢弃符号
strip 可以从目标文件中删除符号,这可以减小文件大小并加快执行速度。
我们可以通过 objdump 显示符号表。符号表显示每个功能/标签的条目/偏移量。
# objdump -t main
main: file format elf64-x86-64
SYMBOL TABLE:
0000000000400078 l d .text 0000000000000000 .text
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l df *ABS* 0000000000000000 func1.c
0000000000000000 l df *ABS* 0000000000000000 func2.c
0000000000400090 g F .text 0000000000000000 func1
0000000000400078 g .text 0000000000000000 _start
00000000006000ab g *ABS* 0000000000000000 __bss_start
0000000000400078 g F .text 0000000000000000 main
00000000004000a0 g F .text 0000000000000000 func2
00000000006000ab g *ABS* 0000000000000000 _edata
00000000006000b0 g *ABS* 0000000000000000 _end
在 strip (#strip main) 之后,符号表将被删除。
#objdump -t main
main: file format elf64-x86-64
SYMBOL TABLE:
no symbols
阅读更多:10 个 Linux Strip 命令示例(减少可执行文件/二进制文件大小)
10. c++filt - Demangle 命令
C++ 支持重载,可以让相同的函数名采用不同种类/数量的参数。
这是通过将函数名称更改为低级汇编程序名称来完成的,这称为重整。c++filt 可以对 C++ 和 Java 进行解构。
在这里,我们制作了一个新的示例代码来解释 mangling。
假设我们有两种类型的 func3,它们接受不同类型的输入参数,void 和 int。
==> mangling.cpp <==
int func3(int a) {
return a;
}
int func3() {
return 0;
}
int main() {
return func3(1);
}
在汇编格式中,它们确实有不同的名称,_Z5func3v 和 _Z5func3i。并且,将根据我们在 mangling.cpp 中传递给 func3 的参数类型调用其中之一。在此示例中,调用了 _Z5func3i。
==> mangling.s <==
.file "mangling.cpp"
.text
.globl _Z5func3i
.type _Z5func3i, @function
_Z5func3i:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
leave
ret
.globl _Z5func3v
.type _Z5func3v, @function
_Z5func3v:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
leave
ret
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %edi
call _Z5func3i
leave
ret
#grep func3.*: mangling.s
_Z5func3i:
_Z5func3v:
我们可以把这些汇编函数名传给c++filt,原来的函数define语句就恢复了。
#grep func3.*: mangling.s | c++filt
func3(int):
func3():
objdump 还可以使用不同的样式进行 demangle:
-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'
11. addr2line - 将地址转换为文件名和数字
addr2line 可以通过传递调试信息来获取重新分配部分内给定地址或偏移量的文件和行号。
首先,我们必须编译带有 -g 标志的程序集文件,以便将调试信息添加到对象中。从下面可以看出,现在有一些调试部分。
objdump -h mainD
mainD: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000033 0000000000400078 0000000000400078 00000078 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .debug_aranges 00000090 0000000000000000 0000000000000000 000000b0 2**4
CONTENTS, READONLY, DEBUGGING
2 .debug_info 000000dd 0000000000000000 0000000000000000 00000140 2**0
CONTENTS, READONLY, DEBUGGING
3 .debug_abbrev 0000003c 0000000000000000 0000000000000000 0000021d 2**0
CONTENTS, READONLY, DEBUGGING
4 .debug_line 000000ba 0000000000000000 0000000000000000 00000259 2**0
CONTENTS, READONLY, DEBUGGING
从2.d objdump的反汇编结果可以看出,0x400090是func1的入口,和addr2line给出的结果是一样的。
addr2line -e mainD 0x400090
/media/shared/TGS/func1.s:6
12. readelf - 显示 ELF 文件信息
readelf 和 elfedit 只能对 elf 文件进行操作。
readelf 可以显示来自 elf 文件的信息。
我们可以显示ELF头的详细信息。
#readelf -h main_full
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: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400078
Start of program headers: 64 (bytes into file)
Start of section headers: 208 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 64 (bytes)
Number of section headers: 5
Section header string table index: 2
就像 readelf 一样,你也可以使用 elfedit,它可以在 elf 头文件中更新机器、文件类型和 OS ABI。请注意,默认情况下,elfedit 可能不包含在您的发行版中。
- 点赞
- 收藏
- 关注作者
评论(0)