使用WinDbg —— .NET篇 (一)

时间:2022-07-17 23:20:55

转载请注明出处:http://blog.csdn.net/ecjtu_luowei/article/details/43974727

一、前言

WinDBG作为Microsoft的御用工具,其强大之处使我这等小辈难以望其项背,它设计了极其丰富的功能来支持各种调试任务,包括用户态调试、内核态调试、dump文件调试、远程调试等。其灵活性和可扩展性能极大满足调试要求。所以对于WinDBG,读者非常有必要花费一些业余时间来学习研究。

本文主要是通过调试C#编写的NET程序来对WinDBG作一个片面的介绍,由于笔者真正开始使用Windbg的时间并不长,所以文中难免有许多谬误之处,如在阅读本文过程中遇到问题或错误,可直接联系笔者(sbcd3760@gmail.com)。

二、概述

尽管Windbg是个GUI程序,但是大部分工作都是通过命令的方式。Windbg共支持三种命令:标准命令、元命令、和扩展命令。在接下来的文章中,这三种命令都会有所涉及。

标准命令往往都非常简单,通常一两个字符或符号,用来提供使用各种调试目标的最基本调试功能。如:gtpq等。

元命令用来提供标准命令没有提供的调试功能,元命令和标准命令都是内建在调试器或Windbg程序文件中。

扩展命令用于扩展某一方面的调试功能。扩展命令是通过加载第三方的DLL来实现调试任务的。调试net程序大部分使用的都是扩展命令,主要的扩展dll就是SOS.dllSOSEX.dll

这三种命令有个明显的区分方式就是元命令都是以点(.)开始,扩展命令通常以感叹号(!)开始,如果一个有效的命令既不是扩展命令也不是元命令那么它就是标准命令了。

三、开始调试

3.1 调试符号(Symbol

看大部分Windbg的基础教程和Windbg的官网首页上都首先提到Symbol路径的设置,Symbol其实主要起一个辅助作用,在调试分析的过程中对内存中的机器代码翻译成对应的源代码的信息或是Win32 API的信息,如函数名,数据结构名,变量名、机器指令对应的源文件名和行号等。一般我们在组建编译文件的时候生成exedll文件的同时也会生成一个对应的pdb文件,这个文件就包含了exedll的辅助信息。

是否加载Symbol的区别如下:

没有加载Symbol

0:000> u D20108

*** ERROR: Module load completed but symbols could not be loaded for notepad.exe

notepad+0x108:

00d20108 8936            mov     dword ptr [esi],esi

00d2010a 0000            add     byte ptr [eax],al

00d2010c 0010            add     byte ptr [eax],dl

00d2010e 0000            add     byte ptr [eax],al

00d20110 00c0            add     al,al

00d20112 0000            add     byte ptr [eax],al

00d20114 0000            add     byte ptr [eax],al

00d20116 d200            rol     byte ptr [eax],cl

 

加载Symbol

0:000> u D20108

notepad!_imp__RegSetValueExW <PERF> (notepad+0x108):

00d20108 8936            mov     dword ptr [esi],esi

notepad!_imp__RegSetValueExW <PERF> (notepad+0x10a):

00d2010a 0000            add     byte ptr [eax],al

notepad!_imp__RegSetValueExW <PERF> (notepad+0x10c):

00d2010c 0010            add     byte ptr [eax],dl

notepad!_imp__RegSetValueExW <PERF> (notepad+0x10e):

00d2010e 0000            add     byte ptr [eax],al

notepad!_imp__RegSetValueExW <PERF> (notepad+0x110):

00d20110 00c0            add     al,al

notepad!_imp__RegSetValueExW <PERF> (notepad+0x112):

00d20112 0000            add     byte ptr [eax],al

notepad!_imp__RegSetValueExW <PERF> (notepad+0x114):

00d20114 0000            add     byte ptr [eax],al

notepad!_imp__RegSetValueExW <PERF> (notepad+0x116):

00d20116 d200            rol     byte ptr [eax],cl

 

如果Symbol文件和对应的dllexe在同一目录下,那么在调试的过程中,当dllexe文件被加载的时候,对应的Symbol文件也会自动加载,所以一般情形下我们不需要设置Symbol路径。如果pdb文件和dll文件不再同一目录下,我们也可以手动的设置Symbol的路径。一般设置Symbol的方式有两种:

1. 通过菜单设置

使用WinDbg —— .NET篇 (一)使用WinDbg —— .NET篇 (一) 

2. 通过命令设置:

0:004> .sympath d:\symbol

Symbol search path is: d:\symbol

Expanded Symbol search path is: d:\symbol

 

************* Symbol Path validation summary **************

Response                         Time (ms)     Location

OK                                             d:\symbol

0:004> .reload

Reloading current modules

........................

注意:设置了Symbol路径需要重新加载,否则设置的Symbol路径无效。

Symbol路径的设置同样可以指定多个路径,多个路径通过分号(;)隔开, 例如:

使用WinDbg —— .NET篇 (一) 

0:004> .sympath+ d:\symbol2

Symbol search path is: d:\symbol;d:\symbol2

Expanded Symbol search path is: d:\symbol;d:\symbol2

 

************* Symbol Path validation summary **************

Response                         Time (ms)     Location

OK                                             d:\symbol

OK                                             d:\symbol2

Symbol路径还可以设置连到server路径,需要的时候从server上下载(前提是server有你需要的Symbol)。最有名的Symbol server就是Microsofthttp://msdl.microsoft.com/download/symbols,但是Server上含有的Symbol大都是一些Common的调试符号,Microsoft的这个Symbol Server 使用于驱动开发程序的Debug或者对Win API的分析,于NET程序的调试帮助有限,所以这里不多讲,详细配置可参考Windbg的官网。

3.2 调试目标

不同的调试环境决定了对目标进行调试的局限性,根据调试目标和调试环境的不同,一般建立调试会话分为4种:

1. 启动调试目标程序

2. 附加(attach)已经启动的程序

3. 调试Dump文件

4. 远程调试

 

1. 启动调试目标程序

第一种是非常常见的,就像我们用vs写完了程序直接F5一样,建立这样的调试环境也很简单。打开Windbg后通过快捷键Ctrl + E或者通过菜单:

使用WinDbg —— .NET篇 (一) 

启动一个NET程序后能看到:

Microsoft (R) Windows Debugger Version 6.3.9600.16384 X86

Copyright (c) Microsoft Corporation. All rights reserved.

 

CommandLine: D:\Code\Ext1\Ext1\bin\Debug\Ext1.exe

Symbol search path is: *** Invalid ***

****************************************************************************

* Symbol loading may be unreliable without a symbol search path.           *

* Use .symfix to have the debugger choose a symbol path.                   *

* After setting your symbol path, use .reload to refresh symbol locations. *

****************************************************************************

Executable search path is: 

ModLoad: 00360000 00368000   Ext1.exe

ModLoad: 77b60000 77ce0000   ntdll.dll

ModLoad: 73570000 735ba000   C:\Windows\SysWOW64\MSCOREE.DLL

ModLoad: 75720000 75830000   C:\Windows\syswow64\KERNEL32.dll

ModLoad: 768f0000 76936000   C:\Windows\syswow64\KERNELBASE.dll

(4e8.1060): Break instruction exception - code 80000003 (first chance)

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll - 

eax=00000000 ebx=00000000 ecx=693b0000 edx=0011e0f8 esi=fffffffe edi=00000000

eip=77c00f3b esp=002af8a4 ebp=002af8d0 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!LdrVerifyImageMatchesChecksum+0x96c:

77c00f3b cc              int     3

最后一行执行的是INT 3,这是个软中断指令,机器码是CCh,所以也被称为CC指令。当调试目标程序执行INT 3时会进入异常处理,这个时候调试器就会捕获这个异常,从而停在这个地方。这就是下断点的方式,当然这次中断并不是用户所下的断点,而是当Windbg调试器在启动一个被调试进程的时候,会自动注入一个中断指令,在下面要讲到的Attach一个进程的后也会自动注入一个中断指令,这样做的目的是便于用户通过调试器分析调试目标。当目标程序在运行的时候,我们可以通过快捷键Ctrl + Break的方式注入一个中断指令或者通过菜单进行设置:

使用WinDbg —— .NET篇 (一) 

(4e8.c90): Break instruction exception - code 80000003 (first chance)

eax=7ef9c000 ebx=00000000 ecx=00000000 edx=77bff7ea esi=00000000 edi=00000000

eip=77b7000c esp=04bfff0c ebp=04bfff38 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!DbgBreakPoint:

77b7000c cc              int     3

需要注意的是,这种方式建立的调试会话都是非入侵式的,这意味着当你关掉Windbg的时候,被调试目标也会随着关掉。为了避免调试目标被关掉可以通过Menu -> Debug -> Detach Debuggee或者输入命令qd

使用WinDbg —— .NET篇 (一) 

2. 附加(attach)已经启动的程序

如果一个程序已经在运行,那么这时候可以使用附加进程的方式建立调试会话。可以通过快捷键F6或者通过Menu -> File -> Attach to a Process

使用WinDbg —— .NET篇 (一) 

注意上面的弹出窗口,在底下有个Noninvasive,这个checkbox就决定了当前的调试会话是否是入侵式的,如果不check,那么就是入侵式的,这个时候关掉调试器,调试目标继续运行。对于非入侵式的调试会话怎么避免调试目标的关闭,可以参考本文的上一小节。

3. 调试Dump文件

很多情况,bug的发现都是在客户的环境上,这个时候我们不可能让客户等着我们去帮他们去调试当时的环境,这个时候我们可以要求客户在出问题的时候创建一个dump文件给我们分析,然后我们拿到dump文件,就可以通过分析这个dump找到对应的问题。

Dump文件主要是将内存中的内容储存起来的物理文件,根据储存的不同内存段,可以将dump文件分为内核模式dumpKernel-mode dump)和用户模式dumpUser-mode dump)。其中这两大模式的Dump也有不同类型,其中用户模式dump又可以分为完全dumpFull User-Mode Dump)和迷你dumpMinidump),通常我们用到的都是用户模式的minidump,因为minidump远远小于full dump,便于客户传给我们分析,同时分析minidump足以让我们分析出问题。

对于内核模式和用户模式的dump,看名字就能知道这两种dump的区别,在这里不多说。Full dumpminidump的区别在于full dump包含了所有用户空间的内存,同时还包括了虚拟内存,minidump只包含目标进程相关的内存。所以在这里只介绍创建minidump的命令:

0:007> .dump /ma d:\1.dmp

Creating d:\1.dmp - mini user dump

GenInvokeEnumStackProviders(C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll) failed, 0x8007007f

Dump successfully written

创建dump的命令是“.dump”, /ma是选项,表示创建minidumpd:\1.dump是参数,表示创建dump的文件名。

当我们拿到dump之后可以直接在Windbg中打开这个dump文件,通过Menu -> File -> Open Crash Dump 。值得一提的是,dump文件是创建dump时的内存镜像,所以我们不能指望还可以执行调试目标,来看怎么重现的问题。

4. 远程调试

远程调试的方法有很多,常用的有四种:

a. 通过调试器构建远程调试会话

b. 通过Remote.exe程序

c. 激活一个进程服务器(dbgsrv.exe

d. 激活Repeaterdbengprx.exe

(a)是最常用的,也是笔者下面要讲的。(b)只适用于控制台的调试器, 如KDCDBNTSD,不能被Windbg利用,这种方式支持批处理命令操作,非常方便。(c)有一定的局限性,就是只能调试live的程序,不能调试dump文件。(d)这种方式是构建一个轻量级的协议服务器,这种方式用的人也不多,我也不是很了解,只是让大家知道有这么种方式。接下来我主要讲(a)方式,其他方式读者若有兴趣可以自行google了。

既然是远程调试,那么一定有个Server(调试目标所在机器),一个Client(调试器所在机器)。通过调试器构建远程调试会话的前提条件是两台机器都装有Windbg,而且是同一版本的。两台机器的联系可以通过不同的协议连接,如tcp/pipe/串口或并口等,至于具体使用什么样的协议取决两台机器的网络环境和安全性的要求。

通过调试器构建远程调试会话一般需要两个步骤:

a. 激活调试服务器

b. 激活调试客户端

在这里我以tcp协议为例:

步骤a:首先在程序要运行的机器或dump文件所在的机器(即调试服务器)上通过Windbg构建调试会话。然后输入命令:“.server tcp:port=1024”,如果端口号被占了,可以使用49152-65535的端口号,输入命令会看到如下,点击允许访问。

使用WinDbg —— .NET篇 (一) 

0:004> .server tcp:port=1024

Server started.  Client can connect with any of these command lines

0: <debugger> -remote tcp:Port=1024,Server=WIN-K584J5H3VLQ

步骤b:在步骤a完成并没问题之后,在Client上启动Windbg,点击Menu -> File -> Connect to Remote Session,这时候有个Connect to Remote Debug Session弹出框:

使用WinDbg —— .NET篇 (一) 

Connection string里面输入命令“tcp:server=192.168.244.128,port=1024”,点击OK

Server started.  Client can connect with any of these command lines

0: <debugger> -remote tcp:Port=1024,Server=WIN-K584J5H3VLQ

ECJTU_LUOWEI-PC\ECJTU_LUOWEI (tcp 192.168.244.1:53682) connected at Sat Feb 21 10:11:09 2015

到此已经完整的建立了一个远程调试会话环境,构建远程调试会话环境的方式有很多,读者可以根据不同的需求灵活运用,在这里只起个抛砖引玉的作用。

3.3 认识界面

Windbg是个典型的Windows 窗口程序,一般Windbg10个常用的窗口:

名称

快捷键

用途

Command

Alt+1

输入命令、显示命令结果和调试信息输出

Watch

Alt+2

监控窗口

Locals

Alt+3

局部变量

Registers

Alt+4

观察和修改寄存器的值

Memory

Alt+5

观察和修改内存数据

Call Stack

Alt+6

Trace 用的

Disassembly

Alt+7

反汇编

Scratch Pad

Alt+8

做笔记用的

Processes and Threads

Alt+9

显示所有调试目标的列表,进程和线程

Command Browser

Ctrl+N

执行和浏览历史命令

这些窗口最常用的就是命令窗口,我们所有的调试工作几乎都是和这个窗口打交道。这个窗口分三个部分:

使用WinDbg —— .NET篇 (一) 

第一部分是Title部分,点击箭头所指的部分,会有个context menu弹出来,主要是显示的设置作用;第二部分是中间的白板部分,是显示命令和输出结果用的;最后一个部分主要是用来显示状态信息或者等待命令输入。

有时候第三部分是显示Busy的状态,如:

使用WinDbg —— .NET篇 (一) 

如果不是Busy的状态,左边的灰色部分显示的是X:Y的格式信息,其中X表示进程IDY表示线程ID,具体哪个进程是哪个ID,可以查看Processes and Threads窗口。同时需要注意的是这里的线程ID不等于C#中线程的ManagedThreadId,这个线程IDWindbg自己维护的一个ID。根据笔者的经验,创建越早的线程,ID值越小,其中随着进程一起创建的线程的ID值为0,大部分情况下UI线程就是随着进程一起创建的,所以一般可以将ID0的线程看做UI线程。

3.4 调试托管程序

要想调试托管程序,调试器需要加载SOS或者SOSEX扩展程序。如果是分析Dump文件,还需要加载Dump文件mscordacwks

SOSSOSEX都是用来调试NET程序的扩展DLL,其中SOS包含在framework的安装目录下:

%windir%\microsoft.net\<architecture>\<version>\sos.dll

SOSEXSteve Johnson写的,这个dll的最新版本下载可以在该作者的网址上下载:

http://www.stevestechspot.com/

加载扩展dll的命令是:

0:004> .load c:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll

加载了SOS之后还需要一个加载同文件夹下的CLR.dll,才能使用扩展命令。

CLR 1.12.0的版本还不是使用CLR.dll,这个时候用的是mscorwks.dll。根据framework版本的不同,还有一个简单的加载方式:

Framework 1.0 & 2.0 & 3.0 & 3.5

Framework 4.0 & 4.5

.loadby sos mscorwks

.loadby sos clr

对于SOSEX的加载只能使用.load的方式,所以建议读者可以将SOSEX.dll下载到Windbg的安装目录下,这样便于加载SOSEX

当调试从其他机器打出的dump文件的时候,SOSmscordacwks版本的问题一直让人纠结,一般可以有三种解决方式:

a. 设置调试符号路径,并连接到Microsoft的公共Symbol server

b. 装一个与创建dump文件一样版本的framework

c. 从创建dump文件的机器上获取对应的SOSmscordacwks

一般都是采用第三种方式,如果是客户创建的dump,在客户给我们dump的时候可以顺便要一下SOSmscordacwks;第二种办法往往都不在正常人的考虑范围;至于第一种办法是在获取不到对应的SOSmscordacwks的时候采取的办法,这个办法唯一的缺点就是从Microsoft Symbol Server上下载对应的Symbol太慢。

值得注意的是,通过启动程序建立的调试会话在第一次中断的时候还没有加载CLRMSCORWKS,这个时候是无法通过loadby找到对应的CLRMSCORWKS模块,通过命令g继续执行等加载了CLRMSCORWKS模块就可以使用loadby

CommandLine: D:\Code\Ext1\Ext1\bin\Debug\Ext1.exe

Symbol search path is: *** Invalid ***

****************************************************************************

* Symbol loading may be unreliable without a symbol search path.           *

* Use .symfix to have the debugger choose a symbol path.                   *

* After setting your symbol path, use .reload to refresh symbol locations. *

****************************************************************************

Executable search path is: 

ModLoad: 01180000 01188000   Ext1.exe

ModLoad: 77180000 77300000   ntdll.dll

ModLoad: 70000000 7004a000   C:\Windows\SysWOW64\MSCOREE.DLL

ModLoad: 76640000 76750000   C:\Windows\syswow64\KERNEL32.dll

ModLoad: 765f0000 76636000   C:\Windows\syswow64\KERNELBASE.dll

(c7c.b58): Break instruction exception - code 80000003 (first chance)

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll - 

eax=00000000 ebx=00000000 ecx=78910000 edx=0015dd28 esi=fffffffe edi=00000000

eip=77220f3b esp=002af7e8 ebp=002af814 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!LdrVerifyImageMatchesChecksum+0x96c:

77220f3b cc              int     3

0:000> .loadby sos clr

Unable to find module 'clr'

0:000> g

ModLoad: 762c0000 76360000   C:\Windows\syswow64\ADVAPI32.dll

ModLoad: 74d40000 74dec000   C:\Windows\syswow64\msvcrt.dll

ModLoad: 763f0000 76409000   C:\Windows\SysWOW64\sechost.dll

ModLoad: 753f0000 754e0000   C:\Windows\syswow64\RPCRT4.dll

ModLoad: 74ce0000 74d40000   C:\Windows\syswow64\SspiCli.dll

ModLoad: 74cd0000 74cdc000   C:\Windows\syswow64\CRYPTBASE.dll

ModLoad: 6ff80000 6fffa000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll

ModLoad: 74ee0000 74f37000   C:\Windows\syswow64\SHLWAPI.dll

ModLoad: 76360000 763f0000   C:\Windows\syswow64\GDI32.dll

ModLoad: 751a0000 752a0000   C:\Windows\syswow64\USER32.dll

ModLoad: 74fc0000 74fca000   C:\Windows\syswow64\LPK.dll

ModLoad: 750f0000 7518d000   C:\Windows\syswow64\USP10.dll

ModLoad: 74e80000 74ee0000   C:\Windows\SysWOW64\IMM32.DLL

ModLoad: 754e0000 755ac000   C:\Windows\syswow64\MSCTF.dll

ModLoad: 6eec0000 6f552000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll

ModLoad: 6ede0000 6eeb3000   C:\Windows\SysWOW64\MSVCR110_CLR0400.dll

(c7c.b58): Unknown exception - code 04242420 (first chance)

ModLoad: 6de20000 6eddf000   C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\51e2934144ba15628ba5a31be2dae7dc\mscorlib.ni.dll

ModLoad: 76be0000 76d3c000   C:\Windows\syswow64\ole32.dll

ModLoad: 6d430000 6d49e000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll

(c7c.508): Break instruction exception - code 80000003 (first chance)

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\syswow64\KERNEL32.dll - 

eax=7ef9c000 ebx=00000000 ecx=00000000 edx=7721f7ea esi=00000000 edi=00000000

eip=7719000c esp=04a5ff0c ebp=04a5ff38 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!DbgBreakPoint:

7719000c cc              int     3

0:004> .loadby sos clr

加载SOS或者SOSEX之后,有个常用的命令是!help!help命令后面可以带参数,参数是命令名,然后命令窗口会输出该命令的详细用法,这个命令的地位好比linux中的man。由于SOSSOSEX的帮助命令都是!help,所以如果同时加载了两个扩展模块,输入命令的时候可以将模块名作为标识,如:!sos.help。以下是help命令的简略信息:

0:004> !sos.help

-------------------------------------------------------------------------------

SOS is a debugger extension DLL designed to aid in the debugging of managed

programs. Functions are listed by category, then roughly in order of

importance. Shortcut names for popular functions are listed in parenthesis.

Type "!help <functionname>" for detailed info on that function. 

 

Object Inspection                  Examining code and stacks

-----------------------------      -----------------------------

DumpObj (do)                       Threads

DumpArray (da)                     ThreadState

...

0:004> !sosex.help

SOSEX - Copyright 2007-2014 by Steve Johnson - http://www.stevestechspot.com/

To report bugs or offer feedback about SOSEX, please email sjjohnson@pobox.com

Quick Ref:

--------------------------------------------------

bhi       [filename]          BuildHeapIndex - Builds an index file for heap objects.

...

Use !help <command> or !sosexhelp <command> for more details about each command.

You can also use the /? (or -?) option on any command to get help for that command.

0:004> !sos.help threads

-------------------------------------------------------------------------------

!Threads [-live] [-special] 

!Threads lists all the mananaged threads in the process. 

-live:     optional. Only print threads associated with a live thread.

-special:  optional. With this switch, the command will display all the special

           threads created by CLR. Those threads might not be managed threads 

           so they might not be shown in the first part of the command's 

           output. Example of special threads include: GC threads (in 

           concurrent GC and server GC), Debugger helper threads, Finalizer 

           threads, AppDomain Unload threads, and Threadpool timer threads.

...