非常简单的内联汇编程序产生分段错误:gcc-5.0上为11

eit6fx6z  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(121)
#include <stdio.h>

int square (int n) {
  __asm__("mov %eax, n"
      "mul %eax");
}

int main(void) {
  printf("\nSquare of 4 is %i", square(4));
  /* Calling square gives Segmentation fault: 11 error */
  return 0;
}

当我在装有Mac OS X 10.7和gcc-5.0.0的iMac(Core 2 Duo)上编译此代码时:gcc -o assem -DDEBUG=9 -ansi -pedantic -Wall -g assem.c编译时出现警告:

assem.c: In function ‘square’:
assem.c:6:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^
assem.c:4:Can't relocate expression. Absolute 0 assumed.

Compilation finished at Mon Jul 25 18:23:47

当我运行它时,它给出Segmentation fault: 11
如何修复?

  • 注意 *:我浏览了大约10个关于Segmentation fault: 11assemblyinline-assembly的问题,没有一个有帮助。
    更新

当我将内嵌组件变更为:asm ("imul %0, %0" : "+r"(n)); return n;编译器给出以下错误:

assem.c: In function ‘square’:
assem.c:4:1: warning: implicit declaration of function ‘asm’ [-Wimplicit-function-declaration]
 asm ("imul %0, %0" : "+r"(n)); 
 ^
assem.c:4:20: error: expected ‘)’ before ‘:’ token
 asm ("imul %0, %0" : "+r"(n)); 
                    ^
assem.c:7:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

Compilation exited abnormally with code 1 at Mon Jul 25 18:46:49

当我将程序集更改为asm ("imul %0, %0" : "+r"(n));时,编译器给出了与上面类似的错误。

更新2(2022年7月25日)

为了解决这个问题而不彻底改变square函数,我从Peter Cordes的注解中复制了一部分代码,并将其作为clang版本:

#include <stdio.h>

int quadrat (int n) {
  asm { mov eax, n / imul eax,eax / mov n, eax / };
}

int main(void) {
  printf("\nSquare of 4 is %i\n", 4*4);
  return 0;
}

我的Mac OS X上确实有clang-3.7.1

>clang -v
clang version 3.7.1 (tags/RELEASE_371/final)
Target: x86_64-apple-darwin11.4.2
Thread model: posix

我尝试使用以下代码编译它:

clang -fasm-blocks ass-clang.c
  • 注意 *:我通常不会使用clang

代码未编译:

ass-clang.c:4:20: error: unexpected token in argument list
  asm { mov eax, n / imul eax,eax / mov n, eax / };

更新#3(特定于奖励问题)

如何修复此代码

int square (int n) {
  __asm__("mov %eax, n"
      "mul %eax");
}

而不改变它的基本结构?也就是说,n将被移动到eax(或任何其他寄存器,如果需要),则该寄存器的值将与其自身相乘,最好使用mul命令,最后返回结果,最好不使用return命令。换句话说,我需要修复代码,不是重写。例如,我认为这是重写:

asm ("imul %0, %0" : "+r"(n)); return n;

此外,这种重写并不直观。:是什么?"+r"在那里做什么,它是分配Unix读取权限吗:)

hc8w905p

hc8w905p1#

最好不使用返回命令。
如果你想让你的C代码返回一个值,它将需要使用return。这就是C的工作原理。这就是为什么Peter建议这样做,使用clang -fasm-blocks来允许MSVC的内联asm风格。它使用with clang on Godbolt进行编译,将n存储到堆栈的低效asm,以便它可以成为asm块中的内存操作数(因为内联asm requires that inefficiency的这种样式)。

int square(int n)
{
    asm { 
    mov eax, n 
    imul eax,eax 
    mov n, eax }
    
    return n;
}

如果你是用纯asm语言写这段代码的话,“eax”寄存器是用来保存函数的返回值的。所以你可以“欺骗”你的方式不使用C return语句,但前提是你告诉编译器你将自己处理从函数返回的行为。这就是Peter建议使用__attribute__((naked))时所说的。
此属性通知编译器,它应该假定所有内容都由函数中的汇编程序代码100%处理(包括返回值和实际返回)。这就是他所说的,当他说你可以省略Cx 1 m5n1x语句,而在你填充了eax之后只使用x86 ret指令。如果你把一个函数标记为naked,您 * 不能 * 使用return语句。函数必须只包含asm,而不是C代码(参见docs)。编译器完全不干涉,所以当asm语句或块获得控制权时,可以确保RSP指向返回地址。但这也意味着不能在asm中使用命名的局部变量或参数。因此您必须自己实现调用约定。
本质上,naked意味着你在汇编中写一个函数,但是使用C编译器给予它一个名字和原型,并使它成为你C源文件的一部分。我不推荐这种方法。如果你想写一个汇编函数,在汇编中写一个汇编函数,并将它链接到你的C代码。试图将两者硬塞在一起通常只会导致混乱。
但是naked允许你省略return语句,使用ret指令,如果出于某种原因这是必要的。
什么是:
如果你读过docs(你读过docs,对吗?),你会看到什么:

  • 第一个冒号分隔“output”约束。
  • 第二个冒号分隔“input”约束。
  • 第三个冒号分隔“clobber”。

事实上,@fuz只使用了一个冒号,这意味着他没有只输入的约束,也没有使用乱码。
那个“+r”是怎么回事
看一下docs"r"表示“在调用asm指令之前将值移入寄存器”。+表示正在更新值(与=相反,=表示“写入但不读取”)。
嵌套将被移动到eax(或任何其他寄存器,如果需要话)
通过写"+r"(n),我们已经将值从n移到了寄存器中,不再需要在模板中包含mov指令。由于n可能已经在前面的代码中的寄存器中,这可能节省了额外的mov指令。
它将选择哪个寄存器?因为我们没有指定,编译器将选择最有效的寄存器。因为我们不知道将选择哪个寄存器(并且它可能在不同的编译中改变),我们使用%0来引用第一个约束,%1引用第二个约束(如果我们有一个的话),等等。
最好使用穆尔命令
这样做有几个问题。mul指令要求使用eax寄存器。如果编译器将eax用于其他用途,则强制编译器显式使用eax可能会导致代码效率降低。
但更重要的是,mul使用2个寄存器。当您将两个寄存器相乘时,最大可能的结果是宽度的两倍。mul是一个扩展乘法,它将输出放入edx:eax
你的原始代码没有为这么大的值做任何准备。但是你不能忽略edx寄存器正在被改变的事实。如果你不告诉编译器你正在改变那个寄存器的内容,它是不会知道的。它怎么会不知道呢?毕竟,'穆尔'指令就在那里,对吗?来自文档:
GCC本身并不解析汇编指令,也不知道它们的含义,甚至不知道它们是否是有效的汇编输入。
也就是说,如果在不通知编译器的情况下更改寄存器(通过输出约束或乱码),如果编译器将其用于其他用途,则可能会出现大混乱。相比之下,具有多个操作数的imul只输出单个寄存器,这与原始代码更加一致。(不需要非加宽mul,因为无论输入被视为有符号还是无符号,完全乘法的低半部分都是相同的。)
例如我认为这是重写:
我不确定我同意。

如果你想把值移到寄存器中,"+r"会帮你完成。如果你坚持的话,你可以使用mulimul,但是你会迫使编译器在eax之外为你释放edx寄存器。并且运行一条较慢的指令(更多的微操作用于写第二个寄存器)。为什么要降低代码的效率呢?
选择是“重写”edx寄存器还是使用imul。哪一个重写更小?
此外,这种改写是不直观的。
好吧,现在你抓到我了。
编写内联asm是 * 困难 * 的(这就是为什么我建议您使用don't do it)。
但这种“扩展的asm”方法是gcc、clang、intel等公司都采用的方法。(毫不奇怪)走了一条不同的路。但他们的解决方案结果是如此困难(或者在他们的编译器中无法维护地实现),他们决定完全不支持64位代码的内联asm。(将汇编语言与C结合使用的最复杂的方法),您需要了解它是如何工作的。
Fuz的方法生成的代码效率最高。它还允许代码被内联,这是这里描述的大多数其他方法所不能做到的。它使用的寄存器数量最少,使这些宝贵的资源可供编译器用于其他目的。
总之,我不知道如何找到一个比这更干净、更高效的内联asm解决方案:

int square (int n) {
    asm ("imul %0, %0" : "+r"(n)); 
    return n;
}

比较一下它的编译方式和MSVC风格的内联asm版本的编译方式,在Godbolt上使用clang 14:
第一个
顺便说一句,MSVC文档支持在asm{}块的末尾在EAX中保留一个值,然后在没有Cx 1 m36n1x语句的情况下从函数末尾退出。(即使内联一个包含asm{}块的函数时,这在MSVC中也是有效的。(可能有足够多的程序员滥用了“碰巧有效”,MS将其正式化)。这降低了从asm{}块中 out 获取结果的效率,但对存储/重新加载获取值没有帮助。
但是在clang -fasm-blocks中,它只是碰巧工作,在内联时中断。

相关问题