Различное выравнивание памяти для разных размеров буфера - PullRequest
0 голосов
/ 10 сентября 2018

Я пытаюсь разобраться с переполнением стека, немного поигрался с этим флагом -fno-stack-protector и попытался понять, как память управляется в процессе.

Я скомпилировал следующий код (используя Ubuntu 18.04.1 LTS (x86_64), gcc 7.3.0., ASLR отключен)

int main (int argc, char *argv[])
{
    char    buff[13];
    return 0;
}

следующим образом: gcc -g -o main main.c -fno-stack-protector. Затем я вызвал gdb main, b 4, run и, как видно из следующих выводов

(gdb) print &buff
$2 = (char (*)[13]) 0x7fffffffd963

0x7fffffffd963: 0xff    0xff    0x7f    0x00    0x00    0x00    0x00    0x00
0x7fffffffd96b: 0x00    0x00    0x00    0x00    0x00    0x10    0x46    0x55
0x7fffffffd973: 0x55    0x55    0x55    0x00    0x00    0x97    0x5b    0xa0
0x7fffffffd97b: 0xf7    0xff    0x7f    0x00    0x00    0x01    0x00    0x00

(gdb) info frame 0
Stack frame at 0x7fffffffd980:
 [...]
 Saved registers:
 rbp at 0x7fffffffd970, rip at 0x7fffffffd978

байты 13, выделенные для буфера, следуют непосредственно после сохраненного базового указателя rbp.

После увеличения размера буфера с 13 до 21 я получил следующие результаты:

(gdb) print &buff   
$3 = (char (*)[21]) 0x7fffffffd950

(gdb) x/48bx buff
0x7fffffffd950: 0x10    0x46    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd958: 0xf0    0x44    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd960: 0x50    0xda    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffd968: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd970: 0x10    0x46    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd978: 0x97    0x5b    0xa0    0xf7    0xff    0x7f    0x00    0x00

(gdb) info frame 0
Stack frame at 0x7fffffffd980:   
 [...]
 Saved registers:
  rbp at 0x7fffffffd970, rip at 0x7fffffffd978

Теперь есть еще 11 байтов после rbp до того, как последует буфер.

  • Во втором случае, почему есть 11 дополнительных байтов? Это связано с выравниванием стека, например должен ли буфер быть выровнен на 16 байтов (кратно 16), начиная с rbp?
  • Почему в первом случае расположение памяти отличается, кажется, нет выравнивания?

1 Ответ

0 голосов
/ 10 сентября 2018

Для x86-64 System V ABI требуется 16-байтовое выравнивание для локальных или глобальных массивов размером 16 байт или более и для всех VLA C99 (которые всегда локальны).

Массив использует то же выравнивание, что и его элементы, за исключением того, что локальный или глобальный переменная массива длиной не менее 16 байтов или переменная массива C99 переменной длины всегда имеет выравнивание не менее 16 байтов. 4

4 Требование выравнивания позволяет использовать инструкции SSE при работе с массивом. Компилятор не может вообще вычислить размер массива переменной длины (VLA), но это ожидается что большинству VLA потребуется по крайней мере 16 байтов, поэтому логично предписать, что VLA имеет как минимум 16-байтовое выравнивание.

Массивы фиксированного размера, меньшие, чем один вектор SIMD (16 байт), не имеют этого требования, поэтому они могут эффективно упаковываться в макет стека.

Обратите внимание, что это не относится к массивам внутри структур, только к локальным и глобальным объектам.

(Для динамического хранения выравнивание возвращаемого значения malloc должно быть выровнено настолько, чтобы удерживать любой объект до этого размера, и, поскольку x86-64 SysV имеет maxalign_t из 16 байтов, malloc также должен возвращать 16-байтовые выровненные указатели, если размер равен 16 или выше. Для меньших распределений он может вернуть только 8B-выровненный для 8B-распределения, если он этого хочет.)


Требование к локальным массивам делает безопасным написание кода, который передает их адрес функции, которая требует 16-байтового выравнивания, но это в основном не то, что сам ABI действительно должен указывать.

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


Указание его для глобальных переменных полезно, однако: для автоматически векторизованного кода, сгенерированного компилятором, безопасно выполнять выравнивание для глобальных массивов, даже если это extern int[] в объектном файле, скомпилированном другим компилятором.

...