You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
7.9 KiB
299 lines
7.9 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// Simple example of how to use non-invasive self-attach to get
|
|
// stack traces for assertion failures.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 2001.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <windows.h>
|
|
#include <dbgeng.h>
|
|
|
|
#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);
|
|
}
|