Posts Tagged ‘vc++ seh minidump windows’

Depurando com minidumps (Windows)

dezembro 8, 2008

“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.