Depurando com minidumps (Windows)

by

“Structured Exceptions” (SE) é o nome que se dá para as condições anormais que, normalmente, encerram um processo dentro do Windows. Muito equivalente ao signal do POSIX.

Semana passada, aprendi uma técnica muito interessante para tratamento da SE em aplicações windows com o Visual C++ Express e também pode ser feito com o windbg. A técnica consiste em implementar um trecho de código que gere um minidump no momento em que for detectada uma exceção deste tipo. A partir daí, é uma escolha da aplicação continuar rodando ou não, uma vez que esse tipo de exceção nunca vem sozinho e sempre deixa alguma seqüela.

Criei um projeto simples de aplicação console que executa este snippet:

// início seh.cpp
#include "iostream"

/*
	Por simplicidade, vamos fazer uma versão de new() que sempre falha e retorna nulo;
	Ignorando a especificação de std::nothrow.
 */
void * operator new[](size_t size)
{
	return 0;
}

int main()
{
	const size_t tamString = 10;
	char * s = new char[tamString]; // Ops! não vi que um new[] falhou...
	strcpy(s, "teste");
	std::cout << s << std::endl;

	return 0;
}
// fim seh.cpp

O operador new[] foi sobrecarregado para gerar o efeito desejado.

Na hora de executar o programa, você ou o usuário pode encontrar (neste caso, encontrará) uma janela como esta:

Tipica tela de SE

Tipica tela de SE

Numa primeira etapa, podemos melhorar o programa para que ele termine tranqüilamente (feche o que tiver que fechar etc). O resultado desta mudança, por exemplo, poderia ser como este:

// início seh.cpp (v1.1)
#include <iostream>
#include <iomanip>

//v1.1 -- cabeçalhos para o tratamento de exceção
#include <sstream>
#include <string>

#include <windows.h>
#include <eh.h>

/*
    Por simplicidade, vamos fazer uma versão de new() que sempre falha e retorna nulo;
    Ignorando a especificação de std::nothrow.
 */
void * operator new[](size_t size)
{
    return 0;
}

//v1.1 Cria uma classe simples de exceção
class StructuredException: public std::exception
{
public:
    StructuredException(PEXCEPTION_POINTERS ptr)
    {
        std::ostringstream out;
        out << "Exceção 0x" << std::hex << ptr->ExceptionRecord->ExceptionCode <<
            std::dec << ": " << "O endereço " << ptr->ExceptionRecord->ExceptionAddress <<
            " tentou " << (ptr->ExceptionRecord->ExceptionInformation[0] ? "GRAVAR" : "LER") <<
            " o endereço " <<
            reinterpret_cast<void*>(ptr->ExceptionRecord->ExceptionInformation[1])
            << std::endl;
        message_ = out.str();
    }

    const char * what() const
    {
        return message_.c_str();
    }

private:
    std::string message_;
};

// v1.1 -- handler para tratamento de exceções, diferente daquela tela.
void MySETranslator(unsigned int code, PEXCEPTION_POINTERS ptr)
{
    std::cout << "Deu merda." << std::endl;
    throw StructuredException(ptr);
}

int main()
{
    // v1.1 -- prepara o ambiente para um melhor tratamento de exceções.
    _set_se_translator(MySETranslator);

    try
    {
        const size_t tamString = 10;
        char * s = new char[tamString]; // Ops! não vi que um new[] falhou...
        strcpy(s, "teste");
        std::cout << s << std::endl;
    } catch(std::exception & e)
    {
        std::cout << e.what() << std::endl;
    }
    std::cout << "Terminei meu programa." << std::endl;
    return 0;
}
// fim seh.cpp

E a execução deste programa seria um pouco mais amigável, mas ainda difícil de depurar (neste caso não, mas imagine isto em um projeto realmente grande).

Nosso primeiro programa, com algum tratamento

Nosso primeiro programa, com algum tratamento

Enfim, é extremamente difícil rastrear entre módulos apenas com esse endereço. Para se extrair informação útil, podemos utilizar a API de minidumps. Aqui está o exemplo atualizado:

// início seh.cpp (v1.2)
#include <iostream>
#include <iomanip>

//v1.1 -- cabeçalhos para o tratamento de exceção
#include <sstream>
#include <string>

#undef UNICODE // para simplificar
#include <windows.h>
#include <eh.h>

// v 1.2 -- cabeçalhos para geração de minidumps.
#include <dbghelp.h>

/*
    Por simplicidade, vamos fazer uma versão de new() que sempre falha e retorna nulo;
    Ignorando a especificação de std::nothrow.
 */
void * operator new[](size_t size)
{
    return 0;
}

//v1.1 Cria uma classe simples de exceção
class StructuredException: public std::exception
{
public:
    StructuredException(PEXCEPTION_POINTERS ptr)
    {
        std::ostringstream out;
        out << "Exceção 0x" << std::hex << ptr->ExceptionRecord->ExceptionCode <<
            std::dec << ": " << "O endereço " << ptr->ExceptionRecord->ExceptionAddress <<
            " tentou " << (ptr->ExceptionRecord->ExceptionInformation[0] ? "GRAVAR" : "LER") <<
            " o endereço " <<
            reinterpret_cast<void*>(ptr->ExceptionRecord->ExceptionInformation[1])
            << std::endl;
        message_ = out.str();
    }

    const char * what() const
    {
        return message_.c_str();
    }

private:
    std::string message_;
};

// v1.1 -- handler para tratamento de exceções, diferente daquela tela.
void MySETranslator(unsigned int code, PEXCEPTION_POINTERS ptr)
{
    std::cout << "Deu merda." << std::endl;
    // v1.2 -- Cria o minidump antes de lançar a exceção.
    HANDLE file = CreateFile("minidump.dmp", GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, 0, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exception = {0};
    exception.ThreadId = GetCurrentThreadId();
    exception.ExceptionPointers = ptr;
    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file,
        MiniDumpNormal, &exception, NULL, NULL);
    CloseHandle(file);
    // v1.2 fim

    throw StructuredException(ptr);
}

int main()
{
    // v1.1 -- prepara o ambiente para um melhor tratamento de exceções.
    _set_se_translator(MySETranslator);

    try
    {
        const size_t tamString = 10;
        char * s = new char[tamString]; // Ops! não vi que um new[] falhou...
        strcpy(s, "teste");
        std::cout << s << std::endl;
    } catch(std::exception & e)
    {
        std::cout << e.what() << std::endl;
    }
    std::cout << "Terminei meu programa." << std::endl;
    return 0;
}
// fim seh.cpp

O uso deste recurso também implica na inclusão da biblioteca “dbghelp.lib” nas opções do projeto. Vamos, agora, executar o projeto mais uma vez. A tela da execução ficou exatamente igual à anterior. Porém, agora, temos o arquivo de minidump no diretório do projeto:

Listagem de diretório com o arquivo de Minidump

Listagem de diretório com o arquivo de Minidump

Agora vem a parte mais fácil: como depurar nosso programa?? Dois cliques no arquivo .dmp e temos um novo projeto do visual studio:

Iniciando a depuração com o minidump

Iniciando a depuração com o minidump

Depuração do erro na execução.

Depuração do erro na execução.

Como podemos ver aqui, depois da geração do arquivo de minidump, ficou meio “automático” para encontrar o erro.

Tags:

2 Respostas to “Depurando com minidumps (Windows)”

  1. F.G.Testa Says:

    Ótimo artigo!
    Só comentaria que a chamada de _set_se_translator() deve ser feita para cada thread, conforme o msdn em http://msdn.microsoft.com/en-us/library/5z4bw5h5(VS.80).aspx : In a multithreaded environment, translator functions are maintained separately for each thread. Each new thread needs to install its own translator function. Thus, each thread is in charge of its own translation handling. _set_se_translator is specific to one thread; another DLL can install a different translation function.

  2. Andre Pantaliao Says:

    Paulinho!!!

    Obrigado pelo post.
    Bem detalhado, com código bem explicado e imagens.
    Fique à vontade para postar mais vezes!

    Até mais,

    André

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s


%d blogueiros gostam disto: