<< 4. Praktikum | NASM Lehele | 6. Praktikum >>

NASM – Praktikum5: Libprax 1: memcpy, memset, itoa, ftoa, puti, putf

Selles praktikumis jätkame põhjalikumalt LIBPRAX-i arendust. Põhifookus on eeskätt Assembleri programmeerimisel ning uute makrode kasutamisel.

Alusfailid (Windows ja Linux): Prax5.zip

:/NASM/Prax5/
  `- Makefile
  `- libprax.h
  `- test.c
  ./libprax/
    `- io.asm
    `- lib.asm
    `- string.asm
    `- macros.inc

1. Kordusülevaade Makrodest

Eelmisest praktikumist on juurde lisandunud paar uut Makrofunktsiooni. Samuti on täiendatud ka eelnevaid makrosid:

GLOBALFUNC <func>, <arg1:type>, <arg2:type>, ...

Globalfunc definitsioon võimaldab argumentide deklareerimist. See teeb argumentide adresseerimise palju selgemaks, kuid eeldab PROLOGUE ülesseadmist. Argumentide adresseerimine käib . prefiksiga.

globalfunc _swap_ints, dst:dword, src:dword
	prologue
		; *dst = *src;
		mov     eax, .dst   ; mov eax, dword[ebp+8]
		mov		edx, .src   ; mov edx, dword[ebp+12]
		mov     edx, [edx]  ; edx  = *src
		mov		[eax], edx  ; *dst = edx
	epilogue

LOCAL <local1:type>, <local2:type>, ...

Local definitsioon võimaldab lokaalsete muutujate deklareerimist ENNE prologue kutsumist. See teeb lokaalsete muutujate üle valitsemise kergemaks ning jätab meie eest meelde nende asukohad. Prologue definitsiooni ei pea enam lisama lokaalsete baitide arvu - see arvutatakse automaatselt.

globalfunc _localstest, dst:dword, src:dword
	LOCAL a:dword, b:dword
	prologue
		mov		.a, 0	; mov dword[ebp-4], 0 ; a = 0;
		mov		.b, 1	; mov dword[ebp-8], 1 ; b = 0;
		
		mov		eax, .a	; mov eax, dword[ebp-4]
	epilogue

.if a == b | .elseif a > b | .else | .endif

If konstruktsioonid lihtsustavad loogilisi hüppeid ja lausekonstruktsioon lahendatakse sümbolparsimisega. Seega võime lihtsalt kirjutada: .if EAX != 0 .

	.if EAX == 5
		; ...
	.elseif EAX <= 4
		.if ECX > 48
			; nesting ifs allowed
		.endif
	.else
		; ...
	.endif

.while a == b | .endwhile

While tsükkel laenab If konstruktsioonide sümbolparsimist, kuid ei lisa juurde uusi funktsionaalsusi:

	.while byte[ebp+eax] != 0
		inc eax
	.endwhile

invoke _proc, arg1, arg2, arg3, ...

Invoke makro kasutab samuti sümbolparsimist, et aru saada kas on tegu CDECL võid STDCALL funktsiooniga. Seega sõltub invoke puhul nüüd calling convention lihtsalt funktsiooni nimest:

	invoke _cfunc, 10, 20	    ; CDECL
	invoke _stdfunc@8, 10, 20   ; STDCALL


2. LIBPRAX

Loengus mainitud LIBPRAXI moodulid mida järgnevate praktikumide jooksul arendame on järgnevad:

IO

int  aprints(const char* str);   // prindi konsooli sõne       @return N prinditud tähemärki
int  aputi(int i);               // prindi konsooli täisarv    @return N prinditud tähemärki
int  aputf(float f);             // prindi konsooli ujukomaarv @return N prinditud tähemärki

int  afopen(const char* file);   // ava fail                   @return faili handle
void afclose(int fh);            // sulge faile                @fh: faili handle  
int  afsize(int fh);             // faili suurus baitides      @return faili suurus

int  afread(int fh, void* dst, int nbytes);                   // loe failist baite
int  afwrite(int fh, const void* data, int size);             // kirjuta baite faili

LIB

int aatoi(const char* str);     // sõne täisarvuks  @return Täisarv
int aitoa(char* dst);           // täisarv sõneks   @return Sõne pikkus
int aatof(const char* str);     // sõne ujukomaks   @return Ujukomaarv
int aftoa(char* dst);           // ujukoma sõneks   @return Sõne pikkus

STRING

int  astrlen(const char* str);                                // @return Sõne pikkus
void amemcpy(void* dst, const void* src, int nbytes);         // mälubloki kopeerimine
void amemset(void* mem, int value, int nbytes);               // mälubloki väärtustamine
int  amemcmp(const void* memA, const void* memB, int nbytes); // mälubloki võrdlus


3. Mäluoperatsioonid

Kõige lihtsam on alustada mäluoperatsioonidest, mis asuvad string.asm failis. Selles praktikumis implementeerime memcpy ja memset funktsioonid. Selleks tuleb avada libprax.h ja lisada vastavate funktsioonide prototüübid:

libprax.h

...

//////// string.asm ////////
/**
 * Copy a non-overlapping block of memory from SRC to DST.
 * @param dst Pointer to destination
 * @param src Pointer to source
 * @param nbytes Number of bytes to copy
 */
void amemcpy(void* dst, const void* src, int nbytes);

/**
 * Set a block of memory to a specific byte value.
 * @param mem Pointer to memory
 * @param value Value of the byte to set
 * @param nbytes Number of bytes to set in [mem]
 */
void amemset(void* mem, int value, int nbytes);

...

ÜLESANNE: Implementeerida amemcpy ja amemset assembleris ning testida test.c programmis kasutades aprints funktsiooni.



4. Library funktsioonid

Kõvasti mahukam on implementeerida itoa - täisarvust sõneks muutmine. Antud puhul on väärtusi sõneks muuta palju lihtsam kui sõnesid väärtusteks tagasi lugeda. Kui oleme implementeerinud itoa, siis saame lahenduse kopeerida ning olemasoleva baasil kirjutada ka ftoa.

libprax.h

...

////////  lib.asm    ////////
/**
 * @brief Converts an integer to a string
 * @return Number of characters in the new string
 */
int aitoa(char* buffer, int value);

/**
 * @brief Converts a float to a string
 * @return Number of characters in the new string
 */
int aftoa(char* buffer, float value);

...

itoa ja ftoa C-s

Selleks, et implementeerimine oleks lihtsam, võtame ette antud funktsiooni kirjutatuna C-s:

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);
	
	*end = '\0'; // always null-terminate

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

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

ÜLESANNE: Implementeerida aitoa assembleris ning testida test.c programmis.

Funktsiooni aftoa ei pea veel implementeerima, kuna x86 Float Stacki uurime alles järgmises praktikumis.



5. IO funktsioonid

Viimane ülesanne on kõige lihtsam, kuna see sisaldab ainult aitoa ja aftoa kapseldamist funktsioonide puti ja putf funktsioonide sisse.

libprax.h

...

////////    io.asm    ////////
/**
 * @brief Prints an integer in the console
 * @return Number of characters printed
 */
int aputi(int i);

/**
 * @brief Prints a floating point number in the console
 * @return Number of characters printed
 */
int aputf(float f);

...

ÜLESANNE: Implementeerida aputi assembleris.
LISA: Implementeerida veel aputf.



6. Kokkuvõte

On näha, et makrod muudavad Assembleris programmeerimist järjest kergemaks ja kergemaks. Muidugi peab arvestama, et IF ja WHILE konstruktsioonid assembleris on aeglasemad kui läbimõeldud ja optimeeritud ASM. Siinkohal peame leidma kompromissi kiire ja aeglase koodi vahel.

Praktikumi lahendused (Windows ja Linux): Prax5-complete.zip

Järgmises praktikumis jätkame LIBPRAX-i arendamist.

<< 4. Praktikum | NASM Lehele | 6. Praktikum >>