Дневник чайника=) Ассемблер 3 часть

Тема в разделе "Другие языки", создана пользователем pawel, 02.02.16.

Дальше писать

  1. да

  2. нет

Результаты будут видны только после голосования.
  1. pawel

    pawel Участник

    Сообщения:
    13
    Репутация:
    5
    Рейтинг:
    +1 / -0
    Регистры процессора.
    Итак, почему же ah?

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

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

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


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

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

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

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

    • Приём данных от пользователя (данные передаются в регистры).
    • Вычисления (загрузили в регистр число и дали команду, как его обработать).
    • Приём данных из устройств (опять они - регистры).
    • Изменение состояния ЦП и периферийных устройств (только регистры! Например, регистр флагов).
    • Вывод данных на устройства.
    • Передача данных другим программам.
    И т.д.

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

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

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

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


    Загрузите prax01.com в отладчик CodeView. Для этого скопируйте файл в каталог отладчика (

    Ссылки могут видеть только зарегистрированные пользователи. Зарегистрируйтесь или авторизуйтесь для просмотра ссылок!

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

    [​IMG]
    • В окне 3 - дизассемблированный код.
    • В окне 5 - hex-байты. Их адреса (слева) и символы, которые они означают (справа).
    • В окне 7 - регистры процессора.
    • 9 - командная строка.

    Итак, смотрим на код программы в памяти:
    [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-часть
    AH AL E-часть BH BL E-часть CH CL E-часть DH DL
    (Каждый отдельно)

    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] код и данные программы. Чем больше времени вы будете проводить в отладчике, тем быстрее вы станете толковым программистом.

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

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