リリースコードのステップ実行/事後デバッグ(VS / C ++)
-
05-07-2019 - |
質問
リリースコードをステップ実行する意味はありますか?いくつかのコード行が省略されている、つまりメソッド呼び出しがいくつかあることに気付きました。また、変数のプレビューでは一部の変数が表示されず、他の一部の変数に対して無効な(実数ではない)値が表示されるため、まったく誤解を招きます。
WinDbgクラッシュダンプファイルをVisual Studioに読み込むと、ステップ実行と同じスタックと変数の部分ビューが表示されるため、この質問をしています。最適化せずにアプリケーションを再コンパイルする以外に、クラッシュダンプ分析のエクスペリエンスを改善する方法はありますか?
Windows、Visual Studio 2005、アンマネージC ++
解決
最適化せずに目的のファイルのみを再コンパイルします:)
一般:
- インターリーブされた逆アセンブリモードに切り替えます。逆アセンブリをシングルステップ実行すると、スキップされた関数呼び出しにステップインでき、インラインコードがより明確になります。
- デバッガが直接表示できない変数の値を取得する別の方法を探します。それらが引数として渡された場合は、コールスタックを検索します-多くの場合、呼び出し元に表示されます。それらが何らかのオブジェクトからゲッターを介して取得された場合、そのオブジェクトを調べます。それらを計算するコードによって生成されたアセンブリを見て、それらが格納されている場所を見つけます。他のすべてが失敗し、最適化を無効にする/ printf()を追加することでデバッグに影響を与えるほどタイミングが歪む場合は、ダミーのグローバル変数を追加し、対象セクションへのエントリで対象の値に設定します。
他のヒント
はい-ビルド用の.pdbとクラッシュの.dmpファイルがある場合、正確な障害点でデバッガーを開き、その時点でアプリの状態を調べることができます。
いくつか指摘したように、いくつかの変数は最適化されますが、少し創造的/好奇心re盛であれば、それらの値を取得する方法があります。
次のようなコードを使用して、すべてのWindowsフレーバーで動作する.dmpファイルを自動的に生成するコードのルートクラッシュハンドラーを構築できます(Windowsアプリを作成している場合):
// capture the unhandled exception hook - we will create a mini dump for ourselves
// NOTE: according to docs, if a debugger is present, this API won't succeed (ie. debug builds ignore this)
MiniDumper::Install(
true,
filename,
"Please send a copy of this file, along with a brief description of the problem, to [insert your email address here] so that we might fix this issue."
);
上記には、以下で記述したMiniDumperクラスが必要です。
#pragma once
#include <dbghelp.h>
#include "DynamicLinkLibrary.h"
#include "FileName.h"
//////////////////////////////////////////////////////////////////////////
// MiniDumper
//
// Provides a mechanism whereby an application will generate its own mini dump file anytime
// it throws an unhandled exception (or at the client's request - see GenerateMiniDump, below).
//
// Warning: the C-runtime will NOT invoke our unhandled handler if you are running a debugger
// due to the way that the SetUnhandledExceptionFilter() API works (q.v.)
//
// To use this facility, simply call MiniDumper::Install - for example, during CWinApp initialization.
//
// Once this has been installed, all current and future threads in this process will be covered.
// This is unlike the StructuredException and CRTInvalidParameter classes, which must be installed for
// for each thread for which you wish to use their services.
//
class MiniDumper
{
public:
// install the mini dumper (and optionally, hook the unhandled exception filter chain)
// @param filename is the mini dump filename to use (please include a path)
// @return success or failure
// NOTE: we can be called more than once to change our options (unhook unhandled, change the filename)
static bool Install(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType = MiniDumpNormal)
{
return GetSingleton().Initialize(bHookUnhandledExceptionFilter, filenameMiniDump, strCustomizedMessage, dwMiniDumpType);
}
// returns true if we've been initialized (but doesn't indicate if we have hooked the unhandled exception filter or not)
static bool IsInitialized() { return g_bInstalled; }
// returns true if we've been setup to intercept unhandled exceptions
static bool IsUnhandledExceptionHooked() { return g_bInstalled && GetSingleton().m_bHookedUnhandledExceptionFilter; }
// returns the filename we've been configured to write to if we're requested to generate a mini dump
static CFilename GetMiniDumpFilename() { return g_bInstalled ? GetSingleton().m_filenameMiniDump : ""; }
// you may use this wherever you have a valid EXCEPTION_POINTERS in order to generate a mini dump of whatever exception just occurred
// use the GetExceptionInformation() intrinsic to obtain the EXCEPTION_POINTERS in an __except(filter) context
// returns success or failure
// DO NOT hand the result of GenerateMiniDump to your __except(filter) - instead use a proper disposition value (q.v. __except)
// NOTE: you *must* have already installed MiniDumper or this will only error
static bool GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers);
private:
// based on dbghelp.h
typedef BOOL (WINAPI * MINIDUMPWRITEDUMP_FUNC_PTR)(
HANDLE hProcess,
DWORD dwPid,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
// data we need to pass to our mini dump thread
struct ExceptionThreadData
{
ExceptionThreadData(EXCEPTION_POINTERS * exceptionPointers, bool bUnhandled, DWORD threadID = ::GetCurrentThreadId())
: pExceptionPointers(exceptionPointers)
, dwThreadID(threadID)
, bUnhandledException(bUnhandled)
{
}
EXCEPTION_POINTERS * pExceptionPointers;
DWORD dwThreadID;
bool bUnhandledException;
};
// our unhandled exception filter (called automatically by the run time if we've been installed to do so)
static LONG CALLBACK UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers);
// creates a new thread in which to generate our mini dump (so we don't run out of stack)
static bool ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException);
// thread entry point for generating a mini dump file
static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
// obtains the one and only instance
static MiniDumper & GetSingleton();
// flag to indicate if we're installed or not
static bool g_bInstalled;
// create us
MiniDumper()
: m_pPreviousFilter(NULL)
, m_pWriteMiniDumpFunction(NULL)
, m_bHookedUnhandledExceptionFilter(false)
{
}
// install our unhandled exception filter
bool Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType);
// generates a mini dump file
bool GenerateMiniDumpFile(ExceptionThreadData * pData);
// handle an unhandled exception
bool HandleUnhandledException(ExceptionThreadData * pData);
bool m_bHookedUnhandledExceptionFilter;
CFilename m_filenameMiniDump;
CString m_strCustomizedMessage;
DWORD m_dwMiniDumpType;
MINIDUMPWRITEDUMP_FUNC_PTR m_pWriteMiniDumpFunction;
LPTOP_LEVEL_EXCEPTION_FILTER m_pPreviousFilter;
};
そしてその実装:
#include "StdAfx.h"
#include "MiniDumper.h"
using namespace Toolbox;
//////////////////////////////////////////////////////////////////////////
// Static Members
bool MiniDumper::g_bInstalled = false;
// returns true if we were able to create a mini dump for this exception
bool MiniDumper::GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers)
{
// obtain the mini dump in a new thread context (which will have its own stack)
return ExecuteMiniDumpThread(pExceptionPointers, false);
}
// this is called from the run time if we were installed to hook the unhandled exception filter
LONG CALLBACK MiniDumper::UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers)
{
// attempt to generate the mini dump (use a separate thread to ensure this one is frozen & we have a fresh stack to work with)
ExecuteMiniDumpThread(pExceptionPointers, true);
// terminate this process, now
::TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF);
// carry on as normal (we should never get here due to TerminateProcess, above)
return EXCEPTION_CONTINUE_SEARCH;
}
bool MiniDumper::ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException)
{
// because this may have been created by a stack overflow
// we may be very very low on stack space
// so we'll create a new, temporary stack to work with until we fix this situation
ExceptionThreadData data(pExceptionPointers, bUnhandledException);
DWORD dwScratch;
HANDLE hMiniDumpThread = ::CreateThread(NULL, 0, MiniDumpThreadProc, &data, 0, &dwScratch);
if (hMiniDumpThread)
{
VERIFY(::WaitForSingleObject(hMiniDumpThread, INFINITE) == WAIT_OBJECT_0);
VERIFY(::GetExitCodeThread(hMiniDumpThread, &dwScratch));
VERIFY(::CloseHandle(hMiniDumpThread));
return AsBool(dwScratch);
}
return false;
}
DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam)
{
// retrieve our exception context from our creator
ExceptionThreadData * pData = (ExceptionThreadData *)lpParam;
// generate the actual mini dump file in this thread context - with our own stack
if (pData->bUnhandledException)
return GetSingleton().HandleUnhandledException(pData);
else
return GetSingleton().GenerateMiniDumpFile(pData);
}
bool MiniDumper::HandleUnhandledException(ExceptionThreadData * pData)
{
// generate the actual mini dump file first - hopefully we get this even if the following errors
const bool bMiniDumpSucceeded = GenerateMiniDumpFile(pData);
// try to inform the user of what's happened
CString strMessage = FString("An Unhandled Exception occurred in %s\n\nUnfortunately, this requires that the application be terminated.", CFilename::GetModuleFilename());
// create the mini dump file
if (bMiniDumpSucceeded)
{
// let user know about the mini dump
strMessage.AppendFormat("\n\nOn a higher note, we have saved some diagnostic information in %s", m_filenameMiniDump.c_str());
}
// append any custom message(s)
if (!IsEmpty(m_strCustomizedMessage))
strMessage.AppendFormat("\n\n%s", m_strCustomizedMessage);
// cap it off with an apology
strMessage.Append("\n\nThis application must be terminated now. All unsaved data will be lost. We are deeply sorry for the inconvenience.");
// let the user know that things have gone terribly wrong
::MessageBox(GetAppWindow(), strMessage, "Internal Error - Unhandled Exception", MB_ICONERROR);
// indicate success or not
return bMiniDumpSucceeded;
}
//////////////////////////////////////////////////////////////////////////
// Instance Members
MiniDumper & MiniDumper::GetSingleton()
{
static std::auto_ptr<MiniDumper> g_pSingleton(new MiniDumper);
return *g_pSingleton.get();
}
bool MiniDumper::Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType)
{
// check if we need to link to the the mini dump function
if (!m_pWriteMiniDumpFunction)
{
try
{
// attempt to load the debug helper DLL
DynamicLinkLibrary dll("DBGHelp.dll", true);
// get the function address we need
m_pWriteMiniDumpFunction = (MINIDUMPWRITEDUMP_FUNC_PTR)dll.GetProcAddress("MiniDumpWriteDump", false);
}
catch (CCustomException &)
{
// we failed to load the dll, or the function didn't exist
// either way, m_pWriteMiniDumpFunction will be NULL
ASSERT(m_pWriteMiniDumpFunction == NULL);
// there is nothing functional about the mini dumper if we have no mini dump function pointer
return false;
}
}
// record the filename to write our mini dumps to (NOTE: we don't do error checking on the filename provided!)
if (!IsEmpty(filenameMiniDump))
m_filenameMiniDump = filenameMiniDump;
// record the custom message to tell the user on an unhandled exception
m_strCustomizedMessage = strCustomizedMessage;
// check if they're updating the unhandled filter chain
if (bHookUnhandledExceptionFilter && !m_bHookedUnhandledExceptionFilter)
{
// we need to hook the unhandled exception filter chain
m_pPreviousFilter = ::SetUnhandledExceptionFilter(&MiniDumper::UnhandledExceptionFilter);
}
else if (!bHookUnhandledExceptionFilter && m_bHookedUnhandledExceptionFilter)
{
// we need to un-hook the unhandled exception filter chain
VERIFY(&MiniDumper::UnhandledExceptionFilter == ::SetUnhandledExceptionFilter(m_pPreviousFilter));
}
// set type of mini dump to generate
m_dwMiniDumpType = dwMiniDumpType;
// record that we've been installed
g_bInstalled = true;
// if we got here, we must have been successful
return true;
}
bool MiniDumper::GenerateMiniDumpFile(ExceptionThreadData * pData)
{
// NOTE: we don't check this before now because this allows us to generate an exception in a different thread context (rather than an exception while processing an exception in the main thread)
ASSERT(g_bInstalled);
if (!g_bInstalled)
return false;
HANDLE hFile = ::CreateFile(m_filenameMiniDump.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
// indicate failure
return false;
}
else
{
// NOTE: don't use exception_info - its a #define!!!
Initialized<_MINIDUMP_EXCEPTION_INFORMATION> ex_info;
ex_info.ThreadId = pData->dwThreadID;
ex_info.ExceptionPointers = pData->pExceptionPointers;
// generate our mini dump
bool bStatus = FALSE != ((*m_pWriteMiniDumpFunction)(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)m_dwMiniDumpType, &ex_info, NULL, NULL));
// close the mini dump file
::CloseHandle(hFile);
return bStatus;
}
}
これはドロップインソリューションではないことをおaび申し上げます。ツールボックスライブラリの他の部分に依存関係があります。しかし、「クラッシュミニダンプをキャプチャ」する方法を組み込む方法については、正しいアイデアを提供するのに大いに役立つと思います。コードから自動的に作成します。これを.dspファイルと組み合わせて、開発サイクルの通常の部分にすることができます。これにより、.dmpが入ったときに、保存された.pdbでデバッガを起動できます。リリースビルド(配布しない!)を使用すると、クラッシュ状態を非常に簡単にデバッグできます。
上記のコードは、さまざまなソースの融合です-デバッグブック、MSDNドキュメントなどからのコードスニペット。属性を省略した場合、問題はありません。ただし、上記のコードのいずれかが自分以外の誰かによって大幅に作成されたとは思わない。
少なくともIA64ダンプではありません...
実際には、完全なダンプシンボルとプライベートシンボルを持つ以外にできることはあまりありません。最近のコンパイラには、コードを使用してフィールドデイがあり、特に LTCG 。
次の2つが有用であることがわかりました。
-
「this」が実際に指し示すものに対する適切なアンカーが得られるまで、スタックを歩いてください。ほとんどの場合、オブジェクトメソッドフレーム「this」はレジストリの最適化のために信頼できません。通常、スタックを数回呼び出して、正しいアドレスを持つオブジェクトを取得し、クラッシュポイントまでメンバー参照ごとにナビゲートし、「this」の正しい値を取得できます
-
uf(Windbgの逆アセンブリ関数コマンド)。この小さなヘルパーは、通常のディスアセンブリビューよりも扱いやすい形式で関数ディスアセンブルをリストできます。ジャンプとコードの再配置に続くので、uf出力のロジックに従うのが簡単です。
最も重要なことは、シンボルファイル(* .pdb)を持つことです。リリースビルド用に生成できます。デフォルトではアクティブではありません。
次に、最適化のためにコードの順序が変更される可能性があるため、デバッグが少しぎくしゃくする可能性があることを知っておく必要があります。また、いくつかの中間変数は最適化されている可能性があります。一般的に、データの動作と可視性にはいくつかの制限があります。
Visual Studio C ++ 2008では、*。dmpファイルを自動的にデバッグできます。 VS 2005でも動作すると思います。古いコンパイラの場合、WinDbgを使用する必要があると思います...(もちろん、WinDbgの* .pdbファイルも指定してください。そうしないと、情報がかなり制限されます)