现在的位置: 首页 > 综合 > 正文

Windbg调试命令详解(2)

2013年05月11日 ⁄ 综合 ⁄ 共 6459字 ⁄ 字号 评论关闭
转载注明>> 作者张佩】【原文http://blog.csdn.net/blog_index

2. 符号与源码

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

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

  • 全局变量(类型、名称、地址);
  • 局部变量(类型、名称、地址);
  • 函数(名称、原型、地址);
  • 变量、结构体类型定义;

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

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

symbols

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

2.1 模块列表

每个可执行程序都是由若干个模块构成,有些模块静态加载,有些模块以动态方式进行加载。所以对于有些模块,可能在A时刻运行时被加载,而在B时刻运行时,自始至终都未被加载。调试过程中,调试器根据模块的加载情况加载符号。有几个命令可以用来列举模块列表,分别是:lm、!dlls、.reload /l、!imgreloc。下面分别来看。

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

lm是list loaded modules的缩写,他还有一个DML版本:

  • lmD [选项] [a Address] [m Pattern | M Pattern]

使用/v选项能列出模块的详细信息,包括:模块名、模块地址、模块大小、镜像名、时间戳、以及对应的符号文件信息(包括类型、路径、类型、编译器、符号加载状态)。

如使用参数a,后面跟地址(address),则只有指定地址所在的模块能够被列出;

如使用参数m,后面跟一个表示模块名的字符串通配符,如lm  m  *o*将显示所有名称中包含字母o的模块,下图所示:

||0:0:001> lm m *o*
start             end                 module name
f3380000 f3512000   dwmcore    (private pdb symbols) 
f92d0000 f9327000   d3d10_1core   (deferred)             
fa890000 fa9f1000   WindowsCodecs   (deferred)             
faa50000 fac44000   comctl32   (deferred)             
fbf70000 fbf7c000   version    (deferred)             
fce20000 fce2f000   profapi    (deferred)             
fd970000 fdb73000   ole32      (deferred)             
fee60000 fee7f000   sechost    (deferred)

下面介绍另一个命令:

  • !dlls [选项] [LoaderEntryAddress]

            首先看他的可选参数:

            -i/-l/-m:排序方式,分别按照初始化顺序、加载顺序、内存起始地址顺序排列。

-a:列出镜像文件PE结构的文件头、Section头等详细信息,是分析PE结构的好帮手(更好的帮手是利用自如PEView或Stud_PE等UI工具)。

            -c:指定函数所在的模块。这个选项非常实用,比如我想知道NtCreateFile函数是哪个模块暴露出来的接口,如下:

0:000> !dlls -c ntcreatefile
Dump dll containing 0x7c92d0ae:
0x00251f48: C:\WINDOWS\system32\ntdll.dll
      Base   0x7c920000  EntryPoint  0x7c932c48  Size        0x00096000
      Flags  0x00085004  LoadCount   0x0000ffff  TlsIndex    0x00000000
             LDRP_IMAGE_DLL
             LDRP_LOAD_IN_PROGRESS
             LDRP_ENTRY_PROCESSED
             LDRP_PROCESS_ATTACH_CALLED

除了lm和!dlls外,下文将讲到的.reload命令在加入 /l选项后,也能列举模块,其命令格式如下:

  • .reload /l

最后再来看一个!imgreloc命令,它也能够列出模块列表并显示各模块地址。但其主要作用尚不在此,它用来判断各个模块是否处于preferred地址范围。所谓Preferred地址是这么一回事:二进制文件在编译的时候,编译器都会为其设置一个理想地址(Preferred Address),这样二进制文件被加载时,系统会尽可能将他映射到这个理想地址。当然,所谓“理想”往往是会受到“现实”的挑战的,当存在地址竞争的时候,需要适当调整二进制文件的加载地址,选择另一个合适的地方加载之。!imgreloc命令就是用来查看这种情况的,命令如下:

  • !imgreloc [模块地址]

命令!imgReloc是Image Relocate的缩写,字面已能够反映其含义:镜像文件重定位信息。下面是一个例子。

 imgreloc

      上例中,大部分系统模块(上图下部方框所示)其地址由于事先经过统筹分配,所以一般都能被加载到preferred地址处。只有少数模块(如最上面的Normaliz模块)由于地址冲突而受到了调整。

2.2 模块信息

      上一节我们了解了如何枚举模块列表,这一节我们研究针对单个模块,如何获取详细信息。有多个命令可以查看指定模块的详细模块信息,这包括:lm、!dh、lmi等,下面来一一介绍。

首先看lm,这个命令上面我们已经介绍过,现在利用它来获取指定模块信息。其命令格式如下:

  • lm v a 模块地址

这里使用了v选项,以显示详细(verbose)信息;并使用a参数以指定模块地址。通过此命令显示的信息,和我们在explorer资源管理器中通过鼠标右键查看一个文件的属性所看到的信息差不多。请看下面的清单:

0:000> lm v a 00400000
start    end        module name
00400000 00752000   UsbKitApp C (private pdb symbols)  C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
    Loaded symbol image file: UsbKitApp.exe
    Image path: UsbKitApp.exe
    Image name: UsbKitApp.exe
    Timestamp:        Tue Mar 16 22:07:02 2010 (4B9F9086)
    CheckSum:         00000000
    ImageSize:        00352000
    File version:     1.0.0.1
    Product version:  1.0.0.1
    File flags:       1 (Mask 3F) Debug
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0804.03a8
    CompanyName:      TODO: <公司名>
    ProductName:      TODO: <产品名>
    InternalName:     UsbKitApp.exe
    OriginalFilename: UsbKitApp.exe
    ProductVersion:   1.0.0.1
    FileVersion:      1.0.0.1
    FileDescription:  TODO: <文件说明>
    LegalCopyright:   TODO: (C) <公司名>。保留所有权利。

    下面看!lmi命令,此命令通过指定模块地址查找模块并获取其信息,其命令格式如下

  • !lm模块地址

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

0:000> !lmi 0x400000
Loaded Module Info: [0x400000]
         Module: UsbKitApp
   Base Address: 00400000
     Image Name: UsbKitApp.exe
   Machine Type: 332 (I386)
     Time Stamp: 4b9f9086 Tue Mar 16 22:07:02 2010
           Size: 352000
       CheckSum: 0
Characteristics: 103
Debug Data Dirs: Type  Size     VA  Pointer
CODEVIEW  - GUID: {5DB12DF1-71CA-43F7-AD85-0977FB3629A4}
               Age: 3, Pdb: C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
     Image Type: FILE     - Image read successfully from debugger.
                 UsbKitApp.exe
    Symbol Type: PDB      - Symbols loaded successfully from image header.
                 C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
       Compiler: Resource - front end [0.0 bld 0] - back end [9.0 bld 21022]
    Load Report: private symbols & lines, not source indexed
                 C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb

 如果还要查看更详细、丰富的模块信息,可以使用!dh命令,命令格式如下:

  • !dh [标志] 模块地址

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

!dh

模块相关的知识点讲完了,下面讲符号有关命令。和符号相关的知识点包括:符号路径、符号服务器、符号缓存、符号加载以及符号的使用等。

2.3 符号路径

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

设置符号路径:

设置符号路径的语法如下:

  • .sympath  [+]  [路径]

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

  • .sympath

如要覆盖原来的路径设置,使用新路径即可:

  • .sympath  <新路径>

要在原有路径的基础上添加一个新路径,可使用:

  • .sympath+  <新增路径>

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

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

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

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

符号服务器与符号缓存:

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

  • SRV*[符号缓存]*服务器地址

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

  • .sympath+ srv*c:\symbols*\\symsrv\symbols

此外,我们总是应该把微软的公用符号库加入到我们的符号路径中:

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

  • .symfix [+] [符号缓存地址]

这个命令等价于上面的.sympath命令,而不用输入长长的http地址。

0:000> .symfix c:\windows\symbols

0:000> .sympath
Symbol search path is: SRV*c:\windows\symbols*http://msdl.microsoft.com/download/symbols

符号选项:

命令格式如下:

  • 显示当前设置:.symopt
  • 增加选项:.symopt+  Flags
  • 删除选项:.symopt-  Flags

第一个命令没有任何参数,显示当前设置。后面两个,第二个命令含有“+”代表添加一个选项,第三个命令含有“-”代表去除一个选项。

001> .symopt
Symbol options are 0x30337:
  0x00000001 - SYMOPT_CASE_INSENSITIVE
  0x00000002 - SYMOPT_UNDNAME
  0x00000004 - SYMOPT_DEFERRED_LOADS
  0x00000010 - SYMOPT_LOAD_LINES
  0x00000020 - SYMOPT_OMAP_FIND_NEAREST
  0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
  0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
  0x00010000 - SYMOPT_AUTO_PUBLICS
  0x00020000 - SYMOPT_NO_IMAGE_SEARCH

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

可读名称

描述

0×1

SYMOPT_CASE_INSENSITIVE

符号名称不区分大小写

0×2

SYMOPT_UNDNAME

符号名称未修饰

0×4

SYMOPT_DEFERRED_LOADS

延迟加载

0×8

SYMOPT_NO_CPP

关闭C++转换,C++中的::符号将以__显示

0×10

SYMOPT_LOAD_LINES

从源文件中加载行号

0×20

SYMOPT_OMAP_FIND_

NEAREST

如果由于编译器优化导致找不到对应的符号,就以最近的一个符号代替之

0×40

SYMOPT_LOAD_ANYTHING

使得符号匹配的时候,匹配原则较松散,不那么严格。

0×80

SYMOPT_IGNORE_CVREC

忽略镜像文件头中的CV记录

0×100

SYMOPT_NO_UNQUALIFIED_

LOADS

只在已加载模块中搜索符号,如果搜索符号失败,不会自动加载新模块。

0×200

SYMOPT_FAIL_CRITICAL_

ERRORS

不显示文件访问错误对话框。

0×400

SYMOPT_EXACT_SYMBOLS

进行最严格的符号文件检查,只要有微小的差异,符号文件都不会被加载。

0×800

SYMOPT_ALLOW_ABSOLUTE_

SYMBOLS

允许从内存的一个绝对地址处读取符号

抱歉!评论已关闭.