你的代码存在一些问题。让我一步一步来解释。
首先,int $0x80
系统调用接口仅适用于 32 位代码。在 64 位代码中不应使用它,因为它只接受 32 位参数。 在 64 位代码中,请使用 syscall
接口。系统调用相似,但某些编号不同。
其次,在 AT&T 汇编语法中,立即数必须以美元符号为前缀。因此,应该是 mov $4, %rax
,而不是 mov 4, %rax
。后者会尝试将地址 4
的内容移动到 rax
中,这显然不是你想要的。
第三,你不能仅仅引用内联汇编中的自动变量名称。如果需要,你必须使用扩展汇编告诉编译器你要使用哪些变量。例如,在你的代码中,你可以这样做:
asm volatile("mov $4, %%eax; mov $1, %%edi; mov %0, %%esi; mov %2, %%edx; syscall"
:: "r"(str_ptr), "r"(n_chars) : "rdi", "rsi", "rdx", "rax", "memory");
第四,gcc是一个优化编译器。默认情况下,它假定内联汇编语句像纯函数一样,输出是显式输入的纯函数。如果输出没有被使用,asm语句可以被优化掉,或者如果使用相同的输入运行,则可以从循环中提取出来。
但是像write这样的系统调用具有需要保留的副作用,因此它不是纯的。您需要让asm语句以与C抽象机器相同的次数和顺序运行。
asm volatile
将使这种情况发生。(没有输出的asm语句是隐式易失性的,但是当副作用是asm语句的主要目的时,将其明确表示是一个好习惯。此外,我们确实希望使用输出操作数告诉编译器RAX已经被修改,以及是一个输入,这是我们无法使用clobber实现的。)
你需要始终使用
扩展内联汇编语法准确描述汇编的输入、输出和 clobber,否则会干扰编译器(它假定寄存器未更改,除非它们是输出或 clobber)。相关:
如何指示内联 ASM 参数所指向的内存可能被使用? 表明一个指针输入操作数仅仅意味着指向的内存不是一个输入。使用虚拟的
"m"
输入或
"memory"
clobber 强制所有可达内存保持同步。
你应该简化你的代码,不要编写自己的mov
指令将数据放入寄存器,而是让编译器完成这个过程。例如,你的汇编变成了:
ssize_t retval;
asm volatile ("syscall" // note only 1 instruction in the template
: "=a"(retval) // RAX gets the return value
: "a"(SYS_write), "D"(STDOUT_FILENO), "S"(str_ptr), "d"(n_chars)
: "memory", "rcx", "r11" // syscall destroys RCX and R11
);
其中SYS_WRITE
在<sys/syscall.h>
中定义,而STDOUT_FILENO
在<stdio.h>
中定义。我不打算向您解释扩展内联汇编的所有细节。通常使用内联汇编是一个坏主意。如果您感兴趣,请阅读文档。(来源: https://stackoverflow.com/tags/inline-assembly/info)
第五,应该尽量避免使用内联汇编。例如,要执行系统调用,请使用unistd.h
中的syscall
函数:
syscall(SYS_write, STDOUT_FILENO, str_ptr, (size_t)n_chars);
这段代码功能正确,但是它不能内联到您的代码中,因此如果要真正内联系统调用而不是调用libc函数,请使用MUSL的包装器宏。第六点,始终检查您想要调用的系统调用是否已经在C标准库中可用。在这种情况下,它是可用的,所以你只需要写。
write(STDOUT_FILENO, str_ptr, n_chars);
我希望你能避免所有这些问题。
第七点,如果您更喜欢使用stdio
,请改用fwrite
:
fwrite(str_ptr, 1, n_chars, stdout);