Permalink
1
contributor
Users who have contributed to this file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
org 100h | |
jmp start ; jump over data declaration | |
msg: db «1-Add»,0dh,0ah,«2-Multiply»,0dh,0ah,«3-Subtract»,0dh,0ah,«4-Divide», 0Dh,0Ah, ‘$’ | |
msg2: db 0dh,0ah,«Enter First No : $» | |
msg3: db 0dh,0ah,«Enter Second No : $» | |
msg4: db 0dh,0ah,«Choice Error $» | |
msg5: db 0dh,0ah,«Result : $» | |
msg6: db 0dh,0ah ,‘thank you for using the calculator! press any key… ‘, 0Dh,0Ah, ‘$’ | |
start: mov ah,9 | |
mov dx, offset msg ;first we will display hte first message from which he can choose the operation using int 21h | |
int 21h | |
mov ah,0 | |
int 16h ;then we will use int 16h to read a key press, to know the operation he choosed | |
cmp al,31h ;the keypress will be stored in al so, we will comapre to 1 addition ………. | |
je Addition | |
cmp al,32h | |
je Multiply | |
cmp al,33h | |
je Subtract | |
cmp al,34h | |
je Divide | |
mov ah,09h | |
mov dx, offset msg4 | |
int 21h | |
mov ah,0 | |
int 16h | |
jmp start | |
Addition: mov ah,09h ;then let us handle the case of addition operation | |
mov dx, offset msg2 ;first we will display this message enter first no also using int 21h | |
int 21h | |
mov cx,0 ;we will call InputNo to handle our input as we will take each number seprately | |
call InputNo ;first we will move to cx 0 because we will increment on it later in InputNo | |
push dx | |
mov ah,9 | |
mov dx, offset msg3 | |
int 21h | |
mov cx,0 | |
call InputNo | |
pop bx | |
add dx,bx | |
push dx | |
mov ah,9 | |
mov dx, offset msg5 | |
int 21h | |
mov cx,10000 | |
pop dx | |
call View | |
jmp exit | |
InputNo: mov ah,0 | |
int 16h ;then we will use int 16h to read a key press | |
mov dx,0 | |
mov bx,1 | |
cmp al,0dh ;the keypress will be stored in al so, we will comapre to 0d which represent the enter key, to know wheter he finished entering the number or not | |
je FormNo ;if it’s the enter key then this mean we already have our number stored in the stack, so we will return it back using FormNo | |
sub ax,30h ;we will subtract 30 from the the value of ax to convert the value of key press from ascii to decimal | |
call ViewNo ;then call ViewNo to view the key we pressed on the screen | |
mov ah,0 ;we will mov 0 to ah before we push ax to the stack bec we only need the value in al | |
push ax ;push the contents of ax to the stack | |
inc cx ;we will add 1 to cx as this represent the counter for the number of digit | |
jmp InputNo ;then we will jump back to input number to either take another number or press enter | |
;we took each number separatly so we need to form our number and store in one bit for example if our number 235 | |
FormNo: pop ax | |
push dx | |
mul bx | |
pop dx | |
add dx,ax | |
mov ax,bx | |
mov bx,10 | |
push dx | |
mul bx | |
pop dx | |
mov bx,ax | |
dec cx | |
cmp cx,0 | |
jne FormNo | |
ret | |
View: mov ax,dx | |
mov dx,0 | |
div cx | |
call ViewNo | |
mov bx,dx | |
mov dx,0 | |
mov ax,cx | |
mov cx,10 | |
div cx | |
mov dx,bx | |
mov cx,ax | |
cmp ax,0 | |
jne View | |
ret | |
ViewNo: push ax ;we will push ax and dx to the stack because we will change there values while viewing then we will pop them back from | |
push dx ;the stack we will do these so, we don’t affect their contents | |
mov dx,ax ;we will mov the value to dx as interrupt 21h expect that the output is stored in it | |
add dl,30h ;add 30 to its value to convert it back to ascii | |
mov ah,2 | |
int 21h | |
pop dx | |
pop ax | |
ret | |
exit: mov dx,offset msg6 | |
mov ah, 09h | |
int 21h | |
mov ah, 0 | |
int 16h | |
ret | |
Multiply: mov ah,09h | |
mov dx, offset msg2 | |
int 21h | |
mov cx,0 | |
call InputNo | |
push dx | |
mov ah,9 | |
mov dx, offset msg3 | |
int 21h | |
mov cx,0 | |
call InputNo | |
pop bx | |
mov ax,dx | |
mul bx | |
mov dx,ax | |
push dx | |
mov ah,9 | |
mov dx, offset msg5 | |
int 21h | |
mov cx,10000 | |
pop dx | |
call View | |
jmp exit | |
Subtract: mov ah,09h | |
mov dx, offset msg2 | |
int 21h | |
mov cx,0 | |
call InputNo | |
push dx | |
mov ah,9 | |
mov dx, offset msg3 | |
int 21h | |
mov cx,0 | |
call InputNo | |
pop bx | |
sub bx,dx | |
mov dx,bx | |
push dx | |
mov ah,9 | |
mov dx, offset msg5 | |
int 21h | |
mov cx,10000 | |
pop dx | |
call View | |
jmp exit | |
Divide: mov ah,09h | |
mov dx, offset msg2 | |
int 21h | |
mov cx,0 | |
call InputNo | |
push dx | |
mov ah,9 | |
mov dx, offset msg3 | |
int 21h | |
mov cx,0 | |
call InputNo | |
pop bx | |
mov ax,bx | |
mov cx,dx | |
mov dx,0 | |
mov bx,0 | |
div cx | |
mov bx,dx | |
mov dx,ax | |
push bx | |
push dx | |
mov ah,9 | |
mov dx, offset msg5 | |
int 21h | |
mov cx,10000 | |
pop dx | |
call View | |
pop bx | |
cmp bx,0 | |
je exit | |
jmp exit | |
Время на прочтение
16 мин
Количество просмотров 117K
В наше время редко возникает необходимость писать на чистом ассемблере, но я определённо рекомендую это всем, кто интересуется программированием. Вы увидите вещи под иным углом, а навыки пригодятся при отладке кода на других языках.
В этой статье мы напишем с нуля калькулятор обратной польской записи (RPN) на чистом ассемблере x86. Когда закончим, то сможем использовать его так:
$ ./calc "32+6*" # "(3+2)*6" в инфиксной нотации
30
Весь код для статьи здесь. Он обильно закомментирован и может служить учебным материалом для тех, кто уже знает ассемблер.
Начнём с написания базовой программы Hello world! для проверки настроек среды. Затем перейдём к системным вызовам, стеку вызовов, стековым кадрам и соглашению о вызовах x86. Потом для практики напишем некоторые базовые функции на ассемблере x86 — и начнём писать калькулятор RPN.
Предполагается, что у читателя есть некоторый опыт программирования на C и базовые знания компьютерной архитектуры (например, что такое регистр процессора). Поскольку мы будем использовать Linux, вы также должны уметь использовать командную строку Linux.
Настройка среды
Как уже сказано, мы используем Linux (64- или 32-битный). Приведённый код не работает в Windows или Mac OS X.
Для установки нужен только компоновщик GNU ld
из binutils
, который предварительно установлен в большинстве дистрибутивов, и ассемблер NASM. На Ubuntu и Debian можете установить и то, и другое одной командой:
$ sudo apt-get install binutils nasm
Я бы также рекомендовал держать под рукой таблицу ASCII.
Hello, world!
Для проверки среды сохраните следующий код в файле calc.asm
:
; Компоновщик находит символ _start и начинает выполнение программы
; отсюда.
global _start
; В разделе .rodata хранятся константы (только для чтения)
; Порядок секций не имеет значения, но я люблю ставить её вперёд
section .rodata
; Объявляем пару байтов как hello_world. Псевдоинструкция базы NASM
; допускает однобайтовое значение, строковую константу или их сочетание,
; как здесь. 0xA = новая строка, 0x0 = нуль окончания строки
hello_world: db "Hello world!", 0xA, 0x0
; Начало секции .text, где находится код программы
section .text
_start:
mov eax, 0x04 ; записать число 4 в регистр eax (0x04 = write())
mov ebx, 0x1 ; дескриптор файла (1 = стандартный вывод, 2 = стандартная ошибка)
mov ecx, hello_world ; указатель на выводимую строку
mov edx, 14 ; длина строки
int 0x80 ; отправляем сигнал прерывания 0x80, который ОС
; интерпретирует как системный вызов
mov eax, 0x01 ; 0x01 = exit()
mov ebx, 0 ; 0 = нет ошибок
int 0x80
Комментарии объясняют общую структуру. Список регистров и общих инструкций можете изучить в «Руководстве по ассемблеру x86 университета Вирджинии». При дальнейшем обсуждении системных вызовов это тем более понадобится.
Следующие команды собирают файл ассемблера в объектный файл, а затем компонует исполняемый файл:
$ nasm -f elf_i386 calc.asm -o calc
$ ld -m elf_i386 calc.o -o calc
После запуска вы должны увидеть:
$ ./calc
Hello world!
Makefile
Это необязательная часть, но для упрощения сборки и компоновки в будущем можно сделать Makefile
. Сохраните его в том же каталоге, что и calc.asm
:
CFLAGS= -f elf32
LFLAGS= -m elf_i386
all: calc
calc: calc.o
ld $(LFLAGS) calc.o -o calc
calc.o: calc.asm
nasm $(CFLAGS) calc.asm -o calc.o
clean:
rm -f calc.o calc
.INTERMEDIATE: calc.o
Затем вместо вышеприведённых инструкций просто запускаем make.
Системные вызовы
Системные вызовы Linux указывают ОС выполнить для нас какие-то действия. В этой статье мы используем только два системных вызова: write()
для записи строки в файл или поток (в нашем случае это стандартное устройство вывода и стандартная ошибка) и exit()
для выхода из программы:
syscall 0x01: exit(int error_code)
error_code - используем 0 для выхода без ошибок и любые другие значения (такие как 1) для ошибок
syscall 0x04: write(int fd, char *string, int length)
fd — используем 1 для стандартного вывода, 2 для стандартного потока вывода ошибок
string — указатель на первый символ строки
length — длина строки в байтах
Системные вызовы настраиваются путём сохранения номера системного вызова в регистре eax
, а затем его аргументов в ebx
, ecx
, edx
в таком порядке. Можете заметить, что у exit()
только один аргумент — в этом случае ecx и edx не имеют значения.
eax | ebx | ecx | edx |
---|---|---|---|
Номер системного вызова | arg1 | arg2 | arg3 |
Стек вызовов
Стек вызовов — структура данных, в которой хранится информация о каждом обращении к функции. У каждого вызова собственный раздел в стеке — «фрейм». Он хранит некоторую информацию о текущем вызове: локальные переменные этой функции и адрес возврата (куда программа должна перейти после выполнения функции).
Сразу отмечу одну неочевидную вещь: стек увеличивается вниз по памяти. Когда вы добавляете что-то на верх стека, оно вставляется по адресу памяти ниже, чем предыдущий элемент. Другими словами, по мере роста стека адрес памяти в верхней части стека уменьшается. Чтобы избежать путаницы, я буду всё время напоминать об этом факте.
Инструкция push
заносит что-нибудь на верх стека, а pop
уносит данные оттуда. Например, push еах
выделяет место наверху стека и помещает туда значение из регистра eax
, а pop еах
переносит любые данные из верхней части стека в eax
и освобождает эту область памяти.
Цель регистра esp
— указать на вершину стека. Любые данные выше esp
считаются не попавшими в стек, это мусорные данные. Выполнение инструкции push
(или pop
) перемещает esp
. Вы можете манипулировать esp
и напрямую, если отдаёте отчёт своим действиям.
Регистр ebp
похож на esp
, только он всегда указывает примерно на середину текущего кадра стека, непосредственно перед локальными переменными текущей функции (поговорим об этом позже). Однако вызов другой функции не перемещает ebp
автоматически, это нужно каждый раз делать вручную.
Соглашение о вызовах для архитектуры x86
В х86 нет встроенного понятия функции как в высокоуровневых языках. Инструкция call
— это по сути просто jmp
(goto
) в другой адрес памяти. Чтобы использовать подпрограммы как функции в других языках (которые могут принимать аргументы и возвращать данные обратно), нужно следовать соглашению о вызовах (существует много конвенций, но мы используем CDECL, самое популярное соглашение для x86 среди компиляторов С и программистов на ассемблере). Это также гарантирует, что регистры подпрограммы не перепутаются при вызове другой функции.
Правила вызывающей стороны
Перед вызовом функции вызывающая сторона должна:
- Сохранить в стек регистры, которые обязан сохранять вызывающий. Вызываемая функция может изменить некоторые регистры: чтобы не потерять данные, вызывающая сторона должна сохранить их в памяти до помещения в стек. Речь идёт о регистрах
eax
,ecx
иedx
. Если вы не используете какие-то из них, то их можно не сохранять. - Записать аргументы функции на стек в обратном порядке (сначала последний аргумент, в конце первый аргумент). Такой порядок гарантирует, что вызываемая функция получит из стека свои аргументы в правильном порядке.
- Вызвать подпрограмму.
По возможности функция сохранит результат в eax
. Сразу после call
вызывающая сторона должна:
- Удалить из стека аргументы функции. Обычно это делается путём простого добавления числа байтов в
esp
. Не забывайте, что стек растёт вниз, поэтому для удаления из стека необходимо добавить байты. - Восстановить сохранённые регистры, забрав их из стека в обратном порядке инструкцией
pop
. Вызываемая функция не изменит никакие другие регистры.
Следующий пример демонстрирует, как применяются эти правила. Предположим, что функция _subtract
принимает два целочисленных (4-байтовых) аргумента и возвращает первый аргумент за вычетом второго. В подпрограмме _mysubroutine
вызываем _subtract
с аргументами 10
и 2
:
_mysubroutine:
; ...
; здесь какой-то код
; ...
push ecx ; сохраняем регистры (я решил не сохранять eax)
push edx
push 2 ; второе правило, пушим аргументы в обратном порядке
push 10
call _subtract ; eax теперь равен 10-2=8
add esp, 8 ; удаляем 8 байт со стека (два аргумента по 4 байта)
pop edx ; восстанавливаем сохранённые регистры
pop ecx
; ...
; ещё какой-то код, где я использую удивительно полезное значение из eax
; ...
Правила вызываемой подпрограммы
Перед вызовом подпрограмма должна:
- Сохранить указатель базового регистра
ebp
предыдущего фрейма, записав его на стек. - Отрегулировать
ebp
с предыдущего фрейма на текущий (текущее значениеesp
). - Выделить больше места в стеке для локальных переменных, при необходимости переместить указатель
esp
. Поскольку стек растёт вниз, нужно вычесть недостающую память изesp
. - Сохранить в стек регистры вызываемой подпрограммы. Это
ebx
,edi
иesi
. Необязательно сохранять регистры, которые не планируется изменять.
Стек вызовов после шага 1:
Стек вызовов после шага 2:
Стек вызовов после шага 4:
На этих диаграммах в каждом стековом фрейме указан адрес возврата. Его автоматически вставляет в стек инструкция call
. Инструкция ret
извлекает адрес с верхней части стека и переходит на него. Эта инструкция нам не нужна, я просто показал, почему локальные переменные функции находятся на 4 байта выше ebp
, но аргументы функции — на 8 байт ниже ebp
.
На последней диаграмме также можно заметить, что локальные переменные функции всегда начинается на 4 байта выше ebp
с адреса ebp-4
(здесь вычитание, потому что мы двигаемся вверх по стеку), а аргументы функции всегда начинается на 8 байт ниже ebp
с адреса ebp+8
(сложение, потому что мы двигаемся вниз по стеку). Если следовать правилам из этой конвенции, так будет c переменными и аргументами любой функции.
Когда функция выполнена и вы хотите вернуться, нужно сначала установить eax
на возвращаемое значение функции, если это необходимо. Кроме того, нужно:
- Восстановить сохранённые регистры, вынеся их из стека в обратном порядке.
- Освободить место в стеке, выделенное локальным переменным на шаге 3, если необходимо: делается простой установкой
esp
в ebp - Восстановить указатель базы
ebp
предыдущего фрейма, вынеся его из стека. - Вернуться с помощью
ret
Теперь реализуем функцию _subtract
из нашего примера:
_subtract:
push ebp ; сохранение указателя базы предыдущего фрейма
mov ebp, esp ; настройка ebp
; Здесь я бы выделил место на стеке для локальных переменных, но они мне не нужны
; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не
; собираюсь изменять
; Тут начинается функция
mov eax, [ebp+8] ; копирование первого аргумента функции в eax. Скобки
; означают доступ к памяти по адресу ebp+8
sub eax, [ebp+12] ; вычитание второго аргумента по адресу ebp+12 из первого
; аргумента
; Тут функция заканчивается, eax равен её возвращаемому значению
; Здесь я бы восстановил регистры, но они не сохранялись
; Здесь я бы освободил стек от переменных, но память для них не выделялась
pop ebp ; восстановление указателя базы предыдущего фрейма
ret
Вход и выход
В приведённом примере вы можете заметить, что функция всегда запускается одинаково: push ebp
, mov ebp
, esp
и выделение памяти для локальных переменных. В наборе x86 есть удобная инструкция, которая всё это выполняет: enter a b
, где a
— количество байт, которые вы хотите выделить для локальных переменных, b
— «уровень вложенности», который мы всегда будем выставлять на 0
. Кроме того, функция всегда заканчивается инструкциями pop ebp
и mov esp
, ebp
(хотя они необходимы только при выделении памяти для локальных переменных, но в любом случае не причиняют вреда). Это тоже можно заменить одной инструкцией: leave
. Вносим изменения:
_subtract:
enter 0, 0 ; сохранение указателя базы предыдущего фрейма и настройка ebp
; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не
; собираюсь изменять
; Тут начинается функция
mov eax, [ebp+8] ; копирование первого аргумента функции в eax. Скобки
; означают доступ к памяти по адресу ebp+8
sub eax, [ebp+12] ; вычитание второго аргумента по адресу ebp+12 из
; первого аргумента
; Тут функция заканчивается, eax равен её возвращаемому значению
; Здесь я бы восстановил регистры, но они не сохранялись
leave ; восстановление указателя базы предыдущего фрейма
ret
Написание некоторых основных функций
Усвоив соглашение о вызовах, можно приступить к написанию некоторых подпрограмм. Почему бы не обобщить код, который выводит «Hello world!», для вывода любых строк: функция _print_msg
.
Здесь понадобится ещё одна функция _strlen
для подсчёта длины строки. На C она может выглядеть так:
size_t strlen(char *s) {
size_t length = 0;
while (*s != 0)
{ // начало цикла
length++;
s++;
} // конец цикла
return length;
}
Другими словами, с самого начала строки мы добавляем 1 к возвращаемым значением для каждого символа, кроме нуля. Как только замечен нулевой символ, возвращаем накопленное в цикле значение. В ассемблере это тоже довольно просто: можно использовать как базу ранее написанную функцию _subtract
:
_strlen:
enter 0, 0 ; сохраняем указатель базы предыдущего фрейма и настраиваем ebp
; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не
; собираюсь изменять
; Здесь начинается функция
mov eax, 0 ; length = 0
mov ecx, [ebp+8] ; первый аргумент функции (указатель на первый
; символ строки) копируется в ecx (его сохраняет вызывающая
; сторона, так что нам нет нужды сохранять)
_strlen_loop_start: ; это метка, куда можно перейти
cmp byte [ecx], 0 ; разыменование указателя и сравнение его с нулём. По
; умолчанию память считывается по 32 бита (4 байта).
; Иное нужно указать явно. Здесь мы указываем
; чтение только одного байта (один символ)
je _strlen_loop_end ; выход из цикла при появлении нуля
inc eax ; теперь мы внутри цикла, добавляем 1 к возвращаемому значению
add ecx, 1 ; переход к следующему символу в строке
jmp _strlen_loop_start ; переход обратно к началу цикла
_strlen_loop_end:
; Здесь функция заканчивается, eax равно возвращаемому значению
; Здесь я бы восстановил регистры, но они не сохранялись
leave ; восстановление указателя базы предыдущего фрейма
ret
Уже неплохо, верно? Сначала написать код на C может помочь, потому что большая его часть непосредственно преобразуется в ассемблер. Теперь можно использовать эту функцию в _print_msg
, где мы применим все полученные знания:
_print_msg:
enter 0, 0
; Здесь начинается функция
mov eax, 0x04 ; 0x04 = системный вызов write()
mov ebx, 0x1 ; 0x1 = стандартный вывод
mov ecx, [ebp+8] ; мы хотим вывести первый аргумент этой функции,
; сначала установим edx на длину строки. Пришло время вызвать _strlen
push eax ; сохраняем регистры вызываемой функции (я решил не сохранять edx)
push ecx
push dword [ebp+8] ; пушим аргумент _strlen в _print_msg. Здесь NASM
; ругается, если не указать размер, не знаю, почему.
; В любом случае указателем будет dword (4 байта, 32 бита)
call _strlen ; eax теперь равен длине строки
mov edx, eax ; перемещаем размер строки в edx, где он нам нужен
add esp, 4 ; удаляем 4 байта со стека (один 4-байтовый аргумент char*)
pop ecx ; восстанавливаем регистры вызывающей стороны
pop eax
; мы закончили работу с функцией _strlen, можно инициировать системный вызов
int 0x80
leave
ret
И посмотрим плоды нашей тяжёлой работы, используя эту функцию в полной программе “Hello, world!”.
_start:
enter 0, 0
; сохраняем регистры вызывающей стороны (я решил никакие не сохранять)
push hello_world ; добавляем аргумент для _print_msg
call _print_msg
mov eax, 0x01 ; 0x01 = exit()
mov ebx, 0 ; 0 = без ошибок
int 0x80
Хотите верьте, хотите нет, но мы рассмотрели все основные темы, которые нужны для написания базовых программ на ассемблере x86! Теперь у нас есть весь вводный материал и теория, так что полностью сосредоточимся на коде и применим полученные знания для написания нашего калькулятора RPN. Функции будут намного длиннее и даже станут использовать некоторые локальные переменные. Если хотите сразу увидеть готовую программу, вот она.
Для тех из вас, кто не знаком с обратной польской записью (иногда называемой обратной польской нотацией или постфиксной нотацией), то здесь выражения вычисляются с помощью стека. Поэтому нужно создать стек, а также функции _pop
и _push
для манипуляций с этим стеком. Понадобится ещё функция _print_answer
, которая выведет в конце вычислений строковое представление числового результата.
Создание стека
Сначала определим для нашего стека пространство в памяти, а также глобальную переменную stack_size
. Желательно изменить эти переменные так, чтобы они попали не в раздел .rodata
, а в .data
.
section .data
stack_size: dd 0 ; создаём переменную dword (4 байта) со значением 0
stack: times 256 dd 0 ; заполняем стек нулями
Теперь можно реализовать функции _push
и _pop
:
_push:
enter 0, 0
; Сохраняем регистры вызываемой функции, которые будем использовать
push eax
push edx
mov eax, [stack_size]
mov edx, [ebp+8]
mov [stack + 4*eax], edx ; Заносим аргумент на стек. Масштабируем по
; четыре байта в соответствии с размером dword
inc dword [stack_size] ; Добавляем 1 к stack_size
; Восстанавливаем регистры вызываемой функции
pop edx
pop eax
leave
ret
_pop:
enter 0, 0
; Сохраняем регистры вызываемой функции
dec dword [stack_size] ; Сначала вычитаем 1 из stack_size
mov eax, [stack_size]
mov eax, [stack + 4*eax] ; Заносим число на верх стека в eax
; Здесь я бы восстановил регистры, но они не сохранялись
leave
ret
Вывод чисел
_print_answer
намного сложнее: придётся конвертировать числа в строки и использовать несколько других функций. Понадобится функция _putc
, которая выводит один символ, функция mod
для вычисления остатка от деления (модуля) двух аргументов и _pow_10
для возведения в степень 10. Позже вы поймёте, зачем они нужны. Это довольно просто, вот код:
_pow_10:
enter 0, 0
mov ecx, [ebp+8] ; задаёт ecx (сохранённый вызывающей стороной) аргументом
; функции
mov eax, 1 ; первая степень 10 (10**0 = 1)
_pow_10_loop_start: ; умножает eax на 10, если ecx не равно 0
cmp ecx, 0
je _pow_10_loop_end
imul eax, 10
sub ecx, 1
jmp _pow_10_loop_start
_pow_10_loop_end:
leave
ret
_mod:
enter 0, 0
push ebx
mov edx, 0 ; объясняется ниже
mov eax, [ebp+8]
mov ebx, [ebp+12]
idiv ebx ; делит 64-битное целое [edx:eax] на ebx. Мы хотим поделить
; только 32-битное целое eax, так что устанавливаем edx равным
; нулю.
; частное сохраняем в eax, остаток в edx. Как обычно, получить
; информацию по конкретной инструкции можно из справочников,
; перечисленных в конце статьи.
mov eax, edx ; возвращает остаток от деления (модуль)
pop ebx
leave
ret
_putc:
enter 0, 0
mov eax, 0x04 ; write()
mov ebx, 1 ; стандартный вывод
lea ecx, [ebp+8] ; входной символ
mov edx, 1 ; вывести только 1 символ
int 0x80
leave
ret
Итак, как мы выводим отдельные цифры в числе? Во-первых, обратите внимание, что последняя цифра числа равна остатку от деления на 10 (например, 123 % 10 = 3
), а следующая цифра — это остаток от деления на 100, поделенный на 10 (например, (123 % 100)/10 = 2
). В общем, можно найти конкретную цифру числа (справа налево), найдя (число % 10**n) / 10**(n-1)
, где число единиц будет равно n = 1
, число десятков n = 2
и так далее.
Используя это знание, можно найти все цифры числа с n = 1
до n = 10
(это максимальное количество разрядов в знаковом 4-байтовом целом). Но намного проще идти слева направо — так мы сможем печатать каждый символ, как только находим его, и избавиться от нулей в левой части. Поэтому перебираем числа от n = 10
до n = 1
.
На C программа будет выглядеть примерно так:
#define MAX_DIGITS 10
void print_answer(int a) {
if (a < 0) { // если число отрицательное
putc('-'); // вывести знак «минус»
a = -a; // преобразовать в положительное число
}
int started = 0;
for (int i = MAX_DIGITS; i > 0; i--) {
int digit = (a % pow_10(i)) / pow_10(i-1);
if (digit == 0 && started == 0) continue; // не выводить лишние нули
started = 1;
putc(digit + '0');
}
}
Теперь вы понимаете, зачем нам эти три функции. Давайте реализуем это на ассемблере:
%define MAX_DIGITS 10
_print_answer:
enter 1, 0 ; используем 1 байт для переменной "started" в коде C
push ebx
push edi
push esi
mov eax, [ebp+8] ; наш аргумент "a"
cmp eax, 0 ; если число не отрицательное, пропускаем этот условный
; оператор
jge _print_answer_negate_end
; call putc for '-'
push eax
push 0x2d ; символ '-'
call _putc
add esp, 4
pop eax
neg eax ; преобразуем в положительное число
_print_answer_negate_end:
mov byte [ebp-4], 0 ; started = 0
mov ecx, MAX_DIGITS ; переменная i
_print_answer_loop_start:
cmp ecx, 0
je _print_answer_loop_end
; вызов pow_10 для ecx. Попытаемся сделать ebx как переменную "digit" в коде C.
; Пока что назначим edx = pow_10(i-1), а ebx = pow_10(i)
push eax
push ecx
dec ecx ; i-1
push ecx ; первый аргумент для _pow_10
call _pow_10
mov edx, eax ; edx = pow_10(i-1)
add esp, 4
pop ecx ; восстанавливаем значение i для ecx
pop eax
; end pow_10 call
mov ebx, edx ; digit = ebx = pow_10(i-1)
imul ebx, 10 ; digit = ebx = pow_10(i)
; вызываем _mod для (a % pow_10(i)), то есть (eax mod ebx)
push eax
push ecx
push edx
push ebx ; arg2, ebx = digit = pow_10(i)
push eax ; arg1, eax = a
call _mod
mov ebx, eax ; digit = ebx = a % pow_10(i+1), almost there
add esp, 8
pop edx
pop ecx
pop eax
; завершение вызова mod
; делим ebx (переменная "digit" ) на pow_10(i) (edx). Придётся сохранить пару
; регистров, потому что idiv использует для деления и edx, eax. Поскольку
; edx является нашим делителем, переместим его в какой-нибудь
; другой регистр
push esi
mov esi, edx
push eax
mov eax, ebx
mov edx, 0
idiv esi ; eax хранит результат (цифру)
mov ebx, eax ; ebx = (a % pow_10(i)) / pow_10(i-1), переменная "digit" в коде C
pop eax
pop esi
; end division
cmp ebx, 0 ; если digit == 0
jne _print_answer_trailing_zeroes_check_end
cmp byte [ebp-4], 0 ; если started == 0
jne _print_answer_trailing_zeroes_check_end
jmp _print_answer_loop_continue ; continue
_print_answer_trailing_zeroes_check_end:
mov byte [ebp-4], 1 ; started = 1
add ebx, 0x30 ; digit + '0'
; вызов putc
push eax
push ecx
push edx
push ebx
call _putc
add esp, 4
pop edx
pop ecx
pop eax
; окончание вызова putc
_print_answer_loop_continue:
sub ecx, 1
jmp _print_answer_loop_start
_print_answer_loop_end:
pop esi
pop edi
pop ebx
leave
ret
Это было тяжкое испытание! Надеюсь, комментарии помогают разобраться. Если вы сейчас думаете: «Почему нельзя просто написать printf("%d")
?», то вам понравится окончание статьи, где мы заменим функцию именно этим!
Теперь у нас есть все необходимые функции, осталось реализовать основную логику в _start
— и на этом всё!
Вычисление обратной польской записи
Как мы уже говорили, обратная польская запись вычисляется с помощью стека. При чтении число заносится на стек, а при чтении оператор применяется к двум объектам наверху стека.
Например, если мы хотим вычислить 84/3+6*
(это выражение также можно записать в виде 6384/+*
), процесс выглядит следующим образом:
Шаг | Символ | Стек перед | Стек после |
---|---|---|---|
1 | 8 |
[] |
[8] |
2 | 4 |
[8] |
[8, 4] |
3 | / |
[8, 4] |
[2] |
4 | 3 |
[2] |
[2, 3] |
5 | + |
[2, 3] |
[5] |
6 | 6 |
[5] |
[5, 6] |
7 | * |
[5, 6] |
[30] |
Если на входе допустимое постфиксное выражение, то в конце вычислений на стеке остаётся лишь один элемент — это и есть ответ, результат вычислений. В нашем случае число равно 30.
В ассемблере нужно реализовать нечто вроде такого кода на C:
int stack[256]; // наверное, 256 слишком много для нашего стека
int stack_size = 0;
int main(int argc, char *argv[]) {
char *input = argv[0];
size_t input_length = strlen(input);
for (int i = 0; i < input_length; i++) {
char c = input[i];
if (c >= '0' && c <= '9') { // если символ — это цифра
push(c - '0'); // преобразовать символ в целое число и поместить в стек
} else {
int b = pop();
int a = pop();
if (c == '+') {
push(a+b);
} else if (c == '-') {
push(a-b);
} else if (c == '*') {
push(a*b);
} else if (c == '/') {
push(a/b);
} else {
error("Invalid inputn");
exit(1);
}
}
}
if (stack_size != 1) {
error("Invalid inputn");
exit(1);
}
print_answer(stack[0]);
exit(0);
}
Теперь у нас имеются все функции, необходимые для реализации этого, давайте начнём.
_start:
; аргументы _start получаются не так, как в других функциях.
; вместо этого esp указывает непосредственно на argc (число аргументов), а
; esp+4 указывает на argv. Следовательно, esp+4 указывает на название
; программы, esp+8 - на первый аргумент и так далее
mov esi, [esp+8] ; esi = "input" = argv[0]
; вызываем _strlen для определения размера входных данных
push esi
call _strlen
mov ebx, eax ; ebx = input_length
add esp, 4
; end _strlen call
mov ecx, 0 ; ecx = "i"
_main_loop_start:
cmp ecx, ebx ; если (i >= input_length)
jge _main_loop_end
mov edx, 0
mov dl, [esi + ecx] ; то загрузить один байт из памяти в нижний байт
; edx. Остальную часть edx обнуляем.
; edx = переменная c = input[i]
cmp edx, '0'
jl _check_operator
cmp edx, '9'
jg _print_error
sub edx, '0'
mov eax, edx ; eax = переменная c - '0' (цифра, не символ)
jmp _push_eax_and_continue
_check_operator:
; дважды вызываем _pop для выноса переменной b в edi, a переменной b - в eax
push ecx
push ebx
call _pop
mov edi, eax ; edi = b
call _pop ; eax = a
pop ebx
pop ecx
; end call _pop
cmp edx, '+'
jne _subtract
add eax, edi ; eax = a+b
jmp _push_eax_and_continue
_subtract:
cmp edx, '-'
jne _multiply
sub eax, edi ; eax = a-b
jmp _push_eax_and_continue
_multiply:
cmp edx, '*'
jne _divide
imul eax, edi ; eax = a*b
jmp _push_eax_and_continue
_divide:
cmp edx, '/'
jne _print_error
push edx ; сохраняем edx, потому что регистр обнулится для idiv
mov edx, 0
idiv edi ; eax = a/b
pop edx
; теперь заносим eax на стек и продолжаем
_push_eax_and_continue:
; вызываем _push
push eax
push ecx
push edx
push eax ; первый аргумент
call _push
add esp, 4
pop edx
pop ecx
pop eax
; завершение call _push
inc ecx
jmp _main_loop_start
_main_loop_end:
cmp byte [stack_size], 1 ; если (stack_size != 1), печать ошибки
jne _print_error
mov eax, [stack]
push eax
call _print_answer
; print a final newline
push 0xA
call _putc
; exit successfully
mov eax, 0x01 ; 0x01 = exit()
mov ebx, 0 ; 0 = без ошибок
int 0x80 ; здесь выполнение завершается
_print_error:
push error_msg
call _print_msg
mov eax, 0x01
mov ebx, 1
int 0x80
Понадобится ещё добавить строку error_msg
в раздел .rodata
:
section .rodata
; Назначаем на некоторые байты error_msg. Псевдоинструкция db в NASM
; позволяет использовать однобайтовое значение, строковую константу или их
; сочетание. 0xA = новая строка, 0x0 = нуль окончания строки
error_msg: db "Invalid input", 0xA, 0x0
И мы закончили! Удивите всех своих друзей, если они у вас есть. Надеюсь, теперь вы с большей теплотой отнесётесь к языкам высокого уровня, особенно если вспомнить, что многие старые программы писали полностью или почти полностью на ассемблере, например, оригинальный RollerCoaster Tycoon!
Весь код здесь. Спасибо за чтение! Могу продолжить, если вам интересно.
Дальнейшие действия
Можете попрактиковаться, реализовав несколько дополнительных функций:
- Выдать вместо segfault сообщение об ошибке, если программа не получает аргумент.
- Добавить поддержку дополнительных пробелов между операндами и операторами во входных данных.
- Добавить поддержку многоразрядных операндов.
- Разрешить ввод отрицательных чисел.
- Заменить
_strlen
на функцию из стандартной библиотеки C, а_print_answer
заменить вызовомprintf
.
Дополнительные материалы
- «Руководство по ассемблеру x86 университета Вирджинии» — более подробное изложение многих тем, рассмотренных нами, в том числе дополнительная информация по всем популярным инструкциям x86.
- «Искусство выбора регистров Intel». Хотя большинство регистров x86 — регистры общего назначения, но у многих есть историческое значение. Следование этим соглашениям может улучшить читаемость кода и, как интересный побочный эффект, даже немного оптимизировать размер двоичных файлов.
- NASM: Intel x86 Instruction Reference — полное руководство по всем малоизвестным инструкциям x86.
this is a basic calculator written in assembly, it has some nice features, it was developed for the laboratory of the Computer Architecture course of the Computer Science Engineering on the USAC (so it is an useful tool for learning purposes)
- Written fully in assembly, no external library for input/output, just interruptions.
- Internally use a 128 bit representation for holding operators and result. The requirement was operate (+,-) with numbers up to 20 digits, and (*,/) with numbers up to 10 digits.
- Multiplication using repeated addition operations instead of the native “mult”.
- Divide by repeated subtraction and counting instead of the native “div”.
- Convert an ASCII String to Binary Number and vice versa. this is required to handle user input, and to display results.
- Basic Input/Output functions, like GetCh, PutCh, NewLine, WriteLine
- Custom Input/Output functions to read and write 128bit numbers, like Read128BitNum, Write128BitNum
- Basic Menu in Text Mode.
- Easy to modify, build. The package include the assembler and linker so the executable can be created anytime. (using DOSBox if you are running on a modern operating system)
For Example, in this image sequence, we can see a simple addition.
Calculator Main Menu
Calculator after execution of a addition operation.
And for large numbers (123456789 + 987654321)
Calculator after execution of an addition of two large numbers.
Some of the functions used for this project.
GetCh
this function just read a key from the keyboard and puts it’s ascii code on the AL register.
; function GetCh ; get ascii code key pressed, BIOS int GetCh: xor ah,ah ; clear ah int 0x16 ; call interruption ret ; return
Write
this function takes the address of any string and display on screen, the string must end with a “$” character.
; function Write ; display a string on screen ; ; parameter dx string address ; the string must end with a '$' character Write: push ax ; backup ax mov ah,0x9 ; func 9, display on screen int 0x21 ; DOS interruption pop ax ; restore ax ret ; return
Usage Example
mov dx, Msg1 ; call Write ; SEGMENT data ; Segment containing initialized data Msg1 DB "(1) ADD -> RESULT = A + B $"
NewLine
; function NewLine ; new line , chr(13) + chr(10) on screen NewLine: mov dx, CRLF ; call Write ; ret ; SEGMENT data ; Segment containing initialized data CRLF DB 0DH,0AH,'$'
WriteLn
this fuction just aggregate the standar Write with the predefined CRLF string, to make a string with newline.
; function Writeln ; string + newline ; parameter dx address of string to print Writeln: call Write ; Display the string proper through Write mov dx,CRLF ; Load offset of newline string to DX call Write ; Display the newline string through Write ret ; Return to the caller
This Video Explain how to Use, Modify and Rebuild the Source Code.
Download Source Code
by [googleplusauthor]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
.model small .stack 100h data segment _errorCF db 'Finded overflow at ADD operation!','$' _errorOF db 'Finded overflow at SUB operation!','$' _INFO db 'Types of operations:',10,13,'1. + -> sum',10,13,'2. - -> subtraction',10,13,'$' _nc db 10,13,'$' first dw 65535 ;первый операнд second dw 5 ;второй операнд data ends code segment assume cs:code,ds:data begin: mov ax,data mov ds,ax mov es,ax ;es ссылается туда же, где и ds ;------------------------------------------------ ;Вывод приглашения на ввод числа mov ah,09h ;команда на вывод строки mov dx,offset _INFO ;смещение на строку int 21h ;выполнить команду ;------------------------------------------------ ;Определяем тип операции enter_type_operation: mov ah,01h ;вызов команды ввода символа с клавиатуры int 21h ;выполнить команду cmp al,'+' ;введен "+" je summ ;да -> суммируем 2 числа cmp al,'-' ;введен "-" je minus ;да -> вычитаем 2 числа jmp enter_type_operation;во всех других случаях - повтор ввода ;------------------------------------------------ ;Суммирование двух чисел summ: mov ah,09h ;команда на вывод строки mov dx,offset _nc ;в dx смещение на строку int 21h ;выполнить команду mov ax,first ;ax=first mov bx,second add ax,bx ;ax=ax+bx jnc write_res ;если нет переноса(CF=0), то вывод результата jmp errorCF ;иначе вывести ошибку ;------------------------------------------------ minus: mov ah,09h ;команда на вывод строки mov dx,offset _nc ;в dx смещение на строку int 21h ;выполнить команду mov ax,first ;ax=first mov bx,second sub ax,bx ;ax=ax-bx jno write_res ;если нет заема(OF=0), то вывод результата jmp errorOF ;иначе вывести ошибку ;------------------------------------------------ ;Вывод числа write_res: test ax,ax ;проверим знак числа jns init ;SF=0? если да, то просто вывод числа mov cx,ax ;иначе выводим как отрицательное, cx=ax mov ah,02h ;выводим символ mov dl,'-' ;поместим в dl символ минуса int 21h ;выполним команду mov ax,cx ;вернем старое значение, cx=ax neg ax ;сменим знак операнда init: xor cx,cx ;cx=0 xor dx,dx ;dx=0 push -1 ;сохраним признак конца числа mov cx,10 ;делим на 10 repeat: xor dx,dx ;очистим регистр dx div cx ;делим push dx ;сохраним цифру cmp ax,0 ;остался 0? (оптимальнее or ax,ax) jne repeat ;нет -> продолжим mov ah,2h ;вывод символа digit: pop dx ;восстановим цифру cmp dx,-1 ;дошли до конца -> выход {оптимальнее: or dx,dx jl ex} je exit ;завершить вывод add dl,'0' ;преобразуем число в цифру int 21h ;выведем цифру на экран jmp digit ;и продолжим ;------------------------------------------------ ;Вывод ошибки о переполнении, при операции сложения errorCF: mov ah,09h ;команда на вывод строки mov dx,offset _errorCF;в dx смещение на строку int 21h ;выполнить команду jmp exit ;переход по метке ;------------------------------------------------ ;Вывод ошибки о заеме, при операции вычитания errorOF: mov ah,09h ;команда на вывод строки mov dx,offset _errorOF;в dx смещение на строку int 21h ;выполнить команду jmp exit ;переход по метке ;------------------------------------------------ exit: mov ax,4c00h int 21h code ends end begin |
1. Дизайн темы
Простой калькулятор
2. Дизайн контента
2.1 Основное содержание
- 1. Напишите программу, которая может считывать данные с клавиатуры и выполнять вычисления сложения, вычитания, умножения и деления.
2. Используйте прерывания BIOS и DOS для разработки калькулятора, который требует отображения главного меню на экране, предлагая пользователю ввести соответствующие цифровые клавиши для выполнения сложения, вычитания, умножения и деления соответственно Четыре функции расчета и функции для завершения программы.
3. Используйте функцию вызова прерывания BIOS № 10, чтобы установить режим отображения. 4. Используйте подфункции 01 и 02, прерванные DOS, чтобы завершить прием с клавиатуры, и результаты отобразятся.
2.2 Основные функции
1. Показать функцию главного меню
Главное меню выглядит следующим образом:
Please input a number to choose the arithmetic operation
1—add 2—subtract 3—multiply
4—divide q—return to DOS
Во-вторых, функция расчета
- 1. Не нажимайте цифровые клавиши «1», «2», «3», затем выполните соответствующие подмодули 1, 2, 3, сложите, вычтите и умножьте два байта и два байта и на экране Результат расчета отображается на экране.
2. Нажмите цифровую клавишу «4», выполните подмодуль 4, выполните операцию деления, разделив два байта на один байт, и отобразите результат.
3. Нажмите буквенную клавишу «q», чтобы запустить подмодуль 5, программа выйдет и вернется в DOS. 4. Если вы нажмете другие клавиши, появится строка приглашения: «Пожалуйста, нажмите
№ 1, 2, 3, 4, q: «, а затем продолжайте отображать главное меню.
3. Принципы и планы дизайна
3.1 Общий план
Используйте функцию вызова прерывания BIOS № 10, чтобы установить режим отображения.
Используйте прерванные подфункции DOS 01 и 02 для завершения принятия клавиатуры и отображения результатов.
Установите режим отображения экрана.
MOV AH,00H
MOV AL, NUM (NUM - слово атрибута режима отображения)
INT 10H
Затем примите значение ключа с клавиатуры и после различения перейдите к соответствующему субмодулю для расчета и отобразите результат.
MOV AH,01H
INT 21H; AL = ASCII-код набираемых символов
Получите строку команд с клавиатуры:
MOV AH,0AH
MOV DX, OFFSET BUF; BUF - буфер данных, определенный в сегменте данных
INT 21H
Показать односимвольную команду:
MOV AH,02H
INT 21H
Команда для отображения строки строк:
MOV AH,09H
MOV DX, OFFSET BUF; BUF - первый адрес отображаемой строки
INT 21H
Процесс отображения десятичного числа:
- Десятичное число отправляется в BX, BX делится на 1000, остаток отправляется в BX, частное находится в AL, диапазон составляет 0-9, и отображается содержимое в AL. Разделите BX на 100 и отправьте остаток в BX. Частное находится в AL. Содержимое в AL отображается и т. Д., Пока одна цифра не будет разделена.
Если старший бит равен 0, он не должен отображаться, а самый старший 0 отображается. Вы можете определить, отображать ли 0, установив флаг и комбинируя частное.
3.2 Блок-схема программы
3.2.1 Основная программа
3.2.2 Подпрограмма расчета
3.3 Детальное проектирование системного модуля
Структурная схема, которая будет реализована в этом проекте, показана на рисунке 1.
(1) Настройки интерфейса
В основном реализует функцию определения стиля интерфейса, который отображается в виде меню.
(2) Настройки выбора алгоритма
используется для выбора сложения, вычитания, умножения и деления для расчета.
(3) Настройки преобразования цифровой системы
Применить алгоритм десятичного двоичного преобразования для обработки четырех операций сложения, вычитания, умножения и деления.
3.3.1 Настройки интерфейса
1. Установите режим отображения экрана:
MOV AH,00H
MOV AL,2
INT 10H
Установите режим отображения экрана, вызвав функцию BOIS O2H. Здесь AL=2. Установите режим отображения экрана на 80 * 25 черно-белый текст.
2. Отобразите главное меню:
DOS предоставляет пользователям не только множество инструкций, но и сотни часто используемых подпрограмм, которые пользователи могут вызывать напрямую. Функции этих подпрограмм в основном предназначены для чтения / записи на диск, базового управления вводом / выводом и т. Д. При использовании пользователь должен знать только номер каждой подпрограммы, который становится номером вызова функции DOS. Процесс вызова:
- (1) Функциональная панель DOS отправляется в регистр AH с номером. (2) При необходимости задайте входные параметры по мере необходимости (вводить параметры не нужно). (3) Написать инструкцию прерывания «INT
21H”。
В конце вызова используйте его выходные параметры в соответствии с функцией.
Код отображения главного меню этой программы выглядит следующим образом:
MOV AH,09H
MOV DX,OFFSET TOP
INT 21H
Сначала определите строку символов, которая будет отображаться в главном меню в разделе данных, а затем завершите функцию, отобразив функцию 09H системы DOS, отображающую строку символов (конечный символ строки равен $, но она не отображается).
3.3.2 Настройки выбора алгоритма
Инструкциями сложения, вычитания, умножения и деления на языке ассемблера являются ADD, SUB, MUL и DIV.
Сначала выберите четыре операции, которые должны быть выполнены, которые предусматривают: если вы выбираете 1, вы добавляете; если вы выбираете 2, вы выполняете вычитание; когда вы выбираете 3, вы выполняете умножение; когда вы выбираете 4, вы выполняете деление. Конкретный процесс расчета анализируется следующим образом:
1. Получить номер с клавиатуры:
MOV AH,01H
INT 21H
Это реализуется путем вызова функции 01H системно набираемых символов DOS и нажатия клавиши Enter, параметром выхода является код ASCII введенных символов, который хранится в AL.
2. Получите номер, введенный для расчета:
MOV AH,0AH
MOV DX,OFFSET BUF1
Это достигается путем вызова функции 0AH системы DOS, которая вводит строку символов в буфер памяти. Параметры ввода следующие: DS: DX=Первый адрес буфера, в котором хранится набранная строка (DS: DX)=Пользовательский буфер; (DS: DX + 1)=Количество фактически введенных символов, введите введенную строку, начиная с (DS: DX + 2). Поэтому обратите внимание на первый адрес строки символов при использовании введенных чисел в следующей программе.
3. Показать результаты:
MOV AH,09H
MOV DX,OFFSET BUF3
INT 21H
Сохраните результат в буфере BUF3, определенном в разделе данных, а затем завершите функцию, отобразив функцию 09H системы DOS, отображающую строку символов (конечный символ строки равен $, но он не отображается).
3.3.3 Настройки преобразования цифровой системы
1. Подпрограмма A2-Преобразовать ASCII-код числа в двоичное число:
A2 PROC NEAR
MOV CL, BUF1 + 1; положить количество фактически набранных символов в BUF1 + 1 в CL
MOV CH, 0; CH очистить
MOV BX, OFFSET BUF1 + 2, введите адрес первой цифры в BX
PUSH BX; сохранить значение в BX
PUSH CX; сохранить значение в CX
L1: MOV AL,[BX]
AND AL, 0FH; преобразовать ASCII-код в десятичную
MOV [BX], AL; хранить десятичное число
INC BX; адрес следующей цифры
LOOP L1
POP CX; восстановить CX
POP BX; восстановить BX
MOV AX, 0; добавить накопление и ноль
MOV SI,10
L2: MUL SI
ADD AL,[BX]
ADC AH,0
INC BX
LOOP L2; кумулятивное умножение на 10
L3: RET
A2 ENDP
Вход: ASCII десятичного числа находится в BUF1; Выход: Преобразованное двоичное число находится в AX.
Алгоритм : сначала преобразуйте его в десятичную, а затем используйте метод накопления и умножения на 10 плюс X для преобразования в двоичную форму. Расчет (((0 * 10 + 1) * 10 + 6) * 10 + 3) приводит к двоичному числу.
2. Преобразуйте двоичное число результата вычисления в соответствующий десятичный код ASCII и выведите на экран (блок A3):
Для разработки программы этого модуля, пожалуйста, обратитесь к блокам A3 и BB в списке программ и описании
Запись: двоичное число находится в AX; Выход: преобразованный код ASCII находится в BUF.
Алгоритм : диапазон чисел в AX составляет от +32767 до -32768, сначала проверьте знаковый бит AX, решите вывести ‘+’ ‘-‘, если он отрицательный, сначала найдите дополнение После того, как оригинальный код может быть обработан с положительным числом. Метод: Разделите двоичное число, которое нужно сначала преобразовать, на 10000, частное — 10 000 цифр, затем разделите остаток на 1000, частное — это число тысяч, и так далее, найдите сто десять цифр, а остальные — те, которые являются цифрами. число. Наконец, добавьте 30H к каждому числу, которое является соответствующим символом.
4. Соберите исходный код
DATA SEGMENT
N=15
TOP DB 5 DUP(0AH)
DB N DUP(' '),' Please input a number to choose the arithmetic operation', 0DH,0AH
DB N DUP(' '),' ', 0DH,0AH
DB N DUP(' '),'1—add 2—subtract 3—multiply', 0DH,0AH
DB N DUP(' '),'4—divide q—return to DOS', 0DH,0AH
DB N DUP(' '),' ', 0DH,0AH
DB N DUP(' '),'Choice (1.2.3.4.q):$'
MESG1 DB 0DH,0AH,'Please press number: 1.2.3.4.q !$'
MESG2 DB 0DH,0AH,'Please input the first number :$'
MESG3 DB 0DH,0AH,'Please input the second number :$'
MESG4 DB 0DH,0AH,'Play Add :$'
MESG5 DB 0DH,0AH,'Play Subtract :$'
MESG6 DB 0DH,0AH,'Play Multiply :$'
MESG7 DB 0DH,0AH,'Play Divide :$'
MESG8 DB 0DH,0AH,'The Result :$'
MESG9 DB 0DH,0AH,'PRESS Q RETURN DOC,ANY PRESS RETURN START!$'
BUF1 DB 6 ; Десятичный буфер, может принимать 6 символов
DB ? ; Зарезервировано, используется для фактического количества символов, введенных в DOC при вызове 10-го числа.
DB 6 DUP(?) ; Один символьный бит, четырехзначный код ASCII плюс возврат каретки
BUF2 DW ? ; После преобразования первого числа в двоичное, поместите его здесь
BUF3 DB 6 DUP(?),'$' ; После преобразования результата в код ASCII, поместите его здесь
DATA ENDS
STACK SEGMENT STACK'STACK'
CODE SEGMENT
ASSUME CS:CODE , DS:DATA
START: MOV AX,DATA
MOV DS,AX
MOV AH,00H ; Установите метод отображения экрана
MOV AL,2
INT 10H
MOV AH,09H ; Показать меню
MOV DX,OFFSET TOP
INT 21H
AA: MOV AH,01H ; Получить номер с клавиатуры
INT 21H
CMP AL,'1' ; По сравнению с 1
JNE NEXT1 ; Если оно не равно 1, перейдите к следующему
JMP JIA ; Если оно равно 1, перейти к процессу сложения
NEXT1: CMP AL,'2' ; По сравнению с 2
JNE NEXT2 ; Если оно не равно 2, перейдите к следующему
JMP JIAN ; Равное 2 пойдет в процесс вычитания
NEXT2: CMP AL,'3' ; По сравнению с 3
JNE NEXT3 ; Не равно 3, перейти к следующему
JMP CHENG ; Перейти к программе умножения, когда она равна 3
NEXT3: CMP AL,'4' ; По сравнению с 4
JNE NEXT4 ; Если оно не равно 4, перейдите к NEXT4
JMP CHU ; Равный 4 и перейти к процедуре деления
NEXT4: CMP AL,'q' ; По сравнению с q
JNE NEXT5 ; Если оно не равно q, переходите к NEXT5
MOV AH,4CH ; Вернуться к DOS
INT 21H
NEXT5: MOV AH,09H ; Дисплей MESG1
MOV DX,OFFSET MESG1
INT 21H
JMP AA ; Перейти к аа
A1 PROC NEAR
MOV AH,09H ; Подскажите по первому номеру
MOV DX,OFFSET MESG2
INT 21H
MOV AH,0AH ; Получить первый номер
MOV DX,OFFSET BUF1
INT 21H
CALL A2 ; Вызов A2 для двоичного преобразования ASCII
MOV BUF2,AX ; Поместите первое преобразованное двоичное число в BUF2
MOV AH,09H ; Подскажите второй номер
MOV DX,OFFSET MESG3
INT 21H
MOV AH,0AH ; Получить второй номер
MOV DX,OFFSET BUF1
INT 21H
CALL A2 ; Вызовите А2, чтобы преобразовать второе число в двоичный код ASCII
RET
A1 ENDP
A2 PROC NEAR
MOV CL,BUF1+1 ; Поместите количество фактически введенных символов в BUF1 + 1 в CL
MOV CH,0 ; Сброс CH
MOV BX,OFFSET BUF1+2 ; Введите адрес первой цифры в BX
PUSH BX ; Сохранить значение в BX
PUSH CX ; Сохранить значение в CX
L1: MOV AL,[BX]
AND AL,0FH ; Преобразовать ASCII-код в десятичную
MOV [BX],AL ; Хранить десятичные
INC BX ; Адрес следующей цифры
LOOP L1
POP CX ; Восстановить CX
POP BX ; Восстановить BX
MOV AX,0 ; Добавить и ноль
MOV SI,10
L2: MUL SI
ADD AL,[BX]
ADC AH,0
INC BX
LOOP L2 ; Суммарное умножение на 10
L3: RET
A2 ENDP
JIA: MOV AH,09H ; Процедура добавления
MOV DX,OFFSET MESG4
INT 21H
CALL A1 ; Звоните А1
ADD AX,BUF2 ; Поместите преобразованное первое число в BUF2 и поместите его в AX
JMP A3 ; Добавьте второе число
JIAN: MOV AH,09H ; Процедура вычитания
MOV DX,OFFSET MESG5
INT 21H
CALL A1
MOV BX,AX ; Поместите преобразованный второй номер в AX в BX
MOV AX,BUF2 ; Поместите первый номер в BUF2 после преобразования в AX
SBB AX,BX ; Вычтите два числа
JMP A3
CHENG: MOV AH,09H ; Программа умножения
MOV DX,OFFSET MESG6
INT 21H
CALL A1
MOV BX,AX ; Поместите преобразованный второй номер в AX в BX
MOV AX,BUF2 ; Поместите первое число в BUF2 после преобразования в AX
MUL BX ; Умножьте два числа
JMP A3
CHU: MOV AH,09H ; Процедура деления
MOV DX,OFFSET MESG7
INT 21H
CALL A1
MOV DX,AX ; Поместите преобразованный второй номер в AX в DX
MOV AX,BUF2 ; Поместите первое число в BUF2 после преобразования в AX
DIV DL ; Разделите два числа, частное в AL
MOV AH,0H ; Очистить AH
JMP A3
A3: MOV BUF3,'+' ; Поставить BUF3'+'
CMP AX,0 ; Сравните результат с 0
JGE L4 ; AX>0 Перейти к L4
NEG AX ; AX<0 инвертировать AX
MOV BUF3,'-' ; Поставить BUF3'-'
L4: CWD
MOV BX,10000
DIV BX ; Частное является первой цифрой в AL, а остальная часть находится в DX
CALL BB
MOV BUF3+1,AL ; Поместите первый номер в единицу BUF3 + 1
MOV AX,DX ; Положите остаток в DX в AX
CWD
MOV BX,1000
DIV BX ; Частное является второй цифрой в AL, а остаток в DX
CALL BB
MOV BUF3+2,AL ; Поместите второе число в единицу BUF3 + 2
MOV AX,DX ; Положите остаток в DX в AX
MOV BL,100
DIV BL ; Частное является третьей цифрой в AL, а остаток в AH
CALL BB
MOV BUF3+3,AL ; Поместите третий номер в блок BUF3 + 3
MOV AL,AH ; Поместите остаток в AH в AL
CBW
MOV BL,10
DIV BL ; Частное является четвертой цифрой в AL, а остаток в A
CALL BB
MOV BUF3+4,AL ; Поместите четвертое число в единицу BUF3 + 4
ADD AH,30H ; Преобразовать свою цифру в код ASSCII
MOV BUF3+5,AH ; Поместите пятое число в блок BUF3 + 5
MOV AH,09H ; Быстрый результат
MOV DX,OFFSET MESG8
INT 21H
MOV AH,09H
MOV DX,OFFSET BUF3 ; Показать результаты в буфере BUF3
INT 21H
MOV AH,09H ; Подскажите стоит ли возвращаться в DOS или пересчитать
MOV DX,OFFSET TOP
INT 21H
JMP NEXT5 ; Пересчет других цифр
BB PROC NEAR
CMP AL,0H ; Сравнить с нулем
JE L5
JMP L6
L5: MOV AL,20H ; Равный нулю, назначьте AL пробел
JMP L7
L6: ADD AL,30H ; Не равно нулю, преобразовать число в код ASSCII
L7: RET
BB ENDP
A4: MOV AH,4CH ; AL равняется'Q'Просто вернитесь в DOS
INT 21H
CODE ENDS
END START
5. Экспериментальные результаты
прибавление
Вычитание
Умножение
Калькулятор
Усовершенствованный калькулятор
Резюме
В предыдущей теме была рассмотрена программа для ввода шестнадцатеричных чисел. Но эта программа обладала существенным недостатком: кроме чисел 0 … F она воспринимала практически все вводимые символы, и интерпретировала их как числа.
Поэтому, нам следует написать процедуру, которая при вводе будет фильтровать символы шестнадцатеричных чисел: «0» … «9», «A» … «F».
Фильтр
Рассмотрим две новые инструкции, которые потребуются для фильтрации чисел:
JA («Jump if Above» — перейти если больше)
JB («Jump if Below» — перейти если меньше)
Инструкции JA и JB работают только с положительными числами 0 … FFFFh, и игнорируют флаг переполнения (OF). Изученная ранее инструкция JL (перейти, если меньше), работает как с положительными, так и отрицательными числами (проверяет OF).
Если для запроса символа использовать функцию 01h прерывания INT 21h, то ввод любого символа будет сопровождаться эхом (т.е. выводом данного символа на экран).
Новая процедура не должна отображать ошибочно введенные символы. Для ввода символа без эхо-повтора, мы используем функцию 08h прерывания INT 21h.
Ниже приводится программа-тест для проверки фильтра. Для повышения читабельности, из листинга удалены коды инструкций и сегмент адреса. Инструкции основной программы внесите по адресам 100h и 103h, а процедуру расположите по адресу 200h:
0100 CALL 0200 основная программа
0103 INT 20
0200 PUSH DX процедура ввода и фильтрации чисел 0 ... F
0201 MOV AH,08 ввод символа без эха
0203 INT 21
0205 CMP AL,30
0207 JB 0203 если AL < 30h, то переход на 203h
0209 CMP AL,46
020B JA 0203 если AL > 46h, то переход на 203h
020D CMP AL,3A
020F JB 0215 если AL < 3Ah, то переход на 215h
0211 CMP AL,41
0213 JB 0203 если AL < 41h, то переход на 203h
0215 MOV AH,02
0217 MOV DL,AL печать отфильтрованного символа
0219 INT 21
021B SUB AL,30
021D CMP AL,09 преобразование кода символа в число
021F JLE 0223
0221 SUB AL,07
0223 POP DX
0224 RET
Протестируйте программу с различными символами и убедитесь, что фильтр пропускает только символы шестнадцатеричных чисел: «0» … «F». Проверьте ввод символов «a» … «f».
Почему процедура начинается с адреса 200h? Процедуры могут располагаться в любом месте программы, но обычно их пишут после основной программы. Адрес 200h выбран с учетом места под текст основной программы (100h — ячеек). Можно перенести процедуру на адрес 105h, тогда между основной программой и процедурой не останется свободных ячеек. При этом придется поменять все адреса в инструкциях условных переходов.
В процедуре используются регистры AX и DX, но в стеке сохраняется только регистр DX. Почему? В ходе выполнения процедуры, в AL передается цифра введенная с клавиатуры. Если в начале процедуры сохранить значение регистра AX в стек, а в конце процедуры его восстановить, то введенная внутри процедуры цифра будет потеряна. Поэтому, не каждый регистр можно временно сохранять в стеке.
Данную процедуру можно использовать для ввода двузначных чисел. Например, программа должна запрашивать hex-код символа, и выводить этот символ на экран:
0100 CALL 0200 переход на процедуру ввода hex-цифры
0103 MOV DL,AL
0105 MOV CL,04
0107 SHL DL,CL
0109 CALL 0200 переход на процедуру ввода hex-цифры
010C ADD DL,AL
010E MOV AH,02
0110 INT 21
0112 INT 20
Проверьте работу программы с кодами:
2Ah (символ «*»), 07h (звуковой сигнал), 40h (символ «@») и др.
Калькулятор
Программа запрашивает две hex-цифры, суммирует их и выводит результат на экран.
Например: F + 1 = 10
0100 CALL 0200
0103 MOV BL,AL
0105 MOV AH,02
0107 MOV DL,2B печать знака "+"
0109 INT 21
010B CALL 0200
0110 MOV AH,02
0112 MOV DL,3D печать знака "="
0114 INT 21
010E ADD BL,AL
0116 CALL 0150
0119 INT 20
...
0150 Процедура печати двузначного числа из регистра BL
...
0200 Процедура ввода hex-цифры в регистр AL
Программу печати двузначного числа из регистра BL нужно переделать в процедуру:
- замените INT 20h на RET;
- измените адреса в инструкциях JL;
- можете добавить инструкции PUSH и POP (но в данном случае это не обязательно).
Задачи:
- Если результат сложения меньше 10h, то в старшем разряде ответа печатается ноль. Например: 3 + 2 = 05. Вам нужно убрать этот «лишний» ноль.
- Убрать пустые адресные пространства между основной программой и процедурами. Записать программу на диск.
Усовершенствованный калькулятор
Вы написали программу, которая умеет складывать два шестнадцатеричных числа.
Теперь необходимо «обучить» калькулятор вычитанию.
В приложениях находится текст готовой программы. Но не спешите искать готовое решение. Эта программа является итоговой задачей, желательно написать ее самостоятельно. Если вы успешно справитесь с ней, то знайте: время, проведенное за клавиатурой, потрачено не зря.
Для написания программы можно использовать следующую блок-схему:
Ввод-фильтр первой цифры, копия числа в регистре BH. |
Ввод-фильтр знака «+» или «-«, копия знака в регистре DH. |
Ввод-фильтр второй цифры, копия числа в регистре BL. |
Ввод-фильтр знака «=». После ввода «=» появляется ответ. |
Вычисление результата: если DH = «+», то BH + BL если DH = «-«, то BH — BL |
Вывод результата на экран. |
INT 20h |
Процедура: «Вывод символа на экран» |
Процедура: «Ввод-фильтр hex-цифры» |
Эта блок-схема не является единственным решением, вы можете разработать свой вариант алгоритма.
Программу можно подготовить в более простом виде: без контроля отрицательных чисел и проверки разрядности числа. Например:
1 - 2 = FF
2 + 3 = 05
В усложненном варианте результат выглядит иначе:
1 - 2 = -1
2 + 3 = 5
Именно так формирует результат программа из приложения.
Для написания программы вам может потребоваться инструкция безусловного перехода:
JMP (Jump — переход). Например:
JMP 210 переход на адрес 210h
Резюме
Седьмая тема завершает вводную часть курса «Основы программирования на ассемблере». Теперь вы знаете, что:
- числа можно представлять не только в десятичной системе счисления;
- арифметические операции выполняются в любой системе счисления;
- регистры позволяют хранить в микропроцессоре одновременно несколько чисел;
- система команд CPU позволяет выполнять арифметические и другие действия;
- процедуры и прерывания значительно упрощают разработку программ.
Многое из того, что вы написали, будет использовано далее. Во второй части курса мы перейдем к более серьезным и интересным вопросам программирования на ассемблере.
This program is a programmable big hexadecimal number calculator, written in x86 assembly language using FASM (the flat assembler). It works with unsigned integers that can be up to 400 bytes (800 hex digits) in length. It looks like this:
Up to 14 parameters are available to the user of the program, all of which are 400 byte hex numbers. Ten of these parameters can be defined by the user, while the other 4 are built-in.
The edit control on the left allows the user to enter up to ten 400 byte input parameters. Curly brackets are used to define the name of a parameter. A parameter name can be up to 8 characters in length and can include letters, numbers and an underscore. It is case sensitive. The parameter name is followed by a hex value which can be up to 400 bytes (or 800 hex digits) in length. If nothing follows a parameter name definition, the value of that parameter will be initialised to zero. For example:
Once the parameters and their values have been entered, press the [Load Parameters] button to read them into memory. To clear all user defined parameters, press the [Clear Parameters] button.
There are also 4 built-in parameters: reg_1, reg_2, reg_3 and reg_4. These are displayed in the edit control on the lower right.
Calculations are implemented by entering instructions into the edit control on the upper right. Up to 40 instructions can be entered and executed at any one time. Instructions are executed by pressing the [Run Instructions] button. Press the [Clear Instructions] button to clear the instructions edit control.
Press the [Reset All] button to clear everything.
The instructions used to perform arithmetic operations are like x86 assembly language instructions. But instead of the arguments being registers or memory locations, they are the 400 byte hex number parameters as described above. Some of the instructions also take an immediate value as one of their arguments. Depending on the instruction, the immediate value will be read either as a hex number or a decimal number. The hex number immediate values can be up to 16 bytes (32 hex digits) in length and must not contain any white space or non hex digit characters. The decimal number immediate values can only be up to 4 decimal digits in length and must not be greater than 3200.
The instruction are not case sensitive. So for example, mov can be written as: mov, Mov or MOV. The arguments (the parameter names) are case sensitive.
The instructions and their arguments are white space delimited, and there can also be any amount of leading and trailing white spaces.
In other words: move some immediate hex values into reg_1, reg_2 and reg_3. Then add reg_1 to reg_2. Not reg_1 and then multiply reg_3 by reg_1. Xor reg_2 into reg_3 and then right rotate reg_2 by 127 bits.
Dest is one of the 400 byte hex values.
Both dest and src are one of the 400 byte hex values.
Dest is one of the 400 byte hex values. The second argument is a 16 byte (128 bit) hex value. There must be no spaces or non hex digits in this argument.
Dest is one of the 400 byte hex values. The second argument is a decimal number of up to 4 digits. It cannot be greater than 3200.
The argument is an immediate value which is read as a decimal number of up to 4 digits. It cannot be greater than 3200.
The Nbytes instruction affects the SHL, SHR, ROTL and ROTR instructions. It sets the word size that these instructions operate on. For example Nbytes 4 sets the word size to 4 bytes, and any parameter which is being shifted or rotated will be treated as a 4 byte value by these operations.
Nbytes must be in the range 1 – 400. Any value outside of this range will cause the nBytes value to be set to the default of 400.
Here are some examples showing the sort of calculations that can be done with the program.
To find the greatest common divisor (GCD) of two numbers A and B, the Euclidean Algorithm repeatedly applies the identity:
– until A mod B becomes zero.
To use the calculator to find the GCD of a pair of numbers, first enter the numbers (for example):
Repeatedly press the [Run Instructions] button to execute these two instructions again and again until B has been reduced to zero. At this point, A will hold the value of the GCD (which in this case is 1).
From the Advanced Encryption Standard, the finite field multiplication function xtime():
These instructions fill an array with the byte values 0 – FF, and a second array with the byte values after finite field multiplication by 2.
To encrypt using the above test vector, enter the following parameters and press [Load Parameters]:
Note the first instruction: nbytes 4. This instruction tells the program that when the SHL and SHR operations are applied, the parameters being shifted are to be treated as 4 byte values (as required by the TEA algorithm).
Executing these instructions should give the result: v0 = 8df5d71f and v1 = 9c3be43f. Note that at the end of the calculation v0 and v1 are truncated to 32 bit (4 byte) values.
Use an adaptation of the middle square method to generate a pseudo random 400 byte value:
Use the Linear Congruential Generator to generate a pseudo random 400 byte value:
The modulus value m (0x100000000) in this case is just 232, which is equivalent to truncating to 32 bits.
Use the Galois Linear Feedback Shift Register to generate a pseudo random 400 byte value:
Enter instructions to do the modular exponentiation calculation. Note that each of A and C should not be greater than 200 bytes.
Note that for the larger input numbers, it can take a couple of minutes for this calculation to execute.
To grab this code, select it with the mouse and copy it. It can then be pasted directly into the FASM IDE.
; ------------------------------------------------------------------------------------- format PE GUI 4.0 entry start include 'win32a.inc' ; ------------------------------------------------------------------------------------- IDD_THE_DIALOG = 102 IDC_INPUT = 1000 IDC_CALCS = 1001 IDC_OUTPUT = 1002 IDC_BTN_LOAD_DATA = 1003 IDC_BTN_CALC = 1004 IDC_BTN_CLR_DATA = 1005 IDC_BTN_CLR_INSTR = 1006 IDC_BTN_RESET_ALL = 1007 ; ------------------------------------------------------------------------------------- HEX_LEN = 400 MAX_HEX_DIGITS = 2*HEX_LEN N_CALCS = 40 CALCS_BUFFER_SZ = 20*N_CALCS ; ------------------------------------------------------------------------------------- section '.code' code readable executable start: invoke GetModuleHandle,0 invoke DialogBoxParam,eax,IDD_THE_DIALOG,0,DialogProc,0 exit: invoke ExitProcess,0 ; ------------------------------------------------------------------------------------- proc DialogProc uses esi edi ebx,hwnddlg,msg,wparam,lparam cmp [msg],WM_INITDIALOG je .wminitdialog cmp [msg],WM_COMMAND je .wmcommand cmp [msg],WM_CLOSE je .wmclose xor eax,eax jmp .quit .wminitdialog: invoke SetDlgItemText,[hwnddlg],IDC_INPUT,szInputText invoke SetDlgItemText,[hwnddlg],IDC_CALCS,szExprText stdcall ClearRegisterValues stdcall PrintRegisterValues invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .wmcommand: cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_LOAD_DATA je .LOAD cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CALC je .CALC cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CLR_DATA je .CLR_DATA cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CLR_INSTR je .CLR_INSTR cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_RESET_ALL je .RESET jmp .done .LOAD: invoke GetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay,12000 stdcall ValidateParamsBuffer cmp al,0xff je .ERR_8 stdcall ProcessDisplayBuffer stdcall PrintSymbolsParams invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay stdcall ClearRegisterValues stdcall PrintRegisterValues invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .ERR_8: mov [nErrorCode],byte 8 jmp .ERROR .CALC: invoke GetDlgItemText,[hwnddlg],IDC_CALCS,bfDisplay,12000 stdcall ValidateCalcsBuffer cmp al,0xff je .ERROR stdcall LoadCalculations cmp al,0xff je .ERROR stdcall ExecuteCalculations cmp eax,0xffffffff je .CALC_ERROR stdcall PrintSymbolsParams invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay stdcall PrintRegisterValues invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .CALC_ERROR: stdcall PrintExeError invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay stdcall PrintSymbolsParams invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay jmp .done .ERROR: stdcall PrintErrorMessage invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .CLR_DATA: invoke SetDlgItemText,[hwnddlg],IDC_INPUT,"" stdcall ClearSymbolTable stdcall ClearRegisterValues stdcall PrintRegisterValues invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .CLR_INSTR: invoke SetDlgItemText,[hwnddlg],IDC_CALCS,"" jmp .done .RESET: invoke SetDlgItemText,[hwnddlg],IDC_INPUT,szInputText invoke SetDlgItemText,[hwnddlg],IDC_CALCS,szExprText mov [nBytes],HEX_LEN stdcall ClearSymbolTable stdcall ClearRegisterValues stdcall PrintRegisterValues invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay jmp .done .wmclose: invoke EndDialog,[hwnddlg],0 .done: mov eax,1 .quit: ret endp ; ------------------------------------------------------------------------------------- proc ClearRegisterValues lea edi,[Reg_1] stdcall ClearParam lea edi,[Reg_2] stdcall ClearParam lea edi,[Reg_3] stdcall ClearParam lea edi,[Reg_4] stdcall ClearParam ret endp ; ------------------------------------------------------------------------------------- macro PRINT_CRLF { mov [edi],byte 13 inc edi mov [edi],byte 10 inc edi } ; ------------------------------------------------------------------------------------- proc PrintRegisterValues ; print reg_1, reg_2, reg_3, reg_4 lea edi,[bfDisplay] ; reg_1 mov [edi],dword "reg_" add edi,4 mov [edi],dword "1= " add edi,4 PRINT_CRLF PRINT_CRLF lea esi,[Reg_1] stdcall PrintHexValue ; reg_2 mov [edi],dword "reg_" add edi,4 mov [edi],dword "2= " add edi,4 PRINT_CRLF PRINT_CRLF lea esi,[Reg_2] stdcall PrintHexValue ; reg_3 mov [edi],dword "reg_" add edi,4 mov [edi],dword "3= " add edi,4 PRINT_CRLF PRINT_CRLF lea esi,[Reg_3] stdcall PrintHexValue ; reg_4 mov [edi],dword "reg_" add edi,4 mov [edi],dword "4= " add edi,4 PRINT_CRLF PRINT_CRLF lea esi,[Reg_4] stdcall PrintHexValue ; null terminate mov [edi],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc PrintSymbolsParams ; print the parameter names and their values to the bfDisplay buffer lea edi,[bfDisplay] ; Symbol_1 and Param_1 lea esi,[Symbol_1] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_1] stdcall PrintHexValue ; Symbol_2 and Param_2 lea esi,[Symbol_2] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_2] stdcall PrintHexValue ; Symbol_3 and Param_3 lea esi,[Symbol_3] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_3] stdcall PrintHexValue ; Symbol_4 and Param_4 lea esi,[Symbol_4] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_4] stdcall PrintHexValue ; Symbol_5 and Param_5 lea esi,[Symbol_5] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_5] stdcall PrintHexValue ; Symbol_6 and Param_6 lea esi,[Symbol_6] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_6] stdcall PrintHexValue ; Symbol_7 and Param_7 lea esi,[Symbol_7] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_7] stdcall PrintHexValue ; Symbol_8 and Param_8 lea esi,[Symbol_8] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_8] stdcall PrintHexValue ; Symbol_9 and Param_9 lea esi,[Symbol_9] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_9] stdcall PrintHexValue ; Symbol_10 and Param_10 lea esi,[Symbol_10] cmp [esi],byte 0 je .DONE stdcall PrintSymbol lea esi,[Param_10] stdcall PrintHexValue .DONE: ; null terminate mov [edi],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc PrintHexValue uses ecx eax ; ESI and EDI set outside of this procedure locals count db 0 endl ; find the first non zero dword in the hex value ; starting from the high dword mov cx,HEX_LEN-4 add esi,HEX_LEN-4 .FIND: mov eax,[esi] cmp eax,0 jne .PRINT sub esi,4 sub cx,4 jcxz .PRINT jmp .FIND .PRINT: mov eax,[esi] ; dword to 8 digits stdcall DWordToStr jcxz .DONE sub cx,4 sub esi,4 inc [count] ; add a CRLF to the output string after every 6 dwords cmp [count],byte 6 je .ADD_CRLF jmp .PRINT .ADD_CRLF: mov [count],byte 0 PRINT_CRLF jmp .PRINT .DONE: PRINT_CRLF PRINT_CRLF ret endp ; ------------------------------------------------------------------------------------- proc WordToStr uses edx ; print a word (2 bytes) to a string ; word passed in AX ; EDI set outside of this function mov dx,ax mov al,ah stdcall ByteToDigits mov [edi],ah inc edi mov [edi],al inc edi mov al,dl stdcall ByteToDigits mov [edi],ah inc edi mov [edi],al inc edi ret endp ; ------------------------------------------------------------------------------------- proc DWordToStr ; print a dword (4 bytes) to a string ; word passed in EAX ; EDI set outside of this function locals tmp dw 0 endl mov word [tmp],ax shr eax,16 stdcall WordToStr mov ax,word [tmp] stdcall WordToStr ; add a space mov [edi],byte 32 inc edi ret endp ; ------------------------------------------------------------------------------------- proc PrintSymbol uses ecx ; ESI and EDI set outside of this procedure mov cx,0 .PRINT: mov al,[esi] cmp al,0 je .DONE mov [edi],al inc esi inc edi inc cx cmp cx,8 jge .DONE jmp .PRINT .DONE: ; '=' mov [edi],byte 61 inc edi PRINT_CRLF PRINT_CRLF ret endp ; ------------------------------------------------------------------------------------- proc ProcessDisplayBuffer ; extract the symbols and hex values from bfDisplay stdcall ClearSymbolTable lea esi,[bfDisplay] ; get Symbol_1 and Param_1 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_1] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_1] stdcall InputBufferToParam ; get Symbol_2 and Param_2 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_2] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_2] stdcall InputBufferToParam ; get Symbol_3 and Param_3 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_3] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_3] stdcall InputBufferToParam ; get Symbol_4 and Param_4 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_4] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_4] stdcall InputBufferToParam ; get Symbol_5 and Param_5 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_5] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_5] stdcall InputBufferToParam ; get Symbol_6 and Param_6 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_6] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_6] stdcall InputBufferToParam ; get Symbol_7 and Param_7 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_7] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_7] stdcall InputBufferToParam ; get Symbol_8 and Param_8 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_8] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_8] stdcall InputBufferToParam ; get Symbol_9 and Param_9 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_9] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_9] stdcall InputBufferToParam ; get Symbol_10 and Param_10 stdcall FindSymbol cmp al,0 je .DONE lea edi,[Symbol_10] stdcall GetSymbol stdcall HexStrToInputBuffer lea edi,[Param_10] stdcall InputBufferToParam .DONE: ret endp ; ------------------------------------------------------------------------------------- proc FindSymbol ; look for the opening brace { (ascii = 123) in bfDisplay ; ESI is set outside of this procedure ; set AL to indicate success mov al,0 .FIND: mov dl,[esi] cmp dl,0 je .DONE cmp dl,123 je .FOUND inc esi jmp .FIND .FOUND: mov al,1 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc GetSymbol uses ecx ; store the symbol in the symbol table ; delimiters = { and } = 123 and 125 ; ESI and EDI set outside of this procedure xor ecx,ecx ; ESI already points to the '{' inc esi .STORE: mov al,[esi] cmp al,0 je .DONE ; '}' cmp al,125 je .DONE mov [edi],al inc esi inc edi inc cx ; read up to 8 chars cmp cx,8 jge .CLOSE jmp .STORE .CLOSE: ; if 8 chars were read, find the closing brace mov al,[esi] cmp al,0 je .DONE cmp al,125 je .DONE inc esi jmp .CLOSE .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ClearSymbolTable uses edi ecx ; clear the symbols lea edi,[Symbol_1] mov cx,0 .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,100 jl .CLEAR ret endp ; ------------------------------------------------------------------------------------- proc InputBufferToParam uses esi ecx ; convert the string in InputBuffer to a hex value ; EDI is set outside of this procedure ; EDI points to one of: Param_1, Param_2, etc lea esi,[InputBuffer] ; clear the buffer pointed to by EDI mov edx,edi stdcall ClearParam ; restore EDI mov edi,edx ; count the digits in InputBuffer and save to AX stdcall CountDigits ; check if the input buffer is empty cmp ax,0 je .DONE ; calculate the offset in bytes from the start of EDI ; based on the number of digits in the input buffer (given by AX) xor ecx,ecx mov cx,ax shr cx,1 test ax,1 jnz .ODD .EVEN: ; the input buffer contains an even number of hex digits ; offset = (nDigits/2) - 1 dec cx add edi,ecx jmp .GET_DIGITS .ODD: ; the input buffer contains an odd number of hex digits ; offset = nDigits/2 ; convert the first digit add edi,ecx mov al,[esi] stdcall HexDigitToValue mov [edi],al inc esi dec edi dec cx cmp cx,0 jl .DONE .GET_DIGITS: ; convert 2 hex digits to 1 byte mov al,[esi] stdcall HexDigitToValue shl al,4 mov [edi],al inc esi mov al,[esi] stdcall HexDigitToValue or [edi],al inc esi dec cx cmp cx,0 jl .DONE dec edi jmp .GET_DIGITS .DONE: ret endp ; ------------------------------------------------------------------------------------- proc HexStrToInputBuffer uses edi ecx ; copy a string containing a hex number to the InputBuffer ; filter out any non hex digit characters ; ESI is set outside of this procedure lea edi,[InputBuffer] xor ecx,ecx .FILTER: mov al,[esi] ; 0 = null terminator cmp al,0 je .DONE ; 123 = { cmp al,123 je .DONE cmp al,48 jl .NOT_HEX cmp al,57 jle .COPY cmp al,65 jl .NOT_HEX cmp al,70 jle .COPY cmp al,97 jl .NOT_HEX cmp al,102 jg .NOT_HEX .COPY: mov [edi],al inc edi inc ecx cmp ecx,MAX_HEX_DIGITS jge .DONE .NOT_HEX: inc esi jmp .FILTER .DONE: ; null terminate the new string mov [edi],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc ClearParam uses ecx ; clear the input parameter ; EDI is set outside of this procedure xor ecx,ecx .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,HEX_LEN jl .CLEAR ret endp ; ------------------------------------------------------------------------------------- proc CountDigits uses esi ecx ; count the number of hex digits in the InputBuffer string mov ax,0 xor ecx,ecx lea esi,[InputBuffer] .COUNT: mov dl,[esi] cmp dl,0 je .DONE inc cx inc esi jmp .COUNT .DONE: ; return the digit count in AX mov ax,cx ret endp ; ------------------------------------------------------------------------------------- proc HexDigitToValue ; convert a hex digit to a 4 bit value ; input in AL - output in AL ; is AL in the range: 48 - 57 (0 - 9) ? cmp al,48 jl .NOT_HEX cmp al,57 jg .A_Z sub al,48 jmp .DONE .A_Z: ; is AL in the range: 65 - 70 (A - B) ? cmp al,65 jl .NOT_HEX cmp al,70 jg .a_z sub al,55 jmp .DONE .a_z: ; is AL in the range: 97 - 102 (a - b) ? cmp al,97 jl .NOT_HEX cmp al,102 jg .NOT_HEX sub al,87 jmp .DONE .NOT_HEX: ; set EAX to 0xffff if input is not a valid hex digit mov eax,0xffff .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ByteToDigits ; convert 1 byte to 2 hex digits ; input in AL - output in AX ; copy AL to AH mov ah,al ; AH: get the upper 4 bits of the byte shr ah,4 ; nibble to hex digit add ah,48 cmp ah,57 jle .NEXT add ah,7 .NEXT: ; AL: get the lower 4 bits of the byte and al,0xf ; nibble to hex digit add al,48 cmp al,57 jle .DONE add al,7 .DONE: ; output is in AX ret endp ; ------------------------------------------------------------------------------------- proc LoadCalculations ; load the calculations from the bfDisplay string stdcall ResetCalculations mov [nErrorCode],byte 0 lea esi,[bfDisplay] .NEXT: ; get the next line lea edi,[CurrentLine] mov al,[esi] cmp al,0 je .DONE .GET_LINE: ; read the line into EDI (don't include CRLF) mov al,[esi] cmp al,13 je .CRLF cmp al,10 je .CRLF cmp al,0 je .PROCESS mov [edi],al inc esi inc edi jmp .GET_LINE .CRLF: ; advance ESI to the first non-CRLF (ready for the next line) inc esi mov al,[esi] cmp al,13 je .CRLF cmp al,10 je .CRLF .PROCESS: ; null terminate EDI mov [edi],byte 0 ; process the line in EDI stdcall ProcessLine cmp al,0xff je .DONE inc [nCalculation] cmp [nCalculation],N_CALCS jge .DONE jmp .NEXT .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ProcessLine uses esi edi ; the current calculation (line) is in the string CurrentLine ; convert to instr and param codes, and store in the Calculations buffer ; structure of Calculations buffer: ; 1 calculation = 20 bytes ; byte 1 = operation code ; byte 2 = arg 1 code ; byte 3 = arg 2 code ; byte 4 = arg 3 code ; byte 5-20 = 16 byte immediate value ; total size of Calculations buffer = 800 bytes = 40 calculations ; set via N_CALCS and CALCS_BUFFER_SZ locals instrcode db 0 endl lea esi,[CurrentLine] lea edi,[Calculations] ; adjust EDI to point to the correct location in the Calculations buffer xor edx,edx ; multiply by 20 = shl(DL,4) + shl(DL,2) mov dl,[nCalculation] shl edx,4 xor eax,eax mov al,[nCalculation] shl eax,2 add edx,eax ; nCalculation has been multiplied by 20, now add to EDI add edi,edx ; skip any leading spaces in the line stdcall SkipSpaces ; get the instruction stdcall GetInstrCode cmp al,0xff je .DONE ; store the instr code in the Calculations buffer mov [edi],al inc edi ; also save to instrcode mov [instrcode],al ; instr codes 1 to 31 - 2 arguments ; instr codes 32 to 63 - 1 argument + 1 immediate ; instr codes 64 to 95 - 1 argument ; instr code 96 to 127 - 1 immediate cmp [instrcode],95 jg .96_127 ; get the first argument stdcall SkipSpaces cmp al,0xff je .DONE stdcall GetNextArgument cmp al,0xff je .DONE ; store the arg code in the Calculations buffer mov [edi],al inc edi .1_31: cmp [instrcode],31 jg .32_63 ; get the second argument stdcall SkipSpaces cmp al,0xff je .DONE stdcall GetNextArgument cmp al,0xff je .DONE ; store the arg code in the Calculations buffer mov [edi],al inc edi jmp .EXTRA_CHARS .32_63: cmp [instrcode],63 jg .64_95 ; EDI points to the location in the Calculations buffer ; where the 128 bit immediate value will be stored inc edi inc edi ; get the immediate value stdcall SkipSpaces cmp al,0xff je .DONE stdcall GetImmediate ; is the Immediate argument in decimal or hex cmp [instrcode],38 jl .HEX_IMMED cmp [instrcode],43 jg .HEX_IMMED .DEC_IMMED: stdcall DecImmediateToHex jmp .NEXT .HEX_IMMED: stdcall ImmediateToHex .NEXT: cmp al,0xff je .DONE stdcall ImmediateToCalcsBuffer jmp .EXTRA_CHARS .64_95: cmp [instrcode],95 jg .96_127 jmp .EXTRA_CHARS .96_127: ; EDI points to the location in the Calculations buffer ; where the 128 bit immediate value will be stored inc edi inc edi inc edi ; get the immediate value stdcall SkipSpaces cmp al,0xff je .DONE stdcall GetImmediate ; immediate value is in decimal stdcall DecImmediateToHex cmp al,0xff je .DONE stdcall ImmediateToCalcsBuffer .EXTRA_CHARS: stdcall CheckExtraChars .DONE: ret endp ; ------------------------------------------------------------------------------------- proc CheckExtraChars ; the instr and args have been read ; are there still unread chars on the line ? ; spaces are OK ; ESI is set outside of this procedure mov al,0 .CHECK: mov dl,[esi] cmp dl,0 je .DONE cmp dl,32 je .NEXT mov al,0xff mov [nErrorCode],byte 1 jmp .DONE .NEXT: inc esi jmp .CHECK .DONE: ret endp ; ------------------------------------------------------------------------------------- proc GetImmediate uses edi ; read the immediate hex value in ESI ; ESI is set outside of this procedure ; read up to 32 hex digits lea edi,[bfImmediate] mov cx,0 ; read until either a space or a NULL is found .READ: mov dl,[esi] cmp dl,0 je .DONE cmp dl,32 je .DONE mov [edi],dl inc esi inc edi inc cx ; read up to 32 digits cmp cx,32 jl .READ .DONE: ; null terminate mov [edi],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc ImmediateToHex uses esi edi ; convert the string in bfImmediate to a 16 byte hex value lea edi,[Immediate] mov cx,0 ; zero the dest value .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,16 jl .CLEAR ; find the end of the bfImmediate string xor ecx,ecx lea esi,[bfImmediate] .EOS: ; seek the null terminator, or count 32 digits mov dl,[esi] cmp dl,0 je .NEXT inc esi inc cx cmp cx,32 jl .EOS .NEXT: cmp cx,0 je .DONE dec esi lea edi,[Immediate] ; CX contains the digit count .CONVERT: ; read the digits from right to left (in pairs) mov al,[esi] stdcall HexDigitToValue cmp eax,0xffff je .ERROR ; put the first digit into EDI mov [edi],al dec cx cmp cx,0 je .DONE dec esi mov al,[esi] stdcall HexDigitToValue cmp eax,0xffff je .ERROR shl al,4 ; OR the next digit into EDI or [edi],al dec cx cmp cx,0 je .DONE dec esi inc edi jmp .CONVERT mov al,0 jmp .DONE .ERROR: mov [nErrorCode],byte 2 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc DecImmediateToHex uses esi edi ; convert the string in bfImmediate to a hex value ; the value in the string is read as a decimal number ; it cannot be greater than 3200 (4 decimal digits) ; count the number of digits in bfImmediate lea esi,[bfImmediate] mov cx,0 xor eax,eax .COUNT: mov dl,[esi] cmp dl,0 je .NEXT ; check for non decimal digits cmp dl,48 jl .ERROR cmp dl,57 jg .ERROR ; copy to EAX shl eax,8 mov al,dl inc esi inc cx ; check the count cmp cx,4 jg .ERROR jmp .COUNT .NEXT: lea edi,[Immediate] mov cx,0 ; zero the dest value .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,16 jl .CLEAR ; decimal digits are in EAX - convert to a hex number stdcall DecToHex cmp eax,3200 jg .ERROR ; save the result to Immediate lea edi,[Immediate] mov [edi],eax ; don't want to accidently return AL = 0xff xor eax,eax jmp .DONE .ERROR: mov al,0xff mov [nErrorCode],byte 6 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc DecToHex ; convert the decimal digit chars in EAX to a hex value ; result returned in EAX ; swap the bytes bswap eax ; find the most significant digit cmp al,0 jne .NEXT shr eax,8 cmp al,0 jne .NEXT shr eax,8 cmp al,0 jne .NEXT shr eax,8 ; if AL is zero here, there were no digits in EAX cmp al,0 je .DONE .NEXT: ; the most significant digit is now in AL xor edx,edx .CONVERT: ; if AL is zero, there are no more digits cmp al,0 je .HEX ; multiply the existing value in EDX by 10 mov ebx,edx shl edx,3 shl ebx,1 add edx,ebx ; put the next digit into EBX xor ebx,ebx mov bl,al sub bl,48 add edx,ebx shr eax,8 jmp .CONVERT .HEX: mov eax,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ImmediateToCalcsBuffer uses esi ; copy the immediate hex value to the Calculations buffer ; EDI is set outside of this procedure lea esi,[Immediate] mov cx,0 .COPY: mov dl,[esi] mov [edi],dl inc esi inc edi inc cx cmp cx,16 jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc GetNextArgument uses edi ; get the next argument from the string in ESI ; ESI set outside of this procedure ; code for argument returned in AL: ; Param_1 = 1 ; Param_2 = 2 ; Param_3 = 3 ; Param_4 = 4 ; Param_5 = 5 ; Param_6 = 6 ; Param_7 = 7 ; Param_8 = 8 ; Param_9 = 9 ; Param_10 = 10 ; Reg_1 = 11 ; Reg_2 = 12 ; Reg_3 = 13 ; Reg_4 = 14 lea edi,[CurrentArg] mov cx,0 .GET_ARG: ; read into EDI until a space or null is found mov al,[esi] cmp al,0 je .GET_CODE cmp al,32 je .GET_CODE mov [edi],al inc cx cmp cx,8 jge .GET_CODE inc esi inc edi jmp .GET_ARG .GET_CODE: ; null terminate EDI mov [edi],byte 0 cmp cx,0 jl .ERROR ; test the string in EDI to get the argument code stdcall Test_Argument jmp .DONE .ERROR: ; error code mov al,0xff mov [nErrorCode],byte 3 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Test_Argument uses esi edi ; test the argument against the symbols ; symbol 1 lea esi,[CurrentArg] lea edi,[Symbol_1] stdcall MatchSymbol mov dl,1 cmp al,0 je .SET_CODE ; symbol 2 lea esi,[CurrentArg] lea edi,[Symbol_2] stdcall MatchSymbol mov dl,2 cmp al,0 je .SET_CODE ; symbol 3 lea esi,[CurrentArg] lea edi,[Symbol_3] stdcall MatchSymbol mov dl,3 cmp al,0 je .SET_CODE ; symbol 4 lea esi,[CurrentArg] lea edi,[Symbol_4] stdcall MatchSymbol mov dl,4 cmp al,0 je .SET_CODE ; symbol 5 lea esi,[CurrentArg] lea edi,[Symbol_5] stdcall MatchSymbol mov dl,5 cmp al,0 je .SET_CODE ; symbol 6 lea esi,[CurrentArg] lea edi,[Symbol_6] stdcall MatchSymbol mov dl,6 cmp al,0 je .SET_CODE ; symbol 7 lea esi,[CurrentArg] lea edi,[Symbol_7] stdcall MatchSymbol mov dl,7 cmp al,0 je .SET_CODE ; symbol 8 lea esi,[CurrentArg] lea edi,[Symbol_8] stdcall MatchSymbol mov dl,8 cmp al,0 je .SET_CODE ; symbol 9 lea esi,[CurrentArg] lea edi,[Symbol_9] stdcall MatchSymbol mov dl,9 cmp al,0 je .SET_CODE ; symbol 10 lea esi,[CurrentArg] lea edi,[Symbol_10] stdcall MatchSymbol mov dl,10 cmp al,0 je .SET_CODE ; symbol 11 lea esi,[CurrentArg] lea edi,[Symbol_11] stdcall MatchSymbol mov dl,11 cmp al,0 je .SET_CODE ; symbol 12 lea esi,[CurrentArg] lea edi,[Symbol_12] stdcall MatchSymbol mov dl,12 cmp al,0 je .SET_CODE ; symbol 13 lea esi,[CurrentArg] lea edi,[Symbol_13] stdcall MatchSymbol mov dl,13 cmp al,0 je .SET_CODE ; symbol 14 lea esi,[CurrentArg] lea edi,[Symbol_14] stdcall MatchSymbol mov dl,14 cmp al,0 je .SET_CODE ; match not found mov dl,0xff mov [nErrorCode],byte 4 .SET_CODE: ; return the code in AL mov al,dl .DONE: ret endp ; ------------------------------------------------------------------------------------- proc MatchSymbol ; test the name of the argument in ESI against the symbol name in EDI ; ESI and EDI are set outside of this procedure ; result returned in AL ; AL = 0 is a match mov al,0xff mov cx,0 .TEST: mov dl,[esi] cmp [edi],dl jne .DONE cmp [edi],byte 0 je .MATCH inc esi inc edi inc cx cmp cx,8 jl .TEST .MATCH: mov al,0 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc GetInstrCode uses edi ; get the instr code from the string in ESI ; ESI set outside of this procedure ; instr code returned in AL lea edi,[CurrentInstr] mov cx,0 .GET_INSTR: ; read the chars into EDI, stop when a space or null is found mov al,[esi] cmp al,0 je .GET_CODE mov [edi],al inc cx cmp al,32 je .GET_CODE cmp cx,8 jge .GET_CODE inc esi inc edi jmp .GET_INSTR .GET_CODE: cmp cx,3 jl .ERROR ; convert letters in EDI to upper case stdcall InstrToUpper ; test the string in EDI to get an instr code stdcall Test_Instr cmp al,0xff jne .DONE .ERROR: mov [nErrorCode],byte 5 mov al,0xff .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Test_Instr uses edi ; find the instr code for the instr in CurrentInstr ; return in AL ; not found code is FF mov al,0xff lea edi,[CurrentInstr] ; ========================================================================= ; 2 ARGS: ; MOV = 1 ADD = 2 SUB = 3 MUL = 4 DIV = 5 ; MOD = 6 AND = 7 OR = 8 XOR = 9 XCHG = 10 ; ========================================================================= ; MOV = 1 cmp [edi],byte 'M' jne .ADD cmp [edi+1],byte 'O' jne .ADD cmp [edi+2],byte 'V' jne .ADD cmp [edi+3],byte 32 jne .ADD mov al,1 jmp .DONE .ADD: ; ADD = 2 cmp [edi],byte 'A' jne .SUB cmp [edi+1],byte 'D' jne .SUB cmp [edi+2],byte 'D' jne .SUB cmp [edi+3],byte 32 jne .SUB mov al,2 jmp .DONE .SUB: ; SUB = 3 cmp [edi],byte 'S' jne .MUL cmp [edi+1],byte 'U' jne .MUL cmp [edi+2],byte 'B' jne .MUL cmp [edi+3],byte 32 jne .MUL mov al,3 jmp .DONE .MUL: ; MUL = 4 cmp [edi],byte 'M' jne .DIV cmp [edi+1],byte 'U' jne .DIV cmp [edi+2],byte 'L' jne .DIV cmp [edi+3],byte 32 jne .DIV mov al,4 jmp .DONE .DIV: ; DIV = 5 cmp [edi],byte 'D' jne .MOD cmp [edi+1],byte 'I' jne .MOD cmp [edi+2],byte 'V' jne .MOD cmp [edi+3],byte 32 jne .MOD mov al,5 jmp .DONE .MOD: ; MOD = 6 cmp [edi],byte 'M' jne .AND cmp [edi+1],byte 'O' jne .AND cmp [edi+2],byte 'D' jne .AND cmp [edi+3],byte 32 jne .AND mov al,6 jmp .DONE .AND: ; AND = 7 cmp [edi],byte 'A' jne .OR cmp [edi+1],byte 'N' jne .OR cmp [edi+2],byte 'D' jne .OR cmp [edi+3],byte 32 jne .OR mov al,7 jmp .DONE .OR: ; OR = 8 cmp [edi],byte 'O' jne .XOR cmp [edi+1],byte 'R' jne .XOR cmp [edi+2],byte 32 jne .XOR mov al,8 jmp .DONE .XOR: ; XOR = 9 cmp [edi],byte 'X' jne .XCHG cmp [edi+1],byte 'O' jne .XCHG cmp [edi+2],byte 'R' jne .XCHG cmp [edi+3],byte 32 jne .XCHG mov al,9 jmp .DONE .XCHG: ; XCHG = 10 cmp [edi],byte 'X' jne .MOVI cmp [edi+1],byte 'C' jne .MOVI cmp [edi+2],byte 'H' jne .MOVI cmp [edi+3],byte 'G' jne .MOVI cmp [edi+4],byte 32 jne .MOVI mov al,10 jmp .DONE ; ========================================================================= ; 1 ARG + 1 IMMED: ; MOVI = 32 ADDI = 33 SUBI = 34 ANDI = 35 ORI = 36 XORI = 37 ; SHL = 38 SHR = 39 ROTL = 40 ROTR = 41 ; TRUNC = 42 BSET = 43 ; MULI = 44 DIVI = 45 MODI = 46 ; ========================================================================= .MOVI: ; MOVI = 32 cmp [edi],byte 'M' jne .ADDI cmp [edi+1],byte 'O' jne .ADDI cmp [edi+2],byte 'V' jne .ADDI cmp [edi+3],byte 'I' jne .ADDI cmp [edi+4],byte 32 jne .ADDI mov al,32 jmp .DONE .ADDI: ; ADDI = 33 cmp [edi],byte 'A' jne .SUBI cmp [edi+1],byte 'D' jne .SUBI cmp [edi+2],byte 'D' jne .SUBI cmp [edi+3],byte 'I' jne .SUBI cmp [edi+4],byte 32 jne .SUBI mov al,33 jmp .DONE .SUBI: ; SUBI = 34 cmp [edi],byte 'S' jne .ANDI cmp [edi+1],byte 'U' jne .ANDI cmp [edi+2],byte 'B' jne .ANDI cmp [edi+3],byte 'I' jne .ANDI cmp [edi+4],byte 32 jne .ANDI mov al,34 jmp .DONE .ANDI: ; ANDI = 35 cmp [edi],byte 'A' jne .ORI cmp [edi+1],byte 'N' jne .ORI cmp [edi+2],byte 'D' jne .ORI cmp [edi+3],byte 'I' jne .ORI cmp [edi+4],byte 32 jne .ORI mov al,35 jmp .DONE .ORI: ; ORI = 36 cmp [edi],byte 'O' jne .XORI cmp [edi+1],byte 'R' jne .XORI cmp [edi+2],byte 'I' jne .XORI cmp [edi+3],byte 32 jne .XORI mov al,36 jmp .DONE .XORI: ; XORI = 37 cmp [edi],byte 'X' jne .SHL cmp [edi+1],byte 'O' jne .SHL cmp [edi+2],byte 'R' jne .SHL cmp [edi+3],byte 'I' jne .SHL cmp [edi+4],byte 32 jne .SHL mov al,37 jmp .DONE .SHL: ; SHL = 38 cmp [edi],byte 'S' jne .SHR cmp [edi+1],byte 'H' jne .SHR cmp [edi+2],byte 'L' jne .SHR cmp [edi+3],byte 32 jne .SHR mov al,38 jmp .DONE .SHR: ; SHR = 39 cmp [edi],byte 'S' jne .ROTL cmp [edi+1],byte 'H' jne .ROTL cmp [edi+2],byte 'R' jne .ROTL cmp [edi+3],byte 32 jne .ROTL mov al,39 jmp .DONE .ROTL: ; ROTL = 40 cmp [edi],byte 'R' jne .ROTR cmp [edi+1],byte 'O' jne .ROTR cmp [edi+2],byte 'T' jne .ROTR cmp [edi+3],byte 'L' jne .ROTR cmp [edi+4],byte 32 jne .ROTR mov al,40 jmp .DONE .ROTR: ; ROTR = 41 cmp [edi],byte 'R' jne .TRUNC cmp [edi+1],byte 'O' jne .TRUNC cmp [edi+2],byte 'T' jne .TRUNC cmp [edi+3],byte 'R' jne .TRUNC cmp [edi+4],byte 32 jne .TRUNC mov al,41 jmp .DONE .TRUNC: ; TRUNC = 42 cmp [edi],byte 'T' jne .BSET cmp [edi+1],byte 'R' jne .BSET cmp [edi+2],byte 'U' jne .BSET cmp [edi+3],byte 'N' jne .BSET cmp [edi+4],byte 'C' jne .BSET cmp [edi+5],byte 32 jne .BSET mov al,42 jmp .DONE .BSET: ; BSET = 43 cmp [edi],byte 'B' jne .MULI cmp [edi+1],byte 'S' jne .MULI cmp [edi+2],byte 'E' jne .MULI cmp [edi+3],byte 'T' jne .MULI cmp [edi+4],byte 32 jne .MULI mov al,43 jmp .DONE .MULI: ; MULI = 44 cmp [edi],byte 'M' jne .DIVI cmp [edi+1],byte 'U' jne .DIVI cmp [edi+2],byte 'L' jne .DIVI cmp [edi+3],byte 'I' jne .DIVI cmp [edi+4],byte 32 jne .DIVI mov al,44 jmp .DONE .DIVI: ; DIVI = 45 cmp [edi],byte 'D' jne .MODI cmp [edi+1],byte 'I' jne .MODI cmp [edi+2],byte 'V' jne .MODI cmp [edi+3],byte 'I' jne .MODI cmp [edi+4],byte 32 jne .MODI mov al,45 jmp .DONE .MODI: ; MODI = 46 cmp [edi],byte 'M' jne .NOT cmp [edi+1],byte 'O' jne .NOT cmp [edi+2],byte 'D' jne .NOT cmp [edi+3],byte 'I' jne .NOT cmp [edi+4],byte 32 jne .NOT mov al,46 jmp .DONE ; ========================================================================= ; 1 ARG: ; NOT = 64 NEG = 65 CLR = 67 ; ========================================================================= .NOT: ; NOT = 64 cmp [edi],byte 'N' jne .NEG cmp [edi+1],byte 'O' jne .NEG cmp [edi+2],byte 'T' jne .NEG cmp [edi+3],byte 32 jne .NEG mov al,64 jmp .DONE .NEG: ; NEG = 65 cmp [edi],byte 'N' jne .CLR cmp [edi+1],byte 'E' jne .CLR cmp [edi+2],byte 'G' jne .CLR cmp [edi+3],byte 32 jne .CLR mov al,65 jmp .DONE .CLR: ; CLR = 66 cmp [edi],byte 'C' jne .NBYTES cmp [edi+1],byte 'L' jne .NBYTES cmp [edi+2],byte 'R' jne .NBYTES cmp [edi+3],byte 32 jne .NBYTES mov al,66 ; ========================================================================= ; 1 IMMED: ; NBYTES = 96 REPEAT = 97 ; ========================================================================= .NBYTES: ; NBYTES = 96 cmp [edi],byte 'N' jne .REPEAT cmp [edi+1],byte 'B' jne .REPEAT cmp [edi+2],byte 'Y' jne .REPEAT cmp [edi+3],byte 'T' jne .REPEAT cmp [edi+4],byte 'E' jne .REPEAT cmp [edi+5],byte 'S' jne .REPEAT cmp [edi+6],byte 32 jne .REPEAT mov al,96 .REPEAT: ; REPEAT = 97 cmp [edi],byte 'R' jne .DONE cmp [edi+1],byte 'E' jne .DONE cmp [edi+2],byte 'P' jne .DONE cmp [edi+3],byte 'E' jne .DONE cmp [edi+4],byte 'A' jne .DONE cmp [edi+5],byte 'T' jne .DONE cmp [edi+6],byte 32 jne .DONE mov al,97 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc InstrToUpper uses edi ; convert all letters in the instr to upper case lea edi,[CurrentInstr] mov cx,0 .CONVERT: mov al,[edi] cmp al,97 jl .NEXT cmp al,122 jg .NEXT sub al,32 mov [edi],al .NEXT: inc edi inc cx cmp cx,8 jl .CONVERT ret endp ; ------------------------------------------------------------------------------------- proc SkipSpaces ; ESI is set outside of this procedure ; advance ESI to the first non space char mov al,0 .NEXT: mov dl,[esi] cmp dl,32 jne .TEST inc esi jmp .NEXT .TEST: cmp dl,byte 0 jne .DONE ; a non space char was not found mov al,0xff mov [nErrorCode],byte 3 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ValidateParamsBuffer uses esi ; make sure that bfDisplay - when used as the input buffer ; for the parameters - only contains valid chars ; 0-9 = ascii 48-57 ; A-Z = ascii 65-90 ; a-z = ascii 97-122 ; space = ascii 32 ; underscore = ascii 95 ; crlf = 13,10 ; {} = 123,125 ; return the result in AL mov al,0 lea esi,[bfDisplay] .SCAN: mov dl,[esi] cmp dl,0 je .DONE cmp dl,123 je .NEXT cmp dl,125 je .NEXT stdcall ValidateChar cmp al,0xff je .DONE .NEXT: inc esi jmp .SCAN .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ValidateCalcsBuffer uses esi ; make sure that bfDisplay - when used as the input buffer ; for the calculations - only contains valid chars ; 0-9 = ascii 48-57 ; A-Z = ascii 65-90 ; a-z = ascii 97-122 ; space = ascii 32 ; underscore = ascii 95 ; crlf = 13,10 ; return the result in AL mov al,0 lea esi,[bfDisplay] .SCAN: mov dl,[esi] cmp dl,0 je .DONE stdcall ValidateChar cmp al,0xff je .DONE inc esi jmp .SCAN .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ValidateChar ; test the char passed in DL ; set AL to 0xff if invalid ; 0-9 = ascii 48-57 ; A-Z = ascii 65-90 ; a-z = ascii 97-122 ; space = ascii 32 ; underscore = ascii 95 cmp dl,32 je .DONE cmp dl,10 je .DONE cmp dl,13 je .DONE cmp dl,95 je .DONE cmp dl,48 jl .INVALID cmp dl,122 jg .INVALID cmp dl,58 jl .DONE cmp dl,65 jl .INVALID cmp dl,91 jl .DONE cmp dl,96 jg .DONE .INVALID: mov al,0xff mov [nErrorCode],byte 7 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ResetCalculations uses edi ecx ; clear the Calculations buffer mov [nCalculation],byte 0 lea edi,[Calculations] mov cx,0 .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,CALCS_BUFFER_SZ jl .CLEAR ret endp ; ------------------------------------------------------------------------------------- proc PrintErrorMessage uses esi edi ; print the error message to bfDisplay lea edi,[bfDisplay] mov dl,[nErrorCode] .ERR_1: cmp dl,1 jne .ERR_2 lea esi,[szError_1] jmp .PRINT .ERR_2: cmp dl,2 jne .ERR_3 lea esi,[szError_2] jmp .PRINT .ERR_3: cmp dl,3 jne .ERR_4 lea esi,[szError_3] jmp .PRINT .ERR_4: cmp dl,4 jne .ERR_5 lea esi,[szError_4] jmp .PRINT .ERR_5: cmp dl,5 jne .ERR_6 lea esi,[szError_5] jmp .PRINT .ERR_6: cmp dl,6 jne .ERR_7 lea esi,[szError_6] jmp .PRINT .ERR_7: cmp dl,7 jne .ERR_8 lea esi,[szError_7] jmp .PRINT .ERR_8: cmp dl,8 jne .ERR_0 lea esi,[szError_8] jmp .PRINT .ERR_0: lea esi,[szError_0] .PRINT: mov dl,[esi] cmp dl,0 je .MORE mov [edi],dl inc esi inc edi jmp .PRINT .MORE: mov dl,[nErrorCode] cmp dl,1 jl .DONE cmp dl,6 jg .DONE ; CRLF x 2 PRINT_CRLF PRINT_CRLF lea esi,[CurrentLine] .PRINTL: mov dl,[esi] cmp dl,0 je .DONE mov [edi],dl inc esi inc edi jmp .PRINTL .DONE: mov [edi],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc PrintExeError uses esi edi ; print the execution error message to the bfDisplay string locals count db 0 endl lea edi,[bfDisplay] ; execution error codes: ; 0 = unknown error ; 1 = unknown instruction or argument code ; 2 = divide by zero error cmp [nExeErrorCode],byte 1 je .EXE_ERR_1 cmp [nExeErrorCode],byte 2 je .EXE_ERR_2 .EXE_ERR_0: lea esi,[szExeError_0] .SZ_0: mov al,[esi] cmp al,0 je .BF_CALCS mov [edi],al inc esi inc edi jmp .SZ_0 .EXE_ERR_1: lea esi,[szExeError_1] .SZ_1: mov al,[esi] cmp al,0 je .BF_CALCS mov [edi],al inc esi inc edi jmp .SZ_1 .EXE_ERR_2: lea esi,[szExeError_2] .SZ_2: mov al,[esi] cmp al,0 je .DONE mov [edi],al inc esi inc edi jmp .SZ_2 .BF_CALCS: ; print the calculations buffer lea esi,[Calculations] PRINT_CRLF PRINT_CRLF mov cx,CALCS_BUFFER_SZ .PRINT: ; byte to digits mov al,[esi] stdcall ByteToDigits ; add the 2 digits to the string mov [edi],ah inc edi mov [edi],al inc edi inc [count] cmp [count],20 je .CRLF jmp .NEXT .CRLF: mov [count],byte 0 PRINT_CRLF .NEXT: inc esi dec cx cmp cx,0 jg .PRINT .DONE: ; NULL terminate mov [edi],byte 0 ; reset the error flag mov [nExeErrorCode],byte 0 ret endp ; ------------------------------------------------------------------------------------- proc ExecuteCalculations uses esi edi ; execute the instructions in the Calculations buffer lea esi,[Calculations] mov cx,0 .INSTR: mov [nExeErrorCode],byte 0 ; get the next instruction xor eax,eax ; put instr code, arg 1 index, arg 2 index into EAX mov eax,[esi] ; instr code is in AL ; instr code = 0, no more instructions cmp al,0 je .DONE cmp al,32 jge .32_63 .1_31: cmp al,1 je .1 cmp al,2 je .2 cmp al,3 je .3 cmp al,4 je .4 cmp al,5 je .5 cmp al,6 je .6 cmp al,7 je .7 cmp al,8 je .8 cmp al,9 je .9 cmp al,10 je .10 jmp .UI_ERROR .1: ; MOV stdcall Mov_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .2: ; ADD stdcall Add_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .3: ; SUB stdcall Sub_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .4: ; MUL stdcall Mul_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .5: ; DIV stdcall Div_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .6: ; MOD stdcall Mod_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .7: ; AND stdcall And_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .8: ; OR stdcall Or_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .9: ; XOR stdcall Xor_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .10: ; XCHG stdcall Xchg_Dest_Src cmp dl,0xff je .UI_ERROR jmp .NEXT .32_63: cmp al,64 jge .64_95 ; get the immediate value lea edi,[Immediate] mov edx,[esi+4] mov [edi],edx mov edx,[esi+8] mov [edi+4],edx mov edx,[esi+12] mov [edi+8],edx mov edx,[esi+16] mov [edi+12],edx cmp al,32 je .32 cmp al,33 je .33 cmp al,34 je .34 cmp al,35 je .35 cmp al,36 je .36 cmp al,37 je .37 cmp al,38 je .38 cmp al,39 je .39 cmp al,40 je .40 cmp al,41 je .41 cmp al,42 je .42 cmp al,43 je .43 cmp al,44 je .44 cmp al,45 je .45 cmp al,46 je .46 jmp .UI_ERROR .32: ; MOVI stdcall Mov_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .33: ; ADDI stdcall Add_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .34: ; SUBI stdcall Sub_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .35: ; ANDI stdcall And_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .36: ; ORI stdcall Or_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .37: ; XORI stdcall Xor_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .38: ; SHL stdcall Shl_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .39: ; SHR stdcall Shr_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .40: ; ROTL stdcall Rotl_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .41: ; ROTR stdcall Rotr_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .42: ; TRUNC stdcall Trunc_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .43: ; BSET stdcall Bset_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .44: ; MULI stdcall Mul_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .45: ; DIVI stdcall Div_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .46: ; MODI stdcall Mod_Dest_Immed cmp dl,0xff je .UI_ERROR jmp .NEXT .64_95: cmp al,96 jge .96_127 cmp al,64 je .64 cmp al,65 je .65 cmp al,66 je .66 jmp .UI_ERROR .64: ; NOT stdcall Not_Dest cmp dl,0xff je .UI_ERROR jmp .NEXT .65: ; NEG stdcall Neg_Dest cmp dl,0xff je .UI_ERROR jmp .NEXT .66: ; CLR stdcall Clr_Dest cmp dl,0xff je .UI_ERROR jmp .NEXT .96_127: ; get the immediate value mov edx,[esi+4] cmp al,96 je .96 cmp al,97 je .97 jmp .UI_ERROR .96: ; NBYTES mov [nBytes],dx jmp .NEXT .97: ; REPEAT ; immediate value is in EDX ; put iteration number into EAX mov eax,[esi+8] cmp eax,edx jae .END_REPEAT inc eax mov [esi+8],eax lea esi,[Calculations] mov cx,0 jmp .INSTR .END_REPEAT: mov [esi+8],dword 0 .NEXT: cmp [nExeErrorCode],byte 0 jne .EXE_ERROR add esi,20 inc cx cmp cx,N_CALCS jl .INSTR jmp .DONE .UI_ERROR: ; error - unknown instruction or argument code mov [nExeErrorCode],byte 1 .EXE_ERROR: mov eax,0xffffffff .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mov_Dest_Src uses esi edi ecx ; copy the src value to the dest ; MOV dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; copy the src value to dest mov cx,0 .COPY: mov dl,[esi] mov [edi],dl inc esi inc edi inc cx cmp cx,HEX_LEN jl .COPY .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Add_Dest_Src uses esi edi ecx ; ADD the src value to the dest ; ADD dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; add the src value to dest mov cx,0 ; save CF from iteration to iteration ; init CF = 0 clc ; push flags register to the stack pushf .ADD: mov dl,[esi] ; get the flags register from the stack popf adc [edi],dl ; save the flags register to the stack, for the next iteration pushf inc esi inc edi inc cx cmp cx,HEX_LEN jl .ADD ; clean up the stack popf .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Sub_Dest_Src uses esi edi ecx ; SUB the src value from the dest ; SUB dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; sub the src value from dest mov cx,0 ; save CF from iteration to iteration ; init CF = 0 clc ; push flags register to the stack pushf .SUB: mov dl,[esi] ; get the flags register from the stack popf sbb [edi],dl ; save the flags register to the stack, for the next iteration pushf inc esi inc edi inc cx cmp cx,HEX_LEN jl .SUB ; clean up the stack popf .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mul_Dest_Src uses esi edi ecx ; Multiply the dest value by the src value ; MUL dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; also need an intermediate value - the temp buffer ; clear the intermediate value stdcall ClearTempBuffer xor ecx,ecx xor edx,edx .MULTIPLY: ; get a byte from ESI and multiply the hex value in EDI ; add the result to the temp buffer mov al,[esi] mov ebx,edi ; the byte is passed in AL, the offset in DL, EDI in EBX stdcall ByteMultiply inc esi inc dx inc cx cmp cx,HEX_LEN jl .MULTIPLY ; copy the result from the temp buffer back to EDI stdcall CopyFromTempBuffer .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ByteMultiply uses ecx edi ; multiply EDI by a byte value and add to TempBf ; EDI is passed in EBX ; byte is passed in AL ; offset is passed in DX locals mbyte db 0 endl ; store the byte mov [mbyte],al ; get EDI mov edi,ebx mov cx,0 xor ebx,ebx ; put the offset into BX (passed to Add_Word) mov bx,dx .B_MULT: ; mul byte [EDI] = [EDI] * AL = |AH|AL| = AX mul byte [edi] ; add AX to the temp buffer stdcall Add_Word ; retore AL for the next iteration mov al,[mbyte] inc edi inc bx cmp bx,HEX_LEN jge .DONE inc cx cmp cx,HEX_LEN jl .B_MULT .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Add_Word uses edi ecx ; add the word in AX to the temp buffer ; at the offset given by BX cmp bx,HEX_LEN jge .DONE lea edi,[TempBf] xor ecx,ecx mov cx,bx cmp bx,HEX_LEN-1 jl .ADD_WD add [edi+ecx],al jmp .DONE .ADD_WD: add [edi+ecx],al inc cx adc [edi+ecx],ah jnc .DONE inc cx .ADD_C: cmp cx,HEX_LEN jge .DONE add byte [edi+ecx],1 jnc .DONE inc cx jmp .ADD_C .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Div_Dest_Src uses esi edi ecx ; Divide the dest value by the src ; DIV dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; reset the quotient and divisor stdcall Zero_Quot_Div ; copy the source (ESI) to the divisor stdcall CopyToDivisor ; do the division stdcall Divide ; copy the quotient to the destination stdcall QuotientToDest .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mod_Dest_Src uses esi edi ecx ; compute dest mod src ; MOD dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; reset the quotient and divisor stdcall Zero_Quot_Div ; copy the source (ESI) to the divisor stdcall CopyToDivisor ; do the division stdcall Divide ; remainder is in EDI (the destination) .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc And_Dest_Src uses esi edi ecx ; AND the src value into the dest ; AND dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; AND the src value into dest mov cx,0 .AND: mov dl,[esi] and [edi],dl inc esi inc edi inc cx cmp cx,HEX_LEN jl .AND .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Or_Dest_Src uses esi edi ecx ; OR the src value into the dest ; OR dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; OR the src value into dest mov cx,0 .OR: mov dl,[esi] or [edi],dl inc esi inc edi inc cx cmp cx,HEX_LEN jl .OR .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Xor_Dest_Src uses esi edi ecx ; XOR the src value into the dest ; XOR dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; XOR the src value into dest mov cx,0 .XOR: mov dl,[esi] xor [edi],dl inc esi inc edi inc cx cmp cx,HEX_LEN jl .XOR .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Xchg_Dest_Src uses esi edi ecx ; exchange the src value and the dest value ; XCHG dest src shr eax,8 ; dest index is in AL and src index is in AH ; get ESI for the src arg mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE mov esi,edi ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; copy ESI to the TempBf stdcall CopyToTempBuffer ; copy EDI to ESI xor ecx,ecx .COPY: mov dl,[edi+ecx] mov [esi+ecx],dl inc cx cmp cx,HEX_LEN jl .COPY ; copy from TempBf to EDI stdcall CopyFromTempBuffer .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mov_Dest_Immed uses esi edi ecx ; move the immediate value into the destination: ; MOVI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the destination stdcall ClearParam ; get EDI again mov dl,ah stdcall GetDestPtr ; copy in the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] mov [edi],edx mov edx,[esi+4] mov [edi+4],edx mov edx,[esi+8] mov [edi+8],edx mov edx,[esi+12] mov [edi+12],edx .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Add_Dest_Immed uses esi edi ecx ; add the immediate value to the destination: ; ADDI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; add in the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] add [edi],edx mov edx,[esi+4] adc [edi+4],edx mov edx,[esi+8] adc [edi+8],edx mov edx,[esi+12] adc [edi+12],edx jnc .DL_0 add edi,16 mov cx,16 .ADD_C: add [edi],byte 1 jnc .DL_0 inc edi inc cx cmp cx,HEX_LEN jl .ADD_C .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Sub_Dest_Immed uses esi edi ecx ; subtract the immediate value from the destination: ; SUBI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; subtract the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] sub [edi],edx mov edx,[esi+4] sbb [edi+4],edx mov edx,[esi+8] sbb [edi+8],edx mov edx,[esi+12] sbb [edi+12],edx jnc .DL_0 add edi,16 mov cx,16 .SUB_B: sub [edi],byte 1 jnc .DL_0 inc edi inc cx cmp cx,HEX_LEN jl .SUB_B .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc And_Dest_Immed uses esi edi ecx ; AND the immediate value into the destination: ; ANDI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; AND in the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] and [edi],edx mov edx,[esi+4] and [edi+4],edx mov edx,[esi+8] and [edi+8],edx mov edx,[esi+12] and [edi+12],edx ; clear the rest of EDI xor ecx,ecx mov cx,16 .CLEAR: mov [edi+ecx],byte 0 inc cx cmp cx,HEX_LEN jl .CLEAR .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Or_Dest_Immed uses esi edi ecx ; OR the immediate value into the destination: ; ORI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; OR in the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] or [edi],edx mov edx,[esi+4] or [edi+4],edx mov edx,[esi+8] or [edi+8],edx mov edx,[esi+12] or [edi+12],edx .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Xor_Dest_Immed uses esi edi ecx ; XOR the immediate value into the destination: ; XORI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; XOR in the 16 byte immediate value lea esi,[Immediate] mov edx,[esi] xor [edi],edx mov edx,[esi+4] xor [edi+4],edx mov edx,[esi+8] xor [edi+8],edx mov edx,[esi+12] xor [edi+12],edx .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Shl_Dest_Immed uses esi edi ecx ; shift left dest by the number of bits given in immed ; SHL dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the shift buffer stdcall ClearShiftBuffer ; check that nBytes is in range stdcall Check_nBytes ; the number of bytes to apply the SHL to is now given by nBytes ; get the number of bits to shift by lea esi,[Immediate] mov edx,[esi] ; if the shift is greater or equal to nBytes, the result is zero xor eax,eax mov ax,[nBytes] shl ax,3 cmp dx,ax jge .DL_0 ; copy EDI to the shift buffer ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer ; do the shift stdcall ShiftLeft .DL_0: stdcall CopyFromShiftBuffer ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ShiftLeft uses esi edi ecx ; for the SHL operation ; SHL the shift buffer by shl_bytes + shl_bits ; size of shift in bits passed in EDX ; ShiftBf is used as the shift buffer locals ncopy dw 0 endl lea esi,[ShiftBf] lea edi,[ShiftBf] mov ebx,edx shr ebx,3 ; EBX now contains shl_bytes cmp bx,0 je .BITS ; ncopy = number of bytes to copy up = nBytes - shl_bytes mov ax,[nBytes] mov [ncopy],ax sub [ncopy],bx .BYTES: sub esi,ebx xor ecx,ecx mov cx,[nBytes] dec cx .COPY: ; copy up the bytes mov bl,[esi+ecx] mov [edi+ecx],bl dec cx dec word [ncopy] cmp [ncopy],word 0 jg .COPY cmp cx,0 jl .BITS .ZERO: ; zero the rest of the buffer mov [edi+ecx],byte 0 dec cx cmp cx,0 jge .ZERO .BITS: ; calculate the size of the remaining bit shift (less than 8 bits) ; calc EDX mod 8 and edx,7 cmp edx,0 ; if bits = 0, nothing more to do je .DONE stdcall SHL_Bits .DONE: ret endp ; ------------------------------------------------------------------------------------- proc SHL_Bits uses edi ecx eax ; shift size in bits is passed in DL lea edi,[ShiftBf] mov cl,dl ; CL contains the shift size, so use AX as the counter xor eax,eax mov ax,[nBytes] dec ax add edi,eax .SHL: mov dh,[edi-1] mov bl,[edi] shld bx,dx,cl mov [edi],bl dec edi dec ax cmp ax,0 jg .SHL .FIRST: shl byte [edi],cl ret endp ; ------------------------------------------------------------------------------------- proc Shr_Dest_Immed uses esi edi ecx ; shift right dest by the number of bits given in immed ; SHR dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the shift buffer stdcall ClearShiftBuffer ; check that nBytes is in range stdcall Check_nBytes ; the number of bytes to apply the SHR to is now given by nBytes ; get the number of bits to shift by lea esi,[Immediate] mov edx,[esi] ; if the shift is greater or equal to nBytes, the result is zero xor eax,eax mov ax,[nBytes] shl ax,3 cmp dx,ax jge .DL_0 ; copy EDI to the shift buffer ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer ; do the shift stdcall ShiftRight .DL_0: stdcall CopyFromShiftBuffer ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ShiftRight uses esi edi ecx ; for the SHR operation ; SHR the shift buffer by shr_bytes + shr_bits ; size of shift in bits passed in EDX ; ShiftBf is used as the shift buffer locals ncopy dw 0 endl lea esi,[ShiftBf] lea edi,[ShiftBf] mov ebx,edx shr ebx,3 ; EBX now contains shr_bytes cmp bx,0 je .BITS ; ncopy = number of bytes to copy down = nBytes - shr_bytes mov ax,[nBytes] mov [ncopy],ax sub [ncopy],bx .BYTES: xor ecx,ecx add esi,ebx .COPY: ; copy down the bytes mov bl,[esi+ecx] mov [edi+ecx],bl inc cx dec word [ncopy] cmp [ncopy],word 0 jg .COPY cmp cx,[nBytes] jge .BITS .ZERO: ; zero the rest of the buffer mov [edi+ecx],byte 0 inc cx cmp cx,[nBytes] jl .ZERO .BITS: ; calculate the size of the remaining bit shift (less than 8 bits) ; calc EDX mod 8 and edx,7 cmp edx,0 ; if bits = 0, nothing more to do je .DONE stdcall SHR_Bits .DONE: ret endp ; ------------------------------------------------------------------------------------- proc SHR_Bits uses edi ecx eax ; shift size in bits is passed in DL lea edi,[ShiftBf] mov cl,dl ; CL contains the shift size, so use AX as the counter xor eax,eax .SHR: mov dl,[edi+1] mov bh,[edi] shrd bx,dx,cl mov [edi],bh inc edi inc ax inc ax cmp ax,[nBytes] jge .LAST dec ax jmp .SHR .LAST: shr byte [edi],cl ret endp ; ------------------------------------------------------------------------------------- proc Rotl_Dest_Immed uses esi edi ecx ; rot left dest by the number of bits given in immed ; ROTL dest immed locals shift dw 0 endl ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the shift buffer stdcall ClearShiftBuffer ; check that nBytes is in range stdcall Check_nBytes ; the number of bytes to apply the ROTL to is now given by nBytes ; get the number of bits to shift by lea esi,[Immediate] mov edx,[esi] ; if the number of bits to shift by is equal to (8 x nBytes) then no rot needed xor eax,eax mov ax,[nBytes] shl eax,3 cmp eax,edx je .NO_ROT ; if the number of bits to shift by is greater than (8 x nBytes) ; calc EDX = EDX mod EAX .MOD: cmp edx,eax jl .B_COPY sub edx,eax cmp edx,eax ; if (EDX mod EAX) = 0 then no rot needed je .NO_ROT jmp .MOD .B_COPY: mov [shift],dx ; copy EDI to the shift buffer ; ROTL(x) = SHL(x) or SHR(nBytes-x) ; do the left shift ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer stdcall ShiftLeft lea esi,[ShiftBf] ; copy the result to the temp buffer stdcall CopyToTempBuffer ; ROTL(x) = SHL(x) or SHR(nBytes-x) ; do the right shift ; clear the shift buffer stdcall ClearShiftBuffer ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer ; put (nBytes - shift) into DX xor eax,eax xor edx,edx mov ax,[nBytes] shl ax,3 sub ax,[shift] mov dx,ax stdcall ShiftRight lea esi,[ShiftBf] stdcall OrIntoTempBuffer stdcall CopyFromTempBuffer jmp .DL_0 .NO_ROT: stdcall TruncToNbytes .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Rotr_Dest_Immed uses esi edi ecx ; rot right dest by the number of bits given in immed ; ROTR dest immed locals shift dw 0 endl ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the shift buffer stdcall ClearShiftBuffer ; check that nBytes is in range stdcall Check_nBytes ; the number of bytes to apply the ROTR to is now given by nBytes ; get the number of bits to shift by lea esi,[Immediate] mov edx,[esi] ; if the number of bits to shift by is equal to (8 x nBytes) then no rot needed xor eax,eax mov ax,[nBytes] shl eax,3 cmp eax,edx je .NO_ROT ; if the number of bits to shift by is greater than (8 x nBytes) ; calc EDX = EDX mod EAX .MOD: cmp edx,eax jl .B_COPY sub edx,eax cmp edx,eax ; if (EDX mod EAX) = 0 then no rot needed je .NO_ROT jmp .MOD .B_COPY: mov [shift],dx ; copy EDI to the shift buffer ; ROTL(x) = SHL(x) or SHR(nBytes-x) ; do the right shift ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer stdcall ShiftRight lea esi,[ShiftBf] ; copy the result to the temp buffer stdcall CopyToTempBuffer ; ROTL(x) = SHL(x) or SHR(nBytes-x) ; do the left shift ; clear the shift buffer stdcall ClearShiftBuffer ; proc takes ESI mov esi,edi stdcall CopyToShiftBuffer ; put (nBytes - shift) into DX xor eax,eax xor edx,edx mov ax,[nBytes] shl ax,3 sub ax,[shift] mov dx,ax stdcall ShiftLeft lea esi,[ShiftBf] stdcall OrIntoTempBuffer stdcall CopyFromTempBuffer jmp .DL_0 .NO_ROT: stdcall TruncToNbytes .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Check_nBytes uses eax ; check that nBytes is in range mov ax,[nBytes] cmp ax,0 jle .ADJUST cmp ax,HEX_LEN jg .ADJUST jmp .DONE .ADJUST: mov [nBytes],HEX_LEN .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Trunc_Dest_Immed uses esi edi ecx ; truncate dest to the number of bits specified in immed ; TRUNC dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; get immed lea esi,[Immediate] ; EAX gives the size of the trunc in bits mov eax,[esi] cmp eax,HEX_LEN * 8 jge .DL_0 ; convert EAX to a byte index shr eax,3 xor ecx,ecx mov cx,HEX_LEN-1 cmp cx,ax je .BITS .TRUNC: mov [edi+ecx],byte 0 dec cx cmp cx,ax jg .TRUNC .BITS: ; deal with the remaining bits (if any) mov edx,[esi] and edx,7 add edi,ecx mov cl,8 sub cl,dl mov al,0xff shr al,cl and [edi],al .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Bset_Dest_Immed uses esi edi ecx ; in dest - turn on the number of bits specified in immed ; starting from bit 0 ; BSET dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; get immed lea esi,[Immediate] mov eax,[esi] ; convert to bytes shr eax,3 cmp eax,0 je .BITS ; set the bytes mov cx,0 .BSET: mov [edi],byte 0xff inc edi inc cx cmp cx,ax je .BITS cmp cx,HEX_LEN jge .DL_0 jmp .BSET .BITS: mov eax,[esi] and eax,7 cmp eax,0 je .DL_0 mov cl,8 sub cl,al mov al,0xff shr al,cl or [edi],al .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mul_Dest_Immed uses esi edi ecx ; Multiply the dest by the immediate value: ; MULI dest immed ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; get the 16 byte immediate value lea esi,[Immediate] ; also need an intermediate value - the temp buffer ; clear the intermediate value stdcall ClearTempBuffer xor ecx,ecx xor edx,edx .MULTIPLY: ; get a byte from ESI and multiply the hex value in EDI ; add the result to the temp buffer mov al,[esi] mov ebx,edi ; the byte is passed in AL, the offset in DL, EDI in EBX stdcall ByteMultiply inc esi inc dx inc cx cmp cx,16 jl .MULTIPLY ; copy the result from the temp buffer back to EDI stdcall CopyFromTempBuffer .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Div_Dest_Immed uses esi edi ecx ; Divide the dest value by the immediate value ; DIVI dest immed shr eax,8 ; dest index is in AL ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; reset the quotient and divisor stdcall Zero_Quot_Div ; copy the immediate value to the divisor stdcall ImmediateToDivisor ; do the division stdcall Divide ; copy the quotient to the destination stdcall QuotientToDest .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Mod_Dest_Immed uses esi edi ecx ; compute dest mod immed ; MODI dest immed shr eax,8 ; dest index is in AL ; get EDI for the dest arg mov dl,al stdcall GetDestPtr cmp dl,0xff je .DONE ; reset the quotient and divisor stdcall Zero_Quot_Div ; copy the immediate value to the divisor stdcall ImmediateToDivisor ; do the division stdcall Divide ; remainder is in EDI (the destination) .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Not_Dest uses edi ecx ; NOT the destination value ; NOT dest ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; NOT the destination mov cx,0 .NOT: not byte [edi] inc edi inc cx cmp cx,HEX_LEN jl .NOT .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Neg_Dest uses edi ecx ; NEG the destination value = NOT(dest) + 1 ; NEG dest ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; NOT the destination xor ecx,ecx .NOT: not byte [edi+ecx] inc cx cmp cx,HEX_LEN jl .NOT ; add 1 to the dest xor ecx,ecx .ADD_C: add [edi+ecx],byte 1 jnc .DL_0 inc cx cmp cx,HEX_LEN jl .ADD_C .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Clr_Dest uses edi ; CLR the destination value ; CLR dest ; get EDI for the arg (dest) ; arg index is in AH mov dl,ah stdcall GetDestPtr cmp dl,0xff je .DONE ; clear the destination stdcall ClearParam .DL_0: ; return DL = 0 (success) xor edx,edx .DONE: ret endp ; ------------------------------------------------------------------------------------- proc GetDestPtr ; the index of the arg is passed in DL ; set EDI to point to the arg ; error code returned in DL cmp dl,1 je .1 cmp dl,2 je .2 cmp dl,3 je .3 cmp dl,4 je .4 cmp dl,5 je .5 cmp dl,6 je .6 cmp dl,7 je .7 cmp dl,8 je .8 cmp dl,9 je .9 cmp dl,10 je .10 cmp dl,11 je .11 cmp dl,12 je .12 cmp dl,13 je .13 cmp dl,14 je .14 jmp .ERROR .1: lea edi,[Param_1] jmp .DONE .2: lea edi,[Param_2] jmp .DONE .3: lea edi,[Param_3] jmp .DONE .4: lea edi,[Param_4] jmp .DONE .5: lea edi,[Param_5] jmp .DONE .6: lea edi,[Param_6] jmp .DONE .7: lea edi,[Param_7] jmp .DONE .8: lea edi,[Param_8] jmp .DONE .9: lea edi,[Param_9] jmp .DONE .10: lea edi,[Param_10] jmp .DONE .11: lea edi,[Reg_1] jmp .DONE .12: lea edi,[Reg_2] jmp .DONE .13: lea edi,[Reg_3] jmp .DONE .14: lea edi,[Reg_4] jmp .DONE .ERROR: mov dl,0xff .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ClearShiftBuffer uses edi ecx ; clear ShiftBf lea edi,[ShiftBf] mov cx,0 .CLEAR: mov [edi],byte 0 inc edi inc cx cmp cx,HEX_LEN jl .CLEAR ret endp ; ------------------------------------------------------------------------------------- proc CopyToShiftBuffer uses edi ecx ; copy nBytes from ESI to the shift buffer ; called by the SHL, SHR, ROTL, ROTR functions ; ESI is not modified lea edi,[ShiftBf] xor ecx,ecx .COPY: mov al,[esi+ecx] mov [edi+ecx],al inc cx cmp cx,[nBytes] jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc CopyFromShiftBuffer uses esi ecx ; copy to EDI from the shift buffer ; EDI is not modified lea esi,[ShiftBf] xor ecx,ecx .COPY: mov al,[esi+ecx] mov [edi+ecx],al inc cx cmp cx,HEX_LEN jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc ClearTempBuffer uses edi ecx ; clear TempBf lea edi,[TempBf] xor ecx,ecx .CLEAR: mov [edi+ecx],byte 0 inc cx cmp cx,HEX_LEN jl .CLEAR ret endp ; ------------------------------------------------------------------------------------- proc CopyToTempBuffer uses edi ecx ; copy from ESI to the temp buffer ; ESI is not modified lea edi,[TempBf] xor ecx,ecx .COPY: mov al,[esi+ecx] mov [edi+ecx],al inc cx cmp cx,HEX_LEN jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc CopyFromTempBuffer uses esi ecx ; copy to EDI from the temp buffer ; EDI is not modified lea esi,[TempBf] xor ecx,ecx .COPY: mov al,[esi+ecx] mov [edi+ecx],al inc cx cmp cx,HEX_LEN jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc OrIntoTempBuffer uses edi ecx ; OR ESI into the temp buffer ; ESI is not modified lea edi,[TempBf] xor ecx,ecx .OR: mov al,[esi+ecx] or [edi+ecx],al inc cx cmp cx,HEX_LEN jl .OR ret endp ; ------------------------------------------------------------------------------------- proc TruncToNbytes uses ecx ; truncate EDI to nBytes xor ecx,ecx mov cx,[nBytes] .TRUNC: cmp cx,HEX_LEN jge .DONE mov [edi+ecx],byte 0 inc cx jmp .TRUNC .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Divide uses ecx ; divide EDI by the divisor ; the result is stored in Quotient ; remainder in EDI ; EDI is set outside of this procedure locals offset dw 0 qdigit db 0 endl stdcall IsDivisorZero cmp al,1 ; divide by zero error je .DIV_BY_ZERO ; is the dividend zero (in EDI) stdcall IsZero cmp al,1 je .DONE stdcall Is_GTE_Divisor cmp al,0 ; do nothing je .DONE ; start the division by aligning the digits in the divisor with the dividend ; put EDI into EAX for the proc mov eax,edi stdcall AlignDivisor ; digit counts returned in EAX ; EAX = |ndigits(src)|ndigits(dest)| cmp ax,0 je .DONE mov [offset],ax shr eax,16 cmp ax,0 je .DONE ; offset = ndigits(src) - ndigits(dest) sub [offset],ax cmp [offset],0 jl .DONE .DIVIDE: mov [qdigit],byte 0 .LT_DIV: ; if EDI is less than the divisor, shift the divisor right by 1 digit stdcall Is_GTE_Divisor cmp al,1 je .SUBTRACT stdcall ShiftDigitsRight dec word [offset] cmp [offset],word 0 jl .DONE jmp .LT_DIV .SUBTRACT: stdcall SubtractDivisor inc byte [qdigit] stdcall Is_GTE_Divisor cmp al,1 je .SUBTRACT mov al,[qdigit] shl eax,16 mov ax,[offset] stdcall AddDigitToQuotient jmp .DIVIDE .DIV_BY_ZERO: mov [nExeErrorCode],byte 2 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc IsZero uses ecx ; is the hex value in EDI zero ? ; EDI set outside of this procedure ; result returned in AL xor eax,eax xor ecx,ecx .TEST: cmp [edi+ecx],byte 0 jne .DONE inc cx cmp cx,HEX_LEN jl .TEST ; number is zero, set AL = 1 mov al,1 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Is_GTE_Divisor uses esi ecx ; compare the hex number in EDI to the divisor ; is EDI greater than or equal to ESI ? ; EDI is set outside of this procedure ; result returned in AL lea esi,[Divisor] xor eax,eax xor ecx,ecx mov cx,HEX_LEN-1 .TEST: mov dl,[esi+ecx] cmp [edi+ecx],dl jb .DONE ja .GTE dec cx cmp cx,0 jge .TEST .GTE: mov al,1 .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Get_nDigits uses esi ecx ; get the number of hex digits in ESI ; ESI is passed in EAX ; result returned in AX mov esi,eax xor ecx,ecx mov cx,HEX_LEN-1 xor eax,eax ; AX will be in the range 0 - 800 (800 hex digits in a 400 byte number) mov ax,2*HEX_LEN .TEST: mov dl,[esi+ecx] test dl,0xf0 jnz .DONE dec ax test dl,0x0f jnz .DONE dec ax dec cx cmp cx,0 jge .TEST .DONE: ret endp ; ------------------------------------------------------------------------------------- proc Zero_Quot_Div uses edi ecx ; zero the quotient and divisor ; quotient xor ecx,ecx lea edi,[Quotient] .ZQ: mov [edi+ecx],byte 0 inc cx cmp cx,HEX_LEN jl .ZQ ; divisor xor ecx,ecx lea edi,[Divisor] .ZD: mov [edi+ecx],byte 0 inc cx cmp cx,HEX_LEN jl .ZD ret endp ; ------------------------------------------------------------------------------------- proc AddDigitToQuotient uses edi ecx ; add a digit to the Quotient ; offset and digit passed in EAX ; offset is in nibbles (hex digits) lea edi,[Quotient] xor ecx,ecx ; put the offset into cx mov cx,ax shr eax,16 ; the digit is now in AL test cx,1 jnz .UPPER .LOWER: ; SHR(1) to get the offset in bytes shr ecx,1 or [edi+ecx],al jmp .DONE .UPPER: ; SHR(1) to get the offset in bytes shr ecx,1 shl al,4 or [edi+ecx],al .DONE: ret endp ; ------------------------------------------------------------------------------------- proc IsDivisorZero uses edi ; is the divisor zero ? lea edi,[Divisor] stdcall IsZero ret endp ; ------------------------------------------------------------------------------------- proc CopyToDivisor uses edi ecx ; copy the value in ESI to the divisor ; ESI is set outside of this procedure ; ESI is not modified lea edi,[Divisor] xor ecx,ecx .COPY: mov dl,[esi+ecx] mov [edi+ecx],dl inc cx cmp cx,HEX_LEN jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc ImmediateToDivisor uses esi edi ecx ; copy the immediate value to the divisor lea esi,[Immediate] lea edi,[Divisor] xor ecx,ecx .COPY: mov dl,[esi+ecx] mov [edi+ecx],dl inc cx cmp cx,16 jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc QuotientToDest uses esi ecx ; copy the quotient to EDI ; EDI is set outside of this procedure lea esi,[Quotient] xor ecx,ecx .COPY: mov dl,[esi+ecx] mov [edi+ecx],dl inc cx cmp cx,HEX_LEN jl .COPY ret endp ; ------------------------------------------------------------------------------------- proc SubtractDivisor uses esi ecx ; subtract the divisor from EDI ; EDI is set outside of this procedure lea esi,[Divisor] xor ecx,ecx clc ; push flags register to the stack pushf .SUB: mov dl,[esi+ecx] ; get the flags register from the stack popf sbb [edi+ecx],dl ; save the flags register to the stack, for the next iteration pushf inc cx ; cmp affects the CF cmp cx,HEX_LEN jl .SUB ; clean up the stack popf ret endp ; ------------------------------------------------------------------------------------- proc AlignDivisor uses esi edi ecx ; align the divisor with the number in EDI ; EDI is passed in EAX locals nDigits_Src dw 0 nDigits_Dest dw 0 shift dw 0 ncopy dw 0 endl ; get EDI mov edi,eax xor eax,eax lea esi,[Divisor] mov eax,esi stdcall Get_nDigits mov [nDigits_Src],ax cmp ax,0 ; number in ESI is zero, do nothing je .DONE mov eax,edi stdcall Get_nDigits mov [nDigits_Dest],ax cmp ax,0 ; number in EDI is zero, do nothing je .DONE cmp ax,[nDigits_Src] ; the two numbers are already aligned or the divisor is greater than EDI ; do nothing jle .DONE ; compute the shift (the number of hex digits to shift the Divisor up by) sub ax,[nDigits_Src] mov [shift],ax ; copy up the hex digits lea edi,[Divisor] ; divide AX by 2 to get the number of bytes to shift up by shr ax,1 cmp ax,0 je .DIGIT ; put the shift into EDX and sub from ESI xor edx,edx mov dx,ax sub esi,edx xor ecx,ecx mov cx,HEX_LEN-1 ; number of bytes to copy up mov word [ncopy],HEX_LEN sub [ncopy],dx .BYTES: ; copy up the bytes mov dl,[esi+ecx] mov [edi+ecx],dl dec cx dec word [ncopy] cmp [ncopy],word 0 jg .BYTES cmp cx,0 jl .DIGIT .ZERO: ; zero the rest of the bytes mov [edi+ecx],byte 0 dec cx cmp cx,0 jge .ZERO .DIGIT: mov ax,[shift] test ax,1 jz .DONE ; shift up by 1 more digit (4 bits) stdcall ShiftDigitsLeft .DONE: ; return the digit counts in EAX mov ax,[nDigits_Src] shl eax,16 mov ax,[nDigits_Dest] ret endp ; ------------------------------------------------------------------------------------- proc ShiftDigitsLeft uses esi edi ecx ; shift the Divisor one digit to the left lea esi,[Divisor] lea edi,[Divisor] xor ecx,ecx mov cx,HEX_LEN-1 add esi,ecx add edi,ecx .SHL: mov dl,[esi] shl dl,4 mov [edi],dl cmp cx,0 jle .DONE dec esi mov dl,[esi] shr dl,4 or [edi],dl dec edi dec cx jmp .SHL .DONE: ret endp ; ------------------------------------------------------------------------------------- proc ShiftDigitsRight uses esi edi ecx ; shift the Divisor one digit to the right lea esi,[Divisor] lea edi,[Divisor] mov cx,0 .SHR: mov dl,[esi] shr dl,4 mov [edi],dl inc esi cmp cx,HEX_LEN-1 jge .DONE mov dl,[esi] shl dl,4 or [edi],dl inc edi inc cx jmp .SHR .DONE: ret endp ; ------------------------------------------------------------------------------------- section '.idata' import data readable writeable library kernel,'KERNEL32.DLL', user,'USER32.DLL' import kernel, GetModuleHandle,'GetModuleHandleA', ExitProcess,'ExitProcess' import user, DialogBoxParam,'DialogBoxParamA', SetDlgItemText,'SetDlgItemTextA', GetDlgItemText,'GetDlgItemTextA', EndDialog,'EndDialog' ; ------------------------------------------------------------------------------------- section '.data' readable writeable bfDisplay rb 12000 InputBuffer rb MAX_HEX_DIGITS + 8 Param_1 rb HEX_LEN Param_2 rb HEX_LEN Param_3 rb HEX_LEN Param_4 rb HEX_LEN Param_5 rb HEX_LEN Param_6 rb HEX_LEN Param_7 rb HEX_LEN Param_8 rb HEX_LEN Param_9 rb HEX_LEN Param_10 rb HEX_LEN Reg_1 rb HEX_LEN Reg_2 rb HEX_LEN Reg_3 rb HEX_LEN Reg_4 rb HEX_LEN TempBf rb HEX_LEN ShiftBf rb HEX_LEN Quotient rb HEX_LEN Divisor rb HEX_LEN Symbol_1 rb 10 Symbol_2 rb 10 Symbol_3 rb 10 Symbol_4 rb 10 Symbol_5 rb 10 Symbol_6 rb 10 Symbol_7 rb 10 Symbol_8 rb 10 Symbol_9 rb 10 Symbol_10 rb 10 Calculations rb CALCS_BUFFER_SZ CurrentLine rb 100 CurrentInstr rb 10 CurrentArg rb 10 bfImmediate rb 40 Immediate rb 16 nCalculation db 0 nErrorCode db 0 nExeErrorCode db 0 nBytes dw HEX_LEN szInputText db "Define up to ten 400 byte input parameters here:",0 szExprText db "Enter instructions here:",0 szError_0 db "Unknown error in calculations input buffer.",0 szError_1 db "Extra chars found at the end of the line:",0 szError_2 db "Immediate value argument contains non hex digits in the line:",0 szError_3 db "Missing argument in the line:",0 szError_4 db "Could not match argument to symbol in the line:",0 szError_5 db "Unknown instruction in the line:",0 szError_6 db "Immediate value must be LTE to 3200 (decimal) in the line:",0 szError_7 db "Invalid chars in calculations input buffer.",0 szError_8 db "Invalid chars in hex parameters input buffer.",0 szExeError_0 db "Unknown error - execution failed:",0 szExeError_1 db "Unknown instruction or argument code:",0 szExeError_2 db "Instruction failed - divide by zero error.",0 Symbol_11 db "reg_1",0 Symbol_12 db "reg_2",0 Symbol_13 db "reg_3",0 Symbol_14 db "reg_4",0 ; ------------------------------------------------------------------------------------- section '.rc' resource data readable directory RT_DIALOG,dialogs resource dialogs,IDD_THE_DIALOG,LANG_ENGLISH+SUBLANG_DEFAULT,the_dialog dialog the_dialog, 'FASM - Hex Calculator',20,50,522,396, DS_MODALFRAME+WS_MINIMIZEBOX+WS_POPUP+WS_VISIBLE+WS_CAPTION+WS_SYSMENU, 0,0,"Lucida Console",11 dialogitem 'EDIT',0,IDC_INPUT,0,0,260,366,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0 dialogitem 'EDIT',0,IDC_CALCS,262,0,260,120,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0 dialogitem 'EDIT',0,IDC_OUTPUT,262,122,260,244,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0 dialogitem 'BUTTON',"Load Parameters",IDC_BTN_LOAD_DATA,7,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0 dialogitem 'BUTTON',"Clear Parameters",IDC_BTN_CLR_DATA,89,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0 dialogitem 'BUTTON',"Run Instructions",IDC_BTN_CALC,171,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0 dialogitem 'BUTTON',"Clear Instructions",IDC_BTN_CLR_INSTR,253,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0 dialogitem 'BUTTON',"Reset All",IDC_BTN_RESET_ALL,335,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0 enddialog ; -------------------------------------------------------------------------------------