Durante a execução de um software, encontrar uma falha inesperada como um encerramento abrupto (crash) pode ser problemático. Em um cenário real, uma aplicação pode finalizar durante a noite, e os logs do Windows podem apenas registrar um código genérico como 0xC0000005, tornando impossível identificar a função específica onde a exceção ocorreu. A solução utiliza o Visual Studio, combinando um arquivo DMP (minidump) gerado no momento da falha com um arquivo PDB (símbolos de depuração) para localizar com precisão a origem do erro.
O código a seguir demonstra como implementar um filtro para exceções não tratadas. Quando um crash ocorre, este código gera um arquivo .dmp no subdiretório 'ErrorDumps' localizado junto ao executável. Para que a análise posterior funcione, é necessário compilar o projeto nos modos 'Debug' ou 'RelWithDebInfo', os quais produzem os arquivos .pdb.
#include <windows.h>
#include <dbghelp.h>
#include <shlwapi.h>
#include <ctime>
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "shlwapi.lib")
// Manipulador global para exceções críticas
LONG __stdcall ProcessUnhandledException(EXCEPTION_POINTERS* pExceptInfo)
{
WCHAR exeFullPath[MAX_PATH] = {0};
WCHAR dumpDirectory[MAX_PATH] = {0};
WCHAR dumpFilePath[MAX_PATH] = {0};
// Obtém o caminho completo do executável atual
if (!GetModuleFileNameW(NULL, exeFullPath, MAX_PATH))
return EXCEPTION_CONTINUE_SEARCH;
// Extrai apenas o diretório do caminho
PathRemoveFileSpecW(exeFullPath);
// Constrói o caminho para a pasta de dumps
wsprintfW(dumpDirectory, L"%s\\ErrorDumps", exeFullPath);
CreateDirectoryW(dumpDirectory, NULL);
// Gera um nome de arquivo único baseado no timestamp atual
time_t now = time(NULL);
struct tm* timeInfo = localtime(&now);
wsprintfW(dumpFilePath, L"%s\\Crash_%04d%02d%02d_%02d%02d%02d.dmp",
dumpDirectory,
timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
// Cria o arquivo DMP
HANDLE hFile = CreateFileW(dumpFilePath, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION exceptInfo;
exceptInfo.ThreadId = GetCurrentThreadId();
exceptInfo.ExceptionPointers = pExceptInfo;
exceptInfo.ClientPointers = FALSE;
// Gera o dump. MiniDumpWithDataSegs é uma alternativa mais leve que FullMemory
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
MiniDumpWithDataSegs, &exceptInfo, NULL, NULL);
CloseHandle(hFile);
}
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
// Registra o nosso manipulador de exceções
SetUnhandledExceptionFilter(ProcessUnhandledException);
// Simulação de um acesso inválido à memória para provocar o crash
int* pNullPointer = nullptr;
*pNullPointer = 10;
return 0;
}
Para projetos que utilizam CMake e necessitam gerar símbolos de depuração no modo Release (essencial para analisar os dumps de um build de produção), as seguintes configurações devem ser adicionadas:
if(MSVC)
# Ativa a geração de informações de depuração (/Zi) nas compilações Release
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
# Configura o linker para gerar o arquivo PDB e otimizar
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
endif()
Após obter o arquivo .dmp gerado durante uma falha real, abra-o diretamente no Visual Studio. O ambiente oferecerá a opção de "Depurar em tempo real" (Just-In-Time Debugging). Se o arquivo .pdb correspondente à mesma build estiver acessível (no mesmo diretório ou em um símbolo configurado), o Visual Studio carregará o estado da aplicação no momento exato da exceção, permitindo navegar pela pilha de chamadas e inspecionar as variáveis locais para identificar a causa raiz do problema.