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]
)实际执行间接跳转到绝对地址而不是相对跳转。
我正在将 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]
)实际执行间接跳转到绝对地址而不是相对跳转。