Какова функция инструкций push/pop, используемых в регистрах в сборке x86?

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

  • Как вы можете нажать регистр? Куда его нажимают? Почему это необходимо?
  • Разве это сводится к одной инструкции процессора или сложнее?

Ответ 1

нажатие значения (не обязательно сохраненное в регистре) означает запись его в стек.

popping означает восстановление всего, что находится поверх стека, в регистр. Это основные инструкции:

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

Ответ 2

Вот как вы нажимаете регистр. Я предполагаю, что мы говорим о 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

A push - это одна команда в x86, которая делает две вещи внутри себя.

  • Сохраните нажатое значение на текущем адресе регистра ESP.
  • Уменьшить регистр ESP до размера нажатого значения.

Ответ 3

Куда его нажимают?

esp - 4. Точнее:

  • esp вычитается на 4
  • значение переносится на esp

pop отменяет это.

Система V ABI сообщает Linux, чтобы заставить rsp указывать на разумное местоположение стека при запуске программы: fooobar.com/questions/47285/... который вы обычно должны использовать.

Как вы можете нажать регистр?

Минимальный пример 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 с runnable утверждениями.

Почему это необходимо?

Верно, что эти инструкции могут быть легко реализованы с помощью mov, add и sub.

Они рассуждают, что они существуют, состоит в том, что эти комбинации инструкций настолько часты, что Intel решила предоставить их нам.

Причина, по которой эти комбинации настолько часты, заключается в том, что они позволяют временно сохранять и восстанавливать значения регистров в памяти, чтобы они не перезаписывались.

Чтобы понять проблему, попробуйте выполнить компиляцию кода C вручную.

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

В идеале все переменные будут вписываться в регистры, которые являются самой быстрой памятью для доступа (в настоящее время примерно на 100 раз быстрее, чем оперативная память).

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

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

Затем мы разрешаем компиляторам оптимизировать распределение регистров для нас, поскольку это NP полный и одна из самых сложных частей написания компилятора. Эта проблема называется распределением регистров, и она изоморфна раскраске графа.

Когда компилятор-распределитель вынужден хранить вещи в памяти, а не только регистры, которые известны как разлив.

Разве это сводится к одной инструкции процессора или сложнее?

Все, что мы точно знаем, это то, что Intel документирует инструкцию push и pop, поэтому они являются одной инструкцией в этом смысле.

Внутри он может быть расширен до нескольких микрокодов, один для изменения esp и один для ввода IO памяти и выполнения нескольких циклов.

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

Это в основном un (der) документировано:

Ответ 4

Почти все процессоры используют стек. Стек программы LIFO с поддержкой аппаратного обеспечения.

Стек представляет собой объем памяти программы (RAM), обычно выделяемый в верхней части кучи памяти процессора и растет (при инструкции PUSH указатель стека уменьшается) в противоположном направлении. Стандартный термин для вставки в стек PUSH, а для удаления из стека POP.

Управление стеком осуществляется через регистр CPU, предназначенный для стека, также называемый указателем стека, поэтому, когда процессор выполняет POP или PUSH, указатель стека будет загружать/хранить регистр или константу в стек стека и указатель стека будут автоматически уменьшены или увеличены в соответствии с количеством слов, вставляемых или вложенных в стек.

Через инструкции ассемблера, которые мы можем сохранить в стек:

  • Регистры CPU, а также константы.
  • Обратные адреса для функций или процедуры
  • Функции/процедуры ввода/вывода переменные
  • Функции/процедуры локальные переменные.

Ответ 5

Нажав и выпадающие регистры за кулисами, эквивалентными этому:

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 Синтаксис & t.

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