Pergunta

Estou procurando uma resposta no MS VC++.

Ao depurar um aplicativo C++ grande, que infelizmente tem um uso muito extenso de exceções C++.Às vezes, pego uma exceção um pouco mais tarde do que realmente desejo.

Exemplo em pseudocódigo:

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

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

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

Posso capturar a exceção com um ponto de interrupção durante a depuração.Mas não consigo rastrear se a exceção ocorreu em FunctionA() ou FunctionB(), ou alguma outra função.(Assumindo o uso extensivo de exceções e uma versão enorme do exemplo acima).

Uma solução para o meu problema é determinar e salvar a pilha de chamadas no construtor de exceção (ou seja,antes de ser capturado).Mas isso exigiria que eu derivasse todas as exceções dessa classe de exceção básica.Também exigiria muito código e talvez tornaria meu programa mais lento.

Existe uma maneira mais fácil que requer menos trabalho?Sem ter que alterar minha grande base de código?

Existem melhores soluções para este problema em outros idiomas?

Foi útil?

Solução

Se você está interessado apenas em saber de onde veio a exceção, você pode simplesmente escrever uma macro simples como

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

que adicionará o nome do arquivo, o número da linha e o nome da função ao texto da exceção (se o compilador fornecer as respectivas macros).

Em seguida, lance exceções usando

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

Outras dicas

Você apontou para um ponto de interrupção no código.Como você está no depurador, você pode definir um ponto de interrupção no construtor da classe de exceção ou definir o depurador do Visual Studio para interromper todas as exceções lançadas (Depurar-> Exceções Clique nas exceções C++, selecione as opções lançadas e não capturadas)

Há um excelente livro escrito por John Robbins que aborda muitas questões difíceis de depuração.O livro se chama Depurando aplicativos para Microsoft .NET e Microsoft Windows.Apesar do título, o livro contém uma série de informações sobre depuração de aplicativos C++ nativos.

Neste livro, há uma longa seção sobre como obter a pilha de chamadas para exceções lançadas.Se bem me lembro, alguns de seus conselhos envolvem o uso tratamento estruturado de exceções (SEH) em vez de (ou além de) exceções C++.Eu realmente não posso recomendar o livro o suficiente.

Coloque um ponto de interrupção no construtor do objeto de exceção.Você obterá seu ponto de interrupção antes que a exceção seja lançada.

Não há como descobrir a origem de uma exceção depois ele é capturado, a menos que você inclua essa informação quando for lançado.No momento em que você captura a exceção, a pilha já está desenrolada e não há como reconstruir o estado anterior da pilha.

Sua sugestão de incluir o rastreamento de pilha no construtor é sua melhor aposta.Sim, custa tempo durante a construção, mas você provavelmente não deveria lançar exceções com frequência suficiente para que isso seja uma preocupação.Fazer com que todas as suas exceções sejam herdadas de uma nova base também pode ser mais do que você precisa.Você poderia simplesmente herdar as exceções relevantes (obrigado, herança múltipla) e ter uma captura separada para elas.

Você pode usar o StackTrace64 função para construir o rastreamento (acredito que existam outras maneiras também).Confira Este artigo por exemplo código.

Veja como faço isso em 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;
}

O construtor da sua exceção pode simplesmente chamar essa função e armazenar o rastreamento de pilha.Leva o parâmetro numskip porque gosto de separar o construtor da exceção dos meus rastreamentos de pilha.

Não existe uma maneira padrão de fazer isso.

Além disso, a pilha de chamadas normalmente deve ser registrada no momento em que a exceção é jogado;uma vez que foi capturado a pilha se desenrolou, então você não sabe mais o que estava acontecendo no momento em que foi lançada.

No VC++ em Win32/Win64, você poder obtenha resultados utilizáveis ​​o suficiente registrando o valor do _ReturnAddress() intrínseco do compilador e garantindo que seu construtor de classe de exceção seja __declspec(noinline).Em conjunto com a biblioteca de símbolos de depuração, acho que você provavelmente poderia obter o nome da função (e o número da linha, se o seu .pdb o contiver) que corresponde ao endereço de retorno usando SymGetLineFromAddr64.

No código nativo, você pode tentar percorrer a pilha de chamadas instalando um Manipulador de exceção vetorial.VC++ implementa exceções C++ sobre exceções SEH e um manipulador de exceção vetorial é fornecido primeiro antes de qualquer manipulador baseado em quadro.No entanto, tenha muito cuidado, pois os problemas introduzidos pelo tratamento de exceções vetoriais podem ser difíceis de diagnosticar.

Também Mike Stall tem alguns avisos sobre como usá-lo em um aplicativo que possui código gerenciado.Finalmente, leia Artigo de Matt Pietrek e certifique-se de entender o SEH e o tratamento de exceções vetoriais antes de tentar isso.(Nada parece tão ruim quanto rastrear um problema crítico no código que você adicionou para ajudar a rastrear problemas críticos.)

Acredito que o MSDev permite definir pontos de interrupção quando uma exceção é lançada.

Alternativamente, coloque o ponto de interrupção no construtor do seu objeto de exceção.

Se você estiver depurando a partir do IDE, vá para Debug->Exceptions, clique em Thrown para exceções C++.

Outras línguas?Bem, em Java você chama e.printStackTrace();Não pode ser muito mais simples do que isso.

Caso alguém esteja interessado, um colega de trabalho me respondeu a esta pergunta por e-mail:

Artem escreveu:

Existe um sinalizador para MiniDumpWriteDump() que pode fazer melhores crash dumps que permitirão ver o estado completo do programa, com todas as variáveis ​​globais, etc.Quanto às pilhas de chamadas, duvido que possam ser melhores por causa das otimizações...a menos que você desative (talvez algumas) otimizações.

Além disso, acho que desabilitar funções embutidas e otimizar todo o programa ajudará bastante.

Na verdade, existem muitos tipos de despejo, talvez você possa escolher um pequeno o suficiente, mas que ainda tenha mais informaçõeshttp://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

Esses tipos não ajudam na pilha de chamadas, eles afetam apenas a quantidade de variáveis ​​que você poderá ver.

Percebi que alguns desses tipos de dump não são suportados no dbghelp.dll versão 5.1 que usamos.Poderíamos atualizá-lo para a versão 6.9 mais recente, acabei de verificar o EULA para ferramentas de depuração do MS - o dbghelp.dll mais recente ainda pode ser redistribuído.

Eu uso minhas próprias exceções.Você pode lidar com eles de forma bastante simples - eles também contêm texto.Eu uso o formato:

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

Também tenho um segundo formato de exceção:

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

Que é então convertido de um valor DWORD para a mensagem real usando FormatMessage.Usar o formato onde/o que mostrará o que aconteceu e em qual função.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top