GCC 内联程序集错误:“'int' 的操作数大小不匹配”

tos*_*a98 3 c assembly gcc x86-64 inline-assembly

首先,如果有人知道标准 C 库的一个函数,该函数无需查找二进制零即可打印字符串,但需要绘制字符数,请告诉我!

否则,我有这个问题:

void printStringWithLength(char *str_ptr, int n_chars){

asm("mov 4, %rax");//Function number (write)
asm("mov 1, %rbx");//File descriptor (stdout)
asm("mov $str_ptr, %rcx");
asm("mov $n_chars, %rdx");
asm("int 0x80");
return;

}
Run Code Online (Sandbox Code Playgroud)

GCC 将以下错误告知“int”指令:

"Error: operand size mismatch for 'int'"
Run Code Online (Sandbox Code Playgroud)

有人可以告诉我这个问题吗?

fuz*_*fuz 9

您的代码存在许多问题。让我一步一步地回顾它们。

首先,int $0x80系统调用接口仅适用于 32 位代码。您不应该在 64 位代码中使用它,因为它只接受 32 位参数。在 64 位代码中,使用syscall接口。系统调用是相似的,但有些数字不同。

其次,在 AT&T 汇编语法中,立即数必须以美元符号为前缀。所以是mov $4, %rax,不是mov 4, %rax。后者会尝试将地址的内容移动4rax显然不是您想要的。

第三,不能在内联汇编中只引用自动变量的名称。如果需要,您必须使用扩展程序集告诉编译器您想使用哪些变量。例如,在您的代码中,您可以执行以下操作:

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");
Run Code Online (Sandbox Code Playgroud)

第四,gcc是一个优化编译器。默认情况下,它假定内联汇编语句类似于纯函数,输出是显式输入的纯函数。如果输出未使用,则可以优化 asm 语句,或者在使用相同输入运行时将其提升到循环之外。

但是像这样的系统调用write有一个副作用,你需要编译器来保持,所以它不是纯粹的。您需要 asm 语句以与 C 抽象机相同的次数和相同的顺序运行。 asm volatile将使这发生。(没有输出的 asm 语句是隐式易失性的,但是当副作用是 asm 语句的主要目的时,最好将其明确化。另外,我们确实希望使用输出操作数来告诉编译器 RAX 已修改,以及作为输入,这是我们无法用 clobber 做的。)

您总是需要使用扩展内联汇编语法向编译器准确描述 asm 的输入、输出和破坏。否则你会踩到编译器的脚趾(它假设寄存器不变,除非它们是输出或破坏)。(相关:?我怎样才能表明该内存*可以使用尖*通过内联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
  );
Run Code Online (Sandbox Code Playgroud)

whereSYS_WRITE<sys/syscall.h>STDOUT_FILENOin 中定义<stdio.h>。我不会向您解释扩展内联汇编的所有细节。通常使用内联汇编通常是一个坏主意。如果您有兴趣,请阅读文档。( https://stackoverflow.com/tags/inline-assembly/info )

第五,尽可能避免使用内联汇编。例如,要进行系统调用,请使用以下syscall函数unistd.h

syscall(SYS_write, STDOUT_FILENO, str_ptr, (size_t)n_chars);
Run Code Online (Sandbox Code Playgroud)

这是正确的。但它不会内联到您的代码中,因此例如,如果您想真正内联系统调用而不是调用 libc 函数,请使用 MUSL 中的包装宏。

第六,经常检查你要调用的系统调用是否已经在C标准库中可用。在这种情况下,它是,所以你应该只写

write(STDOUT_FILENO, str_ptr, n_chars);
Run Code Online (Sandbox Code Playgroud)

并完全避免这一切。

第七,如果您更喜欢使用stdio,请fwrite改用:

fwrite(str_ptr, 1, n_chars, stdout);
Run Code Online (Sandbox Code Playgroud)

  • 你对 `"S"(str_ptr)` 的使用实际上是有问题的。这不能保证在内联和优化时该字符串的内容实际上会首先转储到内存中。潜在的昂贵且快速的解决方法是指定`memory` clobber 或者更好的是,如果你知道一个特定的参数引用内存,你可以添加一个额外的(未使用的)内存约束,比如`"m" (*(const struct {char ch ; char str[];} *) ptr_str)` 以欺骗编译器确保在执行内联汇编之前将整个数组转储到内存中。 (2认同)
  • 最近出现了几个 SO 问题和答案([#1](/sf/answers/3187477751/) 和 [#2](/sf/answers/3195926121/))对这个问题的兴趣最终导致了@DavidWohlferd 关于这个主题的 [GCC 邮件帖子](https://gcc.gnu.org/ml/gcc/2017-08/msg00116.html)。其中一个问题来自另一个与“系统调用”相关的问题,其中优化仅导致显示一些输出。如果有的话,它说明了为什么内联汇编是危险的,除非您了解所有细微差别。 (2认同)

归档时间:

查看次数:

6387 次

最近记录:

5 年,6 月 前