В Windows стек растет от больших адресов к меньшим. Иногда это определяется архитектурно, а иногда просто условно. Значение, на которое указывает регистр указателя стека, располагается на вершине стека, а значения, расположенные глубже в стеке, находятся по адресам выше. Но что происходит с данными по адресам меньшим, чем указатель стека?
⋮ | ||
вершина стека |
данные стека данные стека данные стека |
←указатель стека |
за стеком за стеком за стеком |
страна загадок | |
⋮ |
⋮ | ||
вершина стека |
данные стека данные стека данные стека |
←указатель стека |
действительные данные |
красная зона | |
здесь живут драконы |
вход запрещён | |
⋮ |
Архитектура | Размер красной зоны |
---|---|
x86 | 0 байт |
x64 | 0 байт |
Itanium | 16 байт* |
Alpha AXP | 0 байт |
MIPS32 | 0 байт |
PowerPC | 232 байта |
ARM32 | 8 байт |
ARM64 | 16 байт |
В случае PowerPC красная зона является побочным эффектом соглашения о вызовах.
Любая память ниже стека за красной зоной считается непостоянной (volatile) и может быть изменена операционной системой в любое время.
Подождите, а почему операционная система вообще лезет в мой стек? Я имею в виду, это мой стек! Операционная система же не трогает память, которую я выделяю через
VirtualAlloc
. Чем отличается стек от любой другой памяти?Рассмотрим следующую последовательность на x86:
MOV [esp-4], eax ; сохраняем eax ниже указателя стека MOV ecx, [esp-4] ; теперь читаем его обратно в ecx CMP ecx, eax ; равны ли они? JNZ panic ; Н: произошло что-то странное¹Может ли вообще сработать этот переход?
Поскольку на x86 нет красной зоны, то память с отрицательными смещениями относительно указателя стека может быть перезаписана в любое время. Поэтому приведённая выше последовательность может перейти на метку
panic
.Отладчик может использовать память за красной зоной как удобное место для хранения некоторых данных. Например, если вы используете команду
.call
отладчика WinDbg, отладчик выполнит вложенный вызов на том же стеке и, вероятно, использует часть пространства стека для сохранения регистров, чтобы их можно было восстановить после возврата из функции, вызываемой .call
. Поэтому любые данные, хранящиеся за пределами красной зоны, будут уничтожены.Даже во время нормальной работы операционная система может в любой момент перезаписать данные за пределами красной зоны. Вот пример сценария, когда это может произойти:
Предположим, что квант времени вашего потока заканчивается сразу после сохранения данных за красной зоной. Пока ваш поток ожидает возможности возобновить выполнение, менеджер памяти выгружает его код из оперативной памяти. Позже ваш поток возобновляет выполнение, и менеджер памяти пытается вернуть его обратно. О нет, во время возобновления произошла ошибка ввода-вывода! Операционная система помещает в стек кадр исключений для
STATUS_IN_PAGE_ERROR
, что приводит к перезаписи данных, которые вы скрыли за красной зоной.Затем операционная система возбуждает исключение. Она обращается к векторному обработчику исключений, который был установлен какой-то другой частью вашей программы - специально для обработки таких случаев, потому что ваша программа может быть запущена непосредственно с CD-ROM или ненадежной сети. Программа показывает пользователю запрос (на возврат CD-ROM, восстановление подключения) и предлагает повторить попытку. Если пользователь говорит повторить, то обработчик векторных исключений возвращает
EXCEPTION_CONTINUE_EXECUTION
, и операционная система перезапустит невыполненную инструкцию.На этот раз перезапуск завершается успешно, потому что CD-ROM уже присутствует, и код может быть прочитан с CD-ROM обратно в оперативную память. Выполняется следующая инструкция, которая загружает значение за пределами красной зоны в регистр ecx - но эта операция не загрузит значение, сохраненное предыдущей инструкцией, поскольку исключение
STATUS_IN_PAGE_ERROR
перезаписало это значение. Сравнение не удаётся, и мы переходим к метке panic
.Если вы хотите сохранить данные в стеке, делайте это правильно: сначала уменьшите указатель стека, а затем сохраните значение в допустимой части стека. Не скрывайте данные за красной зоной. Эта память изменчива и может исчезнуть из-под вас в любой момент.
¹ Соглашение по кодированию для ассемблера² говорит, что комментарии к инструкциям перехода должны описывать результат, если переход сделан. В приведенном выше примере инструкция
CMP
задает вопрос: "Являются ли регистры одинаковыми?". И инструкция JNZ
делает переход, если они не равны. Таким образом, комментарий начинается с «Н:» и означает, что переход выполняется, если ответом на предыдущий вопрос является "Нет", а в оставшейся части комментария описывается, что означает выполнение перехода.² Да, у нас в Microsoft есть соглашение по кодированию для ассемблера.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.