Pergunta

Atualmente, estou trabalhando para adicionar exceções e manipulação de exceções ao meu aplicativo OSS. Exceções têm sido a idéia geral desde o início, mas eu queria encontrar uma boa estrutura de exceção e, com toda a honestidade, entender as convenções e idiomas de manuseio de exceções C ++ um pouco melhor antes de começar a usá -las. Tenho muita experiência com C#/. Net, Python e outros idiomas que usam exceções. Não sou estranho à ideia (mas longe de um mestre).

Em C# e Python, quando ocorre uma exceção não atendida, o usuário recebe um bom rastreamento de pilha e, em geral muito útil Informações de depuração inestimável. Se você está trabalhando em um aplicativo OSS, fazer com que os usuários coloque essas informações nos relatórios de problemas seja ... bem, digamos que estou achando difícil viver sem isso. Para este projeto C ++, eu recebo "o aplicativo travado" ou de usuários mais informados ", fiz X, Y e Z e depois travou". Mas eu quero essa informação de depuração também!

Eu já (e com grande dificuldade) fiz as pazes com o fato de nunca ver uma maneira de plataforma cruzada e cruzada de obter um rastreamento de pilha de exceção do C ++, mas sei que posso obter o nome da função e outro informação relevante.

E agora quero isso para minhas exceções não tratadas. estou a usar Boost :: Exception, e eles têm isso muito bom diagnóstico_information Thingamajig que pode imprimir o nome, arquivo, linha, linha e, mais importante, outras informações específicas de exceção que o programador adicionou a essa exceção.

Naturalmente, lidarei com exceções dentro do código sempre que puder, mas não sou tão ingênuo ao pensar que não vou deixar um casal escapar (sem querer, é claro).

Então, o que eu quero fazer é embrulhar meu ponto de entrada principal dentro de um try Bloqueie com um catch Isso cria uma caixa de diálogo especial que informa ao usuário que ocorreu um erro no aplicativo, com informações mais detalhadas apresentadas quando o usuário clica em "mais" ou "informações de depuração" ou qualquer outra coisa. Isso conteria a string do diagnóstico_information. Eu poderia instruir os usuários a colar essas informações em relatórios de problemas.

Mas uma sensação incômoda está me dizendo que embrulhar tudo em um bloco de tentativas é uma ideia muito ruim. É o que estou prestes a fazer estúpido? Se é (e mesmo que não seja), qual é a melhor maneira de alcançar o que eu quero?

Foi útil?

Solução

Embrulhar todo o seu código em um try/catch O bloco é A-OK. Não desacelerará a execução de nada dentro dela, por exemplo. De fato, todos os meus programas têm (código semelhante a) esta estrutura:

int execute(int pArgc, char *pArgv[])
{
    // do stuff
}

int main(int pArgc, char *pArgv[])
{
    // maybe setup some debug stuff,
    // like splitting cerr to log.txt

    try
    {
        return execute(pArgc, pArgv);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Unhandled exception:\n" << e.what() << std::endl;
        // or other methods of displaying an error

        return EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Unknown exception!" << std::endl;

        return EXIT_FAILURE;
    }
}

Outras dicas

Colocar um bloco de tentativa/captura em Main () está bem, não causa problemas. O programa está morto em uma exceção não tratada de qualquer maneira. Não será útil em sua busca para obter o importante rastreamento da pilha. Essa informação é gonzo quando o bloco de captura prende a exceção.

Pegar uma exceção C ++ também não será muito útil. As chances de que o programa morra em uma exceção derivada de STD :: Exception são bastante finas. Embora isso possa acontecer. Muito mais provável em um aplicativo C/C ++ é a morte devido a exceções de hardware, sendo o AccessViolation numero uno. A captura desses requer o __Try e __except palavras -chave no seu método principal (). Novamente, muito pouco contexto está disponível, você basicamente tem um código de exceção. Um AV também informa qual localização exata da memória causou a exceção.

Este não é apenas um problema de plataforma cruzada, mas você não pode obter um bom rastreamento de pilha em nenhuma plataforma. Não existe uma maneira confiável de andar na pilha, há muitas otimizações (como a omissão do Fremepointer) que fazem disso uma jornada perigosa. É a maneira C/C ++: torne -o o mais rápido possível, não deixe a pista do que aconteceu quando explodir.

O que você precisa fazer é depurar esse tipo de problema da maneira C/C ++. Você precisa criar um minidump. É aproximadamente análogo ao "dump do núcleo" do antigo, um instantâneo da imagem do processo no momento em que a exceção acontece. Naquela época, você realmente tinha um despejo completo do núcleo. Atualmente, houve progresso, é "mini", um pouco necessário porque um dump completo do núcleo levaria quase 2 gigabytes. Na verdade, funciona muito bem para diagnosticar o estado do programa.

No Windows, isso começa chamando o setUnHandledExceptionFilter (), você fornece um ponteiro de função de retorno de chamada para uma função que será executada quando seu programa morrer em uma exceção não atendida. Qualquer exceção, C ++ e SEH. Seu próximo recurso é o dbghelp.dll, disponível nas ferramentas de depuração para download do Windows. Possui um ponto de entrada chamado MinidumpWritedump (), cria um minidump.

Depois de criar o arquivo criado pelo MinidumpWriteDump (), você é muito dourado. Você pode carregar o arquivo .dmp no Visual Studio, quase como se fosse um projeto. Pressione F5 e vs Retra por um tempo tentando carregar arquivos .pdb para as DLLs carregadas no processo. Você vai querer configurar o servidor de símbolos, isso é muito importante obter bons traços de pilha. Se tudo funcionar, você receberá um "intervalo de depuração" no local exato em que a exceção foi lançada ". Com um rastreamento de pilha.

Coisas que você precisa fazer para fazer isso funcionar bem:

  • Use um servidor de construção para criar os binários. Ele precisa empurrar os símbolos de depuração (arquivos .pdb) para um servidor de símbolos para que eles estejam prontamente disponíveis quando você depurar o Minidump.
  • Configure o depurador para que ele possa encontrar os símbolos de depuração para todos os módulos. Você pode obter os símbolos de depuração do Windows da Microsoft, os símbolos do seu código precisam vir do servidor de símbolos mencionado acima.
  • Escreva o código para prender a exceção não tratada e criar o Minidump. Mencionei o setUnHandledExceptionFilter (), mas o código que cria o Minidump não deve estar no programa que travou. As chances de que ele possa escrever o Minidump com sucesso são bastante pequenas, o estado do programa é indeterminado. A melhor coisa a fazer é executar um processo de "guarda" que fica de olho em um mutex nomeado. Seu filtro de exceção pode definir o mutex, o guarda pode criar o Minidump.
  • Crie uma maneira de o Minidump ser transferido da máquina do cliente para a sua. Usamos o serviço S3 da Amazon para isso, terabytes a uma taxa razoável.
  • Faça o manipulador do Minidump no seu banco de dados de depuração. Usamos o JIRA, ele possui um serviço na Web que nos permite verificar o balde de falha em um banco de dados de falhas anteriores com a mesma "assinatura". Quando é exclusivo ou não tem acertos suficientes, solicitamos ao código do gerenciador de falhas para fazer o upload do Minidump para a Amazon e criar a entrada do banco de dados de bug.

Bem, foi para isso que fiz pela empresa em que trabalho. Funcionou muito bem, reduziu a frequência do balde de colisão de milhares para dezenas. Mensagem pessoal para os criadores do componente FFDSHOW de código aberto: eu te odeio com uma paixão. Mas você não está mais travando nosso aplicativo! Buggers.

Não, não é estúpido. É uma ideia muito boa e não custa praticamente nada em tempo de execução até que você atinja uma exceção não tratada, é claro.

Esteja ciente de que já existe um manipulador de exceção envolvendo seu tópico, fornecido pelo sistema operacional (e outro pelo C-Runtime, eu acho). Pode ser necessário transmitir certas exceções a esses manipuladores para obter o comportamento correto. Em algumas arquiteturas, o acesso a dados mal alinhados é tratado por um manipulador de exceção. Então você pode querer um caso especial EXCEPTION_DATATYPE_MISALIGNMENT e deixe passar para o manipulador de exceção de nível superior.

Incluo os registros, a versão do aplicativo e o número de construção, o tipo de exceção e um despejo de pilha em hexadecimal com nomes e compensações de módulos para valores hexadecimais que podem ser endereços para o código. Certifique -se de incluir o número da versão e o número/data de criação do seu exe.

Você também pode usar VirtualQuery Para transformar valores de pilha em "ModuleName+Offset" com bastante facilidade. E isso, combinado com um arquivo .map, geralmente lhe dirá exatamente onde você travou.

Descobri que poderia treinar testadores beta para enviar o texto com bastante facilidade, mas nos primeiros dias o que recebi era uma foto da caixa de diálogo de erro e não do texto. Eu acho que é porque muitos usuários não sabem que você pode clicar com o botão direito do mouse em qualquer controle de edição para obter um menu com "Selecionar tudo" e "copiar". Se eu fosse fazer isso de novo, adicionaria um botão que copiava esse texto à área de transferência para que ele possa ser facilmente colado em um email.

Melhor ainda se você quiser se dar ao trabalho de ter um botão 'Enviar Relatório de Erro', mas apenas dar aos usuários uma maneira de colocar o texto em seus próprios e -mails, você o leva a maior parte do caminho, e não levanta nenhuma bandeira vermelha sobre "Que informações estou compartilhando com eles?"

De fato, o Boost :: Diagnostic_information foi projetado especificamente para ser usado em um bloco "global" (...), para exibir informações sobre exceções que não deveriam ter chegado a ele. No entanto, observe que a string retornada por Boost :: Diagnostic_information não é fácil de usar.

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