Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

Asembler - Podstawy


Rekomendowane odpowiedzi

Opublikowano

Część 1 - Podstawy, czyli czym to się je.

 

 

Wyobraźcie sobie, jakby to było móc programować maszynę bezpośrednio, czyli "rozmawiać" z procesorem bez pośrednictwa struktur wysokiego poziomu, np. takich jak spotykamy w języku C. Bezpośrednie operowanie na procesorze umożliwia przecież pełną kontrolę jego działań! Bez zbędnych instrukcji i innych "śmieci" spowalniających nasze programy.

 

Czy już czujecie chęć pisania najkrótszych i najszybszych programów na świecie?

Programów, których czasem w ogóle NIE MOŻNA napisać w innych językach? Brzmi wspaniale, prawda?

 

Tylko pomyślcie o tym, co powiedzieliby znajomi, gdybyście się im pochwalili. Widzicie już te ich zdumione miny?

 

Miła perspektywa, prawda? No, ale dość już gadania. Zabierajmy się do rzeczy!

 

Zacznijmy od krótkiego wprowadzenia:

 

Niedziesiętne systemy liczenia

 

1. Dwójkowy (binarny)

 

Najprostszy dla komputera, gdzie coś jest albo włączone, albo wyłączone. System ten operuje na liczbach zwanych bitami (bit = binary digit = cyfra dwójkowa). Bit przyjmuje jedną z dwóch wartości: 0 lub 1.

Na bajt składa się 8 bitów. Jednym bajtem można przedstawić więc 2^8=256 możliwości.

 

Przeliczenie liczby zapisanej w systemie dwójkowym na dziesiętny jest proste. Podobnie jak w systemie dziesiętnym, każdą cyfrę mnożymy przez odpowiednią potęgę podstawy (podstawa wynosi 2 w systemie dwójkowym, 10 w systemie dziesiętnym).

 

Oto przykład (niech daszek ^ oznacza potęgowanie):

1010 1001 dwójkowo =

1*(2^7) + 0*(2^6) + 1*(2^5) + 0*(2^4) + 1*(2^3) + 0*(2^2) + 0*(2^1) + 1*(2^0) =

128 + 32 + 8 + 1 =

169 dziesiętnie (lub "dec", od "decimal").

 

Działanie odwrotne też nie jest trudne: naszą liczbę dzielimy ciągle (do chwili uzyskania ilorazu równego 0) przez 2, po czym zapisujemy reszty z dzielenia wspak:

(przeskocz konwersję liczby dziesiętnej na dwójkową)

 

169 |

84 | 1

42 | 0

21 | 0

10 | 1

5 | 0

2 | 1

1 | 0

0 | 1

 

Wspak dostajemy: 1010 1001, czyli wyjściową liczbę.

 

2. Szesnastkowy (heksadecymalny, w skrócie hex)

 

Jako że system dwójkowy ma mniej cyfr niż dziesiętny, do przedstawienia względnie małych liczb trzeba użyć dużo zer i jedynek. Jako że bajt ma 8 bitów, podzielono go na dwie równe, 4-bitowe części. Teraz bajt można już reprezentować dwoma znakami, a nie ośmioma. Na każdy taki znak składa się 2^4=16 możliwości. Stąd wzięła się nazwa "szesnastkowy".

Powstał jednak problem: cyfr jest tylko 10, a trzeba mieć 16. Co zrobić?

Postanowiono liczbom 10-15 przyporządkować odpowiednio znaki A-F.

Np.

Liczba 255 dziesiętnie = 1111 1111 binarnie = FF szesnastkowo (1111 bin = 15 dec = F hex)

Liczba 150 dziesiętnie = 1001 0110 binarnie = 96 szesnastkowo.

 

Należy zauważyć ścisły związek między systemem dwójkowym i szesnastkowym: 1 cyfra szesnastkowa to 4 bity, co umożliwia błyskawiczne przeliczanie między obydwoma systemami: wystarczy "tłumaczyć" po 4 bity (1 cyfrę hex) na raz i zrobione.

 

Przeliczenie liczby zapisanej w systemie szesnastkowym na dziesiętny jest równie proste, jak tłumaczenie z dwójkowego na dziesiętny. Każdą cyfrę mnożymy przez odpowiednią potęgę podstawy (podstawa wynosi 16 w systemie szesnastkowym).

 

Oto przykład:

10A szesnastkowo =

1*16^2 + 0*16^1 + A*16^0 =

256 + 0 + 10 =

266 dziesiętnie.

 

Działanie odwrotne też nie jest trudne: naszą liczbę dzielimy ciągle (do chwili uzyskania ilorazu=0) przez 16, po czym zapisujemy reszty z dzielenia wspak:

(przeskocz konwersję liczby dziesiętnej na szesnastkową)

 

266 |

16 | 10

1 | 0

0 | 1

 

Wspak dostajemy kolejno: 1, 0 i 10, czyli 10A, czyli wyjściową liczbę.

 

Podczas pisania programów, liczby w systemie szesnastkowym oznacza się przez dodanie na końcu litery h (lub z przodu 0x), a liczby w systemie dwójkowym - przez dodanie litery b.

Tak więc, 101 oznacza dziesiętną liczbę o wartości 101, 101b oznacza liczbę 101 w systemie dwójkowym (czyli 5 w systemie dziesiętnym), a 101h lub 0x101 oznacza liczbę 101 w systemie szesnastkowym (czyli 257 dziesiętnie).

 

Język asembler i rejestry procesora

 

Co to w ogóle jest asembler?

 

Asembler jest to język programowania, należący do języków niskiego poziomu. Znaczy to tyle, że jednej komendzie asemblera odpowiada dokładnie jeden rozkaz procesora. Asembler operuje na rejestrach procesora.

 

A co to jest rejestr procesora?

Rejestr procesora to zespół układów elektronicznych, mogący przechowywać informacje (taka własna pamięć wewnętrzna procesora).

Zaraz podam Wam podstawowe rejestry, na których będziemy operować. Wiem, że ich ilość może przerazić, ale od razu mówię, abyście NIE uczyli się tego wszystkiego na pamięć! Najlepiej zrobicie, czytając poniższą listę tylko 2 razy, a potem wracali do niej, gdy jakikolwiek rejestr pojawi się w programach, które będę później prezentował w ramach tego kursu.

Oto lista interesujących nas rejestrów:

 

1. ogólnego użytku:

 

* akumulator:

RAX (64 bity) = EAX (młodsze 32 bity) + starsze 32 bity,

EAX (32 bity) = AX (młodsze 16 bitów) + starsze 16 bitów,

AX = AH (starsze 8 bitów) + AL (młodsze 8 bitów)

Rejestr ten najczęściej służy do wykonywania działań matematycznych, ale często w tym rejestrze lub jego części (AX lub AH) będziemy mówić systemowi operacyjnemu i BIOS-owi, co od niego chcemy.

 

* rejestr bazowy:

RBX (64 bity) = EBX (młodsze 32 bity) + starsze 32 bity,

EBX (32 bity) = BX (młodsze 16 bitów) + starsze 16 bitów,

BX = BH (starsze 8 bitów) + BL (młodsze 8 bitów)

Ten rejestr jest używany np. przy dostępie do tablic.

 

* licznik:

RCX (64 bity) = ECX (młodsze 32 bity) + starsze 32 bity,

ECX (32 bity) = CX (młodsze 16 bitów) + starsze 16 bitów,

CX = CH (starsze 8 bitów) + CL (młodsze 8 bitów)

Tego rejestru używamy np. do określania ilości powtórzeń pętli.

 

* rejestr danych:

RDX (64 bity) = EDX (młodsze 32 bity) + starsze 32 bity,

EDX (32 bity) = DX (młodsze 16 bitów) + starsze 16 bitów,

DX = DH (starsze 8 bitów) + DL (młodsze 8 bitów)

W tym rejestrze przechowujemy adresy różnych zmiennych. Jak wkrótce zobaczymy, do tego rejestru będziemy wpisywać adres napisu, który będziemy chcieli wyświetlić.

 

* rejestry dostępne tylko w trybie 64-bitowym:

o 8 rejestrów 8-bitowych: R8B, ..., R15B

o 8 rejestrów 16-bitowych: R8W, ..., R15W

o 8 rejestrów 32-bitowych: R8D, ..., R15D

o 8 rejestrów 64-bitowych: R8, ..., R15

 

 

* rejestry indeksowe:

o indeks źródłowy:

RSI (64 bity) = ESI (młodsze 32 bity) + starsze 32 bity,

ESI (32 bity) = SI (młodsze 16 bitów) + starsze 16 bitów,

SI (16 bitów) = SIL (młodsze 8 bitów) + starsze 8 bitów (tylko tryb 64-bit)

 

o indeks docelowy:

RDI (64 bity) = EDI (młodsze 32 bity) + starsze 32 bity,

EDI (32 bity) = DI (młodsze 16 bitów) + starsze 16 bitów,

DI (16 bitów) = DIL (młodsze 8 bitów) + starsze 8 bitów (tylko tryb 64-bit)

Rejestry indeksowe najczęściej służą do operacji na długich łańcuchach danych, w tym napisach i tablicach.

 

* rejestry wskaźnikowe:

o wskaźnik bazowy:

RBP (64 bity) = EBP (młodsze 32 bity) + starsze 32 bity,

EBP (32 bity) = BP (młodsze 16 bitów) + starsze 16 bitów.

BP (16 bitów) = BPL (młodsze 8 bitów) + starsze 8 bitów (tylko tryb 64-bit)

Najczęściej służy do dostępu do zmiennych lokalnych.

 

o wskaźnik stosu:

RSP (64 bity) = ESP (młodsze 32 bity) + starsze 32 bity,

ESP (32 bity) = SP (młodsze 16 bitów) + starsze 16 bitów.

SP (16 bitów) = SPL (młodsze 8 bitów) + starsze 8 bitów (tylko tryb 64-bit)

Służy do dostępu do stosu (o tym nieco później).

 

o wskaźnik instrukcji:

RIP (64 bity) = EIP (młodsze 32 bity) + starsze 32 bity,

EIP (32 bity) = IP (młodsze 16 bitów) + starsze 16 bitów.

Mówi procesorowi, skąd ma pobierać instrukcje do wykonywania.

 

2. rejestry segmentowe (wszystkie 16-bitowe):

 

* segment kodu CS - mówi procesorowi, gdzie znajdują się dla niego instrukcje.

* segment danych DS - ten najczęściej pokazuje na miejsce, gdzie trzymamy nasze zmienne.

* segment stosu SS - dzięki niemu wiemy, w którym segmencie jest nasz stos. O tym, czym w ogóle jest stos, powiem w następnej części.

* segment dodatkowy ES - często używany, gdy chcemy coś napisać lub narysować na ekranie bez pomocy Windows, DOSa czy nawet BIOSu.

* FS i GS (dostępne dopiero od 80386) - nie mają specjalnego przeznaczenia. Są tu na wypadek, gdyby zabrakło nam innych rejestrów segmentowych.

 

3. rejestr stanu procesora: FLAGI (16-bitowe), E-FLAGI (32-bitowe) lub R-FLAGI (64-bitowe).

Służą one przede wszystkim do badania wyniku ostatniej operacji (np. czy nie wystąpiło przepełnienie, czy wynik jest zerem, itp.). Najważniejsze flagi to CF (carry flag - flaga przeniesienia), OF (overflow flag - flaga przepełnienia), SF (sign flag - flaga znaku), ZF (zero flag - flaga zera), IF (interrupt flag - flaga przerwań), PF (parity flag - flaga parzystości), DF (direction flag - flaga kierunku).

 

Użycie litery "R" przed symbolem rejestru, np. RCX, oznacza rejestr 64-bitowy, dostępny tylko na procesorach 64-bitowych.

Użycie litery "E" przed symbolem rejestru, np. EAX, oznacza rejestr 32-bitowy, dostępny tylko na procesorach rodziny 80386 lub lepszych. Nie dotyczy to rejestru ES.

 

Napisy

 

RAX = EAX+starsze 32 bity; EAX=AX + starsze 16 bitów; AX=AH+AL

 

oznaczają takie zależności między tymi rejestrami:

 

 

RAX (64 bity)

| EAX (32b)

00000000000000000000000000000000 | 0000000000000000 | 00000000 | 00000000

32b | 16b | AX(16b)

| | AH(8b) | AL(8b)

 

Napisy

 

RSI = ESI + starsze 32 bity; ESI = SI + starsze 16 bitów; SI = SIL+starsze 8 bitów

 

oznaczają:

 

RSI (64 bity)

| ESI (32b)

00000000000000000000000000000000 | 0000000000000000 | 00000000 | 00000000

32b | 16b | SI(16b)

| | 8b | SIL(8b)

 

 

 

Tak, w DOSie można używać rejestrów 32-bitowych (o ile posiada się 80386 lub nowszy). Można nawet 64-bitowych, jeśli tylko posiada się właściwy procesor.

 

Jedna ważna uwaga - między nazwami rejestrów może pojawić się dwukropek w dwóch różnych znaczeniach:

 

* zapis "DX : AX" (lub 2 dowolne zwykłe rejestry) będzie oznaczać liczbę, której starsza część znajduje się w rejestrze po lewej stronie (DX), a młodsza - w tym z prawej (AX). Wartość liczby wynosi DX*65536 + AX.

* zapis "CS : SI" (rejestr segmentowy + dowolny zwykły) będzie najczęściej oznaczać wskaźnik do jakiegoś obiektu w pamięci (o pamięci opowiem następnym razem). Rejestr segmentowy zawiera oczywiście segment, w którym znajduje się ów obiekt, a rejestr zwykły - offset (przesunięcie, adres w tym segmencie) tegoż obiektu.

 

Na razie nie musicie się przejmować tymi dwukropkami. Mówię to tylko dlatego, żebyście nie byli zaskoczeni, gdyż w przyszłości się pojawią.

 

Programista może odnosić się bezpośrednio do wszystkich wymienionych rejestrów, z wyjątkiem *IP oraz flag procesora (z wyjątkami).

 

Jak widać po ich rozmiarach, do rejestrów 8-bitowych można wpisać liczbę z przedziału 0-255 (lub od -128 do 127, gdy najwyższy, siódmy bit służy nam jako bit oznaczający znak liczby), w 16-bitowych zmieszczą się liczby 0-65535 (od -32768 do 32767), a w 32-bitowych - liczby od 0 do 4.294.967.295 (od -2.147.483.648 do 2.147.483.647)

 

Dobrym, choć trudnym w odbiorze źródłem informacji są: Intel Architecture Software Developer's Manual (IASDM) dostępny ZA DARMO ze stron Intela oraz DARMOWE podręczniki AMD64 Architecture Programmer's Manual firmy AMD

 

 

 

Pisanie i kompilowanie (asemblowanie) swoich programów

 

Jak pisać programy w asemblerze?

 

Należy zaopatrzyć się w:

 

* Edytor tekstu, mogący zapisywać pliki formatu TXT (bez formatowania), np. Programmer's File Editor, Quick Editor, The Gun (wszystkie są na www.movsd.com) czy zwykły Notatnik

* Kompilator języka asembler (patrz dalej)

* Odpowiedni program łączący (kosolidator, ang. linker), chyba że kompilator ma już taki wbudowany, jak np. A86, NASM lub FASM (patrz dalej)

 

Wtedy wystarczy napisać w edytorze tekstu plik zawierający komendy procesora (o tym później), zapisać go z rozszerzeniem .ASM, po czym użyć kompilatora, aby przetworzyć program na kod rozumiany przez procesor.

Jakiego kompilatora użyć?

 

Istnieje wiele kompilatorów języka asembler. Do najpopularniejszych należą Turbo asembler firmy Borland, Microsoft Macro asembler (MASM), Netwide asembler Project (NASM), A86/A386, NBASM, FASM, HLA.

 

Można je ściągnąć z internetu:

(przeskocz adresy stron kompilatorów)

 

* Strona główna NASMa: sf.net/projects/nasm (Najbardziej polecam używać właśnie TEGO kompilatora - Programerus)

* A86 z eji.com

* Flat asembler (FASM): flatasembler.net

* MASM z Webster.cs.ucr.edu lub z www.movsd.com (wersje 32-bitowe)

* HLA Webster.cs.ucr.edu

 

Po skompilowaniu pliku z kodem źródłowym należy użyć programu łączącego, dostępnego zwykle z odpowiednim kompilatorem (np. tlink z tasm, link z masm). Mamy więc już wszystko, co potrzeba. Zaczynamy pisać. Będę tutaj używał składni Turbo asemblera zgodnego z MASMem oraz FASMa i NASMa.


; wersja TASM
.model tiny
.code
org 100h

start:
	mov	ah, 9
	mov	dx, offset info
	int	21h

	mov	ah, 0
	int	16h

	mov	ax, 4C00h
	int	21h

info	db	"Czesc.$"

end start

Teraz wersja NASM:

; wersja NASM

; nie ma ".model" ani ".code"
; tu można wstawić:
; section .text
; aby dać znać NASMowi, że to będzie w sekcji kodu.
; Nie jest to jednak wymagane, bo to jest sekcja domyślna.

org 100h

start:				; nawet tego NASM nie wymaga
	mov	ah, 9
	mov	dx, info	; nie ma słowa "offset"
	int	21h

	mov	ah, 0
	int	16h

	mov	ax, 4C00h
	int	21h

info	db	"Czesc.$"

; nie ma "end start"

Teraz wersja FASM

; wersja FASM

format binary

; nie ma ".model" ani ".code"
org 100h

start:				; nawet tego FASM nie wymaga
	mov	ah, 9
	mov	dx, info	; nie ma słowa "offset"
	int	21h

	mov	ah, 0
	int	16h

	mov	ax, 4C00h
	int	21h

info	db	"Czesc.$"

; nie ma "end start"

Bez paniki! Teraz omówimy dokładnie, co każda linia robi.

 

* linie lub napisy zaczynające się średnikiem

 

Traktowane są jako komentarze i są całkowicie ignorowane przy kompilacji. Rozmiar skompilowanego programu wynikowego nie zależy od ilości komentarzy. Dlatego najlepiej wstawiać tyle komentarzy, aby inni (również my) mogli później zrozumieć nasz kod.

 

* .model tiny (pamiętajcie o kropce) lub format binary (w FASMie)

 

Wskazuje kompilatorowi rodzaj programu. Jest kilka takich dyrektyw:

o tiny: kod i dane mieszczą się w jednym 64kB segmencie. Typowy dla programów typu .com

o small: kod i dane są w różnych segmentach, ale obydwa są mniejsze od 64kB

o medium: kod może być > 64kB, ale dane muszą być < 64kB

o compact: kod musi być < 64kB, dane mogą mieć więcej niż 64kB

o large: kod i dane mogą być > 64kB, ale tablice muszą być < 64kB

o huge: kod, dane i tablice mogą być > 64kB

 

 

* .code (też z kropką)

 

Wskazuje początek segmentu, gdzie znajduje się kod programu. Można jednak w tym segmencie umieszczać dane, ale należy to robić tak, aby nie stały się one częścią programu. Zwykle wpisuje się je za ostatnią komendą kończącą program. Procesor przecież nie wie, co jest pod danym adresem i z miłą chęcią potraktuje to coś jako instrukcję, co może prowadzić do przykrych konsekwencji. Swoje dane umieszczajcie tak, aby w żaden sposób strumień wykonywanych instrukcji nie wszedł na nie.

Są też inne dyrektywy: ".data", deklarująca początek segmentu z danymi oraz ".stack", deklarująca segment stosu (o tym później), której nie można używać w programach typu ".com", gdzie stos jest automatycznie ustawiany.

 

* org 100h (bez kropki)

 

Ta linia mówi kompilatorowi, że nasz kod będzie (dopiero po uruchomieniu!) znajdował się pod adresem 100h (256 dziesiętnie) w swoim segmencie. To jest typowe dla programów .com. DOS, uruchamiając taki program, szuka wolnego segmentu i kod programu umieszcza dopiero pod adresem (czasami zwanym offsetem - przesunięciem) 100h.

Co jest więc wcześniej? Wiele ciekawych informacji, z których chyba najczęściej używaną jest linia poleceń programu (parametry uruchomienia, np. różne opcje itd.).

Dyrektywa "org" podana na początku kodu NIE wpływa na rozmiar programu, ułatwia kompilatorowi określenie adresów różnych etykiet (w tym danych) znajdujących się w programie.

Jeśli chcemy tworzyć programy typu .com, należy zawsze podać "org 100h" i opcję /t dla Turbo Linkera.

 

* "start:" (z dwukropkiem) i "end start" (bez dwukropka)

 

Mówią kompilatorowi, gdzie są odpowiednio: początek i koniec programu.

 

* mov ah,9

 

Do 8-bitowego rejestru AH (górnej części 16-bitowego AX) wstaw (MOV = move, przesuń) wartość 9. Po co i czemu akurat 9? Zaraz zobaczymy.

Najpierw powiem o czymś innym: komenda MOV ma ważne ograniczenia:

1. nie można skopiować jedną komendą MOV komórki pamięci do innej komórki pamięci, czyli takie coś:

 

mov [a],

 

(gdzie a i b - dwie zmienne w pamięci) jest zabronione.

O tym, co oznaczają nawiasy kwadratowe, czyli o adresowaniu zmiennych w pamięci - następnym razem.

2. nie można skopiować jedną komendą MOV jednego rejestru segmentowego (cs,ds,es,ss,fs,gs) do innego rejestru segmentowego, czyli działanie

 

mov es, ds

 

jest zabronione.

3. Nie można do rejestru segmentowego bezpośrednio wpisać jakieś wartości, czyli nie można

 

mov ds, 0

 

ale można:

 

mov bx, 0

mov ds, bx

 

* mov dx,offset info

 

Do rejestru danych (DX, 16-bitowy) wstaw offset (adres względem początku segmentu) etykiety "info". Można obliczać adresy nie tylko danych, ale etykiet znajdujących się w kodzie programu.

 

* int 21h

 

INT = interrupt = przerwanie. Nie jest to jednak znane np. z kart dźwiękowych przerwanie typu IRQ. Wywołując przerwanie 21h (33 dziesiętnie) uruchamiamy jedną z funkcji DOSa. Którą? O tym zazwyczaj mówi rejestr AX. W spisie przerwań Ralfa Brown'a (RBIL) patrzymy:

(przeskocz opis int 21h, ah=9)

 

INT 21 - DOS 1+ - WRITE STRING TO STANDARD OUTPUT

AH = 09h

DS:DX -> $-terminated string

 

Już widzimy, czemu do AH poszła wartość 9. Chcieliśmy uruchomić funkcję, która wypisuje na na ekran ciąg znaków zakończony znakiem dolara. Adres tego ciągu musi się znajdować w parze rejestrów: DS wskazuje segment, w którym znajduje się ten ciąg, a DX - jego adres w tym segmencie. Dlatego było "mov dx,offset info".

Zaraz, zaraz! Ale przecież my nic nie robiliśmy z DS, a dane znajdują się przecież w segmencie kodu! I to działa?

Oczywiście! Programy typu .com są małe. Tak małe, że mieszczą się w jednym segmencie pamięci. Dlatego przy ich uruchamianiu DOS ustawia nam CS=DS=ES=SS. Nie musimy się więc o to martwić.

 

* mov ah,0

 

Do rejestru AH wpisz 0. Czemu? Zaraz zobaczymy. Ale najpierw wspomnę o czymś innym. Otóż,

 

mov rejestr, 0

 

nie jest najlepszym sposobem na wyzerowanie danego rejestru. Szybsze lub krótsze są dwa inne:

 

xor rej1, rej1 ; 1 xor 1 = 0 oraz 0 xor 0 = 0.

; Stąd "coś XOR to_samo_coś"

; zawsze daje 0.

 

sub rej1, rej1 ; sub=subtract=odejmij.

; rej1 - rej1 = 0

 

Ja zwykle używam XOR.

 

* int 16h

 

Kolejne przerwanie, więc znowu do listy Ralfa Brown'a:

(przeskocz opis int 16h, ah=0)

 

INT 16 - KEYBOARD - GET KEYSTROKE

AH = 00h

Return: AH = BIOS scan code

AL = ASCII character

 

Ta funkcja pobiera znak z klawiatury i zwraca go w rejestrze AL. Jeśli nie naciśnięto nic, poczeka, aż użytkownik naciśnie.

 

* mov ax,4c00h

 

Do rejestru AX wpisujemy wartość 4c00 szesnastkowo.

 

* int 21h

 

Znów przerwanie DOSa, funkcja 4ch. Patrzymy do RBIL:

(przeskocz opis int 21h, ah=4ch)

 

INT 21 - DOS 2+ - "EXIT" - TERMINATE WITH RETURN CODE

AH = 4Ch

AL = return code

Return: never returns

 

Jak widzimy, ta funkcja powoduje wyjście z powrotem do DOSa, z numerem błędu (errorlevel) w AL równym 0. Przyjmuje się, że 0 oznacza, iż program zakończył się bez błędów. Jak widać po rozmiarze rejestru AL (8 bitów), program może wyjść z 2^8=256 różnymi numerami błędu.

 

* info db "Czesc.$"

 

Etykietą "info" opisujemy kilka bajtów, w tym przypadku zapisanych jako ciąg znaków.

A po co znak dolara $? Jak sobie przypomnimy, funkcja 9. przerwania DOSa wypisuje ciąg znaków zakończony właśnie na znak dolara $. Gdyby tego znaczka nie było, DOS wypisywałby różne śmieci z pamięci, aż trafiłby na przypadkowy znak dolara $ nie wiadomo gdzie. O deklarowaniu zmiennych będzie w następnej części.

 

* end start

 

Koniec programu.

 

 

 

Programik kompilujemy poleceniem:

 

tasm naszplik.asm

tlink naszplik.obj /t

 

(opcja /t - utworzy plik typu .com).

Jeśli otrzymujecie komunikaty o braku pamięci, możecie wypróbować następujący sposób:

 

tasmx naszplik.asm

tlink naszplik.obj /t /3

 

Dla NASMa kompilacja wygląda tak:

 

nasm -o naszplik.com -f bin naszplik.asm

 

(-o = nazwa pliku wyjściowego

-f = format pliku. Bin = binarny = na przykład COM lub SYS).

 

A dla FASMa:

 

fasm naszplik.asm naszplik.com

 

Uruchamiamy naszplik.com i cieszymy się swoim dziełem.

Miłego eksperymentowania.

 

Copyright by http://rudy.mif.pg.gda.pl/~bogdro/

9989.jpg

"Głupców nie sieją, sami się rodzą"

  • 1 rok później...
Opublikowano

przecież to plagiat ....

liczylem na barce lecz ona niemogla wygrac.

Przez kibicow ktorzy przy rzutach roznych wolnych swiecili w messiego laserami

Zarchiwizowany

Ten temat przebywa obecnie w archiwum. Dodawanie nowych odpowiedzi zostało zablokowane.

×
×
  • Dodaj nową pozycję...