- •5) Базовая архитектура пк ibm pc , процессор с точки зрения программиста, регистры общего назначения, регистр флагов.
- •Int 21h ; вывели на экран ‘7’
- •25) Исполняемые сом-файлы, их отличие от ехе-файлов, примеры.
- •1) Title Prog_Сom-файл
- •2) .Model tiny
- •2) Extrn p2: far public p2
- •2) Второй вид заголовка:
- •If_l macro X, y, l
If_l macro X, y, l
mov AX, x
cmp AX, Y
jl L
endm
Используя этот макрос, поиск После макрогенерации в программе будет текст:
минимального из 3-х чисел запишется так: ------------------------------
; DX = min (A,B,C) mov DX, A
mov DX, A mov AX, A
IF_L A, B, M1 cmp AX, B
mov DX, B jl, M1
M1: IF_L DX, C, M2 mov DX, B
mov DX, C M1: mov AX, DX
M2: ---------------------- cmp AX, C
jl M2
mov DX, C
M2: ------------------------------
2) Обращение к процедурам будет нагляднее, если передачу параметров оформить как макрос.
Например: Вычислить CX = NOD(A,B) + NOD(C,D), если есть процедура вычисления NOD(x,y), и результат ее находится в АХ.
CALL_NOD MACRO x, y
mov AX, x
mov BX, y
call NOD ; (AX) = NOD(x, y)
endm
CALL_NOD A, B ; наглядное обращение к ПП с параметрами.
mov CX, AX ; (CX) = NOD(A,B)
CALL_NOD C, D ; (AX) = NOD(C,D)
add CX, AX ; (CX) = NOD(A,B) + NOD(C,D)
Использование меток в макросах.
Пример 1. Если в макроопределении используются метки, но нет директивы Local , то после макрогенерации будет сообщение об ошибке – дублирование меток:
макроопределение макрокоманда макрорасширение …………
M Macro M → L: …………
………… …………
L: …………. ---- -----------------------
endm …………
M → L: …………
…………
Вычислить остаток от деления одного числа на другое с помощью вычитания (числа натуральные, r1 и r2 - регистры).
MD Macro r1, r2 ; r1 = r1 mod r2
Local M, M1
M: cmp r1, r2 ; while r1 >= r2 DO r1 = r1 – r2
jb M1
sub r1, r2
jmp M
M1: endm
Обращения к макросу MD:
1) MD AX, BX ; (r1 = AX, r2 = BX)
-----------------
??0000 : cmp AX, BX
jb ?? 0001
sub AX, BX
jmp ?? 0000
??0001: ----------------
MD CX, DX ; (r1 = CX, r2 = DX)
---------------------
??0002 : cmp CX, DX
jb ??0003
sub CX, DX
jmp ??0002
??0003: ---------------------
Макрогенератор, встретив директиву Local M, M1, будет заменять метки M и M1 на специальные имена вида ??хххх, где хххх – четырехзначное 16-ричное число от 0000÷FFFF.
33) Директивы условной генерации в Ассемблере
Директивы условного ассемблирования (ДУА). ДУА управляют процессом ассемблирования путем подключения или отключения фрагментов исходного текста программы. Общий вид:
1) if <выражение> 2) if <выражение >
if- часть if- часть
[ else elseif <выражение 1>
else-часть ] elseif-часть 1
endif elseif <выражение 2>
elseif-часть 2
-------------------------------
[ else
else-часть ]
endif
ДУА в форме 2) используется аналогично операторам выбора в языках высокого уровня.
ДУА много, рассмотрим некоторые из них:
if-часть ассемблируется, включается в исходный текст программы, если значение выражения - истина, т.е. не равно нулю, в противном случае работает else-часть, если она есть, если else-части нет, выполняется следующий за директивой if оператор.
if <константное выражение>
if-часть включается в расширение, если значение выражения <> 0
2) а) ife константное выражение ;if-часть работает, если выражение ложно,
b) elseife константное выражение ;равно нулю.
3) a) ifdef метка ; if-часть работает, если
b) elseifdef метка ; указанная метка определена
4) a) ifndef метка ; if-часть работает, если
b) elseifndef метка ; указанная метка не определена
5) a) ifb <аргумент> ; if-часть работает, если
b) elseifb <аргумент> ; значением аргумента является пробел
6) a) ifnb <аргумент> ; if-часть работает, если значением
b) ifnb <аргумент> ; аргумента является не пробел
7) a) ifdif <арг1>, <арг2> ; if-часть работает, если аргументы
b) elseifdif <арг1>, <арг2> ; различны, прописные и строчные буквы различаются
8) a) ifdifi <арг1>, <арг2> ; if-часть работает, если аргументы различны,
b) elseifdifi <арг1>, <арг2> ; прописные и строчные буквы не различаются
9) a) ifidn <арг1>, <арг2> ; if-часть работает, если аргументы одинаковы,
b) elseifidn <арг1>, <арг2> ; прописные и строчные буквы различаются
10) a) ifidni <арг1>, <арг2> ; if-часть работает, если аргументы одинаковы,
b) elseifidni <арг1>, <арг2> ; прописные и строчные буквы не различаются
Здесь угловые скобки обязательны.
Примеры использования ДУА
Использование ДУА непосредственно в Ассемблере. При отладке большой программы обычно используются отладочные печати для проверки правильности работы отдельных ее участков. В отлаженной программе эти печати удаляются. Но если отладка выполняется в несколько этапов, то добавлять печати в программу и исключать их – это тоже громоздкая работа, во время которой можно внести новые ошибки.
Используя ДУА, задачу включения в исходный текст программы и исключения из нее отладочных печатей перекладываем на макрогенератор, а он не ошибается. Для этого программисту нужно определить константу, установив таким образом режим отладки или счета. Например, debug EQU 1 - отладка, debug EQU 0 - счет, а в программе должно быть: mov x, AX
if debug
OutInt x
endif
После макрогенерации в программе появится текст mov BX, 0
если debug < > 0: mov x, AX если debug = 0: mov x, AX
OutInt x mov BX, 0
mov BX, 0
Здесь OutInt – макрокоманда обращения к процедуре вывода.
Решить задачу, чтобы отладочная печать работала в режиме отладки и не работала при счете, можно и с помощью команды сравнения и условного перехода: mov x, AX
mov CL, debug
cmp CL, 1
jne L
OutInt x
L: mov BX, 0
----------------------------
Однако, этот текст всегда будет занимать место в памяти и, если таких участков будет много, то исходный текст будет громоздким и время выполнения программы будет существенно больше. При использовании условной генерации время тратится только на этапе ассемблирования. При счете этих операций в исходном тексте не будет.
Использование ДУА в макросах
Пример 1. Опишем в виде макроса операцию сдвига значения переменной x на n разрядов вправо. n – явно заданное положительное число. Макрорасширение должно содержать минимально возможное число команд. Это можно сделать так:
shift macro x, n Обращения: shift A, 5 ; x = A, n = 5
ife n – 1 ; n – 1 = 0 ? После макрогенерации:
shr x, 1 mov CL, 5
else ; n > 1 shr A, CL
mov CL, n
shr x, CL
endif
endm
Пример 2. Константное выражение в if и ife может быть любым, но так как оно вычисляется на этапе макрогенерации, в нем не должно быть ссылок на величины, которые станут известными только при выполнении программы. Например, в константных выражениях не должно быть ссылок на регистры и ячейки памяти, не должно быть ссылок вперед. Константное выражение должно быть вычислено макрогенератором при первом проходе. Константное выражение часто бывает логическим, в нем могут использоваться операторы отношения: EQ, NE, LT, LE, GT, GE и логические операторы NOT, OR, XOR. Запишем в виде макроса SET_0 x операцию x = 0, если x – переменная размером в байт, слово или двойное слово.
SET_0 macro x
if type x EQ dword
mov dword ptr x, 0
elseif type x EQ word
mov word ptr x, 0
else mov byte ptr x, 0
endif
endm
Пример 3. Напишем еще одно макроопределение для сдвига вправо на n разрядов значения байтовой переменной B. Учтем, что при n = 0 сдвига нет и макрорасширение не должно появиться в тексте программы, а при n > 7 результат сдвига – это 0, поэтому сдвиг можно заменить записью нуля в B. Set_0 macro B, n
if (n GT 0) AND (n LT 8) ;; 0 < n <8
mov CL, n
shr b, CL
else
if n GE 8 ;; n >= 8
mov B, 0
endif
endif
endm
Пример 4 использование ifidn и ifdif. Поиск max или min из двух знаковых величин, хранящихся в байтовых регистрах, т.е. вычислить R1 = T (R1, R2), где T – это max или min, причем, должно генерироваться непустое макрорасширение только если R1 и R2 – это разные регистры. Если обращение к макросу будет Max_Min R1, R2, T, то необходимо проверять несовпадение первых двух параметров. И чтобы один макрос вычислял и max и min, нужно проверять и значение третьего параметра. Макрос может быть таким:
Max_Min macro R1, R2, T
local L
ifdif <R1>, <R2> ;; R1 и R2 – разные регистры
cmp R1, R2
ifidn <T>, <max> ;; T = max ?
jge L
else
jle L
endif
mov R1, R2
L: endif
endm
Макрокоманда Max_Min AL, BH, MIN приведет к следующим действиям макрогенератора: (пусть метка L заменилась меткой вида ??0110)
----------------------------
ifdif <AL>, <BH> cmp AL, BH в исходном тексте
cmp AL, BH ifidn <MIN>, <MAX> программы
ifidn <MIN>, <MAX> jge ??0110 окажется
jge ??0110 else cmp AL, BH
else jle ??0110 jle ??0110
jle ??0110 endif mov AL, BH
endif mov AL, BH ??0110:
mov AL, BH ??0110: ----------------------
??0110: ----------------------------
endif
26) Многомодульные программы в Ассемблере, директивы для организации межмодульных связей
Предположим, что есть модуль, содержащий процедуры ввода и вывода символов и строк, который подключается к основной программе на этапе редактирования:
masm p.asm, p.obj, p.list
link p.obj + ioproc.obj, p.exe
p.exe
Есть файл io.asm, содержащий описания макросов обращения к этим процедурам. Этот файл подключается на этапе ассемблирования с помощью директивы include io.asm.
Для иллюстрации организации многомодульной программы решим задачу: ввести текст не более, чем из 100 символов, заканчивающийся точкой и вывести его в обратном порядке, заменив прописные буквы на строчные.
Пусть программа состоит из двух модулей - головного и вспомогательного. Во вспомогательном описывается переменная EOT, значением которой является символ конца ввода текста, и процедура LOWLAT, заменяющую прописную на строчную.
Головной модуль должен вводить текст, записывать его в массив в обратном порядке, обращаясь к процедуре LOWLAT для замены больших букв на малые, а затем выводить этот массив на экран.
; вспомогательный модуль
public EOT, LOWLAT
D1 segment
EOT DB ‘.’ ; символ конца ввода
D1 ends
C1 segment
assume CS: C1
LOWLAT proc far
; процедура перевода больших букв в малые,
; на входе (AL) – любой символ, на выходе (AL) – малая буква
cmp AL, ‘A’ ; AL < ‘A’ или AL > ‘Z’, то nolat
jb nolat
cmp AL, ‘Z’
ja nolat
add AL, - ‘A’ + ‘a’
nolat: ret
LOWLAT endp
C1 ends
end
; головной модуль
include io.asm
extrn EOT: byte, LOWLAT: far
s segment stack
DB 256 dup (?)
s ends
d segment
txt DB 100 dup (?), ‘$’
d ends
c segment
assume SS: s, DS: d, CS: c
start: mov AX, d
mov DS, AX ; DS = d для доступа к TXT
mov AX, seg EOT
mov ES, AX ; ES = D1 для доступа к EOT
mov SI, 100
OutCH ‘>’ ; приглашение к вводу символа
inp: InCH AL ; ввод символа
cmp AL, ES:EOT ; если достигнут конец ввода
je PR ; то PR
call LOWLAT ; замена символа
dec SI
mov TXT[SI], AL
jmp inp
PR: lea DX, TXT[SI] ; вывод
OutSTR ; на экран TXT
Finish
c ends
end start
В основной программе OutCH <параметр> и InCH <параметр> - макрокоманды ввода и вывода на экран параметра OutSTR - макрокоманда вывода строки
Finish macro ; макрос окончания счета
mov AH, 4Ch
int 21h
endm
2) Передача параметров в подпрограммы по ссылке и по значению, организация рекурсивных подпрограмм
При передаче параметров по значению процедуре передается значение фактического параметра, оно копируется в ПП, и ПП использует копию, поэтому изменение, модификация параметра оказывается невозможным. Этот механизм используется для передачи параметров небольшого размера.
Например, нужно вычислить c = max (a, b) + max (7, a-1). Здесь все числа знаковые, размером в слово. Используем передачу параметров через регистры. Процедура получает параметры через регистры AX и BX, результат возвращает в регистре AX.
Процедура: AX = max (AX, BX)
max proc
cmp AX, BX
jge met1
mov AX, BX
met1: ret
max endp
Фрагмент вызывающей программы:
----------------------------------
; c = max (a,b) + max (7, a-1)
mov AX, a
mov BX, b
call max ; AX = max (a,b)
mov c, AX ; c = max (a,b)
mov AX, 7
mov BX, a
Dec BX
call max ; AX = max (7, a-1)
add c, AX
Передача параметров по ссылке.
Оформим как процедуру вычисление x = x div 16
Процедура имеет один входной параметр-переменную х, которой в теле процедуры присваивается новое значение. Т.е. результат записывается в некоторую ячейку памяти. И чтобы обратиться к процедуре с различными параметрами, например, a и b, ей нужно передавать адреса памяти, где хранятся значения переменных a и b. Передавать адреса можно любым способом, в том числе и через регистры. Можно использовать различные регистры, но чаще используются BX, BP, SI, DI. Пусть адрес параметра передается через регистр BX, тогда фрагмент программы:
; основная программа
----------------------------
lea BX, a
call Proc_dv
lea BX, b
call Proc_dv
-------------------------------
Процедура:
Proc_dv proc
push CX
mov CL, 4
shr word ptr [BX], CL ; x = x div 16
pop CX
ret
Proc_dv endp
Сдвиг на 4 разряда вправо эквивалентен делению нацело на 16 и выполняется быстрее.
Здесь первая команда в процедуре сохраняет в стеке значение регистра CX, так как затем использует CL в команде сдвига и возможно этот регистр используется в основной программе.
Т.к. регистров немного а и ПП и основная программа могут использовать одни и те же регистры, то при входе в ПП нужно сохранять в стеке значения регистров, которые будут использоваться в ПП, а перед выходом из нее восстанавливать значения этих регистров. Для поддержки этого, начиная с ix186, в систему команд введены команды сохранения в стеке и извлечения из него сразу всех регистров общего назначения pusha и popa, а, начиная с ix386, pushad popad.
Не нужно сохранять в стеке значение регистра, в который записывается результат работы ПП.
Передача параметров по ссылке в блоке параметров
Если параметров много, например, массив, адрес начала массива, как блока параметров, можно передать через регистр, даже если результат ПП не будет записываться по этому адресу.
Даны два массива целых положительных чисел без знака
X DB 100 dup (?)
Y DB 50 dup (?)
Вычислить DL = max (X[i]) + max (Y[i]), использовав процедуру max (A[i]), пересылая адрес массива через регистр BX, а результат сохраняя в AL.
-------------------------------; фрагмент программы
lea BX, X
mov CX, 100
call max ; AL = max (X[i])
mov DL, AL ; DL = max (X[i])
lea BX, Y
mov CX, 50
call max ; AL = max (Y[i])
ADD DL, AL
Процедура max: AL = max (A[0..n-1]), BX – начальный адрес A, CX = n
max proc
push CX
push BX
mov AL, 0 ; начальное значение max
met1: cmp [BX], AL
jle met2
mov AL, [BX]
met2: inc BX
loop met1
pop BX
pop CX
ret
max endp
8) Работа с подпрограммами в Ассемблере, способы передачи параметров
Подпрограмма – это программа, оформленная как процедура, к которой обращение происходит из ОС, заканчивается командой возврата ret.
Подпрограмма, как вспомогательный алгоритм, к которому возможно многократное обращение помощью команды call, тоже оформляется как процедура с помощью директив proc и endp. Структуру процедуры можно оформить так:
<имя процедуры> proc <параметр>
<тело процедуры>
ret
<имя процедуры> endp
В Ассемблере один тип подпрограмм – процедура. Размещать ее можно в любом месте программы, но так, чтобы управление на нее не попадало случайно, а только по команде call. Поэтому описание ПП принято располагать в конце программного сегмента (после последней исполняемой команды), или вначале его – перед первой исполняемой команды.
Графическое представление
1) cseg segment …… 2) cseg segment 3)cseg_pp segment……
beg: ------------------- < подпрограмма 1> <подпрограммы>
------------------- <подпрограмма 2> cseg _pp ends
---------------------- -------------------- cseg segment…
-------------------- --------------- ------------------
fin: -------------------- <подпрогрмма N> beg: ------------------------
< подпрограмма 1> beg: ------------------- cseg ends
<подпрограмма 2> --------------------- end beg
---------------------------
<подпрогрмма N> cseg ends
cseg ends end beg
end beg
Если программа содержит большое количество подпрограмм, то ПП размещают в отдельном кодовом сегменте – вариант структуры 3).
Замечания:
1) После имени в директивах proc и endp двоеточие не ставится, но имя считается меткой, адресом первой исполняемой команды процедуры.
2) Метки, описанные в ПП, не локализуются в ней, поэтому они должны быть уникальными в рамках всей программы.
3) Параметр в директиве начала процедуры один – FAR или NEAR, если отсутствует, то NEAR.
Основная проблема при работе с ПП в Ассемблере – это передача параметров и возврат результатов в вызывающую программу.
Существуют различные способы передачи параметров: 1) по значению, 2) по ссылке, 3) по возвращаемому значению, 4) по результату, 5) отложенным вычислением.
Параметры можно передавать: 1) через регистры, 2) в глобальных переменных, 3) через стек,
4) в потоке кода, 5) в блоке параметров.
Передача параметров через регистры – наиболее простой способ. Вызывающая программа записывает в некоторые регистры фактические параметры, подпрограмма их оттуда извлекает и обрабатывает и в какие-то регистры записывает результат. Работает только, если параметров мало.
Примерами использования этого метода являются вызовы некоторых прерываний OC и BIOS.
Когда регистров не хватает, один из способов обойти это ограничение – записать параметр в сегмент данных или глобальную переменную, к которой затем обращаться в ПП. Но этот метод считается не эффективным, так как может оказаться невозможной рекурсия, и даже простое повторное обращение к ПП.
Передача параметров через стек. Перед обращением к процедуре фактические параметры (их значения или адреса) записываются в стек, а процедура их из стека извлекает. Именно этот способ используют языки высокого уровня.
Передача параметров в потоке кода заключается в том, что данные, передаваемые в ПП, располагаются сразу за командой обращения к ПП call. ПП, чтобы использовать эти данные, должна обратиться к ним по адресу, который записывается в стек автоматически как адрес возврата из ПП. Но ПП в этом случае должна перед командой возврата изменить адрес возврата на адрес байта, следующий за передаваемыми параметрами. Этот метод реализует передачу параметров медленнее, чем через регистры, глобальные переменные или стек, но примерно также, как и метод передачи параметров в блоке параметров.
Блок параметров – это участок памяти, содержащий параметры и располагающийся обычно в сегменте данных. Процедура получает адрес начала этого блока при помощи любого из рассмотренных методов: в регистре, в переменной, в стеке, в коде или даже в другом блоке параметров. Примеры использования этого способа – многие функции OC и BIOS, например, поиск файла, использующий блок параметров DTA, или загрузка и исполнение программы, использующая блок параметров EPB.
О передаче параметров в ПП
Передача по значению:
mov AX, word ptr value
call PP
Передача по ссылке:
mov AX, offset value
call PP
Передача параметров по возвращаемому значению объединяет передачу по значению и по ссылке: процедуре передается адрес переменной, она делает локальную копию этого параметра, работает с этой копией, а в конце процедуры записывает эту копию по переданному адресу. Этот механизм оказывается эффективным, если процедуре приходится много раз обращаться к параметру в глобальной переменной.
Передача параметров по результату заключается в том, что ПП передается адрес только для записи по этому адресу результата работы ПП.
Передача параметров по имени макроопределения. Пример:
name macro parametr
mov AX, parametr
name endm
Обращение к ПП может быть таким:
name value ; обращение к макро
call PP ; обращение к ПП
Передача параметров отложенным вычислением. Как и в случае передачи параметров по имени, процедура получает адрес ПП, вычисляющей значение параметра. Этот механизм чаще используется в системах искусственного интеллекта и в ОС.
14) Передача параметров через стек, локальные параметры в процедуре
Передача параметров через стек.
Этот способ передачи параметров называют универсальным, его можно использовать при любом количестве параметров, хотя он сложнее, чем передача параметров через регистры. Но для передачи результатов чаще используют регистры.
Если ПП имеет k параметров PP(a1, a2, ….ak) размером в слово и параметры сохраняются в стеке в последовательности слева направо, то команды, реализующие обращение к ПП, должны быть следующими:
; обращение к процедуре PP содержимое стека при входе в PP
push
a1 SP av
push a2 ak
------------- -----
push ak a2
call PP a1
av: ------------- -----
SS
Обращение к параметрам в процедуре можно осуществить с помощью регистра BP, присвоив ему значение SP.
Но при этом мы испортим старое значение BP, которое может быть используется в основной программе. Поэтому следует вначале сохранить старое значение BP в стеке, а затем использовать его для доступа к параметрам, т.е тело процедуры должно начинаться следующими командами:
в стеке после вып-ия этих команд:
P
P proc
near SP, BP BPст
push BP + 2 av
mov BP, SP + 4 ak
------------ -------
a1
-------
Для доступа к фактическим параметрам мы должны использовать адресацию по базе со смещением, например, mov AX, [BP + 4] ; ak -> AX
После «входных действий» в ПП идут команды, реализующие вспомогательный алгоритм, а за ними д.б. команды, реализующие «выходные действия»:
pop BP ; восстановить старое значение BP
ret 2*k ; очистка стека от k параметров (по 2 байта)
PP endp ; возврат в вызывающую программу
n в команде ret n – это количество освобождаемых байтов в стеке, поэтому количество параметров д. б. умножено на длину параметра.
Команда ret вначале считывает значение av, а затем удаляет из стека параметры. Очистку стека можно выполнять не в ПП, а после выхода из нее, в основной программе, сразу после команды call PP, например, командой add SP, 2*k
Каждый способ имеет свои достоинства и недостатки, если в ПП, то исполняемый код будет короче, если в основной программе, то можно вызвать ПП несколько раз с одними и теме же параметрами последовательными командами call.
Для удобства использования параметров, переданных через стек, внутри ПП можно использовать директиву equ, чтобы при каждом обращении к параметрам не писать точное смещение относительно BP.
push x
push y
push z
call PP
-----------
PP proc near ; процедура
push BP
mov BP, SP
pp_x equ [BP + 8]
pp_y equ [BP + 6]
pp_z equ [BP + 4]
---------------------
mov AX, pp_x ; использование параметра x
----------------------
pop BP
ret 6
pp endp
Пример передачи параметров через стек.
Пусть процедура заполняет нулями массив A[0..n-1] , основная программа обращается к ней для обнуления массивов X[0..99] и Y[0..49]. Через стек в ПП передается имя массива и его размер, размер можно передавать по значению, а имя массива нужно передавать по ссылке, т.к. этот параметр является и входным и выходным.
zero_1 proc
push BP ; входные
mov BP, SP ; действия
push BX ; сохранение значений
push CX ; регистров
mov CX, [BP + 4] ; CX = n считывание из стека
mov BX, [BP + 6] ; BX = A параметров
m1: mov byte ptr [BX], 0 ; цикл обнуления
inc BX ; массива
loop m1 ; A[0..n-1]
; восстановление регистров и выходные действия
pop CX
pop BX
pop BP
Ret 4
zero_1 endp
Фрагмент основной программы:
X DB 100 dup (?)
Y DB 50 dup (?)
------------------------
lea AX, X ; загрузка параметров:
push AX ; адреса массива X
mov AX, 100 ; и его размера
push AX ; в стек
call zero_1 ; обращение к ПП
lea AX, Y ; загрузка параметров для массива Y
puah AX ;
mov AX, 50 ;
push AX ;
call zero_1 ; обращение к ПП
---------------------------
Использование локальных параметров.
Если локальных параметров немного, то их размещают в регистрах, но если их много, то возможны различные варианты: им можно отвести место в сегменте данных, но тогда большую часть времени эта область памяти не будет использоваться.
Лучший способ – разместить локальные параметры в стеке на время работы ПП, а перед выходом из ПП их удалить. Для этого после входных действий в процедуре нужно уменьшить значение указателя на вершину стека SP на количество байтов, необходимых для хранения локальных величин и затем записывать их в стек и извлекать их можно с помощью выражений вида: [BP – n], где n определяет смещение локального параметра относительно значения BP, а к фактическим параметрам – [BP + k], где k определяет положение фактического параметра.
Н
апример,
если предполагается, что ПП будет
использовать 3 локальные параметра
размером в слово, то стек графически
можно представить так: SP Lz
BP - 4 Ly
BP - 2 Lx
BP BPct
BP + 2 av
BP + 4 ak
-------
a1
---------
SS
При выходе из процедуры перед выполнением завершающих действий нужно возвратить регистру SP его значение.
Если в стеке хранятся и фактические и локальные параметры, то начало процедуры и ее завершение должно выглядеть следующим образом:
PP proc
push BP ; сохранить старое значение BP
mov BP, SP ; (SP) в BP
sub SP, k1 ; отвести в стеке k1 байтов под лок пар - ы
push AX ; сохранить в стеке регистры,
-------------- ; используемые в ПП
<тело процедуры>
------------- ; восстановить регистры
pop AX ;
mov SP, BP ; восстановить SP, т.е. освободить место
pop BP ; в стеке от локальных параметров восстановить BP ret k2 ;очистка стека от фактических параметров и возврат в вызывающую программу
PP endp ; конец ПП
Подсчет количества различных символов в заданной строке.
Строка задана как массив символов. Начальный адрес ее передадим в ПП через регистр BX, длину строки через CX, а результат - через AX. Создадим процедуру, в которой выделяется 256 – ый локальный массив L по количеству возможных символов. K-ому элементу этого массива будем присваивать единицу, если символ, цифровой код которого равен K, в заданной строке существует. Затем подсчитаем количество единиц в этом массиве. Вначале весь массив обнуляется. К первому элементу этого массива можно обратиться так: L1 = [BP – 256] к K – ому Lk = [BP – 256 + k]. Работая со строками, эту задачу можно решить проще.
Count_s proc ;
; входные действия
push BP
mov BP, SP
sub SP, 256
push BX
push CX
push SI
; Обнуление локального массива
mov AX, CX ; сохранение длины исходной строки
mov CX, 256 ; возможное количество символов
mov SI, 0 ; индекс элемента массива
m1: mov byte ptr [BP – 256 + SI], 0 ;
inc SI
loop m1
; просмотр заданной строки и запись 1 в локальный массив
mov CX, AX ; длину строки в CX
mov AX, 0
m2: mov AL, [BX] ; код очередного символа в AL
mov SI, AX ; пересылаем его в SI
mov byte ptr [BP – 256 + SI], 1 ; пересылаем 1 в к –й элемент массива
inc BX
loop m2 ;
; подсчет количества 1 в локальном массиве
mov AX, 0 ; результат будет в AX
mov CX, 256 ; количество повторений цикла
mov SI, 0 ; индекс массива в SI
m3: cmp byte ptr [BP – 256 + SI], 1
jne m4
inc AX
m4: inc SI
loop m3
; выходные действия
pop SI ; восстановление
pop CX ; регистров
pop BX ;
mov SP, BP ; освобождение стека от локальных параметров
pop BP ; восстановить старое BP
ret
const_s endp
15) Команды пересылки безусловной и условной, команды загрузки адреса
К командам пересылки относят команду обмена значений операндов.
XCHG OP1, OP2 ; r«r Ú r«m
MOV AX, 10h ;
MOV BX, 20h ;
XCHG AX, BX ; (AX) = 20h, (BX) = 10h
Для перестановки значений байтов внутри регистра используют BSWOP.
(EAX) = 12345678h
BSWOP EAX ; (EAX) = 78563412h
Команды конвертирования:
CBW ; безадресная команда, (AL) ® AX. Конвертирует байт в слово
CWD ;( AX) ® DX:AX, конвертирует слово в двойное слово
CWE ; ( AX) ® EAX (для i386 и выше) из 16 в 32 разряда
CDF ; (EAX) ® EDX:EAX (для i386 и выше) из 32 в 64 разряда
Команды условной пересылки CMOVL
CMOVL AL, BL ; если (AL) < (BL), то (BL) ® AL
Загрузка адреса.
LEA OP1, OP2 ; вычисляет адрес OP2 и пересылает 1 операнду, который м.б. только регистром.
LEA BX, M[BX][DI]
16) Команды для работы со строками – MOVS, LODS, STOS, организация работы со строками
Строка – это последовательность байтов, слов или двойных слов. Все команды для работы со строками считают, что строка-источник находится по адресу DS:SI (DS:ESI), а строка приемник – по адресу ES:DI (ES:EDI). Все команды работают с одним элементом строки: одним байтом, одним словом или одним двойным словом в зависимости от команды и /или от типа операндов.
Чтобы выполнить действие над всей строкой, слева от команды записывается специальный префикс. Префикс – это команда повторения операции. Префикс действует только на команды работы со строками, поставленный рядом с любой другой командой он никак не влияет на ее выполнение.
Существуют следующие префиксы:
rep – повторять
repe – повторять пока равно
repz – повторять пока ноль
repne – повторять пока не равно
repnz – повторять пока не ноль
1) Префикс повторять rep <строковая команда> заставляет повторяться указанную команду n раз, где n - содержимое регистра CX (ECX). Если (CX) = 0, то команда не выполнится не разу.
2) repe <стр. команда> = repz <стр. команда> Указанная строковая команда будет повторяться до тех пор пока флаг ZF = 1, но не более n раз, где n = (CX) или (ECX). Работу команды с этими префиксами можно на псевдокоде описать так:
m: if (CX) = 0 then goto m1;
(CX) = (CX) – 1;
< стр команда>;
if ZF = 1 then goto m
m1: -------------------------
repne <стр команда> = repnz <стр команда> Указанная строковая команда повторяется до тех пор, пока флаг ZF = 0, но не более n раз, где n – содержимое счетчика CX (ECX). Семантика этих префиксов на псевдокоде:
m: if (CX) = 0 then goto m1;
(CX) = (CX) – 1;
<стр команда>;
if ZF = 0 then goto m;
m1: ---------------------------
Префикс rep используется обычно с командами: movs, lods, stos, ins и outs
Префиксы repe, repz, repne, repnz с командами cmps и scas.
Команды копирования для строк.
movs op1, op2 ; источник op2 = DS:SI (DS:ESI), приемник op1 = ES:DI (ES:EDI)
movsb ; байт данных из (DS:SI) пересылается в ES:DI
movsw ; слово данных из (DS:SI) пересылается в ES:DI
movsd ; дв. слово данных из (DS:SI) пересылается в ES:DI
При использовании команды 1) - movs Ассемблер сам определяет по типу указанных в команде операндов сколько байтов данных нужно переслать – 1, 2 или 4. В этой команде можно изменить DS на другой регистр: ES, GS, FS, CS, SS, но регистр операнда приемника ES изменять нельзя.
Чаще команды для строк используются без операндов. После выполнения любой команды со строками содержимое регистров SI и DI автоматически изменяется в зависимости от значения флажка DF. Если DF = 0 , то (SI/ESI) и (DI/EDI) увеличивается на 1 или 2 или 4, Если DF = 1 , то (SI/ESI) и (DI/EDI) уменьшается на 1 или 2 или 4 в зависимости от операндов или кода команды.
Команды считывания строки из памяти и загрузки в регистр AL, AX или EAX
lods op2 ; op2 – источник DS:SI или DS:EDI
lodsb ; 1 байт из DS:SI или DS:EDI -> AL
lodsw ; 2 байта из DS:SI или DS:EDI -> AX
lodsd 4 байта из DS:SI или DS:EDI -> EAX
lods op2 работает как lodsb или lodsw или lodsd в зависимости от типа операнда и здесь DS можно заменить на ES, FS, GS, CS SS.
Команды записи строки из регистра AL, AX или EAX в память по адресу ES:DI или ES:EDI.
stos op1
stosb
stosw
stosd ; для i386 и выше
При использовании этих команд с префиксом rep строка длиной в (CX) (ECX) ячеек заполнится числом, хранящимся в аккумуляторе AL, AX или EAX
20) Команды работы со строками: CMPS, SCAS, INS, OUTS
Команды сравнения строк.
cmps op1, op2 ;
cmpsb ; сравнение байтов
cmpsw ; сравнение слов
cmpsd ; для i386 и > сравнение двойных слов
По команде 1) в зависимости от типа операндов сравнивается содержимое байтов, слов или дв. слов, расположенных по адресам источника и приемника.
В остальном команды сравнения работают также, как и команды пересылки. Эти команды используются с префиксами 1) repe / repz и 2) repne / repnz
При использовании префиксов 1) сравнение идет до первого не совпадения, 2) до первого совпадения.
Команды: 1) scas op1 ; op1 - приемник
2) scasb ; сравнивает (AL) с байтом из ES:DI / ES:EDI
3) scasw ; сравнивает (AX) со словом из ES:DI / ES:EDI
4) scasd ; для i386 и выше, сравнивает (EAX) с двойным словом из ES:DI / ES:EDI
При работе команды 1) количество сравниваемых байтов зависит от разрядности операнда.
Команды cmps и scas устанавливают флаги аналогично команде cmp.
Считывание из порта ввода/вывода.
ins op1, DX
insb
insw
insd ; i386 и >
Эти команды считывают из порта ввода/вывода, номер которого содержится в регистре DX, байт (insb), слово (insw) или двойное слово (insd) и пересылает их в память по адресу ES:DI или ES:EDI. Команда 1) принимает одну из форм 2), 3), 4) в зависимости от типа операнда.
При использовании с префиксом rep она считывает из порта ввода/вывода блок данных (байтов, слов или двойных слов) длинной, определяемой регистром CX (ECX) и пересылает в память по адресу приемника.
Запись в порт в/в содержимого ячейки памяти, размером в байт, слово или дв. слово, находящейся по адресу DS:SI или DS:EDI
1) outs DX, op2
2) outsb
3) outsw
4) outsd ; i386 и >
Номер порта в командах работы с портами в/в должен находиться в регистре DX. В команде outs можно заменить DS на ES, FS, GS, CS SS. Используя префикс rep можно переслать в порт блок данных размером в (CX) или (ECX) байтов, слов или двойных слов.
Команды управления флагами.
После выполнения команд со строками изменяется содержимое регистров – индексов в зависимости от значения флажка направления DF. Автоматически его значение не изменяется, его должен изменить программист с помощью команд:
cld ; CLear Df, DF = 0
std ; SeT Df, DF = 1
Программист может установить следующие флажки:
stc ; CF = 1
clc ; CF = 0
cmc ; инвертировать флаг переноса
lahf ; копирует младший байт регистра FLAGS а AH
sahf ; из AH загружает флажки SF, ZF, AF, PF, CF
cli ; IF = 0
sti ; IF = 1
salc ; установить регистр AL в соответствии с CF
Загрузка сегментных регистров
lds op1, op2
les op1, op2
lfs op1, op2
lgs op1, op2
lss op1, op2
Первый операнд регистр. Для всех команд op2 – переменная в ОП размером в 32 или 48 бит в зависимости от разрядности операндов. Первые 16 бит этой переменной загружаются в соответствующий сегмент DS, ES и т.д., а следующие 16 или 32 - в регистр общего назначения, указанный в качестве первого операнда. В защищенном режиме значение, загружаемое в сегментный регистр, всегда должно быть правильным селектором сегмента, в реальном режиме любое число может использоваться как селектор.
S1 DB “ABC$”
ADR DD S1
les DI, ADR
В переменную ADR записывается полный адрес, определяемый именем S1 (Seg:Ofs). В ES записывается значение сегментной части адреса S1, а в DI ее смещение.
Пример 1 использования команд работы со строками:
X DW 100 dup (?)
Y DW 100 dup (?)
Выполнить пересылку содержимого одной области памяти в другую X = Y
CLD ; DF = 0 (просмотр строки слева направо)
lea SI, Y ; DS:SI – начало Y
push DS
pop ES ; (ES) = (DS)
lea DI, X ; ES:DI –начало X
mov CX, 100
rep movsw
Пример 2. В строке S, состоящей из 500 символов заменить первое вхождение звездочки на точку.
CLD ; просмотр строки слева направо
push DS
pop ES
lea DI, S ; ES:DI –начало S
mov CX, 500
mov AL, ‘ * ‘
repne scasb ; сканирование строки S и сравнение с (AL)
jne finish ; ‘ * ‘ в строке нет
mov byte ptr ES:[DI – 1], ‘ . ‘ ; замена
finish: --------------------------
Здесь используется выражение [DI – 1] т.к. после того как звездочка найдена DI увеличивается на 1 и указывает на следующий символ.
32) Работа с подпрограммами в Ассемблере, организация рекурсивных подпрограмм
Основные трудности, возникающие при реализации рекурсии – это опасность «зацикливания » рекурсии и использование параметров. Зацикливания не произойдет, если в процедуре есть рекурсивная и не рекурсивная ветви и при выполнении некоторого условия вычислительный процесс пойдет по не рекурсивной ветви.
Рекурсивное обращение ПП можно представить, если предположить, что при каждом обращении создается копия ПП и адреса возврата сохраняются в стеке. А структура стека позволяет извлекать их в последовательности, обратной поступлению.
Также решается и проблема с параметрами. В рекурсивную процедуру нельзя передавать параметры через ячейки памяти в сегменте данных, а если такая необходимость возникает, то при
входе в ПП их необходимо сохранять в стеке, а при выходе из нее восстанавливать. Это значит, что лучше сразу параметры передавать через стек.
Пример рекурсивной функции
F(n) = 1, если n = 0 или n = 1 и
F(n) = F(n – 1) + F(n – 2), если n >1
Вычисление n-го ряда Фибоначчи
Fib proc ; BX = F(n), AL = n
cmp AL, 1
ja m1 ; если n > 1, m1
; не рекурсивная ветвь
mov BX, 1 ; если n < 1 или n = 1 BX = F(n) = 1
ret
; рекурсивная ветвь
m1: push AX ;
dec AL ; AL = n - 1
call Fib ; BX = F(n-1)
push BX ; сохранить в стеке F(n-1)
dec AL ; AL = n - 2
call Fib ; BX = F(n – 2)
pop AX ; AX = F(n – 1)
add BX, AX ; BX = F(n – 2) + F(n – 1)
pop AX ; восстановить AX
ret
Fib endp
34) Не вошедшее
Форматы данных
Процессор ix86 вместе с сопроцессором могут обрабатывать большой набор различных типов данных: целые числа без знака, целые числа со знаком, действительные числа с плавающей точкой, двоично-десятичные числа, символы, строки, указатели.
Целые числа без знака могут занимать байт, слово, двойное слово и принимать значения из диапазонов: 0 - 255, 0 - 65535, 0 – 4294967295 соответственно.
Ц
елые
числа со знаком могут занимать также
байт, слово, двойное слово. Они хранятся
в дополнительном коде и имеют следующий
вид.
Дополнительный код положительного числа равен самому числу.
Дополнительный код отрицательного числа в любой системе счисления может быть получен по формуле: X = 10n - |X|, где n – разрядность числа.
Например, представим в слове отрицательное 16-ричное число -AC716: 104 - AC7 = F539.
Дополнительный код двоичного числа может быть получен инверсией разрядов и прибавлением 1 к младшему разряду.
Например, - 12 в байте: 1) 12 = 000011002, 2) инверсия – 111100112,
3) дополнительный код – 111101002.
Рассмотрим выполнение операции вычитания в машине: дополнительный код вычитаемого прибавляется к уменьшаемому.
Например: 65 – 42 = 23.
1) 65 = 010000012,
2) 42 = 001010102,
3) - 42 = 110101102,
4) 65 - 42 = 000101112 = 20 + 21 + 22 + 24 = 1+2+4+16=23.
Числа с плавающей точкой могут занимать 32 бита или 64 бита или 80 бит, и называются короткое вещественное, длинное вещественное, рабочее вещественное. Формат числа с плавающей точкой состоит из трех полей: <знак числа>, <машинной порядок>, <мантисса>.
короткое вещественное 1+ 8 + 23 - 10±32 - + 10±32
длинное вещественное 1+ 11 + 52 - 10±308 - + 10±308
рабочее вещественное 1+ 15 + 64 - 10±4932 - + 10±4932.
Машинный порядок (Пм) включает в себя неявным образом знак порядка и связан с истинным порядком (Пи) формулой: Пм = Пи + 12710 (102310, 1638310).
П
редполагается,
что мантисса нормализована и старший
единичный разряд мантиссы не помещается
в разрядную сетку.
Например, для короткого вещественного:
П
ример,
306010
представить в виде числа с плавающей
точкой, занимающего 4 байта.
1) 306010 = BF416 £ |M| <1
2) нормализуем число 0. BF4*10316
3) получим машинный порядок Пм = 316 + 7F16 = 8216
4) запишем в разрядную сетку в 2-ичной системе счисления:
0 1000 0010 011 1111 0100 0000 0000 00002
Или в 16-ричном виде: 413F400016.
0100 0001 0 011 1111 0100 0000 0000 00002
Двоично-десятичные данные - процессором могут обрабатываться 8-ми разрядные в упакованном и неупакованном формате, и сопроцессором могут обрабатываться 80-ти разрядные данные в упакованном формате. Упакованный формат предполагает хранение двух цифр в байте, а неупакованный – хранит одну цифру в цифровой части байта.
Символьные данные – символы в коде ASCII. Для любого символа отводится один байт.
Строковые данные – это последовательности бит, байт, слов или двойных слов.
Указатели – существуют два типа указателей: длинный указатель, занимающий 48 бит -селектор(16) + смещение(32) и короткий указатель, занимающий 32 бита - только смещение.
Директивы внешних ссылок
Директивы внешних ссылок позволяют объявлять глобальные величины, устанавливать связь между различными модулями, подключать файлы, расположенные на дискеэ
Public <имя> [, <имя>,…,<имя>] – определяет указанные имена как глобальные величины, к которым можно обратиться из других модулей. <имя> – имена меток и переменных, определенных с помощью директивы ‘=’ и EQU.
Если некоторое имя определено в модуле А как глобальное и к нему нужно обратиться из других модулей В и С, то в этих модулях должна быть директива вида
EXTRN <имя>:<тип> [,<имя>:<тип>…]
Здесь имя то же, что и в Public, а тип определяется следующим образом:
если <имя> – это имя переменной, то типом может быть: BYTE, WORD, DWORD,
FWORD, QWORD, TWORD;
если <имя> – это имя метки, то типом может быть NEAR, FAR.
Директива EXTRN говорит о том, перечисленные имена являются внешними для данного модуля.
Пример:
В модуле А содержится:
Public TOT
-------/-------
TOT DW 0 ;
чтобы обратиться из В и С к имени TOT, в них должна быть директива
EXTRN TOT:WORD
В Ассемблере есть возможность подключения на этапе ассемблирования модулей, расположенных в файлах на диске INCLUDE <имя файла>
Пример: INCLUDE С:\WORK\Prim.ASM Prim.ASM, расположенный в указанной директории, на этапе ассемблирования записывается на место этой директивы.
