UP | HOME

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:

  1. clang
  2. 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 ;

  1. editing;
  2. scrittura di un analizzatore di tocken;
  3. scrittura di un parser che genera un AST e analizza il linguaggio alla ricerca di errori sintattici;
  4. 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:

  1. Portabilità:
    1. Codice Oggetto: Specifico per un'architettura e un sistema operativo. Deve essere ricompilato per altre piattaforme.
    2. Bytecode: Più portabile, può essere eseguito su diverse piattaforme che supportano l'interprete o la macchina virtuale appropriata.
  2. Esecuzione:
    1. Codice Oggetto: Eseguibile direttamente dall'hardware dopo il linking.
    2. Bytecode: Deve essere interpretato o ulteriormente compilato da una macchina virtuale o un JIT compiler.
  3. Uso:
    1. Codice Oggetto: Usato in compilazioni tradizionali di linguaggi come C e C++.
    2. 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

  1. 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'
    
  2. 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;
  3. 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;
  4. 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

  1. 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;
    1. 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;
    2. 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.

  1. Compilazione con istruzioni di debug clang -g -o while while.c
  2. 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

Link Utili

GNU Sito in italiano relativo al progetto GNU

BLOG Sezione ARI Montecatini Terme

ARI ( Associazione Radioamatori Italiani)

PTLUG ( Linux User Group Pistoia )

ARAL ( Associazione Radiamatori Monte Amiata / Monte Labbro )

Author: ARI people

Created: 2024-10-03 gio 10:49

Validate