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

Регистры процессора.
Третий день

Итак, почему же ah?

AH - это регистр процессора.

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

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

Знаю по себе: понять, зачем нужны регистры можно только через практику. Другого способа нет.

Причём где-то дней 10-15 я вообще не врубался - как оно всё... чтобы вот как-то вот так. :)

Но сейчас мне кажется, что регистры - самое простое, и в то же время важное, что есть в процессоре.

Ну, сами подумайте, что делает программа?

И т.д.

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

И если даже данные не хранятся в регистрах, то в них обязательно будут указатели на эти данные (адреса данных в памяти), других способов работы с данными у процессора нет, только через собственные регистры. А если речь идёт исключительно о сторонних устройствах (например, звуковуха, видяха, винт и т.д.), то мы тоже используем регистры, только это уже не регистры процессора, а, например, PCI-регистры, или AGP, или SATA и так далее.

Так что тема АРХИВАЖНАЯ! Но непонятная без примеров. Их должно быть как минимум 10-15 (по одному в день).

Что-то я разболтался, давайте уже к делу.

Загрузите prax01.com в отладчик CodeView. Для этого скопируйте файл в каталог отладчика (примечание) и запустите из командной строки "cv prax01.com". Отладчик CodeView у вас должен выглядеть примерно так:

Внешний вид отладчика Code View (png 19,021b).

Итак, смотрим на код программы в памяти:
[3]
   Адреса Байты    Имена        Операнды
12BD:0100 B409     MOV          AH,09

12BD - сегмент нашей программы, он может быть и другим.

Поскольку все примеры для DOS у нас значительно меньше 64Kb, они будут в одном сегменте.

Значит, как я уже говорил, до следующего витка мы можем игнорировать сегменты вообще, а уж в Win32 тем более.

У com-программ в оперативной памяти адрес первой машинной команды равен 100h.

Так происходит потому, что ДОСу первые адреса нужны для служебной информации о программе: командная строка, атрибуты и всё такое (можете посмотреть в окне[5], что там).

ДОС-программы нас уже не очень интересуют, и об этом больше говорить не будем.

А в Win-программах происходит примерно то же самое, только адрес первой машинной команды обычно находится чуть дальше. В самых простых приложениях - 00401000h. :)

Нужно просто прибавлять 100h, чтоб узнать адрес в памяти из адреса в com-файле. Так же, как вы складываете 8+100d=108d. Будет всё точно так же, 8+100h=108h.

Я легко посчитал адрес текстовой строки в оперативной памяти (010D) и заложил его в код программы.

Ведь мы знаем номер байта в файле, где начинается строка текста. 0D+100h - и вот он, искомый адрес в памяти.

Главная кнопка здесь будет F10, вам нужно нажимать только на неё (всё остальное можно делать мышкой). Эта кнопка с каждым нажатием будет выполнять текущую строку.

Вы уже понимаете, что отображается в окне[5], и также имеете представление, что в окне[3] - код программ. А вот окно [7] для вас пока ничего не означает, и это очень досадно, но мы сейчас исправимся.

Нажмите F10 один раз. Выполнится строка

mov ah,09

Эта команда означает: поместить значение 09 в регистр AH. Но в окне[7] нет регистра AH, а после выполнения этой строки изменится регистр EAX.

Было  EAX=00000000
Стало EAX=00000900

Дело в том, что регистр AH - это часть регистра AX, а он - часть регистра EAX.

Если, предположим, мы загрузили регистр EAX значением 44332211, выглядеть он будет так:

EAX=44332211
 AX=    2211
 AH=    22
 AL=      11

Один 32-разрядный регистр - это всего лишь 4 байта в процессоре.

Точно так же устроены ещё 3 регистра.

Схематично их отображают так:
EAX EBX ECX EDX
 AX  BX  CX  DX
E-частьAHALE-частьBHBLE-частьCHCLE-частьDHDL

(Каждый отдельно)

EAX (сокращение от Accumulator)
EBX (сокращение от Base)
ECX (сокращение от Counter)
EDX (сокращение от Data)

Можно, кстати, писать и строчными буквами, регистры не обидятся.

EAX, EBX, ECX и EDX - 32-битные регистры данных центрального процессора (от 386-го и по сей день). В эти регистры помещаются необходимые значения, и в них же чаще всего оказываются результаты вычислений. В 32 битах можно хранить hex-число от 00 00 00 00h до FF FF FF FFh. И это всё, что может там храниться. На назначения (Accumulator, Base, Counter, Data) в наших уроках можно вообще не обращать внимания. Когда вы освоите команды Ассемблера, вы сами поймете, почему Counter и почему Base.

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

mov  ah,9
Имя команды    операнд1,операнд2

где операнд1 - регистр AH
    операнд2 - цифра 9

Остаётся непонятной только сама команда.

MOV - это основная команда Ассемблера, она встречается в программах гораздо чаще остальных.

Для программирования самое важное понятие - переменная. На самом деле переменная лишь абстракция для удобства программирования. Такая же абстракция, как в алгебре X, Y, Z. Чтоб решить какую-нибудь задачу, мы говорим, что Y будет равен 5, а Z будет равен 3.

Так вот присвоение переменной Y значения 5 на Ассемблере будет выглядеть так:

mov Y,5

Я расскажу только об основных командах и только самое главное. Полную информацию всё равно нужно брать из справочника. Но каждая новая команда будет оформлена в моей статье почти как в справочнике.
Команда MOV
Происхождениеот англ. слова move - движение, перемена места
Форматmov приёмник , источник
ДействиеКопирует содержимое источника в приёмник.
ПримечаниеMOV не может передавать данные между двумя адресами оперативной памяти (для этой цели существуют команды MOVS)

Этого достаточно, чтоб использовать команду в своих программах. Посмотрите ещё раз на первые две строки prax01:

Имена    Операнды  комментарии
команд  

mov      ah,09   ; поместить значение 9 в регистр AH
mov      dx,010D ; поместить значение 010Dh в DX

Поняли?

Получив первую инструкцию, процессор выполнит инициализацию своего 8-битного регистра AH значением 9, после чего регистр AH будет содержать только байт 09.

При выполнении второй команды процессор поместит в свой 16-битный регистр DX значение 010Dh, после чего регистр DX будет содержать только эти два байта 01 и 0Dh.

Причём, если вы ещё раз посмотрите на устройство регистров, вы обязательно поймёте следующее:
Так как регистр DX состоит из DH и DL, то можно сказать, что после выполнения второй строки кода программы в регистре DH окажется значение 01, а в регистре DL окажется значение 0Dh.

   DH DL
DX=01 0D

Это просто, но важно! В 16-битный регистр (AX,BX,CX,DX) нельзя положить значение больше двух байт (FFFFh).

А в 8-битный (AH,AL, BH,BL, CH,CL, DH,DL) нельзя положить больше байта, то есть FFh.

И еще, допустим:

EAX=99884433
 AX=    4433
 AH=    44  
 AL=      33

Вы должны понять, что физически есть только 4 байта (99 88 44 33h). По отдельности можно обращаться к AX за значением 4433h, или к AH за 44h, или к AL за 33h. Но 9988h находится в E-части, а у неё нет собственного имени, она не является подрегистром. Вы не можете прочитать или загрузить 2 старших байта такого регистра, не обратившись ко всему регистру. Пример:

mov  EAX, 0FFFFFFFFh  ; Так правильно, и EAX будет равен FFFFFFFF
mov  EAX, 01FFFFFFFFh ; Так НЕправильно. Значение больше регистра,
                      ; данной операции быть не может
mov  EAX, 0           ; Так правильно, и EAX станет равен 00000000
mov  AX,  0FFFFh      ; Так правильно, и EAX будет равен 0000FFFF
mov  AX,  1FFFFh      ; Так НЕправильно. Значение больше регистра,
                      ; данной операции быть не может
mov  AX,  0           ; Так правильно, и AX станет равен 0000
mov  AH,  111h        ; Так НЕправильно. Значение больше регистра,
                      ; данной операции быть не может
mov  AL,  100h        ; Так НЕправильно. Значение больше регистра,
                      ; данной операции быть не может
mov  AL,  0BBh        ; Так правильно, и EAX станет равен 000000BB
mov  AH,  AL          ; так правильно, и EAX станет равен 0000BBBB

Это, думаю, понятно. А к старшей части EAX отдельно обращаться можно, например, при помощи команд сдвига битов:

shl EAX,10h           ; Сделает теперь регистр EAX равным BBBB0000
shr EAX,10h           ; А эта команда сделает его обратно равным 0000BBBB

О командах сдвига потом напишу подробно, сейчас я их привёл, только чтоб ответить на вопрос, как можно отдельно читать/записывать 2 старших байта E-регистров.

Итак, мы "познакомились" с четырьмя регистрами общего назначения (РОН), и есть ещё четыре.

Регистры-указатели

Они тоже входят в группу РОН. В этой программе нам они безразличны, но далее мы научимся работать и с ними. Причём на практике всё будет очень похоже на регистры данных. Да и вообще отличий между ними совсем немного. Главное отличие в том, что в регистрах-указателях нет подрегистров, есть только одна вложенная часть.

ESP=44332211   (Extended Stack Pointer)
 SP=    2211   (Stack Pointer)

Мы опять видим четыре байта, ведь все E-регистры 32-разрядные (32 бита - это 4 байта).

EBP включает в себя BP   (Base Pointer)  указатель базы
ESP включает в себя SP   (Stack Pointer) указатель стека
ESI включает в себя SI   (Source Index)  индекс источника
EDI включает в себя DI   (Deliver Index) индекс приёмника

Эти страшные слова "указатель базы", "индекс" на самом деле для нас практически ничего не означают. Вы можете забыть о назначении этих регистров и совершенно свободно использовать регистры ESP, EBP, ESI, EDI, как вам захочется. Только нужно сохранять их содержимое на время использования и восстанавливать обратно. Поэтому сейчас не забивайте голову индексами, базами и стеками. Хотя как раз про стек мы скоро поговорим.

Я уже рассказал почти обо всех регистрах, с которыми нам придётся иметь дело. Остались 2 специальных регистра: EIP и E-flags.

Регистр адреса текущей машинной команды - EIP

Процессор берёт из памяти машинную команду и увеличивает текущий адрес так, чтобы он указывал на следующую команду. Именно для этого и существует EIP.

EIP=44332211   (Extended Instruction Pointer)
 IP=    2211   (Instruction Pointer)

И опять посмотрите на первые две строки кода в отладчике:

   Адрес  Байты    Имена    Операнды

    0100: B409     mov      ah,009
    0102: BA0D01   mov      dx,0010D

Обратите внимание на столбик с байтами. Первая инструкция занимает в памяти два байта (B4h - байт кода операции, 09 - значение в команде). А вторая уже 3 байта, так как значение больше. Есть инструкции, которые занимают аж 15d байт.

Упростив процес выполнения машинной команды, можно сказать так: процессор знает, что если в первом байте содержится код операции 1011 0???b (от B0h до B7h), то это означает, что машинная команда занимает два байта. Тогда он увеличивает значение регистра EIP на два. Затем, выполнив саму маш.команду, он берёт следующий байт по адресу, указанному регистром EIP, и, узнав, что там инструкция с опкодом 1011 1???b (от B8h до BFh), увеличивает значение EIP на три. И так далее.

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

Есть такие команды Ассемблера, цель которых просто изменить регистр EIP, например, jmp (от слова jump - прыгать). Допустим, ЦП получает машинную команду EB 10h, тогда он выполняет изменение EIP и сразу же приступает к выполнению следующей инструкции - той, что теперь будет указана в EIP.

Теоретически все это запомнить очень сложно и, самое главное, не нужно. Потому что на практике всё станет просто как 2х2.

Мы ещё не обсудили регистр Eflags, который для нас будет очень важен. Говорить о нём без примера программы, мне кажется, глупо. Скоро и до него доберёмся.

Также вы очень часто будете натыкаться на сегментные регистры (CS,DS,ES,FS,GS,SS). Они фигурируют во всех справочниках, отображаются в отладчиках и даже указываются в командах (например: mov byte ptr DS:[EBX],01).

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

На сегодняшнем этапе развития процессоров и операционных систем понимание сегментации можно отложить до следующего витка.

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

Проведите prax01.com по отладчику CodeView самостоятельно (клавишей F10). Повторите этот процесс несколько раз, посмотрите, как будут меняться регистры. Найдите в окне памяти[5] код и данные программы. Чем больше времени вы будете проводить в отладчике, тем быстрее вы станете толковым программистом.

Я очень хорошо помню своё обучение. Самые сложные дни уже позади, ещё пару дней будет трудно и непонятно.

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


Bitfry

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

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


- CodeView DOS'овый отладчик, он не любит длинных, русских или сложных путей, поэтому рекомендую держать его папку в корне диска.

Форточки будут тормозить, пока программа в отладчике. И другие вы загрузить не сможете. Похожие фокусы есть во всех дебагерах - такой уж класс программ.

Кроме того, ДОС-программы в хрюшке сами по себе здорово тормозят машину. Но для нас это не критично... Я надеюсь :)

Чтоб в CodeView всё выглядело именно так, нужно включить в "Options" пункт "32-bit registers". И для правильного отображения нужно выключить "Screen Swap" (если брали CV у меня - всё уже настроено).

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

Опять же земля не плоская, но для нас в отладчике - это так. :)

Hosted by uCoz