https://www.baeldung.com/linux/assembly-compile-run
最后更新时间:2024 年 6 月 2 日
撰文:布拉克·戈克门
审核人:吉米·阿扎尔
建筑ld
- 概述
汇编语言是一种直接与硬件通信的低级编程语言。汇编语言是人类可读的机器代码版本。我们使用汇编器将汇编代码转换为机器代码。
每个处理器系列都有自己的汇编语言和不同的指令集。例如,x86 汇编语言是英特尔处理器的汇编语言。除了处理器架构之外,可执行文件格式可能因作系统而异。因此,同一架构可能有多个汇编器。
在本教程中,我们将讨论如何在 Linux 中编译和运行汇编代码。我们将首先讨论两种常见的 x86 汇编语言语法,AT&T 和 Intel。然后,我们将研究三种流行的汇编器:GNU 汇编器、Netwide 汇编器和平面汇编器。
- AT&T 和英特尔语法
x86 汇编语言有两种流行的语法,AT&T 和 Intel。汇编器通常只支持其中一种,但有些汇编器可以同时使用两者。
Linux 域中的主要语法自然是 AT&T 语法,因为 Unix 是在 AT&T 贝尔实验室开发的。例如,Linux 的默认编译器 GCC 默认使用 AT&T 语法。但是,Windows 域中的主要语法是 Intel 语法。
这两种语法之间存在一些差异。例如,如果我们想使用 AT&T 语法将值分配给寄存器,我们使用 mov 指令:
mov $1, %rax
在此示例中,我们将值 1 分配给 rax 寄存器。在 AT&T 语法中,目标寄存器在源之后指定。我们在值前加上美元符号,如 $1。同样,我们在寄存器前加上百分号,如 %rax。
要使用 Intel 语法将值分配给寄存器,我们以不同的方式使用 mov 指令:
mov rax, 1
Intel 语法中作数的顺序与 AT&T 语法中的顺序相反 — 目标寄存器在源寄存器之前指定。此外,值和寄存器没有前缀符号。
- 将 AS 与 AT&T 语法一起使用
我们将探讨如何使用 GNU 汇编器,如本节所示,使用 AT&T 语法构建汇编代码。GNU 汇编器默认支持 AT&T 语法。但是,它也支持 Intel 语法,我们将在下一节中看到。
3.1. 示例汇编代码
我们将使用以下汇编代码 hello_baeldung_att.asm:
$ cat hello_baeldung_att.asm
.global _start
.section .data
message: .ascii "Hello Baeldung\n"
.section .text
_start:
mov $1, %rax
mov $1, %rdi
mov $message, %rsi
mov $15, %rdx
syscall
mov $60, %rax
mov $0, %rdi
syscall
此汇编代码打印 Hello Baeldung 并退出,退出状态为 0。
3.2. 《守则》剖析
让我们分解代码来简要分析一下:
.global _start
.global 指令将 _start 指定为程序中的入口点,就像 C 程序中的 main() 函数一样。它导出链接器 ld 的_start符号。因此,当我们编译 hello_baeldung_att.asm 时,_start 会添加到目标代码 hello_baeldung_att.o 中。
然后,我们有数据部分:
.section .data
message: .ascii "Hello Baeldung\n"
.section .data 指令显示数据部分的开头。初始化的变量和常量在数据部分中声明。我们声明一个变量 message,其值为 “Hello Baeldung\n”。.ascii 指令显示变量的类型是字符串。
然后,文本部分开始:
.section .text
.section .text 指令显示文本部分的开头。实际的程序代码在本节中。
我们在_start的开头打印消息:
_start:
mov $1, %rax
mov $1, %rdi
mov $message, %rsi
mov $15, %rdx
syscall
我们需要相应地设置寄存器以写入终端。每个寄存器在系统调用中都有特定的角色。我们需要将系统调用号放入 rax 寄存器中。1 是 x86-64 架构中 sys_write() 的对应系统调用号。rdi 寄存器包含文件描述符 1,这是标准输出。我们将要打印的消息存储在 rsi 寄存器中。最后,rdx 寄存器保存消息的长度,在本例中为 15。
设置寄存器后,我们使用 syscall 指令来触发 sys_write() 系统调用。
最后,我们优雅地退出程序:
mov $60, %rax
mov $0, %rdi
syscall
sys_exit() 的系统调用号是 60。因此,我们将 60 写入 rax 寄存器。我们需要将程序的退出状态写入 rdi 寄存器,在我们的例子中为 0。最后,我们调用系统调用指令来触发 sys_exit() 系统调用。
3.3. 构建和运行
现在让我们使用 as: 构建 hello_baeldung_att.asm:
$ as hello_baeldung_att.asm -o hello_baeldung_att.o
GNU 汇编器 as 将 hello_baeldung_att.asm 中的汇编代码作为输入,并生成目标文件 hello_baeldung_att.o,我们使用 -o 选项指定该文件。然后,我们使用 ld 命令创建可执行文件 hello_baeldung_att:
$ ld -s -o hello_baeldung_att hello_baeldung_att.o
链接器将目标文件作为输入并生成可执行文件 hello_baeldung_att。ld 的 -o 选项指定可执行文件的名称,-s 选项从可执行文件中去除所有符号信息。
构建可执行文件后,让我们运行它:
$ ./hello_baeldung_att
Hello Baeldung
输出符合预期。
- 将 As 与 Intel 语法一起使用
在本节中,我们将看到可以使用 GNU 汇编器以 Intel 语法构建汇编代码。
4.1. 示例汇编代码
我们将使用以下汇编代码 hello_baeldung_intel.asm:
$ cat hello_baeldung_intel.asm
.global _start
.section .data
message: .ascii "Hello Baeldung\n"
.section .text
_start:
mov rax, 1
mov rdi, 1
mov rsi, offset message
mov rdx, 15
syscall
mov rax, 60
mov rdi, 0
syscall
hello_baeldung_intel.asm 的内容与 hello_baeldung_att.asm 的内容相似,只是使用 Intel 语法而不是 AT&T 语法。
4.2. 构建和运行
现在让我们hello_baeldung_intel使用 as:
$ as -msyntax=intel -mnaked-reg hello_baeldung_intel.asm -o hello_baeldung_intel.o
在这种情况下,我们在生成目标文件时使用两个附加选项 hello_baeldung_intel.o。特别是,我们使用 -msyntax=intel 选项来指定汇编代码采用 Intel 语法。此选项的默认值为 att。此外,我们使用 -mnaked-reg 选项指定寄存器不需要 % 前缀。
链接步骤与之前相同:
$ ld -s -o hello_baeldung_intel hello_baeldung_intel.o
构建可执行文件后,让我们运行它:
$ ./hello_baeldung_intel
Hello Baeldung
输出符合预期。
- 使用 nasm
Netwide 汇编器 nasm 是在 Linux 中构建汇编代码的另一种替代方案。它可以使用 nasm.x86_64 包安装。但是,我们需要在基于 RPM 的发行版中启用 PowerTools。
Netwide 汇编程序支持 Intel 语法。
5.1. 示例汇编代码
我们将使用以下汇编代码 hello_baeldung_nasm.asm:
$ cat hello_baeldung_nasm.asm
global _start
section .data
message: db "Hello Baeldung", 0xa
section .text
_start:
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 15
syscall
mov rax, 60
mov rdi, 0
syscall
它与 hello_baeldung_intel.asm 类似,只是有一些细微的区别。
5.2. 构建和运行
现在让我们使用 nasm 构建 hello_baeldung_nasm.asm:
$ nasm -f elf64 hello_baeldung_nasm.asm -o hello_baeldung_nasm.o
nasm 的用法类似于 as。但是,还有一个额外的 -f 选项,它指定输出对象文件 hello_baeldung_nasm.o 的格式。我们使用 -f elf64 将输出格式指定为 64 位 ELF(可执行和可链接格式)。ELF 是 Linux 中可执行文件和共享库的默认格式。
链接步骤与之前相同:
$ ld -s -o hello_baeldung_nasm hello_baeldung_nasm.o
构建可执行文件后,让我们运行它:
$ ./hello_baeldung_nasm
Hello Baeldung
输出符合预期。
- 使用 fasm
平面汇编器 fasm 是在 Linux 中构建汇编代码的另一种选择。它可以从 https://flatassembler.net/download.php 下载。它支持英特尔语法。
6.1. 示例汇编代码
我们将使用以下汇编代码 hello_baeldung_fasm.asm:
$ cat hello_baeldung_fasm.asm
format elf64 executable
entry _start
message: db "Hello Baeldung", 0xa
_start:
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 15
syscall
mov rax, 60
mov rdi, 0
syscall
文本部分类似于英特尔语法中的先前汇编代码。但是,它有自己的一套指令。例如,entry 指令在可执行文件中设置入口点,其_start与前面的示例相同。另一方面,格式 elf64 可执行指令用于创建 64 位 ELF 可执行文件。
6.2. 构建和运行
现在让我们使用 fasm 构建 hello_baeldung_fasm.asm:
$ fasm ./hello_baeldung_fasm.asm hello_baeldung_fasm
flat assembler version 1.73.32 (16384 kilobytes memory, x64)
2 passes, 181 bytes.
fasm 的第一个参数是包含汇编代码的文件,第二个参数是输出的可执行文件。在我们的例子中,它们分别是 hello_baeldung_fasm.asm 和 hello_baeldung_fasm。FASM 直接生成可执行文件,无需任何中间目标文件。
让我们运行hello_baeldung_fasm:
$ ./hello_baeldung_fasm
Hello Baeldung
输出符合预期。
七、结论
在本文中,我们讨论了如何在 Linux 中编译和运行汇编代码。首先,我们学习了 AT&T 和 Intel 语法。然后,我们看到了如何使用 GNU 汇编器、Netwide 汇编器和平面汇编器来编译和运行汇编代码。值得注意的是,GNU 汇编程序同时支持 AT&T 和 Intel 语法,而另外两个仅支持 Intel 语法。
6210

被折叠的 条评论
为什么被折叠?



