GCC - 如何重新排列堆栈?

psi*_*lia 7 c stack gcc sse pthreads

我尝试构建一个使用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);
}
Run Code Online (Sandbox Code Playgroud)

应用程序生成异常并退出.经过简单的调试(printf"%p",&y)后,我发现变量y不是16字节对齐的.

我的问题是:如何在不使用任何GCC标志和属性(它们没有帮助)的情况下正确地重新对齐堆栈(16字节)?我应该在这个线程函数f()中使用GCC内联汇编程序吗?

Pas*_*uoq 7

在堆栈上分配一个大于15字节的数组sizeof(__m128),并使用该数组中的第一个对齐地址.如果需要多个,请在具有单个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);
   ...
}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,无论潜在的编译器优化如何(例如将其保存在寄存器中),这都会强制变量位于堆栈上. (6认同)

psi*_*lia -1

我已经解决了这个问题。这是我的解决方案:

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");
}
Run Code Online (Sandbox Code Playgroud)

首先,我们将堆栈增加 16 个字节。其次,我们使最低有效半字节等于 0x0。我们使用压入/弹出操作数保留堆栈指针。我们调用另一个函数,它的所有局部变量都是 16 字节对齐的。所有嵌套函数的局部变量也将按 16 字节对齐。

它有效!

  • 此代码似乎将 ESP 保存在堆栈上,然后将 ESP 移至其他位置,然后弹出 ESP。这将导致随机值被弹出到 ESP 中。这不会导致崩溃吗?或者您是否使用一种调用约定,其中 ESP 保存在其他地方(可能保存到 EBP 中),并在最后恢复,从而使 POP 变得多余? (8认同)
  • 严重地。更新您的编译器。不要因为在代码中放入鲁布·戈德堡设备而感到自豪。 (6认同)
  • 就像 user9876 所说 - 你知道“pushl %esp”是做什么的吗?从概念上讲,它的工作原理如下: Memory[%esp] = %esp %esp -= 4; //根据堆栈的增长方式,它可能是“+=4”,那么“popl %esp”本质上就是:%esp += 4; %esp = Memory[%esp] 现在,如果在“push”和“pop”之间修改了 esp - 第二次内存访问(“pop”)将从错误的地址读取。其工作原理的唯一合理解释是编译器将 %esp 保存在函数 f() 的序言​​中的其他地方(例如,在 ebp 中?),然后在 f() 的结尾中恢复它。因此,它隐藏了您的错误。 (5认同)