Linux GNU 二进制实用程序 Binutils 命令和示例

举报
Tiamo_T 发表于 2022/10/15 10:09:39 2022/10/15
【摘要】 以下是本教程中介绍的 12 个不同的 binutils 命令。

GNU Binary Utilities,通常称为 binutils,是处理汇编文件、目标文件和库的开发工具的集合。

如果您是在 Linux / UNIX 平台上工作的开发人员,那么了解作为 GNU 开发工具的一部分可用的各种命令是必不可少的。

以下是本教程中介绍的 12 个不同的 binutils 命令。

  1. as – GNU 汇编器命令
  2. ld – GNU 链接器命令
  3. ar – GNU 归档命令
  4. nm - 列出目标文件符号
  5. objcopy – 复制和翻译目标文件
  6. objdump – 显示对象文件信息
  7. size - 列出部分大小和总大小
  8. strings – 显示文件中的可打印字符
  9. strip – 从目标文件中丢弃符号
  10. c++filt - Demangle 命令
  11. addr2line - 将地址转换为文件名和数字
  12. 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 可能不包含在您的发行版中。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。