Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29197
Скачиваний: 1689
Глава 3. Сегментация памяти в реальном режиме
25
"В таком случае, почему на экране мы видим набор символов (текст, предложе-
ния, слова), а не "единички-нолики"?" — спросите вы. Чтобы удовлетворить ваше
любопытство, забежим немного вперед и отметим, что всю работу по выводу само-
го символа (а не битов) на экран выполняет видеокарта (видеоадаптер), которая
находится в вашем компьютере. И если бы ее не было, то мы, естественно, на экра-
не ничего бы не увидели.
В ассемблере после двоичного числа всегда должен стоять символ b. Это нужно
для того, чтобы в процессе обработки нашего файла ассемблер-программа смогла
различать десятичные, шестнадцатеричные и двоичные числа. Например: 10 — это
десять, 10h — это шестнадцать, а 10b — это два. Таким образом, в регистры можно
загружать двоичные, десятичные и шестнадцатеричные числа. Например:
...
mov ax,20
mov bh,10100b
mov cl,14h
...
В результате в регистрах
ax
,
bh
и
cl
будет находиться одно и то же число, толь-
ко загружаем мы его, используя разные системы счисления. В компьютере же оно
будет храниться в двоичном формате (как в регистре
bh
).
Итак, подведем итог. В компьютере вся информация хранится в двоичном фор-
мате (двоичной системе) примерно в таком виде: 10101110 10010010 01111010
11100101 (естественно, без пробелов; для наглядности мы разделили байты). Во-
семь бит — это один байт. Один символ занимает один байт, т. е. восемь бит. По
идее, ничего сложного нет. Очень важно уяснить данную тему, т. к. мы будем по-
стоянно пользоваться двоичной системой, и вам необходимо знать ее на "отлично".
В принципе, даже если что-то не совсем понятно, то — не отчаивайтесь! Со време-
нем все станет на свои места.
3.1.1. Как перевести двоичное число в десятичное
Чтобы перевести двоичное число в десятичное, надо сложить двойки в степенях,
показатели которых соответствуют позициям единиц в двоичном числе.
Например, возьмем число 20. В двоичной системе оно имеет следующий вид:
10100b.
Итак, начнем слева направо, считая от 4 до 0. Число в нулевой степени всегда
равно единице:
10100b = 2
4
+ 0 + 2
2
+ 0 + 0 = 16 + 8 = 20
либо
10100b = 1 16 + 0 8 + 1 4 + 0 2 + 0 1 = 16 + 0 + 4 + 0 + 0 = 20.
3.1.2. Как перевести десятичное число в двоичное
Можно делить его на два, записывая остаток справа налево:
20/2 = 10, остаток 0;
10/2 = 5, остаток 0;
Часть I. Знакомьтесь: ассемблер
26
5/2 = 2, остаток 1;
2/2 = 1, остаток 0;
1/2 = 0, остаток 1.
В результате получаем: 10100b = 20.
3.1.3. Как перевести шестнадцатеричное число
в десятичное
В шестнадцатеричной системе номер позиции цифры в числе соответствует сте-
пени, в которую надо возвести число 16:
8Ah = 8 16 + 10 (0Ah) = 138.
В настоящий момент существует множество калькуляторов, которые могут счи-
тать и переводить числа в разных системах счисления. Например, программа Каль-
кулятор в инженерном виде в Windows. Очень удобен калькулятор и в DOS
Navigator. Если он у вас есть, то отпадает необходимость в ручном переводе одной
системы в другую, что, естественно, упростит работу. Однако знать этот принцип
крайне важно!
3.2.
Сегментация памяти в реальном режиме
Возьмем следующее предложение: "Изучаем сегменты памяти". Теперь давайте
посчитаем, на каком месте стоит буква "ы" в слове "сегменты" от начала предло-
жения, включая пробелы... На шестнадцатом. Подчеркнем, что мы считали от на-
чала предложения.
Теперь немного усложним задачу и разобьем предложение, как показано в при-
мере 3.1 (символом "_" обозначен пробел).
Пример 3.1
0000: Изучаем_
0010: сегменты_
0020: памяти
0030:
В слове "Изучаем" символ "И" стоит на нулевом месте; символ "з" на первом,
"у" на втором и т. д. В данном случае мы считаем буквы, начиная с нулевой пози-
ции и используя два числа. Назовем их сегментом и смещением. Тогда символ "ч"
будет иметь следующий адрес:
0000:0003
, т. е. сегмент
0000
, смещение
0003
.
В слове "сегменты" будем считать буквы, начиная с десятого сегмента, но с ну-
левого смещения. Тогда символ "н" будет иметь следующий адрес:
0010:0005
, т. е.
пятый символ, начиная с десятой позиции:
0010
— сегмент,
0005
— смещение.
В слове "память" считаем буквы, начиная с сегмента
0020
и также с нулевой по-
зиции. Таким образом, символ "а" будет иметь адрес
0020:0001
, т. е. сегмент
0020
,
смещение
0001
.
Глава 3. Сегментация памяти в реальном режиме
27
Итак, мы выяснили, что для того, чтобы найти адрес нужного символа, необхо-
димы два числа: сам сегмент и смещение внутри этого сегмента. В ассемблере сег-
менты хранятся в сегментных регистрах:
cs
,
ds
,
ss
,
es
(см. разд. 2.1.3), а смещения
могут храниться в других (но не во всех):
регистр
cs
служит для хранения сегмента кода программы (code segment — сег-
мент кода);
регистр
ds
— для хранения сегмента данных (data segment — сегмент данных);
регистр
ss
— для хранения сегмента стека (stack segment — сегмент стека);
регистр
es
— дополнительный сегментный регистр, который может хранить ад-
рес любого другого сегмента (например, видеобуфера).
Давайте попробуем загрузить в пару регистров
es:di
сегмент и смещение буквы
"м" в слове "памяти" из примера 3.1. Вот как это будет выглядеть на ассемблере
(пример 3.2).
Пример 3.2
...
(1) mov ax,0020
(2) mov es,ax
(3) mov di,2
...
Теперь в регистре
es
находится сегмент с номером 20, а в регистре
di
— сме-
щение к букве (символу) "м" в слове "памяти". Проверьте, пожалуйста...
В этом месте стоит отметить, что загрузка числа (номера любого сегмента) на-
прямую в сегментный регистр запрещена. Поэтому мы в строке (1) сперва загрузи-
ли номер сегмента в
ax
, а в строке (2) поместили число 20 из регистра
ax
в
es
.
mov ds,15 ;ошибка!
mov ss,34h ;ошибка!
Когда мы загружаем программу в память, она автоматически располагается в
первом свободном сегменте. В файлах типа COM все сегментные регистры по
умолчанию инициализируются для этого сегмента (устанавливаются значения,
равные тому сегменту, в который загружена программа). Это можно проверить при
помощи отладчика. Если, например, мы загружаем программу типа COM в память,
и компьютер находит первый свободный сегмент с номером
5674h
, то сегментные
регистры будут иметь следующие значения:
cs = 5674h
ds = 5674h
ss = 5674h
es = 5674h
Иными словами:
cs = ds = ss = es = 5674h
.
Код программы типа COM должен начинаться со смещения
100h
. Для этого мы,
собственно, и ставили в наших прошлых примерах программ оператор
org 100h
,
Часть I. Знакомьтесь: ассемблер
28
указывая ассемблеру при ассемблировании использовать смещение
100h
от начала
сегмента, в который загружена наша программа (позже мы рассмотрим, почему
так). Сегментные регистры, как уже упоминалось, автоматически принимают зна-
чение того сегмента, в который загрузилась наша программа. Пара регистров
cs:ip
задает текущий адрес кода.
3.2.1. Исследование программы в отладчике
Теперь рассмотрим, как все это происходит, на конкретном примере (лис-
тинг 3.1).
Листинг 3.1. Исследование программы в отладчике
(01) CSEG segment
(02) org 100h
(03) _start:
(04) mov ah,9
(05) mov dx,offset String
(06) int 21h
(07) int 20h
(08) String db 'Test message$'
(09) CSEG ends
(10) end _start
Итак, строки (01) и (09) описывают сегмент:
CSEG
(даем имя сегменту)
segment
(оператор ассемблера, указывающий, что имя
CSEG
— это название сегмента);
CSEG ends
(
END Segment
— конец сегмента) указывает ассемблеру на конец сегмента.
Строка (02) сообщает о том, что код программы будет располагаться, начиная со
смещения
100h
. По этому адресу в память всегда загружаются программы типа COM.
Запускаем программу из листинга 3.1 под отладчиком AFD Pro. Допустим, она
загрузилась в свободный сегмент
1DF1h
(рис. 3.1). Первая команда в строке (04)
будет располагаться по такому адресу:
1DF1h:0100h (т. е. cs = 1DF1h, а ip = 0100h)
Обратите внимание на регистры
cs
и
ip
!
Перейдем к следующей команде. Для этого в отладчике AFD нажмите клавишу
<F1>, в CodeView — <F8>, в другом — посмотрите, какая клавиша нужна; будет
написано что-то вроде <F8>+<Step> или <F7>+<Trace>. Теперь вы видите, что из-
менились следующие регистры (рис. 3.2):
ax
=
0900h
(точнее,
ah = 09h
, а
al = 0
, т. к. мы загрузили командой
mov ah,9
число 9 в регистр
ah
, при этом не трогая
al
. Если бы
al
был равен, скажем,
15h
,
то после выполнения данной команды в
ax
находилось бы число
0915h
);
ip
=
102h
(т. е. указывает на адрес следующей команды). Из этого можно сде-
лать вывод, что команда
mov ah,9
занимает 2 байта: 102h – 100h = 2.
Глава 3. Сегментация памяти в реальном режиме
29
Рис. 3.1. Вид программы из листинга 3.1 в отладчике AFD Pro
Рис. 3.2. В отладчике выполнена команда mov ah,9
Следующая команда (нажимаем клавишу <F8>/<F1>) изменяет регистры
dx
и
ip
. Теперь
dx
указывает на смещение строки
"Test message$"
относительно начала
сегмента, т. е.
109h
, а
ip
равняется
105h
(адрес следующей команды). Нетрудно посчи-
тать, что команда
mov dx,offset String
занимает 3 байта (105h – 102h = 3) (рис. 3.3).
Обратите внимание, что в ассемблере мы пишем:
mov dx,offset String
а в отладчике видим следующее:
mov dx,109 ;109 — шестнадцатеричное число, но CodeView и многие другие
;отладчики символ 'h' не ставят. Это надо иметь в виду.