与GCC内联汇编和VC之间的差异

时间:2017-10-20 04:31:41

标签: gcc assembly inline-assembly

我将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 $0x2e, %%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语法。

我不知道问题出在哪里。

1 个答案:

答案 0 :(得分:2)

这不太可能只是因为获取语法正确,因为你依赖于寄存器中的值被设置为asm语句之前的某些内容,并且你没有使用任何输入操作数来实现发生。 (出于某种原因,你需要在跳跃之前用cmp设置标志?)

如果该片段在MSVC中以某种方式工作,那么你的代码取决于MSVC优化器的选择(就哪个C值在哪个寄存器中),这看起来很疯狂。

无论如何,任何内联asm问题的第一个答案是https://gcc.gnu.org/wiki/DontUseInlineAsm,如果你可以避免它。现在可能是用C语言重写事物的好时机(如果需要,可能还有一些__builtin函数)。

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

您可以使用asm goto,但这可能仅适用于同一功能中的标签。

就语法而言,请忽略%%ds:,因为它无论如何都是默认段。 ($ds之后的所有内容都被视为垃圾邮件,因为$ds是符号ds的地址。注册名称以%开头。)另外,请忽略base完全,而不是使用零。使用

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

您可以通过汇编英特尔版本并以AT& T语法反汇编来告诉您如何编写该反汇编程序。

您可以轻松地在纯C中实现该存储,例如int32_t *p = (int32_t *)0x57F0F0; p[foo]=bar;

对于jmp操作数use %c0 to get the address with no $因此编译器的asm输出为jmp 0x12345而不是jmp $0x12345。另请参阅https://stackoverflow.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无法轻松访问复制/粘贴原始机器代码,但您可以在鼠标左侧列上看到它。)

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