Wstęp do programowania niskopoziomowego pod Windows

Wykonanie zadań wymaga zapoznania się z rejestrami procesora oraz funkcjami obsługiwanymi po wywołaniu przerwań 0x10, 0x16 i 0x21.

Pierwsze programy

Listing 1 Asemblerowe "Hello world!" - szablon pliku .COM

; wersja NASM: program zapisany w pliku tekstowym a.asm

; kompilacja: nasm -fbin -o a.com a.asm

org 0x100

section .data ; sekcja danych zainicjalizowanych

info: db "Hello world!",13, 10, '$' ; ciąg znaków zakończony znakiem dolara - wymagane przez funkcję ; o numerze 9, wyświetlającą ciąg, znaki 10 i 13 dają nową linię

section .bss ; sekcja danych niezainicjalizowanych

bufor: resb 5 ; bufor 5 bajtów

section .text ; sekcja kodu

mov ah, 9

mov dx, info

int 0x21 ; wywołaj przerwanie 0x21 - po załadowaniu 9 do AH wyświetlona będzie

; zawartość rejestru DX

mov ah,0

int 0x16 ; wywołaj przerwanie 0x16 - po załadowaniu 0 do AH program czeka

; na naciśnięcie klawisza

mov ax, 0x4C00 ; wyjście z programu z kodem błędu 0: w AH wpisane 4C, w AL wpisane 00,

; czyli kod wyjścia

int 0x21

Program napisany w szablonie .COM jest tłumaczony przez NASMa na wykonywalny plik binarny. Ograniczeniem na zastosowanie szablonu COM jest rozmiar wynikowego pliku binarnego (64K). Jeżeli program nie zmieści się w segmencie pamięci o rozmiarze 64KB musi być zbudowany jako plik .EXE.

NASM generuje format bin jako kod 16-bitowy. Z tego względu nie może on być uruchomiony na Windowsach 64 bitowych.

Listing 2 Asemblerowe "Hello world!" - szablon pliku .EXE

; To build:

; kompilacja nasm -fobj objexe.asm

; linkowanie val objexe.obj,objexe.exe;

segment code

..start: ; punkt wejściowy programu (dla linkera VAL)

mov ax,data ; inicjalizacja rejestrów - to jest standard

mov ds,ax

mov ax,stack

mov ss,ax

mov sp,stacktop

;kod użytkownika

mov dx,hello

mov ah,9

int 0x21

;koniec kodu użytkownika

mov ax,0x4c00 ; wyjście z programu

int 0x21

segment data ; segment danych

hello: db 'Hello world!', 13, 10, '$'

segment stack stack ; segment stosu

resb 64

stacktop:

Plik w szablonie .EXE jest kompilowany do pliku .OBJ, który należy następnie poddać linkowaniu. W przykładzie przedstawionym na Listingu 2 wykorzystano darmowy linker VAL.EXE. Informacje o innych linkerach można znaleźć tutaj.

Podstawowe operacje

MOV

mov eax, 3 ; zapis 3 do EAX (3 jest natychmiastowym operandem)

mov bx, ax ; zapis wartości z AX do BX

ADD

Instrukcję ADD używamy do dodawania liczb całkowitych.

add eax, 4 ; eax = eax + 4

add al, ah ; al = al + ah

SUB

Instrukcję SUB używamy do odejmowania liczb całkowitych.

sub bx, 10 ; bx = bx - 10

sub ebx, edi ; ebx = ebx - edi

INC i DEC

inc ecx ; ecx++

dec dl ; dl--

MUL i IMUL

Instrukcja MUL jest używana do mnożenia liczb całkowitych bez znaku, a instrukcja IMUL do mnożenia liczb całkowitych ze znakiem. Składnia instrukcji MUL i IMUL jest następująca:

mul source

imul dest, source, source1

Akcja wykonywana przez instrukcje mnożenia zależy od rozmiaru danych, co przedstawiono w Tabeli 1.

Tabela 1 Zestawy parametrów i wyniki instrukcji MUL (pierwsze 3 wiersze) i IMUL

mul cl ; AX = AL*CL

mul bx ; DX:AX = AX*BX

mul esi ; EDX:EAX = EAX*ESI

mul rdi ; RDX:RAX = RAX*RDI

imul eax ; EDX:EAX = EAX*EAX

imul ebx,ecx,2 ; EBX = ECX*2

imul ebx,ecx ; EBX = EBX*ECX

imul si,5 ; SI = SI*5

DIV i IDIV

Instrukcje DIV i IDIV wykonują dzielenia na liczbach całkowitych bez znaku (DIV) i ze znakiem (IDIV), wyliczając jednocześnie resztę z dzielenia. Składnia obu operacji jest identyczna:

div source

idiv source

Jeśli operand source jest 8-bitowy, to AX jest dzielony przez operand. Wynik dzielenia całkowitego jest umieszczany w AL a reszta z dzielenia w AH. Jeśli operand jest 16-bitowy, to dzielna znajduje się w DX:AX. Wynik dzielenia całkowitego jest umieszczany w AX a reszta w DX. Jeśli operand jest 32-bitowy, to dzielna jest pobierana z EDX:EAX, wynik trafia do EAX a reszta do EDX. Wynik nie mieszczący się w rejestrze powoduje błąd wykonania.

Przed wykonaniem dzielenia IDIV rejestry DX (EDX) należy zainicjalizować instrukcją cdq. Instrukcja ta rozszerza AX do DX:AX wykorzystując bit znaku z AX.

div cl ; AL = (AX div CL), AH = (AX mod CL)

div bx ; AX = (DX:AX div BX),

; DX = (DX:AX mod BX)

div edi ; EAX = (EDX:EAX div EDI),

; EDX = (EDX:EAX mod EDI)

div rsi ; RAX = (RDX:RAX div RSI),

; RDX = (RDX:RAX mod RSI)

Deklarowanie zmiennych

Informacje o deklarowaniu danych i tematach pokrewnych są dostępne po kliknięciu na poniższe linki:

Pamięć w programie asemblerowym może być zarezerwowana na dwa sposoby. W pierwszym przypadku rezerwujemy miejsce na dane, a w drugim: rezerwujemy miejsce i inicjalizujemy dane. Pierwsza metoda wykorzystuje dyrektywę RESX, gdzie X zastępujemy literą określającą rozmiar obiektu (byte B, word W, double word D, quad word Q, ten bytes T). Druga metoda wykorzystuje dyrektywę DX, gdzie X ma to samo znaczenie, jak w przypadku RESX. Zarezerwowane miejsca w pamięci oznacza się etykietami, które pozwalają w prosty sposób uzyskać dostęp do adresu komórki pamięci jak i do danych tam wpisanych.

L1 db 0 ; byte labeled L1 with initial value 0

L2 dw 1000 ; word labeled L2 with initial value 1000

L3 db 110101b ; byte initialized to binary 110101 (53 in decimal)

L4 db 12h ; byte initialized to hex 12 (18 in decimal)

L5 db 17o ; byte initialized to octal 17 (15 in decimal)

L6 dd 1A92h ; double word initialized to hex 1A92

L7 resb 1 ; 1 uninitialized byte

L8 db "A" ; byte initialized to ASCII code for A (65)

L9 db 0, 1, 2, 3 ; defines 4 bytes

L10 db "w", "o", "r", ’d’, 0 ; defines a C string = "word"

L11 db ’word’, 0 ; same as L10

L12 times 100 db 0 ; equivalent to 100 (db 0)’s

L13 resw 100 ; reserves room for 100 words

Etykiety używa się na dwa sposoby. Etykieta oznacza adres zmiennej, a więc pełni tą samą rolę co wskaźnik w C. Jeśli etykieta jest umieszczona w nawiasach kwadratowych, oznacza wartość wpisaną pod adresem skojarzonym z etykietą (* w C w połączeniu ze wskaźnikiem).

mov al, [L1] ; copy byte at L1 into AL

mov eax, L1 ; EAX = address of byte at L1

mov [L1], ah ; copy AH into byte at L1

mov eax, [L6] ; copy double word at L6 into EAX

add eax, [L6] ; EAX = EAX + double word at L6

add [L6], eax ; double word at L6 += EAX

mov al, [L6] ; copy first byte of double word at L6 into AL

Asembler nie sprawdza, czy etykiety są używane poprawnie, co może być źródłem wielu błędów. Przy próbie wpisywania danych do pamięci należy pamiętać o konwersji typów np.:

mov [L6], 1 ; próba zapisu 1 do L6 - niepoprawna, zwróci błąd operation size not specified

mov dword [L6], 1 ; próba zapisu 1 do L6 - poprawna

Wejście/wyjście

Wejście i wyjście z programu asemblerowego są, łagodnie rzecz ujmując, uciążliwe. Aby uprościć obsługę operacji wejścia/wyjścia, w programach asemblerowych można wywoływać funkcje C. Asemblerowy interfejs do funkcji printf i scanf jest zaimplementowany w bibliotece asm_io.

Tabela 2 Funkcje biblioteki asm_io

Po załadowaniu do rejestrów odpowiednich danych, procedury z Tabeli 2 woła się instrukcją call np.:

call print_int

Zastosowanie biblioteki asm_io wymaga pobrania paczki plików, z których modyfikowany będzie tylko plik asemblerowy, przedstawiony na Listingu 3.

Listing 3

%include "asm_io.inc" ;odpowiednik dyrektywy #include z C

segment .data

;

; dane zainicjalizowane

;

segment .bss

;

; dane niezainicjalizowane

;

segment .text

global _asm_main

_asm_main:

enter 0,0 ; setup

pusha

; ----

; Właściwy kod wstawiamy tu.

; Nie należy modyfikować kodu przed i po tym komentarzu

dump_regs 1 ; wypisuje zawartosc rejestrow (do debugowania)

; ---

popa

mov eax, 0 ; powrót do C

leave

ret

Na paczkę składają się pliki asm_io.asm, Listing3.asm, driver.c, cdecl.h i asm_io.inc. Zbudowanie programu .EXE wymaga wykonania kilku operacji:

nasm -felf -o asm_io.obj asm_io.asm

nasm -felf -o Listing3.obj Listing3.asm

gcc -o driver.obj -c driver.c

gcc -o Listing3.exe driver.obj asm_io.obj Listing3.obj

Zadanie 1

Proszę skompilować i uruchomić programy przedstawione na Listingach 1-3. Proszę zmodyfikować Listing 3, tak aby program wyrzucał na ekran tekst "Hello world!". Proszę zmodyfikować programy tak, aby pytały użytkownika o imię,a następnie wyświetlały formułkę powitalną, zawierającą imię (należy użyć funkcji 0ah przerwania 0x21).

Zadanie 2

Korzystając z funkcji biblioteki asm_io proszę napisać program czytający dwie liczby całkowite, sumujący je i wyrzucający wynik na ekran.

Zadanie 3

Proszę napisać program wczytujący liczbę z klawiatury, wyliczający jej kwadrat, sześcian, resztę z dzielenia przez zadaną liczbę...

Można używać asm_io.