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

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

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

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

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

Суть проблемы: У нас нет redzone, и мы сделали:

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

Ответ 1

"Красная зона" не является строго необходимой. В ваших терминах это можно считать "бессмысленным". Все, что вы могли бы сделать, используя красную зону, вы также можете сделать традиционный способ, которым вы это сделали, нацеливаясь на 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 гарантирует, что асинхронные вещи, такие как сигналы и обработчики прерываний, не изменяют красную зону. Звонки на другие функции могут иметь значение clobber в красной зоне, поэтому это не особенно полезно, кроме функций листа (которые те функции, которые не вызывают никаких других функций, как если бы они находились в "листе" дерева функций).


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

Ответ 2

У вас есть смещения неправильного пути в вашем примере, поэтому это не имеет смысла. Код не должен иметь доступ к региону ниже указателя стека - это undefined. Красная зона защищает первые 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).