0

当我使用 GCC 和 -fomit-frame-pointer 选项编译 32 位 C 代码时,不使用帧指针 (ebp),除非我的函数使用 stdcall 和至少一个参数调用 Windows API 函数。

例如,如果我只使用 Windows API 中的 GetCommandLine(),它没有参数/参数,GCC 将省略帧指针并将 ebp 用于其他事情,从而加快代码速度并且没有那个无用的序言。

但是当我调用一个接受至少一个参数的 stdcall Win32 函数时,GCC 完全忽略了 -fomit-frame-pointer 并使用了帧指针,并且代码在检查中更糟糕,因为它不能将 ebp 用于一般用途事物。更不用说我发现帧指针毫无意义。我的意思是,我想编译发布和分发,我为什么要关心调试?(如果我想调试,我只会在重现错误后使用调试版本)

我的堆栈肯定不包含像 alloca 这样的动态分配。那么,堆栈具有已定义的结构,但尽管我有选择,但 GCC 还是选择了愚蠢的方法?我是否缺少一些东西来强制它不使用帧指针?

我对它的第二个看法是它拒绝对 Win32 函数使用“推送”指令。我尝试过的所有其他编译器,他们使用推送指令来推送堆栈,从而产生更好更紧凑的代码,更不用说它是推送 stdcall 参数的最自然方式。然而 GCC 顽固地使用“mov”指令在每个位置手动移动,相对于 esp 偏移,因为它需要保持堆栈指针完全静止。stdcall 对调用者来说很容易,但 GCC 完全忽略了 stdcall 的意义,因为它在与它交互时会生成这个糟糕的代码。更糟糕的是,由于堆栈指针是静态的,它仍然使用帧指针?只是为什么?

我试过-mpush-args,它什么也没做。

我还注意到,如果我让我的堆栈大到足以超过一页(4096 字节),GCC 将添加一个函数的序言,该函数除了“按位或”每 4096 字节零堆栈(什么都不做) . 我假设它是为了触摸堆栈并在堆栈被保留的情况下自动提交带有页面错误的内存?不幸的是,即使我将堆栈的初始提交(不是保留)设置为足够高以容纳我的堆栈,它也会这样做,更不用说一开始甚至不需要这样做。最好的冗余代码。

这些错误在 GCC 中吗?还是我在选项中缺少的东西?我应该使用其他东西吗?如果我遗漏了一些选项,请告诉我。

我真的希望我不必为了调用 stdcall 函数和使用推送指令而制作内联 asm 宏(我猜这也将避免帧指针)。对于今天应该在编译器中使用的如此基本的东西来说,这听起来真的有点矫枉过正。是的,我使用的是 GCC 4.8.1,所以不是一个古老的版本。

作为额外的问题,是否可以强制 GCC 在函数序言中不将寄存器保存在堆栈上?我将自己的直接入口点与 -nostartfiles 参数一起使用,因为它是一个纯 Windows 应用程序,并且无需标准 lib 启动就可以正常工作。如果我使用属性((noreturn)),它会丢弃恢复寄存器的结尾,但它仍然会在序言时将它们推送到堆栈上,我不知道是否有办法强制它不为这个入口点保存寄存器功能。不管怎样,至少没什么大不了的,我猜它只会感觉更完整。谢谢!

4

2 回答 2

2

请参阅答案强制 GCC 在调用函数之前将参数推送到堆栈上(使用 PUSH 指令)

即尝试-mpush-args -mno-accumulate-outgoing-args-mno-stack-arg-probe如果 gcc 抱怨,它也可能需要。

于 2014-07-27T19:36:10.357 回答
1

它看起来像提供 -mpush-args -mno-accumulate-outgoing-args -mno-stack-arg-probe 作品,特别是最后一个作品。现在代码像其他编译器一样更干净、更正常,并且它使用 PUSH 作为参数,甚至通过这种方式在 OllyDbg 中更容易跟踪。

不幸的是,这会强制使用愚蠢的帧指针,即使在完全不需要它的小函数中也是如此。真的有办法绝对强制 GCC 禁用帧指针吗?!

于 2014-07-27T15:40:49.100 回答