Asamblare în linie GCC

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 12 octombrie 2019; verificările necesită 3 modificări .

GCC Inline Assembly  - Asamblator inline al compilatorului GCC , care este un limbaj de descriere macro pentru interfața de cod compilat de nivel înalt cu inserare de asamblare .

Caracteristici

Sintaxa și semantica GCC Inline Assembly are următoarele diferențe semnificative:

Preliminari

Pentru a înțelege cum funcționează GCC Inline Assembly, trebuie să înțelegeți bine pașii implicați în procesul de compilare.

La început, gcc apelează preprocesorul cpp, care include fișierele antet , extinde toate directivele condiționate și efectuează substituții de macro. Puteți vedea ce sa întâmplat după înlocuirea macro cu comanda gcc -E -o preprocessed.c some_file.c. Comutatorul -E este rar folosit, mai ales atunci când depanați macrocomenzi.

Apoi gcc analizează codul rezultat, optimizează codul în aceeași fază și în cele din urmă produce cod de asamblare. Puteți vedea codul de asamblare generat cu comanda gcc -S -o some_file.S some_file.c.

Apoi gcc apelează gazul de asamblare pentru a crea cod obiect din codul de asamblare . De obicei, comutatorul -c (numai pentru compilare) este utilizat în proiecte care constau din multe fișiere.

gcc apelează apoi linkerul ld pentru a construi executabilul din fișierele obiect rezultate .

Pentru a ilustra acest proces, să creăm un fișier test.c cu următorul conținut:

int main () { asm ( "Bla-Bla-Bla" ); // introduceți o astfel de instrucțiune return 0 ; }

Dacă în timpul compilării este generată avertismentul -Wimplicit-function-declaration „Declarație implicită a funcției asm”, utilizați:

__asm__ ( "Bla-Bla-Bla" );

Dacă spunem execute gcc -S -o test.S test.c, atunci descoperim un fapt important: compilatorul a procesat instrucțiunea „greșită” și fișierul de asamblare rezultat test.S conține șirul nostru „Bla-Bla-Bla”. Cu toate acestea, dacă încercăm să creăm cod obiect sau să construim un fișier binar, atunci gcc va scoate următoarele:

test.c: Mesaje asamblatorului: test.c:3: Eroare: nu există o astfel de instrucțiune: „Bla-Bla-Bla”

Mesajul vine de la asamblator.

De aici rezultă o concluzie importantă: GCC nu interpretează în niciun fel conținutul inserției de asamblare, percepându-l ca o substituție macro în timp de compilare.

Sintaxă

Structura generală

Structura generală a insertului de asamblare este următoarea:

asm [volatil]("comenzi și directive ale asamblatorului": parametri de ieșire: parametri de intrare: parametri modificabili);

Cu toate acestea, există și o formă mai scurtă:

asm [volatil] („instrucțiuni de asamblare”);

Sintaxa comenzii

O caracteristică a asamblatorului de gaz și a compilatorului gcc este faptul că folosesc sintaxa AT&T , ceea ce este neobișnuit pentru x86 , care diferă semnificativ de sintaxa Intel . Principalele diferențe [1] :

  1. Ordinea operanzilor: Операция Источник,Приёмник.
  2. Numele de registru sunt prefixate în mod explicit %pentru a indica faptul că acesta este un registru. Acest lucru vă permite să lucrați cu variabile care au același nume ca un registru, ceea ce nu este posibil în sintaxa Intel , care nu utilizează prefixe de registru și numele lor sunt cuvinte cheie rezervate.
  3. Setarea explicită a dimensiunilor operanzilor în sufixele instrucțiunilor: b-byte, w-word, l-long, q-quadword. În comenzi precum movl %edx,%eax, acest lucru poate părea redundant, dar este foarte vizual când vine vorba de incl (%esi) sau xorw $0x7,mask
  4. Numele constantelor încep cu $ și pot fi expresii. De exemplumovl $1,%eax
  5. O valoare fără prefix înseamnă o adresă. De exemplu:
    movl $123,%eax - scrieți numărul 123 la %eax,
    movl 123,%eax - scrieți conținutul celulei de memorie cu adresa 123
    movl var,%eax la %eax, - scrieți valoarea variabilei var în %eax,
    movl $var,%eax - încărcați adresa variabilei var
  6. Parantezele trebuie folosite pentru adresare indirectă. De exemplu movl (%ebx),%eax , încărcați în %eax valoarea variabilei la adresa situată în registrul %ebx
  7. Adresă SIB: offset (bază, index, multiplicator)

Faptul de obicei ignorat că în interiorul directivei asm pot fi nu doar comenzi de asamblare, ci, în general, orice directive recunoscute de gaz, poate servi bine. De exemplu, puteți insera conținutul unui fișier binar în codul obiect rezultat:

asm ( „fișierul_de_date_noastre: \n\t ".incbin \" some_bin_file.txt \"\n\t " // folosiți directiva .incbin "nostru_fișier_de_date_len: \n\t " ".long .-our_data_file \n\t " // introduceți .long valoare cu lungimea fișierului calculată );

Și apoi adresați acest fișier binar:

extern char fişierul_date_noastră []; extern long our_data_file_len ;

Cum funcționează înlocuirea macro

Să vedem cum se întâmplă înlocuirea.

Proiecta:

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

se va transforma in

movl $1 , %eax

Parametrii de intrare și de ieșire

Modificatori

Momente subtile

Cuvântul cheie volatil

Cuvântul cheie volatil este folosit pentru a indica compilatorului că codul de asamblare inserat poate avea efecte secundare, astfel încât încercările de optimizare pot duce la erori logice.

Cazuri în care cuvântul cheie volatil este obligatoriu:

Să presupunem că există o inserție de asamblare în interiorul buclei care verifică utilizarea unei variabile globale și așteaptă în spinlock eliberarea acesteia. Când compilatorul începe să optimizeze bucla, aruncă totul din buclă care nu este modificat în mod explicit în buclă. Deoarece în acest caz compilatorul de optimizare nu vede o relație explicită între parametrii insertului de asamblare și variabilele care se modifică în buclă, insertul de asamblare poate fi aruncat din buclă cu toate consecințele care decurg.

SFAT: specificați întotdeauna asm volatile în cazurile în care inserția de asamblare ar trebui să fie „acolo unde este”. Acest lucru este valabil mai ales atunci când lucrați cu primitive atomice.

„memorie” în lista clobber

Următorul „moment subtil” este indicarea explicită a „memoriei” în lista clobber. Pe lângă faptul că pur și simplu îi spune compilatorului că o inserție de asamblare modifică conținutul memoriei, servește și ca directivă de barieră de memorie pentru compilator. Aceasta înseamnă că acele operațiuni de acces la memorie care sunt mai înalte în cod vor fi executate în codul mașină rezultat înaintea celor care sunt mai mici decât inserția de asamblare. În cazul unui mediu multi-threaded, când riscul unei condiții de cursă depinde direct de aceasta , această circumstanță este esențială.

SFAT #1:

O modalitate rapidă de a crea o barieră de memorie

#define mbarrier() asm volatile ("":::"memorie")

SFAT #2: Specificarea „memoriei” în lista clobber nu este doar „o practică bună”, ci și în cazul lucrului cu operațiuni atomice menite să rezolve condiția de cursă, este obligatorie.

Exemple de utilizare

int main () { int sum = 0 , x = 1 , y = 2 ; asm ( "adăugați %1, %0" : "=r" ( suma ) : "r" ( x ), "0" ( y ) ); // suma = x + y; printf ( "suma = %d, x = %d, y = %d" , suma , x , y ); // suma = 3, x = 1, y = 2 returnează 0 ; }
  • cod: adăugați %1 la %0 și stocați rezultatul în %0
  • parametri de ieșire: registru universal salvat în variabila locală după executarea codului de asamblare.
  • parametrii de intrare: registre universale inițializate din variabilele locale x și y înainte de a executa codul de asamblare.
  • parametri modificabili: nimic altceva decât registre I/O.

Note

  1. Wikibooks: Assembler în Linux pentru programatori C. Preluat la 8 mai 2022. Arhivat din original la 26 aprilie 2022.

Link -uri

  • Documentație oficială (Utilizarea GNU Compiler Collection (GCC) - 6 extensii la familia de limbi C - 6.45 Cum să utilizați limbajul de asamblare inline în codul C   )