Windbg教程

windbg文档网页:

https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/

windbg官网:

http://windbg.org/

参考网站:

https://www.stl-tec.de/tutorials/WinReverseEng/setup/
https://www.dumpanalysis.org/blog/index.php/about/
http://advdbg.org/

关于工作空间

工作空间保存有断点 用户定义的别名 调试器的设置 图形界面信息 调试会话状态等等信息,类似VS的项目文件,PS的工作区。

命令概述

WinDBG主要是以命令方式工作的,WinDBG共支持三类命令:标准命令、元命令和扩展命令

标准命令

标准命令通常是一两个字符(version除外)或者符号,用来提供适用于各种调试目标的最基本调试功能。标准命令是不分大小写的。比如:

g 运行
t 单步步入
p 单步步过
r 查看和修改寄存器

元命令

元命令用来提供标准命令没有提供的调试功能,与标准命令一样,元命令也是内建在调试器引擎或者WinDBG程序文件中的。
所有元命令都以一个点(.)开始,所以元命令也被称为点命令,例如:

.reload 重新载入符号
.reboot 重启目标机器
.restart 重启调试器
.logfile 显示信息

扩展命令

扩展命令用于扩展某一方面的调试功能。与标准命令和元命令是内建在WinDBG程序文件中不同,扩展命令是实现在动态加载的扩展模块(DLL)文件中的。

所有的扩展命令都以!开头

通过WinDBG的SDK,用户可以编写自己的扩展模块和扩展命令,例如检测内存测试的!heap

调试技巧

在开始调试之前,有几个要点,记住以下几个点对于调试事半功倍

  • 直接按回车可以执行上一条命令
  • 使用分分号作为分隔符,可以在同一行输入多条命令
  • 按上下方向键可以浏览和选择以前输入过的命令
  • 当命令提示符显示为BUSY时,即使命令编辑框可以输入命令,但是这个命令也不会被马上执行,要等WinDBG恢复到空闲状态才能执行
  • 使用Ctrl+Break 来终止一个长时间未完成的命令。如果使用KD或则CDB,那么用Ctrl+C
  • 选择菜单->Edit_>Write Window Text to File可以把之前敲过的所有命令记录到文件
  • tab键可以补齐
  • Windbg –p PID 命令行参数启动windbg直接附加
  • Windbg -pn 进程名 命令行参数启动windbg直接附加

寻求帮助

?

元命令有一百多个,使用下面命令列举所有元命令:

0:004> ?

Open debugger.chm for complete debugger documentation

B[C|D|E][<bps>] - clear/disable/enable breakpoint(s)
BL - list breakpoints
BA <access> <size> <addr> - set processor breakpoint
BP <address> - set soft breakpoint
D[type][<range>] - dump memory
DT [-n|y] [[mod!]name] [[-n|y]fields]
   [address] [-l list] [-a[]|c|i|o|r[#]|v] - dump using type information
DV [<name>] - dump local variables
DX [-r[#]] <expr> - display C++ expression using extension model (e.g.: NatVis)
E[type] <address> [<values>] - enter memory values
G[H|N] [=<address> [<address>...]] - go
K <count> - stacktrace
KP <count> - stacktrace with source arguments
LM[k|l|u|v] - list modules
LN <expr> - list nearest symbols
P [=<addr>] [<value>] - step over
Q - quit
R [[<reg> [= <expr>]]] - view or set registers
S[<opts>] <range> <values> - search memory
SX [{e|d|i|n} [-c "Cmd1"] [-c2 "Cmd2"] [-h] {Exception|Event|*}] - event filter
T [=<address>] [<expr>] - trace into
U [<range>] - unassemble
version - show debuggee and debugger version
X [<*|module>!]<*|symbol> - view symbols
? <expr> - display expression
?? <expr> - display C++ expression
$< <filename> - take input from a command file

或者输入 命令查看有哪些参数

BA ?

.help [/D]

列出元命令
如使用“/D”参数,命令列表将以DML格式显示。

.chain [/D]

此命令能够给出一个扩展命令集的动态库列表

!模块名.help

根据chain命令列表的模块,查询具体模块里的命令

标准命令

version

此命令显示操作系统的版本信息以及Windbg本身的版本信息,Windbg的配置和操作系统密切相关,所以将操作系统的版本信息一并显示出来是很有必要的。

u [地址]

进行反汇编

ub [地址]

倒着反汇编

uf [函数地址]

对nt模块的函数进行反汇编

uf [模块名!函数地址]

反汇编函数

q | qq | qd

q是Quit的缩写。结束当前调试会话,并返回到最简单的工作空间,甚至把命令行界面也关闭掉。q和qq两个命令将结束(close)被调试的进程,qd不会关闭调试进程,而是进行解挂操作。

双机调试的时候,如果你感觉调试已经陷入僵局,比如目标机Hang住了动都动不了,此时通过主机让目标机强制宕机或重启,不失为一个好主意。

lm [选项] [a Address] [m Pattern | M Pattern]

列出模块信息

(1)/v选项能列出模块的详细信息
(2)参数[a Address]只有指定地址所在的模块能够被列出
(3)参数[m Pattern | M Pattern]通过模块名称通配符列出模块

lm

0:000> lm
start    end        module name
00400000 0042d000   VC6Heap    (deferred)             
761b0000 763c4000   KERNELBASE   (deferred)             
765e0000 766d0000   KERNEL32   (deferred)             
77da0000 77f43000   ntdll      (pdb symbols)          e:\localsymbols\wntdll.pdb\A6877C973CEB6E8103A62F2F219BD5E01\wntdll.pdb

lm v

0:000> lm v
start    end        module name
00400000 0042d000   VC6Heap    (deferred)             
    Image path: VC6Heap.exe
    Image name: VC6Heap.exe
    Timestamp:        Wed Oct 27 00:23:15 2021 (61782B73)
    CheckSum:         00000000
    ImageSize:        0002D000
    Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4
    Information from resource tables:
761b0000 763c4000   KERNELBASE   (deferred)             
    Image path: C:\Windows\SysWOW64\KERNELBASE.dll
    Image name: KERNELBASE.dll
    Image was built with /Brepro flag.
    Timestamp:        11253621 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         00216100
    ImageSize:        00214000
    File version:     10.0.19041.964
    Product version:  10.0.19041.964
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     Kernelbase.dll
        OriginalFilename: Kernelbase.dll
        ProductVersion:   10.0.19041.964
        FileVersion:      10.0.19041.964 (WinBuild.160101.0800)
        FileDescription:  Windows NT BASE API Client DLL
        LegalCopyright:   © Microsoft Corporation. All rights reserved.
765e0000 766d0000   KERNEL32   (deferred)             
    Image path: C:\Windows\SysWOW64\KERNEL32.DLL
    Image name: KERNEL32.DLL
    Image was built with /Brepro flag.
    Timestamp:        60AA50B0 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         000A9360
    ImageSize:        000F0000
    File version:     10.0.19041.928
    Product version:  10.0.19041.928
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     kernel32
        OriginalFilename: kernel32
        ProductVersion:   10.0.19041.928
        FileVersion:      10.0.19041.928 (WinBuild.160101.0800)
        FileDescription:  Windows NT BASE API Client DLL
        LegalCopyright:   © Microsoft Corporation. All rights reserved.
77da0000 77f43000   ntdll      (pdb symbols)          e:\localsymbols\wntdll.pdb\A6877C973CEB6E8103A62F2F219BD5E01\wntdll.pdb
    Loaded symbol image file: C:\Windows\SYSTEM32\ntdll.dll
    Image path: ntdll.dll
    Image name: ntdll.dll
    Image was built with /Brepro flag.
    Timestamp:        B7DB0838 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         001A4A97
    ImageSize:        001A3000
    File version:     10.0.19041.964
    Product version:  10.0.19041.964
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     ntdll.dll
        OriginalFilename: ntdll.dll
        ProductVersion:   10.0.19041.964
        FileVersion:      10.0.19041.964 (WinBuild.160101.0800)
        FileDescription:  NT Layer DLL
        LegalCopyright:   © Microsoft Corporation. All rights reserved.

lm m o 将显示所有名称中包含字母o的模块

lm v a 00400000 显示地址00400000所在模块的详细信息

lm f 显示所有模块的信息(包含模块的文件路径)

元命令

.cls

清屏命令

n [8|10|16]

软件默认是16进制,但有时候我们也需要把默认进制改成八进制或十进制的。

.effmach x86

命令.effmach表示Effective Machine Type,即有效的机器类型。此命令将当前的处理器模式设置为x86模式。

.formats 整数

0:004> .formats 0xeee
Evaluate expression:
  Hex:     00000eee
  Decimal: 3822
  Octal:   00000007356
  Binary:  00000000 00000000 00001110 11101110
  Chars:   ....
  Time:    Thu Jan  1 09:03:42 1970
  Float:   low 5.35576e-042 high 0
  Double:  1.88832e-320

.attach PID

通过pid附加程序

.create 程序启动命令行

.opendump 文件名

此命令打开一个dump文件,并建立一个DUMP调试会话。如何手动创建一个dump文件呢?比如在调试过程中,遇到了无法解决的问题,希望获得异地帮助,则把当前调试环境保存到Dump文件中发送给能提供帮助的人,不失为一种好办法。

.dump 文件名

生成dump文件

选项(1): /m
命令行示例:.dump /m C:\dumps\myapp.dmp
注解: 缺省选项,生成标准的minidump, 转储文件通常较小,便于在网络上通过邮件或其他方式传输。 这种文件的信息量较少,只包含系统信息、加载的模块(DLL)信息、 进程信息和线程信息。
选项(2): /ma
命令行示例:.dump /ma C:\dumps\myapp.dmp
注解: 带有尽量多选项的minidump(包括完整的内存内容、句柄、未加载的模块,等等),文件很大,但如果条件允许(本机调试,局域网环境), 推荐使用这中dump。
选项(3):/mFhutwd
命令行示例:.dump /mFhutwd C:\dumps\myapp.dmp
注解:带有数据段、非共享的读/写内存页和其他有用的信息的minidump。包含了通过minidump能够得到的最多的信息。是一种折中方案。

.detach

此命令结束当前调试会话, Windbg解除和被调试进程之间的调试关系(不管是通过挂载,还是通过创建方式建立的调试关系),解挂后,被调试进程能够独立运行;如果当前的调试会话是一个Dump文件,此命令直接结束对dump文件的调试,即结束调试会话。

扩展命令

!address

显示了有关目标进程或目标计算机使用的内存的信息

!address Address
!address -summary 
!address [-f:F1,F2,...] {[-o:{csv | tsv | 1}] | [-c:"Command"]}
!address -? | -help

列出进程整个内存布局
Windbg教程_第1张图片
列出内存简要信息
Windbg教程_第2张图片
堆内存
Windbg教程_第3张图片

!heap

显示堆使用情况信息、控制堆管理器中的断点、检测泄漏的堆块、搜索堆块或显示页堆信息

查看堆栈简要信息
Windbg教程_第4张图片
显示堆栈的完整信息
Windbg教程_第5张图片
显示指定堆的使用情况统计信息
Windbg教程_第6张图片
查找指定大小的堆栈
在这里插入图片描述
查看某块内存的申请调用栈
!heap -p -a [UsrPtr]

命令搜索包含给定地址的堆块。 如果使用了 -v 选项,则此命令将另外在当前进程的整个虚拟内存空间中搜索指向此堆块的指针
!heap -x

!dlls [选项] [LoaderEntryAddress]

列出dll库的详细信息

0:000> !dlls 
This is Win8 with the loader DAG.

0x005d3148: G:\C++Demo\VC6Heap\Release\VC6Heap.exe
      Base   0x00400000  EntryPoint  0x00405e2b  Size        0x0002d000    DdagNode     0x005d3208
      Flags  0x800022cc  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             <unknown>
             LDRP_LOAD_NOTIFICATIONS_SENT
             LDRP_IMAGE_DLL

0x005d3040: C:\Windows\SYSTEM32\ntdll.dll
      Base   0x77da0000  EntryPoint  0x00000000  Size        0x001a3000    DdagNode     0x005d3100
      Flags  0x0000a2c4  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             <unknown>
             LDRP_IMAGE_DLL

0x005d3528: C:\Windows\System32\KERNEL32.DLL
      Base   0x765e0000  EntryPoint  0x765ff640  Size        0x000f0000    DdagNode     0x005d35e8
      Flags  0x000ca2cc  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             <unknown>
             LDRP_LOAD_NOTIFICATIONS_SENT
             LDRP_IMAGE_DLL
             LDRP_DONT_CALL_FOR_THREADS
             LDRP_PROCESS_ATTACH_CALLED

0x005d38f8: C:\Windows\System32\KERNELBASE.dll
      Base   0x761b0000  EntryPoint  0x762c4ed0  Size        0x00214000    DdagNode     0x005d39b8
      Flags  0x0008a2cc  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             <unknown>
             LDRP_LOAD_NOTIFICATIONS_SENT
             LDRP_IMAGE_DLL
             LDRP_PROCESS_ATTACH_CALLED

!dlls -a 列出镜像文件PE结构的文件头、Section头等详细信息,是分析PE结构的好帮手

!dlls -c NtCreateFile 根据函数名查找指定dll

0:000> !dlls -c NtCreateFile

0x005d3040: C:\Windows\SYSTEM32\ntdll.dll
      Base   0x77da0000  EntryPoint  0x00000000  Size        0x001a3000    DdagNode     0x005d3100
      Flags  0x0000a2c4  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             <unknown>
             LDRP_IMAGE_DLL

!lmi 模块地址

此命令侧重获取对调试器有用的信息,请看下面的列表:

2:002> !lmi ntdll
Loaded Module Info: [ntdll] 
         Module: ntdll
   Base Address: 771f0000
     Image Name: ntdll.dll
   Machine Type: 332 (I386)
     Time Stamp: b7db0838 (This is a reproducible build file hash, not a true timestamp)
           Size: 1a3000
       CheckSum: 1a4a97
Characteristics: 2102  
Debug Data Dirs: Type  Size     VA  Pointer
             CODEVIEW    23, 25ba8,   24fa8 RSDS - GUID: {A6877C97-3CEB-6E81-03A6-2F2F219BD5E0}
               Age: 1, Pdb: wntdll.pdb
                 POGO   548, 25bcc,   24fcc [Data not mapped]
                REPRO    24, 26114,   25514 Reproducible build[Data not mapped]
                   ??     4, 26138,   25538 [Data not mapped]
     Image Type: FILE     - Image read successfully from debugger.
                 C:\Windows\SYSTEM32\ntdll.dll
    Symbol Type: PDB      - Symbols loaded successfully from symbol server.
                 e:\localsymbols\wntdll.pdb\A6877C973CEB6E8103A62F2F219BD5E01\wntdll.pdb
    Load Report: public symbols , not source indexed 
                 e:\localsymbols\wntdll.pdb\A6877C973CEB6E8103A62F2F219BD5E01\wntdll.pdb

!dh [标志] 模块地址

dh是display header的缩写,直译就是“显示文件头”的意思,它能够显示非常详细的PE头信息。下图截取了输出信息中的开头部分,其它详细内容,需要读者熟悉微软的PE结构才能看懂:

2:002> !dh ntdll

File Type: DLL
FILE HEADER VALUES
     14C machine (i386)
       8 number of sections
B7DB0838 time date stamp
       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
    2102 characteristics
            Executable
            32 bit word machine
            DLL

OPTIONAL HEADER VALUES
     10B magic #
   14.20 linker version
  120000 size of code
   7D200 size of initialized data
       0 size of uninitialized data
       0 address of entry point
    1000 base of code
         ----- new -----
771f0000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
   10.00 operating system version
   10.00 image version
   10.00 subsystem version
  1A3000 size of image
     400 size of headers
  1A4A97 checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
    4140  DLL characteristics
            Dynamic base
            NX compatible
            Guard
  10D880 [   12E55] address [size] of Export Directory
       0 [       0] address [size] of Import Directory
  12D000 [   6FD28] address [size] of Resource Directory
       0 [       0] address [size] of Exception Directory
  198800 [    5C08] address [size] of Security Directory
  19D000 [    51AC] address [size] of Base Relocation Directory
    72C0 [      70] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
    1620 [      AC] address [size] of Load Configuration Directory
       0 [       0] address [size] of Bound Import Directory
       0 [       0] address [size] of Import Address Table Directory
       0 [       0] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory


SECTION HEADER #1
   .text name
  11F6D5 virtual size
    1000 virtual address
  11F800 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read


Debug Directories(4)
	Type       Size     Address  Pointer
	cv           23       25ba8    24fa8	Format: RSDS, guid, 1, wntdll.pdb
	(    13)     548       25bcc    24fcc
	(    16)      24       26114    25514
	(    20)       4       26138    25538

SECTION HEADER #2
    PAGE name
     42A virtual size
  121000 virtual address
     600 size of raw data
  11FC00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

SECTION HEADER #3
      RT name
     1A9 virtual size
  122000 virtual address
     200 size of raw data
  120200 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

SECTION HEADER #4
   .data name
    5A54 virtual size
  123000 virtual address
     E00 size of raw data
  120400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #5
 .mrdata name
    2378 virtual size
  129000 virtual address
    2400 size of raw data
  121200 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #6
  .00cfg name
       4 virtual size
  12C000 virtual address
     200 size of raw data
  123600 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #7
   .rsrc name
   6FD28 virtual size
  12D000 virtual address
   6FE00 size of raw data
  123800 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #8
  .reloc name
    51AC virtual size
  19D000 virtual address
    5200 size of raw data
  193600 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only

!vprot [地址]

显示指定内存块的信息,侧重于内存保护信息

0:000> !vprot 0057fe2c
BaseAddress:       0057f000
AllocationBase:    00480000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE

!vadump [-v]

显示整个内存空间信息,dump者倾泻也,开启-v选项将显示详细(Verbose)信息

0:000> !vadump -v
BaseAddress:       00000000
AllocationBase:    00000000
RegionSize:        00080000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS

BaseAddress:       00080000
AllocationBase:    00080000
AllocationProtect: 00000080  PAGE_EXECUTE_WRITECOPY
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              01000000  MEM_IMAGE

符号与源码

符号与源码是调试过程中的重要因素,它们使得枯燥生硬的调试内容更容易地调试人员读懂。在可能的情况下,应该尽量地为模块加载符号和源码。大部分情况下源码难以得到,但符号却总能以符号文件的形式易于得到。

什么是符号文件呢?编译器和链接器在创建二进制镜像文件(诸如exe、dll、sys)时,伴生的后缀名为.dbg、.sym或.pdb的包含镜像文件编译、链接过程中生成的符号信息的文件称为符号文件。具体来说,符号信息包括如下内容:

  1. 全局变量(类型、名称、地址);
  2. 局部变量(类型、名称、地址);
  3. 函数(名称、原型、地址);
  4. 变量、结构体类型定义;

源文件路径以及每个符号对应于源文件中的行号,这是进行源码级别调试的基础。

有这么多的信息包含在符号文件中,使得符号文件通常要比二进制文件(PE格式文件)本身要大很多。调试过程中,符号之重要性不言而喻。只有正确设置了符号路径,使得调试器能够将调试目标、符号文件以及源码文件一一对应起来,才能够最好地发挥调试器的强大功用。

Windbg教程_第7张图片
符号信息隶属于指定的模块,所以只有调试器需要用到某个模块时,他的符号信息才有被加载和分析的必要。所以我们在讲符号内容之前,先讲和模块相关的命令。

符号路径

么是符号路径呢?就是调试器寻找符号文件的方向,它可以是本地文件夹路径、可访问的UNC路径、或者是符号服务器路径。什么是符号服务器呢?如果调试过程中,需要涉及到成千上万个符号文件,以及同一个符号文件存在不同平台下的不同符号文件版本的时候,那么一一手动设置符号路径肯定是不现实的,于是引入符号服务器的概念。符号服务器有一套命名规则,使得调试软件能够正确找到需要的符号文件。一般来说,符号服务器比较大,都是共用的,放在远程主机上。为了降低网络访问的成本,又引入了符号缓存的概念,即将从服务器上下载到的符号文件,保存在本地缓存中,以后调试器需要符号文件的时候,先从缓存中寻找,找不到的时候再到服务器上下载。下面分几部分一一来看。

.sympath

如果不加入任何参数执行.sympath命令,将显示当前的路径设置:

2:002> .sympath 
Symbol search path is: SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols;G:\C++Demo\VC6Heap\Release
Expanded Symbol search path is: srv*e:\localsymbols*http://msdl.microsoft.com/download/symbols;g:\c++demo\vc6heap\release

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols
OK                                             G:\C++Demo\VC6Heap\Release

.sympath <新路径>
如要覆盖原来的路径设置

2:002> .sympath D:\Game
Symbol search path is: D:\Game
Expanded Symbol search path is: d:\game

************* Path validation summary **************
Response                         Time (ms)     Location
OK                                             D:\Game

.sympath+ <新增路径>

2:002> .sympath+ SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols
Symbol search path is: D:\Game;SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: d:\game;srv*e:\localsymbols*http://msdl.microsoft.com/download/symbols

************* Path validation summary **************
Response                         Time (ms)     Location
OK                                             D:\Game
Deferred                                       SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols

要注意的是,使用.sympath改变或新增符号路径后,符号文件并不会自动更新,应再执行.reload命令以更新之。

这里要谈一谈延迟加载的知识点。延迟加载使得模块的符号表,只在第一次真正使用的时候才被加载。这加快了程序启动,不用在一开始耗费大量时间加载全部的符号文件。

使用.symopt +4和.symopt -4来开启或关闭延迟加载设置。

在已经启动了延迟加载的情况下,如想临时改变策略,立刻将指定模块的符号加载到调试器中,可以使用ld或者.reload /f命令。
`
符号服务器与符号缓存:

设置符号服务器的基本语法是:

SRV*[符号缓存]*服务器地址
语法由SRV引导,符号缓存和服务器地址的前面各有一个星号引导。符号缓存一般也叫做下游符号库。如某公司有一台专门的符号服务器,地址为\symsrv\symbols,则他们公司的所有开发人员都应该在他们的调试器中使用类似下面的命令:

.sympath+ srvc:\symbols\symsrv\symbols
此外,我们总是应该把微软的公用符号库加入到我们的符号路径中:

.sympath+ srv*<缓存地址>*http://msdl.microsoft.com/download/symbols

这是一台微软对外公开的服务器,使用http地址访问,不是所有人都能牢记这个网址,所以最好的办法就是使用.symfix命令,语法如下:

.symfix+ e:\localsymbols

一般调试时的设置

cpp
SRV*e:\localsymbols*http://msdl.microsoft.com/download/symbols
G:\C++Demo\VC6Heap\Release

第一条,当本地找不到符号时,从微软服务器下载符号到e:\localsymbols
第二条,应用程序的符号目录

.symopt

符号选项

增加选项:.symopt+ Flags
删除选项:.symopt- Flags

可用的符号选项请见下表:

可读名称 描述
0x1 SYMOPT_CASE_INSENSITIVE 符号名称不区分大小写
0x2 SYMOPT_UNDNAME 符号名称未修饰
0x4 SYMOPT_DEFERRED_LOADS 延迟加载
0x8 SYMOPT_NO_CPP 关闭C++转换,C++中的::符号将以__显示
0x10 SYMOPT_LOAD_LINES 从源文件中加载行号
0x20 SYMOPT_OMAP_FIND_NEAREST 如果由于编译器优化导致找不到对应的符号,就以最近的一个符号代替之
0x40 SYMOPT_LOAD_ANYTHING 使得符号匹配的时候,匹配原则较松散,不那么严格。
0x80 SYMOPT_IGNORE_CVREC 忽略镜像文件头中的CV记录
0x100 SYMOPT_NO_UNQUALIFIED_LOADS 只在已加载模块中搜索符号,如果搜索符号失败,不会自动加载新模块。
0x200 SYMOPT_FAIL_CRITICAL_ERRORS 不显示文件访问错误对话框。
0x400 SYMOPT_EXACT_SYMBOLS 进行最严格的符号文件检查,只要有微小的差异,符号文件都不会被加载。
0x800 SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 允许从内存的一个绝对地址处读取符号信息。
0x1000 SYMOPT_IGNORE_NT_SYMPATH 忽视在环境变量中设置的符号路径,也忽视被调试进程的执行路径。也就是说,当搜索符号文件的时候,不会从这些路径中搜索。
0x2000 SYMOPT_INCLUDE_32BIT_MODULES 让运行在安腾系统上的调试器,也枚举32位模块。
0x4000 SYMOPT_PUBLICS_ONLY 仅搜索符号文件的公共(PUBLIC)符号表,忽略私有符号表。
0x8000 SYMOPT_NO_PUBLICS 不搜索符号文件的公共(PUBLIC)符号表
0x10000 SYMOPT_AUTO_PUBLICS 先搜索pdb文件的私有符号表,如果在其中找到对应的符号,就不再搜索公共(PUBLIC)符号表,这可以加快搜索速度。
0x20000 SYMOPT_NO_IMAGE_SEARCH 不搜索镜像拷贝
0x40000 SYMOPT_SECURE 安全模式,让调试器尽量不影响到主机。
0x80000 SYMOPT_NO_PROMPTS 不显示符号代理服务器的认证对话框,将导致某些时候无法访问符号服务器
0x80000000 SYMOPT_DEBUG 显示符号搜索的详细过程和信息

符号加载

ld 模块名 [/f 符号文件名]

加载指定模块的符号。调试器默认采用延迟模式加载符号,也就是直到符号被使用的时候,才将符号文件加载到调试器中并进行解析。ld使得延迟模式被打破,让指定模块的符号文件立刻加载到调试器中。此指令可为模块的符号文件设置自定义的匹配名称,比如:

ld  123  /f  abc

这样一来,abc.pdb将成为123.exe的符号文件。正常情况下,这是不可能的,只能是abc.pdb对应abc.exe。

.reload /f /v [模块名]

.reload命令的作用是删除指定或所有已加载的符号文件,默认情况下,调试器不会立刻根据符号路径重新搜索并加载新的符号文件,而是推迟到调试器下一次使用到此文件时。

使用/f参数(force),将迫使调试器立刻搜索并重新加载新的符号文件。

其它参数解释如下:
/v:将搜索过程中的详细信息都显示出来。
/i:不检查pdb文件的版本信息;
/l:只显示模块信息,内核模式下,和“lm n t”命令类似,但显示内容比后者更多,因为包含了用户模块信息;
/n:仅重载内核符号,不重载用户符号;
/o:强制覆盖符号库中的符号文件,即使版本相同;
/d:用户层模式下使用Windbg时的默认选项,重载调试器模块列表中的所有模块;
/s:内核模式下使用Windbg时的默认选项,重载系统模块列表中的所有模块,另外,如果调试器在用户模式下运行,要加载内核模块,也必须使用/s选项,否则调试器将只会在调试器模块列表中搜索而导致找不到内核模块;
/u:卸载指定模块。如发现当前符号版本不对,使用/u开关先卸载之再重新加载。

!chksym <模块名> [符号名]

2:002> !chksym KERNELBASE

C:\Windows\SysWOW64\KERNELBASE.dll
    Timestamp: 11253621
  SizeOfImage: 214000
          pdb: wkernelbase.pdb
      pdb sig: E2F1482D-471C-A623-A201-55C3C8683C1A
          age: 1

Loaded pdb is e:\localsymbols\wkernelbase.pdb\E2F1482D471CA623A20155C3C8683C1A1\wkernelbase.pdb

wkernelbase.pdb
      pdb sig: E2F1482D-471C-A623-A201-55C3C8683C1A
          age: 1

MATCH: wkernelbase.pdb and C:\Windows\SysWOW64\KERNELBASE.dll

!sym

有两类符号加载选项。第一类是Noisy/Quiet,Noisy选项将打印符号加载的详细信息,Quiet选项则忽略这些信息。第二类是Prompts/Prompts off,即是否允许执行提示(Prompts)对话框。

一般都是在调用.reload 命令之前,执行加载选项命令,以见立竿见影之效。

所谓Noisy是吵闹的意思,调试器在搜索、加载符号的时候,会显示更多与搜索有关的信息。而安静模式下,则不会显示这些信息。不管吵闹与否,都不会影响到最终的搜索、加载结果。

当从网络上下载符号文件的时候,可能会碰到网络服务器要求客户进行安全认证的情况,如果开启Prompts选项,则弹出认证对话框,让用户输入认证信息;否则,不弹出对话框,并且不会下载符号文件。

不加任何参数的情况下,显示当前加载选项设置,下面的清单表明当前的设置为Quite及Prompts模式:

lkd> !sym
!sym  - quiet mode - symbol prompts on

符号搜索

符号搜索包括全局搜索和就近搜索两种

x [参数] [模块!符号]

/f:将只显示函数符号;并且会显示函数的详细定义。
/d:显示更多的变量类型相关信息。

如果什么参数都没有的话,它将列出当前调试环境下的所有局部变量,前提是要在有局部变量存在的情况下

0:000> x
     hHeap = 
     pBuffer2 = 
     pBuffer4 = 
     pBuffer3 = 
     pBuffer1 = 

模糊搜

0:000> x HeapMemory!*Enum*
00081050          HeapMemory!EnumHeapEntry (void *)

就近搜索

如果知道了符号的大概地址,但不能确定确切的符号名称,该怎么处理呢?就近查找命令ln能发挥作用,ln是List Nearest的缩写。它的作用是:(根据给定的地址)列出附近一定范围内的所有符号。下表中,指定地址0x7c8179f0的前后各有一个符号被找到:

0:000> ln 7c8179f0
(7c8179c3)   kernel32!NlsServerInitialize+0x29   |  (7c8179fe)   kernel32!AllocTables

源码命令

srcpath[+] [路径1;路径2]

不含任何参数的情况下,显示当前设置的源码路径。

.srcpath <路径信息>
覆盖原设置,设置新的源码搜索路径

.srcpath+ <路径信息>
使用“+”可以将新的路径添加到原设置中,而不会把原设置覆盖掉

.srcnoisy [1|0]

此命令乃source noisy缩写,可以理解成“嘈杂的源码”,类似于符号设置中也存在的noisy选项。他的三种运用如下所示:

状态:.srcnoisy
开启:.srcnoisy 1
关闭:.srcnoisy 0
开始“吵闹的源码”选项后,在源码加载、卸载,甚至单步的时候,都会显示丰富的源码信息。

.lines [-d|-e|-t]

行号选项,即在符号文件加载过程中,是否将行号也一并加载进来

打开:l+ [选项]
关闭:l- [选项]

进程与线程

既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息。调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺。

| [进程号]

竖线命令显示当前被调试进程列表的状态信息

0:000> |
.  0	id: 1234	create	name: HeapMemory.exe

.tlist [选项] [模块名]

.tlist命令显示当前系统中的进程列表,他是目前唯一可在用户模式下显示系统当前进程列表的命令。它有两个可选项:-v显示进程详细信息,-c只显示当前进程信息。

0:000> .tlist -c
 0n4660 HeapMemory.exe

!process

显示调试器当前运行进程信息

0:000> .process
Implicit process is now 00254000

00254000为PEB地址

!peb [地址]

如果未设置PEB地址,则默认为当前进程。内核模式下可通过!process命令获取PEB结构体地址;用户模式下只能显示当前进程的PEB信息,故而一般不带参数。

0:000> !peb
PEB at 00254000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00080000
    NtGlobalFlag:             70
    NtGlobalFlag2:            0
    Ldr                       77315d80
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 006230b8 . 006243d0
    Ldr.InLoadOrderModuleList:           006231b0 . 006243c0
    Ldr.InMemoryOrderModuleList:         006231b8 . 006243c8
            Base TimeStamp                     Module
           80000 61719cbe Oct 22 01:00:46 2021 G:\Windows开发Demo\WindowsProject1\Release\HeapMemory.exe
        771f0000 C:\Windows\SYSTEM32\ntdll.dll
        750a0000 60aa50b0 May 23 20:55:12 2021 C:\Windows\System32\KERNEL32.DLL
        75700000 11253621 Feb 12 15:09:21 1979 C:\Windows\System32\KERNELBASE.dll
        76f10000 C:\Windows\System32\ucrtbase.dll
        552f0000 5c5840da Feb 04 21:40:42 2019 C:\Windows\SYSTEM32\VCRUNTIME140.dll
    SubSystemData:     00000000
    ProcessHeap:       00620000
    ProcessParameters: 00621a68
    CurrentDirectory:  'E:\Microsoft.WinDbg_1.1910.3003.0_neutral__8wekyb3d8bbwe\'
    WindowTitle:  'G:\Windows开发Demo\WindowsProject1\Release\HeapMemory.exe'
    ImageFile:    'G:\Windows开发Demo\WindowsProject1\Release\HeapMemory.exe'
    CommandLine:  'G:\Windows开发Demo\WindowsProject1\Release\HeapMemory.exe'
    DllPath:      '< Name not readable >'
    Environment:  00620b80
        =::=::\
        ALLUSERSPROFILE=C:\ProgramData
        APPDATA=C:\Users\Administrator\AppData\Roaming
        CommonProgramFiles=C:\Program Files (x86)\Common Files
        CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
        CommonProgramW6432=C:\Program Files\Common Files
        COMPUTERNAME=WIN-AAOR63PJEOR
        ComSpec=C:\Windows\system32\cmd.exe
        DBGHELP_HOMEDIR=C:\ProgramData\Dbg
        DriverData=C:\Windows\System32\Drivers\DriverData
        FPS_BROWSER_APP_PROFILE_STRING=Internet Explorer
        FPS_BROWSER_USER_PROFILE_STRING=Default
        HOMEDRIVE=C:
        HOMEPATH=\Users\Administrator
        LOCALAPPDATA=C:\Users\Administrator\AppData\Local
        LOGONSERVER=\\WIN-AAOR63PJEOR
        NUMBER_OF_PROCESSORS=8
        OS=Windows_NT
        Path=E:\Microsoft.WinDbg_1.1910.3003.0_neutral__8wekyb3d8bbwe\x86;E:\Microsoft.WinDbg_1.1910.3003.0_neutral__8wekyb3d8bbwe\amd64;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps;
        PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
        PROCESSOR_ARCHITECTURE=x86
        PROCESSOR_ARCHITEW6432=AMD64
        PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel
        PROCESSOR_LEVEL=6
        PROCESSOR_REVISION=5e03
        ProgramData=C:\ProgramData
        ProgramFiles=C:\Program Files (x86)
        ProgramFiles(x86)=C:\Program Files (x86)
        ProgramW6432=C:\Program Files
        PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
        PUBLIC=C:\Users\Public
        QtMsBuild=C:\Users\Administrator\AppData\Local\QtMsBuild
        SESSIONNAME=Console
        SRCSRV_SHOW_TF_PROMPT=1
        SystemDrive=C:
        SystemRoot=C:\Windows
        TEMP=C:\Users\Administrator\AppData\Local\Temp
        TMP=C:\Users\Administrator\AppData\Local\Temp
        USERDOMAIN=WIN-AAOR63PJEOR
        USERDOMAIN_ROAMINGPROFILE=WIN-AAOR63PJEOR
        USERNAME=Administrator
        USERPROFILE=C:\Users\Administrator
        windir=C:\Windows

dt nt!_peb 地址

此命令显示系统nt模块中所定义的内核结构体PEB详细内容。使用之前必须先熟悉结构体定义。

~

命令“~”能够进行线程相关的操作。不带任何参数的情况下,它列出当前调试进程的线程。下图是计算器进程某时刻的线程列表:

0:000> ~
.  0  Id: 1234.22c8 Suspend: 1 Teb: 00257000 Unfrozen
   1  Id: 1234.268c Suspend: 1 Teb: 0025a000 Unfrozen
   2  Id: 1234.2690 Suspend: 1 Teb: 0025d000 Unfrozen
~2f

表示把2号线程冻住,在解冻之前,不再分发CPU时间给它。

若要让指定线程重新运行,需使用参数u:

~2u

让指定线程重新运行

~ 线程号

显示指定线程信息

0:000> ~1
   1  Id: 1234.268c Suspend: 1 Teb: 0025a000 Unfrozen
      Start: ntdll!TppWorkerThread (772258e0)
      Priority: 0  Priority class: 32  Affinity: ff
!teb [teb地址]
0:000> !teb
TEB at 00257000
    ExceptionList:        0057fe60
    StackBase:            00580000
    StackLimit:           0057d000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00257000
    EnvironmentPointer:   00000000
    ClientId:             00001234 . 000022c8
    RpcHandle:            00000000
    Tls Storage:          0025702c
    PEB Address:          00254000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0
~ 线程号 s

在多线程间作切换

0:000> ~2 s
eax=00000000 ebx=006252b8 ecx=00000000 edx=00000000 esi=006252b8 edi=006236f8
eip=772646ec esp=00a6f5e4 ebp=00a6f7a0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h
~~[线程ID] s

根据线程ID来切线程

0:002> ~~[268c] s
eax=00000000 ebx=006249d8 ecx=00000000 edx=00000000 esi=006249d8 edi=006236f8
eip=772646ec esp=0092fbb0 ebp=0092fd6c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h
~*k

显示所有线程栈信息

0:001> ~* k

#  0  Id: 1234.22c8 Suspend: 1 Teb: 00257000 Unfrozen
ChildEBP RetAddr  
0057fe28 000813d1 HeapMemory!main [g:\windows开发demo\windowsproject1\heapmemory\heapmemory.cpp @ 127]
(Inline) -------- HeapMemory!invoke_main+0x1c [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
0057fe70 750bfa29 HeapMemory!__scrt_common_main_seh+0xfa [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
0057fe80 77257a7e KERNEL32!BaseThreadInitThunk+0x19
0057fedc 77257a4e ntdll!__RtlUserThreadStart+0x2f
0057feec 00000000 ntdll!_RtlUserThreadStart+0x1b

   1  Id: 1234.268c Suspend: 1 Teb: 0025a000 Unfrozen
ChildEBP RetAddr  
0092fbac 77225b80 ntdll!NtWaitForWorkViaWorkerFactory+0xc
0092fd6c 750bfa29 ntdll!TppWorkerThread+0x2a0
0092fd7c 77257a7e KERNEL32!BaseThreadInitThunk+0x19
0092fdd8 77257a4e ntdll!__RtlUserThreadStart+0x2f
0092fde8 00000000 ntdll!_RtlUserThreadStart+0x1b

   2  Id: 1234.2690 Suspend: 1 Teb: 0025d000 Unfrozen
ChildEBP RetAddr  
00a6f5e0 77225b80 ntdll!NtWaitForWorkViaWorkerFactory+0xc
00a6f7a0 750bfa29 ntdll!TppWorkerThread+0x2a0
00a6f7b0 77257a7e KERNEL32!BaseThreadInitThunk+0x19
00a6f80c 77257a4e ntdll!__RtlUserThreadStart+0x2f
00a6f81c 00000000 ntdll!_RtlUserThreadStart+0x1b
~*r

显示线程寄存器信息

0:001> ~* r
eax=006282b0 ebx=00254000 ecx=00000000 edx=00000000 esi=770212f0 edi=00624720
eip=00081120 esp=0057fe2c ebp=0057fe70 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000200
HeapMemory!main:
00081120 55              push    ebp
eax=00000000 ebx=006249d8 ecx=00000000 edx=00000000 esi=006249d8 edi=006236f8
eip=772646ec esp=0092fbb0 ebp=0092fd6c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h
eax=00000000 ebx=006252b8 ecx=00000000 edx=00000000 esi=006252b8 edi=006236f8
eip=772646ec esp=00a6f5e4 ebp=00a6f7a0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h
~*e

上面的e是execute(执行)的缩写,后可跟一个或多个Windbg命令。它遍历线程并对每个线程执行指定命令,如:
~e k;r
此命令意为:在所用线程环境中(~
),分别执行(e)栈指令(k)和寄存器指令(r)。

0:001> ~*e  k;r
ChildEBP RetAddr  
0057fe28 000813d1 HeapMemory!main [g:\windows开发demo\windowsproject1\heapmemory\heapmemory.cpp @ 127]
(Inline) -------- HeapMemory!invoke_main+0x1c [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
0057fe70 750bfa29 HeapMemory!__scrt_common_main_seh+0xfa [d:\agent\_work\3\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
0057fe80 77257a7e KERNEL32!BaseThreadInitThunk+0x19
0057fedc 77257a4e ntdll!__RtlUserThreadStart+0x2f
0057feec 00000000 ntdll!_RtlUserThreadStart+0x1b
eax=006282b0 ebx=00254000 ecx=00000000 edx=00000000 esi=770212f0 edi=00624720
eip=00081120 esp=0057fe2c ebp=0057fe70 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000200
HeapMemory!main:
00081120 55              push    ebp
ChildEBP RetAddr  
0092fbac 77225b80 ntdll!NtWaitForWorkViaWorkerFactory+0xc
0092fd6c 750bfa29 ntdll!TppWorkerThread+0x2a0
0092fd7c 77257a7e KERNEL32!BaseThreadInitThunk+0x19
0092fdd8 77257a4e ntdll!__RtlUserThreadStart+0x2f
0092fde8 00000000 ntdll!_RtlUserThreadStart+0x1b
eax=00000000 ebx=006249d8 ecx=00000000 edx=00000000 esi=006249d8 edi=006236f8
eip=772646ec esp=0092fbb0 ebp=0092fd6c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h
ChildEBP RetAddr  
00a6f5e0 77225b80 ntdll!NtWaitForWorkViaWorkerFactory+0xc
00a6f7a0 750bfa29 ntdll!TppWorkerThread+0x2a0
00a6f7b0 77257a7e KERNEL32!BaseThreadInitThunk+0x19
00a6f80c 77257a4e ntdll!__RtlUserThreadStart+0x2f
00a6f81c 00000000 ntdll!_RtlUserThreadStart+0x1b
eax=00000000 ebx=006252b8 ecx=00000000 edx=00000000 esi=006252b8 edi=006236f8
eip=772646ec esp=00a6f5e4 ebp=00a6f7a0 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtWaitForWorkViaWorkerFactory+0xc:
772646ec c21400          ret     14h

线程时间

在软件调试的时候,若发现某线程占用执行时间过多,就需要当心是否有问题。线程执行时间的多少,其实就是占用CPU执行工作的时间多少。某线程占用越多,此长彼消,则系统中其它线程占用CPU的时间就越少。

线程的时间信息包括三个方面:自创建之初到现在的总消耗时间、用户模式执行时间、内核模式执行时间。需注意的是,消耗时间一定会远远大于用户时间+内核时间,多出来的是大量空闲时间(为Idle进程占用)。使用下面的命令查看线程时间:

.ttime

显示当前线程执行时间

0:001> .ttime
Created: Thu Oct 28 23:26:09.000 2021 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000

分别为自创建之初到现在的总消耗时间、用户模式执行时间、内核模式执行时间

!runaway 7

在!runaway命令中加入标志值7,将显示线程的全部三种时间值

0:001> !runaway 7
 User Mode Time
  Thread       Time
    2:2690     0 days 0:00:00.000
    1:268c     0 days 0:00:00.000
    0:22c8     0 days 0:00:00.000
 Kernel Mode Time
  Thread       Time
    0:22c8     0 days 0:00:00.031
    2:2690     0 days 0:00:00.000
    1:268c     0 days 0:00:00.000
 Elapsed Time
  Thread       Time
    0:22c8     0 days 0:32:26.752
    1:268c     0 days 0:32:25.999
    2:2690     0 days 0:32:25.996

异常与事件

在调试器语境中,事件是一个基本概念,Windbg是事件驱动的。Windows操作系统的调试子系统,是“事件”的发生源。调试器的所有操作,都是因事件而动,因事件被处理而中继。Windows定义了9类调试事件,异常是其中一类(ID为1)。所以异常和事件,这二者是前者包含于后者的关系。

系统对各种异常和调试事件进行了分类,执行sx命令可以列出针对当前调试目标的异常或非异常事件的处理。下面是一个片段:

0:001> sx
  ct - Create thread - ignore
  et - Exit thread - ignore
 cpr - Create process - ignore
 epr - Exit process - break
  ld - Load module - output
  ud - Unload module - ignore
 ser - System error - ignore
 ibp - Initial breakpoint - break
 iml - Initial module load - break
 out - Debuggee output - output

  av - Access violation - break - not handled
asrt - Assertion failure - break - not handled
 aph - Application hang - break - not handled
 bpe - Break instruction exception - break
bpec - Break instruction exception continue - handled
  eh - C++ EH exception - second-chance break - not handled
 clr - CLR exception - second-chance break - not handled
clrn - CLR notification exception - second-chance break - handled
 cce - Control-Break exception - break
  cc - Control-Break exception continue - handled
 cce - Control-C exception - break
  cc - Control-C exception continue - handled
  dm - Data misaligned - break - not handled
dbce - Debugger command exception - ignore - handled
  gp - Guard page violation - break - not handled
  ii - Illegal instruction - second-chance break - not handled
  ip - In-page I/O error - break - not handled
  dz - Integer divide-by-zero - break - not handled
 iov - Integer overflow - break - not handled
  ch - Invalid handle - break
  hc - Invalid handle continue - not handled
 lsq - Invalid lock sequence - break - not handled
 isc - Invalid system call - break - not handled
  3c - Port disconnected - second-chance break - not handled
 svh - Service hang - break - not handled
 sse - Single step exception - break
ssec - Single step exception continue - handled
 sbo - Security check failure or stack buffer overrun - break - not handled
 sov - Stack overflow - break - not handled
  vs - Verifier stop - break - not handled
vcpp - Visual C++ exception - ignore - handled
 wkd - Wake debugger - break - not handled
 rto - Windows Runtime Originate Error - second-chance break - not handled
 rtt - Windows Runtime Transform Error - second-chance break - not handled
 wob - WOW64 breakpoint - break - handled
 wos - WOW64 single step exception - break - handled

   * - Other exception - second-chance break - not handled

可以看到这几个调试事件,当发生进程退出(Exit Process)和初始化断点(Initial breakpoint)事件的时候,调试器应当被中断(Break)。模块加载(Load Modual)以及有调试输出(Debuggen Output)时,需要输出相关信息;其他的都被忽略掉,不做处理(Ignore)。 我们分析一下前两个事件。使用调试器调试记事本进程时,不管是用.attach挂载方式还是.create创建方式,在调试器正式侵入记事本进程前,都会有一个中断(Initial breakpoint异常);调试开始后运行一段时间,在外面将记事本关闭,又会发生一个中断(Exit Process异常)。

可以通过Debug|Event Filters…打开事件设置对话框。这个对话框中列出了全部调试事件,用户可分别对它们进行设置。
Windbg教程_第8张图片
这个对话框列出了对于当前调试会话可用的全部调试事件。针对每个调试事件,可设置其属性。右列Execution和Continue两组单选键,分别表示事件的中断属性与中继属性。右列Argument按钮可设置调试事件执行参数(上图中Load Module事件有一个Kernel32.dll参数,即当Kernel32.dll模块被加载时,调试器将被中断),Commands按钮可设置事件两轮机会发生时的执行命令。

.exr 记录地址

此命令显示一个异常记录的详细内容,传入一个异常记录地址

如果仅仅为了显示最近的一条异常记录,可以用-1代替异常记录地址:

.exr -1

!cppexr

他分析并显示一个C++异常信息

!analyze

此命令分析当前最近的异常事件(如果在进行dump分析,则是bug check),并显示分析结果。这个异常事件,就是上面.lastevent命令对应的事件。

-v:显示异常的详细信息,这个选项在调试错误的时候,最有用。
-f:f是force的缩写。强制将任何事件都当作异常来分析,即使仅仅是普通的断点事件。将因此多输出一些内容。
-hang:这个选项很有用,对于遇到死锁的情况,它会分析原因。在内核环境中,它分析内核锁和DPC栈;在用户环境中,它分析线程的调用栈。用户环境中,调试器只会对当前线程进行分析,所以一定要将线程环境切换到最可能引起问题的那个线程中去,才有帮助。这个参数非常有用,当真的遇到死锁时,它可以救命(另一个分析死锁的有效命令是!locks)。

!error:

此命令和VC里面内置的errlook工具类似(请有兴趣的读者使用作者编写的免费软件e-look,它比errlook功能更好且易于使用)。用来根据错误码,查看对应的可读错误信息。微软系统中常用的全局错误码有两套,一套是Win32错误码,通过函数GetLastError()获得的值;另一套是NTSTATUS值。!error命令对这二者都能支持。区别的方法,若错误码后面无参数1,则为win32错误码;否则就是NTSTATUS错误码。

        比如,获取错误码为2的Win32错误信息,可用:!error 2

        获取错误码为2的NTSTATUS错误信息,可用:!error 2 1 

!gle:

此命令是Get Last Error的缩写。它调用Win32接口函数GetLastError()取得线程的错误值,并打印分析结果。如果带有-all选项,则针对当前进程的所有线程(内核环境下为所有用户线程)执行GetLastError()操作;否则仅针对当前线程。

gh/gn

h是go with Exception handled的缩写,意思是:把异常标识为已处理而并继续执行程序;注意这里面的措辞,仅仅把异常“标识为”已处理,而并非真的被处理了。gh的作用在于,当遇到某个可以忽略的非致命异常时,将它先放过一边,而继续执行程序。

        而gn是go with Exception not handled的缩写,意思是,对异常不进行任何处理,而继续执行程序。这时候,程序自己的异常处理模块将有机会处理异常

局部变量

有两个命令可以打印当前的局部变量列表:x 和dv。x命令前文已经讲过。dv是Display local Variable的缩写。下面是对一段简单的Win32控制台代码获取其局部变量的情况:

dv

        /v:显示虚拟地址(virtual);
        /i:显示变量详细信息(information),包括局部变量、全局变量、形参、函数变量等。
        /t:显示变量类型(type),如int、char等等。
        /f:可指定进行分析的函数,需指定函数名。
0:000> dv /i /t
prv local  void * hHeap = 
prv local  unsigned char * pBuffer2 = 
prv local  unsigned char * pBuffer4 = 
prv local  unsigned char * pBuffer3 = 
prv local  unsigned char * pBuffer1 = 

命令中选项/f wmain是指针对wmain函数(即_tmain)分析其局部变量。看第一个变量argc,“prv param”对应/i开关选项;“@ebp+0x08”对应/v开关选项;“int”对应/t开关选项。

断点

软件断点

软件断点的本质是代码改写,即:将INT 3(代码为0xCC)指令替换到断点所在指令处(第一个字节),并保存被替换掉的代码(即一个字节内容)。等执行到断点处时,调试器将因断点而中断,并将被替换的一字节内容恢复到原内存中。其原理和代码补丁是一样的。

源码或汇编模式下,最简单的断点设置方式,是定位到正确的代码处,并按下F9键。此外还有三种设置软件断点的指令,分别讲解如下:

bp

命令bp是BreakPoint的缩写。其指令格式如下:

bp[ID] [Options] [Address [Passes]] [“CommandString”]

参数Address表示需设置断点的地址,缺省情况下使用当前指令指针(EIP)的地址。ID是断点号,一般不手动设置,由调试器内部编号。Passes是一个整数值,即第几次经过断点所在代码时,调试器才需要中断执行,默认为1,即每次都中断。CommandString用来设置一组命令,当断点发生的时候,就执行这一组命令,比如可以把它设置为“k”,这样断点处就会输出当前的调用栈。

Options是一组可选开关项,有下面几种:

/1:即阿拉伯数字1。这个选项表明这个被设置的断点只会在第一次有效,此后断点信息即被删除。

/p:这个开关项后跟一个EPCOESS结构体指针,只能用在内核调试环境下。内核调试环境下,如果要把断点设置到用户程序地址(即用户空间地址),需要使用这个开关,因为用户地址是进程相关的。

/t:这个开关项后跟一个ETHREAD结构体指针,只能用在内核调试环境下。此开关项与/p起到类似的作用,只不过前者定位到进程,后者更进一步定位到线程。

/c与/C:c或者C代表CallStack(调用栈)。这两个开关项和调用栈深度有关,都后跟一个整数值。前者表示调用栈深度如果小于这个整数值,断点才会引发中断,后者表示调用栈深度如果大于这个整数值,断点才会引发中断。

bu

此命令格式与bp类似,u代表了Unresolved。使用此命令设置的断点虽登记到调试器,但它具体对应到哪处代码或指令,尚未确定。

比如某EXE程序使用动态加载的方式加载DLL(使用函数LoadLibrary()),那么当DLL尚未加载时,就可用bu指令设置DLL中的代码断点,等到DLL加载时,调试器再正式落实此断点。

bm

此命令用来批量设置代码断点,它带有一个通配符字符串,凡是符合通配符格式的地址都将被设置断点,如:

bm /a ntdll!NtCreate*File
则诸如NtCreateFile\NtCreateMailslotFile\NtCreateNamedPipeFile等函数都将被设置断点。

硬件断点

硬件断点的原理和软件断点完全不同,硬件断点是通过CPU的断点寄存器来实现的,亦即依靠硬件方式实现。由于CPU的调试寄存器数量是有限的,所以能设置的硬件断点数量也是有限的。设置硬件断点的命令是ba,a代表了Address。指令格式如下:

ba[ID] Access Size [Options] [Address [Passes]] [“CommandString”]

参数ID、Options、Passes及CommandString,含义与前文bp指令相同,此处不述。

参数Address是内存地址,有别于前文的指令地址,内存地址既可以是指令地址,也可以是数据地址。缺省为当前指令寄存器地址(EIP)。参数Size表示地址长度,x86系统可选值为1、2、4,X64系统可选值为1、2、4、8。需要注意的是,Address地址必须对齐到Size,即Address值必须是Size的整数倍。参数Access是内存访问类型,有下面几种:

e:作为指令执行;r:读,或者写;w:写;i:执行IN/OUT操作。 比如:

ba r4 @ebp-0x08

地址@ebp-8一定是一个局部变量地址,所以当CPU对这个局部变量执行读写操作时,将引发硬件中断。

bl

列出所有断点

bd

禁止断点,d代表Disable。如bd 1,禁止断点1。断点被禁止后将不起作用,但亦未删除。

be

恢复断点,e代表Enable。恢复被禁止的断点。如be 1恢复1号断点。

bc

清除断点,如:bc 1,清除断点1;bc *,清除全部断点。

br

序号管理,r代表ReNumber,即重新排序。如:br 2 0,将2号断点重设为0号断点。

内存命令

内存是存储数据、代码的地方,通过内存查看命令可以分析很多问题。相关命令可以分为:内存查看命令和内存统计命令。内存统计命令用来分析内存的使用状况。

d[类型] [地址范围]

/c 列数:指定列数。默认情况下,列数 等于16除以列长,如dd命令的默认列数即为4列(=16/4)。例:

dd /c 8
此命令每列显示8个DWORD数,即32字节内容。

/p:此选项用来显示物理内存信息,只能用于内核模式中。不使用此命令时,都将显示虚拟内存信息。如:

d /p [地址范围]
L 长度: 默认情况下,d命令只显示固定长度的内存,一般为128或64字节。L可指定长度,如下面的命令将显示地址0x80000000开始处的0x100个字节内容:

db 0x80000000 L100

d代表Display,类型包括:字符、字符串、双字等。具体来说,d*命令共有这几种:d、 da、db、dc、dd、dD、df、dp、dq、du、dw、dW、dyb、dyd、ds、dS。解释如下:

内存类型

基本类型:

dw = 双字节WORD格式;
dd = 4字节DWORD格式 ;
dq = 8字节格式;
df = 4字节单精度浮点数格式;
dD =8字节双精度浮点数格式;
dp = 指针大小格式,32位系统下4字节,64位系统下为8字节

基本字符串:

da = ASCII字符串格式;
du = UNICODE字符串格式;
db =字节 + ASCII字符串;
dW = 双字节WORD + ASCII字符串;
dc = 4字节DWORD + ASCII字符串。

高级字符串:

ds = ANSI_STRING类型字符串格式;
dS = UNICODE_STRING类型字符串格式。

二进制 + 基本类型:

byb = 二进制 + 字节;
byd = 二进制 + DWORD值

数组形式内存

难能可贵的是,d*命令还能够以数组形式显示一段内存信息,包括:dda, ddp、 ddu、dds、dpa、dpp、dpu、dps、dqa、dqp、dqu、dqs。

何谓“以数组形式显示”呢?这一组命令能够将指定地址处的内容,作为一系列指针,进而显示指针所指处内容

dds、dps、dqs

dds [Options] [Range] 
dqs [Options] [Range] 
dps [Options] [Range]

命令显示给定范围内内存的内容。 此内存假定为符号表中的一系列地址。 相应的符号也显示

dds 命令 显示双字 (4 字节) dd 命令等值。 dqs 命令显示四字 (8 个字节) dq 命令等值。 dps 命令显示指针大小 (4 字节或 8 字节,具体取决于目标计算机的体系结构)

dx(显示调试器对象模型表达式)

dx 命令使用 NatVis 扩展模型显示 C++ 表达式

dx [-g|-gc #][-c #][-n|-v]-r[#] Expression[, ]
dx [{-?}|{-h}]

-g
显示为可访问的数据网格对象。 每个 itererated 元素都是网格中的一行,并且这些元素的每个显示子元素都是一列。 这允许你查看结构数组等内容,其中每个数组元素都显示在行中,结构的每个字段显示在列中。

选择列名称 (具有可用 DML 链接) 按该列进行排序。 如果已按该列排序,则排序顺序将反转。

任何可访问的对象都将选择并按住 (或右键) 通过名为"显示为网格"的 DML 添加的上下文菜单项。 选择并按住 (或右键) 输出窗口中的对象,然后选择此选项将在网格视图中显示该对象,而不是标准树视图。

列 (显示) +“按钮提供"选择并按住” (或右键单击) 选择行为。

Select 将采用该列,将其分解成其自己的表。 你会看到原始行加上展开列的子行。
选择并按住 (或右键单击) “展开到网格中”,这将采用列,并将列作为最右边的列添加回当前表。
-gc #
显示为网格,将网格单元格大小限制为指定数量的 # () 字符。

-c #
显示容器延续 (跳过 # 容器元素的) 。此选项通常用于自定义输出自动化方案,并提供"…"列表底部的 continuation 元素。

-n
有两种方法可以呈现数据。 使用 NatVis 可视化 (默认) 或基础本机 C/C++ 结构。 指定 -n 参数,以仅使用本机 C/C++ 结构而不是 NatVis 可视化效果来呈现输出。

-v
显示包含方法和其他非典型对象详细信息。

-r#
以递归方式显示 (级别) 的子 # 类型。 如果 # 未指定 ,则递归级别为 1 是默认值。

[ < ,FormatSpecifier > ]
使用以下任一格式说明符修改默认呈现。

,x: 以十六进制显示序号

,d: 以十进制形式显示序号

,o: 以八进制显示序号

,b: 以二进制形式显示序号

,en: 仅按名称显示枚举 (没有值)

,c: 显示为单个字符 (而不是字符串)

,s: 以 ASCII 引号显示 8 位字符串

,sb: 将 8 位字符串显示为 ASCII 未标出

,s8: 以 UTF-8 引号显示 8 位字符串

,s8b: 将 8 位字符串显示为未标出 UTF-8

,su: 以 UTF-16 引号显示 16 位字符串

,sub: 将 16 位字符串显示为 UTF-16 未qouted

!: 仅在原始模式下显示对象 (例如:无 NatVis)

、: # 指定指针/数组/容器的长度作为文本值, (# 数字值)

,[<>expression ]:指定指针/数组/容器的长度作为表达式 < 表达式>

,nd: 找不到派生 (类型) 类型的 runtype。 仅显示静态值

dx {-?}
显示命令行帮助。

dx {-h}
显示调试器中可用对象的帮助。

dx {-id}
仅供 Microsoft 内部使用。 用于跟踪命令输出中的数据模型链接。

遍历map

dx -r1 (*((DuiLib!std::map,std::allocator > > *)0xe60fcf4))
0:000> dx -r1 (*((DuiLib!std::map,std::allocator > > *)0xe60fcf4))
(*((DuiLib!std::map,std::allocator > > *)0xe60fcf4))                 : { size=0x2e } [Type: std::map,std::allocator > >]
    []     [Type: std::map,std::allocator > >]
    [comparator]     : less [Type: std::_Compressed_pair,std::_Compressed_pair,void *> >,std::_Tree_val > >,1>,1>]
    [allocator]      : allocator [Type: std::_Compressed_pair,void *> >,std::_Tree_val > >,1>]
    [0x0]            : {...}, false [Type: std::pair]
    [0x1]            : {...}, true [Type: std::pair]
    [0x2]            : {...}, true [Type: std::pair]
    [0x3]            : {...}, true [Type: std::pair]
    [0x4]            : {...}, true [Type: std::pair]
    [0x5]            : {...}, true [Type: std::pair]
    [0x6]            : {...}, true [Type: std::pair]
    [0x7]            : {...}, true [Type: std::pair]
    [0x8]            : {...}, true [Type: std::pair]
    [0x9]            : {...}, true [Type: std::pair]

把key的指针显示出来

0:000> dx -r2 (*((DuiLib!std::map,std::allocator > > *)0xe60fcf4))
(*((DuiLib!std::map,std::allocator > > *)0xe60fcf4))                 : { size=0x2e } [Type: std::map,std::allocator > >]
    []     [Type: std::map,std::allocator > >]
    [comparator]     : less [Type: std::_Compressed_pair,std::_Compressed_pair,void *> >,std::_Tree_val > >,1>,1>]
        []     [Type: std::_Compressed_pair,std::_Compressed_pair,void *> >,std::_Tree_val > >,1>,1>]
    [allocator]      : allocator [Type: std::_Compressed_pair,void *> >,std::_Tree_val > >,1>]
        []     [Type: std::_Compressed_pair,void *> >,std::_Tree_val > >,1>]
    [0x0]            : {...}, false [Type: std::pair]
        []     [Type: std::pair]
        first            : 0xa66f130 [Type: DuiLib::CGlobalImage *]
        second           : false [Type: bool]
    [0x1]            : {...}, true [Type: std::pair]
        []     [Type: std::pair]
        first            : 0xa66f460 [Type: DuiLib::CGlobalImage *]
        second           : true [Type: bool]
    [0x2]            : {...}, true [Type: std::pair]
        []     [Type: std::pair]
        first            : 0xa66f490 [Type: DuiLib::CGlobalImage *]
        second           : true [Type: bool]
    [0x3]            : {...}, true [Type: std::pair]
        []     [Type: std::pair]
        first            : 0xa6a97f8 [Type: DuiLib::CGlobalImage *]
        second           : true [Type: bool]

-g遍历数组
Windbg教程_第9张图片

伪寄存器

WinDBG自动定义了很多伪寄存器。在命令行和命令文件中都可以使用伪寄存器。WinDBG会自动将其替换(展开)为合适的值。例如下面这个@$scopeip就是一个伪寄存器,它代表当前的eip指针

下表列出了windbg所定义的部分寄存器(字典型知识,需要时查阅即可)

伪寄存器 含义
$ea 调试目标所执行上一条指令的有效地址
$ea2 调试目标所执行上一条指令的第二个有效地址
$exp 表达式评估器所评估的上一条表达式
$ra 当前函数的返回地址
$eip 指令指针寄存器
$eventip 当前调试事件发生时的指令指针
$previp 上一事件的指令指针
$relip 与当前事件关联的指令指针
$scopeip 当前上下文的指令指针
$exentry 当前进程的入口地址
$retreg 首要的函数返回值寄存器
$retreg64 64位格式的首要函数返回寄存器
$csp 栈顶指针ESP
$p 上一个内存显示命令所打印的第一个值
$proc 当前进程EPROCESS结构的指针
$thread 当前线程ETHREAD结构的指针
$peb 当前进程的进程环境块(PEB)的地址
$teb 当前线程的线程环境块(TEB)地址
$tpid 拥有当前线程的进程ID(PID)
$tid 当前线程的线程ID
$bpx X号断点的地址
$frame 当前栈帧的序号
$dbgtime 当前时间
$callret 使用.call命令调用的上一个函数的返回值
$ptrsize 调试目标所在系统的指针类型宽度
$pagesize 调试目标所在的系统的内存页字节数

你可能感兴趣的:(windows,windows,linux)