转载:如何在 Linux 中编译和运行汇编代码

https://www.baeldung.com/linux/assembly-compile-run
最后更新时间:2024 年 6 月 2 日

撰文:布拉克·戈克门

审核人:吉米·阿扎尔
建筑ld

  1. 概述
    汇编语言是一种直接与硬件通信的低级编程语言。汇编语言是人类可读的机器代码版本。我们使用汇编器将汇编代码转换为机器代码。

每个处理器系列都有自己的汇编语言和不同的指令集。例如,x86 汇编语言是英特尔处理器的汇编语言。除了处理器架构之外,可执行文件格式可能因作系统而异。因此,同一架构可能有多个汇编器。

在本教程中,我们将讨论如何在 Linux 中编译和运行汇编代码。我们将首先讨论两种常见的 x86 汇编语言语法,AT&T 和 Intel。然后,我们将研究三种流行的汇编器:GNU 汇编器、Netwide 汇编器和平面汇编器。

  1. 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 语法中的顺序相反 — 目标寄存器在源寄存器之前指定。此外,值和寄存器没有前缀符号。

  1. 将 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

输出符合预期。

  1. 将 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

输出符合预期。

  1. 使用 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

输出符合预期。

  1. 使用 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 语法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值