Куда и почему должен указывать указатель кадра x64? (Windows x64 ABI)

Я читал длинный каталог очень хороших статей по Windows x64 ABI. Очень второстепенным аспектом этих статей является описание указателя фрейма. Общая суть состоит в том, что, поскольку правила стека вызовов Windows x64 настолько жесткие, выделенный указатель кадра обычно не требуется, хотя и является необязательным.

Единственное исключение, которое я постоянно отмечал, - это когда используется alloca() для динамического выделения памяти в стеке. Функции, выполняющие это, очевидно, требуют указателя фрейма. Например, чтобы процитировать документацию Microsoft по «Распределение стека» (курсив и жирным шрифтом добавлены мной):

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

К этому alloca() документация Microsoft к этому загадочно добавляет:

_alloca должен быть выровнен по 16 байт и дополнительно необходим для использования указателя кадра.

Прежде всего, зачем его использовать? Я предполагаю, что стек вызовов раскручивается при исключении, но я еще не нашел удовлетворительного объяснения.

Следующий вопрос: куда он должен указывать? В первой из двух приведенных выше цитат говорится, что он «должен» использоваться для обозначения основания «фиксированной части стека». Что такое «фиксированная часть стека»? У меня сложилось впечатление, что этот термин обозначает в данном кадре диапазон адресов, который включает (от более высоких адресов к более низким):

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

Опять же, я не нашел удовлетворительного определения этой «фиксированной части». Страница «Распределение стека», на которую я ссылалась выше, содержит приведенную ниже диаграмму со словами "если используется, указатель стека будет обычно указывать сюда":

введите описание изображения здесь

Это отличное сообщение в блоге столь же расплывчато. , включая диаграмму, на которой указатель кадра «указывает где-то здесь», где «здесь» - адреса для сохраненных энергонезависимых регистров и локальных переменных.

И последний кусочек загадочности из статьи Microsoft MSDN, озаглавленной «Построение области стека динамических параметров», который содержит только это:

Если используется указатель кадра, существует возможность динамического создания области стека параметров. В настоящее время это не делается в компиляторе x64.

Что значит «в целом»? Где «где-то здесь»? Какой вариант существует? Есть ли правило? Какая разница?

Или, tl; dr: О чем спрашивает название. Любой ответ, содержащий аннотированную сборку, с благодарностью принимается.


person 0xbe5077ed    schedule 03.07.2014    source источник


Ответы (2)


Из диаграммы ясно видно, что указатель кадра указывает на нижнюю часть фиксированной части кадра локального стека. «Фиксированная часть» - это часть, размер которой не изменяется и положение которой фиксировано относительно начального указателя стека. На схеме это обозначено как «Локальные переменные и сохраненные энергонезависимые регистры» [1].

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

void function1()
{
  int a;
  int *b = (int*)alloca(sizeof(int));
  ...
}

void function2()
{
  int& a = *(int*)alloca(sizeof(int));
  int *b = (int*)alloca(sizeof(int));
  ...
}

Операционная система не может различить эти две функции. Оба они хранят a в стеке непосредственно под энергонезависимыми регистрами.

Эта эквивалентность является причиной того, что на диаграмме написано «в целом». На практике компиляторы указывают его в указанном месте, но теоретически они могут указать его в любом месте внутри локального фрейма, если расстояние от указателя фрейма до адреса возврата является постоянным.

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

[1] Это можно сделать из того факта, что в тексте указано, что указатель фрейма указывает на «основание фиксированной части стека», а на диаграмме говорится: «Указатель фрейма обычно будет указывать сюда», и он указывает на база локальных переменных и сохраненных энергонезависимых регистров. Предполагая, что текст и диаграмма согласуются, это означает, что фиксированная часть стека совпадает с локальными переменными и сохраненными энергонезависимыми регистрами. Это тот же самый вывод, который вы делаете каждый день, даже не осознавая этого. Например, если в рассказе говорится

Салли позвала брата. "Билли, ты где?"

Вы можете сделать вывод, что Билли - брат Салли.

person Raymond Chen    schedule 03.07.2014
comment
Хороший ответ. Единственное, что меня смущает, - это упоминание об операционной системе, которая, ИМХО, вообще во всем этом не участвует. Соглашение о вызовах и использование стека могут работать так же хорошо на голом железе без какой-либо ОС. Информирование ОС будет означать что-то вроде системного вызова ... которого здесь явно не происходит. - person BitTickler; 10.12.2017
comment
Сообщите, что здесь важна операционная система, потому что за пределами вашей программы (операционной системы) есть код, который должен понимать структуру стека вашего кода. Если вы кодируете «голое железо», вы можете настроить свой стек так, как вам нравится, и никому не будет до этого дела. - person Raymond Chen; 10.12.2017

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

Указатель кадра также удобен, когда приходит время освободить alloca память, потому что вы можете просто восстановить указатель стека в известное место, не беспокоясь о том, насколько изменился указатель стека.

Я не думаю, что ABI требует указатель фрейма как таковой, или что он должен быть rbp, или что он должен указывать на какое-то конкретное место (отказ от ответственности: я не использую окна).

person Jester    schedule 03.07.2014