Назначение инструкции NOP и оператора выравнивания в сборке x86

15

Прошел год или около того с тех пор, как я в последний раз посещал занятия по сборке. В этом классе мы использовали MASM с библиотеками Irvine, чтобы упростить программирование.

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

Кто-нибудь знает?

alvonellos
источник
NOP ничего не делает, но он потребляет циклы. Я не думаю, что на ваш вопрос можно ответить, без кода, который мы можем только догадываться. Ну, я думаю, что слайд NOP ...
Яннис
11
НОП на самом деле что-то делает. Увеличивает указатель инструкций.
EricSchaefer

Ответы:

37

Часто время NOPиспользуется для выравнивания адресов команд. Это обычно встречается, например, при написании Shellcode для использования уязвимости переполнения буфера или форматной строки .

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

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

Другим вариантом использования для NOPинструкций будет то, что называется сани NOP . По сути, идея состоит в том, чтобы создать достаточно большой массив инструкций, которые не вызывают побочных эффектов (таких какNOPили увеличивая, а затем уменьшая регистр), но увеличивая указатель инструкции. Это полезно, например, когда нужно перейти к определенному коду, адрес которого неизвестен. Хитрость заключается в том, чтобы поместить указанные салазки NOP перед целевым кодом, а затем прыгнуть куда-нибудь к указанным салазкам. То, что происходит, - то, что выполнение, мы надеемся, продолжается от массива, у которого нет никаких побочных эффектов, и это переходит вперед команду за инструкцией, пока это не достигает желаемого фрагмента кода. Этот метод обычно используется в вышеупомянутых уязвимостях переполнения буфера и особенно для противодействия мерам безопасности, таким как ASLR .

Еще одно конкретное использование NOPинструкции - когда кто-то модифицирует код какой-либо программы. Например, вы можете заменить части условных переходов на NOPs и, таким образом, обойти это условие. Это часто используемый метод при « взломе » защиты от копирования программного обеспечения. Самое простое - это просто удалить конструкцию ассемблерного кода для if(genuineCopy) ...строки кода и заменить инструкции на NOPs и .. Вуаля! Никакие проверки не сделаны, и не подлинная копия работает!

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

zxcdw
источник
2
Это был замечательный ответ, спасибо, что нашли время, чтобы объяснить это! Я наконец понял!
alvonellos
Некоторые системы реального времени (на ум приходят ПЛК) позволяют вам «вставлять» новую логику в существующую программу во время ее работы. Эти системы оставляют NOP перед каждым небольшим фрагментом логики, так что вы можете перезаписать NOP с помощью перехода к новой логике, которую вы вставляете. В конце новой логики она перейдет к концу исходной логики, которую вы заменяете. Новая логика также будет иметь NOP впереди, так что вы также можете заменить новую логику.
Скотт Уитлок
10

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

lw   v0,4(v1)
jr   v0

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

Способ исправить это будет:

lw   v0,4(v1)
nop
jr   v0
nop

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

Дальнейшее чтение - немного о SPARC заполнении слотов задержки . Из этого документа:

Что можно положить в отсек задержки?

  • Некоторые полезные инструкции, которые должны быть выполнены независимо от того, ветвитесь вы или нет.
  • Некоторые инструкции, которые полезны, работают только тогда, когда вы переходите (или когда вы не переходите), но не приносят вреда, если выполняются в другом случае.
  • Когда все остальное терпит неудачу, инструкция NOP

Что НЕ ДОЛЖНО быть помещено в отсек задержки?

  • Все, что устанавливает CC, от которого зависит решение филиала. Команда ветвления принимает решение о том, выполнять ветвление или нет, но на самом деле ветвление выполняется только после команды задержки. (Только ветка задерживается, а не решение.)
  • Еще одна ветка инструкции. (Что произойдет, если вы это сделаете, это даже не определено! Результат непредсказуем!)
  • «Заданная» инструкция. Это действительно две инструкции, а не одна, и только половина из них будет в слоте задержки. (Ассемблер предупредит вас об этом.)

Обратите внимание на третий вариант в поле «Задержка». Вероятно, ошибка, которую вы увидели, связана с тем, что кто-то заполняет одну из вещей, которую нельзя помещать в отсек задержки. Поместив nop в это место, вы исправите ошибку.

Примечание: после повторного чтения вопроса это было для x86, у которого нет слотов задержки (вместо этого ветвление просто останавливает конвейер). Так что это не будет причиной / решением проблемы. В системах RISC это могло быть ответом.


источник
4
Обратите внимание, что вопрос помечен x86, а x86 не имеет слотов задержки. Никогда не будет, так как это серьезное изменение.
MSalters
6

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

permeakra
источник
Это не совсем то, что блок должен быть выровнен, это то, что вам не нужно извлекать последние пару байтов предыдущего блока. Так что переходить нормально 0x1002, потому что в выровненном блоке из 16B все еще есть 14 байтов инструкций, которые содержат целевой адрес, но не подходят для перехода 0x099D.
Питер Кордес
3

Одна из целей NOP (в общем случае сборки, а не только x86) - ввести временные задержки. Например, вы хотите запрограммировать микроконтроллер, который должен выводить на некоторые светодиоды с задержкой 1 с. Эта задержка может быть реализована с помощью NOP (и ответвлений). Конечно, вы можете использовать некоторые ADD или что-то еще, но это сделает код более нечитаемым; или, может быть, вам нужны все регистры.

m3th0dman
источник
1
Обычно для длительных периодов времени, таких как 1 секунда, используются таймеры. NOPS используются для эпох в пределах порядка часов - нано и микросекунд.
Mattnz
Это имеет смысл только на микроконтроллере, а не на современном x86. Большая часть кода x86 не насыщает конвейерную ширину современных суперскалярных неупорядоченных процессоров, поэтому добавление NOP между каждой инструкцией в большинстве кода будет иметь небольшое влияние (я думаю, число для «среднего» кода может быть 5 до 20% для удвоения количества инструкций, с некоторым кодом , не оказывающее замедления , но несколько тугих петель , показывающих почти 2x замедлением.) Во всяком случае, хрустящий код старого x86 традиционно используется в loopинструкции для петель задержки , а не NOPS.
Питер Кордес
3

Обычно для 80x86 инструкции NOP не требуются для корректности программы, хотя иногда на некоторых машинах стратегически размещенные NOP могут приводить к более быстрому выполнению кода. Например, на 8086 код должен быть выбран в виде двухбайтовых блоков, а процессор имеет внутренний буфер «предварительной выборки», который может содержать три таких блока. Некоторые инструкции будут выполняться быстрее, чем они могут быть получены, в то время как другие инструкции будут выполняться некоторое время. Во время медленных инструкций процессор будет пытаться заполнить буфер предварительной выборки, чтобы, если бы следующие несколько инструкций были быстрыми, они могли быть выполнены быстро. Если инструкция, следующая за медленной инструкцией, начинается с четной границы слова, инструкции из следующих шести байтов будут предварительно выбраны; если он начинается на границе нечетного байта, только пять байтов будут предварительно выбраны.

Такие проблемы с выравниванием памяти могут повлиять на скорость работы программы, но, как правило, они не влияют на корректность. С другой стороны, на старых процессорах есть некоторые проблемы, связанные с предварительной выборкой, когда NOP может повлиять на корректность. Если инструкция изменяет кодовый байт, который уже был предварительно выбран, 8086 (и я думаю, что 80286 и 80386) выполнит предварительно выбранную инструкцию, даже если она больше не соответствует тому, что находится в памяти. Добавление одного или двух NOP между инструкцией, которая изменяет память, и измененным байтом кода, может препятствовать извлечению байта кода до тех пор, пока он не будет записан. Заметьте, кстати, что многие схемы защиты от копирования использовали такого рода поведение; обратите внимание, однако, что это поведение не гарантируется. Различные варианты процессора могут обрабатывать предварительную выборку по-разному, некоторые могут сделать недействительными предварительно выбранные байты, если изменена память, из которой они были прочитаны, и прерывания, как правило, делают недействительным буфер предварительной выборки; код будет получен заново, когда прервутся возвраты.

Supercat
источник
3

Существует особый случай x86, который еще не описан в других ответах: обработка прерываний. Для некоторых его стилей могут быть разделы кода, когда прерывания отключены, потому что основной код работает с некоторыми данными, совместно используемыми обработчиками прерываний, но разумно разрешать прерывания между такими разделами. Если один наивно пишет


    STI
    CLI

это не будет обрабатывать ожидающие прерывания, потому что, ссылаясь на Intel:

После того, как установлен флаг IF, процессор начинает реагировать на внешние маскируемые прерывания после выполнения следующей инструкции.

так что это должно быть переписано как минимум:


    STI
    NOP
    CLI

Во втором варианте все ожидающие прерывания будут обрабатываться только между NOP и CLI. (Конечно, может быть много альтернативных вариантов, например, удвоение инструкции STI. Но явный NOP более очевиден, по крайней мере, для меня.)

Netch
источник
-2

NOP означает отсутствие операции

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

Также используется взломщиками и отладчиками для установки точек останова.

Так что, вероятно, делать что-то вроде: XCHG BX, BX также приведет к тому же.

Звучит так, как будто было несколько операций, которые все еще выполнялись, и, следовательно, это вызвало ошибку.

Если вы знакомы с VB, я могу привести пример:

Если вы создаете систему входа в систему в VB и загружаете 3 страницы вместе - Facebook, YouTube и Twitter в 3 разных вкладках.

И использовать 1 кнопку входа для всех. Это может привести к ошибке, если ваше интернет-соединение работает медленно. Это означает, что одна из страниц еще не загружена. Поэтому мы добавили Application.DoEvents, чтобы преодолеть это. Таким же образом в сборке можно использовать NOP.

Total Anime Immersion
источник