Подтвердить что ты не робот

Выравнивание памяти сегодня и 20 лет назад

В знаменитой статье "Smashing the Stack for Fun and Profit" ее автор использует функцию C

void function(int a, int b, int c) {
  char buffer1[5];
  char buffer2[10];
}

и генерирует соответствующий выход кода сборки

pushl %ebp
movl %esp,%ebp
subl $20,%esp

Автор объясняет, что, поскольку компьютеры адресуют память в кратном размере слова, компилятор зарезервировал 20 байтов в стеке (8 байтов для buffer1, 12 байтов для buffer2).

Я попытался воссоздать этот пример и получил следующее

pushl   %ebp
movl    %esp, %ebp
subl    $16, %esp

Другой результат! Я пробовал различные комбинации размеров для buffer1 и buffer2, и кажется, что современные gcc не имеют размер буфера для буферов, чтобы увеличить размер слова. Вместо этого он выполняет параметр -mpreferred-stack-boundary.

В качестве иллюстрации - используя бумажные арифметические правила, для buffer1 [5] и buffer2 [13] я бы получил 8 + 16 = 24 байта, зарезервированных в стеке. Но на самом деле я получил 32 байта.

Статья довольно старая, и с тех пор произошло много чего. Я хотел бы знать, что именно мотивировало это изменение поведения? Это движение к 64-битным машинам? Или что-то еще?

Edit

Код компилируется на машине x86_64 с использованием gcc версии 4.8.2 (Ubuntu 4.8.2-19ubuntu1) следующим образом:

$ gcc -S -o example1.s example1.c -fno-stack-protector -m32

4b9b3361

Ответ 1

Что изменилось, SSE, для которого требуется 16-байтовое выравнивание, это описано в этом более раннем документе gcc для - mpreferred-stack-border = num, в котором говорится (внимание мое):

В Pentium и PentiumPro двойные и длинные двойные значения должны быть выровнены с 8-байтовой границей (см. -malign-double) или имеют значительные штрафы за производительность во время выполнения. На Pentium III, тип данных потокового расширения SIMD Extension (SSE) __m128 имеет аналогичные штрафы, если он не выровнен по 16 байт.

Это также подкрепляется бумагой Smashing the Modern Stack For Fun and Profit, которая охватывает это другие современные изменения, которые ломают Smashing the Stack для развлечения и прибыли.

Ответ 2

Ориентация памяти, выравнивание стека - это всего лишь один аспект, зависит от архитектуры. Он частично определен в бинарном интерфейсе Applicion для языка и стандартном стандарте обработки вызовов (иногда он является как единым спецификацией) для архитектуры (процессор, он может даже варьироваться в зависимости от платформы), а также зависит от компилятора/инструментальной цепочки, где первые документы оставляют место для вариантов.

Первые два документа (имена могут меняться) в основном предназначены для внешнего интерфейса между функциями; они могут оставить внутреннюю структуру инструментальной цепочки. Однако это должно соответствовать архитектуре. Обычно аппаратное обеспечение требует минимального выравнивания, но допускает большее выравнивание по соображениям производительности (например, минимальное выравнивание по байтам, но для этого потребуется несколько циклов шины для чтения 32-битного слова, поэтому компилятор использует 32-битное выравнивание).

Обычно компилятор (после PCS) использует оптимальное выравнивание для архитектуры и под контролем настроек оптимизации (оптимизация для скорости или размера). Он учитывает не только размер объекта (согласованный с его естественной границей), но и размеры внутренних шин (например, 32-разрядный x86 имеет внутренние 64 или 128-битные шины, процессоры ARM имеют внутренние 32 до 128 (возможно, даже более широкие ) биты), кеши и т.д. Для локальных переменных он также может принимать во внимание шаблоны доступа, поэтому две смежные переменные могут быть загружены параллельно в пару регистров вместо двух отдельных нагрузок или даже переупорядочить такие переменные.

Для экземпляра стека может потребоваться, например, более высокое выравнивание, поэтому ЦП может одновременно вводить в кадр прерывания два регистра, нажимать векторные регистры, которые требуют более высокого выравнивания и т.д. Вы можете написать довольно толстую книгу об этом предмете (и я ставка, у кого-то уже есть).

Таким образом, в общем случае единого правила не подходит для всех правил. Однако для структурирования и упаковки массивов стандарт C определяет некоторые правила для упаковки/выравнивания, в основном для обеспечения согласованности, например. sizeof (type) и адрес в массиве (требуется для правильного malloc()).

Даже макеты char могут быть выровнены для оптимальной компоновки кеша. Обратите внимание, что не только процессор, у которого могут быть кэши, но также мосты PCIe, не говоря уже о том, что PCIe передает себя на страницы DRAM.

Ответ 3

Я не пробовал эту конкретную версию компилятора или версию распространения, о которой вы сообщаете. Я предполагаю, что это 16 из требований выравнивания байтов в стеке (т.е. Все корректировки стека будут выровнены по оси x, а x может быть 16 для вашего вызова).

Обратите внимание, что выравнивание параметров, с которым вы, похоже, начали работать, немного отличается от приведенного выше и контролируется выровнением по переменной в gcc. Попробуйте использовать их, и вы должны увидеть разницу.