Montaż - skrócona instrukcja
Co to jest język asemblera?
Każdy komputer osobisty jest wyposażony w mikroprocesor, który zarządza działaniami arytmetycznymi, logicznymi i kontrolnymi komputera.
Każda rodzina procesorów ma własny zestaw instrukcji do obsługi różnych operacji, takich jak pobieranie danych wejściowych z klawiatury, wyświetlanie informacji na ekranie i wykonywanie różnych innych zadań. Ten zestaw instrukcji nazywany jest „instrukcjami języka maszynowego”.
Procesor rozumie tylko instrukcje języka maszynowego, które są ciągami jedynek i zer. Jednak język maszynowy jest zbyt niejasny i skomplikowany, aby można go było używać w tworzeniu oprogramowania. Tak więc język asemblera niskiego poziomu jest przeznaczony dla określonej rodziny procesorów, które reprezentują różne instrukcje w kodzie symbolicznym i bardziej zrozumiałej formie.
Zalety języka asemblera
Zrozumienie języka asemblera uświadamia -
- Jak programy współpracują z systemem operacyjnym, procesorem i systemem BIOS;
- Sposób reprezentacji danych w pamięci i innych urządzeniach zewnętrznych;
- W jaki sposób procesor uzyskuje dostęp i wykonuje instrukcje;
- Jak instrukcje uzyskują dostęp do danych i je przetwarzają;
- W jaki sposób program uzyskuje dostęp do urządzeń zewnętrznych.
Inne zalety używania języka asemblera to -
Wymaga mniej pamięci i czasu wykonywania;
Pozwala w łatwiejszy sposób na złożone zadania specyficzne dla sprzętu;
Nadaje się do prac, w których liczy się czas;
Jest najbardziej odpowiedni do pisania procedur obsługi przerwań i innych programów rezydujących w pamięci.
Podstawowe cechy sprzętu komputerowego
Główny wewnętrzny sprzęt komputera PC składa się z procesora, pamięci i rejestrów. Rejestry to elementy procesora, które przechowują dane i adresy. W celu wykonania programu system kopiuje go z urządzenia zewnętrznego do pamięci wewnętrznej. Procesor wykonuje instrukcje programu.
Podstawową jednostką pamięci komputera jest trochę; może być WŁĄCZONY (1) lub WYŁĄCZONY (0), a grupa 8 powiązanych bitów tworzy bajt na większości nowoczesnych komputerów.
Tak więc bit parzystości jest używany, aby liczba bitów w bajcie była nieparzysta. Jeśli parzystość jest równa, system zakłada, że wystąpił błąd parzystości (choć rzadki), który mógł być spowodowany usterką sprzętową lub zakłóceniem elektrycznym.
Procesor obsługuje następujące rozmiary danych -
- Słowo: 2-bajtowa pozycja danych
- Podwójne słowo: 4-bajtowa (32-bitowa) pozycja danych
- Quadword: 8-bajtowy (64-bitowy) element danych
- Akapit: obszar 16-bajtowy (128-bitowy)
- Kilobajt: 1024 bajty
- Megabajt: 1048576 bajtów
System liczb binarnych
Każdy system liczbowy używa notacji pozycyjnej, tj. Każda pozycja, w której zapisana jest cyfra, ma inną wartość pozycyjną. Każda pozycja jest potęgą podstawy, która wynosi 2 dla systemu liczb binarnych, a te potęgi zaczynają się od 0 i rosną o 1.
W poniższej tabeli przedstawiono wartości pozycyjne 8-bitowej liczby binarnej, w której wszystkie bity są ustawione na ON.
Wartość bitowa | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
Wartość pozycji jako potęga podstawy 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Numer bitu | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Wartość liczby binarnej opiera się na obecności 1 bitów i ich wartości pozycyjnej. Czyli wartość danej liczby binarnej to -
1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255
czyli to samo co 2 8 - 1.
System liczb szesnastkowych
System liczb szesnastkowych używa podstawy 16. Cyfry w tym systemie mieszczą się w zakresie od 0 do 15. Zgodnie z konwencją, litery od A do F są używane do reprezentowania cyfr szesnastkowych odpowiadających wartościom dziesiętnym od 10 do 15.
Liczby szesnastkowe w obliczeniach służą do skrócenia długich reprezentacji binarnych. Zasadniczo system liczb szesnastkowych reprezentuje dane binarne, dzieląc każdy bajt na pół i wyrażając wartość każdego pół-bajtu. Poniższa tabela zawiera odpowiedniki dziesiętne, dwójkowe i szesnastkowe -
Liczba dziesiętna | Reprezentacja binarna | Reprezentacja szesnastkowa |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | ZA |
11 | 1011 | b |
12 | 1100 | do |
13 | 1101 | re |
14 | 1110 | mi |
15 | 1111 | fa |
Aby przekonwertować liczbę dwójkową na jej szesnastkowy odpowiednik, podziel ją na grupy po 4 kolejne grupy każda, zaczynając od prawej strony, i zapisz te grupy na odpowiednich cyfrach liczby szesnastkowej.
Example - Liczba binarna 1000 1100 1101 0001 jest odpowiednikiem liczby szesnastkowej - 8CD1
Aby przekonwertować liczbę szesnastkową na dwójkową, po prostu zapisz każdą cyfrę szesnastkową na jej 4-cyfrowy odpowiednik binarny.
Example - Liczba szesnastkowa FAD8 jest odpowiednikiem liczby dwójkowej - 1111 1010 1101 1000
Arytmetyka binarna
Poniższa tabela przedstawia cztery proste zasady dodawania binarnego -
(ja) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
= 0 | = 1 | = 10 | = 11 |
Reguły (iii) i (iv) pokazują przeniesienie 1-bitu na następną lewą pozycję.
Example
Dziesiętny | Dwójkowy |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
Ujemna wartość binarna jest wyrażana w two's complement notation. Zgodnie z tą zasadą konwersja liczby binarnej na jej wartość ujemną polega na odwróceniu jej wartości bitowych i dodaniu 1 .
Example
Numer 53 | 00110101 |
Odwróć bity | 11001010 |
Dodaj 1 | 0000000 1 |
Numer -53 | 11001011 |
Aby odjąć jedną wartość od drugiej, przekonwertuj odejmowaną liczbę do formatu uzupełnienia do dwóch i dodaj liczby .
Example
Odejmij 42 od 53
Numer 53 | 00110101 |
Numer 42 | 00101010 |
Odwróć bity 42 | 11010101 |
Dodaj 1 | 0000000 1 |
Numer -42 | 11010110 |
53 - 42 = 11 | 00001011 |
Przepełnienie ostatniego 1 bitu zostanie utracone.
Adresowanie danych w pamięci
Proces, za pomocą którego procesor steruje wykonywaniem instrukcji, nazywany jest fetch-decode-execute cycle albo execution cycle. Składa się z trzech ciągłych kroków -
- Pobieranie instrukcji z pamięci
- Dekodowanie lub identyfikacja instrukcji
- Wykonanie instrukcji
Procesor może jednocześnie uzyskiwać dostęp do jednego lub większej liczby bajtów pamięci. Rozważmy liczbę szesnastkową 0725H. Ta liczba będzie wymagać dwóch bajtów pamięci. Najstarszy bajt lub najbardziej znaczący bajt to 07, a najmniejszy bajt to 25.
Procesor przechowuje dane w odwrotnej kolejności bajtów, tj. Bajt niskiego rzędu jest przechowywany w niskim adresie pamięci, a bajt wyższego rzędu w adresie dużej pamięci. Tak więc, jeśli procesor przeniesie wartość 0725H z rejestru do pamięci, prześle najpierw 25 do niższego adresu pamięci, a 07 do następnego adresu pamięci.
x: adres pamięci
Kiedy procesor pobiera dane numeryczne z pamięci do rejestracji, ponownie odwraca bajty. Istnieją dwa rodzaje adresów pamięci -
Adres bezwzględny - bezpośrednie odniesienie do określonej lokalizacji.
Adres segmentu (lub offset) - adres początkowy segmentu pamięci z wartością offsetu.
Konfiguracja środowiska lokalnego
Język asemblera zależy od zestawu instrukcji i architektury procesora. W tym samouczku skupiamy się na procesorach Intel-32, takich jak Pentium. Aby skorzystać z tego samouczka, będziesz potrzebować -
- IBM PC lub dowolny równoważny kompatybilny komputer
- Kopia systemu operacyjnego Linux
- Kopia programu asemblera NASM
Istnieje wiele dobrych programów asemblera, takich jak -
- Asembler firmy Microsoft (MASM)
- Borland Turbo Assembler (TASM)
- Asembler GNU (GAS)
Użyjemy asemblera NASM, tak jak jest -
- Wolny. Możesz go pobrać z różnych źródeł internetowych.
- Dobrze udokumentowane, a otrzymasz mnóstwo informacji w sieci.
- Może być używany zarówno w systemie Linux, jak i Windows.
Instalowanie NASM
Jeśli wybierzesz "Development Tools" podczas instalacji Linuksa, możesz zainstalować NASM wraz z systemem operacyjnym Linux i nie musisz pobierać i instalować go oddzielnie. Aby sprawdzić, czy masz już zainstalowany NASM, wykonaj następujące czynności -
Otwórz terminal Linux.
Rodzaj whereis nasm i naciśnij ENTER.
Jeśli jest już zainstalowany, pojawi się taka linia, jak nasm: / usr / bin / nasm . W przeciwnym razie zobaczysz po prostu nasm:, wtedy musisz zainstalować NASM.
Aby zainstalować NASM, wykonaj następujące czynności -
Sprawdź witrynę internetową asemblera sieci (NASM), aby uzyskać najnowszą wersję.
Pobierz archiwum źródłowe Linuksa
nasm-X.XX.ta.gz
, gdzie w archiwumX.XX
znajduje się numer wersji NASM.Rozpakuj archiwum do katalogu, który tworzy podkatalog
nasm-X. XX
.cd do
nasm-X.XX
i wpisz./configure. Ten skrypt powłoki znajdzie najlepszy kompilator C do użycia i odpowiednio skonfiguruje pliki Makefile.Rodzaj make aby zbudować pliki binarne nasm i ndisasm.
Rodzaj make install zainstalować nasm i ndisasm w / usr / local / bin oraz strony podręcznika.
To powinno zainstalować NASM w twoim systemie. Alternatywnie możesz użyć dystrybucji RPM dla Fedory Linux. Ta wersja jest prostsza w instalacji, wystarczy dwukrotnie kliknąć plik RPM.
Program montażu można podzielić na trzy części -
Plik data Sekcja,
Plik bss sekcja i
Plik text Sekcja.
Dane Sekcja
Plik dataSekcja służy do deklarowania zainicjowanych danych lub stałych. Te dane nie zmieniają się w czasie wykonywania. W tej sekcji można zadeklarować różne wartości stałe, nazwy plików, rozmiar bufora itp.
Składnia deklarowania sekcji danych to -
section.data
BSS Sekcja
Plik bssSekcja służy do deklarowania zmiennych. Składnia deklarowania sekcji bss to -
section.bss
Tekst sekcja
Plik textSekcja służy do przechowywania aktualnego kodu. Ta sekcja musi zaczynać się od deklaracjiglobal _start, który informuje jądro, gdzie rozpoczyna się wykonywanie programu.
Składnia deklarowania sekcji tekstowej to -
section.text
global _start
_start:
Komentarze
Komentarz asemblera zaczyna się od średnika (;). Może zawierać dowolne znaki drukowalne, w tym puste. Może pojawić się samodzielnie w linii, na przykład -
; This program displays a message on screen
lub w tym samym wierszu wraz z instrukcją, na przykład -
add eax, ebx ; adds ebx to eax
Instrukcje języka asemblera
Programy w języku asemblerowym składają się z trzech typów instrukcji -
- Wykonywalne instrukcje lub instrukcje,
- Dyrektywy asemblera lub pseudo-operacje i
- Macros.
Plik executable instructions lub po prostu instructionspowiedz procesorowi, co ma robić. Każda instrukcja składa się z plikuoperation code(kod operacji). Każda instrukcja wykonywalna generuje jedną instrukcję języka maszynowego.
Plik assembler directives lub pseudo-opspowiedz asemblerowi o różnych aspektach procesu montażu. Nie są one wykonywalne i nie generują instrukcji języka maszynowego.
Macros są w zasadzie mechanizmem zastępowania tekstu.
Składnia instrukcji języka asemblera
Instrukcje asemblera są wprowadzane po jednej instrukcji w każdym wierszu. Każda instrukcja ma następujący format -
[label] mnemonic [operands] [;comment]
Pola w nawiasach kwadratowych są opcjonalne. Instrukcja podstawowa składa się z dwóch części, pierwsza to nazwa instrukcji (lub mnemonik), która ma zostać wykonana, a druga to operandy lub parametry polecenia.
Oto kilka przykładów typowych instrukcji języka asemblera -
INC COUNT ; Increment the memory variable COUNT
MOV TOTAL, 48 ; Transfer the value 48 in the
; memory variable TOTAL
ADD AH, BH ; Add the content of the
; BH register into the AH register
AND MASK1, 128 ; Perform AND operation on the
; variable MASK1 and 128
ADD MARKS, 10 ; Add 10 to the variable MARKS
MOV AL, 10 ; Transfer the value 10 to the AL register
Program Hello World w asemblerze
Poniższy kod języka asemblera wyświetla na ekranie ciąg „Hello World” -
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;string to be printed
len equ $ - msg ;length of the string
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Hello, world!
Kompilowanie i łączenie programu Assembly w NASM
Upewnij się, że ustawiłeś ścieżkę nasm i ldpliki binarne w zmiennej środowiskowej PATH. Teraz wykonaj następujące kroki, aby skompilować i połączyć powyższy program -
Wpisz powyższy kod za pomocą edytora tekstu i zapisz go jako hello.asm.
Upewnij się, że jesteś w tym samym katalogu, w którym zapisałeś hello.asm.
Aby złożyć program, wpisz nasm -f elf hello.asm
Jeśli wystąpi jakikolwiek błąd, zostaniesz o tym poproszony na tym etapie. W przeciwnym razie plik obiektowy programu o nazwiehello.o zostanie utworzona.
Aby połączyć plik obiektu i utworzyć plik wykonywalny o nazwie hello, wpisz ld -m elf_i386 -s -o hello hello.o
Uruchom program, wpisując ./hello
Jeśli wszystko wykonałeś poprawnie, wyświetli się komunikat „Hello, world!” na ekranie.
Omówiliśmy już trzy sekcje programu montażu. Te sekcje reprezentują również różne segmenty pamięci.
Co ciekawe, jeśli zamienisz słowo kluczowe sekcja na segment, uzyskasz ten sam wynik. Wypróbuj następujący kod -
segment .text ;code segment
global _start ;must be declared for linker
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
segment .data ;data segment
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Hello, world!
Segmenty pamięci
Model pamięci segmentowej dzieli pamięć systemową na grupy niezależnych segmentów, do których odwołują się wskaźniki znajdujące się w rejestrach segmentów. Każdy segment zawiera określony typ danych. Jeden segment jest używany do przechowywania kodów instrukcji, inny segment przechowuje elementy danych, a trzeci segment przechowuje stos programu.
W świetle powyższej dyskusji możemy określić różne segmenty pamięci jako -
Data segment - Jest reprezentowany przez .data sekcja i .bss. Sekcja .data służy do zadeklarowania obszaru pamięci, w którym przechowywane są elementy danych programu. Ta sekcja nie może zostać rozszerzona po zadeklarowaniu elementów danych i pozostaje statyczna w całym programie.
Sekcja .bss jest również sekcją pamięci statycznej, która zawiera bufory dla danych, które mają być zadeklarowane później w programie. Ta pamięć buforowa jest zapełniona zerami.
Code segment - Jest reprezentowany przez .textSekcja. To definiuje obszar w pamięci, który przechowuje kody instrukcji. Jest to również obszar stały.
Stack - Ten segment zawiera wartości danych przekazywane do funkcji i procedur w programie.
Operacje procesora obejmują głównie przetwarzanie danych. Te dane mogą być przechowywane w pamięci i stamtąd dostępne. Jednak odczytywanie i przechowywanie danych w pamięci spowalnia procesor, ponieważ wiąże się ze skomplikowanymi procesami wysyłania żądania danych przez magistralę sterującą do jednostki pamięci i pobierania danych tym samym kanałem.
Aby przyspieszyć działanie procesora, procesor zawiera kilka lokalizacji pamięci wewnętrznej, tzw registers.
Rejestry przechowują elementy danych do przetwarzania bez konieczności dostępu do pamięci. Ograniczona liczba rejestrów jest wbudowana w chip procesora.
Rejestry procesorów
W architekturze IA-32 jest dziesięć 32-bitowych i sześć 16-bitowych rejestrów procesora. Rejestry są podzielone na trzy kategorie -
- Rejestry ogólne,
- Rejestry kontrolne i
- Rejestry segmentowe.
Rejestry ogólne są dalej podzielone na następujące grupy -
- Rejestry danych,
- Rejestry wskaźnikowe i
- Rejestry indeksowe.
Rejestry danych
Cztery 32-bitowe rejestry danych są używane do operacji arytmetycznych, logicznych i innych. Te 32-bitowe rejestry mogą być używane na trzy sposoby -
Jako kompletne 32-bitowe rejestry danych: EAX, EBX, ECX, EDX.
Dolne połówki rejestrów 32-bitowych można wykorzystać jako cztery rejestry danych 16-bitowych: AX, BX, CX i DX.
Dolne i wyższe połówki wspomnianych powyżej czterech 16-bitowych rejestrów mogą być użyte jako osiem 8-bitowych rejestrów danych: AH, AL, BH, BL, CH, CL, DH i DL.
Niektóre z tych rejestrów danych mają szczególne zastosowanie w operacjach arytmetycznych.
AX is the primary accumulator; jest używany we wprowadzaniu / wyprowadzaniu i większości instrukcji arytmetycznych. Na przykład, w operacji mnożenia, jeden operand jest przechowywany w rejestrze EAX lub AX lub AL zgodnie z rozmiarem argumentu.
BX is known as the base register, ponieważ może być używany w adresowaniu indeksowanym.
CX is known as the count register, podobnie jak w ECX, rejestry CX przechowują liczbę pętli w operacjach iteracyjnych.
DX is known as the data register. Jest również używany w operacjach wejścia / wyjścia. Jest również używany z rejestrem AX wraz z DX do operacji mnożenia i dzielenia obejmujących duże wartości.
Rejestry wskaźników
Rejestry wskaźnikowe to 32-bitowe rejestry EIP, ESP i EBP oraz odpowiadające im 16-bitowe prawe części IP, SP i BP. Istnieją trzy kategorie rejestrów wskaźników -
Instruction Pointer (IP)- 16-bitowy rejestr IP przechowuje przesunięty adres następnej instrukcji do wykonania. IP w połączeniu z rejestrem CS (jako CS: IP) podaje pełny adres aktualnej instrukcji w segmencie kodu.
Stack Pointer (SP)- 16-bitowy rejestr SP dostarcza wartość przesunięcia w stosie programu. SP w połączeniu z rejestrem SS (SS: SP) odnosi się do aktualnej pozycji danych lub adresu w stosie programu.
Base Pointer (BP)- 16-bitowy rejestr BP pomaga głównie w odwoływaniu się do zmiennych parametrów przekazywanych do podprogramu. Adres w rejestrze SS jest łączony z przesunięciem w BP, aby uzyskać lokalizację parametru. BP można również łączyć z DI i SI jako rejestrem bazowym do specjalnego adresowania.
Rejestry indeksowe
32-bitowe rejestry indeksowe, ESI i EDI oraz ich 16-bitowe prawostronne części. SI i DI są używane do adresowania indeksowanego, a czasami używane jako dodawanie i odejmowanie. Istnieją dwa zestawy wskaźników indeksu -
Source Index (SI) - Jest używany jako indeks źródłowy dla operacji na łańcuchach.
Destination Index (DI) - Jest używany jako indeks docelowy dla operacji na łańcuchach.
Rejestry kontrolne
32-bitowy rejestr wskaźnika instrukcji i 32-bitowy rejestr flagowy są uważane za rejestry sterujące.
Wiele instrukcji obejmuje porównania i obliczenia matematyczne oraz zmienia stan flag, a niektóre inne instrukcje warunkowe testują wartość tych flag stanu, aby przenieść przepływ sterowania w inne miejsce.
Typowe bity flag to:
Overflow Flag (OF) - Wskazuje przepełnienie wyższego rzędu bitu (skrajny lewy bit) danych po operacji arytmetycznej ze znakiem.
Direction Flag (DF)- Określa lewy lub prawy kierunek przesuwania lub porównywania danych ciągów. Gdy wartość DF wynosi 0, operacja na łańcuchu przyjmuje kierunek od lewej do prawej, a gdy wartość jest ustawiona na 1, operacja na łańcuchu przebiega w kierunku od prawej do lewej.
Interrupt Flag (IF)- Określa, czy zewnętrzne przerwania, takie jak wprowadzanie danych z klawiatury itp., Mają być ignorowane lub przetwarzane. Wyłącza przerwanie zewnętrzne, gdy wartość wynosi 0 i włącza przerwania, gdy jest ustawiona na 1.
Trap Flag (TF)- Umożliwia ustawienie pracy procesora w trybie jednostopniowym. Program DEBUG, którego użyliśmy, ustawia flagę pułapki, abyśmy mogli przejść przez wykonywanie jednej instrukcji na raz.
Sign Flag (SF)- Pokazuje znak wyniku operacji arytmetycznej. Ta flaga jest ustawiana zgodnie ze znakiem elementu danych po operacji arytmetycznej. Znak jest wskazywany przez najwyższy rząd lewego bitu. Wynik dodatni powoduje skasowanie wartości SF na 0, a wynik ujemny na 1.
Zero Flag (ZF)- Wskazuje wynik operacji arytmetycznej lub porównania. Wynik niezerowy czyści flagę zero na 0, a wynik zerowy ustawia ją na 1.
Auxiliary Carry Flag (AF)- Zawiera przeniesienie z bitu 3 do bitu 4 po operacji arytmetycznej; używany do specjalistycznej arytmetyki. AF jest ustawiane, gdy 1-bajtowa operacja arytmetyczna powoduje przeniesienie z bitu 3 do bitu 4.
Parity Flag (PF)- Wskazuje całkowitą liczbę 1-bitów w wyniku otrzymanym z operacji arytmetycznej. Parzysta liczba 1-bitów zeruje flagę parzystości do 0, a nieparzysta liczba 1-bitów ustawia flagę parzystości na 1.
Carry Flag (CF)- Zawiera przeniesienie 0 lub 1 z bitu najwyższego rzędu (najbardziej po lewej) po operacji arytmetycznej. Przechowuje również zawartość ostatniego bitu operacji przesunięcia lub obrotu .
Poniższa tabela przedstawia położenie bitów flag w 16-bitowym rejestrze flag:
Flaga: | O | re | ja | T | S | Z | ZA | P. | do | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit nie: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Rejestry segmentowe
Segmenty to określone obszary zdefiniowane w programie, które zawierają dane, kod i stos. Istnieją trzy główne segmenty -
Code Segment- Zawiera wszystkie instrukcje do wykonania. 16-bitowy rejestr segmentu kodu lub rejestr CS przechowuje początkowy adres segmentu kodu.
Data Segment- Zawiera dane, stałe i obszary robocze. 16-bitowy rejestr segmentu danych lub rejestr DS przechowuje początkowy adres segmentu danych.
Stack Segment- Zawiera dane i adresy zwrotne procedur lub podprogramów. Jest implementowany jako struktura danych „stosu”. Rejestr segmentu stosu lub rejestr SS przechowuje adres początkowy stosu.
Oprócz rejestrów DS, CS i SS istnieją inne dodatkowe rejestry segmentowe - ES (dodatkowy segment), FS i GS, które zapewniają dodatkowe segmenty do przechowywania danych.
W programowaniu w asemblerze program potrzebuje dostępu do lokalizacji pamięci. Wszystkie lokalizacje pamięci w segmencie odnoszą się do adresu początkowego segmentu. Segment zaczyna się w adresie, który jest równo podzielny przez 16 lub szesnastkowo 10. Tak więc najbardziej prawą cyfrą szesnastkową we wszystkich takich adresach pamięci jest 0, które zwykle nie jest przechowywane w rejestrach segmentów.
Rejestry segmentów przechowują początkowe adresy segmentu. Aby uzyskać dokładną lokalizację danych lub instrukcji w segmencie, wymagana jest wartość przesunięcia (lub przesunięcie). Aby odwołać się do dowolnej lokalizacji pamięci w segmencie, procesor łączy adres segmentu w rejestrze segmentu z wartością przesunięcia lokalizacji.
Przykład
Spójrz na następujący prosty program, aby zrozumieć użycie rejestrów w programowaniu w asemblerze. Ten program wyświetla 9 gwiazdek na ekranie wraz z prostą wiadomością -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,9 ;message length
mov ecx,s2 ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Displaying 9 stars',0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db '*'
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Displaying 9 stars
*********
Wywołania systemowe to interfejsy API dla interfejsu między przestrzenią użytkownika a przestrzenią jądra. Skorzystaliśmy już z wywołań systemowych. sys_write i sys_exit, odpowiednio do pisania na ekranie i wychodzenia z programu.
Połączenia systemowe Linux
Możesz korzystać z wywołań systemowych Linuksa w programach asemblera. Musisz wykonać następujące kroki, aby używać wywołań systemowych Linux w swoim programie:
- Umieść numer wywołania systemowego w rejestrze EAX.
- Przechowuj argumenty wywołania systemowego w rejestrach EBX, ECX itp.
- Wywołaj odpowiednie przerwanie (80h).
- Wynik jest zwykle zwracany w rejestrze EAX.
Istnieje sześć rejestrów przechowujących argumenty użytego wywołania systemowego. Są to EBX, ECX, EDX, ESI, EDI i EBP. Rejestry te przyjmują kolejne argumenty, zaczynając od rejestru EBX. Jeśli argumentów jest więcej niż sześć, to lokalizacja pamięci pierwszego argumentu jest przechowywana w rejestrze EBX.
Poniższy fragment kodu przedstawia użycie wywołania systemowego sys_exit -
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
Poniższy fragment kodu przedstawia użycie wywołania systemowego sys_write -
mov edx,4 ; message length
mov ecx,msg ; message to write
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
Wszystkie wywołania systemowe są wymienione w /usr/include/asm/unistd.h , wraz z ich numerami (wartość, którą należy wprowadzić do EAX przed wywołaniem int 80h).
W poniższej tabeli przedstawiono niektóre wywołania systemowe używane w tym samouczku -
% eax | Nazwa | % ebx | % ecx | % edx | % esx | % edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | struct pt_regs | - | - | - | - |
3 | sys_read | unsigned int | char * | size_t | - | - |
4 | sys_write | unsigned int | const char * | size_t | - | - |
5 | sys_open | const char * | int | int | - | - |
6 | sys_close | unsigned int | - | - | - | - |
Przykład
Poniższy przykład odczytuje liczbę z klawiatury i wyświetla ją na ekranie -
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start: ;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Please enter a number:
1234
You have entered:1234
Większość instrukcji języka asemblera wymaga przetwarzania argumentów. Adres argumentu określa lokalizację, w której przechowywane są dane do przetworzenia. Niektóre instrukcje nie wymagają operandu, podczas gdy inne instrukcje mogą wymagać jednego, dwóch lub trzech operandów.
Gdy instrukcja wymaga dwóch operandów, pierwszy argument jest zwykle miejscem docelowym, który zawiera dane w rejestrze lub komórce pamięci, a drugi argument jest źródłem. Źródło zawiera dane do dostarczenia (adresowanie bezpośrednie) lub adres (w rejestrze lub pamięci) danych. Generalnie dane źródłowe pozostają niezmienione po operacji.
Trzy podstawowe tryby adresowania to -
- Zarejestruj adresowanie
- Natychmiastowe adresowanie
- Adresowanie pamięci
Zarejestruj adresowanie
W tym trybie adresowania rejestr zawiera operand. W zależności od instrukcji rejestr może być pierwszym operandem, drugim operandem lub obydwoma.
Na przykład,
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
Ponieważ przetwarzanie danych między rejestrami nie obejmuje pamięci, zapewnia najszybsze przetwarzanie danych.
Natychmiastowe adresowanie
Bezpośredni operand ma stałą wartość lub wyrażenie. Gdy instrukcja z dwoma argumentami używa adresowania bezpośredniego, pierwszy argument może być rejestrem lub lokalizacją pamięci, a drugi argument jest natychmiastową stałą. Pierwszy operand określa długość danych.
Na przykład,
BYTE_VALUE DB 150 ; A byte value is defined
WORD_VALUE DW 300 ; A word value is defined
ADD BYTE_VALUE, 65 ; An immediate operand 65 is added
MOV AX, 45H ; Immediate constant 45H is transferred to AX
Bezpośrednie adresowanie pamięci
Gdy operandy są określone w trybie adresowania pamięci, wymagany jest bezpośredni dostęp do pamięci głównej, zwykle do segmentu danych. Ten sposób adresowania skutkuje wolniejszym przetwarzaniem danych. Aby zlokalizować dokładną lokalizację danych w pamięci, potrzebujemy adresu początkowego segmentu, który zwykle znajduje się w rejestrze DS i wartości przesunięcia. Ta wartość przesunięcia jest również nazywanaeffective address.
W trybie adresowania bezpośredniego wartość offsetu jest określana bezpośrednio jako część instrukcji, zwykle wskazywana przez nazwę zmiennej. Asembler oblicza wartość przesunięcia i utrzymuje tabelę symboli, w której przechowywane są wartości przesunięcia wszystkich zmiennych używanych w programie.
W bezpośrednim adresowaniu pamięci jeden z argumentów odnosi się do miejsca w pamięci, a drugi do rejestru.
Na przykład,
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
Adresowanie z bezpośrednim przesunięciem
Ten tryb adresowania wykorzystuje operatory arytmetyczne do modyfikacji adresu. Na przykład spójrz na następujące definicje, które definiują tabele danych -
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
Następujące operacje uzyskują dostęp do danych z tablic w pamięci do rejestrów -
MOV CL, BYTE_TABLE[2] ; Gets the 3rd element of the BYTE_TABLE
MOV CL, BYTE_TABLE + 2 ; Gets the 3rd element of the BYTE_TABLE
MOV CX, WORD_TABLE[3] ; Gets the 4th element of the WORD_TABLE
MOV CX, WORD_TABLE + 3 ; Gets the 4th element of the WORD_TABLE
Pośrednie adresowanie pamięci
Ten tryb adresowania wykorzystuje zdolność komputera do adresowania Segment: Offset . Generalnie do tego celu wykorzystywane są rejestry bazowe EBX, EBP (lub BX, BP) i rejestry indeksowe (DI, SI), zakodowane w nawiasach kwadratowych jako odniesienia do pamięci.
Adresowanie pośrednie jest zwykle używane w przypadku zmiennych zawierających kilka elementów, takich jak tablice. Początkowy adres tablicy jest przechowywany, powiedzmy, w rejestrze EBX.
Poniższy fragment kodu pokazuje, jak uzyskać dostęp do różnych elementów zmiennej.
MY_TABLE TIMES 10 DW 0 ; Allocates 10 words (2 bytes) each initialized to 0
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ADD EBX, 2 ; EBX = EBX +2
MOV [EBX], 123 ; MY_TABLE[1] = 123
Instrukcja MOV
Skorzystaliśmy już z instrukcji MOV, która służy do przenoszenia danych z jednej przestrzeni pamięci do drugiej. Instrukcja MOV przyjmuje dwa operandy.
Składnia
Składnia instrukcji MOV to -
MOV destination, source
Instrukcja MOV może mieć jedną z następujących pięciu form -
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
Należy pamiętać, że -
- Oba operandy w operacji MOV powinny mieć ten sam rozmiar
- Wartość operandu źródłowego pozostaje niezmieniona
Instrukcja MOV czasami powoduje niejednoznaczność. Na przykład spójrz na stwierdzenia -
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
Nie jest jasne, czy chcesz przenieść równoważnik bajtowy, czy odpowiednik słowa liczby 110. W takich przypadkach rozsądnie jest użyć znaku type specifier.
W poniższej tabeli przedstawiono niektóre typowe specyfikatory typu -
Specyfikator typu | Adresowane bajty |
---|---|
BAJT | 1 |
SŁOWO | 2 |
DWORD | 4 |
QWORD | 8 |
TBYTE | 10 |
Przykład
Poniższy program ilustruje niektóre z omówionych powyżej koncepcji. Przechowuje nazwę „Zara Ali” w sekcji danych pamięci, a następnie programowo zmienia jej wartość na inną nazwę „Nuha Ali” i wyświetla obie nazwy.
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
;writing the name 'Zara Ali'
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov [name], dword 'Nuha' ; Changed the name to Nuha Ali
;writing the name 'Nuha Ali'
mov edx,8 ;message length
mov ecx,name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
name db 'Zara Ali '
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Zara Ali Nuha Ali
NASM zapewnia różne define directivesdo rezerwowania miejsca do przechowywania zmiennych. Dyrektywa define assembler służy do alokacji przestrzeni dyskowej. Można go użyć do zarezerwowania, jak również zainicjowania jednego lub więcej bajtów.
Przydzielanie miejsca na dane dla zainicjowanych danych
Składnia instrukcji alokacji pamięci dla zainicjowanych danych jest następująca:
[variable-name] define-directive initial-value [,initial-value]...
Gdzie nazwa-zmiennej to identyfikator każdej przestrzeni dyskowej. Asembler przypisuje wartość przesunięcia dla każdej nazwy zmiennej zdefiniowanej w segmencie danych.
Istnieje pięć podstawowych form zdefiniowanej dyrektywy -
Dyrektywa | Cel, powód | Przestrzeń magazynowa |
---|---|---|
DB | Zdefiniuj bajt | przydziela 1 bajt |
DW | Zdefiniuj słowo | przydziela 2 bajty |
DD | Zdefiniuj Doubleword | przydziela 4 bajty |
DQ | Zdefiniuj Quadword | przydziela 8 bajtów |
DT | Zdefiniuj dziesięć bajtów | przydziela 10 bajtów |
Poniżej znajduje się kilka przykładów użycia dyrektyw define -
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
Należy pamiętać, że -
Każdy bajt znaku jest przechowywany jako jego wartość ASCII w postaci szesnastkowej.
Każda wartość dziesiętna jest automatycznie konwertowana na jej 16-bitowy odpowiednik binarny i zapisywana jako liczba szesnastkowa.
Procesor używa kolejności bajtów little-endian.
Liczby ujemne są konwertowane na reprezentację uzupełnienia do 2.
Krótkie i długie liczby zmiennoprzecinkowe są reprezentowane odpowiednio za pomocą 32 lub 64 bitów.
Poniższy program pokazuje użycie dyrektywy define -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,1 ;message length
mov ecx,choice ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
choice DB 'y'
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
y
Przydzielanie miejsca na dane niezainicjowane
Dyrektywy rezerwy służą do rezerwowania miejsca na niezainicjowane dane. Dyrektywy rezerwowe przyjmują jeden operand, który określa liczbę jednostek przestrzeni do zarezerwowania. Każda dyrektywa Definicja ma powiązaną dyrektywę dotyczącą rezerw.
Istnieje pięć podstawowych form dyrektywy w sprawie rezerw -
Dyrektywa | Cel, powód |
---|---|
RESB | Zarezerwuj bajt |
RESW | Zarezerwuj słowo |
RESD | Zarezerwuj podwójne słowo |
RESQ | Zarezerwuj Quadword |
ODPOCZYNEK | Zarezerwuj dziesięć bajtów |
Wiele definicji
W programie można mieć wiele instrukcji definiujących dane. Na przykład -
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
Asembler przydziela ciągłą pamięć dla wielu definicji zmiennych.
Wiele inicjalizacji
Dyrektywa TIMES zezwala na wielokrotne inicjalizacje tej samej wartości. Na przykład tablicę nazwaną markami o rozmiarze 9 można zdefiniować i zainicjować do zera za pomocą następującej instrukcji -
marks TIMES 9 DW 0
Dyrektywa TIMES jest przydatna przy definiowaniu tablic i tabel. Poniższy program wyświetla 9 gwiazdek na ekranie -
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
mov edx,9 ;message length
mov ecx, stars ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
stars times 9 db '*'
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
*********
Istnieje kilka dyrektyw dostarczonych przez NASM, które definiują stałe. Korzystaliśmy już z dyrektywy EQU w poprzednich rozdziałach. Omówimy w szczególności trzy dyrektywy -
- EQU
- %assign
- %define
Dyrektywa EQU
Plik EQUDyrektywa służy do definiowania stałych. Składnia dyrektywy EQU jest następująca -
CONSTANT_NAME EQU expression
Na przykład,
TOTAL_STUDENTS equ 50
Następnie możesz użyć tej stałej wartości w swoim kodzie, na przykład -
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
Operand instrukcji EQU może być wyrażeniem -
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
Powyższy segment kodu zdefiniowałby OBSZAR jako 200.
Przykład
Poniższy przykład ilustruje zastosowanie dyrektywy EQU -
SYS_EXIT equ 1
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
mov eax,SYS_EXIT ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2 msg3 db 'Linux assembly programming! ' len3 equ $- msg3
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Dyrektywa% assign
Plik %assigndyrektywy można używać do definiowania stałych numerycznych, takich jak dyrektywa EQU. Ta dyrektywa pozwala na przedefiniowanie. Na przykład można zdefiniować stałą TOTAL jako -
%assign TOTAL 10
W dalszej części kodu możesz przedefiniować go jako -
%assign TOTAL 20
W tej dyrektywie rozróżniana jest wielkość liter.
% Definiuje dyrektywę
Plik %defineDyrektywa pozwala na zdefiniowanie stałych numerycznych i łańcuchowych. Ta dyrektywa jest podobna do #define w C. Na przykład można zdefiniować stałą PTR jako -
%define PTR [EBP+4]
Powyższy kod zastępuje PTR przez [EBP + 4].
Dyrektywa ta pozwala również na przedefiniowanie i rozróżnia wielkość liter.
Instrukcja INC
Instrukcja INC jest używana do zwiększania operandu o jeden. Działa na pojedynczym operandzie, który może znajdować się w rejestrze lub w pamięci.
Składnia
Instrukcja INC ma następującą składnię -
INC destination
Miejsce docelowe operandu może być 8-bitowym, 16-bitowym lub 32-bitowym operandem.
Przykład
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
Instrukcja DEC
Instrukcja DEC służy do zmniejszania wartości operandu o jeden. Działa na pojedynczym operandzie, który może znajdować się w rejestrze lub w pamięci.
Składnia
Instrukcja DEC ma następującą składnię -
DEC destination
Miejsce docelowe operandu może być 8-bitowym, 16-bitowym lub 32-bitowym operandem.
Przykład
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
Instrukcje ADD i SUB
Instrukcje ADD i SUB są używane do wykonywania prostego dodawania / odejmowania danych binarnych w rozmiarze bajtu, słowa i słowa podwójnego, tj. Do dodawania lub odejmowania odpowiednio 8-bitowych, 16-bitowych lub 32-bitowych argumentów.
Składnia
Instrukcje ADD i SUB mają następującą składnię -
ADD/SUB destination, source
Instrukcja ADD / SUB może mieć miejsce między -
- Zarejestruj się, aby się zarejestrować
- Pamięć do zarejestrowania
- Zarejestruj się w pamięci
- Zarejestruj się na stałe dane
- Pamięć do stałych danych
Jednak, podobnie jak inne instrukcje, operacje z pamięci do pamięci nie są możliwe przy użyciu instrukcji ADD / SUB. Operacja ADD lub SUB ustawia lub usuwa przepełnienie i flagi przenoszenia.
Przykład
Poniższy przykład poprosi użytkownika o podanie dwóch cyfr, zapisze cyfry odpowiednio w rejestrze EAX i EBX, doda wartości, zapisze wynik w lokalizacji pamięci „ res ” i na koniec wyświetli wynik.
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1 msg2 db "Please enter a second digit", 0xA,0xD len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [num1]
sub eax, '0'
mov ebx, [num2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
The program with hardcoded variables −
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The sum is:
7
Instrukcja MUL / IMUL
Istnieją dwie instrukcje dotyczące mnożenia danych binarnych. Instrukcja MUL (Multiply) obsługuje dane bez znaku, a IMUL (Integer Multiply) obsługuje dane ze znakiem. Obie instrukcje mają wpływ na flagę Carry i Overflow.
Składnia
Składnia instrukcji MUL / IMUL jest następująca -
MUL/IMUL multiplier
Mnożnik w obu przypadkach będzie znajdował się w akumulatorze, w zależności od rozmiaru mnożnika i mnożnika, a wygenerowany iloczyn jest również przechowywany w dwóch rejestrach w zależności od rozmiaru argumentów. Poniższa sekcja wyjaśnia instrukcje MUL w trzech różnych przypadkach -
Sr.No. | Scenariusze |
---|---|
1 | When two bytes are multiplied − Mnożnik znajduje się w rejestrze AL, a mnożnik jest bajtem w pamięci lub w innym rejestrze. Produkt jest w AX. 8 bitów produktu wysokiego rzędu jest przechowywanych w AH, a 8 bitów niskiego rzędu jest przechowywanych w AL.
|
2 | When two one-word values are multiplied − Mnożnik powinien znajdować się w rejestrze AX, a mnożnik to słowo w pamięci lub inny rejestr. Na przykład, dla instrukcji takiej jak MUL DX, musisz zapisać mnożnik w DX, a mnożnik w AX. Otrzymany iloczyn jest podwójnym słowem, które będzie wymagało dwóch rejestrów. Część najwyższego rzędu (skrajna lewa) zostaje zapisana w DX, a część niższego rzędu (skrajna prawa) zostaje zapisana w AX.
|
3 | When two doubleword values are multiplied − Kiedy mnoży się dwie wartości podwójnego słowa, mnożnik powinien być w EAX, a mnożnik jest wartością podwójnego słowa przechowywaną w pamięci lub w innym rejestrze. Wygenerowany produkt jest przechowywany w rejestrach EDX: EAX, tj. 32 bity wyższego rzędu są przechowywane w rejestrze EDX, a 32 bity najniższego rzędu są przechowywane w rejestrze EAX.
|
Przykład
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
Przykład
Poniższy przykład mnoży 3 przez 2 i wyświetla wynik -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al,'3'
sub al, '0'
mov bl, '2'
sub bl, '0'
mul bl
add al, '0'
mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The result is:
6
Instrukcje DIV / IDIV
Operacja dzielenia generuje dwa elementy - a quotient i a remainder. W przypadku mnożenia przepełnienie nie występuje, ponieważ do przechowywania iloczynu używane są rejestry o podwójnej długości. Jednak w przypadku podziału może wystąpić przelanie. Procesor generuje przerwanie, jeśli wystąpi przepełnienie.
Instrukcja DIV (Divide) jest używana dla danych bez znaku, a IDIV (Integer Divide) jest używana dla danych ze znakiem.
Składnia
Format instrukcji DIV / IDIV -
DIV/IDIV divisor
Dywidenda jest akumulowana. Obie instrukcje mogą działać z 8-bitowymi, 16-bitowymi lub 32-bitowymi operandami. Operacja wpływa na wszystkie sześć flag stanu. Poniższa sekcja wyjaśnia trzy przypadki dzielenia o różnej wielkości operandu -
Sr.No. | Scenariusze |
---|---|
1 | When the divisor is 1 byte − Zakłada się, że dywidenda znajduje się w rejestrze AX (16 bitów). Po podzieleniu iloraz trafia do rejestru AL, a reszta do rejestru AH.
|
2 | When the divisor is 1 word − Zakłada się, że dywidenda ma długość 32 bity i znajduje się w rejestrach DX: AX. 16 bitów wysokiego rzędu jest w DX, a 16 bitów niskiego rzędu w AX. Po podzieleniu, 16-bitowy iloraz trafia do rejestru AX, a 16-bitowa reszta trafia do rejestru DX.
|
3 | When the divisor is doubleword − Zakłada się, że dywidenda ma długość 64 bitów i jest zapisana w rejestrach EDX: EAX. 32 bity najwyższego rzędu są w EDX, a 32 bity najniższego rzędu w EAX. Po podzieleniu 32-bitowy iloraz trafia do rejestru EAX, a 32-bitowa reszta trafia do rejestru EDX.
|
Przykład
Poniższy przykład dzieli 8 przez 2. dividend 8 jest przechowywany w 16-bit AX register i divisor 2 jest przechowywany w 8-bit BL register.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,'8'
sub ax, '0'
mov bl, '2'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The result is:
4
Zestaw instrukcji procesora zawiera instrukcje AND, OR, XOR, TEST i NOT logikę Boole'a, która testuje, ustawia i czyści bity zgodnie z potrzebami programu.
Format tych instrukcji -
Sr.No. | Instrukcja | Format |
---|---|---|
1 | I | AND operand1, operand2 |
2 | LUB | OR operand1, operand2 |
3 | XOR | XOR operand1, operand2 |
4 | TEST | TEST operand1, operand2 |
5 | NIE | NIE operand1 |
Pierwszy operand we wszystkich przypadkach może znajdować się w rejestrze lub w pamięci. Drugi operand może znajdować się w rejestrze / pamięci lub mieć wartość bezpośrednią (stałą). Jednak operacje wymiany pamięci nie są możliwe. Te instrukcje porównują lub dopasowują bity operandów i ustawiają flagi CF, OF, PF, SF i ZF.
Instrukcja AND
Instrukcja AND jest używana do obsługi wyrażeń logicznych przez wykonywanie operacji bitowej AND. Operacja bitowa AND zwraca 1, jeśli pasujące bity z obu operandów wynoszą 1, w przeciwnym razie zwraca 0. Na przykład -
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
Operacja AND może służyć do kasowania jednego lub więcej bitów. Na przykład, powiedzmy, że rejestr BL zawiera 0011 1010. Jeśli chcesz skasować bity wyższego rzędu do zera, to ORAZ to z 0FH.
AND BL, 0FH ; This sets BL to 0000 1010
Weźmy inny przykład. Jeśli chcesz sprawdzić, czy podana liczba jest nieparzysta czy parzysta, prostym testem byłoby sprawdzenie najmniej znaczącego bitu liczby. Jeśli jest to 1, liczba jest nieparzysta, w przeciwnym razie liczba jest parzysta.
Zakładając, że numer jest w rejestrze AL, możemy napisać -
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
Poniższy program ilustruje to -
Przykład
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax, 8h ;getting 8 in the ax
and ax, 1 ;and ax with 1
jz evnn
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, odd_msg ;message to write
mov edx, len2 ;length of message
int 0x80 ;call kernel
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, even_msg ;message to write
mov edx, len1 ;length of message
int 0x80 ;call kernel
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
even_msg db 'Even Number!' ;message showing even number
len1 equ $ - even_msg odd_msg db 'Odd Number!' ;message showing odd number len2 equ $ - odd_msg
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Even Number!
Zmień wartość w rejestrze ax na nieparzystą cyfrę, na przykład -
mov ax, 9h ; getting 9 in the ax
Program wyświetli:
Odd Number!
Podobnie, aby wyczyścić cały rejestr, możesz ORAZ to zrobić za pomocą 00H.
Instrukcja OR
Instrukcja OR jest używana do obsługi wyrażeń logicznych przez wykonywanie operacji bitowej OR. Operator bitowy OR zwraca 1, jeśli pasujące bity z jednego lub obu operandów są jednością. Zwraca 0, jeśli oba bity są równe zero.
Na przykład,
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
Operacja OR może być używana do ustawiania jednego lub więcej bitów. Na przykład załóżmy, że rejestr AL zawiera 0011 1010, musisz ustawić cztery najmniej znaczące bity, możesz OR na wartość 0000 1111, czyli FH.
OR BL, 0FH ; This sets BL to 0011 1111
Przykład
Poniższy przykład ilustruje instrukcję OR. Zapiszmy wartość 5 i 3 odpowiednio w rejestrach AL i BL, a następnie instrukcję,
OR AL, BL
powinien przechowywać 7 w rejestrze AL -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al, 5 ;getting 5 in the al
mov bl, 3 ;getting 3 in the bl
or al, bl ;or al and bl registers, result should be 7
add al, byte '0' ;converting decimal to ascii
mov [result], al
mov eax, 4
mov ebx, 1
mov ecx, result
mov edx, 1
int 0x80
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
result resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
7
Instrukcja XOR
Instrukcja XOR implementuje bitową operację XOR. Operacja XOR ustawia wynikowy bit na 1, wtedy i tylko wtedy, gdy bity argumentów są różne. Jeśli bity z operandów są takie same (oba 0 lub oba 1), wynikowy bit jest zerowany.
Na przykład,
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
XORing operand sam ze sobą zmienia operand na 0. Służy do czyszczenia rejestru.
XOR EAX, EAX
Instrukcja TEST
Instrukcja TEST działa tak samo jak operacja AND, ale w przeciwieństwie do instrukcji AND nie zmienia pierwszego operandu. Jeśli więc musimy sprawdzić, czy liczba w rejestrze jest parzysta czy nieparzysta, możemy to również zrobić za pomocą instrukcji TEST bez zmiany pierwotnej liczby.
TEST AL, 01H
JZ EVEN_NUMBER
Instrukcja NOT
Instrukcja NOT implementuje operację bitową NOT. Operacja NOT odwraca bity w operandzie. Operand może znajdować się w rejestrze lub w pamięci.
Na przykład,
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
Warunkowe wykonanie w języku asemblera jest realizowane przez kilka instrukcji pętlowych i rozgałęziających. Te instrukcje mogą zmienić przepływ sterowania w programie. Warunkowe wykonanie jest obserwowane w dwóch scenariuszach -
Sr.No. | Instrukcje warunkowe |
---|---|
1 | Unconditional jump Jest to realizowane przez instrukcję JMP. Warunkowe wykonanie często wiąże się z przekazaniem kontroli na adres instrukcji, która nie następuje po aktualnie wykonywanej instrukcji. Przeniesienie kontroli może nastąpić do przodu w celu wykonania nowego zestawu instrukcji lub do tyłu w celu ponownego wykonania tych samych kroków. |
2 | Conditional jump Jest to wykonywane przez zestaw instrukcji skoku j <warunek> w zależności od warunku. Instrukcje warunkowe przekazują sterowanie przez przerwanie przepływu sekwencyjnego i robią to poprzez zmianę wartości przesunięcia w IP. |
Omówmy instrukcję CMP przed omówieniem instrukcji warunkowych.
Instrukcja CMP
Instrukcja CMP porównuje dwa operandy. Jest zwykle używany w wykonywaniu warunkowym. Ta instrukcja zasadniczo odejmuje jeden operand od drugiego w celu porównania, czy operandy są równe, czy nie. Nie zakłóca operandów celu ani źródła. Jest używany wraz z instrukcją warunkowego skoku do podejmowania decyzji.
Składnia
CMP destination, source
CMP porównuje dwa numeryczne pola danych. Operand docelowy może znajdować się w rejestrze lub w pamięci. Operand źródłowy może być stałymi (natychmiastowymi) danymi, rejestrem lub pamięcią.
Przykład
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMP jest często używany do porównywania, czy wartość licznika osiągnęła liczbę razy, gdy pętla musi zostać uruchomiona. Rozważ następujący typowy stan -
INC EDX
CMP EDX, 10 ; Compares whether the counter has reached 10
JLE LP1 ; If it is less than or equal to 10, then jump to LP1
Bezwarunkowy skok
Jak wspomniano wcześniej, jest to wykonywane przez instrukcję JMP. Warunkowe wykonanie często wiąże się z przekazaniem kontroli na adres instrukcji, która nie następuje po aktualnie wykonywanej instrukcji. Przeniesienie kontroli może nastąpić do przodu w celu wykonania nowego zestawu instrukcji lub do tyłu w celu ponownego wykonania tych samych kroków.
Składnia
Instrukcja JMP zapewnia nazwę etykiety, do której przepływ sterowania jest przesyłany natychmiast. Składnia instrukcji JMP to -
JMP label
Przykład
Poniższy fragment kodu ilustruje instrukcję JMP -
MOV AX, 00 ; Initializing AX to 0
MOV BX, 00 ; Initializing BX to 0
MOV CX, 01 ; Initializing CX to 1
L20:
ADD AX, 01 ; Increment AX
ADD BX, AX ; Add AX to BX
SHL CX, 1 ; shift left CX, this in turn doubles the CX value
JMP L20 ; repeats the statements
Skok warunkowy
Jeśli jakiś określony warunek jest spełniony w skoku warunkowym, przepływ sterowania jest przekazywany do instrukcji docelowej. W zależności od stanu i danych istnieje wiele warunkowych instrukcji skoku.
Poniżej znajdują się warunkowe instrukcje skoku używane na podpisanych danych używanych do operacji arytmetycznych -
Instrukcja | Opis | Testowane flagi |
---|---|---|
JE / JZ | Jump Equal lub Jump Zero | ZF |
JNE / JNZ | Skok nie równy lub skok nie zerowy | ZF |
JG / JNLE | Skacz większy lub nie mniejszy / równy | OF, SF, ZF |
JGE / JNL | Skacz większy / równy lub nie mniejszy | OF, SF |
JL / JNGE | Skacz mniej lub skacz nie większy / równy | OF, SF |
JLE / JNG | Skocz mniej / równo lub skocz nie wyżej | OF, SF, ZF |
Poniżej znajdują się instrukcje warunkowego skoku używane na danych bez znaku używanych do operacji logicznych -
Instrukcja | Opis | Testowane flagi |
---|---|---|
JE / JZ | Jump Equal lub Jump Zero | ZF |
JNE / JNZ | Skok nie równy lub skok nie zerowy | ZF |
JA / JNBE | Skocz powyżej lub skocz nie poniżej / równy | CF, ZF |
JAE / JNB | Skocz powyżej / równo lub nie poniżej | CF |
JB / JNAE | Skocz poniżej lub skocz nie powyżej / równo | CF |
JBE / JNA | Skocz poniżej / równo lub skocz nie powyżej | AF, CF |
Poniższe instrukcje warunkowego skoku mają specjalne zastosowania i sprawdzają wartość flag -
Instrukcja | Opis | Testowane flagi |
---|---|---|
JXCZ | Skocz, jeśli CX wynosi zero | Żaden |
JC | Jump If Carry | CF |
JNC | Jump If No Carry | CF |
JO | Skocz, jeśli przepełnienie | Z |
JNO | Skocz, jeśli nie ma przepełnienia | Z |
JP / JPE | Skok z parzystością lub Parzysty skok z parzystością | PF |
JNP / JPO | Skok bez parzystości lub Skok z parzystością Odd | PF |
JS | Znak skoku (wartość ujemna) | SF |
JNS | Jump No Sign (wartość dodatnia) | SF |
Składnia zestawu instrukcji J <condition> -
Przykład,
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
Przykład
Poniższy program wyświetla największą z trzech zmiennych. Zmienne są zmiennymi dwucyfrowymi. Trzy zmienne num1, num2 i num3 mają odpowiednio wartości 47, 22 i 31 -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx, [num1]
cmp ecx, [num2]
jg check_third_num
mov ecx, [num2]
check_third_num:
cmp ecx, [num3]
jg _exit
mov ecx, [num3]
_exit:
mov [largest], ecx
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,largest
mov edx, 2
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1
int 80h
section .data
msg db "The largest digit is: ", 0xA,0xD
len equ $- msg
num1 dd '47'
num2 dd '22'
num3 dd '31'
segment .bss
largest resb 2
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The largest digit is:
47
Do implementacji pętli można użyć instrukcji JMP. Na przykład poniższego fragmentu kodu można użyć do wykonania treści pętli 10 razy.
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
Zestaw instrukcji procesora zawiera jednak grupę instrukcji pętli do implementacji iteracji. Podstawowa instrukcja LOOP ma następującą składnię -
LOOP label
Gdzie etykieta jest etykietą docelową, która identyfikuje instrukcję docelową, tak jak w instrukcjach skoku. Instrukcja LOOP zakłada, żeECX register contains the loop count. Gdy wykonywana jest instrukcja pętli, rejestr ECX jest zmniejszany, a sterowanie przeskakuje do etykiety docelowej, aż wartość rejestru ECX, tj. Licznik osiągnie wartość zero.
Powyższy fragment kodu można zapisać jako -
mov ECX,10
l1:
<loop body>
loop l1
Przykład
Poniższy program drukuje cyfry od 1 do 9 na ekranie -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,10
mov eax, '1'
l1:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
inc eax
add eax, '0'
pop ecx
loop l1
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
num resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
123456789:
Dane liczbowe są zwykle przedstawiane w systemie binarnym. Instrukcje arytmetyczne działają na danych binarnych. Liczby wyświetlane na ekranie lub wprowadzane z klawiatury mają postać ASCII.
Do tej pory przekonwertowaliśmy te dane wejściowe w postaci ASCII na binarne do obliczeń arytmetycznych i przekonwertowaliśmy wynik z powrotem na binarny. Poniższy kod pokazuje to -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The sum is:
7
Takie konwersje mają jednak narzut, a programowanie w języku asemblerowym umożliwia przetwarzanie liczb w bardziej efektywny sposób, w postaci binarnej. Liczby dziesiętne można przedstawić w dwóch formach -
- Formularz ASCII
- BCD lub postać dziesiętna w kodzie binarnym
Reprezentacja ASCII
W reprezentacji ASCII liczby dziesiętne są przechowywane jako ciąg znaków ASCII. Na przykład wartość dziesiętna 1234 jest przechowywana jako -
31 32 33 34H
Gdzie 31H to wartość ASCII dla 1, 32H to wartość ASCII dla 2 i tak dalej. Istnieją cztery instrukcje przetwarzania liczb w reprezentacji ASCII -
AAA - Regulacja ASCII po dodaniu
AAS - Regulacja ASCII po odjęciu
AAM - Regulacja ASCII po pomnożeniu
AAD - Regulacja ASCII przed dzieleniem
Te instrukcje nie pobierają żadnych operandów i zakładają, że wymagany operand znajduje się w rejestrze AL.
Poniższy przykład używa instrukcji AAS, aby zademonstrować koncepcję -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
sub ah, ah
mov al, '9'
sub al, '3'
aas
or al, 30h
mov [res], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,res ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Result is:',0xa
len equ $ - msg
section .bss
res resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The Result is:
6
Reprezentacja BCD
Istnieją dwa rodzaje reprezentacji BCD -
- Rozpakowana reprezentacja BCD
- Zapakowana reprezentacja BCD
W rozpakowanej reprezentacji BCD każdy bajt przechowuje binarny odpowiednik cyfry dziesiętnej. Na przykład liczba 1234 jest przechowywana jako -
01 02 03 04H
Istnieją dwie instrukcje dotyczące przetwarzania tych liczb -
AAM - Regulacja ASCII po pomnożeniu
AAD - Regulacja ASCII przed dzieleniem
Cztery instrukcje dopasowania ASCII, AAA, AAS, AAM i AAD, mogą być również używane z rozpakowaną reprezentacją BCD. W spakowanej reprezentacji BCD każda cyfra jest przechowywana w czterech bitach. W bajcie pakowane są dwie cyfry dziesiętne. Na przykład liczba 1234 jest przechowywana jako -
12 34H
Istnieją dwie instrukcje dotyczące przetwarzania tych liczb -
DAA - Korekta dziesiętna po dodaniu
DAS - Regulacja dziesiętna po odjęciu
Nie ma obsługi mnożenia i dzielenia w spakowanej reprezentacji BCD.
Przykład
Poniższy program dodaje dwie 5-cyfrowe liczby dziesiętne i wyświetla sumę. Wykorzystuje powyższe koncepcje -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov esi, 4 ;pointing to the rightmost digit
mov ecx, 5 ;num of digits
clc
add_loop:
mov al, [num1 + esi]
adc al, [num2 + esi]
aaa
pushf
or al, 30h
popf
mov [sum + esi], al
dec esi
loop add_loop
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,5 ;message length
mov ecx,sum ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Sum is:',0xa
len equ $ - msg
num1 db '12345'
num2 db '23456'
sum db ' '
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The Sum is:
35801
W naszych poprzednich przykładach używaliśmy już ciągów o zmiennej długości. Łańcuchy o zmiennej długości mogą mieć dowolną liczbę znaków. Ogólnie rzecz biorąc, określamy długość ciągu na jeden z dwóch sposobów -
- Jawne przechowywanie długości łańcucha
- Używanie znaku wartowniczego
Możemy jawnie przechowywać długość ciągu, używając symbolu licznika lokalizacji $, który reprezentuje bieżącą wartość licznika lokalizacji. W poniższym przykładzie -
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$ wskazuje bajt po ostatnim znaku zmiennej łańcuchowej msg . W związku z tym,$-msgpodaje długość sznurka. Możemy też pisać
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
Alternatywnie można przechowywać ciągi z końcowym znakiem wartownika, aby oddzielić ciąg, zamiast jawnie przechowywać długość ciągu. Znak wartownika powinien być znakiem specjalnym, który nie występuje w ciągu.
Na przykład -
message DB 'I am loving it!', 0
Instrukcje dotyczące strun
Każda instrukcja łańcuchowa może wymagać operandu źródłowego, operandu docelowego lub obu. W przypadku segmentów 32-bitowych instrukcje łańcuchowe używają rejestrów ESI i EDI do wskazywania odpowiednio operandów źródłowych i docelowych.
Jednak w przypadku segmentów 16-bitowych rejestry SI i DI są używane do wskazywania odpowiednio źródła i celu.
Istnieje pięć podstawowych instrukcji dotyczących przetwarzania łańcuchów. Oni są -
MOVS - Ta instrukcja przenosi 1 bajt, słowo lub podwójne słowo danych z komórki pamięci do innej.
LODS- Ta instrukcja ładuje się z pamięci. Jeśli operand jest jednobajtowy, jest ładowany do rejestru AL, jeśli operand jest jednym słowem, jest ładowany do rejestru AX, a podwójne słowo jest ładowane do rejestru EAX.
STOS - Ta instrukcja przechowuje dane z rejestru (AL, AX lub EAX) do pamięci.
CMPS- Ta instrukcja porównuje dwa elementy danych w pamięci. Dane mogą mieć rozmiar bajtowy, słowo lub podwójne słowo.
SCAS - Ta instrukcja porównuje zawartość rejestru (AL, AX lub EAX) z zawartością pozycji w pamięci.
Każda z powyższych instrukcji ma wersję zawierającą bajty, słowo i podwójne słowo, a instrukcje łańcuchowe można powtórzyć, używając przedrostka powtórzenia.
Te instrukcje używają pary rejestrów ES: DI i DS: SI, gdzie rejestry DI i SI zawierają ważne adresy przesunięcia, które odnoszą się do bajtów przechowywanych w pamięci. SI jest zwykle skojarzona z DS (segmentem danych), a DI jest zawsze związana z ES (segment dodatkowy).
Rejestry DS: SI (lub ESI) i ES: DI (lub EDI) wskazują odpowiednio operandy źródłowe i docelowe. Zakłada się, że operand źródłowy znajduje się w DS: SI (lub ESI), a operand docelowy w ES: DI (lub EDI) w pamięci.
W przypadku adresów 16-bitowych używane są rejestry SI i DI, a w przypadku adresów 32-bitowych - rejestry ESI i EDI.
Poniższa tabela zawiera różne wersje instrukcji łańcuchowych i zakładaną przestrzeń operandów.
Podstawowa instrukcja | Operandy w | Operacja na bajtach | Operacja na słowie | Operacja podwójnego słowa |
---|---|---|---|---|
MOVS | ES: DI, DS: SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS: SI | LODSB | LODSW | LODSD |
STOS | ES: DI, AX | STOSB | STOSW | STOSD |
CMPS | DS: SI, ES: DI | CMPSB | CMPSW | CMPSD |
SCAS | ES: DI, AX | SCASB | SCASW | SCASD |
Prefiksy powtórzeń
Prefiks REP ustawiony przed instrukcją łańcuchową, na przykład REP MOVSB, powoduje powtórzenie instrukcji na podstawie licznika umieszczonego w rejestrze CX. REP wykonuje instrukcję, zmniejsza CX o 1 i sprawdza, czy CX wynosi zero. Powtarza przetwarzanie instrukcji, aż CX będzie wynosić zero.
Flaga kierunku (DF) określa kierunek operacji.
- Użyj CLD (Clear Direction Flag, DF = 0), aby wykonać operację od lewej do prawej.
- Użyj STD (Ustaw flagę kierunku, DF = 1), aby wykonać operację od prawej do lewej.
Prefiks REP ma również następujące odmiany:
REP: To bezwarunkowa powtórka. Powtarza operację, aż wartość CX wynosi zero.
REPE lub REPZ: Jest to warunkowe powtórzenie. Powtarza operację, podczas gdy flaga zero wskazuje równość / zero. Zatrzymuje się, gdy ZF wskazuje, że nie jest równe / zero lub gdy CX wynosi zero.
REPNE lub REPNZ: Jest to również powtórzenie warunkowe. Powtarza operację, podczas gdy flaga zero wskazuje, że nie jest równa / zero. Zatrzymuje się, gdy ZF wskazuje równe / zero lub gdy wartość CX jest zmniejszana do zera.
Omówiliśmy już, że dyrektywy definicji danych do asemblera służą do przydzielania pamięci dla zmiennych. Zmienną można również zainicjować określoną wartością. Zainicjowaną wartość można określić w postaci szesnastkowej, dziesiętnej lub binarnej.
Na przykład, możemy zdefiniować zmienną słowo „miesiące” w jeden z następujących sposobów -
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
Dyrektywy definicji danych mogą być również używane do definiowania jednowymiarowej tablicy. Zdefiniujmy jednowymiarową tablicę liczb.
NUMBERS DW 34, 45, 56, 67, 75, 89
Powyższa definicja deklaruje tablicę sześciu słów, z których każde jest zainicjowane numerami 34, 45, 56, 67, 75, 89. To alokuje 2x6 = 12 bajtów kolejnej pamięci. Symboliczny adres pierwszej liczby to LICZBY, a adres drugiej liczby to LICZBY + 2 i tak dalej.
Weźmy inny przykład. Możesz zdefiniować tablicę o nazwie spis o rozmiarze 8 i zainicjować wszystkie wartości zerem, ponieważ -
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
Który można skrócić jako -
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
Dyrektywy TIMES można również użyć do wielu inicjalizacji tej samej wartości. Używając TIMES, tablicę INVENTORY można zdefiniować jako:
INVENTORY TIMES 8 DW 0
Przykład
Poniższy przykład demonstruje powyższe koncepcje, definiując 3-elementową tablicę x, która przechowuje trzy wartości: 2, 3 i 4. Dodaje wartości do tablicy i wyświetla sumę 9 -
section .text
global _start ;must be declared for linker (ld)
_start:
mov eax,3 ;number bytes to be summed
mov ebx,0 ;EBX will store the sum
mov ecx, x ;ECX will point to the current element to be summed
top: add ebx, [ecx]
add ecx,1 ;move pointer to next element
dec eax ;decrement counter
jnz top ;if counter not 0, then loop again
done:
add ebx, '0'
mov [sum], ebx ;done, store result in "sum"
display:
mov edx,1 ;message length
mov ecx, sum ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
global x
x:
db 2
db 4
db 3
sum:
db 0
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
9
Procedury lub podprogramy są bardzo ważne w języku asemblerowym, ponieważ programy w asemblerze mają zwykle duże rozmiary. Procedury są identyfikowane nazwą. Po tej nazwie opisana jest treść procedury, która wykonuje dobrze zdefiniowaną pracę. Zakończenie procedury sygnalizowane jest zwrotem.
Składnia
Poniżej znajduje się składnia definiująca procedurę -
proc_name:
procedure body
...
ret
Procedura jest wywoływana z innej funkcji za pomocą instrukcji CALL. Instrukcja CALL powinna mieć jako argument nazwę wywoływanej procedury, jak pokazano poniżej -
CALL proc_name
Wywołana procedura zwraca sterowanie do procedury wywołującej przy użyciu instrukcji RET.
Przykład
Napiszmy bardzo prostą procedurę o nazwie sum, która dodaje zmienne zapisane w rejestrze ECX i EDX i zwraca sumę w rejestrze EAX -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,'4'
sub ecx, '0'
mov edx, '5'
sub edx, '0'
call sum ;call sum procedure
mov [res], eax
mov ecx, msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx, res
mov edx, 1
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
sum:
mov eax, ecx
add eax, edx
add eax, '0'
ret
section .data
msg db "The sum is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The sum is:
9
Struktura danych stosów
Stos to macierzowa struktura danych w pamięci, w której dane mogą być przechowywane i usuwane z miejsca zwanego „górą” stosu. Dane, które mają być przechowywane, są „wypychane” do stosu, a dane do pobrania są „wyrzucane” ze stosu. Stos to struktura danych LIFO, tj. Dane zapisane jako pierwsze są pobierane jako ostatnie.
Język asemblera udostępnia dwie instrukcje dotyczące operacji na stosie: PUSH i POP. Te instrukcje mają składnię taką jak -
PUSH operand
POP address/register
Przestrzeń pamięci zarezerwowana w segmencie stosu jest używana do implementacji stosu. Rejestry SS i ESP (lub SP) są używane do implementacji stosu. Szczyt stosu, który wskazuje na ostatni element danych wstawiony do stosu, jest wskazywany przez rejestr SS: ESP, gdzie rejestr SS wskazuje początek segmentu stosu, a SP (lub ESP) podaje przesunięcie do segment stosu.
Implementacja stosu ma następujące cechy -
Tylko words lub doublewords można zapisać na stosie, a nie bajt.
Stos rośnie w odwrotnym kierunku, tj. W kierunku niższego adresu pamięci
Góra stosu wskazuje na ostatni element włożony do stosu; wskazuje na niższy bajt ostatniego wstawionego słowa.
Jak rozmawialiśmy o przechowywaniu wartości rejestrów w stosie przed użyciem ich do jakiegoś użytku; można to zrobić w następujący sposób -
; Save the AX and BX registers in the stack
PUSH AX
PUSH BX
; Use the registers for other purpose
MOV AX, VALUE1
MOV BX, VALUE2
...
MOV VALUE1, AX
MOV VALUE2, BX
; Restore the original values
POP BX
POP AX
Przykład
Poniższy program wyświetla cały zestaw znaków ASCII. Główny program wywołuje procedurę o nazwie display , która wyświetla zestaw znaków ASCII.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
call display
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
display:
mov ecx, 256
next:
push ecx
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 80h
pop ecx
mov dx, [achar]
cmp byte [achar], 0dh
inc byte [achar]
loop next
ret
section .data
achar db '0'
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
Procedura rekurencyjna to taka, która sama siebie wywołuje. Istnieją dwa rodzaje rekursji: bezpośrednia i pośrednia. W przypadku rekursji bezpośredniej procedura wywołuje samą siebie, aw przypadku rekursji pośredniej pierwsza procedura wywołuje drugą procedurę, która z kolei wywołuje procedurę pierwszą.
Rekursję można było zaobserwować w wielu algorytmach matematycznych. Na przykład rozważmy przypadek obliczenia silni liczby. Silnia liczby jest określona równaniem -
Fact (n) = n * fact (n-1) for n > 0
Na przykład: silnia 5 to 1 x 2 x 3 x 4 x 5 = 5 x silnia 4 i może to być dobry przykład pokazujący procedurę rekurencyjną. Każdy algorytm rekurencyjny musi mieć warunek końcowy, tj. Rekurencyjne wywołanie programu powinno zostać zatrzymane, gdy warunek zostanie spełniony. W przypadku algorytmu silniowego warunek końcowy jest osiągany, gdy n wynosi 0.
Poniższy program pokazuje, jak silnia n jest implementowana w języku asemblera. Aby program był prosty, obliczymy silnię 3.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov bx, 3 ;for calculating factorial 3
call proc_fact
add ax, 30h
mov [fact], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,fact ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
proc_fact:
cmp bl, 1
jg do_calculation
mov ax, 1
ret
do_calculation:
dec bl
call proc_fact
inc bl
mul bl ;ax = al * bl
ret
section .data
msg db 'Factorial 3 is:',0xa
len equ $ - msg
section .bss
fact resb 1
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Factorial 3 is:
6
Pisanie makra to kolejny sposób na zapewnienie modułowego programowania w języku asemblera.
Makro to sekwencja instrukcji przypisanych nazwą i może być używane w dowolnym miejscu programu.
W NASM makra są definiowane za pomocą %macro i %endmacro dyrektyw.
Makro zaczyna się od dyrektywy% macro, a kończy na dyrektywie% endmacro.
Składnia definicji makr -
%macro macro_name number_of_params
<macro body>
%endmacro
Gdzie number_of_params określa parametry numeryczne, macro_name Określa nazwę makra.
Makro jest wywoływane przy użyciu nazwy makra wraz z niezbędnymi parametrami. Kiedy musisz wielokrotnie użyć sekwencji instrukcji w programie, możesz umieścić te instrukcje w makrze i używać go zamiast pisać instrukcje przez cały czas.
Na przykład bardzo powszechną potrzebą programów jest napisanie ciągu znaków na ekranie. Aby wyświetlić ciąg znaków, potrzebujesz następującej sekwencji instrukcji -
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
W powyższym przykładzie wyświetlania ciągu znaków, rejestry EAX, EBX, ECX i EDX zostały wykorzystane przez wywołanie funkcji INT 80H. Tak więc za każdym razem, gdy chcesz wyświetlić na ekranie, musisz zapisać te rejestry na stosie, wywołać INT 80H, a następnie przywrócić pierwotną wartość rejestrów ze stosu. Przydałoby się więc napisanie dwóch makr do zapisywania i przywracania danych.
Zauważyliśmy, że niektóre instrukcje, takie jak IMUL, IDIV, INT itp., Wymagają przechowywania niektórych informacji w określonych rejestrach, a nawet zwracania wartości w określonych rejestrach. Jeżeli program korzystał już z tych rejestrów do przechowywania ważnych danych, to istniejące dane z tych rejestrów należy zapisać na stosie i odtworzyć po wykonaniu instrukcji.
Przykład
Poniższy przykład pokazuje definiowanie i używanie makr -
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
write_string msg1, len1
write_string msg2, len2
write_string msg3, len3
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1 msg2 db 'Welcome to the world of,', 0xA,0xD len2 equ $- msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
System traktuje dane wejściowe lub wyjściowe jako strumień bajtów. Istnieją trzy standardowe strumienie plików -
- Wejście standardowe (stdin),
- Standardowe wyjście (stdout) i
- Błąd standardowy (stderr).
Deskryptor pliku
ZA file descriptorto 16-bitowa liczba całkowita przypisana do pliku jako identyfikator pliku. Gdy tworzony jest nowy plik lub otwierany jest istniejący plik, do uzyskania dostępu do pliku używany jest deskryptor pliku.
Deskryptor plików standardowych strumieni plików - stdin, stdout i stderr wynoszą odpowiednio 0, 1 i 2.
Wskaźnik pliku
ZA file pointerokreśla lokalizację kolejnej operacji odczytu / zapisu w pliku w bajtach. Każdy plik jest traktowany jako sekwencja bajtów. Każdy otwarty plik jest powiązany ze wskaźnikiem pliku, który określa przesunięcie w bajtach względem początku pliku. Kiedy plik jest otwierany, wskaźnik pliku jest ustawiany na zero.
Wywołania systemowe obsługi plików
Poniższa tabela pokrótce opisuje wywołania systemowe związane z obsługą plików -
% eax | Nazwa | % ebx | % ecx | % edx |
---|---|---|---|---|
2 | sys_fork | struct pt_regs | - | - |
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int | - | - |
8 | sys_creat | const char * | int | - |
19 | sys_lseek | unsigned int | off_t | unsigned int |
Kroki wymagane do korzystania z wywołań systemowych są takie same, jak omówiliśmy wcześniej -
- Umieść numer wywołania systemowego w rejestrze EAX.
- Przechowuj argumenty wywołania systemowego w rejestrach EBX, ECX itp.
- Wywołaj odpowiednie przerwanie (80h).
- Wynik jest zwykle zwracany w rejestrze EAX.
Tworzenie i otwieranie pliku
Aby utworzyć i otworzyć plik, wykonaj następujące czynności -
- Umieść wywołanie systemowe sys_creat () numer 8 w rejestrze EAX.
- Umieść nazwę pliku w rejestrze EBX.
- Umieść uprawnienia do plików w rejestrze ECX.
Wywołanie systemowe zwraca deskryptor utworzonego pliku w rejestrze EAX, w przypadku błędu kod błędu znajduje się w rejestrze EAX.
Otwieranie istniejącego pliku
Aby otworzyć istniejący plik, wykonaj następujące czynności -
- Umieść wywołanie systemowe sys_open () numer 5 w rejestrze EAX.
- Umieść nazwę pliku w rejestrze EBX.
- Umieść tryb dostępu do pliku w rejestrze ECX.
- Umieść uprawnienia do plików w rejestrze EDX.
Wywołanie systemowe zwraca deskryptor utworzonego pliku w rejestrze EAX, w przypadku błędu kod błędu znajduje się w rejestrze EAX.
Wśród trybów dostępu do plików najczęściej używane są: tylko do odczytu (0), tylko do zapisu (1) i do odczytu i zapisu (2).
Czytanie z pliku
Aby odczytać z pliku, wykonaj następujące czynności -
Umieść wywołanie systemowe sys_read () numer 3 w rejestrze EAX.
Umieść deskryptor pliku w rejestrze EBX.
Umieść wskaźnik na buforze wejściowym w rejestrze ECX.
Umieść rozmiar bufora, tj. Liczbę bajtów do odczytania, w rejestrze EDX.
Wywołanie systemowe zwraca liczbę bajtów odczytanych w rejestrze EAX, w przypadku błędu kod błędu znajduje się w rejestrze EAX.
Zapisywanie do pliku
Aby zapisać do pliku, wykonaj następujące czynności -
Umieść wywołanie systemowe sys_write () numer 4 w rejestrze EAX.
Umieść deskryptor pliku w rejestrze EBX.
Umieść wskaźnik na buforze wyjściowym w rejestrze ECX.
Umieść rozmiar bufora, tj. Liczbę bajtów do zapisu, w rejestrze EDX.
Wywołanie systemowe zwraca rzeczywistą liczbę bajtów zapisanych w rejestrze EAX, w przypadku błędu kod błędu znajduje się w rejestrze EAX.
Zamykanie pliku
Aby zamknąć plik, wykonaj następujące czynności -
- Umieść wywołanie systemowe sys_close () numer 6 w rejestrze EAX.
- Umieść deskryptor pliku w rejestrze EBX.
W przypadku błędu wywołanie systemowe zwraca kod błędu w rejestrze EAX.
Aktualizacja pliku
Aby zaktualizować plik, wykonaj następujące czynności -
- Umieść wywołanie systemowe sys_lseek () numer 19 w rejestrze EAX.
- Umieść deskryptor pliku w rejestrze EBX.
- Umieść wartość przesunięcia w rejestrze ECX.
- Umieść punkt odniesienia dla przesunięcia w rejestrze EDX.
Punktem odniesienia mogłoby być:
- Początek pliku - wartość 0
- Aktualna pozycja - wartość 1
- Koniec pliku - wartość 2
W przypadku błędu wywołanie systemowe zwraca kod błędu w rejestrze EAX.
Przykład
Poniższy program tworzy i otwiera plik o nazwie myfile.txt i zapisuje tekst „Welcome to Tutorials Point” w tym pliku. Następnie program odczytuje z pliku i zapisuje dane w buforze o nazwie info . Na koniec wyświetla tekst zapisany w info .
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
;create the file
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ;read, write and execute by all
int 0x80 ;call kernel
mov [fd_out], eax
; write into the file
mov edx,len ;number of bytes
mov ecx, msg ;message to write
mov ebx, [fd_out] ;file descriptor
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
; close the file
mov eax, 6
mov ebx, [fd_out]
; write the message indicating end of file write
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80
;open the file for reading
mov eax, 5
mov ebx, file_name
mov ecx, 0 ;for read only access
mov edx, 0777 ;read, write and execute by all
int 0x80
mov [fd_in], eax
;read from file
mov eax, 3
mov ebx, [fd_in]
mov ecx, info
mov edx, 26
int 0x80
; close the file
mov eax, 6
mov ebx, [fd_in]
int 0x80
; print the info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
file_name db 'myfile.txt'
msg db 'Welcome to Tutorials Point'
len equ $-msg
msg_done db 'Written to file', 0xa
len_done equ $-msg_done
section .bss
fd_out resb 1
fd_in resb 1
info resb 26
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Written to file
Welcome to Tutorials Point
Plik sys_brk()wywołanie systemowe jest dostarczane przez jądro, aby przydzielić pamięć bez potrzeby jej późniejszego przenoszenia. To wywołanie przydziela pamięć bezpośrednio za obrazem aplikacji w pamięci. Ta funkcja systemu pozwala ustawić najwyższy dostępny adres w sekcji danych.
To wywołanie systemowe przyjmuje jeden parametr, który jest najwyższym adresem pamięci wymaganym do ustawienia. Ta wartość jest przechowywana w rejestrze EBX.
W przypadku jakiegokolwiek błędu sys_brk () zwraca -1 lub sam zwraca ujemny kod błędu. Poniższy przykład ilustruje dynamiczną alokację pamięci.
Przykład
Poniższy program przydziela 16kb pamięci za pomocą funkcji systemowej sys_brk () -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, 45 ;sys_brk
xor ebx, ebx
int 80h
add eax, 16384 ;number of bytes to be reserved
mov ebx, eax
mov eax, 45 ;sys_brk
int 80h
cmp eax, 0
jl exit ;exit, if error
mov edi, eax ;EDI = highest available address
sub edi, 4 ;pointing to the last DWORD
mov ecx, 4096 ;number of DWORDs allocated
xor eax, eax ;clear eax
std ;backward
rep stosd ;repete for entire allocated area
cld ;put DF flag to normal state
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 80h ;print a message
exit:
mov eax, 1
xor ebx, ebx
int 80h
section .data
msg db "Allocated 16 kb of memory!", 10
len equ $ - msg
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Allocated 16 kb of memory!