Программирование в LINUX (первый пример).
Си-модуль:
#include < stdlib.h >
#include < stdio.h >
extern void asm_calc(int,int,int*,int*);
int main(void){
int i,j,iplusj=0,imulj=0;
scanf("%d %d",&i,&j);
printf("C :\t i+j=%d\n",i+j);
printf("C :\t i*j=%d\n",i*j);
asm_calc(i,j,&iplusj,&imulj);
printf("ASM:\t i+j=%d\n",iplusj);
printf("ASM:\t i*j=%d\n",imulj);
return 0;
};
ASM-модуль:
SECTION .TEXT
GLOBAL asm_calc
asm_calc:
; параметры: int i,int j, int* iplusj, int* imulj
; то есть - число 1,число 2, указатель на результат 1, указатель на результат 2
; по правилам вызова С - операнды в стеке.
; [esp] - адрес возврата
; [esp+4] - первый аргумент
; [esp+8] - второй и т.д.
PUSH EAX PUSH ECX PUSH EBX PUSH EDX
|
|
MOV EAX, [ESP+4+4*4] |
; i     4*4 - сохранённые в стек регистры |
MOV EBX, [ESP+8+4*4] |
; j |
MOV ECX, [esp+16+4*4] |
; imulj |
MUL EBX |
; Результат в edx:eax |
MOV [ECX],EAX |
; Результат перемножения заброшен по адресу в ecx |
MOV ECX, [ESP+12+4*4]
|
; iplusj |
MOV EAX, [ESP+4+4*4] |
; i |
MOV EBX, [ESP+8+4*4] |
; j |
ADD EAX,EBX
MOV [ECX],EAX |
; Результат сложения заброшен туда же - по адресу в ecx
|
POP EDX POP EBX POP ECX POP EAX
RET
|
|
makefile:
LDFLAGS=-g
CFLAGS=-g
all: main
main: main.o asm_file.o
main.o: main.c
asm_file.o: asm_file.asm
nasm -g -o $@ -f elf $^
Утилита make.
Задача утилиты make - автоматически определять, какие файлы проекта
были изменены и требуют компиляции, и применять необходимые для этого команды.
Хотя примеры применения относятся к использованию утилиты для описания процесса
компиляции программ на языке С/С++, утилита может использоваться для описания
сценариев обновления любых файлов.
Структура Makefile.
Мakefile состоит из так называемых "правил", имеющих вид:
имя-результата:     исходные-имена ...
команды
...
...
имя-результата - это обычно имя файла, генерируемого
программой, например, исполняемый или объектный файл. "Результатом" может
быть действие никак не связанное с процессом компиляции, например,
clean - очистка.
исходное-имя - это имя файла, используемого на вводе,
необходимое, чтобы создать файл с именем-результата.
команда - это действие, выполняемое утилитой make.
Правило может включать более одной команды, В начале каждой команды надо
вставлять отступ (символ "Tab"). Команда выполняется, если один из
файлов в списке исходные-имена изменился. Допускается написание правила
содержащего команду без указания зависимостей. Например, можно создать
правило "clean", удаляющее объектные файлы проекта, без указания имен.
Итак, правила объясняют как и в каком случае надо пересобирать определённые файлы проекта.
Стандартные правила:
К числу стандартных правил относятся:
- all - основная задача, компиляция программы.
-
install - копирует исполняемые коды программ, библиотеки
настройки и всё что необходимо для последующего использования
-
uninstall - удаляет компоненты программы из системы
-
clean - удаляет из директории проекта все временные и
вспомогательные файлы.
Пример Makefile:
Ниже приводится простой пример (номера строк добавлены для ясности).
# Создать исполняемый файл "client"
1 client: conn.o
2     g++ client.cpp conn.o -o client
# Создать объектный файл "conn.o"
3 conn.o: conn.cpp conn.h
4     g++ -c conn.cpp -o conn.o
В этом примере строка, содержащая текст client: conn.o
, называется
"строкой зависимостей", а строка g++ client.cpp conn.o -o client
называется "правилом" и описывает действие, которое необходимо выполнить.
1 Задается цель -- исполняемый файл client, который
зависит от объектного файла conn.o;
2 Правило для сборки данной цели;
3 Задается цель conn.o и файлы, от которых она
зависит -- conn.cpp и conn.h;
4 Описывается действие по сборке цели conn.o.
Строки, начинающиеся с символа "#", являются комментариями.
"Ложная" цель:
Обычно "ложные" [phony] цели, представляющие "мнимое" имя
целевого файла, используются в случае возникновения конфликтов между именами целей
и именами файлов при явном задании имени цели в командной строке. Допустим в
makefile имеется правило, которое не создает ничего, например:
clean:
rm *.o temp
Поскольку команда rm не создает файл с именем clean, то такого
файла никогда не будет создано и поэтому команда make clean
всегда
будет срабатывать.
Декларация .PHONY:
Однако, данное правило не будет работать, если в текущем каталоге
будет существовать файл с именем clean. Поскольку цель clean
не имеет зависимостей, то она никогда не будет считаться устаревшей и,
соответственно, команда 'rm *.o temp
' никогда не будет выполнена. (при
запуске make проверяет даты модификации целевого файла и тех файлов, от
которых он зависит. И если цель оказывается "старше", то make выполняет
соответствующие команды-правила ) Для устранения подобных проблем предназначена
специальная декларация .PHONY, объявляющая "ложную" цель. Например:
.PHONY : clean
Таким образом мы указываем необходимость исполнения цели, при явном ее указании,
в виде make clean
вне зависимости от того существует файл с таким именем или нет.
Переменные.
Определить переменную в makefile вы можете следующим образом:
$VAR_NAME=value
В соответствии с соглашениями имена переменных задаются в верхнем регистре:
$OBJECTS=main.o test.o
Чтобы получить значение переменной, необходимо ее имя заключить в круглые
скобки и перед ними поставить символ '$', например:
$(VAR_NAME)
В makefile-ах существует два типа переменных: "упрощенно вычисляемые" и
"рекурсивно вычисляемые". В рекурсивно вычисляемых переменных все ссылки
на другие переменные будут замещены их значениями, например:
TOPDIR=/home/tedi/project
SRCDIR=$(TOPDIR)/src
При обращении к переменной SRCDIR вы получите значение
/home/tedi/project/src.
Однако рекурсивные переменные могут быть вычислены не всегда, например
следующие определения:
CC = gcc -o
CC = $(CC) -O2
выльются в бесконечный цикл. Для разрешения этой проблемы следует
использовать "упрощенно вычисляемые" переменные:
CC := gcc -o
CC += $(CC) -O2
Где символ ':=' создает переменную CC и присваивает
ей значение "gcc -o". А символ '+=' добавляет "-O2"
к значению переменной CC.
Вывод на экран через INT 80H.
SECTION .TEXT
GLOGAL _start
_start:
MOV EAX,15
MOV EBX,20
MUL EBX
CMP EAX,300
JNZ error
all_ok:
|
|
MOV EDX,len1 |
; third argument: message length |
MOV ECX,msg1 |
; second argument: pointer to message to write |
MOV EBX,1 |
; first argument: file handle (stdout) |
MOV EAX,4 |
; system call number (sys_write) |
INT 0x80 |
; call kernel |
JMP exit1
error: MOV EDX,len2 |
; third argument: message length |
MOV ECX,msg2 |
; second argument: pointer to message to write |
MOV EBX,1 |
; first argument: file handle (stdout) |
MOV EAX,4 |
; system call number (sys_write) |
INT 0x80 |
; call kernel |
JMP exit1 exit1: |
|
MOV EBX,0 |
; first syscall argument: exit code |
MOV EAX,1 |
; system call number (sys_exit) |
INT 0x80 |
; call kernel |
SECTION .DATA
|
; section declaration
|
msg1 DB "All OK!",0xa
|
|
len1 EQU $ - msg1 |
; length of our string |
msg2 DB "Error!",0xa
len2 EQU $ - msg2 |
|
makefile:
all:main
main: main.o
ld -s -o main main.o
main.o:main.asm
nasm -f elf $^
clean:
rm main *.o
Вывод с помощью printf.
EXTERN printf
SECTION .TEXT |
; section declaration |
GLOBAL main main:
; write our string to stdout |
|
MOV EAX,15
PUSH EAX
PUSH DWORD msg
CALL printf
ADD ESP,8
; and exit |
|
MOV EBX,0 |
; first syscall argument: exit code |
MOV EAX,1 |
; system call number (sys_exit) |
INT 0x80 |
; call kernel |
SECTION .DATA
|
; section declaration
|
msg DB "And the number in eax=%d",0xa,0x0 |
|
makefile:
LDFLAGS=-g
all:main
main: main.o
main.o:main.asm
nasm -g -f elf $^
clean:
rm main *.o
Вызов функций scanf и printf из Nasm.
Функции scanf и printf определены в библиотеке glibc.
Эти функции можно указать в ассемблерной программе как внешние с
помощью директивы EXTERN. Объектный файл получается стандартным
образом. А вот при компоновке (линковке) необходимо указать библиотеку
libc.so либо использовать для компоновки gcc, который,
в отличие от ld по умолчанию компонует все объектные файлы с
библиотекой libc.so
global _start
;Объявляем используемые внешние функции из libc
EXTERN exit
EXTERN puts
EXTERN scanf
EXTERN printf
;Сегмент кода:
SECTION .TEXT
;Функция main:
_start:
;Параметры передаются в стеке:
PUSH DWORD msg
CALL puts
;По конвенции Си вызывающая процедура должна
;очищать стек от параметров самостоятельно:
SUB ESP, 4
PUSH DWORD a
PUSH DWORD b
PUSH DWORD msg1
CALL scanf
SUB ESP, 12
MOV EAX, DWORD [a]
ADD EAX, DWORD [b]
PUSH eax
PUSH DWORD msg2
CALL printf
ADD ESP, 8
;Завершение программы с кодом выхода 0:
PUSH DWORD 0
CALL exit
RET
;Сегмент инициализированных данных
SECTION .DATA
msg : DB "An example of interfacing with GLIBC.",0xA,0
msg1 : DB "%d%d",0
msg2 : DB "%d", 0xA, 0
; Сегмент неинициализированных данных
SECTION .BSS
a RESD 1
b RESD 1
Арифметические операции в формате ASCII.
Данные, вводимые с клавиатуры, имеют ASCII-формат, например, цифры 1234 -
шест.31323334. Для выполнения арифметических операций над числовыми значениями,
такими как шест.31323334, требуется специальная обработка.
С помощью следующих ассемблерных команд можно выполнять
арифметические операции непосредственно над числами в ASCII-формате:
-
AAA (ASCII Adjust for Addition - коррекция для сложения ASCII-кода)
-
AAD (ASCII Adjust for Division - коррекция для деления ASCII-кода)
-
AAM (ASCII Adjust for Multiplication - коррекция для умножения ASCII-кода)
-
AAS (ASCII Adjust for Subtraction - коррекция для вычитания ASCII-кода)
Эти команды кодируются без операндов и выполняют автоматическую коррекцию
в регистре AX. Коррекция необходима, так как ASCII-код представляет так
называемый распакованный десятичный формат, в то время, как компьютер
выполняет арифметические операции в двоичном формате.
Сложение в ASCII-формате.
Рассмотрим процесс сложения чисел 8 и 4 в ASCII-формате:
Полученная сумма неправильна ни для ASCII-формата, ни для двоичного
формата. Однако, игнорируя левую 6 и прибавив 6 к правой шест. C:
шест.C + 6 = шест.12 - получим правильный результат в десятичном формате.
Правильный пример слегка упрощен, но он хорошо демонстрирует процесс,
который выполняет команда AAA при коррекции.
В качестве примера, предположим, что регистр AX содержит шест.0038, а регистр
BX - шест.0034. Числа 38 и 34 представляют два
байта в ASCII-формате, которые необходимо сложить. Сложение и коррекция кодируется
следующими командами:
ADD AL,BL
    ; Сложить 34 и 38
AAA
          ; Коррекция для сложения ASCII-кодов
Команда AAA проверяет правую шест. цифру (4 бита) в регистре AL. Если эта
цифра находится между A и F или флаг AF равен 1, то к регистру
AL прибавляется 6, а к регистру AH прибавляется 1, флаги AF
и CF устанавливаются в 1. Во всех случаях команда AAA устанавливает в 0
левую шест. цифру в регистре AL. Результат - в регистре AX:
После команды ADD: 006C
После команды AAA: 0102
Для того, чтобы выработать окончательное ASCII-представление, достаточно
просто поставить тройки на место левых шест. цифр:
OR AX,3030H ;Результат 3132
Все показанное выше представляет сложение однобайтовых чисел.
Сложение многобайтных ASCII-чисел требует организации цикла, который выполняет обработку
справа налево с учетом переноса. В примере складываются два трехбайтовых ASCII-числа в
четырехбайтовую сумму. Обратите внимание на следующее:
CODESG SEGMENT
ASSUME CS:CODESG,DS:CODESG,SS:CODESG
ORG 100H
BEGIN: JMP SHORT MAIN
; --------------------------------------------------------------- |
|
ASC1 DB '578' |
; Элементы данных |
ASC2 DB '694'
ASC3 DB '0000'
; --------------------------------------------------------------- |
|
MAIN PROC NEAR CLC |
|
LEA SI,AASC1+2 |
; Адреса ASCII-чисел |
LEA DI,AASC2+2
LEA BX,AASC1+3 |
|
MOV CX,03 |
; Выполнить 3 цикла |
A20:
MOV AH,00 |
; Очистить регистр AH |
MOV AL,[SI] |
; Загрузить ASCII-байт |
ADC AL,[DI] |
; Сложение (с переносом) |
AAA |
; Коррекция для ASCII |
MOV [BX],AL |
; Сохранение суммы |
DEC SI
DEC DI
DEC BX |
|
LOOP A20 |
; Циклиться 3 раза |
MOV [BX],AH |
; Сохранить перенос |
RET
MAIN ENDP
CODESG ENDS
END BEGIN |
|
В программе используется команда ADC, так как любое сложение может вызвать
перенос, который должен быть прибавлен к следующему (слева) байту. Команда CLC
устанавливает флаг CF в нулевое состояние.
Команда MOV очищает регистр AH в каждом цикле, так как команда AAA
может прибавить к нему единицу. Команда ADC учитывает пеpеносы. Заметьте,
что использование команд XOR или SUB для oчистки регистра AH изменяет флаг CF.
Когда завершается каждый цикл, происходит пересылка содержимого pегистра AH (00 или 01)
в левый байт суммы.
В результате получается сумма в виде 01020702. Программа не использует команду OR
после команды AAA для занесения левой тройки, так как при этом устанавливается флаг
CF, что изменит pезультат
команды ADC. Одним из решений в данном случае является сохранение флагового регистра
с помощью команды PUSHF, выполнение команды OR, и, затем, восстановление флагового регистра
командой POPF:
ADC AL,[DI] |
; Сложение с переносом |
AAA |
; Коррекция для ASCII |
PUSHF |
; Сохранение флагов |
OR AL,30H |
; Запись левой тройки |
POPF |
; Восстановление флагов |
MOV [BX],AL |
; Сохранение суммы |
Вместо команд PUSHF и POPF можно использовать команды LAHF
(Load AH with Flags загрузка флагов в регистр AH) и SAHF (Store
AH in Flagregister - запись флагов из регистра AH во флаговый регистр).
Команда LAHF загружает в регистр AH флаги SF, ZF, AF, PF и CF;
а команда SAHF записывает содержимое регистра AH в указанные флаги.
В приведенном примере, однако, регистр AH уже используется для
арифметических переполнений. Другой способ вставки троек для получения ASCII-кодов цифр
- организовать обработку суммы командой OR в цикле.
Вычитание в ASCII-формате.
Команда AAS (ASCII Adjust for Subtraction - коррекция для вычитания ASCII-кодов)
выполняется aналогично команде AAA. Команда AAS проверяет правую шест.
цифру (четыре бита) в регистре AL. Если эта цифра лежит между A и F или
флаг AF равен 1, то из регистра AL вычитается 6, а из регистра
AH вычитается 1, флаги AF и CF устанавливаются в 1.
Во всех случаях команда AAS устанавливает в 0 левую шест.цифру в регистpе AL.
В следующих двух примерах предполагается, что поле ASC1 содержит шест.38,
а поле ASC2 - шест.34:
Пример 1: |
   AX |
AF |
MOV AL,ASC1 |
; 0038 |
|
SUB AL,ASC2 |
; 0034 |
0 |
AAS |
; 0004 |
0 |
Пример 2: |
   AX |
AF |
MOV AL,ASC2 |
; 0034 |
|
SUB AL,ASC1 |
; 00FC |
1 |
AAS |
; FF06 |
1 |
В примере 1 команде AAS не требуется выполнять коррекцию.
В примере 2, так как правая цифра в регистре AL равна шест.C,
команда AAS вычитает 6 из регистра AL и 1 из регистра
AH и устанавливает в 1 флаги AF и CF. Результат
(который должен быть равен -4) имеет шест. представление FF06, т.е.
десятичное дополнение числа -4.
Умножение в ASCII-формате.
Команда AAM (ASCII Adjust for Multiplication - коррекция для умножения ASCII-кодов)
выполняет корректировку результата умножения ASCII-кодов в регистре AX.
Однако, шест. Цифры должны быть очищены от троек и полученные данные уже не
будут являться действительными ASCII-кодами. (В руководствах фирмы IBM для
таких данных используется термин pаспакованный десятичный формат). Например, число
в ASCII-формате 31323334 имеет распакованное десятичное представление 01020304.
Кроме этого, надо помнить, что коррекция осуществляется только для одного байта за
одно выполнение, поэтому можно умножать только oднобайтные поля. Для более длинных
полей необходима организация цикла.
Команда AAM делит содержимое регистра AL на 10 (шест.0A) и записывает
частное в регистр AH, а остаток в AL. Предположим, что в регистре AL
содержится шест.35, а в регистре CL - шест.39.
Следующие команды умножают
содержимое регистра AL на содержимое CL и преобразуют результат в
ASCII-формат:
|
|
AX: |
AND CL,0FH |
; Преобразовать CL в 09 |
|
AND AL,0FH |
; Преобразовать AL в 05 |
0005 |
MUL CL |
; Умножить AL на CL |
002D |
AAM |
; Преобразовать в распак.дес. |
0405 |
OR AX,3030H |
; Преобразовать в ASCII-ф-т |
3435 |
Команда MUL генерирует 45 (шест.002D) в регистре AX, после чего команда
AAM делит это значение на 10, записывая частное 04 в регистр AH и остаток
05 в регистр AL. Команда OR преобpазует затем распакованное десятичное
число в ASCII-формат.
Следующий пример демонстрирует умножение четырехбайтового множимого на однобайтовый множитель.
Так как команда AAM может иметь дело только с однобайтовыми числами, то в программе
организован цикл, который обрабатывает байты справа налево. Окончательный результат
умножения в данном примере - 0108090105.
Если множитель больше одного байта, то необходимо обеспечить еще один цикл, который
обрабатывает множитель. В этом случае проще будет преобразовать число из
ASCII-формата в двоичный формат.
CODESG SEGMENT
ASSUME CS:CODESG,DS:CODESG,SS:CODESG
ORG 100H
BEGIN: JMP MAIN
; --------------------------------------------------------------- |
|
MULTCND DB '3783' |
; Элементы данных |
MULTPLR DB '5'
PRODUCT DB 5 DUP(0)
; ---------------------------------------------------------------
MAIN PROC NEAR |
|
MOV CX,04 |
; 4 цикла |
LEA SI,MULTCND+3
LEA DI,PRODUCT+4 |
|
AND MULTPLR,0FH |
; Удалить ASCII-тройку |
A20: |
|
MOV AL,[SI] |
; Загрузить ASCII-символ |
AND AL,OFH |
; Удалить ASCII-тройку |
MUL MULTPLR |
; Умножить |
AAM |
; Коррекция для ASCII |
ADD AL,[DI] |
; Сложить с |
AAA |
; записанным |
MOV [DI],AL |
; произведением |
DEC DI |
|
MOV [DI],AH |
; Записать перенос |
DEC SI |
|
LOOP A20 |
; Циклиться 4 раза |
RET
MAIN ENDP
CODESG ENDS
END BEGIN |
|
Деление в ASCII-формате.
Команда AAD (ASCII Adjust for Division - коррекция для деления
ASCII-кодов) выполняет корректировку ASCII-кода
делимого до непосредственного деления. Однако, прежде необходимо
очистить левые тройки ASCII-кодов для получения распакованного
десятичного формата. Команда AAD может оперировать с двухбайтовыми
делимыми в регистре AX. Предположим, что регистр AX содержит
делимое 3238 в ASCII-формате и регистр CL содержит
делитель 37 также в ASCII-формате.
Следующие команды
выполняют коррекцию для последующего деления:
AND CL,0FH |
; Преобразовать CL в распак.дес. |
|
AND AX,0F0FH |
; Преобразовать AX в распак.дес. |
0208 |
AAD |
; Преобразовать в двоичный |
001C |
DIV CL |
; Разделить на 7 |
0004 |
Команда AAD умножает содержимое AH на 10 (шест.0A),
прибавляет pезультат 20 (шест.14) к регистру AL и
очищает регистр AH. Значение 001C есть шест. представление
десятичного числа 28. Делитель может быть только однобайтовый
от 01 до 09.
CODESG SEGMENT
ASSUME CS:CODESG,DS:CODESG,SS:CODESG
ORG 100H
BEGIN: JMP SHORT MAIN
; --------------------------------------------------------------- |
|
DIVDND DB '3698' |
; Элементы данных |
DIVSOR DB '4'
QUOTNT DB 4 DUP(0)
; ---------------------------------------------------------------
MAIN PROC NEAR |
|
MOV CX,04 |
; 4 цикла |
SUB AH,AH |
; Стереть левый байт делимого |
AND DIVSOR,0FH |
; Стереть ASCII 3 в делителе |
LEA SI,DIVDND
LEA DI,QUOTNT
A20: |
|
MOV AL,[SI] |
; Загрузить ASCII байт ; (можно LODSB) |
AND AL,0FH |
; Стереть ASCII тройку |
AAD |
; Коррекция для деления |
DIV DIVSOR |
; Деление |
MOV [DI],AL |
; Сохранить частное |
INC SI
INC DI |
|
LOOP A20 |
; Циклиться 4 раза |
RET
MAIN ENDP
CODEGS ENDS
|
|
ДВОИЧНО-ДЕСЯТИЧНЫЙ ФОРМАТ (BCD).
В предыдущем примере деления в ASCII-формате было получено частное
00090204. Если сжать это значение, сохраняя только правые цифры каждого
байта, то получим 0924. Такой формат называется двоично-десятичным (BCD -
Binary Coded Decimal) или упакованным. Он содержит только десятичные
цифры от 0 до 9. Длина двоично-десятичного представления в два раза меньше
ASCII-представления.
Заметим, однако, что десятичное число 0924 имеет основание 10 и,
будучи преобразованным в основание 16 (т.е. в шест. представление), даст
шест.039C.
Можно выполнять сложение и вычитание чисел в двоично-десятичном
представлении (BCD-формате). Для этих целей имеются две корректиpующих
команды:
DAA (Decimal Adjustment for Addition - десятичная коррекция для сложения)
DAS (Decimal Adjustment for Subtraction - десятичн. коррекция для вычит.)
Обработка полей также осуществляется по одному байту за одно выполнение.
В примере программы выполняется преобразование чисел из ASCII-формата
в BCD-формат и сложение их.
Процедура B10CONV преобразует ASCII в BCD.
Обработка чисел может выполняться как справа налево, так и слева
направо. Кроме того, обработка слов проще, чем обработка байтов,
так как для генерации одного байта BCD-кода требуется два байта
ASCII-кода. Ориентация на обработку слов требует четного
количества байтов в ASCII-поле.
Процедура C10ADD выполняет сложение чисел в
BCD-формате. Окончательный результат - 127263.
CODESG SEGMENT PARA "Code"
ASSUME CS:CODESG,DS:CODESG,SS:CODESG
ORG 100H
BEGIN: JMP SHORT MAIN
; ---------------------------------------------------------------
ASC1 DB '057836'
ASC2 DB '069427'
BCD1 DB '000'
BCD2 DB '000'
BCD3 DB 4 DUP(0)
; ---------------------------------------------------------------
MAIN PROC NEAR |
|
LEA SI,ASC1+4 |
; Инициализировать для ASC1 |
LEA DI,BCD1+2 |
|
CALL B10CONV |
; Вызвать преобразование |
LEA SI,ASC2+4 |
; Инициализировать для ASC2 |
LEA DI,BCD2+2 |
|
CALL B10CONV |
; Вызвать преобразование |
CALL C10ADD |
; Вызвать сложение |
RET
MAIN ENDP
; Преобразование ASCII в BCD:
; ---------------------------------------------------------------
B10CONV PROC |
|
MOV CL,04 |
; Фактор сдвига |
MOV DX,03 |
; Число слов |
В20: |
|
MOV AX,[SI] |
; Получить ASCII-пapy |
XCHG AH,AL |
|
SHL AL,CL |
; Удалить тройки |
SHL AX,CL |
; ASCII-кода |
MOV [DI],AH |
; Записать BCD-цифру |
DEC SI
DEC SI
DEC DI
DEC DX
JNZ В20
RET
B10CONV ENDP
; Сложение BCD-чисел:
; ---------------------------------------------------------------
C10ADD PROC
|
|
XOR AН,AН |
; 0чистить AН |
LEA SI,BCD1+2 |
; Инициализация |
LEA DI,BCD2+2 |
; BCD |
LEA BX,BCD3+3 |
; адресов |
MOV CX,03 |
; Трехбайтные поля |
CLC С20: |
|
MOV AL,[SI] |
; Получить BCD1 (или LODSB) |
ADC AL,[DI] |
; Прибавить BCD2 |
DAA |
; Десятичная коррекция |
MOV [BX],AL |
; 3аписать в BCD3 |
DEC SI
DEC DI
DEC BX |
|
LOOP С20 |
; Цикл 3 раза |
RET
C10ADD ENDP
CODESG ENDS
END BEGIN
|
|
Лабораторная работа №3