Asembler - Wstęp

CZĘŚĆ I : Przed startem...

Wprowadzenie
W tej części opisujemy niektóre podstawowe pojęcia na temat danych, CPU i ogólnego programowania. Doświadczony programista może chcieć pominąć tę sekcję.
Bity i bajty
Kiedy programujemy w języku asembler, trzeba wiedzieć coś o bitach, bajtach i słowach. Bit jest najmniejszym elementem informacji, która może być adresowana. Pojedynczy bit może być oprogramowany jako zero lub jeden; dlatego też nie może wyrażać zbyt wiele informacji. Grupa ośmiu bitów połączonych razem tworzy jedne bajt. Osiem bitów połączonych razem może przedstawiać liczby od 0 do 255 lub 256 kombinacji. Jeśli najwyższy bit jest używany do identyfikacji liczby jako dodatniej lub ujemnej, wtedy możemy przedstawić liczby od +128 do -127. kiedy najwyższy bit bajtu jest używany jako flaga dodatnia/ujemna, wtedy mówimy ,że liczba jest ze znakiem. Niektóre instrukcje będą używały logiki ze znakiem lub bez znaku. Dwa ośmiobitowe bajty połączone razem tworzą jedno 16 bitowe słowo. 16 bitami możemy przedstawić liczby od zera do 65535. 8086 jest sklasyfikowany jako procesor 16 bitowy. Większość rejestrów jest 16 bitowych z możliwością adresowania w trybie 8 bitowym. Większość operacji jest zorientowanych na słowo lub bajt. Słowo ze znakiem ma zakres od -32768 do +32767. Podwójne słowo jest 32 bitowe i równe dwóm słowom i czterem bajtom.

CPU
CPU to Central Processing Unit. Wykonuje z bloku pamięci, które można nazwać zakresem adresowania. Ma wskaźnik instrukcji, który jest używany do indeksowania następnej instrukcji do wykonania. Kiedy CPU zaczyna wykonywanie instrukcji, ładuje dane instrukcji do swoich wewnętrznych rejestrów podczas gdy zwiększa się indeks kolejnej instrukcji do wykonania po zakończeniu bieżącej instrukcji. Zauważ, że pewne instrukcje resetują zawartość rejestru wskaźnika instrukcji aby wymusić na programie wykonanie innej instrukcji niż ścieżka kolejnej instrukcji. Jest to nazywane skokiem lub rozgałęzieniem. CPU 8086 zwykle działa na danych w formie bajtu lub słowa. CPU 8086 ma 20 bitową szynę adresowania, która pozwala na adresowanie ponad miliona bajtów bezpośredniej pamięci.
Trochę o programowaniu
Ogólny zarys dla standardowego rutynowego programu jest taki:
1.Odczyt danych z urządzenia
2.Analiza lub modyfikacja danych w razie potrzeby
3.Zapis do urządzenia
Program języka asemblera jest listą instrukcji w pliku z kodem źródłowym , który może być skompilowany do kodu wykonywalnego, który może być zrozumiały przez CPU. Po wygenerowaniu pliku kodu wykonywalnego, powinieneś móc załadować kod do pamięci komputera i wykonać go. W programowaniu asemblerowym, wiele instrukcji jakie piszesz związanych jest z kontrolą aktywności CPU. Piszesz również dyrektywy dla kompilatora, i komentarze dla procedur. Program może współpracować z innymi urządzeniami takimi jak koprocesory matematyczne , kontrolery przerwań, urządzenia zegarowe itp. CPU 8086 ma wiele rejestrów które wykorzystuje dla wykonania tych zadań. Mamy dostępnych 14 rejestrów dla programistów. Każda instrukcja , która jest wykonywana w programie wpływa na jeden lub więcej rejestrów CPU .Nawet instrukcja no operation zmienia rejestr wskaźnika instrukcji. Niektóre rejestry mogą być używane dla przechowywania danych. Inne rejestry są używane do indeksowania danych. Z powodu ograniczonej liczby rejestrów, może się okazać ,że rejestry indeksowania przechowują w danym czasie dane.

CZĘŚĆ II : Rejestry CPU

Programista asemblerowy musi zapoznać się z rejestrami CPU, aby móc programować efektywniej. Poniżej mamy listę 16 bitowych rejestrów i ich zastosowania:
AX :akumulator
FL : flagi X|X|X|X|OF|DF|IF|TF|SF|ZF|X|AF|X|PF|X|CF
BX : indeks bazowy
BP : wskaźnik bazowy
CX : licznik
DX : indeks danych I/O
DI : indeks przeznaczenia
SI : indeks źródłowy
SP : wskaźnik stosu
IP : wskaźnik instrukcji
CS : segment kodu
SS : segment stosu
DS : segment danych
ES : segment dodatkowy
Niektóre rejestry mogą być podzielone i dostępne jako dwa ośmiobitowe rejestry dla operacji bajtowych
AH    AX wyższy bajt
AL    AX niższy bajt, ośmiobitowy akumulator
BH    BX wyższy bajt
BL    BX niższy bajt
CH    CX wyższy bajt
CL    CX niższy bajt, licznik przesunięcia
DH    DX wyższy bajt
DL    DX niższy bajt
AX i AL są Akumulatorem: AX = AH+AL .AX jest 16 bitowym odniesieniem dla akumulatora; AL jest 8 bitowym odniesieniem AL jest niższą połówką AX z AH jako połówką wyższą. Akumulator jest podstawowym rejestrem danych. Większość instrukcji dla obsługi danych wykonuje się szybciej jeśli dana jest w akumulatorze.
FL Rejestr flag : Rejestr Flag jest 16 bitowym rejestrem danych używanym do śledzenia aktywności CPU. Obejmuje to wszystkie wyniki logiczne, arytmetyczne i porównania jak również kontrola przerwań, debuggowanie, łańcuch flagi kierunku itp. Większość instrukcji skoków warunkowych używa zawartości tego rejestru dla określenie czy warunek rozgałęzienia jest prawdziwy czy nieprawdziwy.
Bity Flag : X|X|X|X|OF|DF|IF|TF|SF|ZF|X|AF|X|PF|X|CF
Bity z pozycji X nie są zdefiniowane dla CPU 808/8088 ale zarezerwowane dla późniejszych procesorów Intela
OF Flaga przepełnienia : Bit ten jest ustawiany jeśli manipulacją daną spowoduje zmianę wyższego bitu
DF Flaga kierunku : Bit ten jest używany przez CPU do decydowania o kierunku operacji łańcuchowej. Wyzerowanie tego bitu powoduje ,że operacja działa w przód a ustawienie bitu ,że operacja działa wstecz.
IF Flaga przerwania : Ten bit może być ustawiony lub wyzerowany przez programistę dla zabezpieczenia lub zezwolenia na wystąpienie zamaskowanego przerwania
TF Flaga śledzenia : Ten bit jest używany w trybie debuggowania dla pojedynczego przejścia przez logikę programu
SF Flaga znaku jest resetowana przez operacje logiczne będące równymi wyższemu bitowi danej wynikowej
ZF Flaga zera : Ten bit jest ustawiany jeśli ostatnia manipulacja daną stworzyła warunek zero
AF Flaga przeniesienia pomocniczego : Ten bit jest używana przez instrukcje logiczne które działają z danym w nibblach (cztery bity)
PF Flaga parzystości : Ten bit jest resetowany przez instrukcję ostatniej manipulacji daną odzwierciedlającą czy operacja tworzy parzysty lub nieparzysty warunek parzystości. 1 oznacza parzystość, 0 nieparzystość
CF Flaga przeniesienia : Kiedy dodajemy, bit przeniesienia jest ustawiana jeśli wystąpi przepełnienie. Jeśli odejmujemy bit jest ustawiany jeśli musi pożyczyć bit ponieważ w wyniku odejmowania zmienił się znak
BX Indeks bazowy : NBX jest najbardziej elastycznym rejestrem indeksowym. Jest to rejestr 16 bitowy, który może być adresowany w 8 bitowym formacie jako BH (wyższy) i BL (niższy) gdzie BX = BH+BL moze być dodany do innych rejestrów indeksowych dla pracy z bardziej złożonymi offsetami indeksowania. Na przykład ;[BX + offset], [BX+SI+offset]
BP Wskaźnik bazowy : BP jest rejestrem wskaźnika bazowego używanym do indeksowania danych w obszarze stosu. Rejestr ten jest używany przez wiele kompilatorów do indeksowania ramek danych w obszarze stosu. BP może być połączony z DI lub SI dla indeksowania danych w obszarze stosu. Przykłady : [BP+SI+offset], [BP+DI+offset]
SI Indeks źródła : SI jest rejestrem indeksu źródłowego używanym przez instrukcje łańcuchowe. Może to być połączony z BX lub BP dla indeksowania danych. Przykład : [SI + BX + offset]
DI Indeks przeznaczenia : DI jest rejestrem indeksu przeznaczenia używanym przez instrukcje łańcuchowe. Może być połączony z BX lub BP dla indeksowania danych .Przykłady:[DI + BX + offset]
SP Wskaźnik stosu : SP jest rejestrem wskaźnika stosu używanym przez instrukcje push, pop, call, interrupt i return Zawsze indeksuje ostatnie słowo odłożone na stos
CX Licznik : CX jest rejestrem licznika używanym przez instrukcje string, repeat i loop
DX Rejestr danych, index I/O : DX jest rejestrem danych, DX jest tylko używanym jako indeks dla funkcji portów I/O. Jest używany dla instrukcji mnożenia 16 przez 16 bitów i dzielenia 32 przez 16. Wynik z 16 bitowego mnożenia jest wstawiany do DX:AX gdzie DX przechowuje wyższe 16 bitów a AX przechowuje niższe 16 bitów. Dla dzielenia 32 bitów przez 16 bitów, DX będzie przechowywał resztki (modulo) danych wynikowych z dzielenia.
IP Wskaźnik instrukcji : IP jest używany do indeksowania następnej instrukcji do wykonania. Jest resetowany przez instrukcje call i jump.
Rejestry adresowania segmentowego
CPU 8086 dzieli swoją pamięć adresowalną na cztery obszary. Te cztery obszary to kod, stos, dane i dane dodatkowe. Bieżące położenie tych sekcji jest kontrolowane przez cztery rejestry adresowania segmentów. Architektura 8086 używa tego do rozszerzania zakresu adresowania CPU. Podstawowy zakres adresowania zwykłego 16 bitowego CPU to 65536 bajtów. Przez dodanie offsetów pamięci segmentowanej do pamięci adresowalnej, zakres adresowania CPU 8086 jest zwiększany do 20 bitów lub 1 048 576 bajtów. Jest to wykonywane przez przesunięcie czterech 16 bitowych rejestrów segmentowych o nibble (cztery bity) i dodanie ich do innych rejestrów indeksowych dla osiągnięcia 20 bitowego adresu rzeczywistego. W tym systemie, CPU może adresować jeden megabajt pamięci. Jedyna złożoność systemu pamięci polega na tym ,że jest podzielony na cztery bloki, które mają maksymalnie 64 kB każdy .Ogranicza to aktywny zakres adresowania CPU do 256 KB maksymalnie jednocześnie. Ponieważ istnieją dwa 16 bitowe słowa używane do zakończenia adresu, używamy tu "offsetu" aby odnosić się do adresu w niższym 16 bitowym zakresie (0 - 65536). Aby odnosić się do części adresowej segmentu, który jest górnym 16 bitowym słowem, używamy "SEG" lub "segment"
CS Segment Kodu
Używany jest z rejestrem IP (wskaźnik instrukcji) dla indeksowania kolejnej instrukcji dla wykonania przez logikę programu. Oto jak CPU oblicza adres kodu ze wskaźnikiem instrukcji


Jeśli IP= 7 a CS = 4 wtedy adres rzeczywisty = 37H jak pokazano:


DS Segment Danych
Jest używany jako podstawowy obszar danych. Jest indeksowany przez BX, SI, DI (kiedy DI nie wykonuje instrukcji łańcuchowych), i offsety bez rejestru indeksowego
SS Segment Stosu
Używany jako obszar stosu. Jest indeksowany przez SP i BP. Zwróć uwagę ,że kiedy BX jest używany z BP w obliczaniu offsetu adresu, wtedy używanym segmentem jest SS
ES Extra Segment
Używany jako dodatkowy obszar danych. Jest indeksowany przez DI podczas wykonywania instrukcji łańcuchowych jako adres przeznaczenia .
Nadpisanie może być używane z większością instrukcji aby wymusić rejestr indeksowy odwoływał się do danych w innym rejestrze segmentowym, niż tego, który jest używany zwykle. Na przykład ,dane indeksowane przez BX normalnie pochodzą z segmentu DS, ale z nadpisaniem, dane mogą pochodzić z segmentu CS jako CS:[BX].

CZĘŚĆ III : Elementy instrukcji języka asembler


Omówimy tu podstawową strukturę instrukcji języka Asembler. W standardowej instrukcji języka Asembler są cztery pola. Pola zwykle są oddzielone spacją lub znakiem tabulacji. W wielu instrukcjach istnieją pola zaginione. Generalnie mogą być przedstawiane i badane jak poniżej:
Etykieta   Opertor   Dane        Komentarz
; jeśli AH jest niezerowy, ten program dodaje 30H do AL.
Add_AL_30     PROC     near
       cmp    ah, 0       ; porównuje AH z zerem
        jnz    add_al_0       ; skocz jeśli nie zerowe
       add    al.,30H       ;ta instrukcja dodaje 30 do AL.
add_al_0:
;powrót do wywołania
   ret
Wiele listingów programów zaczyna się od komentarzu , które zwykle wyjaśniają cel i dodatkowe informacje. Pole komentarza zawsze zaczyna się od średnika ( ; ). Wszystkie komentarze są opcjonalne i zazwyczaj służą do śledzenie logiki programu. Pole etykiety , kiedy jest używane, zawsze przychodzi przed innymi polami w linii. Pole to jest używane dla definiowania nazw, jakie tworzy programista dla odniesienia się do położenia w programie. Tu mamy trzy sposoby definiowania etykiety:
1.Zakończenie etykiety znakiem dwukropka ( : )
2.Użycie instrukcji PROC jeśli etykieta służy do kodowania procedury
3.Użycie instrukcji LABEL jeśli etykieta służy strukturze danych lub typowi danej (NEAR, FAR, BYTE, WORD, DWORD, FWORD, PWORD, QWORD, TBYTE, DATAPTR, CODEPTR)
Pole operatora przychodzi po polu etykiety a przed polem danych. Prowadzi to kompilator lub CPU do zrobienia czegoś. Pole to często jest wypełnione mnemonikami języka
Pole danych przychodzi po polu operatora a przed polem komentarza. Jeśli pole operatora wymaga zmiennej danej wtedy dana przychodzi tu. Może być od zera do wielu zmiennych danej lub operandów dla operacji. Kiedy dwie zmienne danej są używane z większością instrukcji 8086, pierwszy operand jest miejscem przeznaczenia a drugi operand źródłem. Przykłady:
nop        ; tu nie ma zmiennych danych
jmp    gdzieś    ;tu mamy 1 zmienną danych
add   ax,bx    tu mamy dwie zmienne danych
macro   1,2,3,4,5    ;makro może mieć wiele

CZĘŚĆ IV : Kompilowanie i wykonywanie programów


Ta część omawia podstawy generowania pliku wykonywalnego z pliku kodu źródłowego. Omawia też opcje kompilatora asemblacji warunkowej i makra. Przy starszych wersjach MASM, istnieją dwa podstawowe kroki używane dla generowania pliku wykonywalnego do uruchomienia pod DOS. Pierwszy krok to kompilacja kodu źródłowego do pliku obiektowego. Drugi krok to połączenie pliku obiektowego z innym potrzebnym plikiem obiektowym i podprogramem bibliotecznym dal wygenerowania pliku wykonywalnego. W nowszych wersjach MASM można to wykonać pojedynczym poleceniem. Przy stosowaniu Microsoft Macro Assemblera (ML) dla kompilacji kodu, mamy cztery domyślne rozszerzenia nazw plików używane dla tych plików. Są to .ASM dla pliku języka Asembler, .OBJ dla plików obiektowych, .LIB dla plików bibliotecznych i .EXE dla pliku wykonywalnego.
Kompilowanie
Instrukcje dla kompilowania programu są określone w oprogramowaniu kompilatora i mogą mieć wiele różnych opcji. Prostym sposobem na kompilację jest wpisanie poniższej instrukcji:
ML nazwa_pliku.ASM
Polecenie to wykonuje standardową kompilację pliku z rozszerzeniem .ASM i tworzy plik o tej samej nazwie ale rozszerzeniem .EXE, który można wykonać. Przykład wiersza poleceń z opcją kompilatora:
ML /Fl nazwa_pliku.ASM
/Fl jest używane dla wygenerowania listingu kodu języka Asembler co jest pomocne dla znajdowania błędów programu.
Przykład Kodu : Prosty program IO
Poniższy przykład ilustruje prosty program, który pobiera dane z klawiatury od użytkownika, dodaje dwie liczby i wyświetla wynik na standardowym wyjściu. Podprogram ten używa trzech podstawowych wywołań DOS : funkcja 1 DOS (StdConInput) dla wejścia, funkcja 2 DOS (StdConOutput) dla wyjścia i funkcja 4CH DOS (Exit) dla zakończenia podprogramu. Program pobiera dwie pojedyncze cyfry liczb od użytkownika, dodaje te liczby razem i wyświetla wynik. Program kontynuuje działanie dopóki użytkownik nie kliknie znaku który nie jest liczbą. ;Kod program dla dodawania liczb
.MODEL small
;######################################
.STACK 500
;######################################
.DATA
data_1 dw 0
;######################################
.CODE
start proc near
mov dx,@data ;uzyskanie indeksu segment danych
mov ds,dx ;ustawienie segment danych
add_loop:
call get_number
jc number_error
mov data_1,ax
call get_number
jc number_error
add ax,data_1
call display_number
jmp add_loop
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
number_error:
mov ah,4CH ;ustawienie funkcji zakończenia DOS
mov al,0 ;ustawienie zmiennej kodu zakończenia
int 21H ;wywołanie DOS dla zakończenia
start endp
;************************************
get_number proc near
mov ah,1 ;ustawienie funkcji dla odczytu z klawiatury
int 21H ;wywołanie DOS dla pobrania danych z klawiatury
cmp al,'0' ;test poprawności liczby
jb get_number_bad ;skok jeśli zła liczba
cmp al,'9'
ja get_number_bad ;skok jeśli zła liczba
an ax,0FH
clc ;ustawienie OK kodu wyjścia ret ;wyjście
get_number_bad:
stc ;ustawienie ERROR kodu wyjścia
ret ;wyjście z programu
get_number endp
;************************************
display_number proc near
;na wyjściu liczba binarna w AX jest wyświetla(między 0 a 19)
call display_new_line
display_number_1:
cmp al,9
ja display_number_2
or al,30H
mov dl,al
mov ah,2 ;ustawienie DOS wyświetlania znaku
int 21H ;wywołanie funkcji DOS
ret ;wyjście z podprogramu
display_number_2:
sub al,10 ;modyfikacja liczby
push ax ;zapisanie liczby
mov dl,'1'
mov ah,2 ;ustawienie DOS dla wyświetlenia znaku
int 21H ;wywołanie funkcji DOS
pop ax ;przywrócenie zmodyfikowanej liczby
jmp display_number_1
display_number endp
;************************************
display_new_line proc near
;na wyjściu, kursor znajduje się na początku nowej linii
push ax ;zapis rejestru AX
push dx ;zapis rejestru DX
mov dl,0DH ;start znaku linii
mov ah,2 ;ustawienie DOS dla wyświetlania znaku
int 21H ;wywołanie funkcji DOS
mov dl,0AH ;znak nowej linii
mov ah,2 ; ustawienie DOS dla wyświetlania znaku
int 21H ; wywołanie funkcji DOS
pop dx ;przywrócenie rejestru DX
pop ax ; przywrócenie rejestru AX
ret ;wyjście z program
display_new_line endp
;************************************
end start

Przykład Kodu : Program drukowania pliku
Poniższy przykład jest prostym programem odczytującym plik i drukującym jego zawartość na standardowej drukarce. Pobiera nazwę pliku do drukowania z wiersza poleceń DOS. Informacja ta jest przekazywana do programu w obszarze bufora Program Segment Prefix (PSP). Adres PSP jest przekazywany do programu w rejestrach ES i DS kiedy program zaczyna się wykonywać. Program sprawdza klawiaturę między każdym drukowanym znakiem dla kody klawisza Escape, kończącego wykonywanie programu. ; Ten program drukuje plik określony w wierszu poleceń
.MODEL small
;************* Stack Section *********************
.STACK 500
;************* Data Section **********************
.DATA
psp_seg dw 0
no_cl_mess db "Ten program wymaga aby "
db "nazwa pliku była w wierszu poleceń dla drukowania."
db 0dh,0ah,"Proszę wypróbować nazwę pliku.",0dh,0ah,"$"
file_bad_open db "Otwarto zły plik",0dh,0ah,"$"
file_bad_read db "Odczyt złego pliku",0dh,0ah,"$"
printer_bad_mess db "!! Błśd Drukarki !!!!",0dh,0ah,"$"
printing_mess db "Plik jest drukowany,",0dh,0ah
db "Aby zatrzymać drukowanie, naciśnij klawisz ESC ",0dh,0ah,"$"
filename db 128 dup(0)
file_handle dw 0
file_count dw 0
file_pointer dw offset file_buffer
file_buffer db 1024 dup(0)
; ************* ----------- *********************
;************* Code Section *******************
.CODE
start proc near
;DS i ES indeksują obszar PSP
mov al,[DS:80H] ;ładowanie do AL rozmiaru linii danych
mov dx,@data ;pobranie adresu segment obszaru danych
mov ds,dx ;DS wskazuje obszar danych
mov psp_seg,ES ;zapis adresu PSP
cmp al,1 ;?? Dane w wierszu poleceń DOS??
ja get_PSP_filename ;skok jeśli znaleziono dane
;jeśli tu nie znaleziono danych w wierszu poleceń
;wyświetlenie komunikatu błędu użytkownikowi i zakończenie
lea dx,no_cl_mess
;-------------------------
terminate_display:
;wyświetlenie komunikatu indeksowanego przez DX potem zakończenie
mov ah,09
int 21H ;Wywołanie DOS
;------------------------- terminate_program:
;zakończenie programu
mov ah,4CH ;ustawienie AH na funkcję zakończenia
mov al,00 ;usatwienie zmiennej kodu zakończenia
int 21H ;wywołanie DOS do zakończenia
;------------------------------------------
; %%%%%%%%%%%%% ----------- %%%%%%%%%%%%%%%
get_PSP_filename:
;przenoszenie nazwy pliku z PSP do bufora w naszym obszarze danych
mov ax,ds
mov es,ax ;ES wskazuje segment danych
mov ds,psp_seg
mov si,82H ;źródło SI jest obszrem danych PSP
lea di,filename
cld ;tworzy łańcuchy w przód
get_PSP_data_1:
lodsb ;ładowanie bajtowego łańcucha danych
;sprawdzenie końca nazwy pliku
cmp al,21H
;skok jeśli koniec łańcucha
jb got_PSP_filename
stosb ;przechowanie bajtowego łańcucha danych
jmp get_PSP_data_1
got_PSP_filename:
mov al,0
stosb ;tworzenie łańcucha ASCIIZ z końcowym zerem
push es
pop ds ;reset wskaźnika segmentu danych
;próba otwarcia pliku
mov ah,3dH
lea dx,filename
mov al,0 ;odczyt kodu dostępu
int 21H ;Wywołanie DOS
jnc file_open_ok
lea dx,file_bad_open
jmp terminate_display
;+++++++++++++++++++++++++++++++++++++++++++
;############### +++++++++++ ###############
file_open_ok:
;zapis uchwytu pliku
mov file_handle,ax
lea dx,printing_mess ;wyświetlenie komunikatu startowego
mov ah,09
int 21H ;Wywołanie DOS
file_read: ;odczyt w bloku danych pliku
mov ah,3fH
lea dx,file_buffer
mov cx,1024
mov bx,file_handle
int 21H ;Wywołanie DOS
jnc file_read_ok ;skok jeśli dobrze odczytane
;albo wystąpił błąd podczas odczytu
;zamykanie pliku
mov ah,3eh
mov bx,file_handle
int 21H
;indeks wyjścia komunikatu błędu
lea dx,file_bad_read
jmp terminate_display
file_read_ok:
;sprawdzenie czy nie ma więcej danych w pliku
cmp ax,0
je close_file ;skok jeśli nie zostawiono danych
;albo resetujemy rozmiar bloku danych i wskaźnik
mov file_count,ax
lea bx,file_buffer
mov file_pointer,bx
;!!!!!!!!!!!!!!!!! ^^^^^^^^ !!!!!!!!!!!!!!!!!!!!
print_data_block:
;główna pętla bloku drukowania danych pliku
;skanowanie klawiatury aby sprawdzić klawisz
mov ah,1
int 16H
jz print_data_block_1 ;skok jeśli nie ma klawisza
;pobranie kodu klawisza z bufora
mov ah,0
int 16H ;wywołaie BIOS klawiatury
cmp al,01BH ;sprawdzenie kodu klawisza
je close_file ;skok jeśli ESC
print_data_block_1:
mov si,file_pointer
mov al,[si]
mov ah,0
mov dx,0 ;select LPT1
int 17H ;Wywołanie BIOS
test ah,25H
jnz printer_error
inc si
mov file_pointer,si
dec file_count
jnz print_data_block ;pętla jeśli więcej danych
;albo odczyt kolejnego bloku danych pliku
jmp file_read
;!!!!!!!!!!!!!!!! ^^^^^^^^ !!!!!!!!!!!!!!!!!!!!
close_file:
mov ah,3eh
mov bx,file_handle
int 21H ;Wywołanie DOS
jmp terminate_program
;-------------- ?????????? -------------------
printer_error:
;indeks wyjścia komunikatu błędu
lea dx,printer_bad_mess
jmp terminate_display
;_______________________________________________
start endp ;koniec procedury start
end start ;określenie start jako początku programu

Przykład Kodu : Pobieranie pory dnia
Pierwszy przykład jest programem, który używa czasu systemowego. Ten przykład używa asemblacji warunkowej dla zdecydowania pomiędzy dwoma sposobami pobrania pory dnia. Jeden sposób to użycie funkcji DOS; drugi sposób , zilustrowany w drugim przykładzie, to użycie funkcji BIOS.
Pierwszy Przykład
Ten przykład trwa dopóki występuje ustawiona pora dnia a potem się kończy. Kod pobiera informację o porze dnia z danych wejściowych z linii poleceń DOS. Jeśli jest zła dana, wtedy wyświetla się komunikat błędu, informujący użytkownika o poprawnej formie wprowadzanych danych. Chociaż program czeka na zakończenie czasu, będzie skanował klawiaturę dla klawisza Escape, który kończy program na życzenie użytkownika
;Program może być wykonany wewnątrz pliku .BAT file aby
; opóźnić wykonanie pliku .BAT do momentu ustawionej pory dnia
;?????????????????????????????????????????????????????????? ???
;flaga asemblacji warunkowej, 0 dla użycia DOS, 1 dla użycia BIOS
.MODEL tiny
use_bios_flag EQU 1
;----------- obszar stosu ---------------
.STACK 500
;--------------------------------------
.CODE
;************* @@@@@@@@@@@@@@ ***************
start proc near
mov bx,80H ;indeks danych wiersza poleceń
mov al,[bx] ;pobranie rozmiaru zmiennej łańcuchowej
mov ax,cs
mov ds,ax ;reset segment danych
mov psp_seg,es ;zapis adresu PSP
mov es,ax ;reset segmentu dodatkowego
cmp al,4 ;czy dana jest w łańcuchu
jb exit_bad ;skok jeśli brak danych
inc bx
inc bx ;wskazanie startu danych
;pobranie liczby poza obszarem bufora
call get_number
jc exit_bad ;skok jeśli zła liczba
mov wait_hour,al ;zapis liczby godzin
cmp al,23 ;?? Liczba zbyt duża ??
ja exit_bad ;skok jeśli zbyt duże
;sprawdzenie liczby znaku końcowego
cmp ah,":"
jne exit_bad ;skok jeśli nie :
;wskzanie startu kolejnej liczby
inc bx
;pobranie kolejnej liczby poza obszarem bufora
call get_number
jc exit_bad ;skok jeśli zła liczba
cmp al,59 ;?? Liczba zbyt duża ??
ja exit_bad ;skok jeśli zbyt duża
mov wait_minute,al ;zapisanie liczby minut
;wyświetlenie wykonywanej wiadomości oczekiwania
mov ah,9 ;ustawienie numeru funkcji DOS
lea dx,wait_message
int 21H ;wywołanie DOS dla wyświetlenia komunikatu
;________________________________
;********** !!!!!!!! **********
wait_loop:
;skanowanie klawiatury
mov ah,1
int 16H
jz wait_no_key ;skok jeśli brak klawisza
mov ah,0 ;jeśl itu, wtedy dana klawiatury
int 16H ;pobranie kodu klawisza z bufora
cmp ax,3B00H ;sprawdzenie kodu klawisza
je exit ;skok jeśli klawisz wyjścia
cmp al,1BH ;sprawdzenie klawisza ESC
je exit ;skok jeśli klawisz ESC
wait_no_key:
;jaki jest czas
;asemblacja warunkowa ????????????????????
;używa tego kodu jeśli łączy kod w tej sekcji
IF use_bios_flag
call get_time_of_day
;albo używa tego kodu jeśli wywołuje DOS dla czasu
ELSE
mov ah,2CH
int 21H ;pobranie bieżącej pory dnia
ENDIF

cmp ch,wait_hour
jne wait_loop ;pętla jeśli brak czasu
cmp cl,wait_minute
jne wait_loop ;pętla jeśli brak czasu
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
exit:
mov ah,4CH
int 21h ;zakończenie programu
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
exit_bad:
mov ah,9
lea dx,exit_bad_message
int 21H ;Wywołanie DOS dla wyświetlenia komunikatu
jmp exit
; ***** ^^^^^^^^ ***** ^^^^^^^^ ****
get_number:
;na wejściu BX indeksuje daną liczbową ASCII w obszarze segmentu PSP
;na wyjściu ,jeśli brak przeniesienia
; rejestr AL ma liczbę binarną, od 0 do 99
; BX indeksuje ostatnią liczbę,
; AH ma kod znaku wyjściowego indeksowanego przez BX
push ds
mov ds,psp_seg
mov al,[bx]
inc bx
call number_check
jc get_number_bad
mov ah,al
mov al,[bx]
call number_check
jc get_number_1
get_number_2a:
cmp ah,0
je get_number_2
add al,10
dec ah
jmp get_number_2a
get_number_2:
inc bx
mov ah,al
mov al,[bx]
get_number_1:
cmp al,":"
je get_number_1a
cmp al,0DH
jne get_number_bad
get_number_1a:
xchg al,ah
pop ds
clc ;set good number flag
ret
get_number_bad:
pop ds
stc ;ustawienie flagi złej liczby
ret
;#################################################
number_check:
;ten kod sprawdza liczbę ASCII w AL
; jeśli znajduje liczbę, wtedy zmienia ją na binarną
; i zwraca z brakiem przeniesienia, albo ustawia przeniesienie
cmp al,"0"
jb number_bad
cmp al,"9"
ja number_bad
and al,0FH
clc
ret stc
ret
;*************************
start endp ;+++ ten program łączy dane i kod w jeden segment +++
; definiowanie obszaru danych
psp_seg dw 0
wait_hour db 0
wait_minute db 0
wait_message db 0DH,0AH,0DH,0AH
db "Oczekiwanie w toku, Naciśnij [ESC] aby wyjść",0DH,0AH
db "$"
exit_bad_message db 0DH,0AH
db "Aby użyć tego programu wprowadź dane limitu czasu "
db "z wiersza poleceń ,jako przykład:",0DH,0AH,0DH,0AH
db "TimeWait 11:30",0DH,0AH,0DH,0AH
db "Zauważ, limit godzin to od 0 do 23, "
db "a minut od 0 do 59.",0DH,0AH
db "$"
;______________________________________
end start

Drugi Przykład
Największy problem występujący podczas pracy z zegarem PC jest to ,że taktuje on 18,2 takty na sekundę. Kiedy wywołujemy BIOS dla pory dnia, mamy 32 bitową wartość , która przedstawia liczbę taktów, które mają przekazać dzień. Dzieląc tą liczbę przez około 18,2 uzyskujemy liczbę sekund, które minęły tego dnia. Na pierwszy rzut oka liczba 18,2 nie wydaje się ładnie dzielić. Dlatego też, spróbujemy pomnożyć ją, najpierw przez 60 (aby dowiedzieć się ile taktów jest w minucie), potem ponownie przez 60 (aby dowiedzieć się ile taktów jest w godzinie)
18,2 x 60 = 1092
1092 x 60 = 65520
Zauważ ,że 65520 jest blisko 65536, co jest równe szesnastkowo 10000. Rzeczywista godzina ma 65543 takty, co pokazuje ,że 18,2 nie jest 100% dokładne. Używając tych informacji, można znaleźć szybki sposób na pobieranie wartości pory dnia z timera systemowego kiedy nie chodzi o absolutną dokładność. Kod ten może być zastosowany w aplikacjach, które chcą pobrać porę dnia w godzinach, minutach i sekundach tak szybko jak to możliwe. Kod jest bardzo szybki. Poniższy przykład używa PC BIOS INT 1AH dla pobrania 32 bitowego czasu z timera systemowego. Potem bierze licznik i generuje bieżący czas w godzinach, minutach i sekundach. Ten kod wstawia dane do tych samych rejestrów do jakich wraca wywołanie DOS. Sztuczka polega w tym programie ,że zakładamy iż górna 32 bitowa wartość to licznik godziny a dolne słowo licznikiem minut/sekund .Kod generuje wartość, która jest bardzo blisko rzeczywistemu czasowi. Jeśli użyjemy tego kodu bez 12 sekundowej poprawki, pojawi się czas 24:00:10. Z ta poprawką , po 23:59:59 czas, po około 12 sekundach przejdzie na 00:00:00
.CODE
IF use_bios_flag
get_time_of_day proc near
;na wyjściu CH ma godziny,CL minuty,DH sekundy,
; DL ma resztki (modulo)
public get_time_of_day
push ax
push bx
;AH na 0 dla BIOS pobiera wywołanie czasu
xor ax,ax
int 1AH ;wywołanie BIOS
cmp cx,24 ;sprawdzenie północy
;skok jeśli północ
jae get_time_of_day_mn
;albo obliczenie czasu
mov ch,cl ;wstaw godziny do CH
mov bx,1092 ;65536/60
mov ax,dx
xor dx,dx
div bx
mov cl,al ;wstaw minuty do CL
mov ax,dx
xor dx,dx
mov bx,18 ;(65536/60)/60
div bx
mov dh,al ;wstaw sekundy do DH
pop bx
pop ax
ret
get_time_of_day_mn:
;poprawka dla 12 sekund północy
; istniej 1800B0h taktów na dzień
xor cx,cx
xor dx,dx
pop bx
pop ax
ret
;*********************************
get_time_of_day endp
ENDIF

Przykład Kodu : Tworzenie dźwięku
Ten kod dostarcza zbioru podprogramów klawiaturowych dla kontroli dźwięku podczas oczekiwania aż użytkownik wprowadzi znak. Zaletą tej metody jest to ,że główny program może wywołać te podprogramy dźwiękowe dla odtworzenia dźwięku, a podprogram dźwięku będzie zwracał sterowanie z powrotem do podprogramu głównego kiedy użytkownik wprowadza dane z klawiatury, tak więc program główny może kontynuować kiedy dźwięk jest odtwarzany w tle. Program ma dwa różne punkty wejścia do kodu dla pobrania danych klawiaturowych. Pierwszy punkt wejścia to standardowa funkcja get_keyinput, która oczekuje na klawisz i aktualizuje dźwiękową daną do znalezienia kodu klawisza. Drugi punkt wejścia do kodu to funkcja get_keyinput_to, która oczekuje ustaloną ilość czasu na kod klawisza na kod klawisza i jeśli nie znajduje, zwraca warunek nieznalezienia kodu klawisza. Program wywołujący wstawia wartość licznika ograniczenia do rejestru AX na wejściu. Wartość licznik jest oparta o zegar systemowy z taktami 18,2 razy na sekundę. Punkt wejścia start_table_sound jest używany do rozpoczęcia sekwencji dźwięku w tle. Na wejściu, rejestr BX indeksuje tablicę danych dźwiękowych. Tablica ma format czterobajtowych wejść i jest zakończona przez zerowe słowo danych. Cztery bajty są używane jako dwa słowa : pierwsze jest licznikiem czasy trwania a drugi wartością sygnału. Istnieją dwa punkty wejścia kodu dla włączania i wyłączania dźwięku tła. Jest również narzędzie do wypłukiwania bufora klawiatury, co może być wykonane funkcją flush_keyboard
;Zbiór podprogramów klawiaturowych z dźwiękiem wyjściowym
.MODEL small
.STACK 500
.DATA
;definiujemy tablicę dla dźwięku wyjściowego
;próbka dźwięku dw 8,45000 ;długi dolny dźwięk
; dw 2,2000 ;krótki i wysoki dźwięk
; dw 0 ;koniec tabeli próbek dźwięków
sound_table dw 0
sound_time_m dw 0
sound_time_l dw 0
sound_flag db 0
sound_on_flag db 0,0
key_time_out_m dw 0
key_time_out_l dw 0
.CODE
;************ ^^^^^^^^^^ *************
;### code entry point #####
get_keyinput proc near
;ten program sprawdza dane klawiaturowe w buforze BIOS
; i zwraca dane jeśli są ;albo aktualizuje daną dźwięku wyjściowego i wykonuje pętlę dla sprawdzenia
; danych klawiaturowych dopóki ich nie znajdzie
;na wyjściu AX ma dane klawiaturowe
public get_keyinput
push bx
push cx
push dx
get_keyinput_loop:
mov ah,1 ;ustawia AH dla skanowania
int 16H ;Wywołanie BIOS
;skok jeśli brak danych
jz sound_update
mov ah,0 ;ustawia AH dla pobrania klawisza
int 16H ;Wywołanie BIOS
pop dx
pop cx
pop bx
ret
;******* -------- *******
sound_update:
cmp sound_flag,0 ;sprawdzenie włączenia dźwięku????
jz get_keyinput_loop ;skok jeśli dźwięk wyłączony
mov cx,sound_time_m ;albo sprawdza aktualizację dźwięku
mov ax,sound_time_l
call test_current_time ;czas na aktualizację ??
jc get_keyinput_loop ;skok jeśli nie ma czasu
mov bx,sound_table
mov ax,[bx] ;pobranie kolejnej wartości aktualizacji dźwięku
or ax,ax ;?? Koniec dźwięku ??
jz turn_sound_off ;skok jeśli koniec dźwięku
call get_time_plus_ax ;reset czasu trwania dźwięku
mov sound_time_m,cx
mov sound_time_l,ax
inc bx
inc bx
mov ax,[bx]
inc bx
inc bx
mov sound_table,bx
call sound_out_ax ;ustawienie częstotliwości dźwięku
jmp get_keyinput_loop ;skok do pętli klawiatury ustawiania częstotliwości dźwięku
turn_sound_off:
call sound_off
mov sound_flag,0
jmp get_keyinput_loop ;skok do pętli klawiatury
get_keyinput endp
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;************ ########## *************
;### kod punktu wejścia #####
get_keyinput_to proc near
;pobranie danych klawiatury z limitem czasu jeśli nie jest dostępna żadna dana
;na wejściu AX czas trwania w 18 taktach na sekundę
;na wyjściu jeśli brak przeniesienia ,wtedy AX ma dane klawiaturowe
public get_keyinput_to
push bx
push cx
push dx
call get_time_plus_ax ;dodajemy czas trwania dla bieżącego czasu
mov key_time_out_m,cx ;ustawienie czasu limitu
mov key_time_out_l,ax
get_keyinput_to_loop:
mov ah,1 ;gotowe do skanowania danych klawiaturowych
int 16H ;Wywołanie BIOS
jz sound_update_to ;skok jeśli brak danych klawiaturowych
mov ah,0 ;gotowe do pobrania danych klawisza
int 16H ;Wywołanie BIOS
pop dx
pop cx
pop bx
clc ;ustawienie flagi danych klawiaturowych
ret
get_keyinput_to_1:
mov cx,key_time_out_m ;sprawdzenie limitu czasu
mov ax,key_time_out_l
call test_current_time
jc get_keyinput_to_loop ;skok jeśli brak limitu czasu
xor ax,ax ;albo zwraca warunek limitu czasu
pop dx
pop cx
pop bx
stc ;nieustawienie żadnej flagi danych klawiaturowych
ret
; ******** %%%%%%% ********
sound_update_to:
cmp sound_flag,0 ;sprawdzenie czy dźwięk włączony????
jz get_keyinput_to_1 ;skok jeśli dźwięk wyłączony
mov cx,sound_time_m ;albo sprawdzenie aktualizacji dźwięku
mov ax,sound_time_l
call test_current_time
jc get_keyinput_to_1 ;skok jeśli niegotowe na aktualizację
mov bx,sound_table
mov ax,[bx]
or ax,ax ;test na koniec tabeli
jz turn_sound_off_to ;skok jeśli koniec tabeli danych
call get_time_plus_ax
mov sound_time_m,cx
mov sound_time_l,ax
inc bx
inc bx
mov ax,[bx]
inc bx
inc bx
mov sound_table,bx
call sound_out_ax
jmp get_keyinput_to_1
turn_sound_off_to:
call sound_off
mov sound_flag,0
jmp get_keyinput_to_1
get_keyinput_to endp
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;************ @@@@@@@@@@ ************
;### kod punktu wejścia #####
start_table_sound proc near
;podprogram dla startu wyjściowego dźwięku w tle
;na wejściu BX indeksuje tablice danych dźwięków
public start_table_sound
push ax
push bx
mov ax,[bx]
call get_time_plus_ax
mov sound_time_m,cx
mov sound_time_l,ax
inc bx
inc bx
mov ax,[bx]
inc bx
inc bx
mov sound_table,bx
call sound_out_ax
mov sound_flag,0FFH
pop bx
pop ax
ret
start_table_sound endp
;************ ========== *************
;### code entry point #####
flush_keyboard proc near
;narzędzie do wypłukania zawartości bufora klawiatury
public flush_keyboard
mov ah,1
int 16H ;Wywołanie BIOS
;skanowanie danych klawiaturowych
jz flush_keyboard_x ;skok jeśli brak danych klawiaturowych
mov ah,0 ;albo pobranie danych klawiaturowych
int 16H ;Wywołanie BIOS
jmp flush_keyboard
flush_keyboard_x:
ret
flush_keyboard endp
;************* ----------- **************
sound_out_ax proc near
;ustawienie częstotliwości dźwięku dla wartości danych w AX
push ax
push ax
cmp sound_on_flag,0
jne sound_out_1
in al,61H ;input port 61h
or al,3
out 61H,al ;output port 61h
sound_out_1:
mov al,0B6H
out 43H,al ;output port 43h
pop ax
out 42H,al ;output port 42h
xchg al,ah
out 42H,al ;output port 42h
mov sound_on_flag,0FFH
pop ax
ret
sound_out_ax endp
;*********** $$$$$$$$$$ ************
;###### kod punktu wejścia #######
sound_off proc near
;wyłączenie portu dźwięku
public sound_off
push ax
cmp sound_on_flag,0
je sound_off_exit
in al,61H ;input port 61h
and al,0FCH
out 61H,al ;output port 61h
mov sound_on_flag,0
sound_off_exit:
pop ax
ret
sound_off endp
;************** %%%%%%%%%% ***************
;ze wszystkimi wartościami czasu CX:AX, CX jest najbardziej znaczące
; a AX jest najmniej znaczące
get_current_time proc near
;na wyjściu CX:AX ma 32 bitową wartość dnia
; w 18.2 taktach na sekundę
push dx
xor ax,ax ;ustawiamy AH na zero
int 1AH ;Wywołanie BIOS Call pobrania czasu
mov ax,dx
pop dx
ret
get_current_time endp
;****************************
get_time_plus_ax proc near
;na wejściu AX ma 16 bitową wartość dla dodania do bieżącego zegara
;na wyjściu CX:AX ma nową 32 bitową wartość zegara
push dx
push ax
xor ax,ax
int 1AH ;Wywołanie BIOS
pop ax
add ax,dx
adc cx,0
pop dx
ret
get_time_plus_ax endp
;************ ######## ************
test_current_time proc near
;na wejściu CX:AX ma wartość czasu
;odejmowanego od bieżącego czasu
;na wyjściu jeśli ustawione przeniesienie wtedy bieżący czas
; jest mniejszy niż czas CX:AX
push dx
push cx
push ax
xor ax,ax
int 1AH ; Wywołanie BIOS
cmp dx,18
jb test_current_time_2
test_current_time_1:
pop ax
sub dx,ax
pop dx
sbb cx,dx
mov cx,dx
pop dx
ret
test_current_time_2:
or cx,cx
jnz test_current_time_1
pop ax ;to jest od poprawki dla czynnika północy
pop dx
pop dx
clc ;zerowanie warunku przeniesienia
ret
test_current_time endp
;*****************************************
end

Przykład Kodu : Interfejs znaku video
Ta część ma zbiór przykładów kodu dla pisania danych znakowych na PC , w trybie 80 znaków w 25 liniach .Pierwszy podprogram będzie sprawdzał tryb video, i jeśli jest to tryb standardowy, kod nie zwróci żadnego warunku przeniesienia i zacznie używać standardowego podprogramu BIOS dla wykonania żądanych funkcji. Te podprogramy dostarczają wiele punktów wejścia do kodu, gdzie inne programy mogą wywoływać. Przed użyciem dowolnego podprogramu video, program musi wywołać program reset_video dla zainicjowania i utworzenia gotowości wywołań video. Niektóre z podprogramów wyświetlania video będą odpowiadać na kod ucieczki. Większość kodów ucieczki działa ze standardową funkcją kontroli kursora .Mamy przykład tablicy wyboru kodów skoku. Wiele komputerów używa systemu grafiki rastrowej. Dana jest wysyłana do monitora jako wiersze od lewej do prawej i układane od góry do dołu. Pierwszy wysłany bit danej jest umieszczany w lewym górnym rogu a ostatni bit danej w ramce, przychodzi w dolnym prawym rogu. Kiedy adresujemy RAM video, pamięć może być podzielona na wiersze, które będą podzielone n a kolumny. Zrozumienie tego systemu jest konieczne kiedy tłumaczymy pozycję wierszy i kolumn na rzeczywisty adres video RAM dla położenia znaku lub pikseli
;ustawienie podprogramów wyświetlania video
.MODEL small
public reset_video
public clear_screen
public get_cursor_position, set_cursor_position
;zapis znaku w AL, używamy metody TTY
public write_to_screen
;zapis łańcucha indeksowanego przez SI używając TTY
public write_asciiz_string
;zapis znaku w AL i wyświetlenie kodu kontrolnego
public display_character
public save_screen, restore_screen
;ustawienie przez program aktywacji funkcji kodu ucieczki
public esc_flag
public normal_attribute
public scroll_screen_up, scroll_screen_down
public screen_buffer, cursor_port
.DATA
;zmienne danych video
even
cursor dw 0
save_cursor dw 0
cursor_port dw 3B4H
screen_buffer dw 2001 dup(0)
esc_flag db 0
esc_on_flag db 0
esc_y_flag db 0
esc_y_line db 0
video_hw_mode db 0
normal_attribute db 07H
;tablica skoków użyana dla kodów ESC
esc_jmp_table db "A"
dw write_esc_a
db "B"
dw write_esc_b
db "C"
dw write_esc_c
db "D"
dw write_esc_d
db "H"
dw write_esc_h
db "I"
dw write_esc_i
db "J"
dw write_esc_j
db "K"
dw write_esc_k
db "Y"
dw write_esc_y
db 0,0,0
.CODE
;****************** $$$$$$$$$$$$$ ******************
reset_video proc near
;ten podprogram musi być wywołany przed innymi
; funkcjami video aby uruchomić system video
;na wyjściu jeśli ustawione przeniesienie, wtedy tryb
;video mniejszy niż 80 kolumn
push ax
push bx
mov ah,15
int 10H ;sprawdzenie bieżącego trybu video
cmp ah,80
jae reset_video_80_b ;skok jeśli 80+ kolumn
;video mniejsze niż 80 kolumn
stc ;ustawienie dla warunku błędu na wyjściu
jmp short reset_video_exit_b
reset_video_80_b:
mov video_hw_mode,0FFH
mov normal_attribute,07H
clc
reset_video_exit_b:
pop bx
pop ax
ret
reset_video endp
;################################################
; ************** ############ ****************
clear_screen proc near
;czyszczenie ekranu używając wywołań
push ax
push cx
push dx
push bx
mov ax,600H
mov cx,0000H
mov dh,24
mov dl,79
mov bh,normal_attribute
int 10H
xor dx,dx
call set_cursor_position
pop bx
pop dx
pop cx
pop ax
ret
clear_screen endp
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;************** ^^^^^^^^^^^^ ***************
set_cursor_position proc near
;ustawienie kursora wywołaniem BIOS
;na wejściu mamy DX ustawiony na nową pozycję kursora
push ax
push bx
push dx
mov ah,2
mov cursor,dx
xor bx,bx
int 10H
pop dx
pop bx
pop ax
ret
set_cursor_position endp
;-----------------------------------------
;************ %%%%%%%%%%%%% ************
get_cursor_position proc near
;na wyjściu DX mamy bieżącą pozycję kursora
mov dx,cursor
ret
get_cursor_position endp
;__________________________________
;*********** -------- *************
write_esc_y_on:
cmp esc_y_flag,0FFH
jne write_esc_y_on1
sub al,20H
mov esc_y_line,al
mov esc_y_flag,0FH
jmp write_to_screen_x
write_esc_y_on1:
sub al,20H
mov dl,al
mov dh,esc_y_line
cmp dh,24
ja write_esc_y_er
cmp dl,79
ja write_esc_y_er
call set_cursor_position
write_esc_y_er:
mov esc_y_flag,0
mov esc_on_flag,0
jmp write_to_screen_x
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
write_esc_data:
cmp esc_y_flag,0
jne write_esc_y_on
lea bx,esc_jmp_table
write_esc_data_l:
cmp al,[bx]
je write_esc_data_jmp
add bx,3
cmp byte ptr[bx],0
jne write_esc_data_l
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_data_jmp:
mov bx,[bx+1] ;wykonanie Table Lookup Jump
jmp bx
;---------------------------------
;*********** %%%%%% ************
write_to_screen proc near
;na wejściu AL ma znak ASCII dla wyjścia TTY
; ten kod używa systemu wywołań BIOS dla funkcji wyświetlania
push ax
push bx
push cx
push dx
cmp esc_on_flag,0
jne write_esc_data
mov bl,normal_attribute
cmp al,20H
jb write_to_screen_c
write_to_screen_0:
mov cx,01
mov bh,0
mov ah,9
int 10H
mov dx,cursor
cmp dl,79
je write_to_screen_1
inc dl
call set_cursor_position
jmp short write_to_screen_x
write_to_screen_1:
mov dl,0
cmp dh,24
je write_to_screen_2
inc dh
call set_cursor_position
jmp short write_to_screen_x
write_to_screen_2:
call set_cursor_position
call scroll_screen_up
write_to_screen_x proc near
pop dx
pop cx
pop bx
pop ax
ret
write_to_screen_x endp
write_to_screen_c:
; sprawdzenie specjalnych kodów kontrolnych klawiatury
mov dx,cursor
cmp al,0DH
je write_to_screen_cr
cmp al,0AH
je write_to_screen_lf
cmp al,09H
je write_to_screen_tab
cmp al,0CH
je write_to_screen_ff
cmp al,08H
je write_to_screen_bs
cmp al,1BH
je write_to_screen_esc
jmp write_to_screen_0 ;skok jeśli nieznany kod
write_to_screen_cr:
mov dl,0
call set_cursor_position
jmp write_to_screen_x
write_to_screen_lf:
cmp dh,24
je write_to_screen_lf1
inc dh
call set_cursor_position
jmp write_to_screen_x
write_to_screen_lf1:
call scroll_screen_up
jmp write_to_screen_x
write_to_screen_tab:
and dl,0F8H
add dl,8
cmp dl,80
je write_to_screen_tab1
call set_cursor_position
jmp write_to_screen_x
write_to_screen_tab1:
mov dl,0
cmp dh,24
je write_to_screen_tab2
inc dh
call set_cursor_position
jmp write_to_screen_x
write_to_screen_tab2:
call set_cursor_position
call scroll_screen_up
jmp write_to_screen_x
write_to_screen_ff:
call clear_screen
jmp write_to_screen_x
write_to_screen_bs:
or dl,dl
jz write_to_screen_bsx
dec dl
call set_cursor_position
write_to_screen_bsx:
jmp write_to_screen_xv write_to_screen_esc:
cmp esc_flag,0
jne write_to_screen_esc_1
mov esc_on_flag,0FFH
jmp write_to_screen_x
write_to_screen_esc_1:
jmp write_to_screen_0
write_to_screen endp
;++++++++++++++++++++++++++++++++++++++++++++
;************* <<<<<< >>>>>> **************
;oto funkcje przetwarzania znaków ucieczki
write_esc_a proc near
;przeniesienie kursora w górę o jedną linię
mov dx,cursor
cmp dh,0
je write_esc_a_0
dec dh
call set_cursor_position
write_esc_a_0:
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_a endp
write_esc_b proc near
; przeniesienie kursora w dół o jedną linię
mov dx,cursor
cmp dh,24
je write_esc_b_0
inc dh
call set_cursor_position
write_esc_b_0:
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_b endp
write_esc_c proc near
;przeniesienie kursora w prawo o jeden znak
mov dx,cursor
cmp dl,79
je write_esc_c_0
inc dl
call set_cursor_position
write_esc_c_0:
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_c endp
write_esc_d proc near
;move cursor left one character
mov dx,cursor
cmp dl,0
je write_esc_d_0
dec dl
call set_cursor_position
write_esc_d_0:
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_d endp
write_esc_h proc near
;przeniesienie kursora do góry w lewą pozycję
xor dx,dx
call set_cursor_position
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_h endp
write_esc_i proc near
;przeniesienie kursora w górę z przewinięciem jeśli w górnej
mov dx,cursor
cmp dh,0
je write_esc_i_0
dec dh
call set_cursor_position
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_i_0:
call scroll_screen_down
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_i endp
write_esc_j proc near
;kasowanie od kursora do końca ekranu
push cx
push bx
mov ax,cursor
mov cx,79
sub cl,al
write_esc_j2:
cmp ah,24
je write_esc_j1
add cx,80
inc ah
jmp write_esc_j2
write_esc_j1:
jcxz write_esc_j3
mov bl,normal_attribute
mov al,20H
add cx,1
mov bh,0
mov ah,9
int 10H
write_esc_j3:
pop bx
pop cx
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_j endp
write_esc_k proc near
;kasowanie od kursor do końca linii
push cx
push bx
mov ax,cursor
mov cx,79
sub cl,al
jcxz write_esc_k1
mov bl,normal_attribute
mov al,20H
add cx,1
mov bh,0
mov ah,9
int 10H
write_esc_k1:
pop bx
pop cx
mov esc_on_flag,0
jmp write_to_screen_x
write_esc_k endp
write_esc_y proc near
;set cursor position
mov esc_y_flag,0FFH
jmp write_to_screen_x
write_esc_y endp
;_______________________________________________
;************ <<<<<<<<< >>>>>>>>>> ************
display_character proc near
;na wejściu AL ma znak dla wyjścia używając wywołań BIOS
push ax
push bx
push dx
push cx
mov bl,normal_attribute
mov ah,9
mov bh,0
mov cx,01
int 10H ;funkcja BIOS do wyświetlania
;reset pozycji kursora dla kolejnego znaku
mov dx,cursor
cmp dl,79 ;czy koiec linii ???
;skok jeśli koniec bieżącej linii
je display_character_1
inc dl ;indeks kolejnej pozycji kolumny
call set_cursor_position
;idź do podprogramu wyjścia
jmp short display_character_x
display_character_1:
mov dl,0 ;indeks linii startu
cmp dh,24 ;czy to ostatnia linia ????
;skok jeśli ostatnia linia na ekranie
je display_character_2
inc dh ;indeks kolejnej linii
call set_cursor_position
;idź do podprogramu wyjścia
jmp short display_character_x
display_character_2:
call set_cursor_position
;przewinięcie ekranu o jedną linię dla nowej lini
call scroll_screen_up
display_character_x:
;wyjście podprogramu
pop cx
pop dx
pop bx
pop ax
ret
display_character endp
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;************** ^^^^^^^^^^ ***************
write_asciiz_string proc near
;na wejściu ma SI indeksowane dane ASCIIZ
mov al,[si]
or al,al
jz write_asciiz_string_x
call write_to_screen
inc si
jmp write_asciiz_string
write_asciiz_string_x:
ret
write_asciiz_string endp
;|||||||||||||||||||||||||||||||||||||||||||
;*************** ######### ***************
scroll_screen_up proc near
;podprogram do przewijania ekranu video o jedną linię
push ax
push bx
push cx
push dx
mov ax,601H
xor cx,cx
mov dh,24
mov dl,79
mov bh,normal_attribute
int 10H
pop dx
pop cx
pop bx
pop ax
ret
scroll_screen_up endp
;*********** @@@@@@@ ************
scroll_screen_down proc near
;program do przewijania ekranu video w dół o jedną linię
;użycie funkcji BIOS dla przewinięcia ekranu w dół
push ax
push bx
push cx
push dx
mov ax,701H
xor cx,cx
mov dh,24
mov dl,79
mov bh,normal_attribute
int 10H
pop dx
pop cx
pop bx
pop ax
ret
scroll_screen_down endp
;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
;*********** &&&&& ***********
save_screen proc near
;podprogram do zapisu video RAN dla późniejszego wywołania
;ten program ma tylko jeden bufor video
; i może tylko zapisać jedną daną ekranową
;zapis bufora video funkcją BIOS
push ax
push bx
push dx
push si
push di
lea di,screen_buffer
mov si,cursor
mov save_cursor,si
xor dx,dx
save_screen_loop:
call set_cursor_position
mov ah,8
mov bh,0
int 10H
cld
stosw
inc dl
cmp dl,80
jb save_screen_loop
mov dl,0
inc dh
cmp dh,25
jb save_screen_loop
mov dx,si
call set_cursor_position
pop di
pop si
pop dx
pop bx
pop ax
ret
save_screen endp
;*********** ////// \\\\ ************
restore_screen proc near
;przywrócenie bufora video z zapisanego bufora video
push ax
push bx
push cx
push dx
push si
lea si,screen_buffer
xor dx,dx
restore_screen_loop:
call set_cursor_position
cld
lodsw
mov bl,ah
mov ah,9
mov bh,0
mov cx,1
int 10H
inc dl
cmp dl,80
jb restore_screen_loop
mov dl,0
inc dh
cmp dh,25
jb restore_screen_loop
mov dx,save_cursor
call set_cursor_position
pop si
pop dx
pop cx
pop bx
pop ax
ret
restore_screen endp
;+++++++++++++++++++++++++++
;***************************
end

CZĘŚĆ V : Interfejs graficzny

W tej części wyjaśnimy kilka podstawowych rzeczy o różnych standardach systemów wideo PC. Omówimy piksele, palety i pewne funkcje BIOS
Elementy obrazu
Pixel lub Pel są standardowymi odnośnikami do elementów obrazu. Pixel jest najmniejszym programowalnym elementem systemu wyświetlania. Liczba bitów użyta do zdefiniowania piksela określa liczbę możliwych kolorów jakie możesz wybrać przy programowaniu piksela. Jeśli jest użyty jeden bit, możesz wybierać między dwoma kolorami, jeśli dwa bity możesz wybierać między czterema kolorami, jeśli trzy bity możesz wybierać między ośmioma kolorami, jeśli cztery bity , to między szesnastoma kolorami itd. Współrzędne piksela są opisane przez kolumnę i wiersz od pozycji kolumny 0 i wiersza 0 ,będących pozycją kolumny najbardziej na lewo w najwyższej linii poziomej. Dla systemu 6490 kolumn i 200 wierszy,. Ostatnia pozycja w dolnym wierszu to kolumna 639 i wiersz 199.
Formaty RAM wideo
W niektórych systemach, bity określające piksel są wszystkie w pojedynczym bajcie. W innych systemach, bity określające piksel są rozmieszczona w wielu różnych bajtach, ile jest bitów dla piksela. Kiedy bity piksela są rozproszone w wielu bajtach, pamięć wideo jest zwykle podzielona na tak zwane płaszczyzny pamięci .W niektórych systemach każda płaszczyzna pamięci może być przypisana do określonego koloru
Wybór koloru i palety
W niektórych systemach, określony wzorzec bitowy dla piksela zawsze wyświetla się jako określony kolor. W pewnych systemach, określony wzorzec bitowy dla piksela jest używany jako indeks określonego rejestru palety. Każdy rejestr palety może być oprogramowany przez oprogramowanie dla wyboru między różnymi kolorami do wyświetlenia. Prze rejestrach palety, kolor obiektu na wyświetlaczu może być zmieniony, bez przerysowania obrazu obiektu na ekranie, przez zmianę rejestru palety używanej przez obraz obiektu .W systemie VGA, rejestr palety jest nazywany konwerterem cyfrowo-analogowym (DAC). Istniej 256 DAC′ów ,każdy szerokości 18 bitów . Te 18 bitów jest dzielonych na trzy grupy kolorów po sześć bitów danych dla każdego koloru. Pozwala to na 64 poziomy dla każdego koloru. Te trzy podstawowe kolory to czerwony, zielony i niebieski . Kiedy programista próbuje podjąć decyzję jakiego koloru użyć dla wyświetlania w programie, często wygodnie jest aby użytkownik wybrał wszystkie znaki i atrybuty koloru aby rozwiązać wszelkie niespójności między różnymi systemami.
Monochrome Display Adapter (MDA)
Ten system wideo nie ma trybu wysokiej rozdzielczości. System wyświetla 80 znaków na 25 wierszy i wymaga dwóch bajtów RAM wideo na znak. Jedne bajt jest używany do wyboru z 256 możliwych standardowych znaków ASCII do wyświetlenia. Drugi bajt wybiera atrybut znaku
Color Graphics Adapter (CGA)
Ten system wideo wspiera tryb wyświetlania znaków i tryb wyświetlania grafiki. Tryb wyświetlania znaków to 80 znaków na 25 wierszy i 40 znaków na 25 wierszy. Tryb wyświetlania graficznego to 640 kolumny na 200 wierszy z dwoma kolorami i 320 kolumn na 200 wierzy w czterema kolorami. IBM PCJR ma system graficzny, który jest podobny do CGA ale jest umieszczony pod innym adresem i dostarcza więcej kolorów w trybie wysokiej rozdzielczości. Adresowanie bezpośrednie RAW wideo w starszym systemie CGA może powodować efekt śnieżenia
Multicolor Graphics Array (MCGA)
Ten system wideo ma te same podstawowe tryby jak CHA ale dostarcza 256 kolorów w 320 kolumnach na 200 wierszy i dwóch kolorów w trybie 640 kolumn i 480 wierszy. System ten również używa rejestrów palety kolorów.
Enhanced Graphics Adapter (EGA)
Ten system wspiera tryb wyświetlania znakowego i tryb wyświetlania graficznego. Tryb znakowy to 80 znaków na 25 wierszy, 40 znaków na 25 wierszy i 80 znaków na 43 wiersze. System dostarcza między 2 a 16 kolorów w 640 kolumnach na 350 wierszy. Więcej RAM na płytce EGA, więcej kolorów do wyboru
Professional Graphics Adapter (PGA)
Ten system wideo dostarcza trybu graficznego w 640 kolumnach na 480 wierszy z 256 kolorami poza zbiorem 12 bitowych rejestrów palety . System dostarcza wysokopoziomowych funkcji graficznych
Funkcja wideo INT 10H
Systemowe przerwanie programowe 10H jest używane dla funkcji wideo BIOS. Tu omówimy pobieranie trybu CRT, ustawianie trybu CRT, zapis i odczyt piksela. Aby ustawić tryb wyświetlania CRT, musimy ustawić AH na 0 a AL ustawiamy kodem trybu wyświetlania, potem wykonujemy przerwanie 10H. Poniże mam wartości kodu szesnastkowo dla standardowych trybów graficznych CRT i powiązanych rozdzielczości Kody te są używane dla pobierania i ustawiania funkcji trybu wyświetlania
04H : 320 na 200, 4 kolory
05H : 320 na 200, 4 kolory monochromatyczne
06H : 640 na 200, 2 kolory
0DH : 320 na 200, 16 kolorów
0EH : 640 na 200, 16 kolorów
0FH : 640 na 350, 4 kolory
10H : 640 na 350, 16 kolorów
11H : 640 na 480, 2 kolory
12H : 640 na 480, 16 kolorów
13H : 320 na 200, 256 kolorów
Dla sprawdzenia bieżącego trybu wyświetlania wideo z programu, funkcja wideo BIOS wywołuje 15 aby pobrać tryb CRT jaki może być użyty. Dla wywołania funkcji rejestr AH ustawiamy na 15 i wykonujemy przerwanie 10H. Na wyjściu z przerwania, rejestr AL ma bieżący tryb CRT, rejestr AH ma liczbę kolumn na ekranie, a rejestr BH ma numer bieżącej aktywnej strony. Ta funkcja może być użyta po ustawieniu trybu CRT dla zweryfikowania ,czy system używa poprawnego trybu. Aby zapisać nową wartość piksela do wyświetlenia, na wejściu mamy rejestr AH ustawiony na 0CH, rejestr DX ustawiony na liczbę wierszy, rejestr CX ustawiony na liczbę kolumn, rejestr AL ustawiony na daną wartości koloru, a rejestr BH ustawiony na numer wyświetlanej strony (dla grafiki ta wartość jest zwykle ustawiana na 0). Wykonanie przerwania 0H wywołuje wywołanie funkcji. Aby odczytać bieżącą wartość piksela, na wejściu mamy rejestr AH ustawiony na 0DH, rejestr DX ustawiony na liczbę wierszy, rejestr CX ustawiony na liczbę kolumn a rejestr BH ustawiony na numer wyświetlanej strony (zwykle wartość 0). Po powrocie z przerwania 10H ,rejestr AL ma w wartość koloru piksela.
Poniższy przykładowy kod pokazuje jak narysować linię poziomą w trybie graficznym
.CODE
;kod rysowania linii
draw_line proc near c, line_color:BYTE, line_length:WORD, line_row_start:WORD, line_col_start:WORD
mov AL,line_color
mov DX,line_row_start
mov CX,line_col_start
mov BX,0 ;wyświetlanie strony
mov SI,line_length
mov AH,12
draw_line_loop:
push AX
push BX
push CX
push DX
push SI
int 10H ;funkcja video BIOS
pop SI
pop DX
pop CX
pop BX
pop AX
inc CX ; wskazanie kolejnej linii pozycji piksela
dec SI ;dostosowanie licznika długości linii
jnz draw_line_loop
ret
draw_line endp

CZĘŚĆ VI : Matematyka komputerowa

Dodawanie danych
Są dwie podstawowe całkowite instrukcje dodawania : standardowa ADD i ADC (dodawanie z przeniesieniem). Zwykle, używana jest standardowe dodawanie dla binarnych liczb całkowitych, ale przy pewnych kodach wymagane jest dodawanie z przeniesieniem .Zarówno instrukcja ADD jak i ADC modyfikują zawartość flagi przeniesienia wskazując przepełnienie dodawania poza pozycję najwyższego bitu, ale ADC będzie sprawdzać zawartość flagi przeniesienia na początku instrukcji i doda dodatkową 1 do dwóch operandów dodawanych jeśli warunek przeniesienia jest prawdziwy.
;dodajemy niższą połówkę 32 bitowej danej do AX
add ax,datalow
; dodajemy górną połówkę 32 bitowej danej do DX
adc dx,datahigh
;skok jeśli przepełnienie przeniesienia
jc carryover
Odejmowanie danych
Mamy dwie podstawowe instrukcje odejmowania : standardowe SUB i SBB (odejmowanie z pożyczką). Zwykle, dla małych liczb , używamy SUB dla standardowego odejmowania binarnych liczb całkowitych, ale w podprogramach ,które obsługują odejmowanie dużych liczb, stosujemy .Warunek pożyczki jest zarządzany z flagi przeniesienia
;odejmujemy niższą połówkę 32 bitowej danej od AX
sub ax,datalow
; odejmujemy wyższą połówkę 32 bitowej danej od DX z pożyczką
sbb dx,datahigh
Mnożenie danych
Istnieją dwie podstawowe instrukcje mnożenia : mnożenia liczb całkowitych bez znaku (MUL) i mnożenie liczb całkowitych ze znakiem (IMUL). Mnożenie może być w formacie 8- albo 16 bitowym. Dla mnożenia 8 bitowego, AL. musi przechowywać jedną zmienną danej. Druga zmienna danej może pochodzić z rejestru lub pamięci. 16 bitowy wynik będzie umieszczony w AX. Dla mnożenia 16 bitowego, AX musi przechowywać jedną zmienną danej. Druga pochodzi z rejestru lub pamięci. 32 bitowy wynik będzie umieszczony w DX:AX z DX przechowującym najbardziej znaczącą daną a AX przechowującym najmniej znaczącą daną
;mnożymy BX * AX = DX:AX
MUL BX
;mnożymy daną z akumulatora
IMUL data_var
Zauważ ,że jeśli data_var jest bajtem, kompilator będzie generował kod dla instrukcji mnożenia osiem bitów przez osiem bitów; jeśli jest słowem, wtedy wygeneruje instrukcję mnożenia 16 bitów przez 16 bitów
Dzielenie danych
Są dwie podstawowe instrukcje dzielenia : dzielenie liczb całkowitych bez znaku (DIV) i dzielenie liczb całkowitych ze znakiem (IDIV). Można dzielić 16 bitową liczbę przez liczbę 8 bitową lub 32 bitową liczbę przez liczbę 16 bitową .Dla małego dzielenia, rejestr AX musi przechowywać liczbę 16 bitową. Liczba 8 bitowa która jest używana dla małego dzielenia może pochodzić z rejestru lub pamięci. Wynik będzie w AL. z modułem z dzielenia w AH. Dla liczb dużych, DX:AX przechowuje 32 bitową daną z AX przechowującym najmniej znaczące bity. 16 bitowa dana używana dla dzielenia pochodzi z rejestru lub pamięci, Główny wynik będzie wstawiony do AX a DX będzie przechowywał moduł danych wynikowych z dzielenia
;dzielimy DX:AX przez BX = AX modulo DX
DIV bx
;dzielimy używając liczby ze znakiem AX by databyte = AL modulo AH
IDIV databyte
Mnożenie przez przesunięcie w lewo
Przesunięcie liczby w lewo o jedną pozycję bitu daje efekt mnożenia liczby przez dwa. Przesunięcie liczby w lewo o dwie pozycje bitu daje efekt mnożenia liczby przez cztery. Przesunięcie liczby w lewo o trzy pozycje bitu daje efekt mnożenia liczby przez osiem. Każde przesunięcie podwaja bieżącą wartość liczby binarnej. Przykład
mov ax,01 ;ładujemy 1 do ax
shl ax,1 ;ax ma teraz 10B lub 2
mov dx,ax ;zapisujemy wartość
shl ax,1 ;ax ma teraz 100B lub 4
shl ax,1 ;ax ma teraz 1000B lub 8
add ax,dx ;ax ma teraz 1010B lub 10
shl ax,1 ;ax ma teraz 10100B lub 20
Zwiększanie i zmniejszanie
Instrukcja INC jest szybki sposobem dodawania jedynki do pamięci lub rejestru. Jest używana w wielu podprogramach zliczania. Instrukcja DEC jest szybki sposobem odejmowania jedynki od rejestru lub pamięci
;dodajemy do akumulatora
inc ax
;odejmujemy 1 od danej w komórce pamięci
dec data
Koprocesor 80x87
Jest to bardzo mocna jednostka matematyczna. Jest opcjonalnym procesorem matematycznym dostępny w wielu PC. 80x87 ma osiem wewnętrznych rejestrów danych dla przetwarzania funkcji matematycznych, które są oddzielone do standardowych rejestrów 80x86. Każdy z ośmiu rejestrów 80x87 jest szerokości 80 bitów. Wszystkie dane wewnątrz 80x87 są obsługiwane w tym samym formacie 80 bitowej liczby rzeczywistej dla funkcji matematycznych. System 80x87 używa systemu wskaźnika stosu dla indeksowania wewnętrznych rejestrów danych. Rejestry danych 80x87 są adresowane jako względne do ich bieżącej pozycji na stosie Stos działa w ruchu kołowym od indeksowania rejestru danych 0 do rejestru danych 7 i z powrotem do rejestru danych 0. Dana na górze stosu jest określana jako ST(0), jeśli zdejmujemy daną ze stosu, wtedy ST(1) staje się ST(0). Jeśli odkładamy daną na stos, wtedy ST(0) staje się ST(1). 80x87 jest zaprojektowany do pracy równoległej z 80x86.

CZĘŚĆ VII : Współpraca z językiem C

Tu omówimy sposób w jaki kod asemblerowy współpracuje z programami w języku C. Wyjaśnia niektóre z wewnętrznych działań języka C i pewne podstawowe o skompilowanym obszarze danych programu w języku C. Ważne jest zrozumienie tych pojęć aby pisać procedury w asemblerze, które mogą współpracować efektywnie z programem języka C System Microsoft Macro Assembler dostarcza wielu funkcji dla pisania kodu w języku asemblera współpracującego z kodem języka C i innych języków wysokopoziomowych. System MASM pozwala na łatwy rozwój kodu asemblera , który łączy się z programem języka C
Obszar danych języka C
Dane dla funkcji i podprogramów kodu C są oparte na strukturach sterty i stosu. Aby wyjaśnić to w prosty sposób, załóżmy ,że program jest przypisany do jednego ,ciągłego bloku pamięci wykorzystywanego do realizacji. Kod jest ładowany do najniższej przestrzeni adresowej bloku pamięci. Dane sterty są ładowane bezpośrednio powyżej tego kodu w pamięci niższej i rozszerzane jeśli to konieczne do większej przestrzeni danych sterty. Dane stosu zaczynają się na górze bloku pamięci i rozszerzone w dół ku danych sterty, jeśli potrzebne jest więcej miejsca na stosie. Miejmy nadzieję ,że dane stosu i sterty nie zbiegają się , ponieważ to może spowodować awarię programu. Ogólną metodą dostępu do danych przekazywanych do programu ze standardowej funkcji C jest użycie rejestru BP. Poniższy segment kodu pokazuje jak zdefiniować BP na początku funkcji kodu języka asemblerowego i adresować parametry przekazywane z programu w języku C
.MODEL small
.CODE
PUBLIC _Test_Asm
_Test_Asm PROC
push bp ;zachowujemy starą wartość BP
mov bp,sp ;ustawiamy BP na indeks lokalnej danej
mov ax,[bp+4] ;ładujemy AX parametrem 1
add ax,[bp+6] ;dodajemy parametr 2 do AX
;wyjście z procedury
pop bp ;przywrócenie starej wartości BP
ret ;wyjście do program C z AX = P1 + P2
_Test_Asm ENDP
END
Tu mamy przykład podprogramu w języku C dla wykonania funkcji asemblerowej _Test_Asm extern "C" { int Test_Asm( int, int); }
main() {
int sum;
Sum = Test_Asm(1, 2)
}
Modele pamięci
Kiedy programy stają się duże i albo segment kodu, stosu lub danych rosną powyżej 64KB, wtedy pojawiają się pewne problemy. Z tego powodu system MASM dostarcza kilku podstawowych modeli pamięci .Są to : tiny, small, medium, compact, large, huge i flat. Podstawowe różnice między różnymi modelami pamięci zależą od założenia na temat danych będących adresowanymi, z bliskim lub dalekim i kodu podprogramu będącego wywoływanym bliskim lub dalekim podprogramem. Wiele z problemów jest rozwiązywanych przez specjalną funkcję kompilatora dostarczaną w systemie Microsoft C. Poniżej jest lista typów danych C i powiązań z typami danych asemblerowych
char : bajt or sbyte
unsigned char : bajt
signed char : sbyte
short : sword
unsigned short : word
int : sword
unsigned int : word
long : sdword
unsigned long : dword
float : real4
double : real8
long double : real10
int *datapt : ptr sword
int far *datapt : far ptr sword
Poniżej mamy listę typów danych zwracanych przez funkcje języka C i rejestry jeżyka asemblera, które używają tych typów danych
unsigned char : AL
char : AL
unsigned short : AX
short : AX
unsigned int : AX
int : AX
unsigned long : DX:AX
long : DX:AX
float : PTR AX lub PTR DX:AX
double : PTR AX lub PTR DX:AX
long double : PTR AX lub PTR DX:AX
Kompilowanie razem C i asemblera
Microsoft Linker dostarcza wygodnego sposobu na połączenie skompilowanych programów języka C z podprogramami asemblerowymi. Programista może połączyć te dwa pliki obiektowe programu razem pojedynczą instrukcją jak poniżej:
LINK źródło1 + źródło2;
Aby użyć tej metody, musimy połączyć kod Asemblera opcją /c w linii poleceń ML. Pliki źródło1 i źródło2 musi mieć rozszerzenie nazwy pliku .OBJ
Znak podkreślenia i konwencje nazewnicze
Kod języka asemblera musi dodawać znak podkreślenia na początku nazwy funkcji będącej wywoływaną z programu języka C. Na przykład, jeśli program C tworzy wywołanie do funkcji asemblerowej nazwanej asm_fun w programie C, wtedy program asemblerowy powinien używać nazwy _asm_fun dla kodu asemblerowego. Nazwa funkcji języka asemblera, jaką musi wywołać program C , musi uwzględniać wielkość liter. Opcja kompilatora /ml może być użyta dla uczynienia programu asemblerowego uwzględniającego wielkość liter. Nazwa funkcji języka asemblerowego musi być zadeklarowana jako PUBLIC