Assembly-funktioiden suoritus C/CPP-koodista

Mureakuha

Loikkaa: valikkoon, hakuun
Teknisistä rajoituksista johtuen artikkelin yllä näkyvä otsikko on virheellinen. Oikea otsikko on Assembly-funktioiden suoritus C/C++-koodista.

by Maniace

Sisällysluettelo

Johdanto

Esimerkeissä käytetään NASM- ja GCC-yhteensopivaa kääntäjää. Esittelen oppaassa muutaman NASM-makron, jotka ovat olleet hyödyllisiä (ja toistaiseksi toimivia) omassa käyttöjärjestelmäprojektissani. Mikäli olet jo niin pitkällä projektissasi, että sinulla on jo siinä Assembly- ja C/C++ -kieltä linkattuna ja painit funktiokutsujen kanssa, sinua tuskin tarvitsee erikseen opastaa linkkerin ja komentorivin käytössä tai tiedostojen luomisesta.

Luonnollisesti en vastaa mistää suorasti tai epäsuorasti aiheutuneesta vahingosta, luonnonkatastrofeista tai mahdollisista ihmishenkien menetyksistä, joita kyseisten makrojen käyttö voi aiheuttaa.

Yleistä

Kutsuttavan assembly-funktion täytyy olla kutsuvan koodin kanssa yhteensopivaa, ts. C-koodista kutsuttavan funktion täytyy toimia kuten C-funktio, muuten C-koodi ei toimi odotetulla tavalla enää funktion suorituksen loputtua. Pinon kanssa riehuessa täytyy olla varovainen, koska paluuosoite on pinossa. Tätähän voi tietysti myös käyttää hyväksi, mikäli haluatkin palata eri kohtaan kuin kutsuttaessa (setjmp()).

C-funktio toimii seuraavalla tavalla:

  1. Tallennetaan pino-osoitin (ESP)
    Mikäli pino syystä tai toisesta menee sekaisin, palautetaan oikea pino-osoittimen oikea arvo ja mitä suurimmalla todennäköisyydellä palaamme oikeaan paikkaan.
  2. Varataan pinosta tilaa paikallisille muuttujille
    Tämä tapahtuu pienentämällä pino-osoittimen arvoa varattavien tavujen verran.
  3. Tallennetaan rekisterit
    Funktiossa et voi käyttää rekistereitä EBX, EDI, ESI, EBP, DS, ES ja SS. Tämä rajoittaakin paljon, siksi suoritetaan nk. callee-save, eli kyseiset rekisterit tallennetaan pinoon. Rekistereitä EAX, ECX, EDX, FS, GS ja EFLAGS ei tarvitse tallentaa, eikä myöskään liukulukurekistereitä.
  4. Suoritetaan itse koodi
  5. Siirretään paluuarvo EAX-rekisteriin
  6. Palautetaan rekisterien arvot
  7. Palautetaan pino-osoittimen arvo
  8. Palataan suorittamaan kutsunutta koodia

Meidän makromme tulevat suorittamaan täsmälleen samat toiminnot, lukuunottamatta paikallisten muuttujien tilanvarausta. Sen voit tehdä itse harjoitustyönä. Lisää yksi parametri enemmän makrolle ja pienennät pino-osoittimen arvoa.

Makrot

Ja sitten itse makrot. Ensimmäisenä makro CGLOBAL, jolla kääntäjälle ilmoitetaan, että kyseinen muuttuja tai nimiö on globaali, ts. näkyy myös C-koodille.

%macro  CGLOBAL 1 
        GLOBAL _%1
        %define %1 _%1
%endmacro

GCC:llä on tapana käyttää alaviivoja muuttujien edessä, mikäli tapauksessasi ei näin ole, ei elämääsi voi helpottaa makrolla vaan käytät vain komentoa GLOBAL muuttujan_nimi.

Seuraavakin makro koskettaa vain alaviivojen kanssa taistelevien kooderien sisintä, ainakin se kosketti mua. *niisk*

%macro  CEXTERN 1
        EXTERN _%1
        %define %1 _%1
%endmacro

Makroja käytetään siis seuraavalla tavalla:

CGLOBAL-muuttuja

Ilmoitetaan ASM-kääntäjälle, että kyseessä on globaali muuttuja, jonka arvoa voidaan muuttaa myös C-koodista tai mikäli kyseessä on nimiö, kyseisen nimiötä voidaan käsitellä C-koodista(käyttää nimiön osoitetta, suorittaa sen kohdalla olevaa koodia, jne.).

CEXTERN-muuttuja

Ilmoitetaan ASM-kääntäjälle, että kyseessä on jossain muualla kuin kyseisessä tiedostossa oleva muuttuja. Käytännössä sama kuin CGLOBAL, mutta homma toimii toiseen suuntaan.

Sitten makro CFUNC, jolla aloitetaan funktio:

%macro CFUNC    1
GLOBAL _%1
_%1:	push    ebp		; Tallennetaan pino-osoitin
	mov     ebp, esp	

	push	ebx		; Callee-save
	push	edi
	push	esi
	push	ds
	push	es
	push	ss
%endmacro

Ja makro ENDFUNC, jolla lopetetaan funktio:

%macro	ENDFUNC  0
	pop	ss
	pop	es
	pop	ds
	pop	esi
	pop	edi
	pop	ebx
	leave
	ret
%endmacro

Kahta ylläolevaa makroa käytetään siis syntaksilla:

CFUNC 	crash_win_9x
loop: 	cli
	jmp	loop
ENDFUNC

Tosin ylläolevassa esimerkissä funktion suoritus ei koskaan lopu, mutta ymmärsit varmaan idean.

Viimeisenä, mutta ei suinkaan vähäisimpänä makro CVAR, jota käytämme funktion parametrien käsittelyyn:

%macro	CVAR	2
	mov	dword %1, [ebp + 8 + (%2 * 4)]
%endmacro

Jos siis haluamme siirtää ensimmäisen parametrin EAX-rekisteriin, käyttö on seuraava:

	CVAR	EAX, 0

Kuten huomasit, ensimmäinen parametri on 0, toinen on 1, jne. Parametri 3 siirtyy rekisteriin EDX komennolla:

	CVAR	EDX, 2

Jos funktiolle vastaanottaa 64-bittisiä parametreja, ne täytyy ottaa kyseisellä makrolla kahtena parametrina, sillä makro käsittelee 32-bittisiä palikoita muistissa.

Esimerkkifunktio

Seuraavana vielä funktio, joka ei tee mitään järkevää ja jonka tarkoitus on vain demonstroida makrojen toimintaa:

CFUNC	addnums			; Esitellään funktio nimeltä addnums
	CVAR	eax, 0		; Siirretään eax-rekisteriin ensimmäinen parametri
	CVAR	ebx, 1  		; Siirretään ebx-rekisteriin toinen parametri
	add	eax, ebx		; Lasketaan parametrien arvot yhteen
	; Koska laskun tulos on eax-rekisterissä, funktio palauttaa yhteenlasketun arvon
ENDFUNC

Kun kutsut sitä C/C++ -koodista, voit käyttää vaikkapa seuraavaa syntaksia:

extern unsigned long int addnums(unsigned long int num1, unsigned long int num2);
printf("1024 + 1024 = %i\n", addnums(1024, 1024));

Jostain minulle tuntemattomasta luonnonlaista johtuen funktion pitäisi toimia, vaikka parametreina unsigned long intien sijasta olisi unsigned short int.

Loppusanat

Toivottavasti tästä on hyötyä. Mikäli joku jaksaa valaista meikäläistä komennoista ENTER ja LEAVE, olisin kiitollinen. Käsittääkseni nämä ovat varta vasten C-kielen tarpeisiin suunniteltuja komentoja ja niissä voi säästää kalliita kellojaksoja. Valaistusta kaipaisin myös 64- ja 128-bittisistä paluuarvoista, eli mikäs otus se hidden pointer oikein on? Hämmästyksen kummastuksen aihe on myös se, että miksi esim. unsigned short (16-bittiä) kulkeutuu funktioilleni 32-bittisenä (toisin sanoen makro toimii myös 32-bittistä pienemmillä muuttujilla, vaikka käsitteleekin 32-bittisiä palikoita).

Ja kommentit virheistä Mureakuhaan tai osoitteeseen maniace@daug.net.

Henkilökohtaiset työkalut