C++ - Ambiente di lavoro
Table of Contents
Premessa
Vi sono vari ambienti di lavoro per poter scrivere e compilare codice per board ESP /Arduino, quelli che ho utilizzato nel tempo sono di seguito elencati. Per ambiente s'intende un editor e un compilatore :
- Platformio : completamente testuale a riga di comando senza un edito predefinito; il vantaggio e' che altamente personalizzabile;
- Emacs : potente editor in cui e' possibile integrare platformio e debuger anche se quest'ultimo e' difficile da gestire;
- Visual Studio : Ambiente di lavoro molto versatile permette l'integrazione con platformio disponibile come estensione. Gestione facilitata di librerie e del debug. Editor con supporto agevolato per la scrittura;
- Arduino IDE : Ambiente nativo per lo sviluppo di codice per board ESP / Arduino, gestione facilitata delle librerie ma non del debug e l'editor non supporta l'agevolazione della sintassi;
Attualmente sto utilizzando Visual Studio per linux liberamente scaricabile con installata l'estessione di platformio la quela permette una facile configurazione dell'ambiente mediante l'editor del file paltformio.ini. L'utilizzo di questo ambiente di lavoro permette di creare una struttua autonoma per ogni progetto di lavoro che ha come radice una cartella con lo stesso nome del progetto. Inoltre questo ambiente permette l'editing agevolato in quanto esplora sia le librerie di sistema che quelle personali segnalando eventuali incongruenze al momento della scrittura.
Per quanto riguarda il linguaggio C++ si utilizza:
- clang
- llvm e le sue utility tipo lli
Compilazione
La compilazione è il processo di traduzione di un programma scritto in un linguaggio di programmazione ad alto livello in un linguaggio di macchina comprensibile per il computer. Durante la compilazione, un software chiamato compilatore legge il codice sorgente scritto dall'utente e lo traduce in una forma eseguibile, che può essere eseguita direttamente dal computer.
I compilatori sono formati da un frontend ed un backend anche sei nei compilatori piu' moderni e' tutto integrato. Il frontend e formatto dal quello che nel seguito e' indicato dai pti 1), 2), 3), mentre il backend e' formato dall'ambiente LLVM comune a molti linguaggi ormai formato da circa 10 milioni di righe di codice.
Nell'ipotesi di sviluppare un nuovo compilatore i passi da seguire sono i seguenti, che tra l'altro sono quelli che si hanno quando si compila un programma ;
- editing;
- scrittura di un analizzatore di tocken;
- scrittura di un parser che genera un AST e analizza il linguaggio alla ricerca di errori sintattici;
- da un grafo AST ad una serie di istruzioni sequenzali in cui,ad esempio la ricorsione o l'iterazione e' fatta per salti uso di LLVM per generare il bytecode o il codice oggetto;
Confronto bytecode o il codice oggetto:
- Portabilità:
- Codice Oggetto: Specifico per un'architettura e un sistema operativo. Deve essere ricompilato per altre piattaforme.
- Bytecode: Più portabile, può essere eseguito su diverse piattaforme che supportano l'interprete o la macchina virtuale appropriata.
- Esecuzione:
- Codice Oggetto: Eseguibile direttamente dall'hardware dopo il linking.
- Bytecode: Deve essere interpretato o ulteriormente compilato da una macchina virtuale o un JIT compiler.
- Uso:
- Codice Oggetto: Usato in compilazioni tradizionali di linguaggi come C e C++.
- Bytecode: Usato in ambienti che necessitano di portabilità, come Java, Python (con .pyc files) e LLVM.
Il compilatore puo' essere integrato nell'editor oppure no e il suo compito e' quello di analizzare il codice sorgente per verificare la sua correttezza sintattica e semantica. Successivamente, traduce il codice sorgente in istruzioni di basso livello o codice oggetto specifico della piattaforma di destinazione. Queste istruzioni sono scritte in un linguaggio di basso livello o codice macchina, che è comprensibile e eseguibile dal processore del computer.
In pratica la compilazione consiste nel tradurre per passi un programma scritto in un linguaggio strutturato la cui semantica' e' esprimibile in un modo molto vicino al linguaggio naturale naturale in una serie di istruzioni sequenziali, poco sopra l'assembler, che verramo poi tradotte in bytecode ( linguaggi interpretati ) o codice oggetto ( per codice eseguibile ).
Nella pratica la suddivisone non e' cosi netta come indicato nel seguito anche perche' con LLVM si puo' ottenere il codice oggetto ed inoltre LLVM supporta il concetto di funzione per cui il linguaggio sequenziale puo' considerarsi esso stesso un linguaggio di programmazione anche se ottenuto partendo da diversi linguaggi di piu' alto livello.
Una volta che il codice sorgente viene compilato con successo, il programma risultante può essere eseguito ripetutamente senza la necessità di ricompilazione, a meno che non vengano apportate modifiche al codice sorgente. La compilazione è un processo fondamentale nello sviluppo del software ed è ampiamente utilizzata per tradurre programmi scritti in linguaggi di programmazione come C, C++, Java, Python e molti altri in codice eseguibile.
Per rendere eseguibile il codice e' necessario fornire al compilatore le informazioni sul microprocessore da utilizzare e nel caso di Visual Studio questo va scelto al momento della creazione del progetto mentre con Arduino IDE prima dell'inizio della compilazione.
La teoria del presente paragrafo si basa sul seguente codice.
int main(void) { int a=5; int b=3; while( b !=0 ) { if(a>b) { a=a-b; } else { b=b-a; } } return a; }
Compilazione Frontend
Editor
ambiente di scrittura, piu' o meno automatizzato.
Analisi lessicale
Durante questa fase, il compilatore legge il codice sorgente carattere per carattere e lo suddivide in token. I token sono unità logiche come parole chiave, identificatori, operatori, simboli e costanti. L'analizzatore lessicale, noto anche come scanner, riconosce i token e li organizza in una sequenza coerente. A questo livello vengono segnalati errori dovuti all'uso di caratteri non previsti ;
clang -Xclang -dump-tokens -fsyntax-only -v while.c int 'int' [StartOfLine] Loc=<while.c:1:1> identifier 'main' [LeadingSpace] Loc=<while.c:1:5> l_paren '(' Loc=<while.c:1:9> void 'void' Loc=<while.c:1:10> r_paren ')' Loc=<while.c:1:14> l_brace '{' [LeadingSpace] Loc=<while.c:1:16> int 'int' [StartOfLine] [LeadingSpace] Loc=<while.c:2:2> identifier 'a' [LeadingSpace] Loc=<while.c:2:6> equal '=' Loc=<while.c:2:7> numeric_constant '5' Loc=<while.c:2:8> semi ';' Loc=<while.c:2:9> int 'int' [StartOfLine] [LeadingSpace] Loc=<while.c:3:2> identifier 'b' [LeadingSpace] Loc=<while.c:3:6> equal '=' Loc=<while.c:3:7> numeric_constant '3' Loc=<while.c:3:8> semi ';' Loc=<while.c:3:9> while 'while' [StartOfLine] [LeadingSpace] Loc=<while.c:5:2> l_paren '(' Loc=<while.c:5:7> identifier 'b' [LeadingSpace] Loc=<while.c:5:9> exclaimequal '!=' [LeadingSpace] Loc=<while.c:5:11> numeric_constant '0' Loc=<while.c:5:13> r_paren ')' [LeadingSpace] Loc=<while.c:5:15> l_brace '{' [LeadingSpace] Loc=<while.c:5:17> if 'if' [StartOfLine] [LeadingSpace] Loc=<while.c:6:3> l_paren '(' Loc=<while.c:6:5> identifier 'a' Loc=<while.c:6:6> greater '>' Loc=<while.c:6:7> identifier 'b' Loc=<while.c:6:8> r_paren ')' Loc=<while.c:6:9> l_brace '{' [LeadingSpace] Loc=<while.c:6:11> identifier 'a' [StartOfLine] [LeadingSpace] Loc=<while.c:7:4> equal '=' Loc=<while.c:7:5> identifier 'a' Loc=<while.c:7:6> minus '-' Loc=<while.c:7:7> identifier 'b' Loc=<while.c:7:8> semi ';' Loc=<while.c:7:9> r_brace '}' [StartOfLine] [LeadingSpace] Loc=<while.c:8:3> else 'else' [LeadingSpace] Loc=<while.c:8:5> l_brace '{' [LeadingSpace] Loc=<while.c:8:10> identifier 'b' [StartOfLine] [LeadingSpace] Loc=<while.c:9:4> equal '=' Loc=<while.c:9:5> identifier 'b' Loc=<while.c:9:6> minus '-' Loc=<while.c:9:7> identifier 'a' Loc=<while.c:9:8> semi ';' Loc=<while.c:9:9> r_brace '}' [StartOfLine] [LeadingSpace] Loc=<while.c:10:3> r_brace '}' [StartOfLine] [LeadingSpace] Loc=<while.c:11:2> return 'return' [StartOfLine] [LeadingSpace] Loc=<while.c:13:2> identifier 'a' [LeadingSpace] Loc=<while.c:13:9> semi ';' Loc=<while.c:13:10> r_brace '}' [StartOfLine] Loc=<while.c:14:1> eof '' Loc=<while.c:14:2> /usr/bin/ld: /lib/x86_64-linux-gnu/Scrt1.o: in function `_start': (.text+0x17): undefined reference to `main'
PARSER
Generazione AST
clang -Xclang ast-dump while.c TranslationUnitDecl 0x562ab5b7b1c8 <<invalid sloc>> <invalid sloc> |-TypedefDecl 0x562ab5b7b9f0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128' | `-BuiltinType 0x562ab5b7b790 '__int128' |-TypedefDecl 0x562ab5b7ba60 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128' | `-BuiltinType 0x562ab5b7b7b0 'unsigned __int128' |-TypedefDecl 0x562ab5b7bd68 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag' | `-RecordType 0x562ab5b7bb40 'struct __NSConstantString_tag' | `-Record 0x562ab5b7bab8 '__NSConstantString_tag' |-TypedefDecl 0x562ab5b7be00 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *' | `-PointerType 0x562ab5b7bdc0 'char *' | `-BuiltinType 0x562ab5b7b270 'char' |-TypedefDecl 0x562ab5b7c0f8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag[1]' | `-ConstantArrayType 0x562ab5b7c0a0 'struct __va_list_tag[1]' 1 | `-RecordType 0x562ab5b7bee0 'struct __va_list_tag' | `-Record 0x562ab5b7be58 '__va_list_tag' `-FunctionDecl 0x562ab5bd7550 <while.c:1:1, line:14:1> line:1:5 main 'int (void)' `-CompoundStmt 0x562ab5bd7b68 <col:16, line:14:1> |-DeclStmt 0x562ab5bd7710 <line:2:2, col:9> | `-VarDecl 0x562ab5bd7688 <col:2, col:8> col:6 used a 'int' cinit | `-IntegerLiteral 0x562ab5bd76f0 <col:8> 'int' 5 |-DeclStmt 0x562ab5bd77c8 <line:3:2, col:9> | `-VarDecl 0x562ab5bd7740 <col:2, col:8> col:6 used b 'int' cinit | `-IntegerLiteral 0x562ab5bd77a8 <col:8> 'int' 3 |-WhileStmt 0x562ab5bd7b00 <line:5:2, line:11:2> | |-BinaryOperator 0x562ab5bd7838 <line:5:9, col:13> 'int' '!=' | | |-ImplicitCastExpr 0x562ab5bd7820 <col:9> 'int' <LValueToRValue> | | | `-DeclRefExpr 0x562ab5bd77e0 <col:9> 'int' lvalue Var 0x562ab5bd7740 'b' 'int' | | `-IntegerLiteral 0x562ab5bd7800 <col:13> 'int' 0 | `-CompoundStmt 0x562ab5bd7ae8 <col:17, line:11:2> | `-IfStmt 0x562ab5bd7ab8 <line:6:3, line:10:3> has_else | |-BinaryOperator 0x562ab5bd78c8 <line:6:6, col:8> 'int' '>' | | |-ImplicitCastExpr 0x562ab5bd7898 <col:6> 'int' <LValueToRValue> | | | `-DeclRefExpr 0x562ab5bd7858 <col:6> 'int' lvalue Var 0x562ab5bd7688 'a' 'int' | | `-ImplicitCastExpr 0x562ab5bd78b0 <col:8> 'int' <LValueToRValue> | | `-DeclRefExpr 0x562ab5bd7878 <col:8> 'int' lvalue Var 0x562ab5bd7740 'b' 'int' | |-CompoundStmt 0x562ab5bd79b8 <col:11, line:8:3> | | `-BinaryOperator 0x562ab5bd7998 <line:7:4, col:8> 'int' '=' | | |-DeclRefExpr 0x562ab5bd78e8 <col:4> 'int' lvalue Var 0x562ab5bd7688 'a' 'int' | | `-BinaryOperator 0x562ab5bd7978 <col:6, col:8> 'int' '-' | | |-ImplicitCastExpr 0x562ab5bd7948 <col:6> 'int' <LValueToRValue> | | | `-DeclRefExpr 0x562ab5bd7908 <col:6> 'int' lvalue Var 0x562ab5bd7688 'a' 'int' | | `-ImplicitCastExpr 0x562ab5bd7960 <col:8> 'int' <LValueToRValue> | | `-DeclRefExpr 0x562ab5bd7928 <col:8> 'int' lvalue Var 0x562ab5bd7740 'b' 'int' | `-CompoundStmt 0x562ab5bd7aa0 <line:8:10, line:10:3> | `-BinaryOperator 0x562ab5bd7a80 <line:9:4, col:8> 'int' '=' | |-DeclRefExpr 0x562ab5bd79d0 <col:4> 'int' lvalue Var 0x562ab5bd7740 'b' 'int' | `-BinaryOperator 0x562ab5bd7a60 <col:6, col:8> 'int' '-' | |-ImplicitCastExpr 0x562ab5bd7a30 <col:6> 'int' <LValueToRValue> | | `-DeclRefExpr 0x562ab5bd79f0 <col:6> 'int' lvalue Var 0x562ab5bd7740 'b' 'int' | `-ImplicitCastExpr 0x562ab5bd7a48 <col:8> 'int' <LValueToRValue> | `-DeclRefExpr 0x562ab5bd7a10 <col:8> 'int' lvalue Var 0x562ab5bd7688 'a' 'int' `-ReturnStmt 0x562ab5bd7b58 <line:13:2, col:9> `-ImplicitCastExpr 0x562ab5bd7b40 <col:9> 'int' <LValueToRValue> `-DeclRefExpr 0x562ab5bd7b20 <col:9> 'int' lvalue Var 0x562ab5bd7688 'a' 'int'
- Analisi sintattica: In questa fase, l'analizzatore sintattico, o parser, analizza la sequenza di token generata nell'analisi lessicale e crea una struttura ad albero chiamata albero di analisi sintattica o albero di parsing AST . Questo albero rappresenta la struttura grammaticale del programma. L'analizzatore sintattico verifica anche che il codice sorgente sia conforme alla grammatica definita dal linguaggio di programmazione;
- Analisi semantica: Sia sa bsull'albero AST e durante l'analisi semantica, il compilatore controlla la correttezza semantica del codice sorgente. Ciò implica la verifica di regole di tipo, la gestione degli ambiti delle variabili, la risoluzione dei riferimenti e la validazione delle espressioni. L'analizzatore semantico svolge questa verifica per assicurarsi che il codice abbia un significato coerente;
- Generazione del codice intermedio: Dopo l'analisi semantica, il compilatore può generare un codice intermedio che rappresenta il programma in una forma più astratta rispetto al codice sorgente originale. Il codice e' ha livello intermedio può essere rappresentato ad esempio come un albero di sintassi astratta (AST) o come codice a tre indirizzi. Questa rappresentazione intermedia semplifica ulteriormente la generazione del codice oggetto;
Compilazione Backend
Uso dell'ambiente LLVM genera un linguaggio sequenziale di poco al di sopra dell'assembler come nel blocco di seguito
clang -S -emit-llvm while.c -o while.ll ; ModuleID = 'while.c' source_filename = "while.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" ; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @main() #0 { %1 = alloca i32, align 4 %2 = alloca i32, align 4 %3 = alloca i32, align 4 store i32 0, ptr %1, align 4 store i32 5, ptr %2, align 4 store i32 3, ptr %3, align 4 br label %4 4: ; preds = %19, %0 %5 = load i32, ptr %3, align 4 %6 = icmp ne i32 %5, 0 br i1 %6, label %7, label %20 7: ; preds = %4 %8 = load i32, ptr %2, align 4 %9 = load i32, ptr %3, align 4 %10 = icmp sgt i32 %8, %9 br i1 %10, label %11, label %15 11: ; preds = %7 %12 = load i32, ptr %2, align 4 %13 = load i32, ptr %3, align 4 %14 = sub nsw i32 %12, %13 store i32 %14, ptr %2, align 4 br label %19 15: ; preds = %7 %16 = load i32, ptr %3, align 4 %17 = load i32, ptr %2, align 4 %18 = sub nsw i32 %16, %17 store i32 %18, ptr %3, align 4 br label %19 19: ; preds = %15, %11 br label %4, !llvm.loop !6 20: ; preds = %4 %21 = load i32, ptr %2, align 4 ret i32 %21 } attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } !llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{i32 8, !"PIC Level", i32 2} !2 = !{i32 7, !"PIE Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"Debian clang version 16.0.6 (27+b1)"} !6 = distinct !{!6, !7} !7 = !{!"llvm.loop.mustprogress"}
Ottimizzazione del codice
- Durante l'ottimizzazione del
codice, il compilatore applica una serie di trasformazioni al
codice intermedio per migliorarne l'efficienza o ridurne la
complessità. Queste ottimizzazioni possono includere la
semplificazione dell'espressione, l'eliminazione del codice
morto, la riduzione delle operazioni di accesso alla memoria e
molto altro. L'obiettivo è generare un codice eseguibile che sia
efficiente e ottimizzato;
- Generazione del codice oggetto: Nella fase finale, il compilatore traduce il codice intermedio ottimizzato in codice oggetto specifico della piattaforma di destinazione. Il codice oggetto è scritto in linguaggio di macchina ed è direttamente eseguibile dal processore del computer. Questa fase coinvolge anche la gestione della memoria, l'assegnazione dei registri e la risoluzione dei riferimenti alle librerie esterne;
- Collegamento (linking): In alcune situazioni, il programma potrebbe essere composto da più file sorgente separati;
Debug
Nella pratica quando si esegue il debug lo si fa facendo riferimento al codice assembler dopo aver inserito in fase di compilazione le istruzioni necessarie.
- Compilazione con istruzioni di debug clang -g -o while while.c
Avvio dell'ambiente di debug lldb while. Il codice assembler di seguito indicato e' ottenuto con l' istruzione disassemble all'interno dell'ambiente lldb oppure si puo' ottenere con l'istruzione objdump -d -M intel while seguito il risultato :
0x7ffff7de3c10 <+0>: subq $0x98, %rsp ; Allocare 152 byte sullo stack per variabili locali e salvataggio di registri. 0x7ffff7de3c17 <+7>: movq %rdi, 0x8(%rsp) ; Salvare il primo argomento della funzione (contenuto in %rdi) a 8 bytes sopra il puntatore dello stack. 0x7ffff7de3c1c <+12>: leaq 0x20(%rsp), %rdi ; Calcolare l'indirizzo effettivo 32 bytes sopra lo stack e memorizzarlo in %rdi (utilizzato per un'operazione futura). 0x7ffff7de3c21 <+17>: movl %esi, 0x14(%rsp) ; Salvare il secondo argomento della funzione (contenuto in %esi) a 20 bytes sopra il puntatore dello stack. 0x7ffff7de3c25 <+21>: movq %rdx, 0x18(%rsp) ; Salvare il terzo argomento della funzione (contenuto in %rdx) a 24 bytes sopra il puntatore dello stack. 0x7ffff7de3c2a <+26>: movq %fs:0x28, %rax ; Caricare il valore situato all'indirizzo 0x28 del segmento %fs nel registro %rax (spesso usato per TLS). 0x7ffff7de3c33 <+35>: movq %rax, 0x88(%rsp) ; Salvare il valore del registro %rax a 136 bytes sopra il puntatore dello stack (salvataggio del TLS). 0x7ffff7de3c3b <+43>: xorl %eax, %eax ; Azzera il registro %eax, impostandolo a 0. 0x7ffff7de3c3d <+45>: callq 0x7ffff7df92c0 ; Chiamare la funzione _setjmp per salvare il contesto di esecuzione (stato dei registri, puntatore dello stack). 0x7ffff7de3c42 <+50>: testl %eax, %eax ; Controllare se il valore di ritorno in %eax è zero. 0x7ffff7de3c44 <+52>: jne 0x7ffff7de3c91 ; Se %eax non è zero (quindi, se è stata chiamata longjmp), saltare all'indirizzo 0x7ffff7de3c91. 0x7ffff7de3c46 <+54>: movq %fs:0x300, %rax ; Caricare il valore all'indirizzo 0x300 nel segmento %fs nel registro %rax (tipicamente parte di TLS). 0x7ffff7de3c4f <+63>: movq %rax, 0x68(%rsp) ; Salvare il valore di %rax a 104 bytes sopra il puntatore dello stack. 0x7ffff7de3c54 <+68>: movq %fs:0x2f8, %rax ; Caricare il valore all'indirizzo 0x2f8 nel segmento %fs nel registro %rax. 0x7ffff7de3c5d <+77>: movq %rax, 0x70(%rsp) ; Salvare il valore di %rax a 112 bytes sopra il puntatore dello stack. 0x7ffff7de3c62 <+82>: leaq 0x20(%rsp), %rax ; Calcolare l'indirizzo 32 bytes sopra lo stack e memorizzarlo in %rax. 0x7ffff7de3c67 <+87>: movq %rax, %fs:0x300 ; Scrivere il valore di %rax all'indirizzo 0x300 del segmento %fs (aggiornare una variabile nel TLS). 0x7ffff7de3c70 <+96>: movq 0x1ae331(%rip), %rax ; Caricare un puntatore dall'indirizzo basato su %rip con un offset di 0x1ae331 nel registro %rax. 0x7ffff7de3c77 <+103>: movq 0x18(%rsp), %rsi ; Caricare il valore a 24 bytes sopra lo stack nel registro %rsi (terzo argomento per la chiamata successiva). 0x7ffff7de3c7c <+108>: movl 0x14(%rsp), %edi ; Caricare il valore a 20 bytes sopra lo stack nel registro %edi (secondo argomento per la chiamata successiva). 0x7ffff7de3c80 <+112>: movq (%rax), %rdx ; Caricare il valore puntato dal puntatore contenuto in %rax nel registro %rdx. 0x7ffff7de3c83 <+115>: movq 0x8(%rsp), %rax ; Caricare il valore a 8 bytes sopra lo stack nel registro %rax (primo argomento per la chiamata successiva). 0x7ffff7de3c88 <+120>: callq *%rax ; Chiamare la funzione puntata da %rax (chiamata indiretta). 0x7ffff7de3c8a <+122>: movl %eax, %edi ; Spostare il valore di ritorno della funzione in %edi (prepara per l'uscita). 0x7ffff7de3c8c <+124>: callq 0x7ffff7dfbb30 ; exit ; Chiamare la funzione di uscita (terminare il programma). 0x7ffff7de3c91 <+129>: callq 0x7ffff7e42f30 ; ___lldb_unnamed_symbol3597 ; Chiamare una funzione interna o gestore di eccezioni. 0x7ffff7de3c96 <+134>: lock ; Iniziare un'operazione di memoria atomica (protezione multithreading). 0x7ffff7de3c97 <+135>: subl $0x1, 0x1ae432(%rip) ; Sottrarre 1 da una variabile in memoria, usando un offset basato su %rip. 0x7ffff7de3c9e <+142>: je 0x7ffff7de3cb0 ; Se il risultato della sottrazione è zero, saltare all'indirizzo 0x7ffff7de3cb0. 0x7ffff7de3ca0 <+144>: movl $0x3c, %edx ; Caricare il valore 0x3c (60 decimale) nel registro %edx (usato come argomento). 0x7ffff7de3ca5 <+149>: nopl (%rax) ; No operation; semplicemente avanzare l'istruzione senza fare nulla. 0x7ffff7de3ca8 <+152>: xorl %edi, %edi ; Azzera il registro %edi (impostandolo a zero). 0x7ffff7de3caa <+154>: movl %edx, %eax ; Spostare il valore di %edx in %eax (preparazione per la chiamata di sistema). 0x7ffff7de3cac <+156>: syscall ; Eseguire una chiamata di sistema (terminare il programma con stato di uscita 60). 0x7ffff7de3cae <+158>: jmp 0x7ffff7de3ca8 ; Saltare all'indirizzo 0x7ffff7de3ca8, creando un loop infinito per l'attesa. 0x7ffff7de3cb0 <+160>: xorl %eax, %eax ; Azzera il registro %eax (impostandolo a zero). 0x7ffff7de3cb2 <+162>: jmp 0x7ffff7de3c8a ; Saltare all'indirizzo 0x7ffff7de3c8a, riprendendo il flusso principale. 0x7ffff7de3cb4 <+164>: nopw %cs:(%rax,%rax) ; No operation; avanzare l'istruzione senza eseguire alcuna azione significativa. 0x7ffff7de3cbf <+175>: nop ; No operation; segnaposto per allineamento delle istruzioni o padding.
Codice assembler
.text .file "while.c" .globl main # -- Begin function main .p2align 4, 0x90 .type main,@function main: # @main .cfi_startproc # %bb.0: pushq %rbp # Salva il valore attuale del base pointer (rbp) sullo stack. .cfi_def_cfa_offset 16 # Aggiorna il Call Frame Information (CFA) per indicare che il frame corrente è 16 byte sopra l'attuale rsp. .cfi_offset %rbp, -16 # Aggiorna il CFA per mostrare che rbp è salvato a -16 dall'attuale CFA. movq %rsp, %rbp # Imposta il base pointer (rbp) al valore dello stack pointer (rsp), creando il nuovo frame di stack. .cfi_def_cfa_register %rbp # Aggiorna il CFA per utilizzare il nuovo rbp come base. movl $0, -12(%rbp) # Inizializza la variabile locale all'indirizzo rbp-12 con 0 (probabilmente una variabile non utilizzata nel codice). movl $5, -8(%rbp) # Inizializza la variabile locale all'indirizzo rbp-8 con 5 (probabilmente rappresenta la variabile i). movl $3, -4(%rbp) # Inizializza la variabile locale all'indirizzo rbp-4 con 3 (probabilmente rappresenta la variabile j). .LBB0_1: # Inizio del ciclo while. cmpl $0, -4(%rbp) # Confronta la variabile a rbp-4 (j) con 0. je .LBB0_6 # Se j è uguale a 0, salta all'etichetta .LBB0_6 (fine del ciclo). # %bb.2: # Corpo del ciclo while: sezione 1 movl -8(%rbp), %eax # Carica il valore di i (rbp-8) nel registro eax. cmpl -4(%rbp), %eax # Confronta il valore di i (eax) con j (rbp-4). jle .LBB0_4 # Se i è minore o uguale a j, salta a .LBB0_4 (sezione 2 del corpo del ciclo). # %bb.3: # Corpo del ciclo while: sezione 1, caso i > j movl -8(%rbp), %eax # Carica il valore di i nel registro eax. subl -4(%rbp), %eax # Sottrae j da i e memorizza il risultato in eax. movl %eax, -8(%rbp) # Aggiorna la variabile i con il risultato della sottrazione. jmp .LBB0_5 # Salta alla fine della sezione 1 del ciclo. .LBB0_4: # Corpo del ciclo while: sezione 2, caso i <= j movl -4(%rbp), %eax # Carica il valore di j nel registro eax. subl -8(%rbp), %eax # Sottrae i da j e memorizza il risultato in eax. movl %eax, -4(%rbp) # Aggiorna la variabile j con il risultato della sottrazione. .LBB0_5: # Fine della sezione del corpo del ciclo while. jmp .LBB0_1 # Salta all'inizio del ciclo while per valutare nuovamente la condizione. .LBB0_6: # Fine del ciclo while. movl -8(%rbp), %eax # Carica il valore finale di i nel registro eax (valore di ritorno della funzione). popq %rbp # Ripristina il valore originale di rbp dal frame chiamante. .cfi_def_cfa %rsp, 8 # Aggiorna il CFA per riflettere lo stato post-popolazione dello stack. retq # Ritorna dal main, terminando la funzione e restituendo il valore in eax. .Lfunc_end0: # Fine della funzione .size main, .Lfunc_end0-main # Definisce la dimensione della funzione main. .cfi_endproc # Termina il supporto per il Call Frame Information. # -- End function .ident "Debian clang version 16.0.6 (27+b1)" # Identifica il compilatore utilizzato e la sua versione. .section ".note.GNU-stack","",@progbits # Sezione per la sicurezza della memoria stack (non eseguibile).
Spiegazione del Codice
- Introduzione
Il codice assembly è generato per una funzione main in linguaggio C. L'assembly mostra un ciclo while che esegue operazioni matematiche su due variabili, i e j, finché j non diventa zero.
- Descrizione delle Istruzioni
Prologo della Funzione
pushq %rbp e movq %rsp, %rbp: Salva il vecchio base pointer e imposta il nuovo base pointer per creare un nuovo stack frame. Le istruzioni cfi aggiornano le informazioni per il debug.
- Inizializzazione delle Variabili
movl $0, -12(%rbp): Inizializza una variabile locale (sembra inutilizzata) a zero. movl $5, -8(%rbp): Inizializza i con 5. movl $3, -4(%rbp): Inizializza j con 3. Ciclo while
cmpl $0, -4(%rbp): Controlla se j è uguale a zero. Se sì, esce dal ciclo. movl -8(%rbp), %eax e cmpl -4(%rbp), %eax: Confronta i con j. Caso i > j: subl -4(%rbp), %eax: Calcola i - j e aggiorna i. Caso i <= j: subl -8(%rbp), %eax: Calcola j - i e aggiorna j. Epilogo della Funzione
movl -8(%rbp), %eax: Carica i in eax, che è il valore di ritorno della funzione. popq %rbp: Ripristina il vecchio base pointer. retq: Termina la funzione e ritorna al chiamante.
- Comportamento del Ciclo
Il ciclo while continua a modificare i e j secondo la seguente logica:
Se i > j, calcola i = i - j. Se i <= j, calcola j = j - i. Il ciclo termina quando j diventa zero. Alla fine del ciclo, i contiene il valore finale che viene restituito dalla funzione main.
Codice ELF
il codice ELF (Executable and Linkable Format) puo' essere ottenuto con l'istruzione
objdump -d -M intel while
e a differenza dell'assembly di cui al
punto precedente e' indipendente dalla piattaforma. Ogni ELF file
ha la seguente struttura :
+-------------------+ | ELF Header | +-------------------+ | Magic Number | -> 0x7F 'E' 'L' 'F' | File Type | -> Eseguibile, Oggetto, Libreria | Architecture | -> x86-64, ARM, ecc. | Entry Point | -> Indirizzo di inizio esecuzione | ... | +-------------------+ | Program Headers | +-------------------+ | Type | -> LOAD, DYNAMIC, INTERP, ecc. | Offset | -> Dove inizia nel file | Virtual Address | -> Dove deve essere caricato in memoria | File Size | -> Dimensione nel file | Memory Size | -> Dimensione in memoria | Flags | -> rwx (read, write, execute) | ... | +-------------------+ | .text Section | -> Codice macchina eseguibile +-------------------+ | Codice Macchina | -> Istruzioni eseguibili +-------------------+ | .data Section | -> Dati inizializzati +-------------------+ | Dati Inizializzati| -> Variabili globali con valori iniziali +-------------------+ | .bss Section | -> Dati non inizializzati +-------------------+ | Dati Non Iniz. | -> Variabili globali non inizializzate +-------------------+ | .Symbol Table | +-------------------+ | Nome | -> Nome del simbolo | Valore | -> Indirizzo in memoria | Dimensione | -> Dimensione del simbolo | Tipo | -> Funzione, Variabile | Bind | -> Locale, Globale | ... | +-------------------+ | .Relocation Table | +-------------------+ | Offset | -> Dove applicare la rilocazione | Tipo | -> Tipo di rilocazione | Simbolo | -> Simbolo associato | Addendo | -> Valore addizionale +-------------------+ | Debugging Info | -> mappa il codice macchina al codice sorgente +-------------------+
nell'esempio del codice C su indicato il file ELF ha il seguente contentuo
while: formato del file elf64-x86-64 Disassemblamento della sezione .init: La sezione .init contiene codice di inizializzazione che viene eseguito prima del main. Solitamente è utilizzata per chiamare il costruttore delle librerie condivise e altre inizializzazioni a livello di runtime. 0000000000001000 <_init>: 1000: 48 83 ec 08 sub rsp,0x8 1004: 48 8b 05 c5 2f 00 00 mov rax,QWORD PTR [rip+0x2fc5] # 3fd0 <__gmon_start__@Base> 100b: 48 85 c0 test rax,rax 100e: 74 02 je 1012 <_init+0x12> 1010: ff d0 call rax 1012: 48 83 c4 08 add rsp,0x8 1016: c3 ret Disassemblamento della sezione .plt: La sezione .plt (Procedure Linkage Table) è utilizzata per la risoluzione delle funzioni esterne in fase di esecuzione. Essa agisce come un ponte tra le chiamate di funzione nel codice e le definizioni delle funzioni nelle librerie condivise. 0000000000001020 <.plt>: 1020: ff 35 ca 2f 00 00 push QWORD PTR [rip+0x2fca] # 3ff0 <_GLOBAL_OFFSET_TABLE_+0x8> 1026: ff 25 cc 2f 00 00 jmp QWORD PTR [rip+0x2fcc] # 3ff8 <_GLOBAL_OFFSET_TABLE_+0x10> 102c: 0f 1f 40 00 nop DWORD PTR [rax+0x0] Disassemblamento della sezione .plt.got: La sezione .plt.got è correlata alla .plt e memorizza indirizzi utilizzati dal PLT per risolvere chiamate di funzione a librerie condivise. È specificamente orientata alla gestione delle chiamate indirette. 0000000000001030 <__cxa_finalize@plt>: 1030: ff 25 aa 2f 00 00 jmp QWORD PTR [rip+0x2faa] # 3fe0 <__cxa_finalize@GLIBC_2.2.5> 1036: 66 90 xchg ax,ax Disassemblamento della sezione .text: La sezione .text contiene il codice eseguibile del programma, compresa la funzione main e altre funzioni definite dall'utente. 0000000000001040 <_start>: 1040: 31 ed xor ebp,ebp 1042: 49 89 d1 mov r9,rdx 1045: 5e pop rsi 1046: 48 89 e2 mov rdx,rsp 1049: 48 83 e4 f0 and rsp,0xfffffffffffffff0 104d: 50 push rax 104e: 54 push rsp 104f: 45 31 c0 xor r8d,r8d 1052: 31 c9 xor ecx,ecx 1054: 48 8d 3d d5 00 00 00 lea rdi,[rip+0xd5] # 1130 <main> 105b: ff 15 5f 2f 00 00 call QWORD PTR [rip+0x2f5f] # 3fc0 <__libc_start_main@GLIBC_2.34> 1061: f4 hlt 1062: 66 2e 0f 1f 84 00 00 cs nop WORD PTR [rax+rax*1+0x0] 1069: 00 00 00 106c: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 0000000000001070 <deregister_tm_clones>: 1070: 48 8d 3d 99 2f 00 00 lea rdi,[rip+0x2f99] # 4010 <__TMC_END__> 1077: 48 8d 05 92 2f 00 00 lea rax,[rip+0x2f92] # 4010 <__TMC_END__> 107e: 48 39 f8 cmp rax,rdi 1081: 74 15 je 1098 <deregister_tm_clones+0x28> 1083: 48 8b 05 3e 2f 00 00 mov rax,QWORD PTR [rip+0x2f3e] # 3fc8 <_ITM_deregisterTMCloneTable@Base> 108a: 48 85 c0 test rax,rax 108d: 74 09 je 1098 <deregister_tm_clones+0x28> 108f: ff e0 jmp rax 1091: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 1098: c3 ret 1099: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 00000000000010a0 <register_tm_clones>: 10a0: 48 8d 3d 69 2f 00 00 lea rdi,[rip+0x2f69] # 4010 <__TMC_END__> 10a7: 48 8d 05 62 2f 00 00 lea rax,[rip+0x2f62] # 4010 <__TMC_END__> 10ae: 48 29 f8 sub rax,rdi 10b1: 48 c1 f8 03 sar rax,0x3 10b5: 48 89 c6 mov rsi,rax 10b8: 48 c1 ee 3f shr rsi,0x3f 10bc: 48 01 c6 add rsi,rax 10bf: 48 d1 fe sar rsi,1 10c2: 74 14 je 10d8 <register_tm_clones+0x38> 10c4: 48 8b 05 0d 2f 00 00 mov rax,QWORD PTR [rip+0x2f0d] # 3fd8 <_ITM_registerTMCloneTable@Base> 10cb: 48 85 c0 test rax,rax 10ce: 74 08 je 10d8 <register_tm_clones+0x38> 10d0: ff e0 jmp rax 10d2: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] 10d8: c3 ret 10d9: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 00000000000010e0 <__do_global_dtors_aux>: 10e0: f3 0f 1e fa endbr64 10e4: 80 3d 25 2f 00 00 00 cmp BYTE PTR [rip+0x2f25],0x0 # 4010 <__TMC_END__> 10eb: 75 2b jne 1118 <__do_global_dtors_aux+0x38> 10ed: 55 push rbp 10ee: 48 83 3d ea 2e 00 00 cmp QWORD PTR [rip+0x2eea],0x0 # 3fe0 <__cxa_finalize@GLIBC_2.2.5> 10f5: 00 10f6: 48 89 e5 mov rbp,rsp 10f9: 74 0c je 1107 <__do_global_dtors_aux+0x27> 10fb: 48 8b 3d 06 2f 00 00 mov rdi,QWORD PTR [rip+0x2f06] # 4008 <__dso_handle> 1102: e8 29 ff ff ff call 1030 <__cxa_finalize@plt> 1107: e8 64 ff ff ff call 1070 <deregister_tm_clones> 110c: c6 05 fd 2e 00 00 01 mov BYTE PTR [rip+0x2efd],0x1 # 4010 <__TMC_END__> 1113: 5d pop rbp 1114: c3 ret 1115: 0f 1f 00 nop DWORD PTR [rax] 1118: c3 ret 1119: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 0000000000001120 <frame_dummy>: 1120: f3 0f 1e fa endbr64 1124: e9 77 ff ff ff jmp 10a0 <register_tm_clones> 1129: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 0000000000001130 <main>: 1130: 55 push rbp 1131: 48 89 e5 mov rbp,rsp 1134: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 113b: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5 1142: c7 45 f4 03 00 00 00 mov DWORD PTR [rbp-0xc],0x3 1149: 83 7d f4 00 cmp DWORD PTR [rbp-0xc],0x0 114d: 0f 84 28 00 00 00 je 117b <main+0x4b> 1153: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 1156: 3b 45 f4 cmp eax,DWORD PTR [rbp-0xc] 1159: 0f 8e 0e 00 00 00 jle 116d <main+0x3d> 115f: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 1162: 2b 45 f4 sub eax,DWORD PTR [rbp-0xc] 1165: 89 45 f8 mov DWORD PTR [rbp-0x8],eax 1168: e9 09 00 00 00 jmp 1176 <main+0x46> 116d: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 1170: 2b 45 f8 sub eax,DWORD PTR [rbp-0x8] 1173: 89 45 f4 mov DWORD PTR [rbp-0xc],eax 1176: e9 ce ff ff ff jmp 1149 <main+0x19> 117b: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 117e: 5d pop rbp 117f: c3 ret Disassemblamento della sezione .fini: 0000000000001180 <_fini>: 1180: 48 83 ec 08 sub rsp,0x8 1184: 48 83 c4 08 add rsp,0x8 1188: c3 ret