Pregunta

Estoy buscando una respuesta en MS VC++.

Al depurar una aplicación C++ de gran tamaño, que desafortunadamente tiene un uso muy extenso de excepciones de C++.A veces detecto una excepción un poco más tarde de lo que realmente quiero.

Ejemplo en pseudocódigo:

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

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

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

Puedo detectar la excepción con un punto de interrupción al depurar.Pero no puedo rastrear si la excepción ocurrió en FunctionA() o FunctionB(), o alguna otra función.(Suponiendo un uso extensivo de excepciones y una versión enorme del ejemplo anterior).

Una solución a mi problema es determinar y guardar la pila de llamadas. en el constructor de excepciones (es decir.antes de ser capturado).Pero esto requeriría que derivara todas las excepciones de esta clase de excepción base.También requeriría mucho código y quizás ralentizaría mi programa.

¿Existe una forma más sencilla que requiera menos trabajo?¿Sin tener que cambiar mi gran base de código?

¿Existen mejores soluciones a este problema en otros idiomas?

¿Fue útil?

Solución

Si solo está interesado en saber de dónde vino la excepción, puede escribir una macro simple como

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

que agregará el nombre del archivo, el número de línea y el nombre de la función al texto de excepción (si el compilador proporciona las macros respectivas).

Luego lanza excepciones usando

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

Otros consejos

Señalaste un punto de interrupción en el código.Dado que está en el depurador, puede establecer un punto de interrupción en el constructor de la clase de excepción o configurar el depurador de Visual Studio para que interrumpa todas las excepciones lanzadas (Depurar->Excepciones, haga clic en excepciones de C++, seleccione las opciones lanzadas y no detectadas).

Hay un libro excelente escrito por John Robbins que aborda muchas cuestiones difíciles de depuración.El libro se llama Aplicaciones de depuración para Microsoft .NET y Microsoft Windows.A pesar del título, el libro contiene una gran cantidad de información sobre la depuración de aplicaciones nativas de C++.

En este libro, hay una sección extensa que trata sobre cómo obtener la pila de llamadas para las excepciones que se generan.Si no recuerdo mal, algunos de sus consejos implican utilizar manejo estructurado de excepciones (SEH) en lugar de (o además de) excepciones de C++.Realmente no puedo recomendar el libro lo suficiente.

Coloque un punto de interrupción en el constructor del objeto de excepción.Obtendrá su punto de interrupción antes de que se lance la excepción.

No hay forma de averiguar el origen de una excepción. después se captura, a menos que incluya esa información cuando se lanza.Cuando detecta la excepción, la pila ya está desenrollada y no hay forma de reconstruir el estado anterior de la pila.

Su sugerencia de incluir el seguimiento de la pila en el constructor es su mejor opción.Sí, cuesta tiempo durante la construcción, pero probablemente no deberías lanzar excepciones con tanta frecuencia como para que esto sea una preocupación.Hacer que todas sus excepciones hereden de una nueva base también puede ser más de lo que necesita.Simplemente podría heredar las excepciones relevantes (gracias, herencia múltiple) y tener una captura separada para ellas.

Puedes usar el pilaTrace64 función para construir el seguimiento (creo que también hay otras formas).Verificar Este artículo por ejemplo código.

Así es como lo hago en C++ usando bibliotecas 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;
}

El constructor de su excepción puede simplemente llamar a esta función y almacenar el seguimiento de la pila.Toma el parametro numskip porque me gusta separar el constructor de la excepción de los seguimientos de mi pila.

No existe una forma estándar de hacer esto.

Además, la pila de llamadas normalmente debe registrarse en el momento en que se genera la excepción. arrojado;una vez que ha sido atrapó la pila se ha desenrollado, por lo que ya no sabes qué estaba pasando en el momento de ser arrojada.

En VC++ en Win32/Win64, usted podría obtenga resultados suficientemente utilizables registrando el valor del compilador intrínseco _ReturnAddress() y asegurándose de que el constructor de su clase de excepción sea __declspec(noinline).Junto con la biblioteca de símbolos de depuración, creo que probablemente podría obtener el nombre de la función (y el número de línea, si su .pdb lo contiene) que corresponde a la dirección del remitente usando SymGetLineFromAddr64.

En código nativo, puede tener la oportunidad de recorrer la pila de llamadas instalando un Manejador de excepciones vectorizado.VC++ implementa excepciones de C++ además de las excepciones SEH y se le da la primera oportunidad a un controlador de excepciones vectorizado antes que a cualquier controlador basado en marcos.Sin embargo, tenga mucho cuidado, los problemas introducidos por el manejo de excepciones vectoriales pueden ser difíciles de diagnosticar.

También Mike Stall tiene algunas advertencias sobre su uso en una aplicación que tiene código administrado.Finalmente, lee Artículo de Matt Pietrek y asegúrese de comprender SEH y el manejo de excepciones vectorizadas antes de intentar esto.(Nada se siente tan mal como rastrear un problema crítico al código que agregó para ayudar a rastrear problemas críticos).

Creo que MSDev le permite establecer puntos de interrupción cuando se produce una excepción.

Alternativamente, coloque el punto de interrupción en el constructor de su objeto de excepción.

Si está depurando desde el IDE, vaya a Depurar->Excepciones, haga clic en Lanzado para excepciones de C++.

¿Otros idiomas?Bueno, en Java llamas a e.printStackTrace();No hay nada más sencillo que eso.

Por si alguien está interesado, un compañero de trabajo me respondió esta pregunta por correo electrónico:

Artem escribió:

Hay un indicador para MiniDumpWriteDump() que puede realizar mejores volcados de memoria que permitirán ver el estado completo del programa, con todas las variables globales, etc.En cuanto a las pilas de llamadas, dudo que puedan ser mejores debido a las optimizaciones...a menos que desactives (tal vez algunas) optimizaciones.

Además, creo que deshabilitar las funciones en línea y la optimización completa del programa será de gran ayuda.

De hecho, hay muchos tipos de volcados, tal vez puedas elegir uno lo suficientemente pequeño pero aún así tengas más información.http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

Sin embargo, esos tipos no ayudarán con la pila de llamadas, solo afectan la cantidad de variables que podrá ver.

Noté que algunos de esos tipos de volcado no son compatibles con la versión 5.1 de dbghelp.dll que usamos.Sin embargo, podríamos actualizarlo a la versión 6.9 más reciente. Acabo de verificar el EULA para las herramientas de depuración de MS: el dbghelp.dll más nuevo todavía se puede redistribuir.

Utilizo mis propias excepciones.Puede manejarlos de forma bastante sencilla: además, contienen texto.Yo uso el formato:

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

También tengo un segundo formato de excepción:

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

Que luego se convierte de un valor DWORD al mensaje real usando FormatMessage.El uso del formato dónde/qué le mostrará qué sucedió y en qué función.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top