Содержание статьи
- Идентификация условных операторов
- Типы условий
- Наглядное представление сложных условий в виде дерева
- Исследование конкретных реализаций
- Сравнение вещественных чисел
- Условные команды булевой установки
- Идентификация типов с помощью условий
- Прочие условные команды
- Команды условного перехода
- Команды условной пересылки
- Булевы сравнения
- Итоги
if — then — else
и разберемся, как компилятор интерпретирует подобные конструкции.Фундаментальные основы хакерства
Пятнадцать лет назад эпический труд Криса Касперски «Фундаментальные основы хакерства» был настольной книгой каждого начинающего исследователя в области компьютерной безопасности. Однако время идет, и знания, опубликованные Крисом, теряют актуальность. Редакторы «Хакера» попытались обновить этот объемный труд и перенести его из времен Windows 2000 и Visual Studio 6.0 во времена Windows 10 и Visual Studio 2019.
Ссылки на другие статьи из этого цикла ищи на странице автора.
Раньше мы уже встречались с условным ходом выполнения программ, однако ограничивались короткими описаниями команд и поверхностным разбором выполняемых ими операций. Управляемый ход выполнения, без сомнения, самая важная веха развития программирования своего времени и затмевает многие последующие, даже такие, как структурное программирование или ООП. Только с высоты высокоуровневого языка кажется, что в условии if
нет ничего любопытного и оно лишено какого‑либо разнообразия. Но для компилятора это простор для самодеятельности! И в близких по духу ситуациях он может построить кардинально различающийся код.
В процессорной архитектуре x86 предусмотрен весьма «оригинальный» набор команд для перенаправления хода выполнения программного кода. «Оригинальный» не значит хороший или плохой, это значит, что он отличается от набора микропроцессоров других архитектур и производителей: ARM, MIPS, SPARC и так далее. Как ты знаешь, первый процессор этой серии создавался впопыхах для временной замены еще не готового iAPX-432, последний, в свою очередь, должен был уметь в многозадачность и управление памятью на аппаратном уровне. Чего в итоге не случилось. А x86 продолжил свое развитие. Поэтому сейчас, когда за годы эволюции процессоров серии x86 скопился набор инструкций, хакерам приходится разгребать весь этот хлам, дабы раскрутить порядок выполнения машинных инструкций.
Идентификация условных операторов
Существует два вида алгоритмов — безусловные и условные. Порядок действий безусловного алгоритма всегда постоянен и не зависит от входных данных. Например, a = b + c. Порядок действий условных алгоритмов, напротив, зависит от данных, поступающих «на вход». Например:
если c не равно нулю,
то: a = b/c;
иначе: вывести сообщение об ошибке
Обрати внимание на выделенные жирным шрифтом ключевые слова «если», «то» и «иначе», называемые операторами условия или условными операторами. Без них не обходится ни одна программа (вырожденные примеры наподобие «Hello, World!» не в счет). Условные операторы — сердце любого языка программирования. Поэтому чрезвычайно важно уметь их правильно определять.
В общем виде (не углубляясь в синтаксические подробности отдельных языков) оператор условия схематично изображается так:
IF (условие) THEN { оператор1; оператор2; } ELSE { операторa; операторb; }
Задача компилятора — преобразовать эту конструкцию в последовательность машинных команд, выполняющих оператор1, оператор2, если условие истинно, и операторa, операторb — если оно ложно. Однако микропроцессоры серии 80x86 поддерживают весьма скромный набор условных команд, ограниченный фактически одними условными переходами. Программистам, знакомым лишь с IBM PC, такое ограничение не покажется чем‑то неестественным, между тем существует масса процессоров, поддерживающих префикс условного выполнения инструкции. То есть вместо того, чтобы писать:
TEST ECX,ECX
JNZ xxx
MOV EAX,0x666
там поступают так:
TEST ECX,ECX
IFZ MOV EAX,0x666
IFZ
и есть префикс условного выполнения, разрешающий выполнение следующей команды только в том случае, если установлен флаг нуля. В этом смысле микропроцессоры 80x86 можно сравнить с ранними диалектами языка Basic, не разрешающими использовать в условных выражениях никакой другой оператор, кроме GOTO. Сравни:
Старый диалект Basic:
10 IF A=B THEN GOTO 30
20 GOTO 40
30 PRINT "A=B"40 ... // Прочий код программы
Новый диалект Basic:
IF A=B THEN PRINT "A=B"
Если ты когда‑нибудь программировал на старых диалектах Basic, то, вероятно, помнишь, что гораздо выгоднее выполнять GOTO
, если условие ложно, а в противном случае продолжать нормальное выполнение программы. Как видишь, вопреки расхожему мнению, навыки программирования на Basic отнюдь не бесполезны, особенно в дизассемблировании программ.
Большинство компиляторов (даже не оптимизирующих) инвертируют истинность условия, транслируя конструкцию
IF (условие) THEN { оператор1; оператор2; }
в следующий псевдокод:
IF (NOT условие) THEN continue
оператор1;
оператор2;
continue:
…
Следовательно, для восстановления исходного текста программы нам придется вновь инвертировать условие и «подцепить» блок операторов { оператор1; оператор2; } к ключевому слову THEN
. Иначе говоря, если откомпилированный код выглядит так:
10 IF A<>B THEN 30
20 PRINT "A=B"30 ...// Прочий код программы
можно с уверенностью утверждать, что в исходном тексте присутствовали строки IF
. А если программист, наоборот, проверял переменные A и B на неравенство, то есть в коде присутствовала конструкция IF
? Все равно компилятор инвертирует истинность условия и сгенерирует следующий код:
10 IF A=B THEN 30
20 PRINT "A<>B"30 ... // Прочий код программы
Конечно, встречаются и компиляторы, страдающие многословием. Их легко распознать по безусловному переходу, следующему сразу же после условного оператора:
IF (условие) THEN do
GOTO continue
do:
оператор1;
оператор2;
continue:
…
В таком случае инвертировать условие не нужно. Впрочем, если это сделать, ничего страшного не произойдет, разве что код программы станет менее понятным, да и то не всегда.
Рассмотрим теперь, как транслируется полная конструкция
IF (условие) THEN { оператор1; оператор2; } ELSE { операторa; операторb; }.
Одни компиляторы поступают так:
IF (условие) THEN do_it
// Ветка ELSE
операторa;
операторb;
GOTO continue
do_it:
//Ветка IF
оператор1;
оператор2;
continue:
А другие — так:
IF (NOT условие) THEN else
// Ветка IF
оператор1;
оператор2;
GOTO continue
else:
// Ветка ELSE
операторa;
операторb
continue:
Разница между ними в том, что вторые инвертируют истинность условия, а первые — нет. Поэтому, не зная «нрава» компилятора, определить, как выглядел подлинный исходный текст программы, невозможно! Однако это не создает проблем, ибо условие всегда можно записать так, как это удобно. Допустим, не нравится тебе вот такая конструкция:
IF (c <> 0) THEN a = b / c ELSE PRINT "Ошибка!"
Пиши ее так:
IF (c == 0) THEN PRINT "Ошибка!" ELSE a = b / c
И никаких гвоздей!
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»