我将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语法。
我不知道问题出在哪里。
答案 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]
)中实际上间接跳转到绝对地址而不是相对跳转。