//---------------------------------------------------------------------------- // // Simple example of how to use non-invasive self-attach to get // stack traces for assertion failures. // // Copyright (C) Microsoft Corporation, 2001. // //---------------------------------------------------------------------------- #include #include #include #include #include #include "out.hpp" #define DE_ASSERT(Expr) \ if (!(Expr)) AssertionFailed(__FILE__, __LINE__, #Expr); else 0 PSTR g_SymbolPath; ULONG g_Pid; BOOL g_Suspend; BOOL g_NoDebuggerCheck; IDebugClient* g_Client; IDebugControl* g_Control; IDebugSymbols* g_Symbols; void ReleaseInterfaces(void) { if (g_Symbols != NULL) { g_Symbols->Release(); } if (g_Control != NULL) { g_Control->Release(); } if (g_Client != NULL) { // // Request a simple end to any current session. // This may or may not do anything but it isn't // harmful to call it. // // We don't want to see any output from the shutdown. g_Client->SetOutputCallbacks(NULL); g_Client->EndSession(DEBUG_END_ACTIVE_DETACH); g_Client->Release(); } } void Exit(int Code, PCSTR Format, ...) { // Clean up any resources. ReleaseInterfaces(); // Output an error message if given. if (Format != NULL) { va_list Args; va_start(Args, Format); vfprintf(stderr, Format, Args); va_end(Args); } exit(Code); } void CreateInterfaces(void) { HRESULT Status; // Start things off by getting an initial interface from // the engine. This can be any engine interface but is // generally IDebugClient as the client interface is // where sessions are started. if ((Status = DebugCreate(__uuidof(IDebugClient), (void**)&g_Client)) != S_OK) { Exit(1, "DebugCreate failed, 0x%X\n", Status); } // Query for some other interfaces that we'll need. if ((Status = g_Client->QueryInterface(__uuidof(IDebugControl), (void**)&g_Control)) != S_OK || (Status = g_Client->QueryInterface(__uuidof(IDebugSymbols), (void**)&g_Symbols)) != S_OK) { Exit(1, "QueryInterface failed, 0x%X\n", Status); } } void SelfAttach(void) { HRESULT Status; // Don't set the output callbacks yet as we don't want // to see any of the initial debugger output. if (g_SymbolPath != NULL) { if ((Status = g_Symbols->SetSymbolPath(g_SymbolPath)) != S_OK) { Exit(1, "SetSymbolPath failed, 0x%X\n", Status); } } // Everything's set up so do the attach. if ((Status = g_Client-> AttachProcess(0, g_Pid, DEBUG_ATTACH_NONINVASIVE | (g_Suspend ? 0 : DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND))) != S_OK) { Exit(1, "AttachProcess failed, 0x%X\n", Status); } // Finish initialization by waiting for the attach event. // This should return quickly as a non-invasive attach // can complete immediately. if ((Status = g_Control->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) != S_OK) { Exit(1, "WaitForEvent failed, 0x%X\n", Status); } // Everything is now initialized and we can make any // queries we want. } void DumpStack(PCONTEXT Context) { HRESULT Status; int Count = 50; char CxrCommand[64]; printf("\nFirst %d frames of the call stack:\n", Count); // Install output callbacks so we get the output from the stack dump. if ((Status = g_Client->SetOutputCallbacks(&g_OutputCb)) != S_OK) { Exit(1, "SetOutputCallbacks failed, 0x%X\n", Status); } sprintf(CxrCommand, ".cxr 0x%p", Context); // Print the call stack for the given context. if ((Status = g_Control-> Execute(DEBUG_OUTCTL_IGNORE, CxrCommand, DEBUG_EXECUTE_NOT_LOGGED)) != S_OK) { Exit(1, "Execute failed, 0x%X\n", Status); } // If the code is optimized at all it is important to have // accurate symbols to get the correct stack. if ((Status = g_Control-> OutputStackTrace(DEBUG_OUTCTL_ALL_CLIENTS, NULL, Count, DEBUG_STACK_SOURCE_LINE | DEBUG_STACK_FRAME_ADDRESSES | DEBUG_STACK_COLUMN_NAMES | DEBUG_STACK_FRAME_NUMBERS)) != S_OK) { Exit(1, "OutputStackTrace failed, 0x%X\n", Status); } // Done with output. if ((Status = g_Client->SetOutputCallbacks(NULL)) != S_OK) { Exit(1, "SetOutputCallbacks failed, 0x%X\n", Status); } // // The full engine API is available so many other things // could be done here. // // A dump file could be written with WriteDumpFile. // The raw stack data could be collected with GetStackTrace and // saved along with or instead of the text. // An analysis of the current program state could be done // to automatically diagnose simple problems. // // The primary thing to watch out for is that context information // for running threads will be stale. This could be avoided // by enumerating and suspending all other threads after the // attach completes and then resuming before the assert // returns controls. Otherwise, switching between threads will // refresh the thread context and can be used to poll the context // state. // } DWORD AssertionExceptionDump(PEXCEPTION_POINTERS Exception) { CreateInterfaces(); SelfAttach(); DumpStack(Exception->ContextRecord); ReleaseInterfaces(); return EXCEPTION_EXECUTE_HANDLER; } void AssertionFailed(PSTR File, int Line, PSTR ExprText) { printf("Assertion failed: %s(%d):\n %s\n", File, Line, ExprText); if (!g_NoDebuggerCheck && IsDebuggerPresent()) { // We're already running under a debugger so just break in. DebugBreak(); } else { // No debugger, so just get a stack from the current // routine and then continue on. We need a context // for the currently running code, so force an exception // to get an EXCEPTION_POINTERS structure with context // information that we can use to get a stack trace. __try { RaiseException(0x1234, 0, 0, NULL); } __except(AssertionExceptionDump(GetExceptionInformation())) { // Nothing to do. } } } void ParseCommandLine(int Argc, char** Argv) { g_Pid = GetCurrentProcessId(); g_Suspend = FALSE; g_NoDebuggerCheck = FALSE; while (--Argc > 0) { Argv++; if (!strcmp(*Argv, "-d")) { g_NoDebuggerCheck = TRUE; } else if (!strcmp(*Argv, "-p")) { if (Argc < 2) { Exit(1, "-p missing argument\n"); } Argv++; Argc--; g_Pid = atoi(*Argv); } else if (!strcmp(*Argv, "-s")) { g_Suspend = TRUE; } else if (!strcmp(*Argv, "-y")) { if (Argc < 2) { Exit(1, "-y missing argument\n"); } Argv++; Argc--; g_SymbolPath = *Argv; } else { Exit(1, "Unknown command line argument '%s'\n", *Argv); } } } void __cdecl main(int Argc, char** Argv) { ParseCommandLine(Argc, Argv); DE_ASSERT(Argc == 0); }