Дневники чайника. Чтива 0, виток0

Практикация всего мозга.
Наш четвёртый день путешествия

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

Подведём итоги.

Команда NOP ничего не делает.

Команда MOV копирует куда-то, чего-нибудь. Именно в таком порядке!

В отладчике или дизассемблере мы видим hex-байты, а не те циферки, к которым вы привыкли.

Регистр - логическая часть ЦП, предназначенная для временного хранения данных. Можно сказать, что регистры общего назначения - это переменные, как X или Y в алгебре. Только регистры физически существуют в отличие от абстрактных переменных.

Вот все восемь РОН32:
32-разрядные регистры данных
EAX EBX ECX EDX
 AX  BX  CX  DX
E-з бвмAHALE-з бвмBHBLE-з бвмCHCLE-з бвмDHDL

 

32-разрядные регистры-указатели
ESP EBP ESI EDI
E-частьSP E-частьBP E-частьSI E-частьDI

Таблица получилась кривоватая, и если вы не поняли, что:
- все регистры имеют одинаковое количество бит,
- между ними нет связи,
- E-часть не является подрегистром и соответственно у неё нет имени обращения,
тогда лучше прочитайте о регистрах ещё раз из всех известных вам источников... Хотя сам я понял, что такое регистры, только из практики :).

Допустим, в EAX мы загрузили число 38B57203h, тогда AX будет 7203h, AH 72h, AL 03.

            AH AL
EAX = 38 B5 72 03
             AX

Вот, пожалуй, и все главные итоги.

Сейчас опять будем писать новую программу.

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

Перед написанием следующего примера познакомимся с парочкой важных команд.

Чтоб сложить содержимое двух регистров, нужно писать так:

add   AH,AL    ; прибавить к AH содержимое AL

или

ADD   eax, ebx ; прибавить к EAX содержимое EBX

или даже так

add   eax, eax ; прибавить к EAX содержимое EAX (то есть удвоить EAX)


Команда ADD
Происхождениеот англ. слова add - прибавлять, присоединять
Форматadd приёмник, источник
Действиеприёмник = приёмник + источник
Примечаниеесли нужно увеличить всего на один, лучше использовать команду INC

Чтобы вычесть из значения регистра, нужно писать так:

SUB   CX,DX    ; отнять от CX содержимое DX

или

sub   ebx, eax ; отнять от EBX содержимое EAX

или опять же так

sub   ECX, ECX ; отнять от EСX содержимое EСX (то есть обнулить EСX)


Команда SUB
Происхождениеот англ. слова sub, subtraction - вычитание
Форматsub приёмник, источник
Действиеприёмник = приёмник - источник
Примечаниеесли нужно отнимать всего один, лучше использовать команду DEC

Вот код нашей следующей программы.

Этот кусок исходника не для Hiew'a, прежде чем набирать программу, обязательно прочитайте пояснение ниже.

prax02.com:

mov  BX, 0133h                ; Поместить в BX значение 0133h (базовый адрес)
mov  dword ptr [ВX],04030201h ; Поместить в память по адресу 0133h 
                              ; значение 01020304h размером в 4 байта

add  dword ptr [BX],30303030h ; Прибавить к 4 байтам в памяти по адресу 0133h
                              ; значение 30303030h, размером в 4 байта.

mov  dx,bx                    ; Копировать содержимое 
                              ; регистра BX в регистр DX (BX=DX=133h)

mov  ah,9                     ; Поместить значение 9 в регистр AH
int  021h                     ; Вызвать подпрограмму вывода текста на экран

mov  ah,10h                   ; Поместить значение 10h в регистр AH
int  16h                      ; Вызвать подпрограмму ожидания нажатия клавиши



mov  dword ptr [BX],50505050h ; Поместить в память по адресу 0133h
                              ; значение 50505050h размером в 4 байта

sub  dword ptr [BX],18191A1Bh ; Отнять от 4 байт в памяти по адресу 0133h
                              ; значение 1B1A1918h размером 4 байта
mov  ah,9                     
int  021h                     ; Ещё один вывод строки

mov  ah,10h                   
int  16h                      ; Повторить ожидание нажатия клавиши

int  020h                     ; Вызвать подпрограмму завершения

Хочу сказать ещё об одной важной особенности.

Все строки в чистом Ассемблере содержат только одну команду.

А что же тогда "dword ptr", спросите вы.

Dword ptr - не команда, это всего лишь определитель размера данных. Подобная запись, можно сказать, - надстроенная условность. После того как вы наберёте эту программу, мы обстоятельно поговорим про данные и их размер. А сейчас только скажу, что в Hiew'e вместо "dword ptr" пишется "d," (маленькая латинская буква "ди" и запятая без пробела) чтоб ещё больше сократить писанину.

В Hiew'e создать такую программу можно как и всегда. Создав отдельно com-файл, откройте его, переключитесь в режим Ассемблера (F4,F4 или Enter, Enter), начните редактировать (F3) и затем нажмите Enter, чтоб вводить команды.

Набрав последнюю инструкцию (int 020h), введите её Enter'ом и нажмите Esc, чтобы прекратить ассемблирование. Теперь впечатайте в самом конце 5 новых байтов 00 00 00 00 24 (ничего при этом не стирая выше). Сохраните файл (F9).

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

А вот байт 24h - это символ $. Так же, как и в первом примере, этот символ нужен для окончания строки текста.

Вот как в Hiew'e будет выглядеть эта программа.

Адреса    Байты          имена   операнды          комментарии

00000000: BB3301         mov     bx,00133         ;" 3"
00000003: 66C70701020304 mov     d,[bx],004030201 ;"    "
0000000A: 66810730303030 add     d,[bx],030303030 ;"0000"
00000011: 8BD3           mov     dx,bx
00000013: B409           mov     ah,009           ;"    "
00000015: CD21           int     021
00000017: B410           mov     ah,010           ;"    "
00000019: CD16           int     016
0000001B: 66C70750505050 mov     d,[bx],050505050 ;"PPPP"
00000022: 66812F1B1A1918 sub     d,[bx],018191A1B ;"    "
00000029: B409           mov     ah,009           ;"    "
0000002B: CD21           int     021
0000002D: B410           mov     ah,010           ;"    "
0000002F: CD16           int     016
00000031: CD20           int     020
00000033: 0000           add     [bx][si],al
00000035: 0000           add     [bx][si],al
00000037: 24             and     al,000

Если у вас получилось так, можете смело запустить программу.
Сначала вы увидите
1234
Нажав на любую кнопку, вы измените строку
12345678
А следующее нажатие приведёт к выходу из программы.

Возможно, программа не так уж и эффектна, но это довольно сложная программа. В ней много нового и полезного для начинающего.

Давайте сразу начнём разбор. Параллельно я рекомендую вам смотреть на действия в отладчике.

Ещё раз напомню, com-программа загрузится в память по адресу 100h. Это значит, что просто прибавляется 100h к адресам в файле.


00000000: BB3301         mov     bx,00133 ;" 3"

Эта строка для вас уже не секрет. При выполнении маш.команды BB 3301h происходит инициализация 16-битного регистра BX значением 01 33h.

Данное значение в следующей строке будет использоваться как адрес в памяти.

Всё, что после точки с запятой, не имеет никакого смысла, это бесполезный (в данном случае) автоматический комментарий Hiew'a. Поэтому дальше я удалю закорючки вместе с символом точки с запятой.
После выполнения этой строки:
EBX=00000133h


00000003: 66C70701020304 mov     d,[bx],004030201

Суть здесь в том, что когда операнд находится в квадратных скобках, это означает, что нужно производить действие по адресу в памяти, указанному операндом. Значит, эта строка просто записывает 4 байта (04 03 02 01h) в оперативную память по адресу 0133h. Но есть и запутка.

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

Самые любопытные сразу же спросят - а почему BX?

Так уж устроен машинный язык Intel.

BX не случайно, дело в том, что его назначение (база) как раз и состоит в адресации.

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

А вот в 32-битном режиме адресации такие инструкции есть для всех РОН, и мы можем использовать любой из них для подобных целей.

Всё это сложно и, самое главное, не очень важно. Вы просто можете попробовать набрать команду "mov d,[AX],004030201" и получить отказ. :)
После выполнения этой инструкции:
Память будет изменена
адрес значение
0133h 01
0134h 02
0135h 03
0136h 04
0137h 24h не меняется (24h - это ASCII-код символа "$")


0000000A: 66810730303030 add     d,[bx],030303030

Здесь происходит сложение четырёх байт в памяти по адресу 0133h и четырёх байт, явно указанных в команде (30h30h30h30h). По адресу 0133h находится приёмник, а источник - непосредственно в команде.
После выполнения этой строки:
Память будет изменена
адрес старое     новое
      значение   значение
0133h 01 +30h= 31h    (31h - это ASCII-код символа "1")
0134h 02 +30h= 32h    (32h - это ASCII-код символа "2")
0135h 03 +30h= 33h    (33h - это ASCII-код символа "3")
0136h 04 +30h= 34h    (34h - это ASCII-код символа "4")
0137h 24h не меняется (24h - это ASCII-код символа "$")


00000011: 8BD3           mov     dx,bx

Здесь происходит копирование 16-битного регистра BX в другой 16-битный регистр DX. Оба они теперь будут содержать значение 0133h. Для чего оно нужно? Для вывода сообщения на экран есть специальная функция, и она должна знать, где находится сообщение в памяти. Адрес сообщается через DX.
После выполнения этой строки:
DX=BX=0133h


00000013: B409           mov     ah,009

Происходит инициализация 8-битного регистра AH значением 09. Далее вы увидите зачем.
После выполнения этой строки:
EAX=00000900
(так как менялся только AH).


00000015: CD21           int     021

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

Int - это не просто команда.

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

Ещё до загрузки какой-либо операционной системы можно использовать некоторые из них (прерывания BIOS). Это вовсе не означает, что они жестко заданы.

Существует специальная таблица, в которой хранятся векторы (адреса) прерываний. Можно самостоятельно задать вектор любого прерывания с номерами от 0 до FFh (больше не придумали). Так и делает любая ОС.

Во время загрузки DOS происходит переназначение (инициализация) таблицы векторов прерываний.

Так уж условились, что прерывание 21h будет занято DOS-функциями. Номер функции сообщается через AH. Там сейчас значение 09 (вывод строки на экран). Строка для вывода будет взята по адресу, сообщённому в DX (опять же чистая условность в ДОС). На самом деле у int 21h очень много функций. Прерывание 21h так и называют - мультифункциональное DOS-прерывание.

В Windows-программах практически не используются прерывания (только в исключениях). В форточках - свои функции, называемые API (об этом дальше).

Когда процессор получает команду "int номер", он переходит к выполнению вызванного прерывания.

Что же происходит внутри подпрограммы?

Прерывание 21h проверяет значение регистра AX, и если AH =9, выполняется функция вывода строки на экран. Её код делает довольно простое действие - копирование в видеопамять участка памяти, начиная от адреса из DX и заканчивая знаком "$". Сделав это, подпрограмма передаст управление обратно нашей программе со следующей строки.
После выполнения этой строки:
На экране в окне DOS должна появиться текстовая строка
1234


00000017: B410           mov     ah,010
00000019: CD16           int     016

В 8-битный регистр AH помещается значение 10h как параметр для BIOS-прерывания, которое вызывается в следующей строке (процесс вызова будет рассмотрен далее). BIOS-прерывание 16h служит для работы с клавиатурой. Параметр 10h заставит это прерывание дождаться нажатия любой клавиши и вернуть управление нашей программе.
Во время выполнения строки 19h:
Компьютер будет ждать от вас нажатия клавиши.


0000001B: 66C70750505050 mov     d,[bx],050505050
00000022: 66812F1B1A1918 sub     d,[bx],018191A1B
00000029: B409           mov     ah,009
0000002B: CD21           int     021
0000002D: B410           mov     ah,010
0000002F: CD16           int     016

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

Строка 1Bh. В память помещаются 4 байта со значением 50 50 50 50h.

Строка 22h выполняет вычитание из 4 байт в памяти.
После выполнения этой строки:

адрес старое    новое
      значение  значение
0133h 50h -1Bh= 35h (35h - это ASCII-код символа "5")
0134h 50h -1Ah= 36h (36h - это ASCII-код символа "6")
0135h 50h -19h= 37h (37h - это ASCII-код символа "7")
0136h 50h -18h= 38h (38h - это ASCII-код символа "8")
0137h 24h           (24h - это ASCII-код символа "$")

Знаю, многие из вас заметили, что вместо 18 19 1A 1Bh мы видим 1B 1A 19 18h, но я уже сказал: об этом - следующая глава.

Строки 29-2Fh полностью повторяются. Они выполняют вывод на экран текстовой строки после уже существующих на экране символов. И ждут нажатия клавиши.
На экране было:
1234
На экране станет:
12345678


00000031: CD20           int     020

Ну а это, как вы уже, наверное, догадались, прерывание, выполняющее завершение программы (ему параметры не нужны, оно умеет только это). В виндах завершение программы выполняется почти так же, только вместо прерывания вызывается API-функция ExitProcess.
После выполнения этой строки:
Управление вернётся к вызывающей стороне. В форточках, если программа была запущена из графики, будет закрыта виртуальная сессия DOS.




Очень удивлюсь, если вы скажете: "Я понял, как работает эта программа". :)

Сам, когда в первый раз набрал подобную программу, ничего практически не понял. Но совсем скоро, через несколько дней, всё стало ясно. Так что практика в течение двух-трёх дней сделает чудо. Очень скоро, если вы не бросите практиковаться, вы будете думать как ассемблерщик, вы будете читать код программ так же быстро, как вы читаете газеты или журналы, параллельно пережёвывая завтрак.

Да, кстати о завтраке, если, не дай Бог, я вас морю голодом, сейчас же отложите чтение. Подобные вещи на голодный желудок читать нельзя, это может принести только вред. Голова в момент развития потребляет гораздо больше калорий, чем в школе на уроке :). Можно даже сказать, что нагрузка значительно больше, чем при спортивной подготовке. Только измерять такие вещи неправильно. Всему своё время и свои сложности.

Будет очень хорошо, если вы запустите эту программу в отладчике ещё и ещё раз.

Всегда замечайте, как изменяются регистры данных (EAX,EBX,ECX,EDX).

И будет ещё лучше, если вы скопируете файл и измените код программы.

Например, могу предложить задание:
Сделайте так, чтоб эта программа выводила на экран буквы ABCDEFGH и при этом не содержала буквы в явном виде до выполнения кода. Если у вас это получится, ставлю вам 4 балла (или по новой системе 94 :).

Ну, а если юнга напишет свою программу, которая будет выводить на экран весь алфавит и его не будет заранее в данных (то есть он будет формироваться по ходу программы), тогда я завтра же произведу его в матросы ;).

Мне всё время приходится решать: рассказать ли вам сейчас о каком-нибудь понятии или отложить это дело на пару страниц. Однако к данному моменту откладывать приходится уже совсем немного, и это здорово. Скоро мы перейдём к написанию программ для Windows.

Bitfry

<<предыдущая глава     следующая глава>>

Вернуться на главную

Hosted by uCoz