分类 调试 下的文章

简介

MiniDumpView实用程序可用于显示minidump中数据流的内容。特别是,可以显示以下信息:

  • 操作系统和CPU信息
  • 进程信息(进程ID和时间)
  • 模块列表(包含每个模块的详细信息)
  • 线程列表(包含每个线程的详细信息)
  • 异常信息
  • 保存在小型转储中的内存区域列表
  • 打开的句柄列表
  • 虚拟内存布局
  • 注释

以绿色包的形式提供,解压后可用

用法

  1. 设置环境变量
  2. 命令行里执行
    MdpView FileName
    其中FileName是小型转储文件的名称

     

     

     

     SYSTEM INFORMATION
    ------------------

    Number of processors:  8
    Architecture:          0 (Intel)
    Level:                 6
    Revision:              24067
    OS version:            10.0.17134
    Service pack:          
    Platform ID:           2 (WinNT)


    COMMENTS (ANSI)
    ---------------

    Not available.



    COMMENTS (UNICODE)
    ------------------

    Not available.



    PROCESS INFORMATION
    -------------------

    Process ID:  bd4
    CreateTime:  5dd365de  Tue Nov 19 11:47:42 2019
    UserTime:    000005f7  0 days 0:25:27
    KernelTime:  0000011b  0 days 0:04:43


    EXCEPTION INFORMATION
    ---------------------

    Thread ID:             c4c

    Code:                  c0000409
    Flags:                 00000001
    Address:               69457666
    Nested exception:      00000000
    Parameters:            1
      [0] = 00000007

    Thread context:

    Context flags: 0001003f
    EIP: 69457666   ESP: 00efdd4c   EBP: 00efdd60   EFLAGS: 00200202
    EAX: 00000001   EBX: 17bc9020   ECX: 00000007   EDX: 000001e0
    EDI: 17bc9430   ESI: 17bc9020
    CS:  0023       SS:  002b
    DS:  002b       ES:  002b       FS:  0053       GS:  002b
    DR0: 00000000   DR1: 00000000   DR2: 00000000   DR3: 00000000
    DR6: 00000000   DR7: 00000000


    THREAD INFORMATION
    ------------------

    Number of threads:     60

    .....

说明

MiniDumpView依赖于DbgHelp.dll(需要DbgHelp.dll 6.3.17.0或更新版本)。该DLL必须与MiniDumpView可执行文件位于同一目录中。

大多数的应用程序都使用多线程技术。对应Windows应用程序,为了能够使用户界面保持快速响应,经常需要把费时的任务放在与主应用程序独立的线程上运行。此时,多个线程的并发执行调试变得很困难,特别是在多个线程访问同一个类和方法时。Threads能够帮助我们减轻复杂度。

打开窗口

一般来说当程序遇到断点进入调试模式,会自动打开Threads窗口,如果没有,我们可以通过下面的方式打开:

  • 通过菜单栏打开
  • 通过快捷键
    Ctrl+Alt+H

窗口的使用

线程窗口包含其中每行描述一个单独的线程在应用程序中的表。 默认情况下,该表列出应用程序中的所有线程,但可以筛选列表以仅显示感兴趣的线程。 每个列说明了不同类型的信息。 您还可以隐藏某些列。 如果显示所有列,显示以下各列,从左到右:

  • 标志:在此未标记的专栏中,可以标记要特别注意的线程。 有关如何标记一个线程的信息,请参阅如何:标记线程和取消标记线程。

  • 当前线程:在此未标记的列,黄色箭头指示当前线程。 概述箭头指示非当前线程的当前调试器上下文。

  • ID:显示每个线程的标识号。

  • 托管 ID:显示托管线程的托管的标识号。

  • 类别:显示为用户界面线程、 远程过程调用处理程序或工作线程的线程的类别。 一个特殊类别标识应用程序的主线程。

  • 名称:如果有的话,或按名称标识每个线程<无名称 >。

  • 位置:显示线程正在其中运行。 可以展开此位置以显示线程的完整调用堆栈。

  • 优先级:(默认情况下隐藏) 的高级的列,显示系统已分配给每个线程的优先级。

  • 关联掩码:高级的列 (默认情况下隐藏),显示了每个线程的处理器关联掩码。 在多处理器系统中,关联掩码确定线程可以在哪些处理器上运行。

  • 挂起项计数:高级的列 (默认情况下隐藏),显示挂起项计数。 此计数确定线程是否可以运行。 

  • 进程名称:(默认情况下隐藏) 的高级的列,显示每个线程所属的进程。 在调试多个进程时,此列中的数据很有用。

  • 进程 ID:(默认情况下隐藏) 的高级的列,显示每个线程所属的进程 ID。

  • 传输限定符:高级的列 (默认情况下隐藏) 唯一标识调试器连接到的计算机。

在顶部的工具栏线程窗口中,选择然后,选中或清除要显示或隐藏的列的名称。

在“线程”窗口中,可以用图标标记来标记要格外关注的线程 。在“线程”窗口中,可以选择显示所有线程或仅显示标记的线程 。

当冻结线程时,系统不会启动线程的执行,即使提供了资源。在本机代码中,您可以挂起或继续线程通过调用 Windows 函数SuspendThreadResumeThread或者,致电 MFC 函数CWinThread::SuspendThread并cwinthread:: Resumethread。 如果您调用SuspendThreadResumeThread,则挂起项计数中所示线程窗口将会更改。 如果冻结或解冻本机线程不会更改挂起项计数。 线程不能在本机代码中执行,除非它线程解冻并且其挂起项计数为零。在托管代码中,当冻结或解冻线程时,将更改挂起项计数。 如果在托管代码中冻结线程,其挂起项计数为 1。 当本机代码中冻结线程时,其挂起项计数为 0,除非使用SuspendThread调用。

在顶部的工具栏线程窗口中,选择冻结线程解冻线程此操作仅影响在“线程”窗口中选中的线程 。

 

黄色箭头指示当前线程 (和执行指针的位置)。 带有卷尾的绿色箭头指示非当前线程具有当前的调试器上下文。

若要切换到另一个线程请按照以下步骤之一操作:

  • 双击任一线程。

  • 右击一个线程,然后选择切换到线程

分组线程时,表中将显示每组的标题。 标题包含组说明(如“辅助线程”或“未标记的线程”)和树控件 。 每组的成员线程显示在组标题下。 如果你想要隐藏组的成员线程,使用树控件折叠组。

因为分组优先于排序,所以您可以先按类别(以此为例)分组线程,再按每个类别中的 ID 对其进行排序。

排序线程

  1. 在顶部的工具栏线程窗口中,选择任意列顶部的按钮。

    线程现在按该列中的值进行排序。

  2. 如果你想要反转排序顺序,请再次选择相同的按钮。

    在列表顶部显示的线程现在显示在底部。

分组线程

  • 在中线程窗口工具栏中,选择分组依据列表,然后选择要分组线程所依据的条件。

对组内线程排序

  1. 在顶部的工具栏线程窗口中,选择分组依据列表,然后选择要分组线程所依据的条件。

  2. 在中线程窗口中,选择任意列顶部的按钮。

    线程现在按该列中的值进行排序。

展开或折叠所有组

在顶部的工具栏线程窗口中,选择展开组折叠组

 

您可以搜索匹配的指定的字符串中的线程线程窗口。 在搜索线程时,窗口将显示匹配的任何列中的搜索字符串的所有线程。 信息包括在“位置”列中调用堆栈顶部显示的线程位置 。 默认情况下,不搜索整个调用堆栈。

搜索特定线程

  1. 在“线程”窗口顶部的工具栏中,转到“搜索”框,执行下列操作之一 :


    • 输入搜索字符串,然后按Enter


    - 或 -


    • 选择下拉列表旁边搜索框并选择上一次搜索的搜索字符串。
  2. (可选)若要在搜索中包括整个调用堆栈,请选择“搜索调用堆栈” 。


MiniDump向导应用程序允许在不编写代码的情况下尝试MiniDumpWriteDump和MiniDumpCallback函数。可以指定将传递给MiniDumpWriteDump函数的MINIDUMP_TYPE标志的组合,并且可以在一系列对话框中响应MiniDumpCallback调用。

如何使用MiniDump Wizard

启动MiniDump向导时,它会显示一个对话框窗口,允许您选择将传递给MiniDumpWriteDump函数的MiniDump类型标志。在同一对话框中,可以指定要响应的回调类型。
对话框还允许指定目标进程。您可以指定目标的进程标识符(pid),也可以为小型转储向导进程本身创建小型转储。为了探索为小型转储向导进程创建的小型转储的内容,提供了MiniDumpWiz.pdb文件。

在第一个对话框中指定所有设置后,按Next将创建小型转储(如果未选择回调选项),或显示指定调用小型转储回调函数的对话框页,您可以在其中检查传递给回调的数据并响应回调调用(通过使用对话框控件修改回调的返回值、MINIDUMP_CALLBACK_OUTPUT structure的内容,或同时修改两者)。

微型转储文件将被称为MiniDumpWiz.dmp。目前无法更改其名称(这是为了使应用程序尽可能简单)。

MiniDump Wizard 界面介绍

主界面

启动MiniDump Wizard时始终显示此页。

  • “Target process”组允许指定目标进程。您可以指定目标进程的十进制进程id(PID),也可以选择“MiniDump Wizard”选项来转储微型转储向导进程(在后一种情况下将引发人工异常)。
  • “Show callbacks”组允许选择应处理的回调类型。
  • “MINIDUMP_TYPE flags”组允许指定将传递给MiniDumpWriteDump函数的MINIDUMP_TYPE标志。

CancelCallback页

当使用CancelCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Response”组允许设置或更改MINIDUMP_CALLBACK_OUTPUT结构的Cancel和CheckCancel成员的值。
  • “Callback return value”组允许指定回调函数的返回值。

IncludeThreadCallback页

当使用IncludeThreadCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Thread information”组显示MINIDUMP_INCLUDE_THREAD_CALLBACK结构的内容。
  • “Callback return value”组允许指定回调函数的返回值。

IncludeModuleCallback页

当使用IncludeModuleCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Module information”组显示Callback return value结构的内容。
  • “Callback return value”组允许指定回调函数的返回值。

ModuleCallback页

当使用ModuleCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Module information”组显示MINIDUMP_MODULE_CALLBACK 结构的内容。
  • “Module write flags”组允许设置或清除MINIDUMP回调输出结构的Module write flags成员中的标志。
  • “Callback return value”组允许指定回调函数的返回值。

 

ThreadCallback页

当使用ThreadCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Thread information”组显示MINIDUMP_MODULE_CALLBACK结构的内容。
  • “Thread write flags”组允许设置或清除MINIDUMP_CALLBACK_OUTPUT结构的ThreadWriteFlags成员中的标志。
  • “Callback return value”组允许指定回调函数的返回值。

 

 

MemoryCallback页

当使用MemoryCallback回调类型调用MiniDumpCallback函数时,将显示此页。

  • “Include memory range”组允许指定其内容应包含在小型转储中的内存范围的基址和大小。两个数字都必须是十六进制,没有前缀。
  • “Callback return value”组允许指定回调函数的返回值。

前面有个案例最终查明原因是System.Convert.ToInt16的调用导致溢出异常:

0:000> !PrintException /d 4ee2e8f4
Exception object: 4ee2e8f4
Exception type:   System.OverflowException
Message:          值对于 Int32 太大或太小
InnerException:   <none>
StackTrace (generated):
    SP       IP       Function
    001EC094 1D3D8831 mscorlib_ni!System.Convert.ToInt32(Double)+0xc4bc19
当时核对了代码,代码里明明调用的是System.Convert.ToInt16(float value),为什么这里却抛出异常是调用System.Convert.ToInt32(Double)引起的呢。

要想查明原因,只有查看源代码。那我们看看DotNet48RTM的源代码:

在工程mscorlib的代码..\DotNet48RTM\Source\ndp\clr\src\BCL\system\convert.cs我们可以找到相关代码

public static short ToInt16(float value) {
            return ToInt16((double)value);
        }

可知ToInt16(float value)调用的是ToInt16(double value) ,那么ToInt16(double value) 的代码如下:

public static short ToInt16(double value) {
            return ToInt16(ToInt32(value));
        }

可知ToInt16(double value)调用的是ToInt32(double value),ToInt32(double value)的代码如下:

 public static int ToInt32(double value) {
            if (value >= 0) {
                if (value < 2147483647.5) {
                    int result = (int)value;
                    double dif = value - result;
                    if (dif > 0.5 || dif == 0.5 && (result & 1) != 0) result++;
                    return result;
                }
            }
            else {
                if (value >= -2147483648.5) {
                    int result = (int)value;
                    double dif = value - result;
                    if (dif < -0.5 || dif == -0.5 && (result & 1) != 0) result--;
                    return result;
                }
            }
           throw new OverflowException(Environment.GetResourceString("Overflow_Int32"));
        }

此时,我们也就明白了为什么抛出的是“值对于 Int32 太大或太小”的异常了,同时,我也比较担忧着个性能的问题。

函数MiniDumpCallback

如果要自定义MINIDUMP类型标志无法访问的MINIDUMP的内容,可以使用MiniDumpCallback函数。这是一个用户定义的回调,当MiniDumpWriteDump需要用户决定是否将某些数据包含到minidump中时,它将被调用。借助此功能,我们可以完成以下任务:

  • 从minidump的模块信息中排除可执行模块(全部或部分)
  • 从minidump的线程信息中排除线程(全部或部分)
  • 将用户指定范围内存的内容包含到小型转储中

让我们看看MiniDumpCallback函数的声明

BOOL CALLBACK MiniDumpCallback(
PVOID CallbackParam,
constPMINIDUMP_CALLBACK_INPUT CallbackInput,
PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
);