GCC内联汇编和VC的区别

The different between with GCC inline assembly and VC

我正在将 VC 内联汇编代码迁移到 GCC 内联汇编代码。

#ifdef _MSC_VER
  // this is raw code.
  __asm
  {
    cmp cx, 0x2e
    mov dword ptr ds:[esi*4+0x57F0F0], edi
    jmp BW::BWFXN_RefundMin4ReturnAddress
  }
#else
  // this is my code.
  asm
  (
    "cmp [=11=]x2e, %%cx\n\t"
    "movl %%edi, $ds:0x57F0F0(0, %%esi, 4)\n\t"
    "jmp %0"
    : /* no output */
    : "i"(BW::BWFXN_RefundGas3ReturnAddress)
    : "cx"
  );
#endif

但是我得到了错误

Error:junk `:0x57F0F0(0,%esi,4)' after expression
/var/.../cckr7pkp.s:3034: Error:operand type mismatch for `mov'
/var/.../cckr7pkp.s:3035: Error:operand type mismatch for `jmp'

参考地址操作数语法

segment:displacement(base register, offset register, scalar multiplier)

等同于

segment:[base register + displacement + offset register * scalar multiplier]

采用 Intel 语法。

不知道是哪里的问题

这不太可能仅仅通过正确的语法来工作,因为您依赖于在 asm 语句之前设置的寄存器中的值,并且您没有使用任何输入操作数来做到这一点。 (出于某种原因,您需要在跳跃前使用 cmp 设置标志?)

如果该片段在 MSVC 中以某种方式独立运行,那么您的代码取决于 MSVC 优化器所做的选择(至于哪个 C 值在哪个寄存器中),这看起来很疯狂。

无论如何,任何内联汇编问题的第一个答案都是https://gcc.gnu.org/wiki/DontUseInlineAsm,如果你能避免的话。现在可能是用 C 语言重写你的东西的好时机(如果需要,可能使用一些 __builtin 函数)。


你至少应该使用 asm volatile 和一个 "memory" 破坏器 。编译器假定在 asm 语句之后继续执行,但至少这将确保它在 asm 之前将所有内容存储到内存中,即它是一个完整的内存屏障(针对编译时重新排序)。但是函数末尾(或调用者中)的任何析构函数都不会 运行,并且不会发生堆栈清理;真的没有办法让它安全。

您或许可以使用 asm goto,但这可能仅适用于同一函数内的标签。


就语法而言,省略%%ds:,因为无论如何它都是默认段。 ($ds 之后的所有内容都被认为是垃圾,因为 $ds 是符号 ds 的地址。寄存器名称以 % 开头。)此外,只需省略 base 完全,而不是使用零。使用

"movl %%edi, 0x57F0F0( ,%%esi, 4)  \n\t"

你可能有一个反汇编程序告诉你如何编写它,通过汇编 Intel 版本和反汇编 AT&T 语法。

您可以很容易地用纯 C 语言实现该存储,例如int32_t *p = (int32_t *)0x57F0F0; p[foo]=bar;.


对于 jmp 操作数use %c0 to get the address with no $ so the compiler's asm output is jmp 0x12345 instead of jmp [=34=]x12345. See also https://whosebug.com/tags/inline-assembly/info 获取指南和文档的更多链接。

您可以而且应该查看 gcc -O2 -S 输出以查看编译器向汇编程序提供的内容。即它在 asm 模板中的填充方式。

我测试了这个on Godbolt以确保它编译,并查看asm输出+反汇编输出

void ext(void);
long foo(int a, int b) { return 0; }
static const unsigned my_addr = 0x000045678;

//__attribute__((noinline))
void testasm(void)
{
    asm volatile( // and still not safe in general
        "movl %%edi, 0x57F0F0( ,%%esi, 4) \n\t"
        "jmp   %c[foo]       \n\t"
        "jmp   foo           \n\t"
        "jmp   0x12345       \n\t"
        "jmp   %c[addr]      "
    : // no outputs
    :  // "S" (value_for_esi), "D" (value_for_edi)
    [foo] "i" (foo),
    [addr] "i" (my_addr)
    : "memory"  // prevents stores from sinking past this
    );

    // make sure gcc doesn't need to call any destructors here
    // or in our caller
    // because jumping away will mean they don't run
}

请注意,"i" (foo) 约束和 %c[operand](或 %c0)将在 asm 输出中产生 jmp foo,因此您可以发出直接 jmp假装你正在使用函数指针。

这也适用于绝对地址。 x86 机器代码无法编码直接跳转,但 GAS asm 语法可以让您将跳转目标编写为绝对数字地址。链接器将填充正确的 rel32 偏移量以从 jmp 结束的任何地方到达绝对地址。

所以你的内联 asm 模板只需要生成 jmp 0x12345 作为汇编器的输入就可以直接跳转。

testasm 的 asm 输出:

    movl %edi, 0x57F0F0( ,%esi, 4) 
    jmp   foo               #
    jmp   foo           
    jmp   0x12345        
    jmp   284280        # constant substituted by the compiler from a static const unsigned C variable
    ret

反汇编输出:

 mov    %edi,0x57f0f0(,%esi,4)
 jmp    80483f0 <foo>
 jmp    80483f0 <foo>
 jmp    12345 <_init-0x8035f57>
 jmp    45678 <_init-0x8002c24>
 ret

请注意,跳转目标已解码为十六进制绝对地址。 (Godbolt 无法轻松访问 copy/paste 原始机器代码,但您可以在鼠标悬停在左栏上时看到它。)

这仅适用于位置相关代码(不适用于 PIC),否则绝对重定位是不可能的。请注意,许多最近的 Linux 发行版将 gcc 设置为默认使用 -pie 来为 64 位可执行文件启用 ASLR,因此 您可能需要 -no-pie -fno-pie to make this work ,或者请求寄存器中的地址(r 约束和 jmp *%[addr])实际执行间接跳转到绝对地址而不是相对跳转。