GCC - Как перестроить стек?

Я пытаюсь создать приложение, которое использует pthreads и тип __m128 SSE. Согласно руководству 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()?


person psihodelia    schedule 04.05.2010    source источник
comment
Если вам необходимо использовать определенную версию gcc, укажите версию gcc (например, gcc 4.3.2 i386) и хост/целевую ОС (например, Debian 5.0 (lenny) Linux 2.6.26 i686). Знание того, предлагать ли варианты gcc 4.3 или 3.4, может иметь значение.   -  person mctylr    schedule 04.05.2010


Ответы (5)


Выделите в стеке массив, который на 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);
   ...
}
person Pascal Cuoq    schedule 04.05.2010
comment
Вы также можете проверить, выделяется ли общий стек потоков с выравниванием по 16 байтам. - person Donal Fellows; 04.05.2010
comment
Спасибо, но что такое ptr_t и почему вы используете &~15? - person psihodelia; 04.05.2010
comment
К сожалению, это вынуждает переменную находиться в стеке независимо от возможных оптимизаций компилятора (например, сохранения ее в регистре). - person Paul R; 04.05.2010
comment
Я предполагаю, что это должно быть uintptr_t, но в любом случае это просто целочисленный тип, достаточно большой для хранения указателя. - person Paul R; 04.05.2010
comment
@Paul R Правильно, я искал правильный заголовочный файл и не смог его найти, потому что неправильно запомнил имя. @psihodelia &~15 означает округление до числа, кратного 16, сразу меньшему. - person Pascal Cuoq; 04.05.2010
comment
У меня это не работает, потому что у меня много вложенных функций и локальных переменных. - person psihodelia; 04.05.2010

Этого не должно происходить в первую очередь, но для решения проблемы вы можете попробовать:

void *f(void *x)
{
   __m128 y __attribute__ ((aligned (16)));
   ...
}
person Paul R    schedule 04.05.2010
comment
Нет, это не помогает. Та же проблема. - person psihodelia; 04.05.2010
comment
Я предполагаю, что вы делаете это в Windows, а не в подходящей операционной системе? Здесь есть полезная информация о том, как обойти эту проблему: sourceware.org/ мл/pthreads-win32/2008/msg00056.html - person Paul R; 04.05.2010
comment
Похоже, что это ошибка в старых версиях gcc — кажется, она была исправлена ​​примерно в 2004 году — есть ли какая-то причина, по которой вы не можете использовать более современную цепочку инструментов? - person Paul R; 04.05.2010
comment
На самом деле нет, я не могу использовать другую версию GCC — у нас есть специфическая аппаратная/программная среда. - person psihodelia; 04.05.2010
comment
Я пытаюсь реализовать явную настройку стека с помощью встроенного ассемблера. - person psihodelia; 04.05.2010

Другим решением было бы использование функции заполнения, которая сначала выравнивает стек, а затем вызывает 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);
}
person ablaeul    schedule 05.05.2010

Извините, что поднимаю старую тему...

Для тех, у кого более новый компилятор, чем 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

person AStupidNoob    schedule 15.06.2017

Я решил эту проблему. Вот мое решение:

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 байтам.

И это работает!

person psihodelia    schedule 04.05.2010
comment
Шутки в сторону. ОБНОВИТЕ КОМПИЛЯТОР. Не гордитесь собой за то, что включили в свой код устройства Руба Голдберга. - person Frank Krueger; 04.05.2010
comment
Этот код, по-видимому, сохраняет ESP в стеке, затем перемещает ESP в другое место, а затем извлекает ESP. Это приведет к тому, что в ESP будет добавлено случайное значение. Не вызывает ли это сбоя? Или вы используете соглашение о вызовах, в котором ESP сохраняется где-то еще, возможно, в EBP, и восстанавливается в конце, что делает этот POP излишним? - person user9876; 04.05.2010
comment
1) Я не могу обновить GCC -> у меня есть определенная среда выполнения и определенный процессор, совместимый с x86. 2) Нет, почему это может привести к сбою? Сохранение ESP, а затем его восстановление не приводит ни к сбою, ни к случайному значению. Я протестировал приведенный выше код также без pushl/popl, и он тоже в порядке. Никаких соглашений о вызовах и ESP не сохраняется где-то еще. - person psihodelia; 05.05.2010
comment
Как сказал пользователь 9876, вы знаете, что делает pushl %esp? Концептуально это работает так: Memory[%esp] = %esp %esp -= 4; //в зависимости от того, как растет ваш стек, он может быть +=4, тогда по сути это popl %esp: %esp += 4; %esp = Memory[%esp] Теперь, если между push и pop вы модифицировали esp - второй доступ к памяти (pop) будет читаться с неправильного адреса. Единственным разумным объяснением того, почему это работает, является то, что компилятор сохраняет %esp еще где-то (например, в ebp?) в прологе функции f(), а затем восстанавливает его в эпилоге f(). Таким образом, он скрывает вашу ошибку. - person Virgil; 07.06.2011