В Windows стек растет от больших адресов к меньшим. Иногда это определяется архитектурно, а иногда это просто принятое соглашение. Значение указателя стека (регистр процессора), является указателем на значение в верхней части стека. А значения, расположенные глубже по стеку, соответственно, находятся по большим адресам. Но что происходит с данными, которые расположены по адресам, меньшим, чем указатель стека?



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



Для Windows размер красной зоны варьируется в зависимости от аппаратной архитектуры и часто равен нулю.


Архитектура Размер красной зоны
x86 0 байт
x64 0 байт
Itanium 16 байт*
Alpha AXP 0 байт
MIPS32 0 байт
PowerPC 232 байта**
ARM32 8 байт
ARM64 16 байт

* У платформы Itanium стоит отметить особенность: там красная зона расположена над указателем стека, а не под ним.
** В случае PowerPC красная зона является побочным эффектом соглашения о вызовах.


Любая память за красной зоной (ниже по стеку) считается непостоянной (volatile) и может быть изменена операционной системой в любое время.


А если серьезно, почему операционную систему вообще заботит то, что я делаю со своим стеком? Я имею в виду, это мой стек! Операционная система не говорит мне, что делать с памятью, которую я выделяю через VirtualAlloc. Что отличает стек от любой другой памяти?


Рассмотрим следующий код для x86 платформы:


    MOV     [esp-4], eax       ; сохранить eax ниже указателя стека
    MOV     ecx, [esp-4]       ; считать сохраненное значение в ecx
    CMP     ecx, eax           ; они одинаковые?
    JNZ     panic              ; N: случилось что-то безумное

Пояснение к комментарию для инструкции JNZ

Соглашение о кодировании для ассемблера говорит, что комментарии для инструкций перехода должны описывать результат, если переход выполнен. В приведенном выше примере инструкция CMP задает вопрос «Являются ли они одинаковыми?». И инструкция JNZ переходит, если они не равны. Таким образом, комментарий начинается с «N:», что означает что переход будет выполнен, если ответом на предыдущий вопрос является «No» (Нет), а в оставшейся части комментария описывается сама ситуация, когда выполняется переход.


Соглашение о кодировании для ассемблера?

Да, у нас есть соглашение о кодировании для ассемблера.


Возможна ли ситуация, когда условный переход будет осуществлен?


Поскольку на x86 нет красной зоны, память с отрицательными смещениями относительно указателя стека может быть перезаписана в любое время. Поэтому для приведенного выше кода возможен переход к метке panic.


Отладчик может использовать память за красной зоной как удобное место для хранения своих данных. Например, если вы используете команду .call, отладчик выполнит вложенный вызов в том же стеке и, вероятно, использует часть этого пространства стека для сохранения регистров, чтобы их можно было восстановить после возврата из вызываемой функции. Поэтому любые данные, хранящиеся за пределами красной зоны, будут уничтожены.


Даже во время нормальной работы операционная система может в любой момент перезаписать данные за пределами красной зоны. Вот, например, как это может произойти:


Предположим, что ваша нить (thread) отработала свой квант времени сразу после сохранения данных за красной зоной. Пока ваша нить ожидает возможности возобновить выполнение, менеджер памяти временно забирает у вашего кода физическую страницу памяти (page out). В конце концов ваша нить снова получает управление и менеджер памяти пытается подкачать страницу кода обратно (page in). О нет, во время подкачки происходит ошибка ввода-вывода! Операционная система помещает фрейм исключения для STATUS_IN_PAGE_ERROR на стек, что приводит к порче данных, которые вы сохранили за красной зоной.


Операционная система диспетчеризует это исключение. Она обращается к векторному обработчику исключений (VEH), который является другой частью вашей программы. Обработчик был установлен специально, для обработки исключительных ситуаций, возникающих из-за возможного запуска вашей программы непосредственно с CD-ROM или ненадежной сетевой ФС. Программа отображает запрос, в котором просит пользователя снова вставить компакт-диск и предлагает повторить попытку. Если пользователь говорит, что нужно повторить, то обработчик векторных исключений возвращает EXCEPTION_CONTINUE_EXECUTION, а операционная система перезапустит инструкцию, на которой возникло исключение.


На этот раз перезапуск завершается успешно, потому что CD-ROM присутствует (и читается) и код может быть успешно подкачан в память. Выполняется следующая инструкция, которая загружает значение за пределами красной зоны в регистр ecx. Но это уже не то значение, которое было сохранено предыдущей инструкцией, поскольку исключение STATUS_IN_PAGE_ERROR перезаписало его. Сравнение говорит, что данные разные, и мы переходим к метке panic.


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

Комментарии (17)


  1. kuza2000
    14.01.2019 01:34
    +1

    В Windows стек растет от больших адресов к меньшим

    Причем здесь Windows? Это определяется архитектурой процессора.
    Иногда это определяется архитектурно, а иногда это просто принятое соглашение

    Какое соглашение??? Есть процессоры без команды ret? Там что, вместо ret так: mov rip, [rsp]? бред какой-то…
    Да, у нас есть соглашение о кодировании для ассемблера.

    Какие соглашения? У кого «у нас»?
    У платформы Itanium стоит отметить особенность: там красная зона расположена над указателем стека, а не под ним.

    Почему? Там стек расположен по другому? Или как?
    Почему у ARM32 8 байт под указателем не меняется? А если прерывание, тогда как?
    Почему у ARM64 16 байт под указателем не может быть изменено?
    В случае PowerPC красная зона является побочным эффектом соглашения о вызовах.

    По ссылке — соглашение о вызовах виндовс на power pc.
    Если честно, я вообще не понял о чем статья. Одно название чего стоит…
    Кто-то понимает, о чем тут речь? )))


    1. Tanner
      14.01.2019 02:10
      +2

      Направление роста стека не всегда определяется архитектурой процессора. У PDP-11, например, стек мог расти в любую сторону, а push и pop были макросами. Что касается вызова подпрограмм, у ARM нет инструкций call и ret, есть только регистр связи ? условная верхушка стека вызовов.

      Основной посыл статьи понятен и даже банален: не используй для хранения данных незаполненную часть стека, потому что она может быть перезаписана в любой момент (в результате обработки прерывания, например). Но вот про «красную зону» я не знал. Хотя, как я понимаю, использование «красной зоны» на практике ? довольно грязный хак, даже когда это безопасно.


      1. khim
        14.01.2019 04:11

        У PDP-11, например, стек мог расти в любую сторону, а push и pop были макросами.
        Зачем поднимать почившую в бозе экзотику? Современный ARM (но не ARM64 AKA AArch64) устроен так же: выделенного регистра стека нет, стек может расти в любую сторону, а если захотеть — можно завести 2, 3, 8 равноправных стеков.

        Хотя, как я понимаю, использование «красной зоны» на практике ? довольно грязный хак, даже когда это безопасно.
        На Linux активно используется — там она 128 байт на x86-64.


      1. kuza2000
        14.01.2019 18:39

        Точно! Почитал архитектуру ARM, действительно определяется соглашением. Правда, один вариант считается стандартом. Про регистр связи мне понравилось — его содержимое можно сохранить один раз на входе в пп и восстановить при выходе. И потом вызывать другие пп без обращений к памяти, неплохо. А для простых пп, если вложенных вызовов нет, просто не трогаем этот регистр.
        Кстати, а на каких платформах Windows работает? x86, PowerPC… про ARM что-то слышал — есть уже или в процессе только? Вообще, хотелось бы ее на ARM, очень уж экономичные они :)


        1. kITerE
          14.01.2019 18:52
          +1

          про ARM что-то слышал — есть уже или в процессе только?

          ASUS NovaGo TP370QL. Но там именно ARM64.


          1. kuza2000
            14.01.2019 19:46

            Понятно. Жалуются на производительность
            Сыровата технология еще. Похоже, эмуляция используется частенько.


    1. khim
      14.01.2019 04:18

      В случае PowerPC красная зона является побочным эффектом соглашения о вызовах.
      По ссылке — соглашение о вызовах виндовс на power pc.
      Если честно, я вообще не понял о чем статья. Одно название чего стоит…
      Кто-то понимает, о чем тут речь? )))
      Я, в свою очередь, не понимаю чего вы не понимаете. Соглашение о вызовах на PowerPC (описанное по ссылке) устроено так, что вначале ниже SP кладутся параметры (232 байта), а потом SP сдвигается так, что он оказывается «под ними». Соотвественно все штуки которые могу асинхронно использовать стек должны эту зону не трогать — а вдруг вы параметры уже сложили, а вызов ещё не успели сделать? Де-факто получается «красная зона», хотя они таковой и не была задумана…


    1. kITerE
      14.01.2019 09:15

      Причем здесь Windows? Это определяется архитектурой процессора.

      Windows тут как конкретная реализация.


      Какие соглашения?

      Coding conventions


      У кого «у нас»?

      У одной из команд Microsoft, в частности, в которой работает Raymond Chen.


      Почему? Там стек расположен по другому? Или как?

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


      Почему у ARM32 8 байт под указателем не меняется? А если прерывание, тогда как?

      Не уверен за ARM, но прерывания, вроде не должны исполняться на стеке пользовательского пространства.


      Почему у ARM64 16 байт под указателем не может быть изменено?

      Потому, что это красная зона, которую ОС не трогает.


      По ссылке — соглашение о вызовах виндовс на power pc.

      А что должно быть по ссылке, озаглавленной "побочный эффект соглашения о вызовах"?


      Если честно, я вообще не понял о чем статья. Одно название чего стоит…
      Кто-то понимает, о чем тут речь? )))

      В статье рассматривается вопрос о возможности в Windows хранения данных с стеке по отрицательным смещениям относительно указателя стека (за красной зоной).


      1. amarao
        14.01.2019 10:31

        Дурной пример. Почему? Потому что для linux или freebsd можно показать что именно там делает ядро со стеком (сырцы доступны), а у винды бага на гитхабе и сырцы всё ещё не доступны.


        1. khim
          14.01.2019 18:39

          Автору переведённой статьи сырцы более, чем доступны: их часть он сам и писал.


          1. amarao
            14.01.2019 18:50

            Что начинает звучать как издевательство над читателем. У меня есть, но не покажу.


  1. Tanner
    14.01.2019 02:08

    Сорри, промахнулся.


  1. MooNDeaR
    14.01.2019 10:04

    Интересно, а почему под некоторые архитектуры red zone имеет размер 0? :) Штука-то удобная, можно было бы юзать для разных отладочных целей. Есть какие-то другие механизмы, замещающие red zone или так сделали «потому что»? Насколько я помню, в Linux для x86_64 red zone имеет размер аж 128 байт, так что вряд ли это аппаратные ограничения.


    1. aamonster
      14.01.2019 10:36

      Как минимум, использование "в отладочных целях" (помнится, на 8080 это было вообще неизбежно, брейкпойнты ставились внедрением в код команды типа RST 3 — по сути, call 18h, кладёт в стек) исключает использование для чего-то ещё.
      А на 8086/88 под DOS порчу памяти под стеком использовали защиты софта для обнаружения отладчика.


    1. kuza2000
      14.01.2019 18:49

      Конечно, не аппаратные, просто соглашение. Даже не знал о существовании подобного, всегда считал, что все что меньше SP, может меняться в любой момент.

      Соглашение о вызовах на PowerPC (описанное по ссылке) устроено так, что вначале ниже SP кладутся параметры (232 байта), а потом SP сдвигается так, что он оказывается «под ними»

      Но можно же вначале сдвинуть SP, а потом положить? В чем смысл таких манипуляций? Поддержка этой red zone требует ресурсов — необходимо помнить о ее наличии, что бы случайно не поменять. Зачем?


      1. khim
        14.01.2019 21:51
        +1

        Эффективность. RISC. Все инструкции имеют одинаковую длину (обычно 4 байта), что значит, что оффсеты в инструкциях, манипулирующих со стеком ограничены (обычно что-то типа ±4K допускается). Если мы разрешим использовать только область памяти «выше» SP, то максимальный объём «фрейма», с которым мы можем работать быстро — уменьшается. А главное — размер начинает зависеть от того, какие функции мы будем вызывать и сколько у них параметров…


        1. kuza2000
          14.01.2019 22:37

          А вот это уже аргумент…