【发布时间】:2015-02-11 21:51:22
【问题描述】:
考虑以下简化的示例函数:
void foo(void) {
int t;
asm("push %0\n\t"
"push %0\n\t"
"call bar"
:
: "m" (t)
:
);
}
如果我在没有优化的情况下使用 Cygwin x86 gcc 4.8.3 编译它,推送指令变为:
push -4(%ebp)
push -4(%ebp)
这很好。问题发生在 -O:
push 12(%esp)
push 12(%esp)
这显然是错误的。第一次推送更改 esp,然后第二次推送访问错误的位置。我有read that adding "%esp" to the clobber list should fix it,但它没有帮助。如何让 GCC 使用帧指针或正确考虑 esp 更改?
(假设从 bar 函数返回会将 esp 设置为它在 asm 语句之前的值。我只需要调用一个 thiscall 函数并为此使用内联汇编。)
【问题讨论】:
-
"r" (&t)怎么样?这给了我push %rax\npush %rax。 -
这是“错误的”,因为 gcc 不对 asm 主体执行任何类型的语义分析。 gcc asm 不接受
%ebp或%esp作为约束 IIRC,也不接受 ELF (PIC) 平台上的%ebx。我认为@DavidWohlferd 应该提交一个答案——这是一个不错的选择,因为%eax无论如何都经常作为返回值被破坏。而是"a" (&t)。 -
好吧,如果我们想假设
%eax正在被返回,那么我们可能应该做一些更像"=a" (retval) : "0" (&t)的事情。毕竟,我们必须让 gcc 知道 eax 的内容正在改变。我会说回调到 c++ 代码会很棘手。您是否在调用之前推送了被调用者可能更改的每个寄存器(并在返回时弹出它们)?您确定所有 bar 可能需要 gcc 存储在寄存器中的所有内容在调用之前都被刷新回内存吗?这里可能没有选项,但为此使用内联 asm 不是我的首选。 -
使用“r”会起作用,但它有点混乱并导致其他问题。这只是一个简化的例子。我实际调用的函数有更多参数,并且破坏了一些寄存器。 GCC 不允许用于输入的寄存器在 clobber 列表中。我将不得不为一些被破坏的寄存器引入虚拟输出变量,这样我就不会用完寄存器。
-
我看不出在这里使用“r”本质上是“混乱”的,尽管它可能会限制其他地方可用的寄存器数量(这可能会导致混乱)。虽然使用虚拟输出在美学上是不纯的,但我不知道使用它们对性能有任何实际影响。因此,虽然这可能确实是最好的解决方案,但如果不查看实际代码、了解调用约定等,就很难说。
标签: gcc inline-assembly gcc4