Domanda

Sto cercando una risposta in MS VC++.

Quando si esegue il debug di un'applicazione C++ di grandi dimensioni, che purtroppo utilizza molto ampiamente le eccezioni C++.A volte osservo un'eccezione un po' più tardi di quanto vorrei.

Esempio in pseudo codice:

FunctionB()
{
    ...
    throw e;
    ...
}

FunctionA()
{
    ...
    FunctionB()
    ...
}

try
{
    Function A()
}
catch(e)
{
    (<--- breakpoint)
    ...
}

Posso rilevare l'eccezione con un punto di interruzione durante il debug.Ma non riesco a risalire se l'eccezione si è verificata in FunctionA() O FunctionB(), o qualche altra funzione.(Supponendo un ampio utilizzo delle eccezioni e una versione enorme dell'esempio precedente).

Una soluzione al mio problema è determinare e salvare lo stack di chiamate nel costruttore dell'eccezione (cioè.prima che venga catturato).Ma questo richiederebbe che derivassi tutte le eccezioni da questa classe di eccezioni base.Richiederebbe anche molto codice e forse rallenterebbe il mio programma.

Esiste un modo più semplice che richieda meno lavoro?Senza dover modificare la mia grande base di codice?

Esistono soluzioni migliori a questo problema in altre lingue?

È stato utile?

Soluzione

Se sei interessato solo alla provenienza dell'eccezione, potresti semplicemente scrivere una semplice macro come

#define throwException(message) \
    {                           \
        std::ostringstream oss; \
        oss << __FILE __ << " " << __LINE__ << " "  \
           << __FUNC__ << " " << message; \
        throw std::exception(oss.str().c_str()); \
    }

che aggiungerà il nome del file, il numero di riga e il nome della funzione al testo dell'eccezione (se il compilatore fornisce le rispettive macro).

Quindi lancia eccezioni usando

throwException("An unknown enum value has been passed!");

Altri suggerimenti

Hai indicato un punto di interruzione nel codice.Dato che sei nel debugger, puoi impostare un punto di interruzione sul costruttore della classe di eccezioni o impostare il debugger di Visual Studio in modo che si interrompa su tutte le eccezioni generate (Debug-> Eccezioni fai clic su eccezioni C++, seleziona le opzioni generate e non rilevate)

C'è un eccellente libro scritto da John Robbins che affronta molte difficili domande di debug.Il libro si chiama Debug di applicazioni per Microsoft .NET e Microsoft Windows.Nonostante il titolo, il libro contiene una serie di informazioni sul debug di applicazioni C++ native.

In questo libro è presente una lunga sezione su come ottenere lo stack di chiamate per le eccezioni generate.Se ricordo bene, alcuni dei suoi consigli riguardano l'uso gestione strutturata delle eccezioni (SEH) invece di (o in aggiunta a) eccezioni C++.Non posso davvero raccomandare vivamente il libro.

Inserisci un punto di interruzione nel costruttore dell'oggetto eccezione.Otterrai il punto di interruzione prima che venga generata l'eccezione.

Non c'è modo di scoprire l'origine di un'eccezione Dopo viene catturato, a meno che tu non includa tale informazione quando viene lanciato.Nel momento in cui rilevi l'eccezione, lo stack è già svolto e non c'è modo di ricostruire lo stato precedente dello stack.

Il tuo suggerimento di includere la traccia dello stack nel costruttore è la soluzione migliore.Sì, costa tempo durante la costruzione, ma probabilmente non dovresti lanciare eccezioni abbastanza spesso da rendere questo un problema.Anche far ereditare tutte le tue eccezioni da una nuova base potrebbe essere più del necessario.Potresti semplicemente far ereditare le eccezioni pertinenti (grazie, ereditarietà multipla) e avere una cattura separata per quelle.

Puoi usare il StackTrace64 funzione per costruire la traccia (credo che ci siano anche altri modi).Guardare Questo articolo ad esempio il codice.

Ecco come lo faccio in C++ usando le librerie GCC:

#include <execinfo.h> // Backtrace
#include <cxxabi.h> // Demangling

vector<Str> backtrace(size_t numskip) {
    vector<Str> result;
    std::vector<void*> bt(100);
    bt.resize(backtrace(&(*bt.begin()), bt.size()));
    char **btsyms = backtrace_symbols(&(*bt.begin()), bt.size());
    if (btsyms) {
        for (size_t i = numskip; i < bt.size(); i++) {
            Aiss in(btsyms[i]);
            int idx = 0; Astr nt, addr, mangled;
            in >> idx >> nt >> addr >> mangled;
            if (mangled == "start") break;
            int status = 0;
            char *demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);

            Str frame = (status==0) ? Str(demangled, demangled+strlen(demangled)) : 
                                      Str(mangled.begin(), mangled.end());
            result.push_back(frame);

            free(demangled);
        }
        free(btsyms);
    }
    return result;
}

Il costruttore della tua eccezione può semplicemente chiamare questa funzione e archiviare l'analisi dello stack.Ci vuole il param numskip perché mi piace eliminare il costruttore dell'eccezione dalle mie tracce di stack.

Non esiste un modo standard per farlo.

Inoltre, lo stack di chiamate deve in genere essere registrato nel momento in cui si verifica l'eccezione gettato;una volta che è stato preso la pila si è srotolata, quindi non sai più cosa stava succedendo nel momento in cui sei stata lanciata.

In VC++ su Win32/Win64, tu Potrebbe ottieni risultati sufficientemente utilizzabili registrando il valore dall'intrinseco _ReturnAddress() del compilatore e assicurandoti che il costruttore della classe di eccezione sia __declspec(noinline).Insieme alla libreria dei simboli di debug, penso che potresti probabilmente ottenere il nome della funzione (e il numero di riga, se il tuo .pdb lo contiene) che corrisponde all'indirizzo di ritorno usando SymGetLineFromAddr64.

Nel codice nativo puoi provare a percorrere lo stack di chiamate installando a Gestore di eccezioni vettoriali.VC++ implementa le eccezioni C++ sopra le eccezioni SEH e viene fornito un gestore di eccezioni vettoriali prima di qualsiasi gestore basato su frame.Tuttavia, fai molta attenzione, i problemi introdotti dalla gestione delle eccezioni vettoriali possono essere difficili da diagnosticare.

Anche Mike Stall ha alcuni avvertimenti sull'utilizzo in un'app con codice gestito.Infine, leggi L'articolo di Matt Pietrek e assicurati di comprendere SEH e la gestione delle eccezioni vettoriali prima di provare questo.(Niente è così brutto come rintracciare un problema critico nel codice che hai aggiunto per aiutare a rintracciare i problemi critici.)

Credo che MSDev ti permetta di impostare punti di interruzione quando viene lanciata un'eccezione.

In alternativa, inserisci il punto di interruzione nel costruttore del tuo oggetto eccezione.

Se stai eseguendo il debug dall'IDE, vai su Debug->Eccezioni, fai clic su Generato per eccezioni C++.

Altre lingue?Bene, in Java chiami e.printStackTrace();Non potrebbe essere più semplice di così.

Nel caso qualcuno fosse interessato, un collega mi ha risposto a questa domanda via email:

Artem ha scritto:

C'è un flag su MiniDumpWriteDump() che può eseguire migliori crash dump che consentiranno di vedere lo stato completo del programma, con tutte le variabili globali, ecc.Per quanto riguarda gli stack di chiamate, dubito che possano essere migliori a causa delle ottimizzazioni...a meno che non disattivi (forse alcune) le ottimizzazioni.

Inoltre, penso che disabilitare le funzioni in linea e l'ottimizzazione dell'intero programma sarà di grande aiuto.

In effetti, ci sono molti tipi di dump, forse potresti sceglierne uno abbastanza piccolo ma che abbia comunque più informazionihttp://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

Tuttavia, questi tipi non aiutano con lo stack di chiamate, influenzano solo la quantità di variabili che sarai in grado di vedere.

Ho notato che alcuni di questi tipi di dump non sono supportati nella versione 5.1 dbghelp.dll che utilizziamo.Tuttavia, potremmo aggiornarlo alla versione più recente, 6.9, ho appena controllato l'EULA per gli strumenti di debug di MS: è ancora possibile ridistribuire il file dbghelp.dll più recente.

Utilizzo le mie eccezioni.Puoi gestirli in modo abbastanza semplice: contengono anche testo.Io utilizzo il formato:

throw Exception( "comms::serial::serial( )", "Something failed!" );

Inoltre ho un secondo formato di eccezione:

throw Exception( "comms::serial::serial( )", ::GetLastError( ) );

Che viene quindi convertito da un valore DWORD al messaggio effettivo utilizzando FormatMessage.Utilizzando il formato dove/cosa ti mostrerà cosa è successo e in quale funzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top