Здравствуйте, Sheridan, Вы писали:
S> прочитал как Star Track... S>Некоторое время тупо думал — а при чем тут исходники и как это сочетается с бедными...
Исходники тут при том что в сообщении реализация класса. Бедные тут при том, что реализация данной фичи — минимальная без лишних наворотов
не очень хорошо оно работает... не вытаскиваются имена файлов/номера строк
File: <unknown>, Line: <unknown>
+вместо названий функций оффсет от какой-то одной определенной в модуле.
чего-то не хватает
// RpcExStackTraceEngine.cpp
//
//////////////////////////////////////////////////////////////////////#include"StdAfx.h"#include"RpcExStackTraceEngine.h"
RpcExStackTraceEngine::RpcExStackTraceEngine() :
m_hDbhHelp(0),
m_hProcess(0),
pfnSymGetLineFromAddr64(0),
pfnSymGetSymFromAddr64(0),
pfnSymInitialize(0),
pfnSymSetOptions(0),
pfnSymGetOptions(0),
pfnStackWalk64(0),
pfnUnDecorateSymbolName(0),
pfnSymFunctionTableAccess64(0),
pfnSymGetModuleBase64(0),
m_InitializationState(IS_NotPerformed)
{
}
HRESULT RpcExStackTraceEngine::Initialize()
{
BOOL Ret;
CRpcExCritSecLock m_DbgHelpLock(m_DbgHelpGuard);
if (m_InitializationState != IS_NotPerformed)
{
return E_UNEXPECTED;
}
//
// Assume initialization is failed.
// I'll set this flag to the IS_Done in the
// end of this function.
//
m_InitializationState = IS_Failed;
//
// Load dbghelp.dll dynamically.
//if ((m_hDbhHelp = LoadLibrary(L"dbghelp.dll")) == 0)
{
return HRESULT_FROM_WIN32(GetLastError());
}
//
// Load procedures.
//
pfnSymInitialize = (tfnSymInitialize) GetProcAddress(m_hDbhHelp, "SymInitialize" );
pfnStackWalk64 = (tfnStackWalk64) GetProcAddress(m_hDbhHelp, "StackWalk64" );
pfnSymGetLineFromAddr64 = (tfnSymGetLineFromAddr64) GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64" );
pfnSymGetSymFromAddr64 = (tfnSymGetSymFromAddr64) GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64" );
pfnUnDecorateSymbolName = (tfnUnDecorateSymbolName) GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName" );
pfnSymFunctionTableAccess64 = (tfnSymFunctionTableAccess64) GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64" );
pfnSymGetModuleBase64 = (tfnSymGetModuleBase64) GetProcAddress(m_hDbhHelp, "SymGetModuleBase64" );
pfnSymSetOptions = (tfnSymSetOptions) GetProcAddress(m_hDbhHelp, "SymSetOptions" );
pfnSymGetOptions = (tfnSymGetOptions) GetProcAddress(m_hDbhHelp, "SymGetOptions" );
if ((pfnSymInitialize == 0) ||
(pfnStackWalk64 == 0) ||
(pfnSymGetLineFromAddr64 == 0) ||
(pfnSymGetSymFromAddr64 == 0) ||
(pfnUnDecorateSymbolName == 0) ||
(pfnSymFunctionTableAccess64 == 0) ||
(pfnSymGetModuleBase64 == 0) ||
(pfnSymSetOptions == 0) ||
(pfnSymGetOptions == 0))
{
FreeLibrary(m_hDbhHelp);
m_hDbhHelp = 0;
return E_UNEXPECTED;
}
DWORD dwOpts = pfnSymGetOptions() ;
//
// Initialize symbols engine.
//if ((Ret = pfnSymSetOptions(dwOpts | SYMOPT_LOAD_LINES)) == FALSE)
{
return HRESULT_FROM_WIN32(GetLastError());
}
m_hProcess = GetCurrentProcess();
//
// SymInitialize 2-nd parameter description from the MSDN.
// Pointer to a null-terminated string that specifies a path,
// or series of paths separated by a semicolon (;),
// that is used to search for symbol files.
// If a value of NULL is used, then the library attempts to
// form a symbol path from the following sources:
// The current working directory of the application
// The _NT_SYMBOL_PATH environment variable
// The _NT_ALTERNATE_SYMBOL_PATH environment variable
//if ((Ret = pfnSymInitialize(
m_hProcess,
0,
TRUE)) == FALSE)
{
return HRESULT_FROM_WIN32(GetLastError());
}
m_InitializationState = IS_Done;
return S_OK;
}
RpcExStackTraceEngine::InitializationState RpcExStackTraceEngine::GetInitializationState()
{
return m_InitializationState;
}
// Declare these so they are accessable anywhere in the project.extern"C"void * _ReturnAddress ( void ) ;
#pragma intrinsic ( _ReturnAddress )
HRESULT RpcExStackTraceEngine::WalkStack(
WCHAR* StackTrace,
DWORD StackTraceSize, /*in characters*/
DWORD MaxStackTraceDepth)
{
CRpcExCritSecLock m_DbgHelpLock(m_DbgHelpGuard);
if ((StackTrace == 0) ||
(IsBadWritePtr(StackTrace, StackTraceSize*sizeof(WCHAR))))
{
//
// Grrrr,
// I donТt like bad buffers.
//return E_INVALIDARG;
}
if (m_InitializationState != IS_Done)
{
//
// Looks like initialization failed
// or not performed yet.
//return E_UNEXPECTED;
}
//
// Init out parameters.
//
StackTrace[0] = L'\0';
HANDLE hCurrentThread = GetCurrentThread();
//
// The following should be enough for walking the call stack...
//
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_FULL;
__asm call x;
__asm x: pop eax;
__asm mov ctx.Eip, eax;
__asm mov ctx.Ebp, ebp;
__asm mov ctx.Esp, esp;
//
// Init STACKFRAME for the first call
//
STACKFRAME64 stfrm = {0}; // in/out stackframe
stfrm.AddrPC.Offset = ctx.Eip;
stfrm.AddrPC.Mode = AddrModeFlat;
stfrm.AddrStack.Offset = ctx.Esp;
stfrm.AddrStack.Mode = AddrModeFlat;
stfrm.AddrFrame.Offset = ctx.Ebp;
stfrm.AddrFrame.Mode = AddrModeFlat;
//
// Symbol info memory buffer.
//char SymbolBuffer[65535] = {0};
IMAGEHLP_SYMBOL64 *pSymbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(&SymbolBuffer);
pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
DWORD SymbolNameSize = (DWORD)sizeof(SymbolBuffer) - offsetof(IMAGEHLP_SYMBOL64, Name);
pSymbol->MaxNameLength = SymbolNameSize;
//
// Line info.
//
IMAGEHLP_LINE64 Line = {0};
Line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
for(ULONG StackNumber = 0; StackNumber < MaxStackTraceDepth; ++StackNumber)
{
//
// TODO:
// Use heap instead of stack.
// USES_CONVERSION uses stack to store strings.
//
USES_CONVERSION;
//
// IMAGE_FILE_MACHINE_I386 is only supported now.
// I have no other platforms to check this code :(
//
pfnStackWalk64(
IMAGE_FILE_MACHINE_I386,
m_hProcess,
hCurrentThread,
&stfrm,
&ctx,
0,
pfnSymFunctionTableAccess64,
pfnSymGetModuleBase64,
0);
if (stfrm.AddrPC.Offset == 0)
break;
//
// If StackWalk64 fails it does not cleans ->Name.
// We must do this manually.
//
pSymbol->Name[0] = '\0';
//
// Get procedure info.
//
DWORD64 SymbolDisplacement = 0;
pfnSymGetSymFromAddr64(
m_hProcess,
stfrm.AddrPC.Offset,
&SymbolDisplacement,
pSymbol);
//
// Get line info.
//
DWORD LineDisplacement = 0;
pfnSymGetLineFromAddr64(
m_hProcess,
stfrm.AddrPC.Offset,
&LineDisplacement,
&Line);
//
// Append symbol name.
//if ((pSymbol->Name == 0) ||
(pSymbol->Name[0] == L'\0'))
{
StringCchCat(
StackTrace,
StackTraceSize,
L"Procedure: <unknown>");
}
else
{
StringCchCat(
StackTrace,
StackTraceSize,
L"Procedure: ");
StringCchCat(
StackTrace,
StackTraceSize,
A2CW(pSymbol->Name));
}
//
// Append symbol location
//if ((Line.FileName == 0) ||
(Line.FileName[0] == L'\0'))
{
StringCchCat(
StackTrace,
StackTraceSize,
L", File: <unknown>, Line: <unknown>\n");
}
else
{
StringCchCat(
StackTrace,
StackTraceSize,
L" , File: ");
StringCchCat(
StackTrace,
StackTraceSize,
A2CW(Line.FileName));
StringCchCat(
StackTrace,
StackTraceSize,
L", Line: ");
WCHAR LineNumberBuffer[16];
_itow(Line.LineNumber, LineNumberBuffer, 10);
StringCchCat(
StackTrace,
StackTraceSize,
LineNumberBuffer);
StringCchCat(
StackTrace,
StackTraceSize,
L"\n");
}
}
return S_OK;
}
h
// RpcExStackTraceEngine.h
//
//////////////////////////////////////////////////////////////////////#if !defined(__RPC_EX_STACK_TRACE_ENGINE_INCLUDED__)
#define __RPC_EX_STACK_TRACE_ENGINE_INCLUDED__
#include"dbghelp.h"/**
* This is class help to obtain call stack as a
* simple string. It uses "dbghelp.dll" to walk
* the call stack and search symbols in the debug
* information. It is recommended to have
* only one instance of this class.
*
* WARNING:
* ASSERT's and TRACE's implementation use
* this class to show call stack, so it is
* not possible to use ASSERT's in this class.
*/class RpcExStackTraceEngine
{
public:
enum InitializationState
{
IS_NotPerformed,
IS_Failed,
IS_Done
};
private:
/**
* HMODULE of the "dbghelp.dll"
* We're loading "dbghelp.dll" dynamically to avoid
* dependency from "dbghelp.dll" because if dependency
* is present loading RpcExFramework library will fail
* if "dbghelp.dll" is absent.
*/
HMODULE m_hDbhHelp;
/**
* Handle of the current process.
* We need it in almost all Sym***
* operations.
*/
HANDLE m_hProcess;
/**
* RpcExStackTraceEngine initialization state.
* We need this because:
* We must do some work in the WalkStack implementation
* only if initialization success.
* We must not do some work in the Initialize only
* if Initialize has not been called before.
*/
InitializationState m_InitializationState;
/**
* All "dbghelp.dll" functions are single threaded.
* So we must protect calls to the "dbghelp.dll"
* with critical section.
*/
CComAutoCriticalSection m_DbgHelpGuard;
//
// Pointers to the "dbghelp.dll" functions.
//
// SymGetLineFromAddr64()typedef BOOL (__stdcall *tfnSymGetLineFromAddr64)(
HANDLE hProcess,
DWORD64 dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE64 Line);
tfnSymGetLineFromAddr64 pfnSymGetLineFromAddr64;
// SymGetSymFromAddr64()typedef BOOL (__stdcall *tfnSymGetSymFromAddr64)(
HANDLE hProcess,
DWORD64 dwAddr,
PDWORD64 pdwDisplacement,
PIMAGEHLP_SYMBOL64 Symbol);
tfnSymGetSymFromAddr64 pfnSymGetSymFromAddr64;
// SymInitialize()typedef BOOL (__stdcall *tfnSymInitialize)(
HANDLE hProcess,
PSTR UserSearchPath,
BOOL fInvadeProcess );
tfnSymInitialize pfnSymInitialize;
// SymSetOptions()typedef DWORD (__stdcall *tfnSymSetOptions)(
DWORD SymOptions);
tfnSymSetOptions pfnSymSetOptions;
// SymGetOptions()typedef DWORD (__stdcall *tfnSymGetOptions)();
tfnSymGetOptions pfnSymGetOptions;
// StackWalk64()typedef BOOL (__stdcall *tfnStackWalk64)(
DWORD MachineType,
HANDLE hProcess,
HANDLE hThread,
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );
tfnStackWalk64 pfnStackWalk64;
// UnDecorateSymbolName()typedef DWORD (__stdcall WINAPI *tfnUnDecorateSymbolName)(
PCSTR DecoratedName,
PSTR UnDecoratedName,
DWORD UndecoratedLength,
DWORD Flags);
tfnUnDecorateSymbolName pfnUnDecorateSymbolName;
// SymFunctionTableAccess64()typedef PVOID (__stdcall *tfnSymFunctionTableAccess64)(
HANDLE hProcess,
DWORD64 AddrBase);
tfnSymFunctionTableAccess64 pfnSymFunctionTableAccess64;
// SymGetModuleBase64()typedef DWORD64 (__stdcall *tfnSymGetModuleBase64)(
HANDLE hProcess,
DWORD64 dwAddr );
tfnSymGetModuleBase64 pfnSymGetModuleBase64;
public:
RpcExStackTraceEngine();
/**
* Initializes the RpcExStackTraceEngine().
* In fact we just initializing symbols engine.
*/
HRESULT Initialize();
/**
* Returns result of the Initialize() call.
*/
InitializationState GetInitializationState();
/**
* Returns stack trace as a string.
*/
HRESULT WalkStack(
WCHAR* StackTrace,
DWORD StackTraceSize, /*in characters*/
DWORD MaxStackTraceDepth);
};
#endif// __RPC_EX_STACK_TRACE_ENGINE_INCLUDED__
Для того, что бы всё работало см выделенное болдом.
Тоесть лучше всего скопировать pdb-шки в каталог с EXE-шником
Tom>>Гмммммм. У меня как раз проблемма была в том, что данная функция не хотела работать и если переделываю на неё — ничего не работает
Tom>Вот кстате из MSDN: Tom>
Tom>You cannot get a valid context for a running thread. Use the SuspendThread function to suspend the thread before calling GetThreadContext.
у меня наоборот %( серьезно!
а как именно "не работало"? можно ли чтобы работало то что работает в текущем варианте?
IP>если поток обвалился по жесткому эксепшену (типа C005, это мой случай) то он наверно априори Suspended и поэтому работает?
А я трейс делаю не из эксепшинов, а в асертах его применяю.
IP>у меня наоборот %( серьезно!
гм.
IP>а как именно "не работало"? можно ли чтобы работало то что работает в текущем варианте?
В трейсе был бред, 2 функции, которые явно не в тему. Типа:
Здравствуйте, Tom, Вы писали:
IP>>если поток обвалился по жесткому эксепшену (типа C005, это мой случай) то он наверно априори Suspended и поэтому работает? Tom>А я трейс делаю не из эксепшинов, а в асертах его применяю.
а я наоборот... хотя я поторопился (когда экспериментировал почему не срабатывало получение имен файлов из пдбшек) — сейчас сделал получение контекста как в оригинале (через асм) — все работает для текущего потока.
так что еще раз спасибо за небольшое и удобное решение! %)
Здравствуйте, Tom, Вы писали:
IP>>если поток обвалился по жесткому эксепшену (типа C005, это мой случай) то он наверно априори Suspended и поэтому работает? Tom>А я трейс делаю не из эксепшинов, а в асертах его применяю.
кстати, внесу свою лепту. для применения в ассертах удобно использовать такую штуку — глобальный хук на ассерты в приложении:
int DebugHelper(int nRptType, char *szMsg, int *retVal);
/** Класс обеспечивающий запись стека вызовов при срабатывании assert'a */
class CAssertWrapper
{
public:
/** Конструктор
Устанавливает хук на ассерты
*/
CAssertWrapper()
{
// Устанавливаем хук на ассерты - в любом случае!!!
_CrtSetReportHook(DebugHelper);
};
/** Деструктор
Снимает хук
*/
~CAssertWrapper()
{
_CrtSetReportHook(NULL);
};
/** Запись отладочной информации в файл
@param szText
*/
BOOL WriteAssert(const char* szText)
{
***
}
};
/** Экземпляр класса CAssertWrapper, который устанавливает и снимает хук */
__declspec ( dllexport ) CAssertWrapper assertWrapperObject;
int DebugHelper(int nRptType, char *szMsg, int *retVal)
{
BOOL bHandled=FALSE;
if(nRptType!=_CRT_WARN){//_CRT_ERROR,_CRT_ASSERT
_CrtSetReportHook(NULL);// Чтобы небыло супер циклов..
bHandled=assertWrapperObject.WriteAssert(szMsg);
_CrtSetReportHook(DebugHelper);
}
return bHandled;
}
и все ассерты (ваши/MFCшные и т.п) будут проходить через WriteAssert, где можно тащить стек и показывать его к примеру или сохранять в файл на будущее ну и т.п. %) Тоже удобно %)
IP>а я наоборот... хотя я поторопился (когда экспериментировал почему не срабатывало получение имен файлов из пдбшек) — сейчас сделал получение контекста как в оригинале (через асм) — все работает для текущего потока. IP>так что еще раз спасибо за небольшое и удобное решение! %)
Уффф. Ты меня успокоил. Я уже начал новый Debugging Tools качать и с контекстом разбираться
IP>кстати, внесу свою лепту. для применения в ассертах удобно использовать такую штуку — глобальный хук на ассерты в приложении:
У меня всё более запущено. Своя реализация макросов и окна. Как то так:
Здравствуйте, Zibir, Вы писали:
Z>Попробовал себе поставить всё это, но получилось только в дебаг версии. В релизе она не может работать?
Может. Нужно включите в настройках генерацию .pdb файла. По умолчанию выключено.