Блог GunSmoker-а Відповідь на завдання - 11

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

Відповідь на завдання №11

Відповідь завдання №11. З якоїсь причини це завдання отримала безліч відгуків та суперечок, хоча вона відносно проста.

Мабуть, я почну підводити до розбору залишених натяків:

  1. Для початку зауважу, що завдання полягало в поясненні, чому показання відладчика не відповідають реальному стану справ. Мені здалося, що не все це вловили.
  2. Декілька перших підказок ("не змінював значення ZF", немає антивірусу/антиналагоджувального коду тощо) говорять нам про те, що ситуація не сфабрикована спеціально (я не малював знімок екрану у фотошопі! :) ), а природно вийшла в програмі .
  3. Друга велика підказка ("це проявляється в порожньому VCL додатку") рішуче натякає на те, що код у питанні тут ні до чого. Якщо код не до того ж, зовнішнього коду немає (інших потоків, антивіруса тощо), то це явно залишає нам тільки саму сесію налагодження.
  4. Пізніше доданий натяк "Ця поведінка, яку ви використовуєте постійно під час налагодження", ще більше вказує на щось, пов'язане з відладчиком. Причому абсолютно типове.
Мені здається, що тільки цієї інформації вже може бути достатньо для відповіді, але я спеціально додав кілька додаткових натяків, щоб зробити ситуацію ще більш очевидною:
  1. Знімок екрана вказує, що байт у питанні знаходиться у секції коду ($004C3B8F належить .exe-файлу).
  2. А код у питанні вказує на те, що здійснюється перевірка коду на відповідність шаблону машинної інструкції CALL.
Зрештою я сказав, що якщо я дам хоч ще одну підказку, скажу хоч ще одне слово, то цим яозвучу відповідь.

Що це за слово?

І це слово.breakpoint

На цей момент відповідь вже очевидна. Що може змінювати код без явного втручання програми, її потоків чи зовнішнього коду? Звісно ж, відладчик. Коли відладчик може змінювати секцію коду, та ще й з урахуванням підказки, що ви постійно цим користуєтеся? Коли ви ставите точку зупинки на код.

Як типовий налагоджувач користувача режиму працює з точками зупинки? Ну, він змінює код у потрібній точці, запам'ятовуючи те, що там було раніше, а натомість записуючи байт $CC, що на мові x86 означає int 3 - програмна точка зупинки.

Звичайно ж, коли відладчик так втручається в програму, він компенсує це, показуючи вам значення, які були б у разі реального виконання програми без налагодження. Тобто. хоча в пам'яті насправді записана програмна точка зупинки ($CC), відладчик це враховує і показує вам вихідні значення - ось чому всі evaluate покажуть вам те, що відбулося б при виконанні програми без налагоджувача, але не поточний стан справ.

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

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

Залишилося тільки пояснити ось це: "Про це не знають багато новачків, а ті, хто знає - часто забувають (навіть досвідчені програмісти). Здається, складність вирішення цього завдання якраз і походить відцього факту". Що ж це таке, про що часто забувають?

Установка точки зупинки була необхідна тому, що перевірочний код викликається зільйони раз, а мене цікавив тільки виклик по конкретній гілці - тому було встановлено дві точки: перша - в місці, що цікавить мене, але неактивна, а друга - в точці інструкції виклику, як ідентифікуюча гілку коду. Крапка була non-stop, а просто активувала другу (неактивну) точку. Таким чином, вони мали працювати парою, зупиняючи сесію налагодження саме в потрібний момент, на потрібній гілці.

Відповідно, не поставивши точку зупинки, я не отримував невірної поведінки, але я й не міг зупинитися у потрібному місці. А зупинка в потрібному місці означала встановлення точки зупинки. Таким чином, з боку здавалося, що це взагалі баг відладника: якщо виконувати програму без нього або під ним, але простим прогоном без зупинки – все OK, але якщо виконувати по кроках конкретну ділянку (що вимагало встановлення точки зупинки) – то програма починає вести себе неправильно.

Проблему з $CC я обійшов просто, вставивши NOP перед CALL і встановивши бряк на NOP. Мені було важливо ідентифікувати лише гілку коду точкою зупинки - немає значення, на якій інструкції гілки коду вона буде встановлена. Як я вже помітив вище, тут немає жодної проблеми при реальному виконанні коду (поза відладчиком) - ми просто отримуємо дивну поведінку під час сесії налагодження. Зрозуміло, після вирішення вихідної проблеми (що не має відношення до питання завдання), NOP був вилучений.