Cos'è un debugger e come può aiutarmi a diagnosticare i problemi?

Aug 19 2014

Questa è una domanda generica per aiutare i nuovi programmatori che hanno un problema con un programma, ma non sanno come utilizzare un debugger per diagnosticare la causa del problema.

Questa domanda copre tre classi di domande più specifiche:

  • Quando eseguo il mio programma, non produce l'output che mi aspetto per l'input che gli ho fornito.
  • Quando eseguo il mio programma, si blocca e mi dà una traccia dello stack. Ho esaminato la traccia dello stack , ma non conosco ancora la causa del problema perché la traccia dello stack non fornisce informazioni sufficienti.
  • Quando eseguo il mio programma, si blocca a causa di un errore di segmentazione (SEGV).

Risposte

68 Raedwald Aug 19 2014 at 20:49

Un debugger è un programma che può esaminare lo stato del tuo programma mentre il tuo programma è in esecuzione. I mezzi tecnici che utilizza per fare ciò non sono importanti per comprendere le basi di come utilizzare un debugger. È possibile utilizzare un debugger per interrompere l'esecuzione del programma quando raggiunge un punto particolare nel codice, quindi esaminare i valori delle variabili nel programma. Puoi usare un debugger per eseguire il tuo programma molto lentamente, una riga di codice alla volta (chiamato single stepping ), mentre esamini i valori delle sue variabili.

L'utilizzo di un debugger è un'abilità di base prevista

Un debugger è uno strumento molto potente per aiutare a diagnosticare i problemi con i programmi. E sono disponibili debugger per tutti i linguaggi di programmazione pratici. Pertanto essere in grado di utilizzare un debugger è considerata un'abilità di base di qualsiasi programmatore professionista o appassionato. E l'utilizzo di un debugger da soli è considerato un lavoro di base che dovresti fare da solo prima di chiedere aiuto ad altri. Poiché questo sito è per programmatori professionisti ed entusiasti e non è un helpdesk o un sito di mentoring, se hai una domanda su un problema con un programma specifico, ma non hai utilizzato un debugger, è molto probabile che la tua domanda venga chiusa e sottovalutata. Se insisti con domande del genere, alla fine ti verrà impedito di pubblicare di più.

Come può aiutarti un debugger

Usando un debugger puoi scoprire se una variabile ha un valore sbagliato e dove nel tuo programma il suo valore è cambiato in un valore sbagliato.

Usando il single stepping puoi anche scoprire se il flusso di controllo è come ti aspetti. Ad esempio, se un iframo viene eseguito quando ti aspetti che dovrebbe essere.

Note generali sull'utilizzo di un debugger

Le specifiche dell'utilizzo di un debugger dipendono dal debugger e, in misura minore, dal linguaggio di programmazione che stai utilizzando.

  • È possibile collegare un debugger a un processo che già esegue il programma. Potresti farlo se il tuo programma è bloccato.

  • In pratica è spesso più facile eseguire il programma sotto il controllo di un debugger fin dall'inizio.

  • Indichi dove il tuo programma deve terminare l'esecuzione indicando il file del codice sorgente e il numero di riga della riga in cui deve terminare l'esecuzione, o indicando il nome del metodo / funzione in cui il programma deve interrompersi (se vuoi fermare come appena l'esecuzione entra nel metodo). Il mezzo tecnico che il debugger utilizza per arrestare il programma è chiamato punto di interruzione e questo processo è chiamato impostazione di un punto di interruzione .

  • La maggior parte dei debugger moderni fa parte di un IDE e fornisce una comoda GUI per esaminare il codice sorgente e le variabili del programma, con un'interfaccia punta e clicca per impostare i punti di interruzione, eseguire il programma e eseguire un singolo passaggio.

  • L'utilizzo di un debugger può essere molto difficile a meno che i file eseguibili o bytecode del programma non includano informazioni sui simboli di debug e riferimenti incrociati al codice sorgente. Potrebbe essere necessario compilare (o ricompilare) il programma in modo leggermente diverso per assicurarsi che le informazioni siano presenti. Se il compilatore esegue ottimizzazioni estese, questi riferimenti incrociati possono creare confusione. Potrebbe quindi essere necessario ricompilare il programma con le ottimizzazioni disattivate .

39 SlugFiller Apr 05 2015 at 19:15

Voglio aggiungere che un debugger non è sempre la soluzione perfetta e non dovrebbe essere sempre la soluzione ideale per il debug. Ecco alcuni casi in cui un debugger potrebbe non funzionare per te:

  • La parte del tuo programma che fallisce è davvero grande (scarsa modularizzazione, forse?) E non sei esattamente sicuro da dove iniziare a passare attraverso il codice. Passare attraverso tutto ciò potrebbe richiedere troppo tempo.
  • Il programma utilizza molti callback e altri metodi di controllo del flusso non lineare, il che rende confuso il debugger quando lo si esegue.
  • Il tuo programma è multi-thread. O peggio ancora, il tuo problema è causato da una condizione di gara.
  • Il codice che contiene il bug viene eseguito molte volte prima che venga eliminato. Ciò può essere particolarmente problematico nei circuiti principali o, peggio ancora, nei motori fisici, dove il problema potrebbe essere numerico. Anche l'impostazione di un punto di interruzione, in questo caso, ti farebbe semplicemente premere molte volte, con il bug che non appare.
  • Il tuo programma deve essere eseguito in tempo reale. Questo è un grosso problema per i programmi che si connettono alla rete. Se imposti un punto di interruzione nel codice di rete, l'altra estremità non aspetterà che tu passi, semplicemente scadrà. Anche i programmi che si basano sull'orologio di sistema, ad esempio giochi con frameskip, non stanno molto meglio.
  • Il tuo programma esegue una qualche forma di azioni distruttive, come scrivere su file o inviare e-mail, e vorresti limitare il numero di volte che devi eseguirlo.
  • Puoi dire che il tuo bug è causato da valori errati che arrivano alla funzione X, ma non sai da dove provengono questi valori. Dover eseguire ripetutamente il programma, impostare punti di interruzione sempre più indietro, può essere una seccatura enorme. Soprattutto se la funzione X viene chiamata da molti punti del programma.

In tutti questi casi, l'interruzione improvvisa del programma potrebbe far sì che i risultati finali differiscano o eseguire manualmente la ricerca dell'unica riga in cui è causato il bug è una seccatura. Questo può accadere anche se il tuo bug è un comportamento scorretto o un crash. Ad esempio, se il danneggiamento della memoria provoca un arresto anomalo, nel momento in cui si verifica l'arresto anomalo, è troppo lontano dal punto in cui si è verificato per la prima volta il danneggiamento della memoria e non vengono lasciate informazioni utili.

Quindi quali sono le alternative?

Il più semplice è semplicemente la registrazione e le affermazioni. Aggiungi log al tuo programma in vari punti e confronta ciò che ottieni con ciò che ti aspetti. Ad esempio, controlla se la funzione in cui pensi che ci sia un bug viene persino chiamata in primo luogo. Verifica se le variabili all'inizio di un metodo sono ciò che pensi che siano. A differenza dei punti di interruzione, va bene che ci siano molte righe di registro in cui non accade nulla di speciale. Puoi semplicemente cercare nel registro in seguito. Una volta raggiunta una riga di registro diversa da quella che ti aspetti, aggiungine altri nella stessa area. Restringilo sempre di più, finché non è abbastanza piccolo da poter registrare ogni linea nell'area interessata.

Le asserzioni possono essere utilizzate per intercettare i valori errati nel momento in cui si verificano, piuttosto che una volta che hanno un effetto visibile all'utente finale. Più velocemente prendi un valore errato, più sei vicino alla linea che lo ha prodotto.

Refactoring e unit test. Se il tuo programma è troppo grande, potrebbe essere utile testarlo una classe o una funzione alla volta. Dagli input e guarda gli output e vedi quali non sono come ti aspetti. Essere in grado di restringere un bug da un intero programma a una singola funzione può fare un'enorme differenza nel tempo di debug.

In caso di memory leak o memory stomping, utilizzare strumenti appropriati in grado di analizzarli e rilevarli in fase di esecuzione. Essere in grado di rilevare dove si verifica l'effettivo danneggiamento è il primo passo. Successivamente, puoi utilizzare i log per tornare al punto in cui sono stati introdotti valori errati.

Ricorda che il debug è un processo che va a ritroso. Hai il risultato finale - un bug - e trova la causa, che lo ha preceduto. Si tratta di lavorare a ritroso e, sfortunatamente, i debugger fanno solo un passo avanti. È qui che una buona registrazione e analisi post-mortem possono darti risultati molto migliori.