GCC Inline Assembly

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2019. október 12-én felülvizsgált verziótól ; az ellenőrzések 3 szerkesztést igényelnek .

GCC Inline Assembly  – A GCC fordító belső összeszerelője , amely egy makróleíró nyelv a lefordított magas szintű kódok interfészéhez összeállítás- beillesztéssel .

Jellemzők

A GCC Inline Assembly szintaxisa és szemantikája a következő jelentős különbségekkel rendelkezik:

Előzetes

A GCC Inline Assembly működésének megértéséhez jól kell ismernie a fordítási folyamat lépéseit.

Kezdetben a gcc meghívja a cpp előfeldolgozót, amely tartalmazza a fejlécfájlokat , kibontja az összes feltételes direktívát, és makróhelyettesítéseket hajt végre. Megnézheti, mi történt a makró paranccsal történő helyettesítése után gcc -E -o preprocessed.c some_file.c. Az -E kapcsolót ritkán használják, főleg makrók hibakeresésekor.

Ezután a gcc elemzi a kapott kódot, optimalizálja a kódot ugyanabban a fázisban, és végül összeszerelő kódot állít elő. A generált assembler kódot a paranccsal láthatja gcc -S -o some_file.S some_file.c.

Ezután a gcc meghívja az assembler gázt, hogy objektumkódot hozzon létre az assembler kódból . Általában a -c (csak fordítás) kapcsolót használják sok fájlból álló projektekben.

A gcc ezután meghívja az ld linkert , hogy a kapott objektumfájlokból létrehozza a végrehajtható fájlt .

A folyamat szemléltetésére hozzunk létre egy test.c fájlt a következő tartalommal:

int main () { asm ( "Bla-Bla-Bla" ); // ilyen utasítás beszúrása return 0 ; }

Ha a fordítás során a -Wimplicit-function-declaration "Implicit asm függvénydeklaráció" figyelmeztetés keletkezik, használja:

__asm__ ( "Bla-Bla-Bla" );

Ha azt mondjuk, hogy execute gcc -S -o test.S test.c, akkor egy fontos tényt fedezünk fel: a fordító feldolgozta a "rossz" utasítást, és az eredményül kapott assembler file test.S tartalmazza a "Bla-Bla-Bla" karakterláncunkat. Ha azonban objektumkódot vagy bináris fájlt próbálunk létrehozni, akkor a gcc a következőt adja ki:

test.c: Assembler üzenetei: test.c:3: Hiba: nincs ilyen utasítás: 'Bla-Bla-Bla'

Az üzenet az összeszerelőtől érkezik.

Ebből egy fontos következtetés következik: a GCC semmilyen módon nem értelmezi az assembler beillesztés tartalmát, azt fordítási idejű makróhelyettesítésként fogja fel.

Szintaxis

Általános szerkezet

Az összeszerelő betét általános felépítése a következő:

asm [volatile]("assembler parancsok és direktívák" : kimeneti paraméterek : bemeneti paraméterek : változtatható paraméterek);

Van azonban egy rövidebb forma is:

asm [illékony] ("összeszerelő utasítások");

Parancs szintaxis

A gas assembler és a gcc fordító jellemzője, hogy az AT&T szintaxist használják, ami szokatlan az x86-nál , ami jelentősen eltér az Intel szintaxisától . Főbb különbségek [1] :

  1. Operandus sorrend: Операция Источник,Приёмник.
  2. A regiszternevek kifejezetten előtaggal vannak ellátva %, jelezve, hogy ez egy regiszter. Ez lehetővé teszi, hogy olyan változókkal dolgozzon, amelyek neve megegyezik egy regiszterrel, ami nem lehetséges az Intel szintaxisában, amely nem használ regiszter előtagokat, és a nevük fenntartott kulcsszavak.
  3. Az operandusok méretének explicit beállítása az utasítás utótagokban: b-byte, w-word, l-long, q-quadword. Az olyan parancsokban, mint a movl %edx,%eax, ez feleslegesnek tűnhet, de nagyon látványos, ha az incl (%esi) vagy xorw $0x7,mask-ról van szó.
  4. A konstans nevek $-val kezdődnek, és lehetnek kifejezések. Példáulmovl $1,%eax
  5. Az előtag nélküli érték címet jelent. Például:
    movl $123,%eax - írja be a 123-as számot a %eax-ba,
    movl 123,%eax - írja be a 123-as memóriacella tartalmát a
    movl var,%eax %eax-ba, - írja be a var változó értékét
    movl $var,%eax a %eax-be,  - töltse be a var változó címét
  6. A közvetett megszólításhoz zárójeleket kell használni. Például movl (%ebx),%eax töltse be a %eax-be a változó értékét a %ebx regiszterben található címen
  7. SIB cím: offset(bázis, index, szorzó)

Jó szolgálatot tehet az az általában figyelmen kívül hagyott tény, hogy az asm direktíván belül nem csak assembler parancsok lehetnek, hanem általában bármilyen, a gas által felismert direktíva. Például beillesztheti egy bináris fájl tartalmát a kapott objektumkódba:

asm ( "adatfájlunk: \n\t " ".incbin \" some_bin_file.txt \"\n\t " // használja a "our_data_file_len: \n\t " .incbin direktívát ".long .-our_data_file \n\t " // .long érték beszúrása számított fájlhosszal );

Ezután címezze meg ezt a bináris fájlt:

extern char our_data_file []; extern long our_data_file_len ;

A makróhelyettesítés működése

Lássuk, hogyan történik a helyettesítés.

Tervezés:

asm ( "movl %0,%%eax" :: "i" ( 1 ));

át fog alakulni

movl $1 , %eax

Bemeneti és kimeneti paraméterek

Módosítók

Finom pillanatok

Az illékony kulcsszó

A volatile kulcsszó arra szolgál, hogy jelezze a fordítónak, hogy a beillesztett assembler kódnak lehetnek mellékhatásai, így az optimalizálási kísérletek logikai hibákhoz vezethetnek.

Azok az esetek, amikor a volatile kulcsszó kötelező:

Tegyük fel, hogy van egy assembler beszúrás a cikluson belül, amely ellenőrzi egy globális változó alkalmazását, és a spinlockban várja a kiadását. Amikor a fordító elkezdi optimalizálni a ciklust, mindent kidob a ciklusból, ami nincs kifejezetten megváltoztatva a ciklusban. Mivel ebben az esetben az optimalizáló fordító nem lát explicit kapcsolatot az assembler inszert paraméterei és a ciklusban változó változók között, az assembler beszúrást ki lehet dobni a ciklusból az ebből eredő összes következménnyel.

TIPP: Mindig adja meg az asm volatile -t azokban az esetekben, amikor az assembler betétnek „ott kell lennie, ahol van”. Ez különösen igaz, ha atomi primitívekkel dolgozik.

"memória" a clobber listában

A következő "finom momentum" a "memória" kifejezett megjelölése a cölöplistában. Amellett, hogy egyszerűen közli a fordítóval, hogy az assembler beillesztés megváltoztatja a memória tartalmát, memóriakorlát direktívaként is szolgál a fordító számára. Ez azt jelenti, hogy azok a memóriaelérési műveletek, amelyek magasabbak a kódban, az eredményül kapott gépi kódban kerülnek végrehajtásra, mielőtt azok, amelyek alacsonyabbak, mint az assembler beillesztése. Többszálú környezet esetén, amikor a versenyhelyzet kockázata közvetlenül ettől függ, ez a körülmény elengedhetetlen.

TIPP #1:

Memóriakorlát készítésének gyors módja

#define mbarrier() asm volatile ("":::"memória")

TIPP #2: A „memória” megadása a cölöplistában nem csak „jó gyakorlat”, hanem a versenyfeltételek megoldására tervezett atomműveletek esetében is kötelező.

Használati példák

int main () { int összeg = 0 , x = 1 , y = 2 ; asm ( "add %1, %0" : "=r" ( összeg ) : "r" ( x ), "0" ( y ) ); // összeg = x + y; printf ( "összeg = %d, x = %d, y = %d" , összeg , x , y ); // összeg = 3, x = 1, y = 2 return 0 ; }
  • kód: adja hozzá a %1-et a %0-hoz, és tárolja az eredményt a %0-ban
  • kimeneti paraméterek: univerzális regiszter a helyi változóba mentve az assembler kód végrehajtása után.
  • bemeneti paraméterek: univerzális regiszterek, amelyeket az x és y helyi változókból inicializálnak az assembler kód végrehajtása előtt.
  • módosítható paraméterek: semmi más, csak az I/O regiszterek.

Jegyzetek

  1. Wikikönyvek: Assembler Linux alatt C programozóknak . Letöltve: 2022. május 8. Az eredetiből archiválva : 2022. április 26..

Linkek

  • Hivatalos dokumentáció (A GNU fordítógyűjtemény (GCC) használata - 6 bővítmény a C nyelvcsaládhoz - 6.45 Az Inline Assembly nyelv használata a C kódban   )