Лаб2

advertisement
Лабораторная работа 2
Тема «Разработка
Ассемблера»
и
отладка
простейших
программ
на
языке
Теоретические сведения
Сегментная организация памяти ПЭВМ на базе микропроцессоров
Intel
Память представляет собой набор ячеек, каждая из которых имеет свой
уникальный номер – адрес. Команды и данные хранятся в ячейках, и их
местоположение в памяти определяется адресами соответствующих ячеек.
Поскольку команды и данные на уровне кодов неотличимы друг от друга, то
для различия команд и данных используется их размещение в различных
областях памяти – сегментах.
Сегмент - это прямоугольная область памяти, характеризующаяся
начальным адресом и длиной. Начальный адрес (адрес начала сегмента) –
это номер (адрес) ячейки памяти, с которой начинается сегмент. Длина
сегмента– это количество входящих в него ячеек памяти. Сегменты могут
иметь различную длину. Все ячейки, расположенные внутри сегмента,
перенумеровываются, начиная с нуля. Адресация ячеек внутри сегмента
ведется относительно начала сегмента; адрес ячейки в сегменте называется
смещением или эффективным адресом - EA (относительно начального
адреса сегмента). На рисунке 1 представлены примеры сегментов, указаны
адреса ячеек в памяти, входящих в сегмент, и их смещения (в квадратных
скобках) относительно начала сегмента (0100h).
В общем случае программа, размещенная в памяти, может иметь
следующие сегменты: сегмент данных для хранения операндов, сегмент кода
для хранения операторов программы и сегмент стека – дополнительную
память для временного размещения информации. Начальные адреса
сегментов помещаются микропроцессором в соответствующие сегментные
регистры, о которых пойдет речь в дальнейшем.
В зависимости от структуры данных и их местонахождении доступ к
ним может осуществляться различными способами. Режимы адресации
данных рассмотрим на примере микропроцессора Intel 8086.
Режимы адресации данных микропроцессора Intel 8086
1) Непосредственный. Данное длинной 8 или 16 бит является частью
команды. Например: выражение-константа: 12; 1010В; 08АH; ‘AB’; XX-YY5.
mov ah,12 ;переслать в регистр ah значение 12
add ax,08AH ;прибавить к содержимому регистра ax
;значение 08AH
0000h
…
0100h
0101h
0102h
0103h
[0000h]
[0001h]
[0002h]
[0003h]
Сегмент
…
Сегмент
Адрес
ячейки
в
памяти
Адрес
ячейки
в
сегмен
FFFFh
те
…
Память
Рисунок 1 – Размещение сегментов в памяти
2) Прямой. 16-битный эффективный адрес данного является частью
команды. Например: переменная  выражение - константа: CNT; CNT +5;
ARRAY -5. Это означает, что соответствующие метки (переменные)
определены предварительно в сегменте данных.
mov ah,cnt ;переслать в регистр ah значение по адресу cnt
;относительно начала сегмента данных
add ax,cnt+5 ;прибавить к содержимому регистра ax
;значение по адресу, вычисляемому как сумма
;адреса cnt и 5
3) Регистровый. Данное содержится в определяемом командой
регистре (АX,BX,CX,DX,SI,DI,SP,BP).
Например: регистр: AX; BH; DL.
mov ah,al ;переслать в регистр ah значение из регистра al
add ax,dx ;прибавить к содержимому регистра ax значение
;из регистра dx
4) Регистровый косвенный. Эффективный адрес данного находится в
базовом регистре BX или индексном регистре SI,DI:
( BX ) 


EA  (SI )  .
(DI ) 


Например: [регистр]: [BX].
mov ah,[bx] ;переслать в регистр ah значение по адресу,
;который находится в регистре bx
Таким образом, если в регистре bx находится значение 6, то в регистр
ah пересылается информация, находящаяся по смещению 0006h
относительно начала сегмента данных.
5) Регистровый относительный. Эффективный адрес равен сумме 8
или 16 битного смещения и содержимого базового или индексного
регистров:
( BX ) 
( BP )  8  offset


 
EA  


(S I )  16  offset 
(DI ) 
.
Например: переменная + [регистрвыражение константа]: CST[BX];
MES[SI+10H]; [BX-1].
mov ah,cst[si] ;переслать в регистр ah значение по адресу,
;который определяется как сумма смещения
;метки cst относительно начала сегмента
;данных и регистра si
При этом метка cst должна быть предварительно описана в сегменте
данных. Если cst имеет смещение 0002h относительно начала сегмента
данных, а в si записано значение 7h, то эффективный адрес данного
относительно начала сегмента данных составит 0009h (0002h+7h).
6) Базовый индексный.Эффективный адрес равен сумме содержимого
базового и индексного регистров, определяемых командой:
( BX )  (S I ) 
EA  


( BP )  (DI ) 
.
Например: [базовый регистр][индексный регистр] : [BX][DI].
mov ah,[bx][di] ;переслать в ah значение по адресу,
;равному сумме содержимого bx и di
Если в регистре bx записано значение 5h, а в регистре di – значение
3h, то в регистр ah пересылается информация по смещению 8h (5h+3h)
относительно начала сегмента данных.
7) Относительный базовый индексный.
Эффективный
адрес равен сумме 8 или 16 битного смещения и базово индексного адреса:
( BX )  (SI )  8  offset 
EA  



( BP )  (DI )  (16  offset ) 
.
Например: переменная + [базовый регистрвыражение
константа]
[индексный
регистрвыражение
константа]:
E[BX+5][SI-2]; DATA[BX][SI]; [BX+2][SI].
mov ah,data[bx][si] ;переслать в регистр ah значение
по
;адресу, равному сумме смещения
;метки data относительно начала
;сегмента, содержимого bx и
; содержимого регистра si
Если метка data имеет смещение 0002h, в регистре bx записано
значение 8h, а в регистре si – значение 5h, то в регистр ah передается
значение, находящееся по смещению 0015h относительно начала сегмента
данных.
Режимы адресации переходов микропроцессора Intel 8086
Команды программы размещаются в сегменте кода, начальный адрес
которого хранится в специальном сегментном регистре CS. При выполнении
текущей команды микропроцессор должен знать адрес следующей команды,
которая поступит на исполнение. Этот адрес хранится в специальном
регистре IP. Таким образом, содержимое регистров CS и IP однозначно
определяет местонахождение команды.
Последовательное выполнение команд микропроцессором может быть
изменено благодаря командам условного и безусловного переходов, а также
командам вызова процедур. Рассмотрим виды переходов и способы
вычисления адресов команд при выполнении этих переходов.
1) Внутрисегментный прямой. Эффективный адрес перехода равен
сумме смещения и текущего содержимого IP. В команде условного перехода
смещение только 8 бит (короткий переход). Например: меткавыражение
константа: Labl1+27.
jmp lab11 ;выполняется безусловный переход на команду,
;которая помечена идентификатором lab11
lab11: mov ah, bh
К текущему содержимому регистра IP прибавляется смещение метки,
которое вычисляется относительно значения регистра IP. Если текущее
значение IP равно 0005h, а смещение метки относительно текущего
значения IP составляет 0004h, то эффективный адрес перехода (смещение
следующей выполняемой команды) равен 0009h.
2) Внутрисегментный косвенный. Эффективный адрес перехода есть
содержимое регистра или ячейки памяти, указанных в любом режиме кроме
непосредственного. Значение регистра IP заменяется соответствующим
содержимым регистра или ячейки памяти. Допустим только для безусловных
переходов.
jmp [bx] ;выполняется переход на команду, адрес которой
;находится в ячейке памяти по адресу относительно
;начала сегмента данных, смещение ячейки памяти
; указано в регистре bx
Если в регистре bx хранится значение 0005h, то адрес следующей
выполняемой команды находится в ячейке памяти по смещению 0005h
относительно начала сегмента данных.
3) Межсегментный прямой. В команде указана пара
сегмент: смещение, содержимое регистра IP заменяется одной
частью команды (смещением), а содержимое CS – другой
(сегментом). Допустим только в командах безусловного перехода.
Например: меткавыражение константа: BRANCH_EXT.
call
far
quickSort
;вызывается
quickSort,
;которая находится в другом сегменте
;кода, и адрес начала этого сегмента
;помещается в cs, а смещение – в ip
процедура
Таким образом, адрес следующей выполняемой команды определяется
смещением первой команды процедуры quickSort относительно своего
сегмента кода.
4) Межсегментный косвенный. Заменяет содержимое IP и
CS содержимым двух смежных слов из памяти, определенных в
любом режиме кроме, непосредственного и регистрового. Младшее
слово помещается в IP, а старшее – в CS. Допустим только в
командах безусловного перехода.
call far [bp+4] ;выполняется переход на команду,
адрес
; которой содержится в четырех ячейках
; памяти, адреса которых начинаются со
; значения, указанного в в ячейке памяти
;по смещению bp+4
Слово состояния микропроцессора Intel 8086
Вычислительная обстановка во время выполнения программы
определяется с помощью однобитных флагов, хранящихся в специальном
регистре микропроцессора – PSW – слово состояния процессора. Все флажки
делятся на две группы: флажки условий и флажки управления.
Флажки условий:
Флажок знака SF. Равен старшему биту результата: ноль – если
результат положительный, и единица – если результат отрицательный.
Флажок нуля ZF. Устанавливается в единицу при получении нулевого
результата и сбрасывается в ноль, если результат отличается от нуля.
Флажок паритета PF. Устанавливается в единицу, если младшие 8 бит
результата содержат четное число единиц, в противном случае он
сбрасывается в ноль.
Флажок переноса CF. При сложении (вычитании) устанавливается в
единицу, если возникает перенос из младшего бита или заем из старшего
бита.
Флажок вспомогательного переноса AF. Устанавливается в единицу,
если при сложении (вычитании) возникает перенос (заем) из бита 3. Только
для двоично-десятичной арифметики.
Флажок переполнения OF. Устанавливается в единицу, если
возникает переполнение, т.е. получение результата вне допустимого
диапазона. При сложении флажок устанавливается, если имеется перенос в
старший бит и нет переноса из старшего бита и наоборот.
Флажки управления:
Флажок направления DF. Применяется в командах манипуляции
цепочками.
Флажок разрешения прерываний IF. Когда установлен этот флажок,
центральный процессор распознает маскируемые прерывания, иначе
прерывания игнорируются.
Флажок прослеживания (трассировки) TF. Когда этот флажок
установлен, после выполнения каждой команды генерируется внутреннее
прерывание.
Список сокращений и условных обозначений
Прежде
чем
перейти
к
рассмотрению
регистров
микропроцессора и основных команд языка ассемблера, следует
ознакомиться с условными обозначениями, используемыми в
дальнейшем. Перечень сокращений и условных обозначений
приведен в таблице 1.
Таблица 1 – Перечень сокращений и условных обозначений
Сокращение
Смысловое
Сокращение Смысловое значение
значение
1
2
3
4
OPR
Операнд
DATA8
8- битный непосредственный
операнд
SRC
Операнд- источник
DATA16
16- битный непосредственный
операнд
Продолжение таблицы 1
1
2
3
4
DST
Операнд- получатель
AX,BX.CX,DX
16- битные регистры
REG
Регистр
AL,AH,BL,BH
8- битные регистры
RSRC
Регистр- источник
CL,CH,DL,DH
8- битные регистры
RDST
Регистр- получатель
IP
Регистр- указатель
команды
CNT
Счетчик
SP
Регистр- указатель стека
DISP
Смещение
BP
Регистр базовый
D8
8- битное смещение
SI,DI
Регистры индексные
ADDR
Адрес
CS,SS,DS,ES
Регистры сегментные
EA
Эффективный адрес
DF,IF,TF
Флаги управляющие
SEG
Сегмент
OF,SF,ZF
Флаги условий
DATA
Непосредственный операнд
AF,PF,CF
Флаги условий
Общий формат ассемблерной команды
Метка:
Комментарий
Мнемоника
Операнд,
Операнд;
Метка представляет собой идентификатор, то есть последовательность
букв и цифр, начинающаяся с буквы. Символы метки могут разделяться
знаком подчеркивания. Все имена регистров являются зарезервированными и
их использовать в качестве метки нельзя. Они используются для указания
соответствующих регистров.
Команда (мнемоника) указывает транслятору с ассемблера какое
действие должен выполнить данный оператор. В сегменте данных команда
или псевдооператор определяет поле, рабочую область или константу.
Операнды - регистры, метки данных или непосредственные данные.
Комментарий служит для пояснения действий команды или директивы
ассемблера. Посте точки с запятой комментарий записывается на одной
строке. Для продолжения комментария на последующих строках он
записывается после точки с запятой.
Определение данных
Формат операторов резервирования и инициализации данных:
Переменная
; Комментарий
Мнемоника
Операнд,..., Операнд
Переменной (идентификатором) назначается смещение первого
резервируемого байта.
Мнемоника определяет длину каждого операнда:
1) DB (определить байт). Диапазон для целых чисел без знака: 0...255,
для целых чисел со знаком: -128…127.
2) DW (определить слово – два байта). Диапазон для целых чисел без
знака: 0...65535, для целых чисел со знаком: -32768…32767.
3) DD (определить двойное слово – четыре байта).
Диапазон для целых чисел без знака: 0...4294967295, для целых чисел
со знаком: -2147483648…2147483647.
Операнды показывают инициализируемые данные или объем
резервируемого
пространства.
Операнд
резервирует
место
без
инициализации. Выражение может содержать константу или символ ? для
неопределенного значения.
Примеры:
Data_byte
Data_word
Data_DW
Data_str
Data_str1
DB 104
DW 100H,FFH,-5
DD 5*25,0FFFDH,1
DB ‘H’,’E’,’L’,’L’,’O’
DB ‘HELLO’
При определении большого числа ячеек можно применять оператор
повторения DUP (Операнд,...,Операнд).
Примеры:
Arr
DB 30 DUP(1,2) - зарезервирует 30 однобайтовых ячеек с
начальными значениями 1 в нечетных и 2 в четных байтах;
ММ1
DB 15 DUP(14) - означает, что по адресу ММ1 находятся
15 байт содержащих шестнадцатиричную цифру 0ЕH (14 в десятичной
системе).
Arr_DW
DW 4 DUP(?) – определяет по адресу Arr_DW 4 ячейки,
содержащих произвольную информацию.
Операнды могут задаваться в различных системах счисления: для
двоичной после значения операнда ставится символ B, для
шестнадцатеричной – символ H, для десятичной – ничего.
Примеры:
Data_word DW 100H
Met DB 01010011B
ARG
DB 15
Регистры процессора Pentium
Пользовательские регистры. Они
называются так потому, что
программист может использовать их при написании своих программ. К этим
регистрам относятся:
1) восемь 32-битных регистров, которые могут использоваться
программистами для хранения данных и адресов (их еще называют
регистрами общего назначения -РОН):
1.1) EAX – 32 бита/ AX – 16 бит/ AH/ AL – по 8 бит;
1.2) EBX – 32 бита/ BX – 16 бит/ BH/ BL – по 8 бит;
1.3) EDX – 32 бита/ DX – 16 бит/ DH/ DL – по 8 бит;
1.4) ECX – 32 бита/ CX – 16 бит/ CH/ CL – по 8 бит;
1.5) EBP – 32 бита/ BP – 16 бит;
1.6) ESI – 32 бита/ SI – 16 бит;
1.7) EDI – 32 бита/DI – 16 бит;
1.8) ESP – 32 бита/ SP – 16 бит.
2) шесть регистров сегментов: CS, DS, SS, ES, FS, GS по 16 бит;
3) регистры состояния и управления:
3.1) регистр флагов EFLAGS – 32 бита/ FLAGS – 16 бит;
3.2) регистр указателя команды EIP – 32 бита/ IP – 16 бит.
Микропроцессоры i486 и Pentium имеют в основном 32-разрядные
регистры. Их количество, за исключением сегментных регистров, такое же,
как и у i8086, но размерность больше, что и отражено в их обозначениях они имеют приставку E (Extended).
Перечислим регистры, относящиеся к группе регистров общего
назначения. Так как эти регистры физически находятся в микропроцессоре
внутри арифметико-логического устройства (АЛУ), то их еще называют
регистрами АЛУ:
1) EAX/AX/AH/AL (Accumulator register) - аккумулятор. Применяется
для хранения промежуточных данных. В некоторых командах использование
этого регистра обязательно. Например, при выполнении операций
умножения и деления используется для хранения первого числа,
участвующего в операции, и результата операции после ее завершения.
2) EBX/BX/BH/BL (Base register) - базовый регистр.
Применяется для хранения базового адреса некоторого объекта в
памяти (например, массивов).
3) ECX/CX/CH/CL (Count register) - регистр-счетчик.
Применяется в командах, производящих некоторые повторяющиеся
действия. Его использование зачастую неявно и скрыто в алгоритме
работы соответствующей команды. К примеру, команда
организации цикла loop кроме передачи управления команде,
находящейся по некоторому адресу, анализирует и уменьшает на
единицу значение регистра ECX/CX;
4) EDX/DX/DH/DL (Data register) - регистр данных. Так же,
как и регистр EAX/AX/AH/AL, он хранит промежуточные данные.
В некоторых командах его использование обязательно; для
некоторых команд это происходит неявно. Используется как
расширение регистра- аккумулятора при работе с 32- разрядными
числами.
Следующие два регистра используются для поддержки
цепочечных операций, то есть операций, производящих
последовательную обработку цепочек элементов, каждый из
которых может иметь длину 32, 16 или 8 бит:
1) ESI/SI (Source Index register) - индекс источника. Этот
регистр в цепочечных операциях содержит текущий адрес элемента
в цепочке-источнике;
2) EDI/DI (Destination Index register) - индекс приемника
(получателя). Этот регистр в цепочечных операциях содержит
текущий адрес в цепочке-приемнике.
В архитектуре микропроцессора на программно-аппаратном
уровне поддерживается такая структура данных, как стек. Для
работы со стеком в системе команд микропроцессора есть
специальные команды, а в программной модели микропроцессора
для этого существуют специальные регистры:
1) ESP/SP (Stack Pointer register) - регистр указателя стека.
Содержит указатель вершины стека в текущем сегменте стека.
2) EBP/BP (Base Pointer register) - регистр указателя базы
начального адреса поля памяти, непосредственно отведенного под
стек. Предназначен для организации произвольного доступа к
данным внутри стека.
Большинство
из
перечисленных
регистров
могут
использоваться при программировании для хранения операндов
практически в любых сочетаниях. Но некоторые команды
используют фиксированные регистры для выполнения своих
действий. Это нужно обязательно учитывать.
В программной модели микропроцессора имеется шесть
сегментных регистров: CS, SS, DS, ES, GS, FS. Фактически в этих
регистрах содержатся адреса памяти с которых начинаются
соответствующие сегменты. Логика обработки машинной команды
построена так, что при выборке команды, доступе к данным
программы или к стеку неявно используются адреса во вполне
определенных
сегментных
регистрах.
Микропроцессор
поддерживает следующие типы сегментов:
1) Сегмент кода. Содержит команды программы. Для доступа
к этому сегменту служит регистр CS (code segment register) сегментный регистр кода. Он содержит адрес сегмента с
машинными командами, к которому имеет доступ микропроцессор
(то есть, эти команды загружаются в конвейер микропроцессора).
2) Сегмент данных. Содержит обрабатываемые программой
данные. Для доступа к этому сегменту служит регистр DS (data
segment register) - сегментный регистр данных, который хранит
адрес сегмента данных текущей программы.
3) Сегмент стека. Этот сегмент представляет собой область
памяти, называемую стеком. Работу со стеком микропроцессор
организует по следующему принципу: последний записанный в эту
область элемент выбирается первым. Для доступа к этому сегменту
служит регистр SS (stack segment register) - сегментный регистр
стека, содержащий адрес сегмента стека.
4) Дополнительный сегмент данных. Неявно алгоритмы
выполнения большинства машинных команд предполагают, что
обрабатываемые ими данные расположены в сегменте данных,
адрес которого находится в сегментном регистре DS. Если
программе недостаточно одного сегмента данных, то она имеет
возможность использовать еще три дополнительных сегмента
данных. Но в отличие от основного сегмента данных, адрес
которого содержится в сегментном регистре DS, при
исфпользовании дополнительных сегментов данных их адреса
требуется указывать явно с помощью специальных префиксов
переопределения сегментов в команде. Адреса дополнительных
сегментов данных должны содержаться в регистрах ES, GS, FS
(extension data segment registers).
В микропроцессор включены несколько регистров, которые
постоянно содержат информацию о состоянии, как самого
микропроцессора, так и программы, команды которой в данный
момент загружены на конвейер. К этим регистрам относятся:
1) регистр флагов EFLAGS/FLAGS;
2) регистр указателя команды EIP/IP.
Младшая часть регистра EFLAGS полностью аналогична
регистру FLAGS (слово состояния процессора, описанного выше)
для i8086.
Регистр EIP/IP содержит смещение следующей подлежащей
выполнению команды относительно содержимого сегментного
регистра cs в текущем сегменте команд. Этот регистр
непосредственно недоступен программисту, но загрузка и
изменение его значения производятся различными командами
управления, к которым относятся команды условных и
безусловных переходов, вызова процедур и возврата из процедур.
Возникновение прерываний также приводит к модификации
регистра EIP/IP.
Пример простой программы на ассемблере
Изучение языка ассемблера целесообразнее всего начать с разработки
простой программы, например, такой:
text segment
;(1)Начало сегмента команд
assume CS:text,DS:data
;(2)Сегментный регистр CS будет указывать на
сегмент
;команд, а сегментный регистр DS - на сегмент
данных
begin: mov AX,data
;(3)Адрес сегмента данных сначала загрузим в АХ,
mov DS,AX
;(4)а затем перенесем из АХ в DS
mov AH,09h
;(5)Функция DOS 9h вывода на экран
mov DX,offset mesg ;(6)Адрес выводимого сообщения должен быть в
DX
int 21h
;(7)Вызов DOS
mov AH,4Ch
;(8)Функция 4Ch завершения программы
mov AL, 0
;(9)Код 0 успешного завершения
int 21h
;(10)Вызов DOS
text ends
;(11)Конец сегмента команд
data segment
;(12)Начало сегмента данных
mesg db 'Начинаем!$' ;(13)Выводимый текст
data ends
;(14)Конец сегмента данных
stk segment stack ;(15)Начало сегмента стека
db 256 dup (0) ;(16)Резервируем 256 байт для стека
stk ends
;(17)Конец сегмента стека
end begin
;(18)Конец текста программы с точкой входа
Данная программа ничего не вычисляет и не обрабатывает, а всего лишь
выводит на экран терминала строку с фразой "Начинаем!".
Чтобы создать такую программу и увидеть, как она работает, на компьютере
должен быть установлен набор программ Turbo Assembler, позволяющий
создавать из представленного исходного текста программы исполнимый
файл, представляющий собой готовую программу, которую можно запускать
на исполнение. Используя Блокнот создайте в этом каталоге файл,
содержащий представленную программу и сохраните его с прозвольным
именем и расширением asm.
Следует заметить, что при вводе исходного текста программы с клавиатуры
можно использовать как прописные, так и строчные буквы: транслятор
воспринимает их одинаково.
Содержимое строк достаточно вводить до точки с запятой, означающих
начало комментария.
Прежде чем продолжить работу с программой внимательно прочтите
следующее краткое описание того, как работает данная программа.
Программа содержит 18 строк-предложений языка ассемблера. Первое
предложение с помощью оператора segment открывает сегмент команд
программы. Сегменту дается произвольное имя text. В конце предложения
после точки с запятой располагается комментарий. Предложение языка
ассемблера может состоять из четырех полей: имени, оператора, операндов и
комментария, располагаемых в перечисленном порядке. Не все поля
обязательны; так, в предложении 1 есть только имя, оператор и комментарий,
а операнды отсутствуют; предложение 3 включает все 4 компонента: имя
begin, оператор (команда процессора) mov, операнды этой команды АХ и data
и, наконец, после точки с запятой комментарий; в предложении 4 (и многих
последующих) отсутствует имя.
Любая программа должна обязательно состоять из сегментов - без сегментов
программ не бывает. Обычно в программе задаются три сегмента: команд,
данных и стека. В сегменте команд располагается собственно программа, т. е.
описание (с помощью команд процессора) последовательности требуемых
действий. В сегменте данных описываются данные, с которыми должна
работать программа; в нашем примере это строка текста. Назначение
сегмента стека будет описано ниже.
В предложении 2 с помощью оператора assume сообщается ассемблеру
(ассемблером называется программа-транслятор, преобразующая исходный
текст программы в коды команд процессора), что сегментный регистр CS
будет указывать на сегмент команд text, а сегментный регистр DS - на
сегмент данных data. Сегментные регистры (а всего их в процессоре 4)
играют очень важную роль. Когда программа загружается в память и
становится известно, по каким адресам памяти она располагается, в
сегментные регистры заносятся начальные адреса закрепленных за ними
сегментов. В дальнейшем любые обращения к ячейкам программы
осуществляются путем указания сегмента, в котором находится
интересующая нас ячейка, а также номера того байта внутри сегмента, к
которому мы хотим обратиться. Этот номер носит название относительного
адреса или смещения. Транслятор должен знать заранее, через какие
сегментные регистры будут адресоваться ячейки программы, и мы сообщаем
ему об этом с помощью оператора assume (assume - предположим). При этом
в регистр CS адрес начала сегмента будет загружен автоматически, а регистр
DS нам придется инициализировать вручную. Обращение к стеку
осуществляется особым образом, и ставить ему в соответствие сегментный
регистр (конкретно - сегментный регистр SS) нет необходимости.
Первые два предложения программы служат для передачи служебной
информации программе ассемблера. Ассемблер воспринимает и запоминает
эту информацию и пользуется ею в своей дальнейшей работе. Однако в
состав выполнимой программы, состоящей из машинных кодов, эти строки
не попадут, так как процессору, выполняющему программу, они не нужны.
Другими словами, операторы segment и assume не транслируются в
машинные коды, а используются лишь самим ассемблером на этапе
трансляции программы. Такие нетранслируемые операторы иногда называют
псевдооператорами или директивами ассемблера в отличие от истинных
операторов - команд языка.
Предложение 3, начинающееся с метки begin, является первой выполнимой
строкой программы. Для того чтобы процессор знал, с какого предложения
начать выполнять программу после ее загрузки в память, начальная метка
программы указывается в качестве операнда самого последнего оператора
программы end (см. предложение 18).
Начиная от точки входа программа выполняется строка за строкой точно в
том порядке, в каком эти строки написаны программистом.
В предложениях 3 и 4 выполняется инициализация сегментного регистра DS.
Сначала значение имени text (т. е. адрес сегмента text) загружается командой
mov (от move - переместить) в регистр общего назначения процессора АХ, а
затем из регистра' АХ переносится в регистр DS. Такая двухступенчатая
операция нужна потому, что процессор в силу некоторых особенностей своей
архитектуры не может выполнить команду непосредственной загрузки адреса
в сегментный регистр. Приходится пользоваться регистром АХ в качестве
"перевалочного пункта".
Предложения 5, 6 и 7 реализуют существо программы - вывод на экран
строки текста. Делается это не непосредственно, а путем обращения к
служебным программам операционной системы MS-DOS. Дело в том, что в
составе команд процессора и, соответственно, операторов языка ассемблера
нет команд вывода данных на экран (как и команд ввода с клавиатуры,
записи в файл на диске и т. д.). Вывод даже одного символа на экран в
действительности представляет собой довольно сложную операцию, для
выполнения которой требуется длинная последовательность команд
процессора. Конечно, эту последовательность команд можно было бы
включить в нашу программу, однако гораздо проще обратиться за помощью
к операционной системе. В состав DOS входит большое количество
программ, осуществляющих стандартные и часто требуемые функции вывод на экран и ввод с клавиатуры, запись в файл и чтение из файла, чтение
или установка текущего времени, выделение или освобождение памяти и
многие другие.
Для того чтобы обратиться к DOS, надо загрузить в регистр общего
назначения АН номер требуемой функции, в другие регистры - исходные
данные для выполнения этой функции, после чего выполнить команду int 2lh,
(int - от interrupt - прерывание), которая передаст управление DOS. Вывод на
экран строки текста можно осуществить с помощью различных функций
DOS; мы воспользовались функцией 09h, которая требует, чтобы в регистре
DX содержался адрес выводимой строки. В предложении 6 адрес строки
mesg загружается в регистр DX, а в предложении 7 осуществляется вызов
DOS.
В предложениях 5 и 7 указанные в тексте программы числа сопровождаются
знаком h. Таким образом в языке ассемблера обозначаются
шестнадцатеричные (далее - 16-ричные) числа, в отличие от десятичных,
которые никакого завершающего знака не требуют.
После окончания работы программы DOS должна выполнить некоторые
служебные действия. Надо освободить занимаемую нашей программой
память, чтобы туда можно было загрузить следующую программу. Надо
вызвать компонент операционной системы, который выведет на экран запрос
DOS (как правило, в виде символа >, предваряемого именем текущего
каталога) и будет ждать следующей команды оператора. Все эти действия
выполняет функция DOS с номером 4Ch. Эта функция предполагает, что в
регистре AL находится код завершения нашей программы, который она
передаст DOS. Если программа завершилась успешно, код завершения
должен быть равен нулю, поэтому в предложении 9 мы загружаем 0 в
регистр AL и вызываем DOS уже знакомой нам командой int 21h. Поскольку
выполняемая часть программы на этом закончилась, можно (и нужно)
закрыть сегмент команд, что выполняется с помощью директивы ends (от end
segment, конец сегмента), перед которой для наглядности обычно
указывается имя закрываемого сегмента, в данном случае сегмента text.
Вслед за сегментом команд описывается сегмент данных. Он, как и сегмент
команд, начинается директивой segment, предваряемой произвольным
именем нашего сегмента, и заканчивается директивой ends. У нас в качестве
данных выступает строка текста. Текстовые строки вводятся в программу с
помощью директивы ассемблера db (от define byte, определить байт) и
заключаются в апострофы или в кавычки. Для того чтобы в программе
можно было обращаться к данным, поля данных, как правило, предваряются
именами. В нашем случае таким именем является вполне произвольное
обозначение mesg (от message, сообщение), с которого начинается
предложение 13.
Выше, в предложении 6, мы через регистр DX передали DOS адрес начала
выводимой на экран строки текста. Но как DOS определит, где эта строка
закончилась? Хотя нам конец строки в программе отчетливо виден, однако в
машинных кодах, из которых состоит выполнимая программа, он никак не
отмечен, и DOS, выведя ча экран последний символ строки восклицательный знак, продолжит вывод байтов памяти, расположенных за
фразой "Начинаем!". Поэтому DOS следует передать информацию о том, где
кончается строка текста. Некоторые функции DOS требуют указания в одном
из регистров длины выводимой строки, однако функция 09h работает иначе.
Она выводит текст до знака доллара ($), которым мы и завершили нашу
фразу.
Сегмент стека, которому мы дали произвольное имя stk, начинается, как и
остальные сегменты, оператором segment и заканчивается оператором ends.
Стек представляет собой отдельный сегмент обычно небольшого объема, в
котором просто резервируется определенное количество пустых байтов. Для
выделения в программе группы байтов используется конструкция
db размер dup (заполнитель)
В нашем примере для стека выделено 256 байт, заполненных нулями.
Оператор segment, начинающий сегмент стека, имеет описатель stack.
Указание этого обозначения приводит к тому, что при загрузке программы в
память регистры процессора, используемые для работы со стеком,
инициализируются системой должным образом. Конкретно, сегментный
регистр стека SS будет настроен на начало сегмента стека, а указатель стека
SP - на его конец (стек заполняется данными от конца к началу).
Последняя строка программы содержит директиву end, которая говорит
программе ассемблера, что закончился вообще весь текст программы и
больше ничего транслировать не нужно. В качестве операнда этой
директивы, как уже отмечалось, обычно указывается точка входа в
программу, т. е. адрес первой выполнимой программной строки. В нашем
случае это метка begin.
Подготовка программы к выполнению
Прежде всего на компьютере должен быть установлен пакет Турбо
Ассемблера TASM. Если его нет, то программное обеспечение, необходимое
для работы с ассемблером, можно скачать здесь TASM.
Процесс подготовки и отладки программы на языке ассемблера включает
этапы подготовки файла с исходным текстом, его трансляции и компоновки
и, наконец, отладки программы с помощью специальной программы
интерактивного отладчика.
Подготовка исходного текста программы выполняется с помощью любого
текстового редактора. Файл с исходным текстом должен иметь расширение
ASM. При выборе редактора для подготовки исходного текста программы
следует иметь в виду, что многие текстовые процессоры (например, Microsoft
Word) добавляют в выходной файл служебную информацию о формате
(размер страниц, типы используемых шрифтов и др.). Поэтому следует
воспользоваться редактором, выводящим в выходной файл "чистый текст",
без каких-либо управляющих символов. К таким редакторам относятся,
например, программа Блокнот.
Трансляция исходного текста программы состоит в преобразовании
предложений исходного языка в коды машинных команд и выполняется с
помощью транслятора с языка ассемблера (т. е. с помощью программы
ассемблера). Можно воспользоваться пакетами TASM корпорации Borland
или Microsoft MASM. Трансляторы различных разработчиков имеют
некоторые различия, в основном в части описания макросредств. Однако
входной язык (т. е. мнемоника машинных команд и других операторов и
правила написания предложений ассемблера) для всех ассемблеров одинаков.
В результате трансляции образуется объектный файл с расширением .OBJ.
Компоновка объектного файла выполняется с помощью программы
компоновщика (редактора связей). Эта программа получила такое название
потому, что ее основное назначение - подсоединение к файлу с основной
программой файлов с подпрограммами и настройка связей между ними.
Однако компоновать необходимо даже простейшие программы, не
содержащие подпрограмм. Дело в том, что у компоновщика есть и вторая
функция - изменение формата объектного файла и преобразование его в
выполнимый файл, который может быть загружен в оперативную память и
выполнен. Файл с программой компоновщика обычно имеет имя LINK.EXE,
хотя это может быть и не так. Например, компоновщик корпорации Borland
назван TLINK.EXE. Компоновщик необходимо брать из одного пакета с
ассемблером. В результате компоновки образуется загрузочный, или
выполнимый, файл с расширением .ЕХЕ.
Отладка готовой программы может выполняться разными методами, выбор
которых определяется структурой и функциями отлаживаемой программы.
Свою специфику отладки имеют, например, резидентные программы,
обработчики аппаратных прерываний, драйверы устройств и другие классы
программ. В целом наиболее удобно отлаживать программы с помощью
какого-либо интерактивного отладчика, который позволяет выполнять
отлаживаемую программу по шагам или с точками останова, выводить на
экран содержимое регистров и областей памяти, модифицировать (в
известных пределах) загруженную в память программу, принудительно
изменять содержимое регистров и выполнять другие действия, позволяющие
в наглядной и удобной форме контролировать выполнение программы.
При использовании пакета Borland следует взять "турбо-дебаггер" TD.EXE,
при трансляции и компоновке программы с помощью пакета Microsoft отладчик Codeview (файл CV.EXE).
В данном практикуме будет предполагаться, что предложенные примеры
выполняются с помощью пакета TASM (транслятор TASM.EXE,
компоновщик TLINK.EXE, отладчик TD.EXE).
Если файл с исходным текстом программы назван P.ASM, то строка вызова
ассемблера может иметь следующий вид:
tasm /z /zi /n p, p, p
(Еще раз напоминаем, что как в тексте программы на языке ассемблера, так и
при вводе с клавиатуры командных строк можно с равным успехом
использовать и прописные и строчные буквы.)
Ключ /z разрешает вывод на экран строк исходного текста программы, в
которых ассемблер обнаружил ошибки (без этого ключа поиск ошибок
пришлось бы всегда проводить по листингу трансляции).
Ключ /zi управляет включением в объектный файл номеров строк исходной
программы и другой информации, не требуемой при выполнении программы,
но используемой отладчиком.
Ключ /n подавляет вывод в листинг перечня символических обозначений в
программе, от чего несколько уменьшается информативность листинга, но
существенно сокращается его размер.
Стоящие далее параметры определяют имена файлов: исходного (P.ASM),
объектного (P.OBJ) и листинга (P.LST). Расширения имен файлов можно не
указывать.
Строка вызова компоновщика может иметь следующий вид:
tlink /v /х р, р
Ключ /v передает в загрузочный файл символьную информацию,
позволяющую отладчику TD выводить на экран полный текст исходной
программы, включая метки, комментарии и пр. Стоящие далее параметры
обозначают имена модулей: объектного (P.OBJ) и загрузочного (Р.ЕХЕ).
Ключ /х подавляет формирование карты загрузки (файла с листингом
компоновки P.MAP), без которого вполне можно обойтись.
Как уже отмечалось, компоновщик создает загрузочный, готовый к
выполнению модуль в формате .ЕХЕ. Запуск подготовленной программы
Р.ЕХЕ осуществляется командой
р.ехе
или просто
p
Если программа не работает должным образом, необходимо прибегнуть к
помощи интерактивного отладчика. Отладчик пакета TASM запускается
командой
td p
где р (или р.ехе) - имя файла с отлаживаемой программой. По умолчанию
отладчик загружает файл с расширением .ЕХЕ. В процессе работы отладчик
использует также файл с исходным модулем P.ASM, поэтому перед отладкой
не следует переименовывать ни исходный, ни выполнимый файлы.
Целесообразно создать командный файл, автоматизирующий выполнение
однотипных операций трансляции и компоновки. Текст командного файла
может быть таким:
Приведенный текст составлен в предположении, что путь к программам
пакета TASM указан в команде PATH. Если это по каким-либо причинам не
так, в командный файл следует включить полную спецификацию файлов
ассемблера и компоновщика, например (если весь пакет находится на диске
D: в каталоге TASM):
d:\tasm\tasm /z /zi /n p,p,p
Создайте файл с программой. Подготовьте программу к выполнению.
Запустите программу и убедитесь, что она работает правильно: выводит на
экран нужный текст и не нарушает работу компьютера.
Интерактивный отладчик TD
Часто бывает так, что программа, успешно пройдя этапы трансляции и
компоновки, все же работает не так, как ожидалось или вообще не работает.
Это значит, что формально, с точки зрения правил языка программирования,
программа написана правильно (в ней нет синтаксических ошибок), однако
алгоритм ее в чем-то неверен. Для отладки такой программы следует
воспользоваться услугами интерактивного отладчика. Интерактивным он
называется потому, что вся работа с ним осуществляется в непрерывном
диалоге с пользователем.
Познакомимся с отладчиком TD.EXE из пакета TASM,
воспользовавшись программой из первого примера.
Как уже отмечалось, для полного использования возможностей
отладчика следует при трансляции программы указать в числе других ключ
/zi, а при компоновке - ключ /v:
tasm /z/zi/n p,p,p
tlink /v p,p
Кроме того, следует убедиться, что в вашем рабочем каталоге имеется
и загрузочный (Р.ЕХЕ) и исходный (P.ASM) файл, поскольку отладчик в
своей работе использует оба этих файла. Вызовем отладчик командой
td p
На экране появится кадр отладчика, в котором видны два окна - окно
Module с исходным текстом отлаживаемой программы и окно Watches для
наблюдения за ходом изменения заданных переменных в процессе
выполнения программы (см.рис.).
Окно Watches нам не понадобится, и его можно убрать, щелкнув
мышью по маленькому квадратику в левом верхнем углу окна или введя
команду Alt+F3, предварительно сделав это окно активным. Переключение
(по кругу) между окнами осуществляется клавишей F6.
Верхняя строка кадра отладчика представляет собой главное меню. Для
перехода в меню необходимо нажать клавишу Alt и первую (выделенную
цветом) букву требуемого пункта. Для выбора затем конкретного действия
надо с помощью клавиш со стрелками вверх и вниз- выделить нужный пункт
и нажать клавишу Enter.
В нижней строке отладчика приведены его основные команды, вызов
которых осуществляется нажатием на функциональные клавиши F1...F10. В
действительности команд гораздо больше; некоторые из них можно
реализовать только с помощью пунктов главного меню, другие вызываются
комбинациями функциональных и управляющих (Alt, Ctrl или Shift) клавиш.
Рассмотрим типичные действия, к которым приходится прибегать по
ходу отладки программы.
После вызова отладчика и загрузки в память отлаживаемой программы
ее первое предложение помечается значком сплошного треугольника ; там же
устанавливается и значок подчеркивания _, который играет в отладчике роль
курсора. По мере выполнения программных предложений значок сплошной
треугольник будет перемещаться по тексту программы, всегда указывая на
очередное (еще не выполненное) предложение; значок _ можно перемещать с
помощью клавиш со стрелками.
Нажав одну из клавиш F8 или F7, мы выполним одно предложение
программы. Различие этих команд весьма существенно: команда F7 (trace,
трассировка) позволяет войти внутрь вызываемых подпрограмм, а также
выполнять циклы шаг за шагом. Команда F8 (step, шаг), наоборот, выполняет
подпрограммы и циклы как одно неразрывное действие, что заметно
ускоряет пошаговую отладку программы, если мы, например, уверены, что
вызываемая подпрограмма выполняется правильно.
Можно выполнить сразу и целый фрагмент программы, т. е. несколько
предложений. Для этого надо поместить курсор _ перед тем предложением,
на котором требуется сделать остановку (или на любой символ внутри него),
и нажать клавишу F4 (here, сюда). Выполнятся все строки программы до той,
на которой установлен курсор; значок сплошной треугольник переместится
на эту строку. Далее можно опять выполнять программу построчно, нажимая
на клавишу F8, или, установив в требуемом месте курсор, выполнить
следующий фрагмент, нажав F4.
Для повторного выполнения программы ее следует "рестартовать". Для
этого надо выбрать в главном меню пункт Run-Program reset или просто
нажать Ctrl+F2.
Важнейшим элементом отладки программы является наблюдение
значений тех или иных полей данных, особенно тех, которые заполняются
программой динамически, т. е. по ходу ее выполнения. Для того чтобы
вывести на экран содержимое поля данных, надо поместить курсор на имя
этого поля (например, mesg в нашем примере) и выбрать пункт меню DataInspect. В появившемся окне ввода переменной (см.рис.) можно
скорректировать имя интересующего нас поля данных или ввести новое; если
имя правильное, достаточно нажать клавишу Enter.
В кадр отладчика будет выведено окно с характеристиками и
содержимым указанной переменной (см.рис.). Отладчик сообщает, что
переменная mesg хранится в памяти по адресу 5D82:000, т. е. имеет
сегментный адрес 5D82h и смещение OOOOh, и описана как
последовательность из 12 байт. Тут же приводятся значения всех байтов
нашей строки, включая их начертание на экране, а также десятичное и 16ричное представление.
В окне Inspecting можно изменить значение отображаемого поля
данных. Для этого надо, сделав это окно активным и поместив курсор на
отображение конкретного элемента нашего символьного массива, например
элемента с индексом 12 (знак "!"), ввести команду Alt+F10. Эта команда для
любого активного окна открывает его внутреннее меню с дополнительными
возможностями. В данном случае внутреннее меню будет иметь вид,
показанный на нижнем рисунке.
Нас будет интересовать пункт Change (изменение). Выбрав этот пункт,
мы получим окно, в котором можно ввести требуемое значение изменяемого
данного. На следующем рисунке показано это окно с введенным символом
'>', которым будет заменен восклицательный знак. Можно было вместо
символа в одинарных кавычках ввести его 16-ричный код ASCII, если он
известен (число ЗЕ для знака >). Допустим ввод и десятичного кода, если
завершить его буквой d F2d).
Если теперь, не выходя из отладчика, выполнить программу до конца,
на экран вместо ! будет выведен >.
Для того чтобы, находясь в отладчике, увидеть результат работы
программы, надо ввести команду Alt+F5 (или выбрать пункт Window-User
screen главного меню). Возврат в кадр отладчика осуществляется нажатием
любой клавиши.
Необходимо отдавать себе отчет в том, что любые изменения,
вносимые в текст программы в процессе работы с отладчиком, будут
действовать только до конца данного сеанса (даже, точнее говоря, до
рестарта программы). Отладчик изменяет не файл Р.ЕХЕ, хранящийся на
диске, а только его копию в памяти. После завершения сеанса работы с
отладчиком все, что было загружено в память, исчезает вместе с внесенными
нами изменениями.
Начальное окно отладчика дает недостаточно информации для
серьезной работы с программой. При отладке программы на уровне языка
ассемблера необходимо контролировать все регистры процессора, включая
регистр флагов, а также во многих случаях поля данных вне программы
(например, векторы прерываний или системные таблицы). Для этого надо
командой Alt+V, С (пункт главного меню View-CPU) открыть "окно
процессора" (см.рис.).
Окно процессора состоит, в свою очередь, из пяти внутренних окон для
наблюдения текста программы на языке ассемблера и в машинных кодах,
регистров процессора, флагов, стека и содержимого памяти. С помощью этих
окон можно полностью контролировать работу процессора при выполнении
отлаживаемой программы. Для того чтобы можно было работать с
конкретным окном, надо сделать его активным, щелкнув по нему мышью.
Переходить из окна в окно можно также (по кругу), нажимая клавишу Tab.
Для управления ходом программы используются функциональные клавиши,
перечисленные в нижней строке кадра (F7 или F8 для пошагового
выполнения, F4 для выполнения до курсора и т. д.). Курсор во всех
внутренних окнах окна процессора выглядит в виде синей ленточки. Добавим
еще, что, щелкнув мышью по значку стрелки вверх в правом верхнем углу
окна процессора, можно увеличить это окно до размеров кадра отладчика.
Продемонстрируем некоторые возможности окна процессора. По ходу
пошагового выполнения программы можно изменять содержимое регистров.
Это дает возможность исправлять обнаруженные ошибки (если выяснилось,
что в какой-то строке программы заполняется не тот регистр или не тем
содержимым), а также динамически модифицировать программу с целью
изучения ее работы.
Выполните программу до первой команды int 21h (предложение 7) и
просмотрите содержимое регистров процессора. Вы увидите, что в старшей
половине регистра АХ находится число 09h - номер вызываемой функции
DOS. Младшая половина регистра АХ заполнена "мусором" - остатком от
выполнения последней операции с регистром АХ. В регистре DX будет
OOOOh - относительный адрес первого байта строки mesg в сегменте
команд. Изменим этот относительный адрес. Для этого надо перейти в окно
регистров, поместить курсор на строку, отображающую содержимое регистра
DX, и ввести команду Alt+F10, открывающую внутреннее меню окна
регистров (см.рис.).
Как видно из рисунка, меню окна регистров предоставляет
возможность выполнить увеличение содержимого регистра на 1 (Increment),
уменьшить его на 1 (Decrement), обнулить (Zero) и заменить на любое
заданное значение (Change). Выбрав пункт Change, занесем в регистр DX
число 5 (см.рис.).
Теперь, если выполнить очередную команду (int 21h), DOS выведет на
экран строку, начало которой расположено в байте 5 сегмента данных. В
нашей фразе "Hello, world!" байт 5 приходится на запятую (нумерация байтов
в строке, естественно, начинается с нуля). В результате на экран будет
выведена строка
Hello, world!
Еще одной полезной операцией является вывод содержимого области
памяти, начиная с заданного адреса (дамп памяти). Таким образом можно
изучать не только поля данных программы, но и содержимое вообще любых
участков памяти компьютера, в частности системных таблиц. Посмотрим,
например, как выглядит в памяти блок окружения программы.
Любая программа, загруженная в память, состоит из двух отдельных
блоков: собственно программы и ее окружения, которое располагается
обычно перед программой, хотя не обязательно вплотную к ней.
Окружение представляет собой область памяти, в которой в виде
символьных строк записаны значения переменных, называемых
переменными окружения, например
PROMPT= $p$g
PATH=C:\DOS; C:\TOOLS; C:\NC;
Здесь PROMPT и PATH - переменные окружения, а справа от знака
равенства указаны их конкретные значения, которое могут быть и другими.
Переменная PROMPT определяет форму системного запроса, выводимого
командным процессором на экран после завершения любой текущей
программы, а переменная PATH - пути к программным файлам, которые
будут вызываться на выполнение просто по именам, без указания полной
спецификации.
Пользователь может включить в окружение строки определения
дополнительных переменных с помощью команды SET. Часто в качестве
значений таких переменных указываются пути к каталогам со
вспомогательными файлами или ключи, задающие режим работы различных
программ.
Задание по лабораторной работе
Разработать и отладить программу вычисления по простой формуле.
Переменные в выражении - целые числа со знаком. Одна программа должна
выполнять вычисления над операндами типа байт со знаком, а вторая - слово
со знаком. Диапазон байта (-128..127), диапазон слова (-32768..32767).
Задание первой лабораторной определяет порядок использования форматов
данных.
Например, в файле lab1.asm представлен образец выполнения первой
лабораторной работы, в которой вычисляется следующая формула.
;Условие
;
a^3+b*c/a
; X = -----------;
;
a^2-b^2
;a=2; b=-5; c=17.
Подставим значения a,b,c в выражение.
;
2^3+(-5)*17/2
8+(-85)/2
8+(-42)
-34
; X = -------------- = ---------- = ------- = --- = 1.
;
2^2-(-5)^2
2-25
-23
-23
Проведенные вычисления показывают, что конечный результат, а
также все промежуточные результаты имеют значения в пределах диапазона
байта. Если результаты вычисления выражения выходят за пределы байта, то
используем слова.
Отладка программы с помощью td.exe
Наша программа не содержит команд ввода-вывода. Поэтому если
запустить программу под управлением операционной системы, то
результатов мы не увидим. Программа запускается под отладчиком.
Отладчик позволяет проверить выполнение программы и выявить
алгоритмические ошибки.
Так как результат выполнения программы заранее известен, то
целесообразно выполнить пошаговое выполнение программы до команды ret
и сверить результаты с расчетным. В случае верного результата отладка
программы завершена.
Если программа выполняется до конца, но результат неверный,
необходимо проверить результаты выполнения каждой команд, найти
первую от начала ошибку и исправить алгоритм программы. Предварительно
надо вычислить результаты каждой команды и записать их в комментариях.
Теперь выполняем программу в пошаговом режиме и проверяем результаты
каждой команды.
Ошибка может быть обнаружена и исправлена при выполнении
расчетов на бумаге.
Если программа завершается прерыванием и сообщением "Divide by
zero", то источником ошибки является одна из команд деления.
Для исправления ошибки надо прочитать описание команды деления в
лекциях.
Формирование протокола выполнения программы
Для создания протокола отладчик запускается в режиме
перенаправления
ввода-вывода. Командная строка имеет вид:
>debug.exe lab1.exe <lab1.cmd >lab1.rez
Символ "<" означает, что ввод с клавиатуры перенаправляется на ввод
из тестового файла lab1.cmd. Это означает, что команды отладчика debug
будут вводиться из текстового файла. Символ ">" означает, что вывод на
экран перенаправляется на вывод в текстовый файл lab1.rez.
Содержимое файла lab1.cmd.
t5
d ds:0 3
t 13
d ds:0 3
q
Команда t 5 означает выполнить 5 команд. Вторая строка содержит
команду, которая показывает содержимое первых четырех байт памяти
сегмента данных (0 3). В программе используеся 4 переменных размером
byte. В этот момент переменные имеют исходные значения.Команда третьей
строки выполняет 13 команд и останавливает программу перед выполнением
команды ret. Теперь команда 4 строки показывает нам результаты
программы.
В 5 строке команда выхода. Вслед за символом "q" необходимо нажать
клавишу "Enter", ввод кода которой после каждой строки является приказом
выполнить команду.
Файл протокола текстовый, его печатают обычным образом.
Состав отчета по лабораторной работе
В отчет по первой лабораторной работе включаются:
- титульный лист;
- задание;
- листинг программы;
- протокол выполнения программы;
- контрольный просчет результатов.
Вопросы для защиты первой лабораторной работы
Прежде всего студент показывает совпадение результатов выполнения
программы и контрольного просчета и доказывает ,что программа сделана
верно.
При защите лабораторной студент рассказывает алгоритм программы,
выполнение каждой команды и способы адресации используемых в
программе команд. Студент рассказывает алгоритм программы по листингу.
Выполнение каждой команды можно проследить по протоколу. Разбор
способов адресации: прямая, регистровая, непосредственная выполняется по
листингу, где в коде команды надо уметь выделять следующие поля:
- байт кода операции;
- пост байт;
- смещение адреса или прямой адрес;
- поле непосредственного операнда.
Студент должен знать алгоритмы выполнения арифметических команд ADD,
SUB, MUL, IMUL, DIV, IDIV, INC, DEC, команды пересылки данных MOV,
PUSH, POP, команды расширения поля CBW,CWD и какие флаги эти
команды формируют. В командах деления надо уметь пояснить
исключительные ситуации "Деление на 0" и "Перполнение".
На примере листинга программы надо уметь рассказать структуру на языке
ассемблера.
Студент должен знать назначение каждого регистра процессора и назначение
каждого бита регистра флагов.
Варианты заданий
1. a = 1;
b = 6;
с = 13;
5a2 - bc
2x - bc
x=
y=
3b2 – a
2
8b - 3a
2. a = 7;
b = 98;
с = 3;
5ac – b2
4x2 + c
x=
y=
b – x2
2
7b - c
3. a = 7;
b = -4;
с = 3;
ac2 + 7b
15a - x2
x=
y=
7c – b
3a - 4c
4. a = 25; b = 8;
с = 12;
3a + b2c2
x2 - 12xb
x=
y=
2
4c - a
5. a = 53; b = 18;
5c + 10(a + b)
с = 12;
b2c - 2a
18c + x2
x=
y=
4b+12c
6. a = 1; b = 1;
4b2 – 2
с = -6;
15ac - 128b - 25
x=
15x - 16(c + a)
y=
2
8a+b
7. a = 5; b = 2;
7b2 - 2c
с = 5;
a3 - x2(c – b)
320a - 25bc
x=
y=
a - 17c
8. a = 3; b = 1;
с = -3;
10ac - 8 (a + b)
x=
16a - 4bc
a5 + 12x
(x – 2)2 +a
y=
x2 - b c
9. a = 13;
b = 13;
с = 13;
xa – 280c
128a2 - 5cb
x=
y=
(x – c)2
32a + 10b
36. a = 11;
b = 2;
с = 21;
xс – b2
9ab + 4ac
x=
y=
b – 2(a + 2)
37. a = 31; b = 2;
с = -3;
16(a – 14) + 2x
2b2  c2 + 3ac
b – a(116+с)
2
x=
y=
ba – 2
38. a = 7; b = 4;
x – 4b
с = -5;
4 + b  x2 + 5
2a(4b + 12c)
x=
y=
a – 3c
39. a = 4; b = 7;
(28 – c)(a – 2)
2
с = 5;
3 bc(2a – b)
14 – x2 + с
x=
y=
4a + 3c
40. a = 5; b = 3;
a2 – 3b
с = -3;
12 a2 + 8b
(21 + a)x + 1
x=
y=
14 – cb
2
2c - a
41. a = 2; b = 7;
с = -3;
7ab – 4bc
x=
4x– 16(a – c)
y=
a – b(a + 1)
42. a = 6; b = -2;
с = 3;
-50 – bx
4ab2 – 3ac
21x –ac2
2
x=
y=
2c + ab
x + 12(x – b)
43. a = -13;
b = 8;
с = 128;
2 c – 7 ba2
x–c
x=
y=
3(a + b) + c
44. a = -11;
b = 0;
(102 + ab)  (x – 20)
с = - 9;
-4x +a2 – 1
a2 + bc2
x=
y=
-2a + 12bc
45. a = -1;
b = -2;
2x + bc
с = 32;
2c2 – bc
3 – x2 – с
x=
y=
8ab – c
46. a = 3; b = 7;
ba – 2x
с = 2;
17ca2 - b2c
14 – 8(b + a)
x=
y=
2
2 b - a
47. a = 6; b = 61;
4x + bc
с = 31;
24ba – 8c
(x + 1) - b2
x=
y=
3ab - bc
48. a = 6; b = -4;
c – 3(a + 3)
с = 3;
128(a – b) + c2
2ac – x2
x=
y=
(a – 1)8 + 78a
49. a = -12; b = 9;
x(12 + a) + 32b
с = 0;
ab – 2(c + a)
x=
(x + c) 2 - 3333
y=
(10a – c) +118
50. a = 124; b = 67;
12a – b
с = 2;
a2 - b(c – 2)
x=
xc – ba
y=
32c - b
(x + 5242) +2c
51. a = 6;
с = 6;
b =-4;
c2 + 8(a – b)
(b – a) 2 + x2
x=
y=
bc + 21
52. a = -6;
18x + c
с = - 13;
b = 7;
b2 – 8(a + 4c)
x 3 – ac
x=
y=
ab - 100
53. a = 21;
(b + a)16 - 5
с = 35;
b = 2;
a3 – (b + c)4
x2 – 4(c + a)
x=
y=
28b - ac
54. a = 6;
b = 4;
x + 24b
с = 11;
c2 – 4b
16(a – c) + bx
x=
y=
7(a +b) – 24b
55. a = 7; b = - 9;
с = - 10;
b3 - ac + 12
7x + 7(b – 1)
12(x + a) + x2
x=
y=
(a – b)8 + c
56. a = 6; b = 7;
x + (b – c)12
с = 3;
(x – 64)2 + bc2
17c + 2b2
x=
y=
ac - 21(b – c)
57. a = -51; b = 26;
ax + 5c
с = 36;
111(a – c) + b2
x2 – bc
x=
y=
cb + 21a
58. a = -3; b = 2;
2(b + x) - a
с = 5;
(x – 2)c - ba
bc2 + 13(a - c)
x=
y=
ac + 14
3b - cx
59. a = 4;
с = 13;
b = 3;
(c – 5)2 + c  a
b a2 – c(21 + x)
x=
y=
b + 21(a – b)
60. a = 22; b = -2;
2
114(x + a) - 920
с = 3;
ca2 + 18(b – c)
4c + 14(x – a) + 18
x=
y=
(b – c) 2 – 32b
14c + 16ab
61. a = 4; b = 2;
с = 3;
a3 + c2(b + 2)
14(x – a) + 10b
x=
y=
7 b – 3a
62. a = 3; b = 2;
cx - 14a
с = - 3;
x – 2(a + b) + 121
cb2 - a3
x=
y=
b a2 – 19x
18(b + c) - 2a
63.
64.
65.
66.
67.
68.
69.
70.
71. a = 3;
b = 0;
с = 3;
16x2 – ba2
c2 - bc + a3
x=
y=
a  b + c a
72. a = 14; b = 9;
3
4xa - 17c
с = 8;
b2c +ac3
x=
14x2 - 2c
y=
8bc - 15a2
73. a = 5; b = -1;
с = 1;
(c + 5)  b + 2ax
a3 – b2 + c2
ab2 - cx
x=
y=
3ab + 4c
b22a - c2
74. a = 6;
с = -2;
b = 2;
2b2 +3ac + 4
28ac + 16(c + x2 )
x=
y=
8bc + 4ab
75. a = 16; b = 17;
ax2 + b c
с = 15;
3ab + a2 b
(4x + b) + a
x=
y=
(a – c)  (a - b )
76. a = -6; b = -4;
с = 3;
(4b2 – a) – 1088
b 3 - c2
(bc – ab)x
2
2
x=
y=
2
2
(b + a) + (a - b )
77. a = 3; b = 8;
с = -14;
(c + 2x)(a + b)
a2 + 2b - 4 c
x2 (b+ c)
x=
y=
(a – b)b - c
2
78. a = 1;
(b + c)a + 2b
с = 2;
b = -2;
(b2 – a)x + c
14ab - 128b2
x=
y=
16ac + 8bc
79. a = 6;
b = -2;
2
3x + 2 b c
с = 3;
21bc + 4 c2
a2 - b + c
x=
y=
2
(a + b) - a
80. a = 4;
2
b = 2;
(x + b) (a - c)
с = 7;
13a2 - b c)4
2c2 + 3x - b
x=
y=
(a – b)a - 4
2
5a - 2b
81. a = -5; b = 9;
с = 7;
a2b + bc
x=
12x2 - ab
y=
2
b - 2bc + a
4a2 + 2cx
82. a = 8;
b = -12;
с = 5;
a2b + b c
3x2 + 2ab
x=
y=
2
b - 2bc + a
83. a = 9; b = -4;
x2 - 2(bc + a)
с = 2;
ba + bc - a2
4x2 +2x + b
x=
y=
2
3b - 23c
84. a = 7; b = 19;
a2 - c
с = 1;
a + bc2 + 16b
cx + ab - 3c
x=
y=
b –a
85. a = 5; b = -2;
3
8x2 + bc
с = 3;
a3 + 4bc
bc + 2x - a2
x=
y=
6ab + 10c
86. a = -7;
b = 11;
4(a + x)
с = 8;
8bc2 + ab
3x2 - bc2
x=
y=
16c - ac
87. a = 6;
b = -2;
36x + a
с = 3;
x3 + 5x2
56a + 32ab
x=
y=
ba + c a
2
88. a = 5;
2
b = 11;
(x - a2)
с = 3;
3bc + a2
x=
89. a = 6;
4a c
b = 7;
(a + 3c)(4b - a
x=
a+b+c
25a2 - x2
y=
c - 2b
с = 3;
y=2
x2 - 4ac
a + 3b - c
90. a = 3;
с = 1;
b = -2;
3x(a – c)
32 + a + 4b
x=
2 c – a + b
91. a = 2; b = -2;
y=
2b
с = 3;
21a + 3b2
128abc + x
x=
4
y=
(c + 2b) 2
92. a = 3; b = 3;
2c - 1
с = 3;
12c3 - a2b
2c - b2 + 6a
x=
y=
(a + b + c)
93. a = 6; b = -2;
2
3x
с = 3;
4b2 + a2c
(3ac - 3b)x
x=
y=
(a + 2b )c
94 a = 8;
b = -5;
abc
с = -5;
8 + a2 - c 4
x3 - 2abc
x=
y=
3(a + c)
95. a = 2; b = 3;
56 – a – c
с = 2;
(73 + b3c) - a2
a2 + b2 +c2
x=
4a (b – c)
96. a = -6; b = -3;
y=
x
с = 3;
(a + b) 2 - c2
x=
x4 + abc
y=
2–a–b–c
97. a = 6; b = 5;
с = 5;
(a2 + b2 + c2)
21c +13b +4a
a3 + b3 + c3
x=
y=
(ab - 4c)3
x2 - 4
98. a = 6; b = -2;
с = -3;
2
2b + 2ac + 4
28ac + 16(c+x2 )
x=
y=
8bc + 3ab
99. a = 6; b = 1;
ax2 + cb
с = 1;
8a2 b2 - 3c
16x - b(a - x)
x=
y=
2
ac - 2b
100. a = 7; b = 4;
32x - 18c
с = -5;
4 + bx2 + 5
2a(4b + 12c)
x=
y=
a2 + 3c
(28 – c)(a - 2)
Download