Урок TASM и культура программирования на ассемблере.
Для того чтобы быстро программировать на ассемблере надо выучить его команды, но это не всё.
Надо научиться грамотно его использовать.
Первое не стоит оптимизировать код. Регистров у компьютера мала, а некоторые команды требуют определеённые регистры человек не способен
удержать в голове более 5 значений. Поэтому используйте стек и процедуры как можно чаще.
Второе правило вызов процедур надо оформлять одним из обще принятых методов.
Есть обще принятые правила по передачи параметров в процедуры их надо выучить и пользоваться.
Для демонстрации этих правил напишем простую програмку которая демонстрирует деление и умножение чисел.
Но прежде чем мы приступим я раскажу о соглашениях при вызовах функций.
Для работы со стеком есть следующие команды PUSH и POP
пример
push ax ; положить регистр на вершину стека
pop ax ; считать регистр с вершины стека
Стек действует по принципу последний вошёл первый вышил (ПоВПрВ англ. LIFO).
Также есть команды PUSHA, POPA и PUSHF, POPF
Про стек поговориле перейдём к соглашению о передачи параметров через стек.
Как я уже говорил чтобы не запутаться надо их строго соблюдать.
А во вторых заботиться о сохранение регистров в неизменном виде.
Машина легко просчитает какие регистры используются в процедуре, а какие нет. А человеку это трудно.
Поэтому перед входом в процедуру сохраняйте регистры в стеке, а при выходе востанавливайте.
Краткий обзор как это делает паскаль.
Использование регистров в Borland Pascal:
Правило по использованию регистров в ассемблерных вставках, точно такое же как для внешних процедур или функций.
Асмблерная вставка должна сохранять регистры:
BP SP
SS DS
И ассемблерная вставка может свободно модифицировать следующие регистры:
AX BX
CX DX
SI DI
ES Flags
Надо заметить что о сохранности регистров тут заботится компилятор.
Но уже в Delphi 7 это пересмотрено и
процедуры и функции должны сохранять значения регистров:
EBX ESI EDI EBP
И может модифицировать регистры
EAX EDX ECX
CS,DS,ES,SS считаются неизменяемыми в течение всей работы программы, это особенность Windows.
Про передачу параметров в процедуру или функцию.
В паскале как и в биосе для передачи параметров используют регистры.
Но во всех остальных Delphi 7, Visual C++, GNU C и др для
передачи параметров используют стек.
Возвращение результата. Результат функции передается либо, как константа либо по ссылке. Ссылку используют для строк, объектов.
В зависимости от размера возвращаемого значения выбирается регистр:
BP7
AL,AX,DX:AX
Delphi 7
AL,AX,EAX,EDX:EAX
Для чисел с плавающей запятой используется sp(0)
Вернёмся к нашему tasm. Параметры
Код:
fill_screen:
mov edi,[ModeInfo_PhysBasePtr]
mov ecx,640*480
@next:
mov DWord PTR [edi],ebx
add edi,4
loop @next
ret
Найдите ошибки в оформление.
Программа не сохраняет EDI, ECX. Параметр передаётся через EBX, а должен в EAX или ещё лучше в стеке.
Более правильный вариант оформления
Код:
; eax - цвет заливки
fill_screen:
push edi
push ecx
mov edi,[ModeInfo_PhysBasePtr]
mov ecx,640*480
@next:
mov DWord PTR [edi],eax
add edi,4
loop @next
pop ecx
pop edi
ret
Теперь задание. Сделать преобразование числа в строку.
Использовать div для деления. И не менее одной процедуры.
Совет. Используйте стек, а передачу параметров делайте через стек или регистры ax, dx, bx или их братьев eax, edx, ebx. В порядке следования. Более детально смотри FastCall Задание для продвинутых. Сохранить все регистры сделать вывода регистров
В следующем формате.
Для этого сделать процедуру
; Указатель на буфер с регистрами и координата для печати на экране.
PrintReg
Код:
EAX 0000 0000 EBX 0000 0000
ECX 0000 0000 EDX 0000 0000
ESI 0000 0000 EDI 0000 0000
EBP 0000 0000 ESP 0000 0000
CS 0000 DS 0000 ES 0000
SS 0000 FS 0000 GS 0000
EIP 0000 0000 EFL 0000 0000
CF=0 PF=0 AF=0 ZF=0 SF=0
TF=0 IF=0 DF=0 OF=0 IOPL=3
MEM[00000000],ud = 0
Здесь EFL сокращение от EFLAG
Ниже идут флаги.
MEM[00000000],ud =
Вывод форматированной ячейки или регистра
первая буква формат
u - беззнаковй
s - знаковый
с - символ или набор символов ASCII
f - число с плавающей запятой
x - шеснадцатеричное
b - бинарное число макс 16 бит с раз пробелами старший бит слева 1000 0000 0000 0000
Вторая буква размер
b-8 бит/1 байт
w-16 бит/2 байта
d-32 бита/4 байта
f-48 бит/6 байт
q-64 бита/8 байт
Пример простой программы на TASM и способ её скомпилировать.
Код:
.model tiny ;Простая модель код и данные в одном сегменте
.code ;Секция кода
.486 ; 32 битные регистры появились в 386, данная инструкция их разрешает
; org 7C00h ; нужно для MBR чтобы линкер правильно сформировал смещения
org 100h ; нужно для ДОС чтобы линкер правильно сформировал смещения
entery: ; Точка входа в программу.
XOR AX,AX
MOV SS,AX ; установка стека в 2 инструкции
MOV SP,7C00h ;
; Стек настроен, ДОС делает за нас, но в MBR это нужно.
PUSH AX
POP ES
PUSH CS
POP DS ;настройка сегментов данных, ДОС делает за нас, но в MBR это нужно.
mov CX, 12
MOV bp, offset SystemEror1
CALL Far ptr WriteStr
@@: JMP @@ ; ВНИМАНИЕ: Бесконечный цикл
WriteStr:
PUSHA
MOV AX,1300h
MOV BX,7
MOV DX,0
push cs
pop es
int 10h
POPA
RETF
SystemEror1: db 'System Error'
end entery ; даём компилятору указания считать entery точкой входа в программу.
Данная программа выводит строчку "System Error" в самый верхний угол экрана.
Я обычно для компиляции пишу bat скрипты и сохраняю их в
make.bat
Код:
..\tool\tasm\Tasm.exe test.asm /z
..\tool\tasm\TLink.exe /3 /t test.obj,test.com
Для отладки удобно использовать Turbo Debuger.
ДЗ. Посмотреть в TD (turbo debuger) и WinHex порядок байт после записи
mov eax,12345678h
mov ds:[100], eax
Про соглашения о вызовах подпрограмм в различных компиляторах можно прочитать здесь
http://en.wikipedia.org/wiki/X86_calling_conventionshttp://agner.org./optimize/calling_conventions.pdf