Kirjautuminen

Haku

Tehtävät

Koodit: Assembly-funktioiden kutsuminen C-koodista

Kirjoittaja: Päärynämies

Kirjoitettu: 20.01.2008 – 20.01.2008

Tagit: koodi näytille, vinkki

Koodivinkki esittelee miten voidaan kutsua x86-assemblyllä kirjoitettuja funktioita C -koodista. Koodi toimii ainakin omassa Linux -järjestelmässäni.

Kun C- koodissa kutsutaan funktiota, niin funktiolle välitettävät arvot laitetaan pinoon käänteisessä järjestyksessä. Esimerkiksi kutsuttaessa funktiota summa ensiksi pinoon laitetaan muuttuja b ja sen jälkeen muuttuja a. Näiden jälkeen pinoon laitetaan seuraavan käskyn osoite, jotta osataan palata funktiosta oikeaan kohtaan koodia. Funktion paluuarvo on rekisterissä %eax. Funktioiden paluuarvot palautetaan yleensä %eax rekisterissä, mutta aina näin ei suinkaan ole. Joskus paluuarvo on liian suuri mahtuakseen 32-bittiseen rekisteriin ja tällöin käytetään myös rekisteriä %edx.

Funktiossa summa pinoon laitetaan yhteenlaskettavan luvut ja paluuosoite. Muuttuja a siis löytyy osoitteesta %esp+4 (Muista! Pino kasvaa alaspäin) ja muuttuja b osoitteesta %esp+8. Osoite %esp+0 sisältää paluuosoitteen.

Funktiossa kasvata pinoon laitetaan funktiolle annetun muuttujan osoite. Sitten vain lisäämme osoitteen osoittamaan muistipaikkaan yhden.

Jos katsot esimerkiksi gcc generoima funktioiden assembly koodia, niin ne alkavat aina:

pushl %ebp
movl %esp, %ebp

ja päättyvät:

movl %ebp, %esp
popl %ebp
ret

(tai vaihtoehtoisesti: leave ja ret, ne tekevät samat asiat)

Näin tekemällä voidaan myös funktion laittaa ja noutaa pinosta arvoja eikä tarvitse huolehtia siitä, että funktion lopussa pino-osoitin osoittaa samaan kohtaan kuin alussa. Tuolloin kuitenkin pitää huomioida, että ensimmäinen funktiolle välitetty arvo on osoitteessa %esp+8.

Kääntäminen:
gcc main.c -c
as asm.s -o asm.o
gcc main.o asm.o -o summa
Ajaminen:
./summa

main.c

/* Kääntäminen:
 * gcc main.c -c
 */

#include <stdio.h>

/*
 * Funktiot summa ja kasvata ovat määritelty tiedostossa asm.s.
 * summa: Palauttaa a+b
 * kasvata: Kasvattaa muutujan arvoa yhdellä
 */
extern int summa(int a, int b);
extern void kasvata(int *a);

int main(int argc, char *argv[]){
	int a = 1;
	int b = 2;
	int c = summa(a,b);
	printf("%i+%i=%i\n%i+1=", a, b, c, c);
	kasvata(&c);
	printf("%i\n", c);
	return 0;
}

ams.s

#Kääntäminen:
#as asm.s -o asm.o

#Osio, joka sisältää suoritettavaa koodia
.section .text

#Määritellään funktiot globaaleiksi, jotta ne näkyvät muuallakin
.globl summa
.globl kasvata

#Laskee kaksi lukua yhteen
summa:
	movl 4(%esp), %eax	#Noudetaan 1. parametri
	movl 8(%esp), %ebx	#Noudetaan 2. parametri
	addl %ebx, %eax		#Lasketaan yhteen. Tulos on rekisterissä %eax
	ret					#Palataan funktiosta

#Kasvattaa muuttujan arvoa yhdellä
kasvata:
	movl 4(%esp), %eax	#Haetaan muuttujan osoite
	incl (%eax)			#(%eax) = Sen muistipaikan sisältö, jonka osoite on %eax
	ret					#incl kasvattaa lukua yhdellä

Kommentit

Metabolix [20.01.2008 01:21:34]

#

Ihan asiallinen vinkki. Olisit saman tien voinut kertoa toiminnan toiseenkin suuntaan.

Stack framella eli pinokehyksellä (jonka nimeä et maininnut) on muutakin merkitystä kuin selittämäsi. Se nimittäin on debuggerin keino selvittää esimerkiksi funktioiden kutsujärjestystä. Siksi sitä kannattaa käyttää isoissa funktioissa aina ja pienissäkin vähintään debug-vaiheessa, jos funktiossa on edes pieni kaatumisen vaara.

lainaus:

%esp sisältää paluuosoitteen.

Ei vaan (%esp). Itse kyllä ymmärrät tuon oikein mutta aloittelijat välttämättä eivät. "Osoite %esp+0" olisi minusta selkein ilmaus.

lainaus:

Funktion paluuarvo on aina rekisterissä %eax.

Tämähän ei tietenkään pidä paikkaansa. Näin toki on, jos arvo mahtuu eax-rekisteriin. Liukuluvut taas kuljetetaan liukulukupinossa. Ainakin GCC:n tuotoksissa 64-bittisen luvun (long long) yläosa kulkee edx:ssä, ja isommat rakenteet hoidetaan sillä, että funktiolle annetaan ylimääräisenä (ensimmäisenä) parametrina osoitin kelvolliseen tallennuspaikkaan. (Kiinnostavaa, ettei GCC ilmeisesti osaa optimoida tällaista rakenteen palauttavaa funktiokutsua kunnolla edes monissa aivan selkeissä tapauksissa...)

Voisit vielä lisätä kooditagit noihin pariin kuvauksessa olevaan koodiin, vaikka lyhyitä ovatkin.

Päärynämies [20.01.2008 01:46:25]

#

Kiitos hyvistä korjauksista Metabolixille. Korjasinkin tuota vinkkiä hieman. Tosiaan noihin funktioidin en lisänny tuota pinokehyksen käyttöä selkeyden takia. Ajattelin, että se on aloittijaystävällisempi tapa vaikkakin niitä olisi tietysti hyvä käyttää ainakin isommissa funktioissa. Tietystu, jos kovasti olisi tarve optimoida, niin ne taas voi jättää pois. Ensisijaisesti ajattelin kuitenkin, että tämä on suunnattu lähinnä aloittelijoille, joten en halunnut liikaa asioita siihen laittaa.

C-funktioiden kutsumisesta assemblykoodista olen suunnitellut tehdä erillisen vinkin. Näin pysyy yksi asia yhdessä vinkissä, joka on mielestäni selkeämpi rakenne. Myös esim. C:n structien (mitä ovatkaan suomeksi) käyttämisestä funktion parametrinä voisin vinkin tehdä.

Lisäksi mietin, että pitäisikö koittaa vaikka pienen oppaan tekemistä tuosta gcc:n käyttämästä at&t syntaksista, kun se monelle tuntuu olevan se hankalampi tai tuntemattomampi. Itse kun vinkkini sillä tulen kirjoittamaan.

Torgo [26.05.2009 11:32:08]

#

Päärynämies kirjoitti:

Kun C- koodissa kutsutaan funktiota, niin funktiolle välitettävät arvot laitetaan pinoon käänteisessä järjestyksessä.

Tämä riippuu seikasta nimeltä Calling Convention. Vapaasti suomennettuna kutsusopimus (en tiedä virallista termiä). Calling Conventioneja on erilaisia ja ne ovat riippuvaisia kielestä, kääntäjästä ja ympäristöstä (käyttis + cpu). x86 arkkitehtuurissakin se vaihtelee laitetaanko parametrit pinoon, rekisteriin (jos mahtuu) vai sekä että. Se mistä ne parametrit sitten löytyvät, täytyy ensin selvittää kyseiselle ympäristölle, eikä vain suoraan olettaa että pinostahan minä ne kaikki parametrit saan.

Totta on että usein c-kielellä x86-arkkitehtuurissa käytetään _oletusarvoisesti_ cdecl Calling Conventionia, missä parametrit pukataan pinoon, mutta ei aina. Vaihtoehtoja on mm. stdcall missä parametrien järjestys on päinvastainen, fastcall missä käytetään rekistereitä hyväksi jne.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta