zoukankan      html  css  js  c++  java
  • 为什么我的堆栈上会有奇怪的函数名?(关于符号的讨论)

    符号可以包含有关全局变量、局部变量、函数名、参数、结构和源行号的信息。符号有三种类型:导出符号、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访问的计算机上,并且您希望能够实时调试它。

    在这种情况下,您可以这样做:

    1. 在加载所有模块时转储进程,并在具有internet访问权限的计算机上打开它。
    2. sympathy设置为srv*c: myappsymbols*http://msdl.microsoft.com/download/symbols;
    3. 运行.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这样的本机调试器时,从托管符号获得的额外“东西”很少。

  • 相关阅读:
    现在, Delphi 的多线程已经非常易用了!
    发现 TSplitter 在嵌套时不好用, 索性写了个替代品
    关于显示透空歌词的思路 回复 "zhaoboaidelphi" 的问题
    简单获取钢琴 88 个键的音高频率值
    准备理一下菜单和工具栏相关的组件
    在 StringGrid 上画线时, 使用 GDI+ 以消除锯齿 回复 "gsjn_8888_6666" 的问题
    解压 svgz 到 svg
    jQuery能做到,PHP能做到,C#也能做到
    监测ASP.NET应用程序性能最简单的方法
    支持高并发的IIS Web服务器常用设置
  • 原文地址:https://www.cnblogs.com/yilang/p/11947762.html
Copyright © 2011-2022 走看看