我使用的是 Ubuntu 22.04 x86_64 系统,内核 6.5.0-15-generic。 我正在学习如何制作使用 GAS AT&T 汇编格式进行系统调用的简单程序。 这个汇编程序在文件中
hello.s
:
.section .rodata
msg: .ascii "Hello, World!\n"
.set msglen, (. - msg)
.section .text
.global main
main:
mov $1, %rax
mov $1, %rdi
lea msg(%rip), %rsi
mov $msglen, %rdx
syscall
mov $60, %rax
mov $0, %rdi
syscall
我获取可执行文件的方式是这样的:
我用
as hello.s -o hello.o
组装来获取目标文件
我通过
gcc hello.o -o hello
链接获取可执行文件
程序运行良好。我只是对以下几点有疑问:
如果我使用名为
hello.S
而不是 hello.s
的文件会怎样?这样做会有所不同吗:
as hello.S -o hello.o
gcc hello.o -o hello
两种形式都正确吗?
为什么当我做类似
gcc -S demo.i
的事情时,我得到的是demo.s
而不是demo.S
?
我的目标是在汇编中创建一个简单的经典主程序,使 write 系统调用写入“Hello World”。我的代码100%正确吗?
您还可以通过执行
gcc -o hello hello.s
一次性组装和链接您的文件。
这与你的问题有关。
gcc
查看输入文件后缀来决定如何处理它们。 .s
文件直接输入到汇编器。如果它被命名为 .S
,那么它会通过 C 预处理器进行过滤,然后输入到汇编器。
因此,如果您想在汇编源代码中使用 C 预处理器功能(例如
#define
),请使用 .S
命名您的文件,并使用 gcc
(或 clang
)来构建它。如果您不想要该功能,请继续使用.s
。
编译器生成的程序集可以直接进入汇编器,无需进一步预处理,因此其程序集输出文件被命名为
.s
而不是 .S
。
就正确操作而言,您的代码本身看起来很好。您可以改进一些与效率相关的事情。
由于需要 REX 前缀字节,大多数具有 64 位操作数的 x86-64 指令比 32 位操作数长一个字节。因此,当 32 位形式有效时,通常最好使用 32 位形式,并利用具有 32 位寄存器目标操作数的指令自动将其零扩展为 64 位这一事实。 (为什么 32 位寄存器上的 x86-64 指令将整个 64 位寄存器的上部清零?)因此
mov $1, %eax
与 mov $1, %rax
完全相同,但前者是首选,因为它节省了一个字节代码大小。
使用
xor %edi, %edi
将寄存器清零会更有效(如上所述,实际上会将所有 %rdi
清零)。 在 x86 汇编中将寄存器设置为零的最佳方法是什么:xor、mov 或 and?