команды ассемблера в двоичном коде

FE23 LOOCH DISASM


СПРАВОЧНИК ПО КОМАНДАМ
ПРОЦЕССОРОВ x86


команды ассемблера в двоичном коде. . команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-. картинка команды ассемблера в двоичном коде. картинка . В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.
ГлавнаяЗагрузкаИнструкцияКоманды x86Карта сайта

команды ассемблера в двоичном коде. . команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-. картинка команды ассемблера в двоичном коде. картинка . В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.
команды ассемблера в двоичном коде. . команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-. картинка команды ассемблера в двоичном коде. картинка . В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.команды ассемблера в двоичном коде. . команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-. картинка команды ассемблера в двоичном коде. картинка . В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

КОМАНДЫ АССЕМБЛЕРА
И МАШИННЫЕ КОМАНДЫ


Команды ассемблера и машинные команды

В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

Основой машинной команды является код операции, который является просто некоторым числом. Для процессоров x86 (впрочем, и для других процессоров тоже) принято использовать шестнадцатиричные числа. (Попутно заметим, что для советских ЭЦВМ были приняты восьмеричные числа, с ними было меньше путаницы, поскольку такие числа состоят только из цифр и не содержат букв).

В таблицах настоящего справочника в колонке Код показан код операции машинной команды, а в колонке Формат показан формат машинной команды.

Можно считать, что количество разных машинных команд у данного процессора равно количеству возможных кодов операции. По формату можно узнать, из каких составных частей состоит данная машинная команда. Разные машинные команды могут иметь разный формат. Код операции машинной команды полностью определяет ее формат.

Часто одна ассемблерная команда имеет несколько разных вариантов машинных команд. Причем форматы этих машинных команд могут быть разными для разных вариантов.

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

Здесь важно обратить внимание, что все эти десять машинных команд выполняют одну и ту же элементарную операцию, которая на языке ассемблера называется ADD.

И, значит, получается, что вроде бы можно рассуждать так: процессор может выполнять столько разных элементарных операций, сколько есть разных команд ассемблера. Однако, этот простой принцип все равно нуждается в оговорках и примечаниях. Так как у части ассемблерных команд бывают еще и синонимы.

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

Способ (1). Взять за основу команды языка ассемблера и расположить команды по алфавиту. Тогда могут получиться вот такие таблицы. Все команды по алфавиту (кратко) Все команды по алфавиту (подробно)

Способ (2). Взять за основу код операции машинной команды и расположить команды в порядке кодов операций. При этом будет лучше, если общий список разделить на две части, сделать отдельные списки для команд с однобайтным кодом операции и для команд с двухбайтным кодом операции. Первый байт кода операций Второй байт кода операций

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

Основной байт кода операции

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

Если в машинной команде код операции состоит из одного байта, то этот единственный байт и является основным байтом кода операции. И содержимое этого байта определяет, что это за операция.

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

В таблицах справочника, в которых показано кодирование машинных команд, основной байт кода операции обычно бывает показан дважды, сначала в колонке «Код» в виде шестнадцатиричного числа, а затем в колонке «Формат» в виде условных восьми черточек, на которых отмечены особые биты, если таковые есть в основном байте кода операции.

Источник

MS-DOS и TASM 2.0. Часть 10. Команды ассемблера.

команды ассемблера в двоичном коде. %D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA %D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9 DOS 1. команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA %D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9 DOS 1. картинка команды ассемблера в двоичном коде. картинка %D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA %D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9 DOS 1. В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

Команды ассемблера и команды процессора.

Стоит пояснить, что если к вопросу подойти формально строго, то команды процессора и команды ассемблера — это не одно и то же. Ассеммблер — хоть и низкоуровневый язык программирования, но иногда он без спроса программиста «корректирует код под себя». Причём у каждого ассемблера (masm, tasm, fasm) это может быть по-разному. Самый яркий пример — команда ret. В ассемблерном коде мы запишем ret, а реальный ассемблер ассемблирует её как retf или retn 8. Может также изменяться код, добавлением в качестве выравнивания кода команды процессора nop (об этом ниже в статье) и т.п. Чтобы не усложнять суть вопроса, под понятиями команды процессора и команды ассемблера мы будем подразумевать одно и то же.

Команды процессора (команды ассемблера) в большинстве своём работают с аргументами, которые в ассемблере называются операндами. Система машинного кода процессоров Intel содержит более 300 команд (команды процессора, сопроцессора, MMX-расширения, XMM-расширения). С каждым новым процессором их количество растёт. Для того, чтобы профессионально программировать, не надо зубрить и разбирать все команды процессора. При необходимости можно воспользоваться справочником. В процессе чтения статей, вы поймёте, что основная суть знания ассемблера состоит не в доскональном знании всех команд, а в понимании работы системы.

Не следует забывать, что команды процессор видит в виде цифр, которые можно рассматривать как данные. Например, команда NOP занимает один байт и её машинный код — 90h.

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

Основные (т.н. целочисленные) команды ассемблера позволяют написать практически любую программу для операционных систем MS-DOS и Windows. Количество команд ассемблера, которыми вы будете пользоваться будет расти со временем прохождения курса. Для более детального понимания, в последствии можете обратиться к справочнику команд.

Рассмотрим команды ассемблера на практическом примере.

С использованием среды разработки TASMED или любого текстового редактора набираем код. Программа, задаст вопрос на английском языке о половой принадлежности (имеется ввиду ваш биологический пол при рождении). Если вы нажмете m (Man), будет выведено приветствие с мужчиной, если w (Woman), то с женщиной, после этого программа прекратит работу. Если будет нажата любая другая клавиша, то программа предположит, что имеет дело с гоблином, не поверит и будет задавать вам вопросы о половой принадлежности, пока вы не ответите верно.

Источник

Что такое ассемблер и нужно ли его изучать

Этому языку уже за 70, но на пенсию он пока не собирается.

Есть традиция начинать изучение программирования с вывода на экран строки «Hello world!». На языке Python, например, это всего одна команда:

Всё просто, понятно и красиво! Но есть язык программирования, в котором, чтобы получить тот же результат, нужно написать солидный кусок кода:

Это ассемблер. Только не нужно думать, что он плох. Просто Python — это язык высокого уровня, а ассемблер — низкого. Одна команда Python при выполнении вызывает сразу несколько операций процессора, а каждая команда ассемблера — всего одну операцию.

Сложно? Давайте разбираться.

команды ассемблера в двоичном коде. 11141029072021 11a3428e7abc9bac29137421556331dcb1a157b9. команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-11141029072021 11a3428e7abc9bac29137421556331dcb1a157b9. картинка команды ассемблера в двоичном коде. картинка 11141029072021 11a3428e7abc9bac29137421556331dcb1a157b9. В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.

Немного о процессорах и машинном языке

Чтобы объяснить, что такое язык ассемблера, начнём с того, как вообще работает процессор и на каком языке с ним можно «разговаривать».

Процессор — это электронное устройство (сейчас крошечная микросхема, а раньше процессоры занимали целые залы), не понимающее слов и цифр. Он реагирует только на два уровня напряжения: высокий — единица, низкий — ноль. Поэтому каждая процессорная команда — это последовательность нулей и единиц: 1 — есть импульс, 0 — нет.

Для работы с процессором используется машинный язык. Он состоит из инструкций, записанных в двоичном коде. Каждая инструкция определяет одну простую машинную операцию: арифметическую над числами, логическую (поразрядную), ввода-вывода и так далее.

Например, для Intel 8088 инструкция 0000001111000011B — это операция сложения двух чисел, а 0010101111000011B — вычитания.

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

Поэтому много лет назад был создан язык ассемблера, в котором коды операций обозначались буквами и сокращениями английских слов, отражающих суть команды. Например, команда mov ax, 6 означает: «переместить число 6 в ячейку памяти AX».

Когда и как был создан ассемблер?

Это произошло ещё в сороковых годах прошлого века. Ассемблер был создан для первых ЭВМ на электронных лампах, программы для которых писали на машинном языке. А так как памяти у компьютеров было мало, то команды вводили, переключая тумблеры и нажимая кнопки. Даже несложные вычисления занимали много времени.

Проблему решили, когда ЭВМ научились хранить программы в памяти. Уже в 1950 году была разработана первая программа-транслятор, которая переводила в машинный код программы, написанные на понятном человеку языке. Эту программу назвали программой-сборщиком, а язык — языком ассемблера (от англ. assembler — сборщик).

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

Как устроен язык ассемблера?

Ассемблер можно считать языком второго поколения, если за первый принять машинный язык. Он работает непосредственно с процессором, и каждая его команда — это инструкция процессора, а не операционной или файловой системы. Перевод языка ассемблера в машинный код называется ассемблированием.

Коды операций в языке ассемблера мнемонические, то есть удобные для запоминания:

Регистрам и ячейкам памяти присваиваются символические имена, например:

EAX, EBX, AX, AH — имена для регистров;

meml — имя для ячейки памяти.

Например, так выглядит команда сложения чисел из регистров AX и BX:

А это команда вычитания чисел из регистров AX и BX:

Кроме инструкций, в языке ассемблера есть директивы — команды управления компилятором, то есть программой-ассемблером.

Вот некоторые из них:

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

Вот, например, код, на ассемблере, выводящий на экран цифры от 1 до 10:

Здесь действие будет выполняться в цикле — как, например, в циклах for или do while в языках высокого уровня.

Единого стандарта для языков ассемблера нет. В работе с процессорами Intel разработчики придерживаются двух синтаксисов: Intel и AT&T. Ни у того ни у другого нет особых преимуществ: AT&T — стандартный синтаксис в Linux, а Intel используется в мире Microsoft.

Одна и та же команда в них выглядит по-разному.

Например, в синтаксисе Intel:

mov eax, ebx — команда перемещает данные из регистра eax в регистр ebx.

В синтаксисе AT&T эта команда выглядит так:

Почему для разных семейств процессоров нужен свой ассемблер?

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

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

Кому и зачем нужен язык ассемблера?

Даже из нашего примера «Hello, World!» видно, что ассемблер не так удобен в разработке, как языки высокого уровня. Больших программ на этом языке сейчас никто не пишет, но есть области, где он незаменим:

Если вы хотите разрабатывать новые микропроцессоры или стать реверс-инженером, то есть смысл серьёзно заняться изучением языка ассемблера.

Востребованы ли программисты на ассемблере сегодня?

Конечно. Хотя на сайтах по поиску работу вы вряд ли найдёте заявки от работодателей с заголовками: «Нужен программист на ассемблере», зато там много таких, где требуется знание ассемблера дополнительно к языкам высокого уровня: C, C++ или Python. Это вакансии реверс-инженеров, специалистов по компьютерной безопасности, разработчиков драйверов и программ для микроконтроллеров/микропроцессоров, системных программистов и другие.

Предлагаемая зарплата — обычная в сфере IT: 80–300 тысяч рублей в зависимости от квалификации и опыта. Вот, например, вакансия реверс-инженера на HeadHunter, где требуется знание ассемблера:

команды ассемблера в двоичном коде. 11223329072021 accf102caaa970ce65d217b9ae9a8e9a57caa67c. команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-11223329072021 accf102caaa970ce65d217b9ae9a8e9a57caa67c. картинка команды ассемблера в двоичном коде. картинка 11223329072021 accf102caaa970ce65d217b9ae9a8e9a57caa67c. В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

команды ассемблера в двоичном коде. 11223329072021 08fda0244b5397e030ee401fd2bea5b24f78a72b. команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-11223329072021 08fda0244b5397e030ee401fd2bea5b24f78a72b. картинка команды ассемблера в двоичном коде. картинка 11223329072021 08fda0244b5397e030ee401fd2bea5b24f78a72b. В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

команды ассемблера в двоичном коде. 11223329072021 6896a8696b8038f4fc8989ab005e4fccc3b90047. команды ассемблера в двоичном коде фото. команды ассемблера в двоичном коде-11223329072021 6896a8696b8038f4fc8989ab005e4fccc3b90047. картинка команды ассемблера в двоичном коде. картинка 11223329072021 6896a8696b8038f4fc8989ab005e4fccc3b90047. В таблицах настоящего справочника в колонке Команда показано имя команды на языке ассемблера.

Стоит ли начинать изучение программирования с языка ассемблера?

Нет, так делать не нужно. Для этого есть несколько причин:

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

обложка: Полина Суворова для Skillbox Media

Источник

Введение в машинный код. Языком ассемблера

Этот документ предназначен для начинающих кодеров и только для них. Профессиональным программистам надлежит относиться к нему снисходительно 😉 Выбор «среды обучения» был совершен в трезвом уме и в здравой памяти, желающие поиронизировать по этому поводу — милости прошу serrgio@gorki.unibel.by.

Содержание

Вы читали «Хроники Амбера» Роджера Желязны? Там есть такой эпизод:
Главный герой находится в заточении. В абсолютной тьме. У него были выколоты глаза, но за год они регенерировали, и зрение постепенно к нему возвращается…
И однажды каким-то чудом в одной камере с ним оказывается загадочный Дворкин — создатель Лабиринта. Именно «чудом» — он просто появился неизвестно откуда. Он тоже находится «в заключении», но, в отличие от Корвина (главного героя), может спокойно ходить через каменные стены.
Удивленный Корвин спрашивает его:
— Как ты оказался в моей камере? Ведь здесь нет дверей.
Дворкин отвечает:
— Двери есть везде. Просто нужно знать, как в них войти.
Будем считать это эпиграфом…

1.1. Система счисления

#1. Наверняка среди ваших знакомых есть «крутые» программисты, или люди, таковыми себя считающие ;). Попробуйте как-нибудь проверить их «на вшивость». Предложите им в уме перевести число 12 из шестнадцатеричной в двоичную систему счисления. Если над подобным вопросом «крутой программист» будет думать дольше 10 секунд — значит он вовсе не так крут, как говорит…

#2. Система счисления (сие не подвластное человеческой логике определение взято из математической энциклопедии) — это совокупность приемов представления обозначения натуральных чисел. Этих «совокупностей приемов представления» существует очень много, но самая совершенная из всех — та, которая подчиняется позиционному принципу. А согласно этому принципу один и тот же цифровой знак имеет различные значения в зависимости от того места, где он расположен. Такая система счисления основывается на том, что некоторое число n единиц (radix) объединяются в единицу второго разряда, n единиц второго разряда объединяются в единицу третьего разряда и т. д.

#3. «Разрядам» нас учили еще в начальных классах школы. Например, у числа 35672 цифра «2» имеет первый разряд, «7» — второй, «6» — третий, «5» — четвертый и «3» — пятый. А «различные значения» цифрового знака «в зависимости от того места, где он расположен» и «объединение в единицу старшего разряда» на тех же уроках арифметики «объяснялось» следующим образом:

#4. Очень наглядно это отображают обыкновенные счеты. Набранное на них число 35672 будет выглядеть… см. рисунок слева в общем…

Чтобы набрать число 35672 мы должны передвинуть влево две «костяшки» на первом «прутике», 7 на втором, 6 на третьем, 5 на четвертом и 3 на пятом. (У нас ведь 1 «костяшка» на втором — это то же самое, что и 10 «костяшек» на первом, а одна на третьем равна десяти на втором, и так далее…) Пронумеруем наши «прутики» снизу вверх — да так, чтобы номером первого был «0»… И снова посмотрим на наши выражения:

Это (если сверху вниз считать) сколько на каждом «прутике» «костяшек» влево отодвинуто.

Это номер прутика (самый нижний — 0), на котором отодвинуто определенное число костяшек.

Это на каждом прутике — по 10 костяшек нанизано, не все влево отодвинуты, но всего-то их — 10!
Кстати, красненькое 10 в последнем выражении соответствует основанию (radix) системы счисления (number system).

#5. Пальцев на руках у человека 10, поэтому и считать мы привыкли в системе счисления с основанием 10, то есть в десятичной. Если вы хорошо представляете себе счеты и немного поупражнялись в разложении чисел аналогично выражению 1, то перейти на систему счисления с основанием, отличным от привычной, особого труда для вас не составит. Нужно всего лишь представить себе счеты, на каждый прут которых нанизано не привычные 10 костяшек, а… скажем, 9 или 8, или 16, или 32, или 2 и… попробовать мысленно считать на них.

#6. Для обозначения десятичных чисел мы используем цифры от 0 до 9, для обозначения чисел в системах счисления с основанием менее 10 мы используем те же цифры:

Если же основание системы счисления больше десяти, то есть больше, чем десять привычных нам чисел, то начинают использоваться буквы английского алфавита. Например, для обозначения чисел в системе счисления с основанием 11 «как цифра» будет использоваться буква А:

В системе счисления с основанием 16 — буквы от A до F:

Правда, при определенном основании (при каком?) буквы аглицкого алфавита закончатся…
Но нам это, пока что, глубоко фиолетово, так как работать мы будем только с тремя radix-ами: 10 (ну естественно), 16 и 2. Правда, если кто на ДВК поизучать это дело собирается, тому еще и radix 8 понадобится.

#7. Числа в любой системе счисления строятся аналогично десятичной. Только на «счетах» не с 10, а с другим количеством костяшек.
Например, когда мы пишем десятичное число 123, то имеем в виду следующее:

Если же мы используем символы 123 для представления, например, шестнадцатеричного числа, то подразумеваем следующее:

Короче — полный беспредел. Говорим одно, а подразумеваем другое. И последнее не для красного словца сказано. А потому, что так оно и есть…

Истина где-то рядом…

#8. Трудность у вас может возникнуть при использовании символов A, B, C и т. д. Чтобы решить эту проблему раз и навсегда, необходимо назубок вызубрить ма-а-аленькую табличку «соответствия» между употребляемыми в «компьютерном деле» систем счисления:

radix 100123456789101112131415
radix 160123456789ABCDEF
radix 201101110010111011110001001101010111100110111101111

Следуя этой таблице, число 5BC в шестнадцатеричном формате «строится» так:

А теперь, если пораскинуть мозгами, с легкостью переведем 5BC из шестнадцатеричной в десятичную систему счисления:

Вот и объединили цифры с буквами. Пространство со временем поучимся объединять немного позже — если не испугаетесь сложностей низкоуровневого программирования.
В общем-то решать вам. В Delphi тоже много чего объединять можно.

#10. Еще немного про перевод между «радиксами». (Вообще-то это плевое дело, конечно, если представляешь себе, что такое «совокупность приемов представления обозначения натуральных чисел»).
Например, преобразование числа 42936 из десятичного в шестнадцатеричный формат проводится следующим образом (в скобках — остаток):

А вот и обратный процесс — перевод из HEX в DEC числа A7B8h:

Преобразования чисел в системы счисления с другим основанием проводятся аналогично… Счеты! Обыкновенные счеты, только с «плавающим» числом «костяшек» на каждом «прутике»…

#11. Если честно, то конкретный «рисунок» цифр — единица там палкой обозначается, двойка — лебедем — это все лишь историческая случайность. Мы запросто можем считать в «троичной» системе счисления с цифрами %, *, _ (где запятая — это знак препинания, а вовсе не число):

Или использовать родные цифры в десятичной системе счисления, но по другому «вектору упорядоченных цифр» — 1324890576:

Правда, этим немножко затрудняется понимание происходящего? А ведь тоже десятичная система! И рисунок цифр как бы знакомый :-)))
Или вообще считать в 256-ричной системе счисления, используя в качестве «рисунка цифр» таблицу ASCII-символов! (По сравнению с вами, извращенцами, любой Биллгейтс будет девственником казаться!!).

#12. Теперь самая интересная часть Марлезонского балета.
Компьютер, как известно, считает только в двоичной системе счисления. Человеку привычна десятичная. Так нахрена еще и шестнадцатеричную какую-то знать нужно?
Все очень просто. В умных книжках пишут, что «шестнадцатеричная нотация является удобной формой представления двоичных чисел». Что это значит?
Переведите число A23F из шестнадцатеричной «нотации» в двоичную. (Один из возможных алгоритм приведен в п.10.). В результате длительных манипуляций у вас должно получиться 1010001000111111.
А теперь еще раз посмотрите на таблицу в п. 8. (которую вы как бы уже и выучили) и попробуйте то же самое сделать в уме :

Каждой шестнадцатеричной цифре соответствует тетрада (4 штуки) ноликов и единичек. Все, что потом нужно сделать — «состыковать» эти тетрады. Круто? Вас еще не то ждет!

#13. Кстати (наверняка вы это уже знаете):

#14. И, напоследок, еще несколько слов про HEX и BIN :). Зайдите в Norton Commander, наведите указатель на какой-нить файл и нажмите там F3. А когда он откроется — на F4. Там какие-то нездоровые циферки попарно сгруппированы. Это и есть «нолики и единички» (которыми в компьютере все-все-все описывается), но в шестнадцатеричном формате…

Следует основательно разобраться с системой счисления. Минимум, что должен вынести из этой главы юзвер, вступивший на скользкий путь низкоуровневого программирования — это научиться переводить числа между DEC, HEX и BIN… хе-хе… В УМЕ!

1.2. Регистры

#1. Наверняка вы имеете представление о том, что такое переменная. Наиболее продвинутые даже знают, что переменная имеет тип. Кажется вполне естественным, что любой высокоуровневый язык программирования позволяет создавать любое количество переменных того или иного типа…

Так вот, господа — при программировании на ассемблере вас ждет большая неожиданность. Потому что для всех ваших навороченных вычислений разрешается использовать только несколько переменных с фиксированными «именами собственными» и имеющих фиксированную «длину». Эти «предопределенные переменные» называются регистрами, и каждая из них имеет свою специализацию.

О специализации нам пока что говорить рано, описание наподобие «регистр-указатель базы кадра стека» вам вряд ли о чем-то скажет. Поэтому для начала познакомимся только с так называемыми регистрами общего назначения (РОН), и то не со всеми, а только с четырьмя основными, которые являются своего рода «рабочими лошадками» микропроцессора.

Вот их «имена собственные» — AX, CX, DX, BX (именно в такой последовательности они «упорядочены» в Intel’овских микропроцессорах).

А сейчас мы поближе посмотрим на эти «рабочие лошадки» микропроцессора.

2. Когда появится приглашение в виде «минусика», введите букву «R» (можно и «r» — регистр символов значения не имеет) и нажмите на «Enter».

Не правда ли, весьма похоже на то, что показывают в художественных фильмах про хакеров?

Ну и что это такое? — скептически спросите вы.
А черт его знает! Будем разбираться!

#2. То, что у вас должно появиться — это список доступных регистров и текущее значение каждого из них. Как видите, значения регистров AX, BX, CX, DX равны 0. Не правда ли, создается впечатление, что они просто-напросто ждут того, чтобы в них внесли какое-либо значение?

Природа не терпит пустоты. Писателей приводит в ужас чистый лист бумаги…

Весьма скоро и вы при виде «пустых» регистров будете испытывать непреодолимое наркотическое желание чем-нибудь их заполнить…

Однако прежде чем мы сделаем это в первый раз, давайте уточним тип этих «переменных».

А он очень простой, этот тип — шестнадцатеричное число в диапазоне 0…FFFF. Или, если в BIN, то — от 0 до 1111 1111 1111 1111.

В общем, в умных книжках рисуют вот такую вот «нездоровую» схемку 3 :

А означает она следующее.

Физически существует один регистр — AX, а вот логически он делится на два — на старшую (AH) и младшую (AL) части (от английского — high и low).

Очевидно, что присвоить AX значение, например, 72F9h, мы можем следующими способами:

Точно так же присвоить значение 78h регистру AH можно двумя способами:

То же самое, но для регистра AL:

Тех, кого смущают числа с буквами, мы со зловредной ухмылкой отсылаем к 1.1. Система счисления :-]

AX2F4D
AHAL2F4D
Значение бита0010111101001101
Номер бита1514131211109876543210
ТетрадыСтаршая AHМладшая AHСтаршая ALМладшая AL

Внимательно смотрим на таблицу: одной шестнадцатеричной цифре соответствует тетрада двоичных цифр (4 шт., они же — 4 бита). «Емкость» регистров AH и AL — две тетрады, т. е. 8 бит. Точно такую «длину» имеют: коды символов, скан-коды клавиш, номера функций прерываний и куча всего прочего, чего вы пока еще не знаете.

Емкость AX (состоящего из двух половинок) — 4 тетрады, т. е. 16 бит; они же (эти 16 бит) иначе еще называются «словом»…

#4. «Принудительно» присвоить регистру значение можно при помощи той же команды «R», только с параметром «имя собственное регистра».

выбросит вам на монитор

Введите после двоеточия, например, число 123 и снова нажмите на Enter:

На дисплее опять появится приглашение «-«, на которое мы отвечаем командой «R» без параметров и таким образом вновь просматриваем значения наших регистров:

Смотрим внимательно — AX=0123, что и требовалось доказать…

Примечания

1). В W9X она находится в папке WINDOWS\COMMAND\. В Y2K и XP — WINDOWS\SYSTEM32\. В обоих случаях достаточно набрать в командной строке «debug», чтобы она запустилась.

2). Не забудьте, что при тогдашней технологической базе и это было большим прорывом. А экстенсивное расширение, например, разрядности, во-первых, нужно правильно предвидеть (вспомните, сколько в том же MS-DOS закладок на будущее, которые никуда не пошли за ненадобностью), а во-вторых, правильно оценить (в буквальном смысле). Неужели вы думаете, что, например, производители памяти не могут легким мановением руки увеличить ширину шины, соединяющую память с процессором? Могут, но во-первых — это резко повысит стоимость памяти, а во-вторых — не гарантирует повышения производительности.

3). Впоследствии мы немного усложним эту схемку — регистры современных процессоров 32-разрядные и называются немного иначе 😉

4). «Первый справа» бит мы будем называть «нулевым». Однако нам попадались руководства, в которых это же бит обозван как «первый». Можно долго обсуждать тонкости русского языка (которые, к сожалению, не всегда понимает переводчик), однако это выходит за рамки данной книги. Просто имейте это ввиду, что можете с этим столкнуться, и будьте бдительнее, читая документацию.

1.3. Память

#1. Первым видом памяти, с которым мы войдем (придется!) в тесный физический контакт, будет оперативная, она же — RAM (от английского — Random Access Memory). Оперативная память — это своего рода «рабочая площадка», по которой суетится этакий шустрый многорукий дядька-процессор — чего-то там собирает, от кучи к куче бегает, всех ругает… 🙂

Оперативная память — это ряд пронумерованых ячеек размером в байт. Мы можем получить доступ к первому байту памяти, ко второму, к третьему и т.д.

Короче — пришло время испробовать еще одну команду из скромного арсенала DEBUG’a! Запустите debug и введите команду D (от английского — DUMP).

«Картинка», которую вы увидели, называется «дамп памяти» (что в переводе с английского означает «свалка») и она насыщена не только важной информацией, но и специальной низкоуровневой энергетикой. Да чего уж там греха таить — каждый ассемблерщик знает, что рассматривание дампа памяти поднимает настроение, жизненный тонус и другие, не менее важные вещи 😉

Слева — это адрес памяти. В центре — 16 столбцов из спаренных цифр…

А здесь и повториться лишний раз не грех. Каждая пара шестнадцатеричных цифр — это байт. Смотрите внимательно на дамп! Байт по адресу 100 имеет значение 6A, байт по адресу 101 — 00, байт по адресу 102 — 68… Эти «сладкие парочки» — и есть неделимая «единица адресации» оперативной памяти.

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

И, наконец, столбец справа — это символы, соответствующие шестнадцатеричным кодам центрального столбца (например, коду 6A соответствует символ J). Большинству кодов не соответствует никакой из «печатных символов» — таким в колонке справа соответствуют точки.

#2. А теперь потренируем наши пальчики дампировать память — пройдемся по некоторым «историческим местам» нашей оперативной памяти… Для этого мы будем вводить команду D с параметром.

Например, команда (параметр L8 означает «вывести 8 байтов»):

покажет вам системную дату в правом столбце дампа.

Короче, искателям приключений выдаем «простыню» самых интересных адресов (большинство слов в описании вам пока должны быть непонятны, но вы не пугайтесь — понимание придет!).

Ну и хватит для первого раза. Кому мало — ищите дополнительную документацию :-p »

1.4. Программа

#1. Любая программа выполняется последовательно (мы ведь пока обсуждаем «простой» IBM PC, а не какой-нибудь крутой векторный параллельный суперкомпьютер). То есть пока не выполнилась текущая «строка» (инструкция) программы, следующая не выполнится. Совсем другой вопрос, какая «строка» будет выполнена после «текущей» (здесь мы имеем дело со всевозможными логическими «ветвлениями», «циклами» и т. д.), или же строчку из какой программы процессор выполнит следующей, а какая — будет ждать своей очереди (так называемая «многозадачность», которую пока трогать не будем — в большинстве случаев мы можем прекрасно прожить и без нее, поскольку все заботы об этом все равно берут на себя операционные системы).

Итак, у нас есть оперативная память, в которую загружается программа перед ее выполнением (сразу же по нажатию на Enter из Norton Commander). Операционная система, которая, собственно, и загружает программу, сообщает процессору, что надо начать обрабатывать команды, которые в памяти начинаются с такого-то адреса. И здесь первый подводный камень, вернее скала, которую трудно не заметить.

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

Каким образом? А очень легко! Компьютер «распознает» как выход из программы специальную последовательность байтов. Например, для исполнимых файлов типа com (именно с этим типом файлов мы будем работать на начальном этапе) достаточно последовательности CD и 20.

Пробуем-проверяем? Ну конечно же! Только для этого вам понадобится какой-нибудь шестнадцатеричный редактор, например, HexWorkshop.

Все очень просто — создаем новый файл, единственным содержимым которого является последовательность CD 20, и сохраняем его как, например, myprg_1.com. Если вы позаботились о том, чтобы после CD 20 не было никаких прочих символов, то исполнимая программа будет «весить» только 2 байта.

Запускать это ваше первое творение лучше из Norton или Volcov Commander (все же это пока что DOS’овская программулька).

Что же она делает, эта 2-байтовая малышка? А ничего, просто этот файл обладает двумя важными свойствами:

Последнее и является единственным, что она пока что может делать (корректно выгружаться из памяти)…

Еще к вопросу о выгружаемости — если после CD 20 вы напишите еще что-нибудь (чепуху), она все равно будет проигнорирована. Дело до ее выполнения просто-напросто не дойдет. Другое дело — если вы напишите чепуху до…

#2. Честно говоря, опасно при низкоуровневом программировании чепуху писать. Можно невзначай и винт отформатировать :))). Поэтому лабуду писать не будем, вернее — будем, но не лабуду…

Вот и давайте создадим еще одну программу типа com со следующим «шестнадцатеричным содержимым»:

Если вы все ввели правильно, то прога у вас без проблем запустится, а операционная система не будет ругаться… Правда, визуально (в смысле на мониторе) вы ее работу так и не заметите, но поверьте на слово — она работает! В этом вы еще убедитесь, когда посмотрите на ее работу изнутри — не различающими цветов глазами компьютера… Только сначала еще немного теории…

#3. Теперь поговорим о втором подводном камне :). Один из принципов фон Неймана звучит приблизительно так: машине безразлично целевое назначение данных… Одна и та же цепочка битов может быть и машинными командами, и данными (например, символами, выраженными в виде кодов — есть такая «таблица символов ASCII», наверняка вы ее знаете).

Что из этого следует? А то, что компьютеру нужно указывать, что подразумевается под той или иной «простыней» из битов — данные или код.

На высоком уровне это делает операционная система. Например, она не пытается загрузить в память для выполнения файлы с расширениями, отличными от COM, EXE и BAT (последний вообще не из этой оперы, но принцип сохраняется).

Хотя…, вы всегда можете поэкспериментировать! Смените, например, у какого-нибудь текстового файла тип с TXT на COM и попробуйте его запустить на выполнение (хотя мы это делать настоятельно не рекомендуем!). В большинстве случаев ваш компьютер зависнет! Потому что:

Почти такой же эффект, но с потенциально большей разрушительной силой может получиться, если управление получит ИСПОРЧЕННЫЙ код, который вроде бы «в основном» правильный, но часть его вместо инициализации переменных и прочих подготовительных действий в лучшем случае ничего не делает, а в худшем портит другой код и данные…

Как вам тяжело «въехать» в смысл повествования, состоящего из кусков различных книг, так и компьютеру тяжело понять подобную «мешанину». С той лишь разницей, что любую «неинтересную книгу» вы можете использовать в качестве туалетной бумаги, а вот «компутер» подобного права выбора лишен — он должен в это «въезжать», его процессор начинает перегреваться, а мозги кипят и вытекают через низкоуровневые порты ввода-вывода (командами IN и OUT соответственно).

#4. Еще немного идеологии. О программе, которая выполняется в памяти…

Сколько бы ни было «мозгов» в вашей навороченной тачке, любая программа выполняется в 640 килобайтах «нижней» (или основной) памяти. Если отнять от этой цифры «резидентную часть» операционной системы, многочисленные драйвера и т.д., то оставшееся и есть объем памяти, в котором выполняется ваша программа. А остальные мегабайты — это место для кэширования диска, хранения промежуточных данных и т.п.

#5. Как уже говорилось в #3, одна и та же последовательность битов в памяти может быть:

Соответственно, и программа состоит из трех частей (сегментов): сегмента данных (data), сегмента кода (code) и сегмента стека (stack)…

Оставим пока что «гнилой базар» про смысл словосочетаний «реентерабельный/рекурсивный код» и «адрес возврата». Чтобы не затруднять себе понимание происходящего, мы попытаемся абстрагироваться от всех этих ужасающих вещей и для начала заняться только кодом.

#6. Помните, как в конце фильма «Matrix» Нео в конце концов увидел ее — черно-зеленую «матрицу»? Сейчас с вами произойдет нечто подобное! 😉

Посмотрите на машинные коды, и «что они делают» в #2. Немножко дополним эту «простыню». Например, командой «внести значение» 1234 последовательно в каждый из «регистров общего пользования»:

Наиболее наблюдательные должны для себя отметить, что первый байт — это команда «переместить в регистр», а второй и третий — само число, только байты почему-то «наоборот».

Однако никто не пишет программы в шестнадцатеричных редакторах! Никто! Это большая глупость! Единственное, зачем мы вам про это рассказываем — это чтобы вы поняли, что могут означать загадочные пары шестнадцатеричных цифр в дампе…

Нет необходимости заучивать, что B8 — это «переместить в регистр AX», BB — «переместить в регистр BX» и так далее… Когда-нибудь это может пригодиться тому, кто будет писать компилятор, умеющий генерировать исполняемый код, упаковщики исполняемых файлов, самомодифицирующийся код или, на худой конец, конструкторы полиморфных самошифрующихся вирусов. Но это мы оставим на будущее…

В этом вы можете убедиться, загрузив вашу программу myprg_1.com в debug (например, командной строкой

А вот дальше начинается самое интересное :)))

#7. Вот что вы должны увидеть:

Возвратившись к #2, перенесем сюда «описание» машинных команд.

Эти mov, add, xor, int — так называемые «мнемонические команды» (более или менее понятные человеку), на основе которых формируется (это debug делает) «машинный код». Не правда ли, так намного легче?

Соответственно, вместо шестнадцатеричных кодов мы легко могли вводить эти команды при помощи команды «A» (однако этим мы займемся позже).

#8. А теперь мы выполним нашу программу пошагово — произведем так называемую «трассировку» при помощи команды «T».

Итак, вводим «T» и жмем на Enter!

Смотрим на значение AX и вспоминаем предыдущую инструкцию — «внести значение 0123h в AX». Внесли? И правда! А в самом низу — код и мнемоника команды, которая будет выполняться следующей…

Вводим команду «T» снова:.

AX=0148 — «прибавить значение 0025h к AX». Сделали? Сделали!!

Вводим команду «T» снова:.

AX=0148=BX — «переслать содержимое AX в BX». Сделали? Сделали!!

Вводим команду «T» снова:

«Прибавить содержимое AX к BX». Оно? А то!

Вводим команду «T» снова:

«Переслать содержимое BX в CX». Сделано!

Вводим команду «T» снова:

«Очистка AX»? И точно: AX=0000!

Вводим команду «T» снова… И ГРОМКО РУГАЕМСЯ!!

Потому что, по идее, сейчас наша программа должна была завершиться — у нас же там код выхода прописан, а она куда лезет? NOPы какие-то (если продолжать команду «T» вводить), CALL 1085 (да вы продолжайте «трассировку», продолжайте!)

Для тех, кому лень продолжать жать на букву «T», введите для разнообразия команду «G» (от английского GO). На монитор должна вывалиться надпись «Нормальное завершение работы программы».

Уф, — должны сказать вы — Работает!

#9. Только непонятно вот, почему вдруг между int 20 (CD 20) и надписью «Нормальное завершение работы программы» куча всяких «левых» непонятных команд (в том случае, если вы и дальше производили тарассировку, а не воспользовались «халявной» командой «G»)?

А потому, дорогие наши, что вы имели счастье нарваться на прерывание (interrupt)!

Понимаете ли, завершить программу — дело непростое :). Нужно восстановить первоначальное значение регистров, восстановить переменные среды и кучу всего другого! Знаете, как это сложно?

Однако эта процедура насколько сложная, настолько и типичная для исполняемых программ. А по сему разработчики операционной системы решили избавить программистов от необходимости делать это вручную, и включили эту стандартную процедуру в ядро операционной системы. И сказали: «да будешь ты (процедура обработки прерывания) вызываться как int 20, и будешь ты обеспечивать корректную передачу управления из выполняемой программы — назад в ядро». И стало так…

Ну, посудите сами, должна же операционная система ну хоть что-нибудь делать!!

1.5. Прерывания

#1. Прерывание — это СИГНАЛ процессору, что одно из устройств в компьютере НУЖДАЕТСЯ в обслуживании со стороны программного обеспечения. В развитие этой же идеи, программам позволили самим посылать запросы на обслуживание через механизм прерываний. Получив этот сигнал, процессор временно переключается на выполнение другой программы («обработчика прерывания») с последующим ВОЗОБНОВЛЕНИЕМ выполнения ПРЕРВАННОЙ программы.

Когда же и «кем» генерируются эти «сигналы» (в смысле «прерывания»)?

Когда процессор получает сигнал прерывания, он останавливает работу приложения и активизирует «программу обработки прерывания», соответствующую «номеру прерывания» (т.е. разных сигналов прерываний больше одного — точнее, их 256). После того как обработчик свое отработает, снова продолжает выполняться основная программа.

Для тех, кто не понял. Представьте себе, что вы сидите за компом и выполняете какую-либо работу. И вдруг ловите себя на мысли, что вам СРОЧНО НУЖНО сходить в туалет (терпеть вы больше уже не можете). Вот это СРОЧНО НУЖНО и есть сигнал-прерывание, по которому вы начинаете выполнять определенную СТАНДАРТНУЮ последовательность инструкций (программу обработки прерывания), как-то: встать, пойти туда-то, включить свет … вернуться, сесть за комп и ПРОДОЛЖИТЬ РАБОТУ с того же самого места, на котором вы остановились перед выполнением программы «поход в туалет». В данном случае наш мозг выполняет роль процессора, наши внутренние органы сигнализируют мозгу о потребности в обслуживании, а само обслуживание проводится «программой-навыком», заложенным в процессе нашего развития и (хм!) воспитания.

#2. Программы обработки прерывания располагаются в оперативной памяти (ну а где же еще им располагаться?!) и, следовательно, имеют свой АДРЕС. Однако генератору прерывания этот адрес знать не обязательно :). Есть такая замечательная штука (спросите у тех, кто пишет вирусы) — таблица векторов прерываний. Это таблица соответствия номеров и адресов памяти, по которым находятся программы их обработки.

Почему «спросите у вирмейкеров?». А потому, что поменять адрес «программы обработки прерывания» на другой — проще пареной репы (мы этим еще займемся), в результате чего при запуске классической программы «HELLO, WORLD» может получиться еще более классический format c:…

Программы обработки прерывания автоматически сохраняют значения регистра флагов, регистра кодового сегмента CS и указателя инструкции IP, чтобы по завершении «обработки прерывания», к нашей безумной радости, снова возвратиться к выполняемой программе (просто программе)… Остальные регистры, содержимое которых меняется в обработчике, должен сохранять сам обработчик — и если он этого делать не будет, то нарушится выполнение основной программы. Ведь она даже не знает, что ее «ненадолго» прервали!

Однако на самом деле все намного сложнее :)). Но ведь это только «первое погружение» в прерывания, верно? А посему — пока что без особых «наворотов»…

#3. Одно прерывание мы с вами уже знаем. Это 20-е прерывание, обеспечившее «выход» из нашей COM-программы. Сегодня мы пойдем немножко дальше — помимо «выхода» попробуем поработать еще с одним прерыванием.

Итак, я достаю свой толстый талмуд с описанием прерываний и выбираю, каким бы это прерыванием вас занять на ближайшие 1/2 часа ;)…

Ну, например, вот одно симпатичное, под названием «прокрутить вверх активную страницу».

Внимательно читаем описание (и наши комментарии):

INT 10h, AH=06h (_1) — прокручивает вверх произвольное окно на дисплее на указанное количество строк.

ВХОДНЫЕ ПАРАМЕТРЫ: (_2)

Далее представим входные параметры в виде таблички: (_7)

AH06hALЧисло строк
BHАтрибутBLНе имеет значения
CHСтрока (верх)CLСтолбец (верх)
DHСтрока (низ)DLСтолбец (низ)

Плюс подробнейшее толкование, что подразумевается под словом «атрибут» (регистр BH):

ВЫХОДНЫЕ ПАРАМЕТРЫ: отсутствуют (т.е. ни один регистр не меняется). (_8)

Входные строки гасятся в нижней части окна. (_9)

Нормальное значение байта атрибута — 07h. (_10)

Совсем недавно, если бы вам показали подобное «описание», вы бы ничего в нем не поняли и ужаснулись. Теперь же, после прочтения предыдущих глав курса, в эти «таблицы» вы, более или менее, но «въехать» должны! Тем более, что сейчас я сделаю комментарии для… хм… «отстающих» учеников (внимательно смотрим на циферки в скобках):

_1. Черным по белому, в толстом талмуде, описывающем функции прерываний, написано: «Драйвер видео вызывается по команде INT 10h и выполняет все функции, относящиеся к управлению дисплеем».

И далее. «…ДЕЙСТВИЕ: после входа управление передается одной из 18 программ в соответствии с кодом функции в регистре AH. При использовании запрещенного кода функции, управление возвращается вызывающей программе.

НАЗНАЧЕНИЕ: прикладная программа может использовать INT 10h для прямого выполнения функций видео…»

Вот что из этого следует:

Дело в том, что «прерывание номер десять» — это не только «прокрутка окна», но и, например, «установка режима видео», «установка типа курсора», «установка палитры» и многое другое. Нас же интересует именно первое, поэтому из списка возможных значений (он приведен ниже) мы выбираем именно AH=06h.

Нижеследующая табличка называется «Функции, реализуемые драйвером видео»:

Соответственно, если перед выполнением INT 10 в регистре AH будет значение 06h, то выполнится именно «прокрутить вверх активную страницу», а не что-то другое из «простыни» функций десятого прерывания…

Теперь читаем описание дальше (смотрим на циферки в скобках):

_2. Входные параметры? Что тут может быть непонятного? Даже запуск ракеты с атомной боеголовкой требует прежде всего указать координаты цели… Чего уж тут говорить об обыкновенной функции?

_3. То, о чем мы уже говорили — номер функции из «простыни».

_4. Т.е. на СКОЛЬКО строчек прокручивать. Вспомните так называемый «скроллинг» в любой прикладной программе. На кнопки Up, Down подвешен скроллинг на одну строчку (не путать с координатами курсора), а вот на PgUp и PgDown — штук на 18 строк (AL=01h и AL=12h соответственно). А вот AL=0, вместо того чтобы вообще не скроллировать (по идее), поступает наоборот — «скроллирует» все, что может.

_5. Скажем так — какого цвета будет окно и символы в нем после скроллирования.

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

_7. Резюме того, что было написано выше.

_8. К примеру, попала ли наша ракета в цель или нет ;).

_9. Если бы мы использовали функцию 07h, то было бы глубокомысленно написано, что «строки гасятся в верхней части окна».

_10. Это то самое, которое в DOS по умолчанию. Т.е. белыми буквами на черном фоне. Правда, это 07h лучше все же рассматривать как 00000111b 🙂 но это уже совсем другая проблема…

#4. А теперь мы напишем программу. Ручками, без использования компилятора. Запускаем наш любимый debug.exe, вводим команду «а» и судорожно стучим по клавиатуре:

Сначала запускаем из-под Norton Commander. Затем запускаем из-под debug. Трассируем. Открываем в HEX-редакторе. Смотрим на «бессмыслицу» шестнадцатеричных циферек. Медитируем, медитируем и еще раз медитируем…

1.6. Немножко программируем и немножко отлаживаем

#1. Тем, кто не в курсе — НАСТОЯТЕЛЬНО рекомендую проштудировать предыдущие части курса, иначе «въехать» будет сложно. Тем же, кто внимательно читал предыдущие главы, нижеследующие упражнения для ума и пальцев покажутся детским лепетом…

Давайте немножко видоизменим программу, которую мы писали в прошлый раз. Сделаем так, чтобы наше «окошко скроллинга» располагалось более или менее посередине экрана.

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

Если мы будем программировать ЭТО линейно (именно так для начала), то очевидно, что все, что мы должны сделать — это заданное количество раз (5) заставить машину выполнить вышеуказанные операции, изменяя перед «запуском прерывания» значения регистров AL, BH, CX, DX (полное описание 6-й функции 10-го прерывания ищите в прошлых главах).

#2. Вот к каким умозаключениям вы должны были придти, пораскинув мозгами. За атрибут (то бишь цвет) у нас отвечает регистр BH. Он был равен 10h, а нужно на 10h больше… это, значит, 20h будет…

Ладно… CX (он же CH и CL, как известно) — это ТОЖЕ ШЕСТНАДЦАТЕРИЧНЫЕ координаты левого верхнего угла нашего окошка. Чтобы «окно в окне» получилось, все это нужно на строчку больше сделать и на колонку больше тоже, и все считать в HEX’e. Получается, что в регистр СH нужно вместо значения 05h внести 06h, а в регистр CL вместо 10h — 11h.

А еще можно одним махом в CX записать число 0510h той же командой mov.

Ладно… DX (DH и DL соответственно) — это координаты правого нижнего угла прямоугольника. DH=10h-1h=Fh и DL=3Eh-1h=3Dh.

Ну, а AL=0 и AH=6 — это уже и ежу понятно из описания данной функции (mov AH,6) данного прерывания (INT 10h).

Все, что осталось — это набить в debug’е после команды «a» эти мнемоники энное количество раз. (кажется 5). Набиваем!!

Правда, красивые циферки-буковки? Набиваем-набиваем! Если сейчас к вам подойдут недZенствующие приятели/коллеги и посмотрят, что вы тут колупаете, то ни черта не поймут и покрутят пальцем у виска. Привыкайте к этому. Только не говорите им, что пытаетесь сейчас получить Матрицу, ПОТОМУ ЧТО это неправда. А неправда это потому, что сейчас Матрица в очередной раз обманула вас!

#3. ВСЕ ПОТОМУ, ЧТО МОЗГАМИ ДУМАТЬ НАДО, А НЕ ТОЛЬКО СЛЕПО СЛЕДОВАТЬ РУКОВОДСТВУ!

У вас только первое окно прорисуется, сразу же после чего программа натолкнется на INT 20h и благополучно завершится! А следовательно, и все, что после первого CD 20 написано будет — останется проигнорированным! Исправляйте! (Т.е. уберите все INT 20 КРОМЕ ПОСЛЕДНЕГО).

Второй момент. ВОЗВРАЩАЕТ ЛИ это прерывание ЧТО-НИБУДЬ В РЕГИСТР AX? Смотрите описание. Ничего? Ну так какого черта тогда по новой вводить XOR AL,AL и MOV AH,06 и переприсваивать AH значение 6h, если и без того AH = 6h? Один раз ввести — более чем достаточно!

Скажите, какая мелочь- байтом больше, байтом меньше! А я скажу вот что — на то он и assembler, чтобы «байтом меньше».

#4.Исправляйте? — возмутитесь вы — Да это же по-новому все вводить нужно!

По-новому? — возмутимся мы в свою очередь! — Зачем по-новому? Вы что, с ума сошли?

1. Что вам мешает после команды «a» указать адрес, который вы желаете переассемблировать? И благополучно заменить старую команду на новую!

А что делать, если не переассемблировать нужно, а вообще удалить?

2. Существует куча способов, что вы в самом-то деле! Например, в HEX Workshop с блоками шестнадцатеричных цифр запросто можно работать. Да и в других программах это можно делать — например, в HIEW или даже в Volcov Commander.

Кстати, если процессор встретит команду NOP, то он просто побездельничает некоторое очень короткое время.

ПРОБУЙТЕ!! В конце концов, ваша прога должна принять такой вот вид:

О, да! Получившаяся у вас программа написана долго и бездарно! Имейте это в виду :)). Мы же торжественно обещаем, что в последующих главах обязательно ее усовершенствуем. Да, вот еще что — особенно извращенные могут попытаться заменить INT 20 на JMP 100. Получится, конечно, не ахти, но все же — «анимация» 😉

#5. А теперь мы попробуем ОТЛАДОЧНЫЙ прием! Все кракеры его знают и пользуются им для взлома софта. Имейте в виду, пока вы будете использовать его для своих исполняемых программ — вы программер, исправляющий ошибки, а как только попытаетесь использовать это для отвязки чужой программы от какого-нибудь серийного номера — ваша деятельность станет считаться неэтичной или незаконной. Так что думайте сами, что лучше — флаг в руки или барабан вместе с петлей на шею.

Итак, вводим приблизительно такую командную строку — debug имя_проги.com или же подгружаем прогу в отладчик командой «l» (от слова load) и трассируем, как вы уже неоднократно это делали.

Цель — «на лету» (без изменения кода) заставить первое окошко «рисоваться» не синим (BH=10h), а красным (BH=40h) цветом.

Мы просто приведем вам последовательность действий, а вывод «зачем это нужно» и прочие возможные выводы вы уже сами делать будете. Ок?

Состояние: обнулился регистр AX (первую команду MOV AL,AL мы не видим). Процессор готовится выполнить команду MOV BH,10. Дадим ему это сделать!

Состояние — в BX уже внесен код синего цвета, который нам по условию необходимо заменить на красный (т. е. заменить значение регистра BX с 1000h на 4000h).

Вот теперь-то мы и делаем это «на лету»:

А действительно ли сделали? Проверим!

Состояние? BH теперь равно 40h! Мы «вклинились» между строчками:

И изменили текущую цепь событий, заставив программу делать ТО, ЧТО НАМ НУЖНО! Поздравляю!

А дальше — вводим команду «g» и даем нашей тупорылой программе исполниться. 1:0 не в пользу Матрицы!

1.7. Стек

#1. В средней школе, где когда-то учился автор, учителем физкультуры был настоящий зверь. Помимо того, что он заставлял нас школьников бегать-прыгать-подтягиваться, у него еще любимое наказание было — проходило оно в так называемом «зале тяжелой атлетики».

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

Штанга — это такая «палка», по бокам которой навешиваются так называемые «блины» — круглые плоские с дыркой посередине диски невероятной тяжести.

Хранятся же эти диски на штырях, которые представляют собой те же «палки», но вторкнутые вертикально в пол. На них и хранились диски — один на другой положенные.

«Наказание» заключалось вот в чем — тот «педагог» заставлял нерадивого ученика комплектовать штангу! Это элементарно сделать, если диски просто валяются на полу. Но когда они аккуратно сложены на штырь — это намного сложнее :(.

К чему это лирическое бредисловие? А к тому, что стек — это тоже своего рода штырь с блинами. И уж поверьте, упражняться вы с ним будете намного чаще, чем мы делали это на уроках физкультуры. С той лишь несущественной разницей, что у нас на следующий день болела спина, а у вас на следующий день будут болеть мозги.

Так вот, о стеке: «штырь» для блинов находится в оперативной памяти (где же еще?). А роль блинов выполняют хорошо знакомые нам всем регистры, вернее — их «значения».

Правила работы с ним те же — вы можете снять только верхний «блин». Чтобы получить самый нижний «блин» — вам нужно прежде снять все те, которые НАД ним.

Очевидно, что из десяти «блинов», которые вы надели на «штырь», первым будет сниматься последний из надетых (верхний), а последним — первый, то есть самый нижний.

Все очень просто: «первый пришел — последним уйдешь» и наоборот «пришел последним — уйдешь первым».

Это вам не очередь времен социализма… Это очередь «загрузки-разгрузки» стека!

#2. Для работы со стеком вам пока что необходимо знать только две команды: push и pop. Так как в качестве «блинов» у нас регистры, то, соответственно, необходимо после этих команд указывать и «имена собственные» помещаемых в стек значений регистров.

Ну а как делать то же самое с остальными регистрами вы, наверняка, уже и сами догадались.

С очередностью заполнения стека, наверное, все понятно :). Я много про абстрактные «блины» загружал. А вот с адреса 114 начинается извлечение из стека. В какой последовательности это делается, вы можете увидеть сами, произведя трассировку этой небольшой проги.

Анализируем. Прога еще не начала работать, готовится выполниться команда по адресу 100. Делаем ШАГ!

Анализируем. AX=0001 — значит, команда выполнилась правильно :). Следующая команда, по идее, должна поместить 1 в стек.

И что? Команда выполнилась, но где мы можем увидеть, что в стек действительно «ушла» единица? Увы, но здесь это не отображается :). Проверим потом. Ведь логично предположить, что если эти значения действительно сохранились в стеке, то мы их потом без проблем оттуда извлечем, т.е. если найдем «там» наши 1, 2, 3, 4, 5 — значит все Ок.

А поэтому — дадим программе работать дальше до адреса 114 (не включительно), не вдаваясь в подробный анализ. Что тут анализировать? Если значение регистра AX последовательно меняется от 1 до 5 — значит, команда mov работает. А стек (команда push) проверим потом, как и договорились.

Проехали до адреса 114.

А вот теперь снова анализируем :). При следующем шаге выполнится команда, извлекающая некогда «запомненное» значение AX из стека.

Обратите внимание, регистр IP указывает на адрес (114) выполняемой команды. Мы с вами это уже проходили, не так ли?

Выполнился первый POP. Готовится выполниться второй. AX=5. Т.е., по сравнению с предыдущим шагом, вроде ничего не изменилось… Но на самом деле это не так. AX=5 — эта пятерка «загрузилась» из стека :)). В этом вы легко убедитесь, сделав следующий шаг трассировки.

Ууупс… AX=4 :). А команда, вроде, та же 🙂 — POP AX 🙂

AX=1 🙂 То есть нашлись-таки наши 1, 2, 3, 4, 5 :). Восстановились из стека. Теперь поверили? А то!

Еще раз обращаю ваше внимание на то, что последовательность записи (четыре PUSH’а) была — 1, 2, 3, 4, 5, а вот последовательность извлечения (четыре POP’а) — 5, 4, 3, 2, 1. Т.е. «последний пришел — первый ушел». Зарубите это себе на носу! (Как сделал это на своем перебитом носе наш школьный учитель физкультуры).

Медитируйте над этой темой до полного просветления! Иначе потом придется туго!

1.8. Цикл

#1. Наша программа для работы со стеком линейна. А линейное программирование — это плохо. Хотя и не всегда 🙂

Итак, давайте еще раз посмотрим на нашу программу для работы со стеком. С 100-го до 113-го адреса у нас имеется пять почти идентичных блоков. Изменяется только значение AX, но на одно и то же число — на единицу в большую сторону. То есть AX = предыдущее значение + 1. Это очевидно.

Еще более очевидно, что простая команда POP AX (с 114 по 119) повторяется у нас тоже 5 раз.

Мне почему-то сразу вспомнился анекдот о том, как два мента едут в машине, и один спрашивает у другого: «Глянь, работает ли у нас мигалка на крыше». Тот высунул в голову в форточку и говорит: «Работает-не работает-работает-не работает-работает-не работает…»

Так вот, не будем уподобляться этим нехорошим людям и сделаем нашу прогу более нормальной.

Добьемся мы этого с помощью так называемого «цикла». Цикл — это… Не буду давать общепринятые определения; кто хочет — поищите в книжках, благо, их навалом.

Скажу только: «сесть-встать, сесть-встать, сесть-встать» — это не цикл, а вот «сесть-встать и так три раза» — уже можно считать циклом.

Реализуется же он (цикл), например, при помощи регистра CX и команды LOOP следующим образом.

Число циклов заносится в регистр CX. После этого следует «простыня» из команд, которые вы хотите «зациклить», т. е. выполнить энное количество раз. Заканчиваться все это должно LOOP’ом с указанием адреса «строки», с которой необходимо начать цикл (обычно это «строка», следующая сразу же после mov СХ.

Давайте мы сначала «набьем» нелинейный вариант нашей проги, а потом разберемся, что там к чему. Набиваем:

Наверное, вы уже поняли, что цикл повторяется до тех пор, пока CX не станет равен 0. Несмотря на то что CX — он как бы регистр общего назначения, для «зацикливания» используется именно он :). С остальными такой фокус не проходит. Это и есть так называемая «специализация регистров», о которой мы уже вскользь упоминали.

Протрассируйте эту программу! Искренне надеюсь, что вы поняли, чем это я вас тут загружал.

#2. А теперь вопрос на засыпку ;). Сколько раз выполнится следующий цикл:

Очевидный ответ — 0 раз. В CX же у нас занесен 0. Так вот — ответ неправильный.

Менее очевидный ответ — 1 раз! Ведь перед LOOP’ом сложение один раз все-таки выполнится. Так вот, этот ответ тоже неправильный.

Самые подозрительные могут сразу же посмотреть на этот цикл под отладчиком, и с удивлением обнаружат, что LOOP сначала уменьшает значение CX (0-1=FFFF), а потом уже проверяет, не равен ли он нулю. И с гордостью за задний ум своей головы воскликнут: FFFFh раз!

Так вот: этот ответ близок к истине, но тоже неправильный 😉

Правильный ответ — цикл выполнится 10000h (65536d) раз.

Но только вы и мне не верьте! Истинно только то утверждение, которое вы сами проверили на практике. Медитируйте!

1.9. Немножко оптимизации

Как мы уже говорили, линейное программирование — это плохо, но не всегда. Сравните размеры ваших линейной и нелинейной программ. Не знаю, как у вас, но у нас линейная «весит» 27, а нелинейная — 19 байтов. Как по-вашему, какая быстрее работать будет?

Ну, естественно, нелинейная, потому что она меньше! — скажете вы и будете неправы.

Попытайтесь оттрассировать «зацикленную». Не правда ли, она трассируется намного дольше своего линейного аналога?

Угу, всё поняли? Сам знаю, что ни черта. 🙁

Объясняю: в «зацикленной» программе «компутеру» приходится выполнять БОЛЬШЕ команд, нежели в «незацикленной».

Аргументирую это голословное утверждение следующей таблицей (построенной на основе трассировки):

Что делает линейнаяЧто делает нелинейная
AX=1AX=0
Помещаем в стек 1CX=5
AX=2AX=AX+1=1
Помещаем в стек 2Помещаем в стек 1
AX=3Конец цикла — переход
Помещаем в стек 3AX=AX+1=2
AX=4Помещаем в стек 2
Помещаем в стек 4Конец цикла — переход
AX=5AX=AX+1=3
Помещаем в стек 5Помещаем в стек 3
Достаем из стека 5Конец цикла — переход
Достаем из стека 4AX=AX+1=4
Достаем из стека 3Помещаем в стек 4
Достаем из стека 2AX=AX+1=5
Достаем из стека 1Помещаем в стек 5
ВыходCX=5
Достаем из стека 5
Конец цикла — переход
Достаем из стека 4
Конец цикла — переход
Достаем из стека 3
Конец цикла — переход
Достаем из стека 1
Выход

Ну и как по-вашему, какую из двух простыней процессор быстрее обработает? Сказать вам по секрету? А вот ничего я вам не скажу! Сами думайте! :]

Как сейчас помню, был в моем Турбо-Си в преференсах к компилятору такой радиобуттон: «оптимайзить» по размеру или по скорости выполнения. Угадайте, на чем основан принцип этой оптимизации?

Только не вздумайте писать линейные проги! Пишите «нелинейные»! Нелинейную в линейную «переоптимайзить» — как два пальца намочить! А вот наоборот — :((

Резюме — бесплатный сыр бывает только в мышеловке, и за все надо платить. Компактность и скорость — обычно параметры конфликтующие, поэтому в каждом конкретном случае нужно выбирать, что предпочтительнее. Исследования, проведенные в свое время еще Кнутом, показали, что 80% времени затрачивается на выполнение 20% программы. Соответственно, рекомендуется тратить время на оптимизацию скорости именно тех 20% программы, а остальные можно оптимизировать по размеру (в частности, за счет циклов). Так и получается баланс между компактностью и скоростью программы ;).

1.10. Разборка с процедурами

#1. В разделе 1.7. #4 мы сделали глупую линейную программульку, выводящую окошки. Обещали, что в следующей главе сделаем ее менее «тупой», да отвлеклись почему-то на циклы и стек. То есть я-то знаю, ПОЧЕМУ, но вот вам об этом — не скажу! Догадайтесь сами. Итак, поехали…

Шаг первый. Внимательно посмотрев на «линейную» прогу из 1.7. #4 и прочитав «условие задачи» из главы 1.7. #1, вы обязаны возмутиться — зачем мы использовали команду MOV, если и ежу понятно, что отличия следующего окошка от предыдущего можно выразить более лаконично: BH=BH+10, CH=CH+1, CL=CL+1, DH=DH-1, DL=DL-1? И не нужно напрягать мозги, подсчитывая новое значение регистра вручную.

Если вы так подумали, то оказались совершенно правы! Программу из #4 запросто можно было представить в таком вот виде:

И несмотря на то, что размер ее оказался несколько большим, она тоже будет работать правильно :).

Но тут любой более или менее наблюдательный программер возмутится повторно — да что это за программа такая? В ней целых четыре раза повторяется один и тот же кусок:

И знаете что? Этот наблюдательный программер будет прав! А если он еще и нехорошо выразится по поводу такого «неправильного» стиля программирования — будет прав… или почти прав…

Есть такой процесс — оптимизация, одной из особенностей которой является уменьшение параметризации (параметризация — вставка процедур в место вызова или фиксация значения отдельных параметров) и развертка циклов. То, что обычно такими вещами должен заниматься компилятор, суть меняет не сильно — тем более, что ассемблер оптимизацией сам не занимается :). Но до ассемблера мы с вами еще не добрались, поэтому говорить об этом пока еще рано.

Внимательно всмотритесь в полный текст программы и в этот выделенный кусок. И помедитируйте над ним до полного просветления текущей «обстановки»…

#2. Итак, у нас есть ПОВТОРЯЮЩАЯСЯ ЧАСТЬ программы. А еще у нас есть пальцы, которым, как правило, лень набивать длинные «простыни» программного кода. Это одна из многочисленных причин, по которым и придумали такого «зверя» как ПОДПРОГРАММУ (она же — ПРОЦЕДУРА, она же — ФУНКЦИЯ). Остальные причины мы рассмотрим попозже, а вот на счет «лени» поговорим прямо сейчас:

Если мы возьмем наш «часто повторяющийся» кусок программы и допишем в конец команду RET, то получится у нас именно ПРОЦЕДУРА — во всей своей красе:

Красота ее вот в чем заключается — процедуру можно «вызвать» командой CALL :)))

Все более чем просто. Когда в программе встречается CALL с указанием АДРЕСА-НАЧАЛА-ПРОЦЕДУРЫ (в нашем случае это 011E), то компьютер «идет» по этому адресу и выполняет все команды, расположенные между «точкой входа» (включительно) и командой RET, то есть так называемое «тело» процедуры.

RET — это тоже команда, но к «телу» (адреса 11E… 12D) она не относится. Она является «ОРГАНИЗАТОРОМ» этого «тела». Процессор, встретив команду RET, возвращает управление обратно после последнего CALL (т.е. «перепрыгивает» на строчку ниже «вызвавшего» данную процедуру CALL’а)…

Короче, CALL XXXX означает — «выполнить процедуру, начинающуюся по адресу XXXX». А RET означает — «конец процедуры» и, соответственно, переход на строчку ниже вызвавшего его CALL’а.

Если же говорить более формально и строго, то процедура — это средство обобщения, когда некоторая общая последовательность действий получает «имя», и потом при необходимости ПОВТОРНОГО ИСПОЛЬЗОВАНИЯ данного кода к нему просто идет обращение по «имени» (напомним, что в отличие от языков высокого уровня и даже ассемблера, в машинном коде от имен остаются одни только адреса, а язык, принимаемый DEBUG, является промежуточным между машинным кодом и ассемблером). Более того, следующим логическим шагом после обобщения является параметризация, когда некоторые части общего кода зависят от передаваемой извне информации (параметров), с чем очень хорошо знакомы программисты на языках высокого уровня. Но о параметризации и ее применении в ассемблере мы поговорим в другой раз.

Не ругайтесь. Мы знаем, что вы ни черта не поняли. А по сему набьем в debug’е эту прогу и посмотрим, что она делает.

Те, кто читал внимательно, могут отметить, что инструкция CALL по своему действию очень похожа на инструкцию генерации прерывания INT, с той лишь разницей, что аргументом CALL является адрес процедуры, а не индекс в таблице «векторов прерываний», где и хранится адрес обработчика прерывания (той же процедуры). А для особо продвинутых отметим, что в ранних моделях процессоров от Intel при подаче запроса на обработку от внешнего устройства контроллер прерываний, помимо собственно сигнала прерывания, посылал в процессор инструкцию CALL.

#3. Кстати, вы уже поняли, почему мы называем debug «до боли любимой программой»? Нет? Неужели вы еще не полюбили это произведение программерского гения всеми фибрами своей души? Еще нет? М-да… мы в вас разочаровались.

А по сему: — НАБИВАЕМ! — злобно кричим, брызгая слюной на эргономичный коврик:

Не правда ли, красиво получилось?

Первое, что вас может смутить — это то, что команда выхода (INT 20) расположена не там, где вы привыкли, то есть не в конце программы.

Ну что я вам могу на это ответить? Концы — они-то разные бывают! Последняя строчка в листинге вовсе не означает, что последней будет выполняться именно она. И это не должно вас смущать! А если все же смущает — смотрим, как работает эта прога из-под отладчика.

Итак, до адреса 0110 вам все должно быть понятно, мы это рассматривали. Трассируем дальше…

Что значит «трассируем»? — попросите вы напомнить.

Мысленно мы ругаем вас нехорошими словами (ну сколько раз повторять-то можно!), а вслух скажем: Команда «T» и Enter. Команда «T» и Enter. Команда «T» и Enter…

Команда CALL 011E по адресу 0110 говорит процессору: «Дальше мы не пойдем, пока не выполним простыню, начинающуюся по адресу 011E». И далее, естественно, следует переход на этот адрес.

Входим в тело процедуры, начиная с 011E, и выполняем команды до 012D включительно…

А теперь внимательно смотрим, на какой адрес нас «перекинет» команда RET.

На 113-й? И это правильно! По 113-му адресу у нас какая команда? Да вот опять CALL 011E!

Опять процедура с адреса 011E, опять RET[URN] на строку ниже, то есть на 116…

И так далее, до того момента, пока следующей строчкой не окажется INT 20 — собственно, на этом и программе конец.

Ну оно и ежу понятно, что, несмотря на то что INT 20 — не в конце программы, последним выполнится именно он.

Короче, куда бы вас не посылали всяческие «столбы с указателями», конец вашего пути только один… А плутать вокруг да около этого конца вы можете сколько вам заблагорассудится…

Кстати, именно это и является одной из многочисленных тайн программинга.

Кто после этого скажет, что программисты — недZенствующие люди?

#4. Те, кто внимательно ознакомились с циклами, они и на этом не остановятся! Посмотрев на адреса 110…119, они вообще возьмут и возомнят себя воистину крутыми парнями! Знаете, что они напишут? А вот что (предвидим!):

То бишь еще и CALL в цикл при помощи MOV CX,4 и LOOP’а «закрутят». И что? А попробуйте!

Что, не «пашет»? А что надо делать, если «не пашет, а должно бы»? Правильно! Смотреть из-под отладчика!

Смотрим? Если посмотрите, то сразу же и «загвоздку» увидите — CX, использованный в качестве «счетчика» циклов, «перебивает» тот же CX, но используемый как «координаты верхнего левого угла окна». И что с этим делать прикажете?

Вот вы и столкнулись с одной из самых больших проблем. В процессорах фирмы Intel есть только 4 регистра общего назначения (и то в большинстве случаев — специализированных). Помните, мы вам говорили об этом?

А теперь попробуйте выкрутиться из этой нехорошей ситуации с использованием стека :). Кстати, весьма «мозгопрочищающая» задачка :).

1.11. Переходы

#1. «Переходы» бывают разные. Если вы пришли в гости, а вас просто послали к черту — такой переход называется «безусловный». А нежели вам сказали: «Если без пива — то иди к черту, а если с пивом — тогда проходи», — то это уже «условный» переход.

Соответствено, для успешного перехода необходимо указать: ПРИ КАКОМ УСЛОВИИ выполнить переход, КУДА ПЕРЕЙТИ, ну и, наконец, сам пинок под зад нужно СДЕЛАТЬ, чтобы переход «гостя» в заданном направлении все-таки «состоялся».

Безусловный переход у нас «делает» мнемоническая команда JMP, после которой следует указать адрес, на который «компьютер» должен пойти «на» ;). В данном случае УСЛОВИЕМ у нас будет «при любых обстоятельствах»: хоть пустой, хоть с пивом, хоть с … все равно. Когда рисовали окошки, вы уже использовали эту команду для создания «спецдефекта». Если кто еще не понял, что делает эта команда — к нему (см. 1.7 #4) и отсылаю. Сделайте «спецдефект» и посмотрите на него под отладчиком. Когда до вас дойдет, почему мы там не предусмотрели выхода (INT 20h) — можете переходить к п.2 текущей главы.

#2. Условный переход у нас организуется в два шага. На первом шаге мы вычисляем условие («принес ли пиво?»), на втором шаге «посылаем» или не «посылаем» — в зависимости от результатов вычислений. Можно привести такую аналогию — на первом шаге два груза кладутся на весы, сравнивающие их массу. Соответственно, возможны только три их положения: наклон влево (груз в левой чашке тяжелее), наклон вправо (груз в правой чашке тяжелее) и равновесие. На втором шаге мы предпринимаем действия в зависимости от положения весов.

Например, на первом шаге можно использовать как «аптекарские весы» инструкцию CMP, которой обязательно нужно указать, ЧТО и С ЧЕМ она будет сравнивать.

В зависимости от значений регистров у нас возможны следующие состояния: «наклон влево» (AX &tt; BX), «наклон вправо» (AX > BX) и «равновесие» (AX = BX). Таким образом ВЫЧИСЛЕНИЕ УСЛОВИЯ у нас уже организовано :). Только условие не бинарное, а есть еще и «серединный вариант» (и даже несколько других!). Это нормально. Это для того сделано, чтобы мы могли выражения типа «больше-или-равно», «меньше-или-равно» да и просто «равно» в своих программах использовать…

Итак, УСЛОВИЕ есть. Теперь решаем, что нам делать при том или ином условии. Вот далеко не полный список возможных «прыг-скоков»:

Естественно, что после мнемоники («прыгнуть, если») должен стоять АДРЕС, куда нужно «прыгнуть», если условие соблюдено. Если же условие не соблюдено, то прыжок не происходит, и выполняется нижеследующая строка программы.

Задание на медитирование — зрительно представьте себе «весы правосудия». И побросайте на их чашки разную шестнадцатеричную дрянь в различных «пропорциях» и «комбинациях». Просветлиться вы должны следующим образом — в какую бы сторону эти ваши «весы» ни склонялись, вы все равно заставите систему работать так, как ЭТО вам угодно! «Весы» — они только констатируют факт. А вот «приговор» выносят судьи. Хе… и пусть после этого только кто-нибудь скажет, что программерам чужда политика — дело, как известно, весьма грязное.

А о чем это мы? Ах да, переходы…

#3. Продолжим программировать, что ли? Напишем что-нибудь красивое и неизменно тупое? С использованием условных и безусловных переходов?

Поехали! Слабаем мы сейчас что-то наподобие графического редактора :)). Не верите?

У-у-у… Сложная задачка! Если въедете, что да как — значит, молодцы! Значит, разобрались-таки с дzебагом! Значит, подключились-таки к программерскому эгрегору и более или менее привели в порядок свои мозги :)… А это сложная штука, мы вам скажем — мозги в порядок приводить! Особенно когда есть куча инструментов, которые «порядок в коде» сами как бы наводят :).

Думаете, вы по нашим текстам программировать учитесь? По-настоящему программировать мы еще не начали! Все, чем мы пока занимаемся — это приводим в порядок свои мозги и тренируемся на кнопки клавиатуры нажимать :)). А вот ско-о-оро НАЧНЕМ… тогда «прощай, здоровье» будет настоящее!

Итак, сначала рассмотрим прерывания, которые в нашем «графическом редакторе» будут использоваться. Их три штуки, и все — BIOS’овские:

Опять-таки — подробности о координатах и «кодах» цвета ищите сами!! Благо, знаете, где искать.

Эта функция считывает код сканирования и код символа (клавиши на клавиатуре и соответствующий ей ASCII-код) из буфера клавиатуры (есть такой). Если в буфере ничего нет — она ждет, пока там что-нибуть появится. То есть ЖДЕТ, чтобы вы нажали на какую-нибудь клавишу, код которой будет занесен в регистр AX. Причем в AL — «символ», а вот в AH — так называемый «код сканирования»…

Кодами вы пока голову не забивайте. Достаточно знать, что после нажатия клавиши Up в AH «попадет» значение 48h, Down — 50h, Left — 4Bh, Right — 4Dh.

Как работает последний кусок кода, обязательно проверьте под отладчиком, это полезно :).

#4. И лезем, лезем в наш горячо любимый DZEBUG, дабы набить там драгоценные строчки машинного мнемонического никому-кроме-вас-непонятного кода!

Тут один из автору вставили шпильку:

«Не хватает проверки и выхода (со сбросом видеорежима!) по Esc — такие действия должны быть обязательным атрибутом, а не домашним заданием.»

Совершенно верная шпилька, товарищи! Но все равно — пусть это будет домашним заданием.

Если вы все ввели правильно — должно заработать! Полюбуйтесь плодами своей медитации… Красиво?

#5. А сейчас мы это все дело прокомментируем:

Правда, здорово получилось?

1.12. Данные

#1. Работать с кодом мы с вами научились. Сейчас поучимся заставить наш код обрабатывать данные…

Итак, запускаем DZEBUG и вводим следующую команду:

Которая означает: «набиваем память всяким дерьмом начиная со смещения 115».

В ответ он вам выплюнет:

Что означает: байт по смещению 115 равно 00. И точка. Но это не простая точка — это приглашение ввести НОВОЕ ЗНАЧЕНИЕ этого байта. Когда вы его ввели, нужно нажать на пробел.

Если вы вознамеритесь последовательно ввести 1,2,3,4,5, то это будет выглядеть приблизительно так:

А теперь делаем дамп памяти и смотрим, что за дрянь у нас получилась…

А ведь получилoсь же!!

#2. Мы запросто умеем «присваивать» регистру любое значение (mov AL,1C какой-нить), запросто можем «копировать» содержимое одного регистра в другой (mov AL,BL например)… А сейчас мы с вами научимся при помощи той же команды MOV еще и с данными из памяти работать.

Все проще пареной репы… Если мы напишем

то в результате выполнения этой команды в регистр AL «внесутся» две шестнадцатеричные циферки (байт), которые по адресу 115 находятся. То есть в нашем случае AL станет равным 1.

А теперь посмотрите, что делает «обратная» команда:

В первой строчке мы присвоили AL значение 55, а второй строчкой «скопировали» значения регистра в байт по адресу 115. Правда, проще некуда?

Обязательно посмотрите на этот процесс под отладчиком!

#3. А еще вот какой изврат с этим можно делать:

Сие присваивает регистру AL значение байта по адресу 115 :). Ну… через посредника «BX» присваивает! Который у нас «переменная», как известно :).

А этот кусок кода у нас «записал» 1C в сегмент данных по адресу 115 :). Ну, и извращения наподобие:

Тоже весьма и весьма полезны в программерском деле :).

Короче: все, что в квадратных скобках, — это адрес в памяти, с которым вы собираетесь «работать». Другой вопрос, что этот адрес может быть «составным»…

#4. Низкоуровневый Paint мы с вами уже писали. Сегодня напишем низкоуровневый дZенский EXCEL.

Задание простое… Есть у нас табличка типа:

в которой данные в формате HEX. И все, что нам нужно с ними сделать — это просуммировать каждую «строчку», а сумму занести в третий «столбец»… В EXCEL’е это делается элементарно… А на машинном уровне, в общем-то, не намного сложней!!

Для начала мы наберем «исходные данные» и зарезервируем место (например, забьем нулями) под третий столбец, в который собираемся помещать результат…

Набиваем блок данных, начиная с адреса, например, 115:

Вот так это у меня в DZEBUG’е выглядело :). Только я еще дамп посмотрел, правильно ли я ввел:

Вроде правильно :)). Ну а программу я вот какую придумал:

В BX я занес адрес начала блока данных (он же — верхний левый угол нашей таблицы). В CX внес 5, чтобы столько раз цикл выполнился (LOOP по адресу 111). А тело цикла вообще простое:

Команда по адресу 106 забирает в AL цифирь из первого столбца.

108 — суммирует «цифирь из первого столбца с цифирью из второго столбца» (сумма, само собой, в AL’е остается).

10B — записывает сумму в третий столбец :).

Ну и ADD BX,3 для перехода на следующую строчку :).

Сделайте трассировку (внутрь INT 20 залезать не надо) и посмотрите на дамп нашего блока данных 🙂

Я и говорю: ПРОЩЕ ПАРЕНОЙ РЕПЫ!! 😉

#5. Видите? В качестве переменных «в компьютере» можно использовать не только регистры, но и «куски» памяти! А уж там вы можете клепать свои переменные в почти неограниченном количестве! Единственное, что нужно иметь ввиду: с переменными-регистрами компьютер работает намного быстрее, чем с переменными-в-памяти :).

Кстати, если вы хотите сохранить плод своих сегодняшних трудов на веник, то имейте ввиду, что вы и сегмент данных тоже должны сохранить! То есть: вам нужно сохранить весь «диапазон» от адреса 100 до 123 включительно :).

Ну и, само собой, при попытке дизассемблирования с адреса 115 у вас абракадабра пойдет… мы об этом уже говорили и упоминали один из принципов фон Неймана.

Диагноз

Полагаю, вы уже поняли, что значит «выучить язык ассемблера» 🙂 и теперь с удовольствием кинете грязью в того, кто скажет вам, что это сложно 😉

Что значит «выучить язык», и что значит «программировать»? А проводите сами границы между этими понятиями! Только имейте в виду, кто скажет «выучить — значит все команды запомнить — тот дурак :((.

Да о чем это я, в общем-то? (Утомлен кофием, поэтому речь несвязна)? Просто хотел сообщить вам, что первая часть курса закончилась. Вооружившись справочником команд и прерываний, вы уже можете программировать под дос. Если вы внимательно штудировали предыдущие главы, то идеология этого дела (под дос) вам уже должна быть понятна как 2х2=100b.

Источник

Добавить комментарий

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