GCC - Как перестроить стек?
Я пытаюсь создать приложение, которое использует pthreads и SSE-тип __m128. Согласно руководству GCC, выравнивание стека по умолчанию составляет 16 байтов. Для использования __m128 требуется выравнивание по 16 байтов.
Мой целевой процессор поддерживает 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 байтам.
Мой вопрос: как я могу правильно перестроить стек (16 байтов) без использования каких-либо флагов и атрибутов GCC (они не помогают)? Должен ли я использовать встроенный Ассемблер GCC в этой функции потока f()?
5 ответов
Я решил эту проблему. Вот мое решение:
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-байтовым локальным переменным.
И это работает!
Выделите в стеке массив, который на 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);
...
}
Это не должно происходить в первую очередь, но чтобы обойти проблему, вы можете попробовать:
void *f(void *x)
{
__m128 y __attribute__ ((aligned (16)));
...
}
Извините, что воскресил старую ветку...
Для тех, кто имеет более новый компилятор, чем 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
Другим решением было бы использование функции заполнения, которая сначала выравнивает стек, а затем вызывает 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);
}