Perché printf () è dannoso per il debug dei sistemi integrati?


16

Immagino sia una brutta cosa provare a eseguire il debug di un progetto basato su microcontrollore utilizzando printf().

Posso capire che non hai un posto predefinito per l'output e che potrebbe consumare pin preziosi. Allo stesso tempo, ho visto le persone consumare un pin UART TX per l'output al terminale IDE con una DEBUG_PRINT()macro personalizzata .


12
Chi ti ha detto che è male? "Di solito non è il migliore" non è lo stesso di un "cattivo" non qualificato.
Spehro Pefhany,

6
Tutto questo parlare di quanto sovraccarico ci sia, se tutto ciò che devi fare è emettere messaggi "Sono qui", non hai bisogno di printf, solo una routine per inviare una stringa a un UART. Questo, oltre al codice per inizializzare l'UART, è probabilmente inferiore a 100 byte di codice. L'aggiunta della capacità di produrre un paio di valori esadecimali non aumenterà così tanto.
Tcrosley,

7
@ChetanBhargava - I file di intestazione C di solito non aggiungono codice all'eseguibile. Contengono dichiarazioni; se il resto del codice non utilizza le cose dichiarate, il codice per quelle cose non viene collegato. Se si utilizza printf, ovviamente, tutto il codice necessario per implementare printfviene collegato all'eseguibile. Ma è perché il codice lo ha usato, non a causa dell'intestazione.
Pete Becker,

2
@ChetanBhargava Non è nemmeno necessario includere <stdio.h> se esegui il rollup della tua semplice routine per generare una stringa come ho descritto (caratteri di output su UART fino a quando non vedi un '\ 0') '
tcrosley

2
@tcrosley Penso che questo consiglio sia probabilmente discutibile se hai un buon compilatore moderno, se usi printf nel semplice caso senza una stringa di formato gcc e la maggior parte degli altri lo sostituisce con una semplice chiamata più efficiente che fa molto come descrivi.
Valità

Risposte:


24

Posso trovare alcuni svantaggi dell'utilizzo di printf (). Tieni presente che il "sistema incorporato" può variare da qualcosa con poche centinaia di byte di memoria del programma a un sistema completo basato su RTN QNX montato su rack con gigabyte di RAM e terabyte di memoria non volatile.

  • Richiede un posto dove inviare i dati. Forse hai già un debug o una porta di programmazione sul sistema, forse no. Se non lo fai (o quello che hai non funziona) non è molto utile.

  • Non è una funzione leggera in tutti i contesti. Questo potrebbe essere un grosso problema se si dispone di un microcontrollore con solo pochi K di memoria, perché il collegamento in printf potrebbe consumare 4K tutto da solo. Se hai un microcontrollore 32K o 256K, probabilmente non è un problema, figuriamoci se hai un grande sistema incorporato.

  • È di scarsa o nessuna utilità per individuare determinati tipi di problemi relativi all'allocazione o agli interrupt di memoria e può modificare il comportamento del programma quando le istruzioni sono incluse o meno.

  • È abbastanza inutile per gestire cose sensibili ai tempi. Staresti meglio con un analizzatore logico e un oscilloscopio o un analizzatore di protocollo o persino un simulatore.

  • Se hai un grande programma e devi ricompilare molte volte mentre cambi le istruzioni printf e le cambi, potresti perdere molto tempo.

A cosa serve- è un modo rapido per trasmettere i dati in un modo preformattato che ogni programmatore C sa usare - curva di apprendimento zero. Se hai bisogno di sputare una matrice per il filtro Kalman di cui stai eseguendo il debug, potrebbe essere bello sputarlo in un formato che MATLAB potrebbe leggere. Sicuramente meglio che guardare le posizioni RAM una alla volta in un debugger o emulatore .

Non penso che sia una freccia inutile nella faretra, ma dovrebbe essere usata con parsimonia, insieme a gdb o altri debugger, emulatori, analizzatori logici, oscilloscopi, strumenti di analisi del codice statico, strumenti di copertura del codice e così via.


3
La maggior parte delle printf()implementazioni non sono thread-safe (ovvero non rientranti) che non è un killer, ma qualcosa da tenere a mente quando lo si utilizza in un ambiente multi-thread.
JRobert,

1
@JRobert fa emergere un buon punto .. e anche in un ambiente senza un sistema operativo, è difficile fare molto utile il debug diretto degli ISR. Ovviamente se stai eseguendo printf () o la matematica in virgola mobile in un ISR l'approccio è probabilmente spento.
Spehro Pefhany,

@JRobert Quali strumenti di debug hanno gli sviluppatori software che lavorano in un ambiente multi-thread (in un'impostazione hardware in cui l'uso degli analizzatori logici e degli oscilloscopi non è pratico)?
Minh Tran,

1
In passato ho creato il mio printf () thread-safe; usato equivalenti put () o putchar () a piedi nudi per sputare dati molto concisi su un terminale; dati binari memorizzati in un array che ho scaricato e interpretato dopo l'esecuzione del test; utilizzato una porta I / O per far lampeggiare un LED o generare impulsi per effettuare misurazioni di temporizzazione con un oscilloscopio; sputa un numero in un D / A e misurato con VOM ... L'elenco è lungo quanto la tua immaginazione e inversamente grande quanto il tuo budget! :)
JRobert

19

Oltre ad alcune altre belle risposte, l'atto di inviare dati a una porta a baud rate seriali può essere decisamente lento rispetto al tempo di ciclo e avere un impatto sul modo in cui funziona il resto del programma (come QUALSIASI debug processi).

Come altre persone ti hanno detto, non c'è niente di "cattivo" nell'uso di questa tecnica, ma ha, come molte altre tecniche di debug, i suoi limiti. Se conosci e riesci a gestire queste limitazioni, può essere estremamente conveniente aiutarti a correggere il codice.

I sistemi integrati hanno una certa opacità che, in generale, rende il debug un po 'problematico.


8
+1 per "i sistemi integrati hanno una certa opacità". Sebbene tema che questa affermazione possa essere comprensibile solo da coloro che hanno una discreta esperienza di lavoro con Embedded, costituisce un riassunto piacevole e conciso della situazione. È vicino a una definizione di "incorporato", in realtà.
njahnke,

5

Esistono due problemi principali che si verificano durante il tentativo di utilizzare printfsu un microcontrollore.

Innanzitutto, può essere una seccatura collegare l'output alla porta corretta. Non sempre. Ma alcune piattaforme sono più difficili di altre. Alcuni file di configurazione possono essere scarsamente documentati e potrebbe essere necessaria molta sperimentazione.

Il secondo è la memoria. Una printflibreria completa può essere GRANDE. A volte non hai bisogno di tutti gli identificatori di formato e possono essere disponibili versioni specializzate. Ad esempio, quello stdio.h fornito da AVR contiene tre differenti printfdi dimensioni e funzionalità diverse.

Poiché l'implementazione completa di tutte le funzionalità menzionate diventa piuttosto ampia, è vfprintf()possibile selezionare tre diversi tipi di opzioni di collegamento. L'impostazione predefinita vfprintf()implementa tutte le funzionalità menzionate tranne le conversioni in virgola mobile. È vfprintf()disponibile una versione minimizzata di che implementa solo le funzioni di conversione di numeri interi e stringhe di base, ma solo l' #opzione aggiuntiva può essere specificata usando i flag di conversione (questi flag vengono analizzati correttamente dalle specifiche del formato, ma poi semplicemente ignorati).

Ho avuto un'istanza in cui non era disponibile alcuna libreria e avevo una memoria minima. Quindi non avevo altra scelta che usare una macro personalizzata. Ma l'uso printfo meno è davvero uno di quelli che si adatteranno alle tue esigenze.


Il downvoter potrebbe spiegare cosa nella mia risposta è errato in modo da poter evitare il mio errore in progetti futuri?
embedded.kyle

4

Per aggiungere ciò che Spehro Pefhany stava dicendo su "cose ​​sensibili ai tempi": facciamo un esempio. Supponiamo che tu abbia un giroscopio da cui il tuo sistema incorporato sta eseguendo 1.000 misurazioni al secondo. Si desidera eseguire il debug di queste misurazioni, quindi è necessario stamparle. Problema: la loro stampa fa sì che il sistema sia troppo occupato per leggere 1.000 misurazioni al secondo, causando il traboccamento del buffer del giroscopio, causando la lettura (e la stampa) di dati corrotti. E così, stampando i dati, hai corrotto i dati, facendoti pensare che ci sia un bug nella lettura dei dati quando forse in realtà non lo è. Un cosiddetto heisenbug.


lol! "Heisenbug" è davvero un termine tecnico? Immagino che abbia a che fare con la misurazione dello stato delle particelle e con il Principio di Heisenburg ...
Zeta.Investigator

3

Il motivo più grande per non eseguire il debug con printf () è che di solito è inefficiente, inadeguato e non necessario.

Inefficiente: printf () e kin usano molto flash e RAM rispetto a ciò che è disponibile su un piccolo microcontrollore, ma la maggiore inefficienza è nel debugging effettivo. La modifica di ciò che viene registrato richiede la ricompilazione e la riprogrammazione del target, che rallenta il processo. Utilizza anche un UART che altrimenti potresti utilizzare per fare un lavoro utile.

Inadeguato: ci sono solo così tanti dettagli che puoi produrre su un collegamento seriale. Se il programma si blocca, non sai esattamente dove, solo l'ultimo output completato.

Non necessario: è possibile eseguire il debug remoto di molti microcontrollori. È possibile utilizzare JTAG o protocolli proprietari per mettere in pausa il processore, sbirciare registri e RAM e persino modificare lo stato del processore in esecuzione senza dover ricompilare. Questo è il motivo per cui i debugger sono generalmente un modo migliore di debug rispetto alle istruzioni di stampa, anche su un PC con tonnellate di spazio e potenza.

È un peccato che la piattaforma di microcontrollori più comune per i neofiti, Arduino, non abbia un debugger. L'AVR supporta il debug remoto, ma il protocollo debugWIRE di Atmel è proprietario e non documentato. Puoi utilizzare una scheda di sviluppo ufficiale per eseguire il debug con GDB, ma se lo hai probabilmente non sei più troppo preoccupato per Arduino.


Non potresti usare i puntatori a funzione per giocare con ciò che viene registrato e aggiungere un sacco di flessibilità?
Scott Seidman,

3

printf () non funziona da solo. Chiama molte altre funzioni e se hai poco spazio nello stack potresti non essere in grado di utilizzarlo per eseguire il debug di problemi vicini al limite dello stack. A seconda del compilatore e del microcontrollore, la stringa di formato può anche essere collocata in memoria, anziché essere referenziata da Flash. Questo può aumentare in modo significativo se aggiungi il tuo codice con istruzioni printf. Questo è un grosso problema nell'ambiente Arduino: i principianti che usano dozzine o centinaia di istruzioni printf si imbattono improvvisamente in problemi apparentemente casuali perché sovrascrivono il loro mucchio con il loro stack.


2
Mentre apprezzo le valutazioni del downvote stesso fornisce, sarebbe più utile per me e gli altri, se quelli che non sono d'accordo spiegato i problemi con questa risposta. Siamo tutti qui per imparare e condividere conoscenze, prendere in considerazione la condivisione delle tue.
Adam Davis

3

Anche se uno vuole i dati sputare a qualche forma di console di registrazione, la printffunzione non è generalmente un buon modo di fare che, dal momento che deve esaminare la stringa di formato passata e analizzarlo in fase di esecuzione; anche se il codice non utilizza mai alcun identificatore di formato diverso %04X, il regolatore generalmente necessario includere tutto il codice che sarebbe necessario per analizzare le stringhe di formato arbitrarie. A seconda del controller esatto che si sta utilizzando, potrebbe essere molto più efficiente utilizzare un codice simile a:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

Su alcuni microcontrollori PIC, log_hexi32(l)sarebbe probabilmente prendere 9 istruzioni e potrebbe richiedere 17 (se lè nella seconda banca), mentre log_hexi32p(&l)prenderebbe 2. La log_hexi32pfunzione stessa potrebbe scrivere per essere circa 14 istruzioni lungo, quindi sarebbe pagare per sé, se chiamato due volte .


2

Un punto che nessuna delle altre risposte ha menzionato: in un micro (IE di base c'è solo il ciclo principale () e forse un paio di ISR ​​in esecuzione in qualsiasi momento, non un sistema operativo multi-thread) se si arresta in modo anomalo / si arresta / ottiene bloccato in un ciclo, la tua funzione di stampa semplicemente non avverrà .

Inoltre, la gente ha detto "non usare printf" o "stdio.h occupa molto spazio" ma non offre molte alternative: embedded.kyle menziona alternative semplificate, e questo è esattamente il genere di cose che dovresti probabilmente essere facendo ovviamente su un sistema embedded di base. Una routine di base per eliminare alcuni caratteri dall'UART potrebbe essere qualche byte di codice.


Se il tuo printf non accade, hai imparato molto su dove il tuo codice è problematico.
Scott Seidman,

Supponendo che tu abbia solo una stampa che potrebbe accadere, sì. Ma gli interrupt possono sparare centinaia di volte nel tempo necessario a una chiamata printf () per ottenere qualcosa dall'UART
John U
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.