【问题标题】:GNU C inline asm "m" constraint with a pointer: address vs. pointed-to value?带有指针的 GNU C 内联 asm“m”约束:地址与指向值?
【发布时间】:2011-10-31 15:27:43
【问题描述】:

我正在尝试了解有关 Linux 中的内联汇编程序的一些信息。我正在使用以下功能:

void test_func(Word32 *var){
   asm( " addl %0, %%eax" : : "m"(var) );
   return;
}

它生成以下汇编代码:

.globl test_func
.type   test_func, @function
test_func:
        pushl %ebp
        movl %esp, %ebp
#APP
# 336 "opers.c" 1
        addl 8(%ebp), %eax
# 0 "" 2
#NO_APP
        popl %ebp
        ret
        .size   test_func, .-test_func

它将 var mem 地址与 eax 寄存器值相加,而不是 var 值。

有没有办法告诉 addl 指令使用 var value 而不是 var mem address 而无需将 var mem 地址复制到寄存器?

问候

【问题讨论】:

  • 您的内联汇编没有定义 EAX 中应该包含的内容,因此 gcc 可以将任何内容放在那里...您实际上想做什么?

标签: c assembly gcc inline-assembly


【解决方案1】:

是的,因为你给他的 var 是地址。给他*var。

喜欢:

void test_func(Word32 *var){
   asm( " addl %0, %%eax" : : "m"(*var) );
   return;
}

我记不太清了,但你应该把“m”换成“r”吗?

内存操作数并不意味着它将从该地址获取值。它只是一个指针

【讨论】:

  • 这样做会用 var 地址覆盖 %eax 值以获取 var 值。
【解决方案2】:

不,x86 处理器没有直接进入两级的寻址模式。

你必须先从内存地址加载指针,然后从指针间接加载。

【讨论】:

    【解决方案3】:

    它将 var mem 地址与 eax 寄存器值相加,而不是 var 值。

    是的,gcc 内联汇编的语法非常晦涩难懂。从GCC Inline Assembly HOWTO"m" 中的相关部分转述大致为您提供了 C 变量的内存位置。

    当您只想要一个可以写入或读取的地址时,您会使用它。注意我说的是 C 变量的位置,所以%0 被设置为Word32 *var 的地址——你有一个指向指针的指针。内联汇编块的 C 转换可能看起来像 EAX += *(&var),因为您可以说 "m" 约束隐式地获取 C 变量的地址并为您提供地址表达式,然后您将其添加到 %eax

    有没有办法告诉 addl 指令使用 var value 而不是 var mem address 而无需将 var mem address 复制到寄存器?

    这取决于你的意思。您需要从堆栈中获取var,因此必须取消引用内存(请参阅@Bo Perssons 答案),但您不必在内联汇编中这样做

    约束必须是"m"(*var)(正如@fazo 建议的那样)。这将为您提供var 指向的值的内存位置,而不是指向它的内存位置。

    现在生成的代码是:

    test_func:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
    #APP
    # 2 "test.c" 1
        addl    (%eax), %eax
    # 0 "" 2
    #NO_APP
        popl    %ebp
        ret
    

    这有点可疑,但这是可以理解的,因为您忘记告诉 GCC 您破坏了(在没有输入/输出列表的情况下进行了修改)%eax。修复 asm("addl %0, %%eax" : : "m"(*var) : "%eax" ) 生成的问题:

        movl    8(%ebp), %edx
        addl    (%edx), %eax
    

    这种的情况下,这并没有更好或更正确,但记住它始终是一个好习惯。请参阅clobber list 部分并特别注意"memory" clobber,以了解内联汇编的高级用法。

    即使您不想(明确地)将内存地址加载到寄存器中,我也会简要介绍一下。 将约束从"m" 更改为"r" 似乎几乎可以工作,相关部分更改为(如果我们在clobber 列表中包含%eax):

        movl    8(%ebp), %edx
        addl    %edx, %eax
    

    这几乎是正确的,我们已经将指针值var 加载到了一个寄存器中,但是现在我们必须指定我们自己是从内存中加载的。更改代码以匹配约束(通常是不可取的,我只是为了完整性而显示它):

    asm("addl (%0), %%eax" : : "r"(var) : "%eax" );
    

    给予:

    movl    8(%ebp), %edx
    addl    (%edx), %eax
    

    "m"相同。

    【讨论】:

    • 你有一个指向指针的指针 - 不完全是。您有一个引用指针对象内存的 寻址模式。如果它不是简单的裸寄存器寻址模式,那么您只能在 CPU 的 AGU 中“拥有”该指针,而不是您可以做任何事情的值。此外,"m" 是只读操作数。如果你想读写,你需要"+m",所以前面提到"m"读写的段落不是很准确,除非你假设在必要时添加+=修饰符。
    • 我添加了my own answer。我不认为您对*(&var) 的描述非常清楚或有帮助,因为在 C 语言中没有 deref,也没有地址被占用。您只是使用Word32* 类型的var 作为输入对象,就像eax += var 在纯C 中所做的那样。
    • 另外,您的asm("addl (%0), %%eax" : : "r"(var) : "%eax" ); 也不安全;您不会告诉编译器指向的内存是输入或使用"memory" clobber,因此在内联后可能会中断。 GNU C inline asm "m" constraint with a pointer: address vs. pointed-to value?
    【解决方案4】:

    试试

    void test_func(Word32 *var){
        asm( " mov %0, %%edx; \
        addl (%%edx), %%eax" : : "m"(var) );    
    
        return;
    } 
    

    【讨论】:

      【解决方案5】:

      "m" 约束不会隐式取消引用任何内容。 就像 "r" 约束一样,只是它扩展为保存表达式值的内存位置的寻址模式,而不是的一个寄存器。 (在 C 中,每个对象都有一个地址,尽管通常可以将其优化掉。)

      作为 asm 的输入(或 "=m" 的输出)的 C 对象是您指定的左值或右值,例如"m"(var) 采用var,而不是*var。所以你要添加指针。 (并告诉编译器您希望输入指针值在内存中,而不是寄存器中。)

      也许你有一个指针但你称它为var,而不是ptr 或其他什么东西让你感到困惑? C 指针是一个对象,它的值是一个地址,它本身可以存储在内存中。如果您使用 C++,Word32 &var 将在您编写 var 时隐式取消引用。


      在 C 语言中,你在写eax += ptr但你想要eax += *ptr,所以你应该写

      void test_func(Word32 *ptr){
          asm( "add %[input], %%eax"
            :  // no inputs.  Probably you should use "+a"(add_to_this) if you want  the add result, and remove the EAX clobber.
            : [input] "m"(*ptr)   // the pointed-to Word32 in memory
            : "eax"       // the instruction modifies EAX; tell the compiler about it
          );
      }
      

      编译 (Godbolt compiler explorer) 结果:

      # gcc -O3 -m32
      test_func:
              movl    4(%esp), %edx      # compiler-generated load of the function arg
              add (%edx), %eax          # from asm template, (%edx) filled in as %[input] for *ptr
              ret
      

      或者,如果您使用 -mregparm=3 或 64 位构建进行编译,则 arg 已经在寄存器中。例如64 位 GCC 发出 add (%rdi), %eaxret.

      如果您在 C 中为返回 Word32 的函数编写了 return *ptr,并且没有内联 asm,则 asm 将类似,从堆栈中加载指针 arg,然后通过 mov (%edx), %eax 加载返回值。请参阅 Godbolt 链接。

      如果内联 asm 没有达到您的预期,请查看编译器生成的 asm 以了解它是如何填充您的模板的。这有时可以帮助您弄清楚编译器认为您的意思。 (但前提是您了解基本设计原则。)


      如果写"m"(ptr),编译如下:

      void add_pointer(Word32 *ptr)
      {
          asm( "add %[input], %%eax"   :  : [input] "m"(ptr)   : "eax"  );
      }
      
      add_pointer:
              add 4(%esp), %eax       # ptr
              ret     
      

      非常类似于如果你写了Word32 *bar(Word32 *ptr){ return ptr; }


      请注意,如果您想增加内存位置,您可以使用"+m"(*ptr) 约束来告诉编译器指向的内存既是输入又是输出。或者,如果您只写到内存,"=m"(*ptr),这样它就可以优化到此内存位置的早期死存储。


      另请参阅 How can I indicate that the memory *pointed* to by an inline ASM argument may be used? 以处理您使用 "r"(ptr) 输入并在 asm 中手动取消引用指针、访问您没有告诉编译器作为输入或输出操作数的内存的情况。

      一般避免先做"r"(ptr),然后再手动做add (%0), %%eax。它需要额外的约束以使其安全,并且它强制编译器在寄存器中实现确切的地址,而不是使用寻址模式来相对于其他一些寄存器来访问它。例如4(%ecx) 如果在内联后发现您实际上是在将指针传递给数组或结构成员。

      当然,通常要完全避免使用内联 asm,除非没有它就无法让编译器发出足够好的 asm。 https://gcc.gnu.org/wiki/DontUseInlineAsm。如果您决定使用它,请参阅https://stackoverflow.com/tags/inline-assembly/info 以获得避免常见错误的指南。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-11
        相关资源
        最近更新 更多