mirror of
https://github.com/JochenKalmbach/StackWalker
synced 2026-06-08 11:24:59 +00:00
268 lines
7.0 KiB
C++
268 lines
7.0 KiB
C++
/**********************************************************************
|
|
*
|
|
* main.cpp
|
|
*
|
|
*
|
|
* History:
|
|
* 2008-11-27 v1 - Header added
|
|
* Samples for Exception-Crashes added...
|
|
* 2009-11-01 v2 - Moved to stackwalker.codeplex.com
|
|
*
|
|
**********************************************************************/
|
|
|
|
#include "stackwalker.h"
|
|
#include <stdio.h>
|
|
#include <tchar.h>
|
|
|
|
#define UNHANDLED_EXCEPTION_TEST
|
|
#define EXCEPTION_FILTER_TEST
|
|
|
|
// secure-CRT_functions are only available starting with VC8
|
|
#if _MSC_VER < 1400
|
|
#define _tcscpy_s _tcscpy
|
|
#define _tcscat_s _tcscat
|
|
#define _stprintf_s _stprintf
|
|
#define strcpy_s(a, b, c) strcpy(a, c)
|
|
#endif
|
|
|
|
int iGlobalInt = 0;
|
|
void (*pGlobalFuncPtr)() = 0;
|
|
|
|
// Specialized stackwalker-output classes
|
|
// Console (printf):
|
|
class StackWalkerToConsole : public StackWalker
|
|
{
|
|
protected:
|
|
virtual void OnOutput(LPCSTR szText) { printf("%s", szText); }
|
|
};
|
|
|
|
void Func5()
|
|
{
|
|
StackWalkerToConsole sw;
|
|
sw.ShowCallstack();
|
|
}
|
|
void Func4()
|
|
{
|
|
Func5();
|
|
}
|
|
void Func3()
|
|
{
|
|
Func4();
|
|
}
|
|
void Func2()
|
|
{
|
|
Func3();
|
|
}
|
|
void Func1()
|
|
{
|
|
Func2();
|
|
}
|
|
|
|
void StackWalkTest()
|
|
{
|
|
Func1();
|
|
}
|
|
|
|
void GlobalIntTest()
|
|
{
|
|
StackWalkerToConsole sw;
|
|
sw.ShowObject(&iGlobalInt);
|
|
}
|
|
|
|
void GlobalFunctionPointerTest()
|
|
{
|
|
StackWalkerToConsole sw;
|
|
sw.ShowObject(&pGlobalFuncPtr);
|
|
}
|
|
|
|
#ifdef UNHANDLED_EXCEPTION_TEST
|
|
|
|
// For more info about "PreventSetUnhandledExceptionFilter" see:
|
|
// "SetUnhandledExceptionFilter" and VC8
|
|
// http://blog.kalmbachnet.de/?postid=75
|
|
// and
|
|
// Unhandled exceptions in VC8 and above… for x86 and x64
|
|
// http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/
|
|
// Even better: http://blog.kalmbach-software.de/2013/05/23/improvedpreventsetunhandledexceptionfilter/
|
|
|
|
#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
|
|
static BOOL PreventSetUnhandledExceptionFilter()
|
|
{
|
|
HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
|
|
if (hKernel32 == NULL)
|
|
return FALSE;
|
|
void* pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
|
|
if (pOrgEntry == NULL)
|
|
return FALSE;
|
|
|
|
#ifdef _M_IX86
|
|
// Code for x86:
|
|
// 33 C0 xor eax,eax
|
|
// C2 04 00 ret 4
|
|
unsigned char szExecute[] = {0x33, 0xC0, 0xC2, 0x04, 0x00};
|
|
#elif _M_X64
|
|
// 33 C0 xor eax,eax
|
|
// C3 ret
|
|
unsigned char szExecute[] = {0x33, 0xC0, 0xC3};
|
|
#elif _M_ARM64
|
|
unsigned char szExecute[] = {
|
|
0x00, 0x00, 0x80, 0xD2, // mov x0, #0
|
|
0xC0, 0x03, 0x5F, 0xD6 // ret
|
|
};
|
|
#else
|
|
#error "The following code only works for x86 and x64!"
|
|
#endif
|
|
|
|
DWORD dwOldProtect = 0;
|
|
BOOL bProt = VirtualProtect(pOrgEntry, sizeof(szExecute), PAGE_EXECUTE_READWRITE, &dwOldProtect);
|
|
|
|
SIZE_T bytesWritten = 0;
|
|
BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, szExecute, sizeof(szExecute),
|
|
&bytesWritten);
|
|
|
|
if ((bProt != FALSE) && (dwOldProtect != PAGE_EXECUTE_READWRITE))
|
|
{
|
|
DWORD dwBuf;
|
|
VirtualProtect(pOrgEntry, sizeof(szExecute), dwOldProtect, &dwBuf);
|
|
}
|
|
return bRet;
|
|
}
|
|
#else
|
|
#pragma message("This code works only for x86, x64 and arm64!")
|
|
#endif
|
|
|
|
static TCHAR s_szExceptionLogFileName[_MAX_PATH] = _T("\\exceptions.log"); // default
|
|
static BOOL s_bUnhandledExeptionFilterSet = FALSE;
|
|
static LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
|
|
{
|
|
#ifdef _M_IX86
|
|
if (pExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
|
|
{
|
|
static char MyStack[1024 * 128]; // be sure that we have enough space...
|
|
// it assumes that DS and SS are the same!!! (this is the case for Win32)
|
|
// change the stack only if the selectors are the same (this is the case for Win32)
|
|
//__asm push offset MyStack[1024*128];
|
|
//__asm pop esp;
|
|
__asm mov eax, offset MyStack[1024 * 128];
|
|
__asm mov esp, eax;
|
|
}
|
|
#endif
|
|
|
|
StackWalkerToConsole sw; // output to console
|
|
sw.ShowCallstack(GetCurrentThread(), pExPtrs->ContextRecord);
|
|
TCHAR lString[500];
|
|
_stprintf_s(lString,
|
|
_T("*** Unhandled Exception! See console output for more infos!\n")
|
|
_T(" ExpCode: 0x%8.8X\n")
|
|
_T(" ExpFlags: %d\n")
|
|
#if _MSC_VER >= 1900
|
|
_T(" ExpAddress: 0x%8.8p\n")
|
|
#else
|
|
_T(" ExpAddress: 0x%8.8X\n")
|
|
#endif
|
|
_T(" Please report!"),
|
|
pExPtrs->ExceptionRecord->ExceptionCode, pExPtrs->ExceptionRecord->ExceptionFlags,
|
|
pExPtrs->ExceptionRecord->ExceptionAddress);
|
|
FatalAppExit(-1, lString);
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
static void InitUnhandledExceptionFilter()
|
|
{
|
|
TCHAR szModName[_MAX_PATH];
|
|
if (GetModuleFileName(NULL, szModName, sizeof(szModName) / sizeof(TCHAR)) != 0)
|
|
{
|
|
_tcscpy_s(s_szExceptionLogFileName, szModName);
|
|
_tcscat_s(s_szExceptionLogFileName, _T(".exp.log"));
|
|
}
|
|
if (s_bUnhandledExeptionFilterSet == FALSE)
|
|
{
|
|
// set global exception handler (for handling all unhandled exceptions)
|
|
SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
|
|
#if defined _M_X64 || defined _M_IX86 || defined _M_ARM64
|
|
PreventSetUnhandledExceptionFilter();
|
|
#endif
|
|
s_bUnhandledExeptionFilterSet = TRUE;
|
|
}
|
|
}
|
|
#endif // UNHANDLED_EXCEPTION_TEST
|
|
|
|
#ifdef EXCEPTION_FILTER_TEST
|
|
LONG WINAPI ExpFilter(EXCEPTION_POINTERS* pExp, DWORD dwExpCode)
|
|
{
|
|
//StackWalker sw; // output to default (Debug-Window)
|
|
StackWalkerToConsole sw; // output to the console
|
|
sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord);
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
void ExpTest5()
|
|
{
|
|
char* p = NULL;
|
|
p[0] = 0;
|
|
printf(p);
|
|
}
|
|
void ExpTest4()
|
|
{
|
|
ExpTest5();
|
|
}
|
|
void ExpTest3()
|
|
{
|
|
ExpTest4();
|
|
}
|
|
void ExpTest2()
|
|
{
|
|
ExpTest3();
|
|
}
|
|
void ExpTest1()
|
|
{
|
|
ExpTest2();
|
|
}
|
|
void TestExceptionWalking()
|
|
{
|
|
__try
|
|
{
|
|
ExpTest1();
|
|
}
|
|
__except (ExpFilter(GetExceptionInformation(), GetExceptionCode()))
|
|
{
|
|
printf("\n\nException-Handler called\n\n\n");
|
|
}
|
|
}
|
|
|
|
int f(int i)
|
|
{
|
|
if (i < 0)
|
|
return i;
|
|
return f(i + 1);
|
|
}
|
|
#endif // EXCEPTION_FILTER_TEST
|
|
|
|
int _tmain(int argc, _TCHAR* argv[])
|
|
{
|
|
printf("\n\n\nShow an object:\n\n\n");
|
|
GlobalIntTest();
|
|
|
|
printf("\n\n\nShow a function pointer:\n\n\n");
|
|
GlobalFunctionPointerTest();
|
|
|
|
printf("\n\n\nShow a simple callstack of the current thread:\n\n\n");
|
|
StackWalkTest();
|
|
|
|
#ifdef EXCEPTION_FILTER_TEST
|
|
printf("\n\n\nShow a the callstack from inside an exception-handler:\n\n\n");
|
|
TestExceptionWalking();
|
|
#endif
|
|
|
|
#ifdef UNHANDLED_EXCEPTION_TEST
|
|
printf("\n\n\nCatch unhandled exceptions and show the callstack:\n\n\n");
|
|
// This will only work, if the program is *not* started under a debugger
|
|
// If the program is running under a debugger, the debugger will catch this exception ;)
|
|
InitUnhandledExceptionFilter();
|
|
//f(0); // endless recursion
|
|
char* szTemp = (char*)1;
|
|
strcpy_s(szTemp, 1000, "A");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|