GCC - 如何重新对齐堆栈?
Posted
技术标签:
【中文标题】GCC - 如何重新对齐堆栈?【英文标题】:GCC - How to realign stack? 【发布时间】:2011-02-15 10:56:47 【问题描述】:我尝试构建一个使用 pthreads 和 __m128 SSE 类型的应用程序。根据 GCC 手册,默认堆栈对齐是 16 个字节。为了使用__m128,要求是16字节对齐。
我的目标 CPU 支持 SSE。我使用不支持运行时堆栈重新对齐的 GCC 编译器(例如 -mstackrealign)。我不能使用任何其他 GCC 编译器版本。
我的测试应用程序如下所示:
#include <xmmintrin.h>
#include <pthread.h>
void *f(void *x)
__m128 y;
...
int main(void)
pthread_t p;
pthread_create(&p, NULL, f, NULL);
应用程序产生异常并退出。经过简单的调试(printf "%p", &y),发现变量y不是16字节对齐的。
我的问题是:如何在不使用任何 GCC 标志和属性(它们没有帮助)的情况下正确地重新对齐堆栈(16 字节)?我应该在这个线程函数 f() 中使用 GCC 内联汇编器吗?
【问题讨论】:
如果您必须使用特定的 gcc 版本,请包括 gcc 版本(例如 gcc 4.3.2 i386)和主机/目标操作系统(例如 Debian 5.0 (lenny) Linux 2.6.26 i686)。知道是否建议 gcc 4.3 选项与 3.4 可能会有所不同。 【参考方案1】:这不应该首先发生,但要解决此问题,您可以尝试:
void *f(void *x)
__m128 y __attribute__ ((aligned (16)));
...
【讨论】:
不,它没有帮助。同样的问题。 我猜你是在 Windows 上而不是在正确的操作系统上做这个?这里有一些关于解决这个问题的好信息:sourceware.org/ml/pthreads-win32/2008/msg00056.html 看起来这是旧版本 gcc 中的一个错误 - 它似乎已在 2004 年左右修复 - 是否有某些原因您不能使用更新的工具链? 其实不行,我不能使用另一个 GCC 版本——我们有一个特定的硬件/软件环境。 我正在尝试使用内联汇编器实现显式堆栈调整。【参考方案2】:在堆栈上分配一个比sizeof(__m128)
大 15 字节的数组,并使用该数组中第一个对齐的地址。如果需要多个,请将它们分配到一个数组中,并留出 15 字节的边距以进行对齐。
我不记得分配 unsigned char
数组是否可以让您免受编译器的严格别名优化,或者它是否只能反过来工作。
#include <stdint.h>
void *f(void *x)
unsigned char y[sizeof(__m128)+15];
__m128 *py = (__m128*) (((uintptr_t)&y) + 15) & ~(uintptr_t)15);
...
【讨论】:
您可能还想检查整个线程堆栈是否以 16 字节对齐方式分配。 谢谢,但是 ptr_t 是什么,为什么要使用 &~15 ? 不幸的是,无论潜在的编译器优化如何(例如将其保存在寄存器中),这都会强制变量位于堆栈上。 我猜它应该是uintptr_t
,但不管怎样,它只是一个整数类型,大到可以容纳一个指针。
@Paul R 对,我正在寻找正确的头文件,但因为我记错了名称而找不到它。 @psihodelia &~15
的意思是“向下舍入到 16 的倍数”。【参考方案3】:
我已经解决了这个问题。 这是我的解决方案:
void another_function()
__m128 y;
...
void *f(void *x)
asm("pushl %esp");
asm("subl $16,%esp");
asm("andl $-0x10,%esp");
another_function();
asm("popl %esp");
首先,我们将堆栈增加 16 个字节。其次,我们使最不重要的半字节等于 0x0。我们使用 push/pop 操作数保存堆栈指针。我们调用另一个函数,它有自己的所有局部变量 16 字节对齐。所有嵌套函数的局部变量也将 16 字节对齐。
而且它有效!
【讨论】:
说真的。更新您的编译器。不要为自己在代码中加入 rube goldberg 设备而自豪。 此代码似乎将 ESP 保存在堆栈上,然后将 ESP 移动到其他位置,然后弹出 ESP。这将导致随机值弹出到 ESP 中。这不会导致崩溃吗?或者您是否使用调用约定,其中 ESP 保存在其他地方,可能保存到 EBP 中,并在最后恢复,从而使 POP 变得多余? 1) 我无法更新 GCC -> 我有一个特定的运行时环境和一个特定的 x86 兼容 CPU。 2)不,为什么会导致崩溃?保存 ESP,然后恢复它不会导致任何崩溃或随机值。我已经在没有 pushl/popl 的情况下测试了上面的代码,它也可以。没有任何调用约定,ESP 也没有保存在其他地方。 就像 user9876 说的——你知道“pushl %esp”是做什么的吗?从概念上讲,它的工作原理如下: Memory[%esp] = %esp %esp -= 4; //取决于你的堆栈如何增长,它可能是 "+=4" 然后,一个 "popl %esp" 本质上是:%esp += 4; %esp = Memory[%esp] 现在,如果在“push”和“pop”之间修改了 esp - 第二次内存访问(“pop”)将从错误的地址读取。唯一合理的解释是编译器在函数 f() 的序言中也将 %esp 保存在其他地方(例如在 ebp 中?),然后在 f() 的尾声中恢复它。因此,它隐藏了您的错误。【参考方案4】:另一种解决方案是,使用填充函数,它首先对齐堆栈,然后调用f
。所以不是直接调用f
,而是调用pad
,它首先填充堆栈,然后调用foo
,并使用对齐的堆栈。
代码如下所示:
#include <xmmintrin.h>
#include <pthread.h>
#define ALIGNMENT 16
void *f(void *x)
__m128 y;
// other stuff
void * pad(void *val)
unsigned int x; // to get the current address from the stack
unsigned char pad[ALIGNMENT - ((unsigned int) &x) % ALIGNMENT];
return f(val);
int main(void)
pthread_t p;
pthread_create(&p, NULL, pad, NULL);
【讨论】:
【参考方案5】:抱歉复活一个旧线程...
对于那些使用比 OP 更新的编译器的人,OP 提到了 -mstackrealign
选项,这导致我转到 __attribute__((force_align_arg_pointer))
。如果您的函数正在优化以使用 SSE,但 %ebp
未对齐,如果您需要,这将透明地执行运行时修复。我还发现这只是i386
上的一个问题。 x86_64
ABI 保证参数对齐到 16 个字节。
__attribute__((force_align_arg_pointer))
void i_crash_when_not_aligned_to_16_bytes()
...
对于那些可能想了解更多信息的人来说很酷的文章:http://wiki.osdev.org/System_V_ABI
【讨论】:
谢谢。它帮助解决了 making.so
files runnable as binaries 的 32 位 x86 问题。它还帮助我找到了讨论 confusion about this stuff 的错误。以上是关于GCC - 如何重新对齐堆栈?的主要内容,如果未能解决你的问题,请参考以下文章
使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)