Глава 3. Первые программы.

- Лед тронулся, господа присяж­ные заседатели.

Остап Бендер.

В данной главе мы приведем несколько программ и постараемся, как можно пол­нее объяснить их. Наша книга содержит обширный справочный материал - по функ­циям DOS и BIOS, по прерываниям, портам ввода-вывода, командам микропроцессо­ра и др. Разбирая каждую из программ, не забывайте справляться в справочнике. Че­рез некоторое время Вы почувствуете, что, пользуясь только справочником, способны самостоятельно писать программы. Произойдет чудо, и уже никто не сможет от­тащить от программирования.

Прежде чем разбирать программы, однако, взгляните на Рис. 3.1, где пред ставлена простая схема функционирования микропроцессора Intel 80868. Как видим из рисун­ка , микропроцессор поделен надве части: операционное устройство (ОУ) и шинный интерфейс (ШИ). Роль ОУ заключается в выполнении команд, в то время как ШИ под­готавливает команды и данные к выполнению. Данная схемаявляется программной и ни в коей мере не отражает детали технического функционирования процессора.

Операвдонноеустройствд содержит арифмети ройство управления (УУ) и десять регистров. Эти устройства обеспечивают выполнение команд, арифметические вычисления и логические операции. Указатель илирегистр ко­манд содержит относительный адрес той команды (относительно сегмента, на который указывает регистр CS), чья очередь сейчас выполняться. Регистр флагов содержит биты,

значения которых определяют состояние микропроцессора или результат предыдущей

арифметической команды. Значения этих битов, в частности, используются микропроцес­сором при вьтолнениикомандусловньгхпереходов. Нижерегистр флагов будетрассмот-рен более подробно. Следуетобратитьвниманиена набор регистров. Эти регистры назы-ваютрабочими. В них можно хранить данные, обмениваться этимиданными с памятью. Над данными, находящимися в этих регистрах, можно производить арифметические и логическиедействия. Все регистры двухбайтные. Однако регистры AX,BX,DX,CX обла­дают дополнительным свойством - можно оперировать однобайтовыми компонентами этих регистров. Например, наряду с регистром АХ можно оперировать регистрами AL (млад­ший байт) и АН (старший байт). Тоже можно сказать и о трех других регистрах.

Три элемента шинного интерфейса - блок управления шиной, очередь команд и сегментныерегистрыосуществляютследующиефункции: во-первых, ШИ управляет

передачей данных на операционное устройство, в память и на внешние устройства.

Во-вторых, четыре сегментных регистра управляют адресацией памяти (см. предыду­щую главу). Третья функция - управление очередью команд (буфером команд). Задача заключается том, чтобы всегда существовала непустая очередь команд, готовых для выполнения. Кстати, именно существование очереди команд нужно учитывать, если Вы задумали написать самомодифицирующуюся программу (см. главы 4,18).

Набор регистров и их разрядность изменилась, начиная с процессора 80386 (см. Главу 20).

ОУ: Операционное устройство

Нэбэр регистров

Ах = АН + AL

BX»BH-»BL

CX-Ch-»CL

DX- DH * DL

SP

3P

CI

АЛУ: Арифметико-логическое устройство

УУ : Устройство дляуправлэния

Регистр флагов

Уозатель юмоьд

111/ :1Ынный ин-єрфейс

Упразлениэ программами

CS

DS

SS

ES

Управление шшой

Нина

Э-шраОэ <оманд. Для

микропроцессора

30286 она составгяз-8 байт.

Рис. 3.1. Простая функциональная схема микропроцессора 8О86/8О88.

Итак, приступаем. Первая программа (Рис. 3.1)позволяет вводить строкусимволов, азатем выводит эту строку наэкран. Для полного уяснения принципаработы програм­мы Вам необходимо разобраться вработе функций 09Ни ОАН прерывания 21Н.

;-сегмент данных-

DSEG SEGMENT

представлена  структура  строкового буфера, /с которым работает функция ОАН MAX     DB 255 /максимальная длина строки

LEN    DB ? после ввода строки

STROKA DB 255 DUP(?)   /буфер для строки

DB? /резервный байт

ENT    DB13,10, •$•  /перевод строки DSEG ENDS

стека STSEG  SEGMENT STACK DB  60 DUP(O)

STSEG ENDS

CODSEG SEGMENT

ASSUME CS:CODSEG,  DS-.DSEG,   SS: STSEG BEGIN:

сегмент стека

MOV AX, STSEG MOV SS,AX

направляем на   сегмент данных MOV AX,DSEG MOV DS,AX строку MOV AH, OAH

LEA DX,MAX       /указываем на буфер

INT 21H

введенную строку для  вывода  по прерыванию

MOV BL,LEN

MOV ВН,0

является признаком конца строки /для функции 09Н

MOV    [BX+STROKA+1], 1 $'

строку

LEA на строку

MOV AH,09Н -

INT 21H

/теперь  печатаем введенную строку

LEA DX,STROKA    /указываем на строку

; функция 4СН прерывания 21Н осуществляет корректный ; выход в  операционную систему MOV АН, 4СН

INT 21H CODSEG ENDS

END BEGIN

Рис. 3.1. Ввод и вывод строки посредством функций 09Н и ОАНпрерывания 21Н.

Прокомментируем программу. Во-первых, рассмотрим команду MOV [BX+STROKA+ 1],Т- засылка в конец строки символа $. Откудаассемблер зна­ет, что строка находится в сегменте данных? Дело втом, что еслиявно неуказан сег­ментный регистр, то всегда предполагается, что данные находятся в том сегменте, на которыйуказывает регистр DS. Болееявноэтукомандуможнозаписатьтак—MOV DS:[BX+STROKA+1],'$'. Если же мы хотим послать данные в сегмент, на который ука­зывает другой сегментный регистр, то его надо указать явно. Например — MOV ES:[BX+5],65. Теперь насчет того, что означает BX+STROKA+1. Берется адрес, соот­ветствующий метке STROKA, и к нему прибавляется содержимое регистра ВХ, а за­тем 1. В результате мы как раз получаем адрес последнего символа введенной строки + 1. Аналогично можно сказать и о команде LEA DX,STROKA. В DX загружается адрес, соответствующий метке STROKA, относительно сегмента, адресуемого регис­тром DS; явная команда будет иметь вид LEA DX,DS:STROKA.

Во-вторых, почему мы только один раз засылаем в регистр АН номер функции -09Н? Оказывается, что при выполнении прерывания 21Н значение всех регистров не меняется, за исключением тех случаев, когда в регистры возвращаются какие-то дан­ные.

В-третьих, что означают байты 13,Ю?Это магическое сочетание для многих фун­кций вывода на экран, и принтер имееттот смысл, что следует перейти к началу следу­ющей строки.

Возможно, не все ясно из моего комментария, но прервемся на этом и перейдем к следующей программе. Вы знаете, что некоторые программы могут возвращатьв DOS кодыошибок.ЭтикодыможноопределитьвВАТСН-файлеприпомощиконструкции IFERRORLEVELNUMBER.Boзвpaтитькoдoшибки(кoдвыxoдa)мoжнocпoмoщью функции 4СН прерывания 21Н (см. Рис.3.1), если в регистр AL предварительно поме­стить этот код. Программа будет ожидать нажатия клавиши, и если нажата цифровая клавиша, то будет происходить выход в DOS с возвратом кода (естественно, из проме­жутка от О до 9). Реально код возврата может иметь значение в пределах одного байта, т.е. до 255.

CODSEG SEGMENT ASSUME   CS: CODSEG ORG 100H BEGIN:

;—вызываем функцию 0 прерывания 16Н—

/—ожидаем нажатие клавиши--MOV АН,0 ' INT 16H

;--в AL   возвращается   код ASCII-­;--проверяем нажата   ли   цифровая клавиша-­;—если нет то повторяем ввод--

СМР AL,48       ;сравнить AL и 48

JB     BEGIN       ;перейти если ниже

CMP AL,58       /сравнить AL и  5 8

JA    BEGIN       /перейти если выше /--из кода  ASCII   получаем цифру--

SUB AL,48       /вычесть из AL 48 /--выходим в DOS,   AL содержит код выхода—

MOV АН,4СН

INT 21H CODSEG ENDS

END BEGIN

Рис. 3.2. Выход в DOS с передачей кода выхода.

Как Выуже, несомненно, догадались, данная программа может быть преобразова­на к СОМ-виду. Несмотря на простоту, она может быть очень полезной при написании ВАТСН-файлов. На базе такой программы можно строить простейшие меню. Наде­юсь, что текст программы достаточно прокомментирован. Мне же хочется обратить Ваше внимание на директиву, которая появилась, начиная с самых первых программ. Это ORG 100Н. Она означает, что адреса всех команд, стоящих после этой директивы, должны вычисляться со смещением 1 ООН. Для ЕХЕ-программы такая директива из­лишняя. Для СОМ-программыона необходима. Дело здесь вот в чем. При загрузке СОМ-программы в память она размещается не с начала сегмента, а со смещением 100Н байт. В начале сегмента располагается некоторый блок служебной информации (PSP - Program Segment Prefix, т.е. префикс программного сегмента). PSP есть и у ЕХЕ-программ, но здесь при загрузке происходит дополнительная настройка адресов, по­этому проблем не возникает, a PSP имеет свой отдельный сегмент длиной 256 байт. Подробнее о PSPMbi будем говорить позднее.

Следующая программа несколько сложнее предыдущих. Онаждет нажатия клави­ши (имеющей код ASCII) и выдает ее символ. Если код расширенный, то выдается звуковой сигнал. Выйти из программы можно по нажатию CTRL-BREAK.

CODSEG SEGMENT ASSUME     CS: CODSEG

ORG 100H BEGIN:

MP BEGIN1

ENT DB 13,10,'$' /данные для перевода строки

2-4072

BEGIN1: '

/—ожидаем нажатие клавиши-'- i

MOV АН,

INT 16H

'/—проверяем: не расширенный ли код CMP AL,О

JNZ DISP

; —если код расширенный то подаем звуковой сигнал— MOV АН,02

/--функция 2 прерывания 21Н выводит символ, но

; —код 7 трактуется ей как звуковой сигнал— ■ MOV DL,7

INT 21H

/--возвращаемся к ожиданию нажатия клавиши— .

JMP BEGIN1

DISP:

; — символ выводим с  помощью функции  ОАН  прерывания   ЮН—. MOV АН,ОАН

MOV СХ, 1     '    /выводим один символ M0V ВН,0 /страница 0

INT  ЮН ;вызов прерывания

—теперь переводим строку и возвращаемся к началу— MOV АН,09 MOV DX,OFFSET ENT

INT 21H JMP BEGIN1

■/--команда выхода в ДОС здесь совсем не нужна— /—т.к. выход осуществляется по CTRL-BREAK—

CODSEG ENDS

END BEGIN

Рис. 3.3. Программа вывода символов нажатых клавиш.

Прежде всего, возникает вопрос: почему в программе используется два способа вывода символов? Дело в том функция 2 прерывания 21Н выводит лишь обычный алфавит. Кодыже меньшие 32 воспринимаются ей как команды: 13-перевод строки, 7 -звуковойсигнал, 8 - сигналтабуляцииит.п. Функция ОАН прерывания ЮНлишена этого недостатка. В частности, Вы можете получить символ, соответствующий коду 7, нажав клавиши CTR.L-G. Кстати, если Вы нажмете CTRL-C, то вместо выхода из про­граммы получите символ, соответствующий данному сочетанию.

Чтобы перейти к следующей программе, введем несколько новых понятий. Преж­де всего это стековая память. Если Вы занимались программированием, то с этим по­нятием Выуже встречались. Стек-это организация памяти по принципу: первым при­шел - последним ушел. Элементом стека является слово. В любой момент доступнотолько слово, находящееся в вершине стека. На это слово указывает регистр SP (Stack Pointer - указатель стека). Самже стек располагается в сегменте, на который в данный момент указывает сегментный регистр SS. Существуют две основные команды рабо­ты со стеком: PUSH -положить в стек, ЮР -вынуть из стека. При выполнении коман­ды PUSH (например, PUSH АХ, PUSH DXh т.п.) содержимое регистра SP уменьшает­ся на 2, а в то слово, на которое теперь указывает этот регистр, помещается содержи­мое соответствующего регистра (в нашем примере АХ или DX). При выполнении ко­манды POP (например, POP DS, POP ВХ и т.п.), содержимое SP увеличивается на 2, а в соответствующий регистр (в нашем примере DS или ВХ) кладется то слово, на кото­рое указывал до этого SP.

Кроме отмеченных команд PUSH и POP, со стеком также работают следующие команды: CALL- вызов процедуры, RET - возврат из процедуры, INT - вызов преры­вания, IRET- возврат из прерывания. Рассмотрим, например, как работает пара CALL и RET. При выполнении команды CALL, например, CALLNET, гдеЫЕТ - метка пере­хода либо имя процедуры (дляязыка ассемблера это практически одно итоже), в стек кладется адрес возврата. При выполнении же команды RET этот адрес возврата из стека извлекается, и микропроцессор переходит к выполнению команды, следующей за командой CALL. Естественно, у вдумчивого читателя может возникнуть вопрос: "Какой адрес возврата - четырехбайтовый или двухбайтовый?" Обещаю, что мы вер­немся к обсуждению данной проблемы в самое ближайшее время. Покаже будем счи­тать, что переходы происходят в пределах одного сегмента и, следовательно, адрес возврата является двухбайтной величиной, т.е. словом.

Перейдем к рассмотрению следующего примера. Эта программа позволяет ввести строку символов, а затем выводит ее в обратном порядке.

;— Сегмент данных — DSEG SEGMENT

; —структура для ввода строки (см. описание функции ОАН)

MAX DB 255

LEN DB О

STROKA    DB  256 DUP(?)

/—перевод строки

ENT     DB 13,10, '$'

DSEG ENDS

;—Сегмент стека--STSEG      SEGMENT STACK

DB  30 DUP(?) TOP     DB ? STSEG ENDS ; —Сегмент кода—

CODSEG SEGMENT

ASSUME    CS:CODSEG,  DS:DSEG,   SS:STSEG, ES:CODSEG IMP BEG

/—Процедура обращения строки--CONVERT PROC

/--подготавливаем регистры--LEA DI,STROKA MOV BL,LEN MOV ВН, О ADD BX,DI

DEC BX / теперь ВХ показывает точно на конец строки

CICL:

CMPDI,BX       /если DI=>BX то заканчиваем

JNB KONEC /—производим обмен символов—

MOV AL, [DI]

MOV АН, [ВХ]

MOV [DI],AH

MOV [BX],AL /--переходим к следующей паре символов--

INC DI

DEC ВХ

JMP CICL

CONVERT ENDP

/--Процедура вывода  строки символов—

DISP_STR PROC

/—вначале переходим к следующей строке— MOV ENT

MOV АН,09Н

INT 21H

/--подготавливаем регистры— MOV DI OFFSET STROKA

MOV

MOV АН,02Н /функция прерывания 21Н для вывода символа

KONEC:

RET

в  основную программу

PROD:

MOV DL, [DI]

INT 21H

/вывод символа из  ОБ: [01] /следующий символ /уменьшаем счетчик /если не   0  продолжаем вывод /выход в основную программу

INC DI

DEC CL

JNZ PROD

RET

DISP_STR ENDP

BEG:

сегментные

MOV

MOV

MOV AX,STSEG MOV SS,AX

MOV SP, OFFSET SS-.TOP

ввод строки

MOV MAX на  структуру для

строки

MOV ввода строки

INT 21H

CMP LEN,0 ;если строка пустая,   то выход

JZ EXIT CALL CONVERT CALL DISP_STR

EXIT:

MOV АН,4СН

INT 21H CODSEG ENDS END BEGIN

Рис. 3.4. Вывод строки в обратном порядке.

Комментируя программу на Рис.3.4, прежде всего хочу заметить, что, разумеется, если речь идет просто о выводе строки в обратном порядке, то нет нужды менять в строке порядок символов. С помощью той же функции 02Н прерывания 21Н можно вывести строку, взяв символы в обратном порядке. Вам предлагается реализовать имен­но этот подход для поставленной задачи.

В нашей программе впервые появилась такая структурная единица как процедура.

Не надо, однако, смотреть на нее как, скажем, на процедуры в Паскале или функции в Си. Фактически это лишь другой способ указания метки перехода. Программа работа­ла бы точно так же, если бы мы просто указали две метки: CONVERT и DIS_STR (разумеется, CONVERT ENDPh DIS_STR ENDP следовало бы при этом убрать). Прав­да, здесь не все так уж просто, и с дальнейшим развитием данного вопроса Вы позна­комитесь в следующей главе. По поводу программы на Рис. 3.4 замечу также, что в конце вместо END BEGIN можно было бы поставить END BEG. Тогда не понадоби­лась бы команда JMP BEG. Впрочем, о подобных тонкостях речь еще впереди.

Две следующие программы иллюстрируют свойства а также регистра ВР

(Рис. 3.5 - 3.б).

/первая программа, иллюстрирующая свойства стека

CODSEG SEGMENT

ASSUME    CS: CODSEG ORG 100Н

BEGIN:

MOV AX, 65

/в стеке слово 0041Н (65) MOV BP,SP

I4 1 * ' *

CMP WORD   PTR    [BP] , 65 ;BP как раз указывает на вершину стека,   где лежит слово ;если бы это  было не так,   то программа никогда не закончила ;бы свою работу

JNZ L1

POP ВХ

/теперь слово   0041Н находится  в  ВХ,   проверим это L2:

СМР АХ,ВХ

JNZ L2

LEA АХ,EXIT

PUSH AX

;в стек поместили смещение EXIT, /поэтому RET работает как JMP EXIT RET

JMP BEGIN

.EXIT:

MOV АН,4CH

INT 21H CODSEG ENDS

END BEGIN

Рис. 3.5. Иллюстрация свойств стека (1).

/вторая программа,   иллюстрирующая свойства стека CODE SEGMENT

ORG 10ОН

ASSUME CS:CODE

BEGIN:

MOV   AH, 2 MOV    DL, 65

PUSH AX

/следующие три команды работают как  PUSH DX

SUB    SP, 2

MOV BP,SP

MOV [BP],DX /—изменим содержимое регистров—

MOV DH,78

MOV   AH, 56

POP DX

/следующие три команды работают как POP АХ

MOV BP,SP

MOV  AX, [BP]

ADD SP,2 ;—вывод символа на экран—

INT 21H

/—передать управление операционной системе—

RET CODE ENDS

END BEGIN

Рис. 3.6. Иллюстрация свойств стека (2).

Советую подробно разобраться в этих программах. Если Вы поняли, как они рабо­тают, то считайте, что поняли, какработает стек. Напомню также, что регистр BP для стека работает так же, как ВХдля сегмента данных. В языках высокого уровня, таких, как Си и Паскаль, при организации процедур (функций) в стеке располагаются ло­кальные переменные, и доступ к ним как раз осуществляется посредством регистра BP (см. главы 11,15).

Состояние регистра флагов влияет на выполнение команд микропроцессором. Сейчас мы рассмотрим структуру регистра флагов. Регистр флагов (Рис. 3.7) состоит из 16 бит9.

15

13

12

11

10

9

8

7

5

4

3

2

1

О

 

 

 

 

OF

DF

IF

TF

SF

ZF

 

 

 

PF

 

CF

Рис. 3.7. Регистр, флагов микропроцессоров 8086/8088.

Из 1 ббитсемь зарезервированы, остальные выполняют различные функции, ко­торые мы сейчас разберем.

СР - флаг переноса. Равен 1, если произошел перенос при сложении или заем при вычитании, в противном случае он равен нулю. При выполнении операции сдвига СР содержит бит, который вышел за границуячейки или регистра. СБтакже служит ин­дикатором результата умножения.

РБ - флаг четности. Равен 1, если в результате операции получилось число с чет­ным числом единиц, и 0 - в противном случае.

АР - вспомогательный флагпереноса. Аналогичен флагу СР, но контролирует заем или перенос третьего бита.

»  Регистр флагов, начиная с процессора 80386 стал 32-битным (см. главу 20).

ZF - флаг нуля. Равен 1, если в результате операции получен нуль, и 0 - в противо­положном случае.

SF - флаг знака. Дублирует значение старшего бита результата операции. Исполь­зуется при работе с числами со знаком.

TF - флаг трассировки. Если этот бит равен 1, то после выполнения каждой опера­ции микропроцессор обращается к специальной процедуре (прерыванию). Использу­ется при отладке программы.

IF - флаг прерывания. Если данный флаг сброшен в 0, то микропроцессор не реа­гирует ни на какие внешние сигналы (сигналы прерывания). Исключение составляет немаскируемое прерывание (NMI). По линии NMI микропроцессор получает сообще­ния о таких критических ситуациях, как отключение питания и ошибка памяти.

DF- флагнаправления. Используется строковыми (цепочечными) командами. Если он сброшен, цепочка обрабатывается с первого элемента, имеющего наименьший ад­рес. В противном случае цепочка обрабатывается от наибольшего адреса к наимень­шему.

OF - флаг переполнения. Флаг равен 1, если результат сложения двух чисел с оди­наковым знаком или результат вычитания двух чисел с противоположными знаками выйдет за пределы допустимого диапазона. Флаг обращается в 1, если старший бит операнда изменился в результате операции арифметического сдвига. OF=0, если част­ное от деления двух чисел переполняет результирующий регистр.

Выполнив команды PUSHFh POP АХ (см. следующую главу), вы сможете прове­рить любой из вышеперечисленных флагов, даже если он никак не воздействует на команды условных переходов (см. следующую главу). Аналогично (PUSH AX/POPF) можно изменить любой флаг по своему усмотрению. Кстати, раз мы здесь говорим о регистре флагов, следует упомянуть один подход, который позволяет различать мик­ропроцессоры Intel. Одна модификация микропроцессора может отличаться от пре­дыдущей версии тем, что в ней появляется новый флаг. Это значит, что мы можем его изменить, а следовательно, отличить один микропроцессор от другого (например, 80386 от 80486) (см. главу 23).