Какова функция инструкций push / pop, используемых для регистров в сборке x86?
Когда я читаю об ассемблере, я часто сталкиваюсь с людьми, которые пишут, что они нажимают на определенный регистр процессора, а потом снова вынимают его, чтобы восстановить его предыдущее состояние.
- Как вы можете нажать регистр? Куда это толкнуло? Зачем это нужно?
- Это сводится к одной инструкции процессора или это более сложный?
5 ответов
выдвижение значения (не обязательно хранящегося в регистре) означает запись его в стек.
Поппинг означает восстановление всего, что находится на вершине стека, в регистр. Это основные инструкции:
push 0xdeadbeef ; push a value to the stack
pop eax ; eax is now 0xdeadbeef
; swap contents of registers
push eax
mov eax, ebx
pop ebx
Вот как вы нажимаете регистр. Я предполагаю, что мы говорим о x86.
push ebx
push eax
Он помещается в стек. Значение ESP
регистр уменьшается до размера выдвигаемого значения по мере увеличения стека в системах x86.
Это необходимо для сохранения ценностей. Общее использование
push eax ; preserve the value of eax
call some_method ; some method is called which will put return value in eax
mov edx, eax ; move the return value to edx
pop eax ; restore original eax
push
это отдельная инструкция в x86, которая выполняет две вещи внутренне.
- Сохраните переданное значение по текущему адресу
ESP
регистр. - Уменьшить
ESP
записать в размер заданного значения.
Куда это толкнуло?
esp - 4
, Точнее:
esp
вычитается на 4- значение доводится до
esp
pop
меняет это.
System V ABI говорит Linux сделать rsp
при запуске программы укажите разумное расположение стека: /questions/9900042/kakovo-sostoyanie-registra-po-umolchaniyu-pri-zapuske-programmyi-asm-linux/9900057#9900057 которое обычно следует использовать.
Как вы можете нажать регистр?
Пример минимального GNU GAS:
.data
/* .long takes 4 bytes each. */
val1:
/* Store bytes 0x 01 00 00 00 here. */
.long 1
val2:
/* 0x 02 00 00 00 */
.long 2
.text
/* Make esp point to the address of val2.
* Unusual, but totally possible. */
mov $val2, %esp
/* eax = 3 */
mov $3, %ea
push %eax
/*
Outcome:
- esp == val1
- val1 == 3
esp was changed to point to val1,
and then val1 was modified.
*/
pop %ebx
/*
Outcome:
- esp == &val2
- ebx == 3
Inverses push: ebx gets the value of val1 (first)
and then esp is increased back to point to val2.
*/
Выше на GitHub с работоспособными утверждениями.
Зачем это нужно?
Это правда, что эти инструкции могут быть легко реализованы с помощью mov
, add
а также sub
,
Они объясняют, что существуют такие комбинации инструкций, что Intel решила предоставить их нам.
Причина, по которой эти комбинации встречаются так часто, заключается в том, что они позволяют легко сохранять и восстанавливать значения регистров в памяти, чтобы они не перезаписывались.
Чтобы понять проблему, попробуйте скомпилировать код на C вручную.
Основная трудность заключается в том, чтобы решить, где будет храниться каждая переменная.
В идеале все переменные должны помещаться в регистры, которые являются самой быстрой памятью для доступа (в настоящее время примерно в 100 раз быстрее, чем оперативная память).
Но, конечно, мы можем легко иметь больше переменных, чем регистров, особенно для аргументов вложенных функций, поэтому единственным решением является запись в память.
Мы могли бы писать по любому адресу памяти, но поскольку локальные переменные и аргументы вызовов и возвращений функций вписываются в красивый шаблон стека, который предотвращает фрагментацию памяти, это лучший способ справиться с этим. Сравните это с безумием написания распределителя кучи.
Затем мы позволяем компиляторам оптимизировать распределение регистров для нас, так как это завершает NP и является одной из самых сложных частей написания компилятора. Эта проблема называется распределением регистров, и она изоморфна раскраске графов.
Когда распределитель компилятора вынужден хранить вещи в памяти вместо просто регистров, это называется разливом.
Это сводится к одной инструкции процессора или это более сложный?
Все, что мы знаем наверняка, это то, что Intel документирует push
и pop
инструкция, поэтому они являются одной инструкцией в этом смысле.
Внутренне его можно расширить до нескольких микрокодов, один для изменения esp
и один, чтобы сделать IO памяти, и принять несколько циклов.
Но также возможно, что один push
быстрее, чем эквивалентная комбинация других инструкций, поскольку она более конкретна.
В основном это документировано:
- Питер Кордес упоминает, что методы, описанные на http://agner.org/optimize/microarchitecture.pdf предполагают, что
push
а такжеpop
взять одну микро операцию. - Johan упоминает, что, поскольку Pentium M Intel использует "механизм стека", в котором хранятся предварительно вычисленные значения esp+regsize и esp-regsize, что позволяет выполнять push и pop за один раз. Также упоминается по адресу: https://en.wikipedia.org/wiki/Stack_register
- Что такое микрокод Intel?
- https://security.stackexchange.com/questions/29730/processor-microcode-manipulation-to-change-opcodes
- Сколько циклов ЦП необходимо для каждой инструкции по сборке?
Зарегистрированные и выталкивающие регистры находятся за кадром, эквивалентным этому:
push reg <= same as => sub $8,%rsp # subtract 8 from rsp
mov reg,(%rsp) # store, using rsp as the address
pop reg <= same as=> mov (%rsp),reg # load, using rsp as the address
add $8,%rsp # add 8 to the rsp
Обратите внимание, что это x86-64 At&t синтаксис.
Используется как пара, это позволяет сохранить регистр в стеке и восстановить его позже. Есть и другие варианты использования.
Почти все процессоры используют стек. Стек программ выполнен в технике LIFO с аппаратным управлением.
Стек - это объем памяти программ (ОЗУ), который обычно выделяется в верхней части кучи памяти ЦП и увеличивается (по инструкции PUSH указатель стека уменьшается) в противоположном направлении. Стандартный термин для вставки в стек - PUSH, а для удаления из стека - POP.
Управление стеком осуществляется с помощью предназначенного для стека регистра ЦП, также называемого указателем стека, поэтому, когда ЦП выполняет POP или PUSH, указатель стека будет загружать / сохранять регистр или константу в памяти стека, а указатель стека будет автоматически уменьшаться или увеличиваться в зависимости от количества нажатых слов или вставлен в (из) стека.
С помощью инструкций ассемблера мы можем хранить в стеке:
- Регистры процессора, а также константы.
- Обратные адреса для функций или процедур
- Переменные функции / процедуры в / из
- Функции / процедуры локальных переменных.