使用内联汇编时出现段故障(堆芯故障)错误

问题描述 投票:-1回答:1

我在GCC中使用内联汇编。我想将变量内容向左旋转2位(我将变量移至rax寄存器,然后将其旋转2次)。我在下面编写了代码,但遇到了分段错误(核心转储)错误。如果您能帮助我,我将不胜感激。

uint64_t X = 13835058055282163712U;
 asm volatile(
            "movq %0 , %%rax\n"
            "rol %%rax\n"
            "rol %%rax\n"
            :"=r"(X)
            :"r"(X)
         );
printf("%" PRIu64 "\n" , X);
c gcc segmentation-fault x86-64 inline-assembly
1个回答
1
投票

了解内联汇编的关键是要理解每个汇编语句都有两个部分:

  1. 实际的汇编程序内容,编译器将在其中进行文本替换,但是不理解

    这是文档中的AssemblerTemplate(所有内容一直到:中的第一个__asm__()为止。

    ] >>
  2. 关于汇编器功能的描述,以编译器理解的术语

  3. 此[: OutputOperands : InputOperands : Clobbers中的the documentation

    这必须告诉编译器汇编器如何适合编译器围绕它生成的所有代码。代码生成正忙于分配寄存器来保存值,确定执行操作的顺序,将事情移出循环,消除未使用的代码片段,丢弃不再需要的值,等等。

    实际的汇编器是一个黑匣子,它接收此处描述的输入,产生所描述的输出,并且副作用可能是“溢出”某些寄存器和/或存储器。这[[must

    是对汇编程序工作的完整描述...否则,编译器在模板周围生成的asm将与其冲突并依赖错误的假设。根据这些信息,

    compiler

    可以决定汇编器可以使用哪些寄存器,应该让它这样做。
    所以,您的片段:

asm volatile( "movq %0 , %%rax\n" "rol %%rax\n" "rol %%rax\n" :"=r"(X) :"r"(X) );

有一些“问题”:

    您可能由于asm()就像一个函数一样选择了%rax作为结果,并且可能期望在%rax中返回结果-但这不是。
  • 您继续使用%rax,编译器可能已经将其分配给了其他东西...,因此,您实际上在“泛滥” %rax,但您没有告诉编译器!
  • 您指定了=r(X)
  • OutputOperand
  • ),它告诉编译器期望某个寄存器中的输出,并且该输出将是变量X的新值。 AssemblerTemplate中的%0将被为输出选择的寄存器替换。可悲的是,您的程序集将%0视为输入:-(并且输出实际上是在%rax中-如上所述,编译器没有意识到。
  • 您还指定了r(X)

    InputOperand

  • ),它告诉编译器安排将变量X的当前值放入某个寄存器中,以供汇编器使用。这将是AssemblerTemplate中的%1。可悲的是,您的程序集不使用此输入。
    即使输出和输入操作数都引用X,编译器也可能不会使%0%1成为同一寄存器。 (这允许它将asm块用作非破坏性操作,使输入的原始值保持不变。如果这不是模板的工作方式,则不要那样写。

    通常,当所有输入和输出均由约束条件正确描述时,您不需要volatile。编译器将做的一件好事是,如果(所有)输出未使用,则丢弃asm() ... volatile告诉编译器不要这样做(并告诉它许多其他事情) ...请参阅手册)。

  • 除此之外,一切都很棒。以下是安全的,并避免使用mov指令:

asm("rol %0\n" "rol %0\n" : "+r"(X));

["+r"(X)表示需要一个组合的输入和输出寄存器,取旧值X并返回一个新值。

现在,如果您不想替换X,那么假设将要生成Y,则可以:

asm("mov %1, %0\n" "rol %0\n" "rol %0\n" : "=r"(Y) : "r"(X));

但是最好让编译器来决定是否需要mov还是只能破坏输入。更好的方法是rol $2, %0,因为the ROL instruction可以立即计数。一个ROL by 2的效率是两个单独的ROL指令的两倍;在现代CPU上的性能并不取决于数量。


关于

InputOperands

有一些规则值得一提:
  • 汇编器

    必须

    覆盖任何InputOperands-编译器正在跟踪其在哪些寄存器中具有哪些值,并期望保留[[InputOperands
。] >编译器希望在编写
  • any

    OutputOperand

  • 之前先读取所有InputOperands。当编译器知道在asm()之后不再使用给定的InputOperand并因此可以将InputOperand的寄存器分配给OutputOperand时,这一点很重要。有一种叫做earcclobber=&r(foo))的东西可以处理这种小皱纹。
    在上面,如果您实际上不再使用X,则编译器可以将%0%1分配给同一寄存器!但是(冗余)mov仍将被汇编-记住编译器确实不理解AssemblerTemplate。因此,通常最好改掉C中的值,而不是asm()。参见https://gcc.gnu.org/wiki/DontUseInlineAsmBest practices for circular shift (rotate) operations in C++

    所以这里有一个主题的四个变体,并且生成了代码(gcc -O2):


    // (1) uses both X and Y in the printf() -- does mov %1, %0 in asm() void Never_Inline footle(void) Dump of assembler code for function footle: { mov $0x492782,%edi # address of format string unsigned long X, Y ; xor %eax,%eax mov $0x63,%esi # X = 99 X = 99 ; rol %rsi # 1st asm __asm__("\t rol %0\n" rol %rsi "\t rol %0\n" : "+r"(X) mov %rsi,%rdx # 2nd asm, compiler using it as a copy-and-rotate ) ; rol %rdx rol %rdx __asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> # tailcall printf "\t rol %0\n" "\t rol %0\n" : "=r"(Y) : "r"(X) ) ; printf("%lx %lx\n", X, Y) ; } // (2) uses both X and Y in the printf() -- does Y = X in 'C' void Never_Inline footle(void) Dump of assembler code for function footle: { mov $0x492782,%edi unsigned long X, Y ; xor %eax,%eax mov $0x63,%esi X = 99 ; rol %rsi # 1st asm __asm__("\t rol %0\n" rol %rsi "\t rol %0\n" : "+r"(X) mov %rsi,%rdx # compiler-generated mov ) ; rol %rdx # 2nd asm rol %rdx Y = X ; jmpq 0x4010a0 <printf@plt> __asm__("\t rol %0\n" "\t rol %0\n" : "+r"(Y) ) ; printf("%lx %lx\n", X, Y) ; } // (3) uses only Y in the printf() -- does mov %1, %0 in asm() void Never_Inline footle(void) Dump of assembler code for function footle: { mov $0x492782,%edi unsigned long X, Y ; xor %eax,%eax mov $0x63,%esi X = 99 ; rol %rsi __asm__("\t rol %0\n" rol %rsi "\t rol %0\n" : "+r"(X) mov %rsi,%rsi # redundant instruction because of mov in the asm template ) ; rol %rsi rol %rsi __asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> "\t rol %0\n" "\t rol %0\n" : "=r"(Y) : "r"(X) ) ; printf("%lx\n", Y) ; } // (4) uses only Y in the printf() -- does Y = X in 'C' void Never_Inline footle(void) Dump of assembler code for function footle: { mov $0x492782,%edi unsigned long X, Y ; xor %eax,%eax mov $0x63,%esi X = 99 ; rol %rsi __asm__("\t rol %0\n" rol %rsi "\t rol %0\n" : "+r"(X) rol %rsi # no wasted mov, compiler picked %0=%1=%rsi ) ; rol %rsi jmpq 0x4010a0 <printf@plt> Y = X ; __asm__("\t rol %0\n" "\t rol %0\n" : "+r"(Y) ) ; printf("%lx\n", Y) ; }

    希望能证明编译器忙于将值分配给寄存器,跟踪其需要保留的值,最大程度地减少寄存器/寄存器的移动,并且通常很聪明。

    因此,诀窍是与编译器一起工作,要了解: 

    OutputOperands

    :

    InputOperands

    :Clobbers是您在描述汇编程序正在执行的操作。
    © www.soinside.com 2019 - 2024. All rights reserved.