Зачем нам нужно выделение стека, когда у нас красная зона?

У меня есть следующие сомнения:

Как мы знаем, System V x86-64 ABI дает нам область фиксированного размера (128 байт) в кадре стека, так называемую красную зону. Таким образом, в результате нам не нужно использовать, например, sub rsp, 12, Просто сделай mov [rsp-12], X и это все.

Но я не могу понять идею этого. Почему это имеет значение? Нужно ли sub rsp, 12 без красной зоны? В конце концов, размер стека ограничен в начале, так почему sub rsp, 12 это важно? Я знаю, что это позволяет нам следовать за вершиной стека, но давайте проигнорируем это в тот момент.

Я знаю, что используют некоторые инструкции rsp значение (как ret) но не волнует это в тот момент.

Суть проблемы в том, что у нас нет красной зоны, и мы сделали:

function:
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    ret

Это разница с?

function:
    sub rsp, 1024
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    add rsp, 1024
    ret

2 ответа

Решение

"Красная зона" не является строго необходимой. С вашей точки зрения, это можно считать "бессмысленным". Все, что вы могли бы сделать, используя красную зону, вы также можете сделать это традиционным способом, ориентируясь на IA-32 ABI.

Вот что говорит AMD64 ABI о "красной зоне":

128-байтовая область за пределами места, на которое указывает %rsp считается зарезервированным и не должен изменяться обработчиками сигналов или прерываний. Поэтому функции могут использовать эту область для временных данных, которые не нужны при вызовах функций. В частности, листовые функции могут использовать эту область для всего кадра стека, вместо того, чтобы корректировать указатель стека в прологе и эпилоге. Эта область известна как красная зона.

Настоящая цель красной зоны - оптимизация. Его существование позволяет коду предполагать, что 128 байтов ниже rsp не будет асинхронно засоряться сигналами или обработчиками прерываний, что позволяет использовать его в качестве рабочего пространства. Это делает ненужным явное создание пустого пространства в стеке путем перемещения указателя стека в rsp, Это оптимизация, потому что инструкции по уменьшению и восстановлению rsp Теперь можно отказаться, экономя время и пространство.

Так что да, хотя вы могли бы сделать это с AMD64 (и нужно было бы сделать это с IA-32):

function:
    push rbp                      ; standard "prologue" to save the
    mov  rbp, rsp                 ;   original value of rsp

    sub  rsp, 32                  ; reserve scratch area on stack
    mov  QWORD PTR [rsp],   rcx   ; copy rcx into our scratch area
    mov  QWORD PTR [rsp+8], rdx   ; copy rdx into our scratch area

    ; ...do something that clobbers rcx and rdx...

    mov  rcx, [rsp]               ; retrieve original value of rcx from our scratch area
    mov  rdx, [rsp+8]             ; retrieve original value of rdx from our scratch area
    add  rsp, 32                  ; give back the stack space we used as scratch area

    pop  rbp                      ; standard "epilogue" to restore rsp
    ret

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

Кроме того, поскольку нам больше не нужно уменьшать указатель стека, мы можем использовать rsp в качестве базового указателя (вместо rbp), что делает ненужным сохранение и восстановление rbp (в прологе и эпилоге), а также освобождая rbp для использования в качестве другого общего регистра!

(Технически, включение пропуска указателя кадра (-fomit-frame-pointer, включен по умолчанию с -O1 поскольку ABI позволяет это), это также позволило бы компилятору исключить разделы пролога и эпилога с теми же преимуществами. Однако при отсутствии красной зоны необходимость настройки указателя стека на резервное пространство не изменится.)

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


И последнее замечание: Windows x64 ABI немного отличается от AMD64 ABI, используемого в других операционных системах. В частности, он не имеет понятия "красная зона". Площадь за пределами rsp считается изменчивым и подлежит перезаписи в любое время. Вместо этого он требует, чтобы вызывающий объект выделил домашнее адресное пространство в стеке, которое затем будет доступно для использования вызывающим абонентом в случае, если ему потребуется пролить любой из переданных регистром параметров.

У вас неправильные смещения в вашем примере, поэтому это не имеет смысла. Код не должен обращаться к области ниже указателя стека - он не определен. Красная зона предназначена для защиты первых 128 байтов ниже указателя стека. Ваш второй пример должен читать:

function:
    sub rsp, 1024
    mov [rsp+16], rcx
    mov [rsp+32], rcx
    mov [rsp+128], rcx
    mov [rsp+1016], rcx
    add rsp, 1024
    ret

Если объем пустого пространства, в котором нуждается функция, составляет до 128 байт, тогда она может использовать адреса под указателем стека без необходимости настройки стека: это оптимизация. Для сравнения:

function:        // Not using red-zone.
    sub rsp, 128
    mov [rsp+120], rcx
    add rsp, 128
    ret

С тем же кодом, используя красную зону:

function:        // Using the red-zone, no adjustment of stack
    mov [rsp-8], rcx
    ret

Путаница в отношении смещений от указателя стека обычно возникает из-за того, что компиляторы генерируют отрицательные смещения из кадра (RBP), а не положительные смещения из стека (RSP).

Другие вопросы по тегам