<< 3. Praktikum | NASM Lehele | 5. Praktikum >>

NASM – Praktikum4: Makrod ja Static Link Library


Selles praktikumis käsitleme makrosid ning kirjutame omandatud teadmistega oma low-level library aluse. Assembleri funktsioonidest koosnev Static Link Library (.lib / .a) on üks praktilisemaid rakendusi assemblerile. Antud madala taseme Teek (Low-Level Library) saab ka meie tulevaste praktikumide aluseks.

1. Makrod

Netwide Assembler on vaikimisi Makroteekidest tühi, sellegipoolest leidub kümneid makroteeke mis laiendavad ja asendavad lakoonilise osa Assemblerist palju värvikama metakeelega. Meile tuntud C keeles on makrod väga piiratud tekstiasendus, kuid NASM-is lisanduvad tavaliste makrode juurde veel makro-funktsioonid, makrodel põhinev loogika ja aritmeetika. Samuti leiduvad konkreetsed stringidel põhinevad makrod, mis võimaldavad veelgi keerulisema metakeele kirjutamist.

Antud praktikumis jääme kahjuks ainult väga põhiliste makrode juurde, sest nende põhjalikuks tundmiseks läheks rohkem kui mitu praktikumi. Samuti keskendume ainult makrode kasutamisele ja mitte makrode kirjutamisele. Huvilised kes soovivad ise makrosid kirjutada, saavad lugeda kõikide makrode kohta NASM-i manuaalist: NASM Macros.

Makrode alusfailid: Prax4-macros.zip

:/NASM/Prax4-macros/
  `- Makefile
  `- macros.inc
  `- test_macros.asm

ÜLESANNE: Asendada programmis test_macros.asm asuvad proloog, epiloog, if/else/while, call, db vastavate makrokäskudega mis asuvad include failis "macros.inc". Uurige makrode faili iseseisvalt.



Lihtmakrod

Lihtmakrod on analoogid C-keele makrodele #define, #ifdef, #undef.

%define NormalDefine 1+2	; regular macro, expands to "1+2"
%undef NormalDefine			; undefines regular macro
%include "macros.inc"

%ifdef FEATURES_DEFAULT
	; enable only specific
%elifdef FEATURES_ALL
	; enable everything
%else
	%warning "You must specify FEATURES_*, assuming FEATURES_DEFAULT"
	%define FEATURES_DEFAULT	; revert to defaults
%endif

Lisaks veel: %idefine - IgnoreCase define. Loob makro mille nimi ei ole tõusutundlik

Aritmeetikamakrod

Aritmeetikamakrode abil saab teostada aritmeetilisi tehteid läbi makrode. Samuti saab seda kasutada makrotsüklite tegemiseks.

%assign ArithmeticDefine 1+2	; regular macro, expands to "3"
%undef ArithmeticDefine			
%if ArithmeticDefine > 0
	; do one thing
%elif ArithmeticDefine == 0
	; assume something bad?
%else
	; negative for some reason...
%endif

Korduskäsk

Repeat käsku saab kasutada makrotsüklite loomiseks.

%assign i 0
%assign j 1
%rep	100			; repeat up to 100 times
	%if j > 65535
		%exitrep	; break from the loop if j gets too big
	%endif
	
	push	j		; push J
	%assign i i+1	; ++i
	%assign j j+i*2 ; j += i*2
%endrep

Makrofunktsioonid

; MyMacro, 0 arguments
%macro prologue 0
	push	ebp
	mov		ebp, esp
%endmacro
%unmacro prologue		; undefine prologue macro

Makrofunktsioonide IgnoreCase varieteet on: %imacro, %unimacro
Lisaks on defineeritud ka makrofunktsioonide erilised muutujad:
%0 - Makrofunktsiooni sisestatud argumentide arv
%1 - 1. argument. %2 on teine argument jne.
%%label: - Makrolokaalne label
%$variable - Makrokontekstis olev muutuja (vt. kontekstipinu)

Kontekstipinu

Lisaks kõigele eelnevale eksisteerib ka kontekstipinu, mille abil saab makrode vahel makromuutujaid jagada. See on võimas tööriist, mille abil on võimalik implementeerida if/elif/else/while ja palju muud C-s tuttavat loogikat.

%macro scope 1
	%push	.scope				; create new context named ".scope"
	sub		esp, %1				; reserve %1 bytes for this scope
	%assign %$bytespushed %1	; .scope.$bytespushed = %1
%endmacro
%macro endscope 0
	%ifctx .scope
		add		esp, %$bytespushed	; cleanup
		%pop						; pop context ".scope"
	%else
		%error "expected 'scope' before 'endscope'"
	%endif
%endmacro

Stringitöötlusmakrod

Stringitöötluseks on eraldi suur hulk makrosid. Nendega saab realiseerida kõige keerulisemaid aspekte makro metakeeles.

%strlen HelloLength "hello"             ; expands to "5"
%strcat HelloWorld  "hello ", "world"   ; expands to "hello world",0
%substr OnlyWorld   "hello world", 6,5  ; expands to "world"
%substr MyChar      "hello world", 0,1  ; expands to "h"
%substr World       "hello world",-5,-1 ; expands to "world"
Lisaks veel vastavad tekstivõrdluse käsud:
%ifidn	%1, hello    ; if argument %1 == "hello"
	; ...
%endif
%ifidni %1, HeLLo    ; case insensitive %1 == "hello"
	; ...
%endif

Makrode Debugimine

Muidugi ei õnnestu makrode kirjutamine algul sajaprotsendiliselt. Selleks on võimalik NASM-iga teha lihtsalt makrotöötlust ning näidata tulemus meile STDOUT-is:

> nasm -f win32 -E test_macros.asm > test_macros-debug.txt

Praktikumi makrod

Muidugi ei pea kõiki elementaarseid makrosid igaüks ise kirjutama. Selleks on kasutada juba eelnevate aastate jooksul kirjutatud NASM-i praktikumi makrod 'macros.inc'. Väike näide makrode kasutamisest:

%include "macros.inc"
extern _printf
globalfunc _main, argc:dword, argv:dword
	local var1:dword
	uses ebx, esi, edi  ; mark regs to save
	prologue            ; create prologue, reserve locals, save regs
	
	; printf("%d\n", argc);
	invoke _printf, `argc=%d\n`, .argc
	
	; if (argc > 0)
	.if .argc > 0
	
		; i = 0;
		mov ebx, 0
		
		; while (i < argc)
		.while ebx < .argc
		
			; printf("argv[%d]=%s\n", i, argv[i]);
			mov     eax, .argv
			invoke  _printf, `argv[%d]=%s\n`, ebx, [eax + ebx*4]
			
			; ++i
			inc     ebx
		.endwhile
		
	.endif
	
	; var1 = 10;
	mov   .var1, 10
	
	; printf("var1=%d\n", var1);
	invoke _printf, `var1=%d\n`, .var1
	
	; return 0;
	xor   eax, eax
	epilogue           ; restore regs, create epilogue, return

Ülesande lahendus

Ülesanne lahendatud kujul: test_macros_complete.asm

2. Static Link Library

Üldiselt on assembleris hea luua taaskasutatavaid teeke, mis sisaldavad väikest või suurt kogumikku kiiretest ja optimeeritud funktsioonidest. Kõige lihtsam viis selle teostamiseks on luua just Static Link Library (.lib/.a), mis on sisult lihtsalt üks komposiitne [.o]. Oma teegiga peaksime kaasa panema headeri, mis on kirjutatud C-s. See teeb meie teegi API lihtsasti kättesaadavaks ning ka arusaadavamaks.
Rohkem lugemist Static Library linkimise kohta leiab siit: Static and Dynamic Libraries

Library alusfailid (Win32/Linux): Prax4-lib.zip

:/NASM/Prax4-lib/
  `- Makefile		
  `- io.asm             ;; IO functions of libprax
  `- lib.asm            ;; general functions of libprax
  `- string.asm         ;; string functions of libprax
  `- macros.inc         ;; useful macros
  `- libprax.h          ;; library header
  `- test.c

Kuidas luua uus Static Library?

Nagu mainitud, on [.lib] lihtsalt arhiiv mitmest [.obj] failist. Selle arhiivi loomiseks on olemas mitmeid tööriistu. Linuxi maailmas on ar (archiver), Windowsi maailmas on lib.exe (Microsoft Library Manager). Antud juhul poleks vahet kumba kasutame, aga kuna pürgime Linux/Windows ühilduvuse poole, siis oleks mõistlikum kasutada ar tööriista.

> nasm -f win32 test.asm -o test.obj
> nasm -f win32 other.asm -o other.obj
> ar rcs mylib.lib test.obj other.obj

> ar -t mylib.lib
  test.obj
  other.obj

RCS tähendab replace+create+index, mis tähendab et arhiivi sisu lihtsalt uuendatakse. Kasutades -t võtit näeme uue [.lib] faili sisu: test.obj ja other.obj.

Kuidas linkida Static Library oma programmi?

Linkimise ajal peavad teegid olema alati viimased, muidu ei vaata linker sümboleid õiges järjekorras. Antud juhul on see praktikumi Makefile'is loomikult õigesti pandud, kuid siiski on see tähtis meelde jätta.

> gcc test.c -o test.exe   mylib.lib


3. LIBPRAX arendus

Oleme jõudnud piisavalt kaugele, et võime alustada oma väikese assembler-teegi loomist. Nagu enne mainitud on nimeks valitud libprax ning järgneva paari praktikumi jooksul arendame ja lõhume just seda teeki. Vaikimisi on reegliks panna funktsiooni nimede ette a, nt: astrlen. Seda selleks, et vältida konflikte C standard library funktsioonidega mida me õppe eesmärgil duplitseerime.
Makefile on üles seatud nii, et C standard library-t programmi ei lingita. See on saavutatud kasutades linkerit ld, eemaldades kõik üleliigse koodi ning sümbolid.

Libprax koosneb esialgu kahest moodulist:
1. IO - algeline konsooli input/output
2. LIB - üldised helper funktsioonid
3. Strings - stringi manipuleerimisfunktsioonid

Kõige kergem oleks alustada Strings moodulist, kus on vaja implementeerida astrlen. Kuna oleme seda varem teinud, ei tohiks see raskust valmistada. Järgmisena peaks implementeerima IO mooduli aprints funktsiooni, mis prindib konsooli C-sõne.
Vimmaks peaksime implementeerima C-s tuntud abifunktsiooni itoa kujul: aitoa, mis muudab täisarvu (i) sõneks (ascii).

Makefile Seadistamine

Kahjuks pole Makefile seadistus täielik. Kuna oleme loobunud hetkeks C standard library kasutamisest ja kõikidest üleliigsetest sümbolitest, peame käsitsi määrama Win32 API moodulid mida laadida. Makefile'is on juba kirja pandud -luser32 ja -lkernel32. Mida on vaja aga igal ühel muuta, on MinGW /lib kaust:

LIBS = -LC:\MinGW\lib -lkernel32 -luser32

Kui see pole korrektselt seadistatud, siis MinGW linker ld ei leia Win32 API teeke üles.


strlen

Stringi pikkuse implementatsioon on juba paar korda näidetest läbi käinud. Seega peaks piisama minimaalsest C keeles pseudokoodist:

int __cdecl astrlen(const char* string)
{
    int len = 0;
    while (*string++)
        ++len;
    return len;
}

Ülesanne - Implementeerida astrlen kasutades makrosid.


aprints - Win32

Kuna oleme loobunud hetkeks C standard library kasutamisest, peame kuidagi ise saama Operatsioonisüsteemi IO-le ligi. Win32 API-s on IO jaoks funktsioon WriteFile. Kusjuures, ka konsooliakna väljund (STDOUT) on fail.

BOOL WINAPI WriteFile(
  _In_         HANDLE hFile,
  _In_         LPCVOID lpBuffer,
  _In_         DWORD nNumberOfBytesToWrite,
  _Out_opt_    LPDWORD lpNumberOfBytesWritten,
  _Inout_opt_  LPOVERLAPPED lpOverlapped
);
HANDLE WINAPI GetStdHandle(
  _In_  DWORD nStdHandle
);

Kasutamine C-s on üpriski keerukas, kuna STDOUT failideskriptor pole fikseeritud väärtusega. Õige STDOUT väärtuse saamiseks tuleb eelnevalt välja kutsuda GetStdHandle(STD_OUTPUT_HANDLE):

int __cdecl aprints(const char* string)
{ 
	DWORD written;
	static HANDLE STDOUT = 0;
	if (STDOUT == 0)  // lazy init
		STDOUT = GetStdHandle(-11/*STD_OUTPUT_HANDLE*/);
	
	WriteFile(STDOUT, string, astrlen(string), &written, 0);
	return written;
}

Ülesanne - Implementeerida aprints. Vihjeks: _WriteFile@20.

aprints - Linux

Linuxi platvormi peal on kõige kergem teha süsteemikutseid läbi interruptide. Pikem nimistu saadavatest interruptidest asub aadressil: http://syscalls.kernelgrok.com/. Antud juhul on tarvis sys_write interrupti:

            EAX     EBX       ECX                EDX
sys_write	0x04    int fd    const char* buf    size_t count

Linuxis on STDOUT kindlaks määratud väärtusega 1:

int __cdecl aprints(const char* string)
{ 
	return sys_write(0x04, 1, string, astrlen(string));
}

Viimane element on syscall interrupt väljakutsumine, mida tehakse käsuga int 0x80. Syscalli argumendid tuleb anda vastavates registrites:

mov eax, 0x01   ; SYS_EXIT (terminate process)
mov ebx, 0      ; exitcode: 0
int	0x80		; syscall interrupt

Ülesanne - Implementeerida aprints.


Kõik ülesanded lahendatud kujul: Prax4-lib-complete.zip


aitoa

C standard library funktsioon itoa muudab täisarvu sõneks ja on väga laialt kasutatud funktsioon. Selle implementeerimise juurde jõuame alles järgmises praktikumis. Hetkel võime vaadelda C implementatsiooni:

int aitoa(char* buffer, int value)
{
	char* start;
	char* rev;
	char* end = buffer;

	if (value < 0) // if neg, abs and writeout '-'
	{
		value = -value;	// flip sign
		*end++ = '-';	// neg
	}
	start = end; // mark start for strrev after:
	
	do // writeout remainder of 10 + '0' while we still have value
	{
		*end++ = '0' + (value % 10);
		value /= 10;
	} while (value != 0);

	// reverse the string:
	rev = end; // for strrev, we'll need a helper pointer
	while (start < rev)
	{
		char temp = *start;
		*start++ = *--rev;
		*rev = temp;
	}

	*end = '\0'; // always null-terminate

	return (int)(end - buffer); // length of the string
}

4. Kokkuvõte

Makrod on üks võimsam osa Netwide Assemblerist ja seal on veel palju ruumi mida avastada. Käesolevate praktikumide raames jääme aga ette valmistatud makrode kasutamise juurde. Vajadusel saame alati nende näidete kujul kirjutada keerulisemaid makrosid.

Static Linkage on väga kiire viis kuidas jagada eelvalmistatud teeke. Üldiselt on see ka just Assembleri puhul kõige parem väljund: väga kiired madala taseme funktsioonid mida saame C-s kasutada. Järgnevates praktikumides arendame edasi libprax teeki.

<< 3. Praktikum | NASM Lehele | 5. Praktikum >>