调试工具包






4.20/5 (5投票s)
2000年3月26日

186013

1523
一个完整的调试工具包,可为您的应用程序添加智能调试功能。
引言
您已经编写了一个很棒的应用程序并将其发布给所有用户,一周后您收到了一封令人沮丧的电子邮件。
Downloaded your program and I love it but it crashed. Can you please fix it.
您不期望有详细信息……是吗?所以现在您需要与用户进行长时间的沟通,试图找出错误,最终您会放弃,将其放入“太难”的篮子,以防其他人遇到问题。
当然,您可以要求用户安装 Dr Watson 或其他在应用程序遇到异常时捕获信息的工具,但如果问题是挂起,或者您希望在问题发生时获取应用程序内部状态信息呢?
有一个更好的方法。使用本文概述的调试工具包,您可以让用户发送给您一个文件,其中包含
- Trace Output
即使在发布版本中也可以捕获跟踪输出。跟踪输出可以被过滤以允许选择性跟踪输出。跟踪输出保存在文件中,确保即使程序挂起或出现异常也不会丢失,并且其大小受到限制,因此不会填满用户的硬盘。 - 命令行
用于运行程序的完整命令行,包括任何命令行选项。 - Program Options
您需要在这里做一些工作,然后您可以保存当前有效的程序选项。这可以以描述性形式和更易于机器读取的形式完成。 - Check Files
检查并转储您的程序依赖的任何 DLL 或文件的详细信息。您只需要告诉它要检查哪些文件。 - Check COM
检查并转储您的程序依赖的任何 COM 对象的详细信息。同样,只需告诉它要检查哪些对象。 - Printer
转储当前默认打印机的设置,以帮助您诊断与打印相关的问题。 - CPU
转储 PC 中 CPU 的详细信息。 - Current Directory
调试工具包将转储当前工作目录的名称。 - Windows Version
转储 Windows 的版本详细信息。 - User definable
当然,如果您还有其他想转储的内容,也可以这样做。
跟踪日志不仅包含您跟踪出的语句。当发生错误(例如异常、挂起或断言失败)时,以下详细信息将被转储到跟踪日志中,以便您可以了解当时的情况:
- 调用堆栈
当前的调用堆栈,这样您就可以将错误追溯到确切的代码行。 - Register Dump
转储所有处理器寄存器以及寄存器指向的 16 字节内存。 - 任务列表
PC 上活动任务的列表。 - Loaded modules
加载到您进程地址空间中的 DLL 和其他模块的列表。 - Windows Resources
可用的 Windows GDI 和 USER 资源。 - Program Stack
程序堆栈内容的转储。
这些是基于以下事件生成的:
- Asserts
当您的代码调用调试工具包来断言一个错误的条件时,工具包将转储程序的 P状态。 - 异常
当 Windows 捕获异常(例如无效内存访问或除零错误)时,工具包将转储程序的 P状态。 - Automatically Detected Hangs
如果创建调试工具包对象的线程在给定时间内未处理任何 Windows 消息,则工具包将转储程序的 P状态并终止程序。 - User Detected Hangs
如果用户按住 <CTRL><F11><F12> 一段时间,则工具包会假定用户挂起,转储程序的 P状态并终止程序。
要获得所有这些,您实际上只需要很少的代码,并且可以轻松地增强或限制功能,而无需直接修改提供的代码。
这里的其中一个设计目标是使此类非常容易集成到现有或新的应用程序中。主类是 CDebugToolkit
。它还有一些辅助类,但您应该永远不需要访问它们。 CDebugToolkit
被设计为您的自定义调试工具类,您将在其中覆盖许多方法,并偶尔调用基类的方法。如果您发现自己修改 CDebugToolkit
,那么要么您找到了一个错误(请告知我们),要么您提供了一个可以供他人使用的通用增强功能(也请告知我们)。在所有其他情况下,我希望功能可以保留在派生类中。
坚持这种方法,您应该能够轻松地集成未来可能进行的增强功能。
类概述
公共方法
BOOL AddFile(const CString& sFile, const BOOL fTestDLL = FALSE, const BOOL fTestFile = FALSE);
将文件添加到需要转储的日志文件中。此函数返回 FALSE,如果文件要被测试但测试失败。
- sFile
要添加的文件名。如果文件必须位于特定目录,请包含完整路径。 - fTestDLL
如果为 TRUE,则文件将被视为 DLL,程序将在启动时验证其是否可加载。 - fTestFile
如果为 TRUE,则将测试文件以查看其在启动时是否可访问。
如果测试文件失败,则返回 FALSE。
BOOL AddCOM(const CString& sFile, const CLSID clsid, const BOOL fTest = FALSE);
将 COM 对象添加到需要转储的 COM 对象列表中。此函数返回 FALSE,如果 COM 对象要被测试但测试失败。
- sFile
要添加的 COM 对象的名称。 - clsid
要添加的 COM 对象的 CLSID。 - fTest
如果为 TRUE,则将测试 COM 对象以查看其在启动时是否可加载。
如果测试 COM 对象失败,则返回 FALSE。
virtual void Assert(DWORD dwLine, const CString& sFile, BOOL fCondition, const CString& sCondition, const CString& sDescription = "");
评估 fCondition
。如果为 FALSE,则写入带有行号、条件和可选描述的详细跟踪输出。请参阅 DT_ASSERT* 宏,以便轻松使用此函数。这是一个虚拟函数,因此您始终可以在派生类中替换它。
- dwLine
包含断言调用的源行号。 - sFile
包含断言调用的源文件。 - fCondition
正在评估的条件。 - sCondition
条件的文本格式。 - sDescription
对正在测试内容的描述。
virtual void Trace(const CString& sOut, const DWORD dwFilterFlag = 0xFFFFFFFF, const BOOL fForce = FALSE);
将消息写入跟踪日志。请参阅 DT_TRACE* 宏,以便轻松使用此函数。这是一个虚拟函数,因此您始终可以在派生类中替换它。
- sOut
要跟踪的文本。 - dwFilterFlag
一个 32 位字段,将与过滤器进行按位与运算,以确定是否应跟踪此内容。 - fForce
如果为 TRUE,则无论任何过滤器或OnTrace
方法的结果如何,文本都将写入跟踪日志。
virtual BOOL DumpToFile(const CString& sFile, const CString& sPre = "");
virtual void DumpToFile(CFile& file, const CString& sPre = "");
virtual void DumpToFile(CFile& file, const CString& sPre = "");
将所有调试信息写入日志文件。这些是虚拟函数,因此您始终可以在派生类中替换它们。
- sFile
要创建的日志文件的名称。 - 文件
一个已打开的文件,用于写入日志内容。 - sPre
一个文本字符串,将在文件开头插入。
如果文件无法创建,则返回 FALSE。
void Delete(void);
删除用于跟踪输出的圆形日志文件。
void Reset(void);
调用此函数将使调试工具包重新加载调试选项。如果您想在运行时打开/关闭调试功能,请使用此函数。
void SetTraceFilter(const DWORD dwFilter)
此函数设置跟踪过滤器 32 位标志,该标志将与每个跟踪请求进行按位与运算。
- dwFiler
32 位标志过滤器。
void SetSuspendHang(const BOOL fSuspend)
此函数挂起并重新激活自动挂起检测例程。通常,您会在执行不处理消息的特别耗时的任务时挂起此例程。
- fSuspend
TRUE 表示挂起。FALSE 表示重新激活。
static CDebugToolkit* GetDebugToolkit(void)
此函数是一个静态函数,用于访问调试工具包的单个实例。DT_ 宏使用此函数,以便您可以轻松编写代码来调用实例方法。
Protected Methods
CDebugToolkit(void);
在构造派生类时,务必调用基类构造函数。您还应该设置所有您不喜欢的默认值的调试工具成员变量。
- m_sAppName
您的应用程序名称。这用作文件名,因此请仔细命名。 - m_sVersion
您的应用程序版本。 - m_fExceptions
如果我们要捕获异常,则为 TRUE。 - m_fAssert
如果我们处理断言,则为 TRUE。 - m_fHangDetect
如果我们自动检测主线程挂起,则为 TRUE。 - m_fUserHangDetect
如果我们查找用户指定的挂起,则为 TRUE。 - m_fTrace
如果我们处理跟踪输出,则为 TRUE。 - m_fDumpOptions
如果我们转储程序选项,则为 TRUE。 - m_fDumpFiles
如果我们测试并转储 DLL 和文件,则为 TRUE。注意:对于大量文件,这可能会很慢。 - m_fDumpLog
如果我们转储跟踪日志,则为 TRUE。 - m_fDumpPrinter
如果我们转储默认打印机详细信息,则为 TRUE。注意:这很慢。 - m_fDumpCPU
如果我们转储机器 CPU 的详细信息,则为 TRUE。 - m_fDumpCWD
如果我们转储当前目录详细信息,则为 TRUE。 - m_fDumpWindowsVersion
如果我们转储 Windows 版本详细信息,则为 TRUE。 - m_fDumpCommandLine
如果我们转储命令行详细信息,则为 TRUE。 - m_fDumpCOMInfo
如果我们测试并转储 COM 对象详细信息,则为 TRUE。注意:对于大量 COM 对象,这可能会很慢。 - m_fDumpLoadableOptions
如果我们转储机器可加载(但文本)选项,则为 TRUE。注意:这通常是 INI 文件格式的选项。 - m_iUserHangVk1
用户在报告用户检测到的挂起时必须按下的第一个键。 - m_iUserHangVk2
用户在报告用户检测到的挂起时必须按下的第二个键。 - m_dwUserHangWait
用户必须在用户检测到的挂起被确认之前按下按键的最大毫秒数。较短的设置会增加 CPU 负载。 - m_dwHangWait
自动检测挂起之前等待的时间量。 - m_fPrefixMultilineTrace
如果我们要为多行跟踪输出的第二行及后续行添加前缀,则为 TRUE。
您通常还会添加任何您希望检查或转储的文件和 COM 对象。
BOOL Initialise(void);
在派生类构造函数返回之前,应调用此方法。在调用此函数之前设置所有选项至关重要。
如果在初始化过程中发生错误,则返回 FALSE。
宏
DT_TRACE[0..3](s, p1, p2, p3)
这些宏是调用 Trace
方法的快捷方式。使用此宏可以避免使用过滤。此宏的使用方式与 MFC 的 TRACE[0..3]
宏完全相同。
DT_TRACEF[0..3](f, s, p1, p2, p3)
这些宏与 DT_TRACE[0..3]
完全相同,只是额外的 f 参数允许您传递一个 32 位标志字段,该字段将与跟踪过滤器标志进行按位与运算,并且仅当至少一个设置位匹配时才进行跟踪。
DT_ASSERT(b)
此宏是调用 Assert
方法的快捷方式。此宏的使用方式与 MFC 的 ASSERT
宏完全相同。
DT_ASSERTS(b, s)
此宏是调用 Assert
方法的快捷方式。它扩展了 DT_ASSERT
宏,允许您描述您正在断言的内容。
Over-ridables
这些方法专门设计用于在您从 CDebugToolkit
派生的类中进行重写。请不要直接修改这些方法。
virtual BOOL OnAssert(void);
在处理断言之前调用,以确定您是否希望处理断言。如果希望处理断言,则返回 TRUE。
virtual void OnEndAssert(void);
在断言被接受并处理后调用。允许您进行一些后处理,例如保存日志文件并建议用户将其发送给您。
virtual BOOL OnException(struct _EXCEPTION_POINTERS *pExceptionInfo);
在处理异常之前调用,以确定您是否希望处理异常。如果希望处理异常,则返回 TRUE。
virtual void OnEndException(void);
在异常被接受并处理后调用。您在此处需要非常小心,因为异常通常意味着某些地方出了严重问题。
virtual BOOL OnHang(void);
在自动检测到挂起后调用,以确定您是否希望处理挂起。如果希望处理挂起,则返回 TRUE。您可能想询问用户程序是否似乎已挂起……以防万一。
virtual void OnEndHang(void);
在挂起被接受并处理后调用。允许您在任务被终止之前进行一些最终处理。注意:调用此方法时,您不在主线程中运行。
virtual BOOL OnUserHang(void);
在用户报告的挂起发生后调用,以确定您是否希望处理挂起。如果希望处理挂起,则返回 TRUE。
virtual void OnEndUserHang(void);
在用户报告的挂起被接受并处理后调用。允许您在任务被终止之前进行一些最终处理。注意:调用此方法时,您不在主线程中运行。
virtual BOOL OnTrace(const DWORD dwFilterFlag);
在处理跟踪之前调用,以确定您是否希望处理跟踪。如果希望处理跟踪,则返回 TRUE。
virtual void OnReset(void);
在重新启动调试之前调用,以允许您更改调试工具包处理选项。
virtual BOOL OnDelete(void);
在处理删除请求之前调用,以允许您进行一些处理。返回 FALSE 以取消删除。
virtual void OnAbnormalExit(void);
当检测到上次运行时程序未正常退出时调用,以允许您进行一些处理。通常在应用程序构造函数完成之前运行此函数,这限制了您可以使用的 MFC 功能。
virtual void OnTestDLLFail(const CString& sFile, DWORD dwError);
当 DLL 无法加载时调用。如果您想告知用户某个关键 DLL 丢失,请重写此函数。
virtual void OnTestCOMFail(const CLSID clsid, const CString& sFile, DWORD dwError);
当 COM 文件无法加载时调用。如果您想告知用户某个关键 COM 对象丢失,请重写此函数。
virtual void OnTestFileFail(const CString& sFile, DWORD dwError);
当文件找不到时调用。如果您想告知用户某个关键文件丢失,请重写此函数。
virtual CString GetDumpOtherAtEvent(void);
允许您在发生挂起、异常或断言事件时提供要写入跟踪日志的其他数据。
virtual CString GetDumpOtherAtSave(void);
允许您在日志文件写入磁盘时提供要写入日志文件的其他数据。
virtual void GetOptionDescriptions(CString& sOptionDescriptions);
允许您以人类可读的形式转储当前有效的选项。
virtual void ExportOptions(CFile& file);
允许您以机器可读(但文本)的形式转储当前有效的选项。
将 Debug Toolkit 添加到您的项目中
- 将这些文件添加到您的项目中。
- DebugToolkit.cpp // 主要调试工具包类 - 请勿更改
- DebugToolkit.h // 主要调试工具包类 - 请勿更改
- FastLogger.cpp - 请勿更改
- FastLogger.h - 请勿更改
- FileInfo.cpp - 请勿更改
- FileInfo.h - 请勿更改
- K32exp.cpp - 请勿更改
- K32exp.h - 请勿更改
- MyDebugToolkit.cpp // 您的重写调试工具类的外壳
- MyDebugToolkit.h // 您的重写调试工具类的外壳
- ProcessorInfo.h - 请勿更改
- ProgressWnd.cpp - 请勿更改
- ProgressWnd.h - 请勿更改
- WindowsVersion.h - 请勿更改
- 将
VERSION.LIB
添加到您的链接器选项中。 - 在项目的链接器选项中选择
Generate mapfile
选项。 - 在 C++ Listing Files 选项中选择
Assembly with Source Code
。 - 编辑您的
CApplication
派生类以添加一个新的公共成员变量:CMyDebugToolkit m_mdt;
- 将
#include "MyDebugToolkit.h"
添加到您的CApplication
派生类头文件的顶部。您可能需要在其他调用调试工具类的 c++ 文件中执行相同的操作。 - 自定义您的新
CMyDebugToolkit
类。 - 现在,将所有
ASSERT()
和TRACE[0..3]
调用替换为DT_
等效项。 - 当您生成最终的发布版本时,请不要忘记创建一个 zip 文件,其中包含您项目的可执行文件以及 .MAP 和所有 .ASM 文件。您需要这些文件来将调用堆栈解码为原始源代码行。
注意:在调试器下运行代码时,此类将不会捕获异常事件。这是因为调试器具有优先权并首先捕获它。
显然,这里有很多功能,但也有很多可能的增强。如果您进行了任何增强或有任何建议,请 告诉我。
Possible Enhancements
以下是我想到的一些可能的增强功能,但由于时间或设备限制而未能实现。
- 修复了在 NT 上程序无法正确转储 COM 对象注册详细信息的问题。
- 为 NT 添加任务列表。
修复
- 2000 年 3 月 28 日:修复了 toolhelp API,使其不会在 Windows NT 上导致运行时错误。
注意:我会在 Code Project 网站上发布修复程序,但有时需要几天时间才能发布。最新源代码始终可在 此处 获取。