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

101

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

  • Как вы можете протолкнуть регистр? Куда это подталкивают? Зачем это нужно?
  • Сводится ли это к инструкции одного процессора или это более сложно?
Ars emble
источник
3
Предупреждение: все текущие ответы даны в синтаксисе сборки Intel; толчок популярности в AT & T синтаксис, например , использует пост-FIX , как b, w, l, или , qчтобы обозначить размер памяти манипулируют. Пример: pushl %eaxиpopl %eax
Hawken
5
@hawken На большинстве ассемблеров, способных воспринимать синтаксис AT&T (особенно газ), постфикс размера может быть опущен, если размер операнда может быть выведен из размера операнда. Это случай для приведенных вами примеров, поскольку %eaxон всегда имеет размер 32 бита.
Gunther Piez

Ответы:

153

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

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

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
Линус Клин
источник
5
Явный операнд для push и pop - это r/mне просто регистрация, так что вы можете push dword [esi]. Или даже pop dword [esp]загрузить, а затем сохранить то же значение обратно по тому же адресу. ( github.com/HJLebbink/asm-dude/wiki/POP ). Я упоминаю об этом только потому, что вы говорите «не обязательно регистр».
Питер Кордес
2
Вы также можете popв области памяти:pop [0xdeadbeef]
SS Anne
Привет, в чем разница между push / pop и pushq / popq? Я использую macos / intel
SteakOverflow
46

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

  1. Уменьшите ESPрегистр на размер введенного значения.
  2. Сохраните переданное значение по текущему адресу ESPрегистра.
Мадхур Ахуджа
источник
@vavan только что отправил запрос на исправление
jgh fun-run
38

Куда это подталкивают?

esp - 4. Точнее:

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

pop меняет это.

ABI System V сообщает Linux rspуказать разумное расположение стека при запуске программы: какое состояние регистра по умолчанию при запуске программы (asm, linux)? что вы обычно должны использовать.

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

Минимальный пример 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а другой для ввода-вывода памяти и занимает несколько циклов.

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

В основном это недокументировано:

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
4
Вам не нужно гадать, как push/ popдекодировать в упс. Благодаря счетчикам производительности возможно экспериментальное тестирование, и Агнер Фог это сделал и опубликовал таблицы с инструкциями . Процессоры Pentium-M и более поздних версий имеют единичный упор push/ popблагодаря механизму стека (см. Pdf-файл Microarch Agner). Сюда входят новейшие процессоры AMD, благодаря соглашению Intel / AMD о совместном использовании патентов.
Питер Кордес
@PeterCordes круто! Значит, Intel задокументировала счетчики производительности для подсчета микроопераций?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Кроме того, локальные переменные, передаваемые из регистров, обычно все еще остаются горячими в кэше L1, если какая-либо из них действительно используется. Но чтение из регистра фактически бесплатно, без задержки. Так что это бесконечно быстрее, чем кеш L1, в зависимости от того, как вы хотите определять термины. Для локальных переменных, предназначенных только для чтения, перенесенных в стек, основная стоимость - это просто дополнительные операции загрузки (иногда операнды памяти, иногда с отдельными movзагрузками). Для разлитых неконстантных переменных циклические обходы переадресации хранилища приводят к большой дополнительной задержке (дополнительные ~ 5 с по сравнению с пересылкой напрямую, а инструкции хранилища не из дешевых).
Питер Кордес
Да, есть счетчики для общего числа ошибок на нескольких различных этапах конвейера (выпуск / выполнение / вывод из эксплуатации), так что вы можете подсчитать объединенный домен или неиспользуемый домен. См., Например, этот ответ . Если бы я переписывал этот ответ сейчас, я бы использовал ocperf.pyсценарий оболочки, чтобы получить простые символические имена для счетчиков.
Питер Кордес
23

Выталкивание и извлечение регистров за кулисами эквивалентно следующему:

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

Обратите внимание, что это синтаксис At & t x86-64.

При использовании в паре это позволяет сохранить регистр в стеке и восстановить его позже. Есть и другие варианты использования.

Gowrath
источник
4
Да, эти последовательности правильно имитируют push / pop. (кроме того, что push / pop не влияют на флаги).
Питер Кордес
2
Лучше использовать lea rsp, [rsp±8]вместо add/, subчтобы лучше имитировать эффект push/ popна флагах.
Руслан
12

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

Стек - это объем программной (ОЗУ) памяти, обычно выделяемой в верхней части кучи памяти ЦП и увеличивающейся (при выполнении команды PUSH указатель стека уменьшается) в противоположном направлении. Стандартный термин для вставки в стек - PUSH, а для удаления из стека - POP .

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

С помощью инструкций ассемблера мы можем сохранить в стек:

  1. Регистры процессора, а также константы.
  2. Обратные адреса для функций или процедур
  3. Функции / процедуры входящие / исходящие переменные
  4. Локальные переменные функций / процедур.
GJ.
источник