Не указывает ли указатель на фреймворк указателя стека? - assembly


1

Насколько я понимаю, указатель стека указывает на "свободную" память в стеке, а "толкание" данных в стеке записывается в местоположение, указанное указателем стека, и увеличивает/уменьшает его.

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

Кажется, действительно нужен только указатель кадра. И это даже требует гораздо большей цели, чем просто модификация данных в текущем фрейме стека, например, для использования в отладке и для разворачивания стека. Я что-то пропустил?

  •  7
  •  2
  • 8 фев 2020 2020-02-08 08:05:36

2 ответа

2

Ваш вопрос должен быть: избыточен ли указатель кадра?

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

Один пример:

mov ebp, esp
push esi
mov eax, [ebp+4]
push edi
mov eax, [ebp+8]

также может быть записано как:

push esi
mov eax, [esp+8]
push edi
mov eax, [esp+16]

Некоторые специальные случаи, такие как функция alloca(), однако требуют использования как указателей фрейма, так и стека.

Указатель стека никогда не бывает лишним:

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

Поскольку такие прерывания предполагают, что память ниже указателя стека свободна, было бы очень плохой идеей использовать память под указателем стека; если происходит прерывание, чем память ниже указателя стека будет уничтожена!

В MIPS-процессорах (например) это чистое соглашение, которое из регистров является указателем стека; вы также можете сказать, что R9 является указателем стека, и стек не находится по адресу R9, но по адресу R9 + 1234. В 64-битном соглашении вызова Sparc используется такое странное соглашение для указателя стека. Однако это требует, чтобы весь код (включая операционную систему и все прерывания) использовал одно и то же соглашение.

На процессорах x86 это невозможно, потому что сам процессор будет считать, что память ниже указателя стека свободна: инструкции PUSH и CALL будут записываться в память под указателем стека, а в случае прерывания сам процессор будет хранить информацию по адресу, на который указывает указатель стека, без возможности изменить это поведение!

4

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

  • языковая среда выполнения обеспечивает нетривиальные гарантии выравнивания. В частности, проблема в 32-битном коде, когда фрейм стека содержит 8-байтовые переменные, такие как double. Доступ к неверно выровненной переменной очень дорог (x2, если он смещен на 4, x3, если он пересекает линию кеша L1) и может привести к недействительности гарантии модели памяти. Генератор кода обычно не может предполагать, что функция вводится с выровненным стеком, поэтому необходимо генерировать код в прологе функции, это может привести к уменьшению указателя стека на дополнительные 4 байта.

  • языковая среда исполнения предоставляет возможность динамически распределять пространство стека. Очень часто и желательно, это очень дешевая и быстрая память. Примерами являются alloca() в CRT, массивы переменной длины в C99 +, ключевое слово stackalloc на языке С#.

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