分类 调试 下的文章

使用线程和进程是调试的重要组成部分。 Visual Studio 提供一个界面,在此界面中,可连接正在运行的待调试进程,可查看在那些进程中运行的附加进程和线程的有关信息,还可以控制正在调试的线程和进程的执行。

在计算机科学中,“线程”和“进程”是两个相关的概念。 二者都表示必须按特定顺序执行的指令序列。 但是不同线程或进程中的指令可以并行执行。

进程存在于操作系统内,并对应于用户可看作为程序或应用程序的事物。 另一方面,线程存在于进程内。 因此,线程有时也称作“轻量进程”。 每个进程都由一个或多个线程组成。

多个进程的存在使得计算机能够一次执行多个任务。 而多个线程的存在使得进程能够分解工作以便并行执行。 在多处理器计算机上,进程或线程可以在不同的处理器中运行。 这使得真正的并行处理成为可能。

并不总是能够完全并行处理。 有时候必须要同步线程。 一个线程可能必须等待另一个线程的结果,或者一个线程可能需要独占访问另一个线程正在使用的资源。 同步问题是多线程应用程序中出现 Bug 的一个常见原因。 有时候线程可能最终等待的是永远不会变得可用的资源。 这导致了一种称为“死锁”的状况。

Visual Studio 调试器为调试线程和进程提供了功能强大但易于使用的工具。

线程和进程

在 Visual Studio 中用于处理进程的主要工具有“附加到进程”对话框、“进程”窗口和“调试位置”工具栏。 用于调试线程的主要工具有“线程”窗口、源窗口中的线程标记和“调试位置”工具栏。调试多线程应用程序的主要工具是“并行堆栈”和“并行任务”窗口。

下面的表显示了可用信息以及可在以上每个位置执行的操作:

用户界面

可用信息

可以执行的操作

“附加到进程”对话框

可以附加的可用进程:


  • 进程名 (.exe)

  • 进程 ID 号

  • 菜单栏标题

  • 类型(托管 v4.0;托管 v2.0、v1.1、v1.0;x86;x64;IA64)

  • 用户名(帐户名)

  • 会话号

选择要附加的进程

选择远程计算机

更改用于连接远程计算机的传输类型

“进程”窗口

附加的进程:


  • 进程名

  • 进程 ID 号

  • 进程 .exe 的路径

  • 菜单栏标题

  • 状态(中断 运行)

  • 调试(本机、托管等。)

  • 传输类型(默认、无身份验证时仅限本机)

  • 传输限定符(远程计算机)

工具:


  • 附加

  • 分离

  • 终止


快捷菜单:


  • 附加

  • 分离

  • 调试停止时分离

  • 终止

“线程”窗口

当前进程中的线程:


  • 线程 ID

  • 托管 ID

  • 类别(主线程、接口线程、远程过程调用处理程序或辅助线程)

  • 线程名

  • 创建线程的位置

  • 优先级

  • 关联掩码

  • 挂起项计数

  • 进程名

  • 标志指示器

  • 挂起指示器

工具:


  • 搜索

  • 搜索调用堆栈

  • 标记“仅我的代码”

  • 标记自定义模块选择

  • 分组依据

  • Columns

  • 展开/折叠调用堆栈

  • 展开/折叠组

  • 冻结/解冻线程


快捷菜单:


  • 在源中显示线程

  • 切换到线程

  • 冻结一个正在运行的线程

  • 解冻冻结的线程

  • 标记一个线程以便进一步研究

  • 取消标记一个线程

  • 重命名一个线程

  • 显示和隐藏线程


其他操作:


  • 查看数据提示中一个线程的调用堆栈

源窗口

左侧滚动条槽中的线程指示符指示单线程或多线程(默认情况下处于关闭状态,可通过使用“线程”窗口中的快捷菜单打开)

快捷菜单:


  • 切换到线程

  • 标记一个线程以便进一步研究

  • 取消标记一个线程

“调试位置”工具栏

  • 当前进程

  • 当前线程

  • 当前堆栈帧

  • 切换到另一个进程

  • 切换到当前进程中的另一个线程

  • 切换到当前线程中的另一个堆栈帧

  • 标记或取消标记当前线程

  • 仅显示标记的线程

“并行堆栈”窗口

  • 一个窗口中多个线程的调用堆栈。

  • 每个线程的活动堆栈帧。

  • 任何方法的调用方和被调用方。

  • 筛选出指定的线程

  • 切换到“并行任务”视图

  • 标记或取消标记线程

  • 缩放

“并行任务”窗口

  • 查看有关 Task 对象的信息,包括任务 ID、任务状态(已计划、正在运行、正在等待和已死锁)以及分配给任务的线程。

  • 调用堆栈中的当前位置。

  • 在创建时传递给任务的委托

  • 切换到当前任务

  • 标记或取消标记任务

  • 冻结或解冻任务

C++ EH Exception是Windows系统VC++里对c++语言的throw的分类和定义,它的代码就是0xe06d7363。在VC++里其本质也是SEH结构化异常机制。在我们分析用户崩溃的例子中经常会遇到它。一般情况下,遇到它,是我们代码里用throw抛出异常后没有处理导致程序崩溃。下面分析一下它的原理。

我们借助一段代码来跟踪和分析

classMyException
{
public:intnErr;char *szMessage;public:
MyException(
void)
:nErr(
0)
, szMessage(NULL)
{

}

MyException(
int nerr,char *szMess)
:nErr(nerr)
, szMessage(szMess)
{

}
~MyException(void)
{

}
};
int _tmain(int argc, _TCHAR*argv[])
{
try
    {
        MyException me(1, "test exception");
        throw me;
    }
    catch (MyException me1)
    {
        printf("err=%s\n",me1.szMessage);
    }
}

Visual Studio 调试器提供了功能强大的命令来控制应用程序的执行。 下面描述了为控制执行利用调试器命令可执行的任务:

  • 开始(或继续)执行

  • 中断执行

  • 停止执行

  • 逐句通过应用程序

  • 运行到指定的位置

  • 设置执行点

开始执行

开始执行是最基本的调试功能之一。

  1. 在“调试”菜单上选择“开始”、“逐语句”或“逐过程”。

    - 或 -

  2. 在源窗口中,右击可执行代码中的某行,然后选择“运行到光标处”。

如果选择“启动”,则应用程序启动并一直运行到断点。 可以在任何时刻中断执行,以检查值,修改变量,或检查程序状态。

若选择了“逐语句”或“逐过程”,应用程序会启动并执行,然后在第一行中断。如果选择“运行到光标处”,则应用程序启动并一直运行到断点或光标位置,具体看是断点在前还是光标在前。 可以在源窗口中设置光标位置。 某些情况下,不出现中断。 这意味着执行始终未到达设置光标处的代码。

解决方案可能包含不止一个项目。 这时候,可以选择将由“调试”菜单执行命令启动的启动项目。 或者,也可以从“解决方案资源管理器”启动选定的项目。可以使用“调试”菜单上的“开始执行(不调试)”命令,在不使用调试器的情况下开始执行项目。

 

 

中断执行

当用 Visual Studio 调试器调试应用程序时,应用程序或者正在运行(执行)或处于中断模式。 大多数调试器功能(比如在**“监视”**窗口中计算表达式)只在中断模式下可用。当执行到达一个断点或发生异常,调试器将中断程序的执行。 您可以随时手动中断执行。 如果在执行没有相应源的代码时中断,将能够在“反汇编”窗口中进行调试。

手动中断程序执行

  • 在“调试”菜单上,单击“全部中断”。

    调试器将停止所有在调试器下运行的程序的执行。 程序并不退出,可以随时恢复执行。 调试器和应用程序现在处于中断模式。

如果正在调试多个程序,则默认情况下,断点或“全部中断”命令将影响所有被调试的程序。 如果想仅中断当前程序,可以更改该默认值。

更改调试多个程序时的中断行为

  1. 在“工具”菜单上,单击“选项”。

  2. 在“选项”对话框中,选择“调试”文件夹,并单击“常规”类别。

  3. 切换“一个进程中断时则中断所有进程”。

  4. 单击“确定”。

 

 

停止调试或停止执行

停止调试意味着终止调试会话。 停止执行意味着终止正调试的进程并结束调试会话。 请勿与中断执行混淆,后者意味着暂停正在调试的进程的执行但调试会话仍处于活动状态。

停止调试

  • 在“调试”菜单上,选择“停止调试”。

如果程序是从 Visual Studio 启动的,则**“停止调试”终止正调试的进程。 如果程序附加到进程,而不是从 Visual Studio 启动的,则该进程仍继续运行。 如果要终止被附加的进程,可以通过“进程窗口”终止单个进程,或者通过“全部终止”**命令终止所有被附加的进程。

终止所有正调试的被附加进程

  • 在“调试”菜单上选择“全部终止”。

如果要停止当前正在调试的运行并立刻开始新运行,可以使用**“重新启动”**命令。

停止调试并重新启动

  • 在“调试”菜单上选择“重新启动”。

“重新启动”**停止当前正在调试的会话,并重启启动项目。

如果退出正在调试的应用程序,调试将自动停止。 如果正在调试多个程序,调试将继续进行,直到退出了最后一个程序。 如果调试的是由其他应用程序承载的项目(如由 Microsoft Internet Explorer 承载的 Web 项目),若退出宿主应用程序(如 Microsoft Internet Explorer),调试将停止。

在 Visual Basic 和 C# 中,如果正在调试一个 Web 服务,而使用该服务的客户端应用程序被终止了,则 Web 服务的调试也会停止。

单步执行

“单步执行”是最常见的调试过程之一。 “单步执行”即一次执行一行代码。

“调试”菜单提供了三个逐句通过代码的命令:

  • 逐语句

  • 逐过程

  • 跳出

“逐语句”和“逐过程”的差异仅在于它们处理函数调用的方式不同。 这两个命令都指示调试器执行下一行的代码。 如果某一行包含函数调用,“逐语句”仅执行调用本身,然后在函数内的第一个代码行处停止。 而“逐过程”执行整个函数,然后在函数外的第一行处停止。 如果要查看函数调用的内容,请使用“逐语句”。 若要避免单步执行函数,请使用“逐过程”。

在嵌套函数调用上,“逐语句”将进入并单步执行嵌套最深的函数。 如果对类似 Func1(Func2()) 的调用使用“逐语句”,调试器将进入并单步执行函数 Func2。如果要进入并单步执行特定的嵌套函数,请使用快捷菜单中的“单步执行特定函数”命令。 位于函数调用的内部并想返回到调用函数时,请使用“跳出”**。 “跳出”将一直执行代码,直到函数返回,然后在调用函数中的返回点处中断。

运行到指定位置

有时,在调试过程中,您想执行到代码中的某一点,然后中断。 如果在要中断的位置设置了断点,则可以实现上述目的。

运行到已设置断点的指定位置

  • 在“调试”菜单上,单击“启动”或“继续”。

    您其实不需要在任何情况下都设置断点。 Visual Studio 调试器提供特定命令以运行到光标位置或运行到指定的函数。

设置下一语句

在 Visual Studio 调试器中,可以移动执行点来设置要执行的下一条代码语句。 源窗口或“反汇编”窗口的空白区域中的黄色箭头标记要执行的下一条语句的位置。 通过移动此箭头,可以跳过部分代码或返回到以前执行过的行。 在某些情况下可以使用此方法,例如,跳过包含已知 bug 的代码段。注意:设置下一条语句将导致程序计数器直接跳到新位置。 使用此命令时要小心以下情况:

  • 不执行旧执行点和新执行点之间的指令。

  • 如果向后移动执行点,则不撤消插入的指令。

  • 将下一条语句移动到另一个函数或范围通常会导致调用堆栈损坏,导致一个运行时错误或异常。 如果尝试将下一条语句移动到另一个范围,则调试器将打开一个含有警告的对话框,并提供一个取消该操作的机会。 在 Visual Basic 中,不能将下一条语句移动到另一个范围或函数。

  • 在本机 C++ 中,如果已启用运行时检查,则设置下一条语句会导致执行到达方法的结尾时引发异常。

  • 当启用“编辑并继续”时,如果您做出了“编辑并继续”无法立即重新映射的编辑,则“设置下一语句”将失败。 例如,如果您编辑了 catch 块中的代码,将发生这种情况。 发生这种情况时,您将看到类似如下的错误消息:“无法将下一语句设置到此位置。不支持操作。未知错误: 错误号”

在托管代码中,在以下情况下不能移动下一条语句:

  • 下一条语句与当前语句不在同一个方法中。

  • 使用实时调试启动调试。

  • 正在展开一个调用堆栈。

  • 已引发一个 System.StackOverflowException 或 System.Threading.ThreadAbortException 异常。

应用程序处于活动运行状态时不能设置下一条语句。 要设置下一语句,调试器必须处于中断模式。

设置要执行的下一语句

  • 在源窗口中,单击黄色箭头,要将下一条语句设置到哪个位置,就将箭头拖到该位置,该位置应在同一源文件中

    - 或 -

  • 在源窗口中,右击要执行的下一条语句,然后选择“设置下一语句”。

  • 在“反汇编”窗口中,右击要执行的下一条汇编语言指令,然后选择“设置下一语句”。

异常是在程序运行时发生的不正常情况。 异常通常表示有需要调试的问题。 发生异常时,调试器将向“输出”窗口中写入一条异常消息,但在““选项”对话框 ->“调试”->“常规””对话框中禁用了该选项的情况除外。

发生异常时,调试器不一定会中断执行。

  • 如果发生了非 ASP.NET 异常并且没有进行处理,调试器总是会中断执行。

  • 您可以让调试器在引发异常时立即中断执行(在调用任何处理程序之前)。

  • 如果使用 如何:单步执行“仅我的代码” 进行调试,您还有第三个选项。 您可以让调试器在遇到任何未由用户代码(“我的代码”)中的处理程序处理的异常时中断执行。 有关更多信息,请参见如何:在遇到用户未经处理的异常时中断。

  • ASP.NET 有一个顶级异常处理程序,该处理程序在对异常进行处理时向浏览器用户显示错误页面。 该顶级异常处理程序会阻止未经处理的异常中断到调试器中,除非打开了“仅我的代码”。 请确保对 ASP.NET 调试启用“仅我的代码”。

    请记住,如果发生了异常但根本没有进行处理,调试器总是会中断执行。 用户未处理的设置不会更改这一行为。

Visual Studio 调试器识别下列类别的异常:

  • C++ 异常

  • 公共语言运行时异常

  • 托管调试助手

  • 本机运行时检查

  • Win32 异常

大多数异常都有相应的处理程序,用于在异常发生时做出响应。 这样程序便有可能从异常状况中恢复过来。 本机运行时检查没有处理程序。在 Visual Basic 应用程序中,调试器将所有错误都表示为异常,即使使用 On Error 样式的错误处理程序。对于 Visual Basic 和 C#,调试器现在具备了一项新增的异常助手功能,此功能可在发生异常时提供更多信息。

在引发异常时中断

调试器可以在发生异常时立即中断应用程序的执行,使您有机会在调用处理程序之前对异常进行调试。

如果您在启用 如何:单步执行“仅我的代码” 的情况下进行调试,行为会略有不同。 启用“仅我的代码”时,调试器将忽略在“我的代码”以外引发并且不通过“我的代码”的最可能的公共语言运行时 (CLR) 异常。 但是,如果该异常完全未进行处理,调试器将始终中断。

如果将调试器设置为在引发 CLR 异常时中断且调试器在发生 CLR 异常时中断,则某些情况下调试器突出显示的行可能会稍有偏差。 例如,如果从托管代码的 if 语句内部引发异常,就可能发生这种情况。 调试器突出显示要执行的下一个 CLR 指令所在的行,即 throw 之后的行,而不是 throw 语句所在的行。

默认情况下,“异常”对话框列出每一类别中最常见的异常。 您可以添加自己的异常和删除所添加的异常。 Visual Studio 将添加的异常的列表与解决方案数据保存在一起,这样在下一次打开和运行项目时这些异常将可用。

设置在引发异常时中断执行

  1. 在“调试->窗口”菜单中,单击“异常”。

  2. 在“异常”对话框中,为整个类别的异常(如“公共语言运行时异常”)选择“引发”。

    - 或 -

    展开一个类别的异常(如“公共语言运行时异常”)的节点,并为该类别中的特定异常选择“引发”。

在“调试”菜单中添加“异常”命令

  1. 在“工具”菜单上,单击“自定义”。

    出现“自定义”对话框。

  2. 单击“命令”选项卡,在“菜单栏”列表中,单击“调试”。

  3. 单击“添加命令”。

  4. 在“添加命令”对话框的“类别”中,单击“调试”。

  5. 在“命令”中,单击“异常设置”,然后单击“确定”。

  6. (可选)可以单击“下移”以调整“异常”命令在“调试”菜单中的位置。

  7. 单击“关闭”。

在遇到用户未经处理的异常时中断

如果使用 如何:单步执行“仅我的代码” 调试,可以让调试器在发生任何没有由用户代码(“我的代码”)中的处理程序进行处理的异常时中断。 下面的过程显示了如何使用“异常”对话框来确定要在发生哪些用户未经处理的异常时中断。

默认情况下,“异常”对话框列出每一类别中最常见的异常。 您可以添加自己的异常和删除所添加的异常。 Visual Studio 将添加的异常的列表与解决方案数据保存在一起,这样在下一次打开和运行项目时这些异常将可用。

  1. 在“调试”菜单中,单击“异常设置”。

  2. 在“异常”对话框中,为整个类别的异常(如“公共语言运行时异常”)选择“用户未处理的”。

    - 或 -

    展开某种异常类别(如“公共语言运行时异常”)的节点,并为该类别中的特定异常选择“用户未处理的”。

  3. 单击“确定”。

在出现异常之后继续执行

由于出现异常而执行调试器中断时,会显示一个对话框。 对于 Visual Basic 或 C#,在默认情况下,您将看到异常助手对话框。 对于 C++,您将看到早期的 “异常” 对话框。 如果您使用的是 Visual Basic 或 C#,但在“选项”对话框中禁用了“异常助手”,您将看到“异常”对话框。

出现“异常助手”或“异常”对话框时,可尝试对导致异常的问题进行修复。

托管代码

在托管代码中,您可以在出现了未经处理的异常后在同一线程内继续执行。 “异常助手”将调用堆栈回退到引发异常的点。

本机代码

在本机 C/C++ 中,您有两个选项:

  • 您可以单击“中断”并尝试修复问题。 在中断模式下,可右击“调用堆栈”窗口中的帧并选择快捷菜单中的“展开到此帧”来展开调用堆栈。 如果未能修复问题,则继续调试时,“异常”对话框将再次显示。 否则,“异常”对话框将不会再次出现。

  • 您可以单击“继续”继续执行,而不尝试修复问题。 “异常”对话框随即重新出现。

混合模式

如果在调试本机和托管混合的代码时遇到未经处理的异常,操作系统约束将会阻止调用堆栈展开。 如果尝试使用快捷菜单来展开调用堆栈,则会出现一个错误消息,告诉您在混合代码调试期间,调试器无法在异常未得到处理的情况下展开调用堆栈。

在发生异常后检查系统代码

发生异常时,您可能需要检查系统调用内部的代码,以确定该异常的起因。 如果您没有为系统代码加载符号,或者启用了“仅我的代码”,则下面的步骤说明了如何执行此操作。

在发生异常后检查系统代码

  1. 在“调用堆栈”窗口中右击,然后单击“显示外部代码”。

    如果未启用“仅我的代码”,则快捷菜单中不提供此选项,默认情况下显示系统代码。

  2. 右击此时显示在“调用堆栈”窗口中的外部代码帧。

  3. 指向“加载符号”,然后单击“Microsoft 符号服务器”。


    1. 如果启用了“仅我的代码”,则将显示一个对话框。 它指出“仅我的代码”现在已禁用。 要单步执行系统调用,必须这样做。

    2. 将出现“正在下载公共符号”对话框。 下载完毕后会自动关闭该对话框。

  4. 现在即可在“调用堆栈”窗口和其他窗口中检查系统代码。 例如,您可以双击调用堆栈帧在源窗口或“反汇编”窗口中查看代码。

使用本机运行时检查

在 Visual C++ 中,可以使用本机 runtime_checks 捕捉常见的运行时错误,例如:

  • 堆栈指针损坏。

  • 本地数组溢出。

  • 堆栈损坏。

  • 未初始化的局部变量上的依赖项。

  • 较短变量赋值的数据丢失。

如果使用带有优化 (/O) 版本的 /RTC,将导致编译器错误。 如果在优化版本中使用 runtime_checks 杂注,则该杂注无效。

调试启用了运行时检查的程序时,如果出现运行时错误,该程序的默认操作是停止并切换到调试器。 可以更改任何运行时检查的此默认行为。 有关更多信息,请参见 异常处理(调试)。

在调试版本中启用本机运行时检查

  • 使用 /RTC 选项,并与 C 运行库(如 /MDd)调试版链接。

使用无 C 运行库的运行时检查

如果链接程序而不链接 C 运行库(使用 /NODEFAULTLIB)并希望使用运行时检查,则必须链接 RunTmChk.lib。

_RTC_Initialize 为运行时检查初始化程序。 如果未链接 C 运行库,必须在调用 _RTC_Initialize 之前检查是否用运行时错误检查编译了程序: