Найти в Дзене
Junior Coder

Ассемблер. Регистры

Регистры - это маленькие ячейки памяти расположенные непосредственно на самом процессоре компьютера, поэтому, скорость обращения к ним очень высока и почти равна тактовой частоте процессора. То есть, если тактовая частота вашего процессора равна 3000 Гц, то скорость выполнения операций будет больше 2500 в секунду, именно поэтому ассемблер был и будет самым быстрым языком программирования. В этой статье мы будем рассматривать 16-ти битный ассемблер архитектуры 8086 на которой работают DOS-овские программы - он проще чем, скажем, ARM архитектура на которой написаны программы для смартфонов, 8086 позволяет лучше понять, как работает память и "железо" компьютера в отличии от 32-х битного ассемблера, на котором работают ОС Windows и Linux. 16-й режим называют "реальным режимом" потому, что он напрямую обращается к аппаратуре компьютера, а 32-й называется "защищенным режимом", здесь прямой доступ пользователя к аппаратуре закрыт и осуществляется через операционную систему которая установлена
Фото в свободном доступе.  Автор Andrew Dawes.
Фото в свободном доступе. Автор Andrew Dawes.

Регистры - это маленькие ячейки памяти расположенные непосредственно на самом процессоре компьютера, поэтому, скорость обращения к ним очень высока и почти равна тактовой частоте процессора. То есть, если тактовая частота вашего процессора равна 3000 Гц, то скорость выполнения операций будет больше 2500 в секунду, именно поэтому ассемблер был и будет самым быстрым языком программирования.

В этой статье мы будем рассматривать 16-ти битный ассемблер архитектуры 8086 на которой работают DOS-овские программы - он проще чем, скажем, ARM архитектура на которой написаны программы для смартфонов, 8086 позволяет лучше понять, как работает память и "железо" компьютера в отличии от 32-х битного ассемблера, на котором работают ОС Windows и Linux. 16-й режим называют "реальным режимом" потому, что он напрямую обращается к аппаратуре компьютера, а 32-й называется "защищенным режимом", здесь прямой доступ пользователя к аппаратуре закрыт и осуществляется через операционную систему которая установлена на компьютере.

Пользовательские регистры.

Пользовательский 16-ти битный регистр состоит из двух частей по 8 бит, которые имеют свои "имена", для примера возьмем регистр AX, он состоит из старшего байта AH и младшего AL, то есть если представить схематично, то выглядит он примерно так:

________AX_________          

___AH________AL____

00000000  00000000

Старший бит находится слева, а младший самый правый.

32-х битный регистр будет выглядеть так:

__________________EAX_________________

____________________________AX________

_______________________AH_______AL____

00000000 00000000 00000000 00000000

то есть старшие два байта не имеют своих названий. Он получает дополнительную букву "E", что означает "extension", или "расширенный" в переводе с английского.

Сейчас компьютеры работают уже на 64-х битных процессорах, здесь регистр называется уже RAX, который включает в себя четыре новых байта не имеющих имен, EAX, AX, AH и AL соответственно. Я не буду его изображать - слишком много нулей.

Значение в регистр грузится с помощью команды MOV (от англ. "move" - переместить), причем значение перемещается справа налево через запятую, например загрузим в регистр AX значение 256:

MOV AX, 256

теперь AX = 256

загрузим значение находящееся в AX в другой регистр - BX:

MOV BX, AX

AX = 256

BX = 256

нередко в ассемблере бывает необходимо загрузить битовое значение:

MOV AX, 1000100000010110b

еще чаще используются шестнадцатеричные значения:

MOV AX, 5Ah

если строковое значение впереди, например A5h, то перед ним ставится "0", чтобы компилятор понял, что это цифра:

MOV AX, 0A5h

Пользовательские регистры:

AX (AH, AL) - аккумулятор и арифметический регистр

BX (BH, BL) - регистр базы

CX (CH, CL) - счетчик

DX (DH,DL) - регистр данных

это не значит, что, например, CX должен обязательно использоваться как счетчик, вы можете загрузить в него какое-то свое значение и использовать его для арифметических операций, но этот регистр "связан" с командами LOOP и REP, поэтому если вы организуете цикл в своей программе, то этот регистр желательно оставлять для использования как счетчик цикла, то же касается и других регистров. Здесь надо отметить, что есть команды "вшитые" в процессор, они называются опкоды (opcode) и определенные регистры связаны с определенными командами на уровне архитектуры процессора. Опкоды можно скачать из интернета, запрос лучше производить на английском - получите больше информации.

Сегментные регистры.

Размер сегмента памяти может быть от 16 до 65536 байт (16 х 16 = 256, 256 х 256 = 65536), так как отсчет в компьютере начинается с нуля, то первое значение будет = 0, последнее = 65535. Для того, чтобы получить доступ к каким-то значениям в памяти, вся память разбита на сегменты, а чтобы получить доступ к самим сегментам, существуют сегментные регистры и регистры указатели. Сегментный регистр всегда указывает на начало сегмента, а регистр указатель на смещение относительно этого регистра. "offset" - смещение, запомните это слово, если вы будете изучать программирование, или просто интересуетесь компьютерами, то оно будет часто вам встречаться.

Сегментный регистр и его смещение принято записывать так:

0000:0000

т.е.:

сег.регитр : смещение в указателе

например возьмем регистр ES, он как бы "связан" с указателем DI. Рассмотрим ситуацию которая часто встречается в программировании на ассемблере - мы хотим записать в видеопамять некоторое значение. Текстовая видеопамять начинается с адреса B800h, мы хотим записать значение по адресу B800:0056h, нам надо учитывать, что к сегментным регистрам нельзя обращаться напрямую, поэтому обращаемся к нему через регистр AX (опять-таки архитектура процессора):

MOV AX, 0B800H

MOV ES, AX

XOR  DI, DI

MOV DI, 56H

Обратите внимание на строку XOR DI,DI - она обнуляет регистр к которому обращается (логическое вычитание - позже мы разберемся как оно работает). Обнуление необходимо для того, чтобы затереть предыдущее значение. Например такая ситуация:

 AX = 3415H ; AX содержит 3415H

мы грузим:

MOV AL, 3

значение загрузится в AL, в AH останется 34H, в итоге значение:

AX = 3403H

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

Сегментные регистры:

CS - (code segment) этот регистр указывает на начало сегмента содержащего код выполняемой программы, а регистр IP - указатель инструкции (instruction pointer) указывает на адрес инструкции, которая должна быть выполнена следующей, то есть как-то так:

CS = сегмент 8561h (например)

8561:0000  -начало кода

....

8561: IP = 0100  MOV AX, 0003h

IP указывает на адрес 8561:0100, после выполнения инструкции, находящейся по этому адресу (MOV AX,0003h), значение находящееся в IP увеличится на два байта и будет указывать на следующую инструкцию находящуюся по адресу 8561:0102 (INT 10h) и т.д.

8561:0102      INT 10h

8561:0104      MOV AX,0500h

DS - (data segment) указывает на начало сегмента данных. Используется он в паре с пользовательским регистром DX и зачастую с регистром-указателем SI - индекс источника (source index).

SS - (stack segment) указывает на начало (вернее конец) сегмента стека и используется вместе с регистром-указателем SP - указатель стека (stack pointer), этот регистр работает примерно также как и регистр IP в сегменте кода, и регистром-указателем BP - указатель базы (base pointer). Здесь надо отметить, что стек растет снизу вверх, т. е., примерно так:

....

и т.д.

SS:FFFA

SS:FFFC

SS:FFFE

ES - (extra segment) указывает на начало дополнительного пользовательского сегмента, используется обычно с регистром-указателем DI - индекс приемника (destination index)

Сегментные регистры FS и GS появились в Windows, но ничто не мешает вам использовать их в своих 16-х программах, работают они также как и регистр ES.

Регистры DI, SI и BP можно использовать как обычные пользовательские регистры.

Флаговый регистр.

FLAGS - регистр флагов или как его еще называют - регистр признаков, меняет значение своих битов в зависимости от результата той или иной операции. Получить значения битов флагового регистра можно так:

PUSHF 

копируем значения регистра в стек

POP AX

возвращаем эти значения в регистр AX

Опишу некоторые значения этого регистра:

Бит 0: CF (Carry flag) - принимает значение 1 если в результате какой-либо арифметической операции произошло переполнение регистра-приемника, например, в случае если к 0FFFFh прибавить 1, то значение уже не помещается в регистре.

Бит 2: PF (Parity flag) - флаг четности, принимает значение 1 если значение младшего байта регистра четное, и 0 если нет.

Бит 4: AF (Auxiliary carry flag) - флаг вспомогательного переноса, применяется при работе с BCD-числами (упакованное число), это отдельная тема для разговора - его надо рассматривать при изучении упакованных и не упакованных чисел.

Бит 6: ZF (Zero flag) - флаг нуля, устанавливается в том случае, если результат операции равен нулю, например, в результате уменьшающегося цикла (декремента) в регистре CX устанавливается значение 0. 

Бит 7: SF (Sign flag) - флаг знака (плюс или минус), всегда равен старшему (самому левому) биту результата операции: 1 - минус, 0 - плюс.

Бит 8: TF (Trace flag) - флаг трассировки (ловушка), используется 16-ти битными программами-отладчиками. Установка этого флага в 1 приводит к тому, что после каждого "шага" программы (увеличения IP регистра), происходит остановка и управление передается программе-отладчику.

Бит 9: IF (Interrupt flag) - флаг прерываний. Установка этого флага в 0 (команда CLI) приводит к тому, что компьютер перестает реагировать на внешние устройства и программа выполняется до того момента, пока в программе не встретится команда STI - включение прерываний.

Бит 10: DF (Directory flag) - флаг направления, используется при работе со строками и устанавливает направление чтения: слева - направо (0) и справа - налево(1).

Бит 11: OF (Overflow flag) - флаг переполнения, устанавливается в случае, если в результате операций со знаковыми числами (положительные, отрицательные) произошло переполнение. То есть он устанавливается в 1 в случае если при сложении двух положительных чисел старший бит (самый левый) равен 1, дело в том, что в положительных числах старший бит равен нулю, в отрицательных равен 1-му.

PS. Старался описывать все наиболее доступно и полно, незнаю как это у меня получилось. Желаю всем любителям успехов в программировании!

#регистры #ассемблер #программирование