c - 使用内联汇编时出现段错误(核心转储)错误

标签 c gcc segmentation-fault x86-64 inline-assembly

我在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);

最佳答案

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


实际的汇编程序内容的文本,编译器将在其中进行文本替换,但无法理解。

这是文档中的AssemblerTemplate(直到:中第一个__asm__()的所有内容)。
用编译器确实了解的术语来描述汇编程序的工作。

这是the documentation中的: OutputOperands : InputOperands : Clobbers

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

实际的汇编程序是一个黑匣子,它接收此处描述的输入,产生所描述的输出,并且副作用可能是“堆积”一些寄存器和/或存储器。这必须是对汇编器功能的完整描述...否则,编译器在模板周围生成的asm将与其冲突并依赖错误的假设。

有了这些信息,编译器就可以决定汇编器可以使用哪些寄存器,您应该让它这样做。


因此,您的片段:

 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还是只允许销毁输入。



关于InputOperands有一些规则值得一提:


汇编器不得覆盖任何InputOperands-编译器正在跟踪其在哪个寄存器中具有的值,并期望保留InputOperands。
编译器希望在写入任何OutputOperand之前先读取所有InputOperand。当编译器知道asm()之后不再使用给定的InputOperand时,这很重要,因此编译器可以将InputOperand的寄存器分配给OutputOperand。有一种叫做Earlyclobber(=&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是您在描述汇编程序正在执行的操作的地方。

关于c - 使用内联汇编时出现段错误(核心转储)错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60237447/

相关文章:

c - 哪些 IDE 对使用 CUDA 编程有很好的支持?

使用 MinGW 在 Windows 上编译小型 Gcc 项目

c - GCC 阵列优化

c - pthread_join 导致段错误(简单程序)

c - Linux mmap() 错误

c - 函数指针赋值

c++ - 从C中的字符串中剪切字符

c - 在 C 中使用条件变量和互斥体来同步线程

c++ - C++ Visual Studio 和 gcc 编译器中的空指针和空地址

c - 搜索/删除时出现 2-3 棵树分割错误