Призначення інструкції NOP та вирівнювання оператора у складі x86


15

Минув рік або близько того, як я востаннє брав клас по складанню. У цьому класі ми використовували MASM з бібліотеками Irvine, щоб полегшити програмування.

Після того, як ми пройшли більшу частину інструкцій, він сказав, що інструкція NOP по суті нічого не робить і не переживає про її використання. У всякому разі, мова йшла про проміжний період, і у нього є приклад коду, який не працює належним чином, тому він сказав нам додати інструкцію NOP, і вона спрацювала нормально. Я запитав, що я після уроку, чому і що це насправді, і він сказав, що не знає.

Хтось знає?


NOP нічого не робить, але він споживає цикли. Я не думаю, що на ваше запитання можна відповісти, без коду ми можемо лише здогадуватися. Ну, я гадаю, буде слайд NOP ...
yannis

11
NOP насправді щось робить. Він збільшує покажчик інструкції.
EricSchaefer

Відповіді:


37

Часто NOPдля вирівнювання адрес інструкцій використовується час. Зазвичай це трапляється, наприклад, при написанні коду Shell для використання переповнення буфера або вразливості рядка формату .

Скажімо, у вас є відносний стрибок на 100 байт вперед і внесіть деякі зміни в код. Цілком ймовірно, що ваші модифікації зіпсують адресу цілі стрибка, і, таким чином, вам доведеться також змінити вищезгаданий відносний стрибок. Тут ви можете додати NOPs, щоб просунути цільову адресу вперед. Якщо у вас є декілька NOPs між цільовою адресою та інструкцією про стрибок, ви можете видалити NOPs, щоб тягнути цільову адресу назад.

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

Іншим випадком використання для NOPнавчання буде щось, що називається санком NOP . По суті, ідея полягає у створенні достатньо великого масиву інструкцій, які не викликають побічних ефектів (наприклад,NOPабо збільшити, а потім зменшити регістр), але збільшити покажчик інструкцій. Це корисно, наприклад, коли потрібно перейти до певного коду, адреса якого не відома. Трюк полягає в тому, щоб розташувати вказану сани NOP перед кодом цілі, а потім стрибнути кудись до вказаних саней. Що стається, це те, що виконання триває, сподіваємось, з масиву, який не має побічних ефектів, і він переходить вперед інструкцію за інструкцією, поки не потрапить на потрібний фрагмент коду. Ця методика зазвичай використовується у вищезгаданих подвигах переповнення буфера і особливо для протидії заходам безпеки, таким як ASLR .

Ще одне особливе використання для NOPінструкції - це коли видозмінюєте код якоїсь програми. Наприклад, ви можете замінити частини умовних стрибків на NOPs і як таке обійти умову. Це часто застосовуваний метод, коли " розтріскується " захист від копіювання програмного забезпечення. Найпростіше, це просто зняти конструкцію коду складання if(genuineCopy) ...рядка коду та замінити інструкції на NOPs та .. Voilà! Жодних перевірок не робиться і неістинна копія працює!

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


2
Це була чудова відповідь, дякую, що ви взяли час, щоб пояснити це! Я нарешті розумію!
alvonellos

Окремі системи в режимі реального часу (ПЛК приходять на думку) дозволяють вам "зафіксувати" нову логіку в існуючій програмі під час її роботи. Ці системи залишають НОП перед кожною невеликою логікою, щоб ви могли перезаписати НОП переходом до нової логіки, яку ви вставляєте. В кінці нової логіки вона перейде до кінця початкової логіки, яку ви замінюєте. Нова логіка також буде мати NOP попереду, так що ви можете замінити нову логіку.
Скотт Вітлок

10

Nop може використовуватися в слоті затримки aa, коли жодна інша інструкція не може бути впорядкована для розміщення туди.

lw   v0,4(v1)
jr   v0

У MIPS це буде помилка, оскільки на той час, коли jr читав реєстр v0, реєстр v0 ще не завантажувався зі значенням з попередньої інструкції.

Спосіб виправити це:

lw   v0,4(v1)
nop
jr   v0
nop

Це заповнює слоти угоди після завантажувального слова та інструкції реєстру стрибків за допомогою nop, щоб інструкція з завантаження слова була завершена до виконання команди реєстрації стрибків.

Подальше читання - трохи про заповнення слотів затримки SPARC . З цього документа:

Що можна вставити в слот затримки?

  • Деякі корисні інструкції, які слід виконати, незалежно від того, чи ви це відділення
  • Деяка інструкція, яка корисна, працює лише тоді, коли ви здійснюєте відділення (або коли ви не здійснюєте відділення), але не приносить шкоди, якщо виконана в іншому випадку.
  • Коли все інше виходить з ладу, інструкція NOP

Що НЕ МОЖЕ бути вставлено у гніздо затримки?

  • Все, що встановлює CC, від якого залежить галузеве рішення. Інструкція відділення приймає рішення про те, чи слід відгалужуватись чи ні, але насправді вона не проводиться до закінчення інструкції про затримку. (Затримується лише філія, а не рішення.)
  • Ще одна галузева інструкція. (Що станеться, якщо ви це зробите, навіть не визначено! Результат непередбачуваний!)
  • Інструкція "набір". Це дійсно дві інструкції, не одна, і лише половина з них буде в слоті затримки. (Асамблер попередить вас про це.)

Зверніть увагу на третій варіант у тому, що потрібно вставити в слот для затримки. Помилка, яку ви бачили, швидше за все, хтось заповнив одну з речей, які не повинні бути поміщені у гніздо затримки. Якщо помістити ноп у цьому місці, тоді виправите помилку.

Примітка: після повторного читання питання це було для x86, у якого немає слотів затримки (розгалуження замість цього просто затримує конвеєр). Тож це не було б причиною / рішенням помилки. У системах RISC це могло бути відповіддю.


4
Зауважте, що питання позначено тегом x86, а x86 не має слотів затримки. Ніколи цього не зробить, оскільки це надзвичайна зміна.
MSalters

6

принаймні одна причина використання NOP - це вирівнювання. Процесори x86 читають дані з основної пам'яті у досить великих блоках, а початок блоку для читання завжди вирівнюється, тому, якщо в блоці є код коду, який буде багато читати, цей блок слід вирівняти. Це призведе до невеликого прискорення.


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

3

Одна мета для NOP (загалом, не тільки x86) - ввести затримки в часі. Наприклад, ви хочете запрограмувати мікроконтролер, який повинен виводити на деякі світлодіоди із затримкою на 1 с. Ця затримка може бути реалізована за допомогою NOP (та відділень). Звичайно, ви можете скористатися певним ADD або чимось іншим, але це зробить код більш нечитабельним; а може, вам потрібні всі регістри.


1
Зазвичай для тривалих часових кадрів, наприклад, 1 секунду, використовують таймери. NOPS використовуються для епох у порядку величини годин - нано- та мікросекунди.
mattnz

Це має сенс лише на мікроконтролері, а не на сучасному x86. Більшість кодів x86 не насичує ширину конвеєра сучасних надсказальних процесорів поза замовленням, тому додавання NOP між кожною інструкцією в більшості кодів матиме лише невеликий вплив (я б припустив, що число для "середнього" коду може бути 5 до 20% для подвоєння кількості інструкцій, з деяким кодом , що не надає уповільнення , але кілька тугих петель , що показують майже 2x уповільненням.) у всякому разі, хрусткий код старого x86 традиційно використовується в loopінструкції для петель затримки , а НЕ NOPS.
Пітер Кордес

3

Як правило, для 80x86 інструкції NOP не потрібні для коректності програми, хоча іноді на деяких машинах стратегічно розміщені NOP можуть спричиняти швидший запуск коду. Наприклад, у 8086 році код отримав би двобайтові фрагменти, і процесор мав внутрішній буфер "попереднього вибору", який міг би вмістити три такі шматки. Деякі вказівки виконуватимуться швидше, ніж вони могли бути отримані, тоді як інші інструкції потребуватимуть деякого часу. Під час повільних інструкцій процесор намагатиметься заповнити буфер попереднього вибору, так що якщо наступні декілька інструкцій були швидкими, вони могли бути виконані швидко. Якщо інструкція, що слідує за повільною інструкцією, починається на межі рівного слова, наступні шість байт, що мають значення, будуть попередньо встановлені; якщо він починається на непарній межі байтів, попередньо буде встановлено лише п'ять байтів.

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


3

Існує конкретний випадок x86, який досі не описаний в інших відповідях: переривання роботи. Для деяких його стилів можуть бути розділи коду, коли переривання вимкнено, оскільки основний код працює з деякими даними, спільними з обробниками переривань, але розумно допускати переривання між такими розділами. Якщо хтось наївно пише


    STI
    CLI

це не буде обробляти очікувані переривання, оскільки, посилаючись на Intel:

Після встановлення прапора IF процесор починає реагувати на зовнішні, маскуючі переривання після виконання наступної інструкції.

тому це буде переписано щонайменше як:


    STI
    NOP
    CLI

У другому варіанті всі очікувані переривання будуть оброблятися просто між NOP та CLI. (Звичайно, може бути багато альтернативних варіантів, як подвоєння інструкції щодо ІПСШ. Але явний NOP є більш очевидним, принаймні для мене.)


-2

NOP означає відсутність операції

Зазвичай використовується для вставки або видалення машинного коду або для затримки виконання певного коду.

Також використовується сухарями та налагоджувачами для встановлення точок прориву.

Тому, ймовірно, робити щось на кшталт: XCHG BX, BX також призведе до того ж.

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

Якщо ви знайомі з VB, я можу навести вам приклад:

Якщо ви створили систему входу в vb і завантажуєте 3 сторінки разом - facebook, youtube та twitter на 3 різних вкладках.

І використовувати 1 кнопку для входу для всіх. Це може призвести до помилки, якщо ваше інтернет-з'єднання повільне. Це означає, що одна зі сторінок ще не завантажена. Тому ми вкладаємо в Application.DoEvents для подолання цього. Таким же чином можна використовувати і в складі NOP.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.