2023年1月

一、什么是线程栈溢出

我们都知道,每一个win32线程都会开辟一个空间,用来临时存储线程执行时所调用的一系列函数的参数、返回地址和局部变量及其他上下文信息。这个空间就是线程的栈区。栈区的容量是有限的,在程序编译链接时,就固定下来了。通过VC++编译的程序,默认的栈区大小是1MB。当我们程序执行时,访问超过了这个空间的边界,就叫栈溢出,又叫Stack overflow。这时会产生代码为STATUS_STACK_OVERFLOW(0xC00000FD)的异常,从而导致程序崩溃。注意一定要与栈缓冲区溢出---STATUS_STACK_BUFFER_OVERRUN(0xC0000409)区别开来。

二、栈溢出的原因

栈溢出是用户模式线程可能会遇到的错误。 有三个可能的原因产生此错误:

  • 线程使用为其保留的整个堆栈。这通常是由无限递归引起的。

  • 线程无法扩展堆栈,因为页文件已最大化,因此无法提交其他页来扩展堆栈。

  • 由于系统内使用以扩展页面文件的短时间内,线程不能扩展堆栈。

当一个线程上运行的函数分配的本地变量时,变量放线程的调用堆栈上。 函数所需的堆栈空间量可能大至所有本地变量的大小的总和。 但是,编译器通常会执行优化,降低函数所需的堆栈空间。 例如,如果两个变量,则在不同的作用域中,编译器可以为这两个这些变量使用相同的堆栈内存。 编译器还可能无法完全消除某些本地变量对计算进行优化。优化的量会影响应用在生成时的编译器设置。 例如,调试版本和发布版本具有不同的优化级别。 所需的调试版本中的函数的堆栈空间量可能会大于该发行版中的相同函数所需的堆栈空间量。

在我们编写代码时,如下情况通常引发栈溢出:

  • 由于某些原因,导致函数递归深度很深或无穷递归
  • 在栈里分配了很大的缓冲区
  • 栈里的某缓冲区溢出

三、溢出代码举例

3.1、在栈里分配了很大的缓冲区导致溢出

代码如下:

#include "stdafx.h"

int  LargeBuffer(void)
{
    char buf[1024 * 1024];
    int a = 0;
    int b = buf[2];
    return a+b;
}


int _tmain(int argc, _TCHAR* argv[])
{
    int n=LargeBuffer();
    printf("n=%d\n", n);
    return 0;
}

3.2、函数递归调用引发的栈溢出

写一段最简单的无穷递归代码,如下:

#include "stdafx.h"
void f(void)
{
f();
}
int _tmain(int argc, _TCHAR*argv[])
{
f();
return 0;
}

现在很多的程序都是多语言混合编程的,比如我司的产品,就是用C++/.net clr混合编制的。那么当我们调试这样的程序时,一定要注意,比如有时我们只看到c++的栈和名称,而.net clr的代码确看不到。比如

那要怎样才能看到.net clr的代码和栈呢,下面简单讲一下。

一、SOS.DLL

SOS 调试扩展 (SOS.dll) 通过提供有关内部公共语言运行时 (CLR) 环境的信息,帮助你在 Visual Studio 和 Windows 调试器 (WinDbg.exe) 中调试托管程序。 此工具需要你启用项目的非托管调试。 SOS.dll 自动随 .NET Framework 一起安装。 若要在 Visual Studio 中使用 SOS.dll,请安装 Windows 驱动程序工具包 (WDK)。SOS 调试扩展允许你查看有关在 CLR 内运行的代码的信息。 例如,可以使用 SOS 调试扩展显示有关托管堆的信息、查找堆损坏情况、显示运行时所使用的内部数据类型以及查看有关在运行时内运行的所有托管代码的信息。还可以通过将 SOS 调试扩展加载到 WinDbg.exe 调试器中并在 WinDbg.exe 中执行命令来使用此扩展。在调试托管代码时,有两个扩展DLL需要注意,它们分别是SOS和SOSEX。

1.1、在Windbg里加载SOS

在使用SOS之前,第一步就是要加载SOS。若要将 SOS 调试扩展加载到 WinDbg.exe 调试器中,请在工具中运行以下命令:

.loadby sos clr

 .loadby sos clrjit

WinDbg.exe 和 Visual Studio 使用与当前使用的 Mscorwks.dll 版本对应的 SOS.dll 版本。 默认情况下,应使用与当前版本的 Mscorwks.dll 匹配的 SOS.dll 版本。若要使用在其他计算机上创建的转储文件,请确保该安装所附带的 Mscorwks.dll 文件存在于符号路径中,并加载相应的 SOS.dll 版本。

若要加载特定版本的 SOS.dll,请在 Windows 调试器中键入以下命令:.load <full path to sos.dll>

如果使用的.Net版本不同,那么还可以手动指定版本,如手动指定载入4.0版本命令:

.load C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/sos.dll
.load C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/sos.dll

一、WinDbg简介

WinDbg是微软发布的一款免费而十分强大的调试工具。既然是微软自己发布的调试工具,那它对微软产品的调试当然是十分的强大。Windows 调试器 (WinDbg) 可用于调试内核模式和用户模式代码,来分析故障转储,并检查代码时 CPU 寄存器执行,同时也是一款相当优秀的源码级(source-level)调试工具。相比较于Visual Studio,它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能,却比VS更为强大。

二、获取和安装

2.1、获取途径

可以将Windows调试工具作为开发工具包的一部分或作为独立工具集获取:

  • 作为WDK的一部分
    Windows的调试工具包含在Windows驱动程序工具包(WDK)中。Download the Windows Driver Kit (WDK).
  • 作为Windows SDK的一部分
    Windows软件开发工具包(SDK)中包含Windows调试工具。要下载安装程序或ISO映像,请参阅Windows开发人员中心上的Windows 10 SDK
  • 作为独立的工具集
    通过开始安装Windows SDK,然后在要安装的功能列表中仅选择Windows的调试工具(并清除所有其他功能的选择),可以在不安装Windows SDK或WDK的情况下单独安装Windows的调试工具。要下载安装程序或ISO映像,请参阅Windows开发人员中心上的Windows 10 SDK

2.2、安装

安装过程略

下面是安装后的截图

作为独立工具集安装后

作为SDK一部分安装后

启动后的界面

三、配置

安装好之后就需要配环境变量,打开环境变量配置界面 我的电脑->右键属性->高级系统设置->环境变量设置->在系统变量里找到path,把你的WinDbg安装目录写上去

设置好后,在命令行里输入windbg,如果能跑起来,则设置成功,然后再在系统变量中创建一个名为 _NT_SYMBOL_PATH,值为:SRV*F:\Debug_Symbol\Symbols32* http://msdl.microsoft.com/download/symbols

启动Windbg 打开一个exe看到这个说明配置成功

 

四、功能

Windbg是Microsoft公司免费调试器调试集合中的GUI的调试器,支持Source和Assembly两种模式的调试。Windbg不仅可以调试应用程序,还可以进行Kernel Debug。结合Microsoft的Symbol Server,可以获取系统符号文件,便于应用程序和内核的调试。Windbg支持的平台包括X86、IA64、AMD64。虽然windbg也提供图形界面操作,但它最强大的地方还是有着强大的调试命令,一般情况会结合GUI和命令行进行操作,常用的视图有:局部变量、全局变量、调用栈、线程、命令、寄存器、白板等。其中“命令”视图是默认打开的。

当我们启动windbg后,我们就能看到Windbg的样子了,如下:

本部分讨论 WinDbg 图形用户界面的元素。 这些元素包括以下各项:菜单、工具栏和快捷键。菜单有:文件菜单、编辑菜单、视图菜单、调试菜单、窗口菜单、帮助菜单。下面分别一 一简单介绍下。

一、菜单

1.1、文件菜单

  • 打开源文件
    加载特定的源文件。此命令相当于按 CTRL + O 或单击开放源代码文件 (Ctrl + O) 按钮 (开放源代码文件按钮的屏幕截图)。

    当您单击打开源文件,则打开源文件对话框随即出现。 若要打开一个文件,请执行以下操作:


    1. 在中查找列表中,选择该文件所在的目录。 默认情况下,选择上一次打开的目录。

    2. 在中类型的文件列表中,选择你想要打开的文件的类型。 仅使用所选扩展的文件将显示在打开源文件对话框。 请注意  还可以使用中的通配符模式文件名框,以显示仅具有特定扩展名的文件。 在更改之前的会话中保留新的通配符模式。 可以使用通配符模式,之间用分号分隔的任意组合。 例如,输入 *。非独占;*.H;*.CPP显示具有这些扩展名的所有文件。 最大的行中的字符数为 251。

    3. 如果找到该文件所需,双击文件名称,或单击文件名称并单击打开。若要放弃更改并关闭对话框,请单击取消。

    当你指向时显示在 WinDbg 中最近打开的四个文件的名称最近使用的文件上文件菜单。 若要打开这些文件之一,请单击其名称。
  • 关闭当前窗口

    单击关闭当前窗口上文件菜单关闭的活动的调试信息窗口。此命令相当于按下 CTRL + F4。此外可以通过单击关闭调试的信息窗口关闭在 WinDbg 窗口中信息窗口的右上角的按钮。

  • 打开可执行文件

    单击打开可执行文件上文件菜单以启动新的用户模式进程,并对其进行调试。此命令相当于按下 CTRL + E。 仅当 WinDbg 处于休眠模式时,可以使用此命令。

    当您单击打开可执行文件,则打开可执行文件对话框随即显示,并且您可以执行以下操作:


    • 输入中的可执行文件的完整路径文件名框。 或者,可以使用对话框的查找并选择适当的文件。 必须指定可执行文件的确切路径。 与 Microsoft Windows 不同运行对话框和命令提示符窗口,打开可执行对话框不会搜索当前路径的可执行文件的名称。

    • 如果你想要使用的可执行文件使用命令行参数,输入中对其自变量框。

    • 如果你想要更改的开始目录从默认目录中输入该目录路径开始目录框。

    • 如果你想要附加到任何的 WinDbg子进程(其他进程的原始目标进程启动),选择调试子进程也。


    进行选择后,单击打开。

    请注意  时使用此命令以打开源文件,该文件的路径自动追加到源路径。

    如果 WinDbg 连接到进程服务器,则无法使用打开可执行文件命令。

  • 附加到进程

    单击附加到进程上文件菜单调试当前正在运行的用户模式应用程序。此命令相当于按 F6。 仅当 WinDbg 处于休眠模式时,可以使用此命令。

    当您单击附加到进程,则附加到进程对话框随即显示,并且您可以执行以下操作:


    • 选择包含正确的进程 ID 和名称的行 (或输入中的进程 ID进程 ID框)。 请注意  列出的每个进程具有关联的加号 (+)。 可以单击加号以显示有关该进程的命令行、 服务和子进程的信息。

      请注意  如果 WinDbg 连接到进程服务器附加到进程对话框将显示在远程计算机运行的进程。 进程服务器的详细信息,请参阅激活智能客户端。

    • 如果你想要 noninvasively(非侵入) 附加到进程,请选择Noninvasive复选框。


    进行选择后,单击确定。

  •  打开故障转储文件

    单击打开故障转储文件菜单以打开用户模式或内核模式崩溃转储文件,并对其进行分析。此命令相当于按下 CTRL + D。 仅当 WinDbg 处于休眠模式时,可以使用此命令。

    当您单击打开崩溃转储,则打开崩溃转储对话框随即出现。 输入中的故障转储文件的完整路径文件名框中,或使用查找列表以查找并选择正确的路径和文件名称。 (转储文件通常具有.dmp 或.mdmp 扩展名结尾)。

    选择适当的文件后,单击打开。

  • 连接到远程会话

    单击连接到远程会话上文件菜单使 WinDbg 调试客户端并连接到活动的调试服务器。此命令相当于按下 CTRL + R。 仅当 WinDbg 处于休眠模式时,可以使用此命令。不能使用此命令连接到进程服务器或 KD 连接服务器。为此,使用文件 |连接到远程存根 (stub)相反。

    当您单击连接到远程会话,则连接到远程调试器会话对话框随即出现。 可以使用此对话框中,输入远程连接参数,或若要浏览的调试服务器列表。

    若要手动指定远程连接参数,请输入中的以下字符串之一连接字符串框:

    text