2013-04-15 8 views
7

Controllare il codice qui sotto:può GCC stampare risultati intermedi?

#include <avr/io.h> 

const uint16_t baudrate = 9600; 

void setupUART(void) { 
     uint16_t ubrr = ((F_CPU/(16 * (float) baudrate)) - 1 + .5); 
     UBRRH = ubrr >> 8; 
     UBRRL = ubrr & 0xff; 
} 

int main(void) { 
     setupUART(); 
} 

Questo è il comando usato per compilare il codice:

avr-gcc -g -DF_CPU=4000000  -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp 

ubrr è calcolato dal compilatore come 25, finora tutto bene. Tuttavia, per verificare cosa ha calcolato il compilatore, ho dato un'occhiata alla lista di disassemblaggio.

000000ae <setupUART()>: 
    ae: 12 b8   out  UBRRH, r1  ; 0x02 
    b0: 89 e1   ldi  r24, 0x19  ; 25 
    b2: 89 b9   out  UBRRL, r24  ; 0x09 
    b4: 08 95   ret 

E 'possibile effettuare avr-gcc stampare il risultato intermedio al momento della compilazione (o tirare le informazioni dal file .o), in modo che quando compilo il codice viene stampato una riga simile (uint16_t) ubbr = 25 o simili? In questo modo posso eseguire un rapido controllo di integrità sui calcoli e le impostazioni.

+0

Hai provato l'opzione '-s'? – devnull

+0

@devnull Non esce dal compilatore? Voglio che il compilatore finisca il lavoro, basta stampare un calcolo intermedio che ha fatto. – jippie

+0

Se non si passa '-Os', probabilmente si otterrà esattamente ciò che si desidera vedere nello smontaggio. Probabilmente non vuoi spedire in quel modo, comunque. ;-) –

risposta

3

GCC ha delle opzioni della riga di comando per chiedere che la sua discarica rappresentazione intermedia dopo ogni fase di compilazione. le discariche "albero" sono pseudo Sintassi C e contiene le informazioni desiderate. Per quello che stai cercando di fare, i dump -fdump-tree-original e -fdump-tree-optimized si verificano in punti utili nell'optimi conduttura di zazione. Non ho un compilatore AVR a portata di mano, così ho modificato il banco di prova per essere autonomo e compilabile con il compilatore devo:

typedef unsigned short uint16_t; 
const int F_CPU = 4000000; 
const uint16_t baudrate = 9600; 
extern uint16_t UBRRH, UBRRL; 

void 
setupUART(void) 
{ 
    uint16_t ubrr = ((F_CPU/(16 * (float) baudrate)) - 1 + .5); 
    UBRRH = ubrr >> 8; 
    UBRRL = ubrr & 0xff; 
} 

e poi

$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c 
$ cat test.c.003t.original 
;; Function setupUART (null) 
;; enabled by -tree-original 


{ 
    uint16_t ubrr = 25; 

    uint16_t ubrr = 25; 
    UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8); 
    UBRRL = ubrr & 255; 
} 

$ cat test.c.149t.optimized 
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0) 

setupUART() 
{ 
<bb 2>: 
    UBRRH = 0; 
    UBRRL = 25; 
    return; 
} 

si può vedere quel piegamento di espressioni costanti è fatto così presto che è già accaduto nel dump "originale" (che è il primo dump comprensibile che si possa avere), e che l'ottimizzazione ha ulteriormente piegato le operazioni di spostamento e maschera nelle istruzioni scrivendo a UBRRH e UBRRL .

I numeri nei nomi file (003t e 149t) saranno probabilmente diversi per voi. Se vuoi vedere tutti gli i dump "albero", usa -fdump-tree-all. Ci sono anche dump "RTL", che non assomigliano a C e probabilmente non ti sono utili. Se sei curioso, però, -fdump-rtl-all li accenderà. In totale ci sono circa 100 tree e 60 dump RTL, quindi è una buona idea farlo in una directory scratch.

(Psssst: Ogni volta che si mette gli spazi all'interno dei vostri parentesi, Dio uccide un gattino.)

+0

Bello! Posso fare un 'grep -E '^ [\ t] * uint16_t ubrr' project.cpp.003t.original' che mi sembra una soluzione robusta di apretty per me – jippie

+0

http://i.stack.imgur.com/ofkaC.png – jippie

2

Potrebbe esserci una soluzione per la stampa di risultati intermedi, ma ci vorrà un po 'di tempo per essere implementata. Quindi vale la pena solo per una base di codice sorgente piuttosto ampia.

È possibile personalizzare il compilatore GCC; tramite un plug-in (dolorosamente codificato in C o C++) o attraverso un'estensione MELT. MELT è un linguaggio di dominio di alto livello simile a Lisp per estendere GCC. (È implementato come un plugin [meta-] per GCC ed è tradotto in codice C++ adatto per GCC).

Tuttavia un tale approccio si richiede di capire interni GCC, quindi aggiungere la tua "ottimizzazione" passare a fare il aspect oriented programming (per esempio usando MELT) per stampare le rilevanti risultati intermedi.

Si potrebbe anche guardare non solo il gruppo generato (e usare -fverbose-asm -S come opzioni per GCC) ma anche forse nelle rappresentazioni Gimple generate (forse con -fdump-tree-gimple). Per alcuni strumenti interattivi, considera il grafico MELT probe.

Forse l'aggiunta del proprio builtin (con estensione MELT) come __builtin_display_compile_time_constant potrebbe essere rilevante.

1

Dubito che ci sia un modo semplice per determinare cosa fa il compilatore. Potrebbero esserci alcuni strumenti in gcc appositamente per scaricare la forma intermedia del linguaggio, ma sicuramente non sarà facile da leggere, e a meno che non si sospetti VERAMENTE che il compilatore stia facendo qualcosa di sbagliato (e abbia un esempio MOLTO piccolo per mostrarlo) , è improbabile che tu possa usarlo per qualcosa di significativo - semplicemente perché è troppo lavoro per seguire quello che sta succedendo.

Un approccio migliore è quello di aggiungere variabili temporanee (e forse stampe) al codice, se si preoccupare che sia corretto:

uint16_t ubrr = ((F_CPU/(16 * (float) baudrate)) - 1 + .5); 
    uint8_t ubrr_high = ubrr >> 8 
    uint8_t ubrr_low = ubrr & 0xff; 
    UBRRH = ubrr_high; 
    UBRRL = ubrr_low; 

Ora, se si dispone di una build non ottimizzato e passo attraverso di essa in GDB, dovresti essere in grado di vedere cosa fa. Altrimenti, aggiungendo stampe di qualche tipo al codice per mostrare quali sono i valori ...

Se non riesci a stamparlo sul sistema di destinazione perché sei in procinto di impostare l'uart che utilizzerai per stampare, quindi replicare il codice sul sistema host locale e eseguirne il debug. A meno che il compilatore non sia molto buggato, dovresti ottenere gli stessi valori dalla stessa compilation.

1

Ecco un trucco: è sufficiente automatizzare ciò che si sta facendo a mano ora.

  • nel makefile, assicurarsi che avr-gcc produce uno smontaggio (-ahlms=output.lst). In alternativa, utilizzare il proprio metodo di dissociazione come passaggio post-compilazione nel makefile.
  • Come passaggio post-compilazione, elaborare il file di elenco utilizzando il linguaggio di scripting preferito per cercare le linee out UBRRH e out UBRRL. Questi verranno caricati dai registri, in modo che lo script possa estrarre i compiti immediatamente precedenti ai registri che verranno caricati in UBRRH e UBRRL. Lo script può quindi riassemblare il valore UBRR dal valore caricato nei registri generici che sono utilizzati per impostare UBRRH e UBRRL.

Questo sembra essere più facile di quanto Basile Starynkevich 's suggerimento molto utile di MELT estensione. Ora, ammesso che questa soluzione sembra fragili, a prima vista, quindi cerchiamo di considerare questo problema:

  • Abbiamo sappiamo che (almeno sul processore) le linee appariranno nella lista di smontaggio out UBRR_, r__: c'è semplicemente non c'è altro modo per impostare i registri/scrivere i dati sulla porta. Una cosa che potrebbe cambiare è la spaziatura dentro/intorno a queste linee ma questa può essere facilmente gestita dal tuo script
  • Sappiamo anche che le istruzioni out possono essere eseguite solo da registri di uso generale, quindi sappiamo che ci sarà un generale scopo registrare come secondo argomento per la riga di istruzioni out, in modo che non dovrebbe essere un problema.
  • Infine, sappiamo anche che questo registro verrà impostato prima dell'istruzione out. Qui dobbiamo consentire una certa variabilità: invece di LDI (carico immediato), avr-gcc potrebbe produrre qualche altra serie di istruzioni per impostare il valore del registro. Penso che come prima passata la sceneggiatura dovrebbe essere in grado di analizzare il caricamento immediato, e in ogni caso scaricare qualsiasi istruzione che trova che coinvolge il registro che sarà scritta nelle porte UBRR_.

Lo script potrebbe dover cambiare se cambiare piattaforma (alcuni processori hanno UBRRH1/2 registri instea di UBRRH, ma in questo caso si codice di trasmissione dovrà cambiare. Se lo script lamenta che non può analizzare lo smontaggio allora ti almeno sapere che il vostro controllo non è stato eseguito.

+0

UBRRH mostrerà r1 che di solito è impostato su 0 all'avvio (usando un 'eor r1, r1', ma è davvero difficile esserne certi – jippie

+0

Bene, nel peggiore dei casi lo script può stampare' eor r1, r1' usando la "stampa l'ultima cosa che coinvolge il registro che viene utilizzato per impostare la porta" euristica, oppure potresti scrivere un mini simulatore AVR nel tuo script, insegnandogli che XOR - un valore con se stesso significa "impostalo a zero":) – angelatlarge

Problemi correlati