符号可以包含有关全局变量、局部变量、函数名、参数、结构和源行号的信息。符号有三种类型:导出符号、pdb符号(公共符号)和专用pdb符号(专用符号)。
导出符号是dll本身的一部分。例如,ntdll.dll和kernel32.dll将其函数的很大一部分公开为导出符号,因此可以将它们称为API,但是在进程中发现的大多数dll都有一组非常小的导出符号。通常导出符号不包含函数的参数信息,并且由于很少有函数以这种方式公开,因此当只有导出符号时,您不能真正依赖堆栈的有效性。
公共符号包含一些基本符号,如函数名和全局变量,但同样,并非所有函数名都在公共符号中公开。dll的开发人员选择公开什么作为公共符号,因此他/她可以隐藏任何他们认为会泄露太多有关实现信息的内容。私人符号包含第一段中列出的几乎所有内容
调试时,通过将dll/exe链接到符号文件的GUID,符号与相应的dll或exe匹配。这意味着,如果符号搜索路径中有多个ntdll.pdb,调试器将知道哪个对应于特定版本的ntdll.dll。搜索路径由.sympath指定,除sympath中列出的内容外,调试器还将查找加载dll的目录以及环境变量_NT_SYMBOL_PATH中给定的路径中的任何内容。
如果符号是错误的的时候会发生什么?
让我们看看这个带有mscorsvr.dll公共符号的堆栈:
54 Id: 62c.1590 Suspend: 1 Teb: 7ffa2000 Unfrozen
ChildEBP RetAddr Args to Child
1212ef44 7c59a030 00000090 00000000 1212ef64 ntdll!NtWaitForSingleObject+0xb [i386usrstubs.asm @ 2004]
1212ef6c 7c57b3db 00000090 00009c40 00000000 kernel32!WaitForSingleObjectEx+0x71 [D:
tprivatewindowsaseclientsynch.c @ 1309]
1212ef7c 791b578b 00000090 00009c40 00000000 kernel32!WaitForSingleObject+0xf [D:
tprivatewindowsaseclientsynch.c @ 1217]
1212efa0 791dbe6e 00000000 00000000 00000000 mscorsvr!ThreadpoolMgr::WorkerThreadStart+0x3a
1212ffb4 7c57b388 0d406838 00000002 00000000 mscorsvr!ThreadpoolMgr::intermediateThreadProc+0x44
1212ffec 00000000 791dbe2d 0d406838 00000000 kernel32!BaseThreadStart+0x52 [D:
tprivatewindowsaseclientsupport.c @ 460]
我们正在创建一个工作线程,然后坐下来等待工作。让我们看一看同一个堆栈,其中包含用于mscorsvr的导出符号:
0:054> kb ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 1212ef6c 7c57b3db 00000090 00009c40 00000000 ntdll!NtWaitForSingleObject+0xb *** ERROR: Symbol file could not be found. Defaulted to export symbols for mscorsvr.dll - 1212efa0 791dbe6e 00000000 00000000 00000000 kernel32!WaitForSingleObject+0xf 1212ffb4 7c57b388 0d406838 00000002 00000000 mscorsvr!GetCompileInfo+0x8e99 1212ffec 00000000 791dbe2d 0d406838 00000000 kernel32!lstrcmpiW+0xb
调试器很好,告诉我们找不到mscorsvr的符号文件,但是它给了我们一个函数名(从导出符号中),所以看起来我们正在调用一个名为GetCompileInfo的函数,嗯…很奇怪,它为什么选择这个名称?
如果我们要列出符号(导出mscorsvr的符号),我们会得到一个如下所示的列:
0:054> x mscorsvr!* 791b0000 mscorsvr!Ordinal73 = 791b0000 mscorsvr!Ordinal76 = 791b0000 mscorsvr!Ordinal77 = 791b0000 mscorsvr!Ordinal75 = 791b0000 mscorsvr!Ordinal78 = 791b0000 mscorsvr!Ordinal71 = 791b0000 mscorsvr!Ordinal79 = 791b0000 mscorsvr!Ordinal74 = 791b0000 mscorsvr!Ordinal72 = 791d2fd5 mscorsvr!GetCompileInfo = 791e0920 mscorsvr!GetAssemblyMDImport
所以我们在地址791dbe6e(791d2fd5+0x8e99)执行一些操作,如果在加载了公共符号的符号处取一个峰值,我们会看到该地址落在intermediateThreadProc函数中(确切地说是(+0x44)
791bbb78 mscorsvr!SimpleRWLock::LeaveRead = 793eaf50 mscorsvr!ExecutionManager::m_dwReaderCount = 791dbe2d mscorsvr!ThreadpoolMgr::intermediateThreadProc = 793035c5 mscorsvr!VtBoolMarshaler::UnmarshalComToNativeIn = 791f8e0d mscorsvr!LayoutClassPtrMarshaler::ClearNativeTemp =
所以本质上,它选择GetCompileInfo这个名字的原因是它能找到的最后一个符号,在我们正在执行的地址之前。
你可以看到这很容易让人困惑。顺便说一句,它不仅给了我们一个完全错误的函数名,而且如果您仔细看一下这两个堆栈,我们会丢失对堆栈的一个带有导出符号的完整函数调用(对WorkerThreadStart的调用)。原因是我们要查看的函数位于地址791b578b,并且该地址位于第一个导出函数之前,因此它甚至无法将其解析为假函数。
怎么知道符号是好的?
首先,通过查看堆栈,我们可以看到函数名看起来很奇怪。线程以lstrcmpiW开始是没有意义的,它们通常以BaseThreadStart或其他类似的东西开始。第二件事是,只要看一下堆栈,就会发现我们应该是GetCompileInfo函数中的0x8e99指令,也就是36505条指令,哇,这将是一个非常长的函数。
更确切的方法是在可执行文件上运行lmv。(注意mscorsvr之前的额外m用于匹配模式)
0:054> lmv mmscorsvr start end module name 791b0000 79418000 mscorsvr (export symbols) mscorsvr.dll Loaded symbol image file: mscorsvr.dll
Image path: C:WINNTMicrosoft.NETFrameworkv1.1.4322mscorsvr.dll
Image name: mscorsvr.dll
Timestamp: Thu Jul 15 09:26:32 2004 (40F631A8)
CheckSum: 002664D4
ImageSize: 00268000
File version: 1.1.4322.2032
Product version: 1.1.4322.2032
File flags: 20 (Mask 3F) Special
File OS: 4 Unknown Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0409.04e4
CompanyName: Microsoft Corporation
ProductName: Microsoft .NET Framework
InternalName: MSCORSVR.DLL
OriginalFilename: mscorsvr.dll
ProductVersion: 1.1.4322.2032
FileVersion: 1.1.4322.2032
FileDescription: Microsoft .NET Runtime Common Language Runtime - Server
LegalCopyright: Copyright © Microsoft Corporation 1998-2002. All rights reserved.
LegalTrademarks: Microsoft® is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation
Comments: Microsoft .NET Runtime Common Language Runtime - Server
Lmv会给你很多关于dll的好信息,但是在这个特殊的例子中,我们感兴趣的是在模块名之后打印的内容,在这个例子中是导出符号。(也可以通过运行lm为所有模块获取此信息)
最后一条评论是:正如您可能已经注意到的,到公共的Microsoft symbols服务器,您有时会得到公共符号,有时最终会得到mscorsvr.dll的导出符号。这是因为某些情况下,例如某些修补程序和专用版本尚未上载到公共符号服务器。
用于脱机调试的延迟符号加载和下载符号
如果运行lm,您将注意到许多dll和exe的符号将被列为延迟。这是因为调试器在启动时并没有实际加载所有符号(因为这将花费大量时间,特别是如果您有一个远程符号存储区的话)。符号按需加载,即运行kb(列出堆栈)或运行x检查特定可执行文件的符号,或运行需要符号查找的任何其他命令时。
有时,下载进程的所有符号是很有用的,一个常见的例子是,如果您的应用程序运行在没有internet访问的计算机上,并且您希望能够实时调试它。
在这种情况下,您可以这样做:
- 在加载所有模块时转储进程,并在具有internet访问权限的计算机上打开它。
- 将sympathy设置为srv*c: myappsymbols*http://msdl.microsoft.com/download/symbols;
- 运行.reload/i/f强制加载转储的所有符号
这将把Microsofts公共符号服务器上所有与DLL匹配的可用符号下载到c:myappsymbols中,然后您只需将此文件夹复制到没有internet访问的计算机上,并将其中的符号路径设置为srv*c:myappsymbols*c:myappsymbols;c:folderwithanyadditionalsymbols。
堆栈上的一些函数只显示为地址,这是怎么回事?
这真的和符号没有关系,但我觉得无论如何都值得一提。在下面的调用堆栈中,我们可以看到一个函数调用仅按其地址列出…0xd44334a,原因是它是一个托管函数,没有本机转换,毕竟windbg只是一个本机调试器。你可以跑!clrstack or !ip2md以获取.net函数名。
0:045> kb
ChildEBP RetAddr Args to Child
1182ecd0 7c59c5b7 7920d6a7 00000010 0f39c800 ntdll!NtYieldExecution+0xb [i386usrstubs.asm @ 2108]
1182ecd4 7920d6a7 00000010 0f39c800 00000010 kernel32!SwitchToThread+0x6 [D: tprivatewindowsaseclient hread.c @ 2611]
1182ed00 791c0283 0f39c800 00000010 00000000 mscorsvr!gc_heap::allocate_more_space+0xe3
1182ef28 791b6930 0f39c800 00000010 00000000 mscorsvr!GCHeap::Alloc+0x7b
1182ef3c 791c0359 00000010 00000000 00000000 mscorsvr!Alloc+0x3a
1182ef5c 791c03a7 0d421928 1182efec 062eff1c mscorsvr!FastAllocateObject+0x25
1182efc8 0d44334a 00000000 1182f01c 1182f040 mscorsvr!JIT_NewFast+0x2c
WARNING: Frame IP not in any known module. Following frames may be wrong.
1182effc 791b33ec 1182f114 791b3c96 1182f050 0xd44334a
1182f004 791b3c96 1182f050 00000000 1182f028 mscorsvr!CallDescrWorker+0x30
1182f114 791b5612 00bf1bdb 79b7a000 00000004 mscorsvr!MethodDesc::CallDescr+0x1b8
1182f1d0 791b56c6 79bf1bdb 79b7a000 79bd1d16 mscorsvr!MethodDesc::CallDescr+0x4f
1182f1f8 791b5c40 1182f210 0ba57968 0f39c7c8 mscorsvr!MethodDesc::Call+0x97
1182f2ac 791b5b97 0f3a6480 00000001 00000000 mscorsvr!AddTimerCallbackEx+0x165
1182f2c0 791b4de8 1182f374 791b3cf4 ffffffff mscorsvr!AddTimerCallback_Wrapper+0x13
1182f308 791b5ae5 0ba57968 791b5b84 1182f374 mscorsvr!Thread::DoADCallBack+0x5c
1182f3c4 791b59ce 0f3a6480 00000001 00000001 mscorsvr!AddTimerCallbackEx+0x165
1182f3d8 791b59a5 0f3a6480 00000001 791b5996 mscorsvr!AddTimerCallback+0x10
1182f3ec 791b5802 0f3a2230 0f3b6bf0 793ea988 mscorsvr!ThreadpoolMgr::AsyncTimerCallbackCompletion+0xf
1182f400 791b57c1 0f3b6bf0 00000000 791b5751 mscorsvr!ThreadpoolMgr::ExecuteWorkRequest+0x19
1182f420 791dbe6e 00000000 00000000 00000000 mscorsvr!ThreadpoolMgr::WorkerThreadStart+0x129
0:045> !clrstack
Loaded Son of Strike data table version 5 from "C:WINNTMicrosoft.NETFrameworkv1.1.4322mscorsvr.dll"
Thread 45
ESP EIP
0x1182efa4 0x77f88fe3 [FRAME: HelperMethodFrame]
0x1182efd0 0x0d44334a [DEFAULT] [hasThis] Void System.Timers.Timer.MyTimerCallback(Object)
0x1182f2cc 0x791b33ec [FRAME: ContextTransitionFrame]
托管dll的符号如何?我需要它们吗?
从技术上讲,使用windbg进行调试的答案是,您将很少或从不需要它们。如果您确实有托管符号,并且dll是在调试模式下编译的,那么您可以获得行号和源文件。使用时还可以获取一些参数信息!但总而言之,当使用像windbg这样的本机调试器时,从托管符号获得的额外“东西”很少。