Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29197

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

Глава 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; 


background image

 

Часть 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


background image

Глава 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


background image

 

Часть 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. 


background image

Глава 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' не ставят. Это надо иметь в виду.