分类 调试 下的文章

现在很多的程序都是多语言混合编程的,比如我司的产品,就是用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

加载DLL 的时候断

  • sxe ld:[dll]

    比如:

    sxe ld:wininet.dll  (在wininet.dll 被装载的时候断点)

    这里DLL名字是支持通配符的

    比如:

    sxe ld:wini*.dll

  • 也可以在事件过滤器里设置

卸载DLL 的时候断

  • sxe ud:[dll]
  • 也可以在事件过滤器里设置

直接在DllMain下断

bu wininet!DllMain

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

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

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

1. 下载dmp文件所有相关模块的symbols,缓存到共享路径,便于其它人快速下载
symchk.exe
/id c:\MyApplication.dmp /s
SRV*\\symbols_server\WinSymbols\*http://msdl.microsoft.com/download/symbols

2. 下载某个已运行进程所有相关模块的symbols,缓存到共享路径,便于其它人快速下载
symchk.exe /ie qq.exe /s SRV*\\symbols_server\WinSymbols\*http://msdl.microsoft.com/download/symbols

3. 下载某个exe/dll文件对应的symbols,例如user32.dll,输入命令行:
symchk.exe c:\windows\system32\user32.dll /s SRV*c:\symbols\*http://msdl.microsoft.com/download/symbols

4. 下载整个目录下(例如system32)所有模块的symbols:
symchk.exe /r c:\windows\system32\ /s SRV*c:\symbols\*http://msdl.microsoft.com/download/symbols

一、什么是线程栈溢出

我们都知道,每一个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;
}