【问题标题】:How do you map a native to IL instruction pointer in-process您如何在进程中将本机映射到 IL 指令指针
【发布时间】:2010-09-16 23:00:53
【问题描述】:

在使用 .NET 框架的非托管 API 来分析 .NET 进程的进程时,是否可以查找与提供给 StackSnapshotCallback 函数的本机指令指针相关的 IL 指令指针?

很明显,我正在拍摄当前堆栈的快照,并希望在堆栈转储中提供文件和行号信息。 Managed Stack Explorer 通过查询 ISymUnmanagedMethod::GetSequencePoints 来完成此操作。这很好,但是序列点与偏移量相关联,到目前为止,我假设这些是从方法开始的偏移量(在中间语言中)。

在对他的博客帖子Profiler stack walking: Basics and beyond 的后续评论中,David Broman 表示可以使用ICorDebugCode::GetILToNativeMapping 实现此映射。但是,这并不理想,因为获取此接口需要从另一个调试器进程附加到我的进程。

我想避免这一步,因为我想在拍摄这些快照时继续能够从 Visual Studio 调试器中运行我的应用程序。它可以更轻松地单击输出窗口中的行号并转到有问题的代码。

该功能是可能的......您可以在托管代码中随意吐出一个行号堆栈跟踪,唯一的问题是它是否可访问。另外,我不想使用System::Diagnostics::StackTrace 或System::Environment::StackTrace 功能,因为出于性能原因,我需要延迟堆栈的实际转储......所以节省了解析方法名称和代码位置的成本稍后是可取的...以及混合本机和托管帧的能力。

【问题讨论】:

    标签: .net profiling clr-profiling-api


    【解决方案1】:

    为了从ICorProfilerInfo2::DoStackSnapshot 提供的本机指令指针转换为中间语言方法偏移量,您必须采取两个步骤,因为DoStackSnapshot 提供了FunctionID 和本机指令指针作为虚拟内存地址。

    第一步,将指令指针转换为原生代码方法偏移量。 (从 JITed 方法开始的偏移量)。这可以通过ICorProfilerInfo2::GetCodeInfo2来完成

    ULONG32 pcIL(0xffffffff);
    HRESULT hr(E_FAIL);
    COR_PRF_CODE_INFO* codeInfo(NULL);
    COR_DEBUG_IL_TO_NATIVE_MAP* map(NULL);
    ULONG32 cItem(0);
    
    UINT_PTR nativePCOffset(0xffffffff);
    if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functioId, 0, &cItem, NULL)) &&
        (NULL != (codeInfo = new COR_PRF_CODE_INFO[cItem])))
    {
        if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functionId, cItem, &cItem, codeInfo)))
        {
            COR_PRF_CODE_INFO *pCur(codeInfo), *pEnd(codeInfo + cItem);
            nativePCOffset = 0;
            for (; pCur < pEnd; pCur++)
            {
                // 'ip' is the UINT_PTR passed to the StackSnapshotCallback as named in
                // the docs I am looking at 
                if ((ip >= pCur->startAddress) && (ip < (pCur->startAddress + pCur->size)))
                {
                    nativePCOffset += (instructionPtr - pCur->startAddress);
                    break;
                }
                else
                {
                    nativePCOffset += pCur->size;
                }
    
            }
        }
        delete[] codeInfo; codeInfo = NULL;
    }
    

    第 2 步。一旦有了与 natvie 代码方法开头的偏移量,您就可以使用它来转换为使用 ICorProfilerInfo2::GetILToNativeMapping 的中间语言方法开头的偏移量。

    if ((nativePCOffset != -1) &&
        SUCCEEDED(hr = pInfo->GetILToNativeMapping(functionId, 0, &cItem, NULL)) &&
        (NULL != (map = new COR_DEBUG_IL_TO_NATIVE_MAP[cItem])))
    {
        if (SUCCEEDED(pInfo->GetILToNativeMapping(functionId, cItem, &cItem, map)))
        {
            COR_DEBUG_IL_TO_NATIVE_MAP* mapCurrent = map + (cItem - 1);
            for (;mapCurrent >= map; mapCurrent--)
            {
                if ((mapCurrent->nativeStartOffset <= nativePCOffset) && 
                    (mapCurrent->nativeEndOffset > nativePCOffset))
                {
                    pcIL = mapCurrent->ilOffset;
                    break;
                }
            }
        }
        delete[] map; map = NULL;
    }
    

    然后可以使用符号 API 将代码位置映射到文件和行号

    感谢Mithun Shanbhag 指导寻找解决方案。

    【讨论】:

    • 这里的小错误——第一个代码片段第17行的frame.pc应该是“instructionPtr”,也就是你的DoStackSnapshot回调的UINT_PTR ip参数。非常感谢史蒂文!你真的帮了我:)
    • 嗯...好点。我不记得 frame.pc 来自哪里。也许我试图让正在使用的东西变得明显。无论如何,带有评论来自何处的实际价值似乎是谨慎的。已编辑。谢谢指点。
    • 第 1 步和第 2 步解释得很好。但是,找到符号 API 并使用它似乎绝非易事。你能解释一下怎么做吗?
    【解决方案2】:
    Console.WriteLine("StackTrace: '{0}'", Environment.StackTrace);
    

    确保您的构建生成符号。

    展开讨论:

    很明显,我正在拍摄当前堆栈的快照,并希望在堆栈转储中提供文件和行号信息。

    鉴于此 - 看起来您不附加到流程的唯一原因是您可以在开发工具时轻松调试工具或其中的一部分。 IMO 是在可用时不选择更好的设计(ICorDebug 或 w/e)的糟糕借口。其糟糕设计的原因是因为您的代码在(可能)外部二进制文件的进程空间中执行,在已知(或更糟 - 未知)损坏的进程状态中导致讨厌的(“有时”罕见)副作用(包括破坏其他人的数据)。这应该足够开始了,但即使是这样,也有几个边缘案例与多线程代码等需要解决设计问题。

    大多数人通常会问“你真正想做什么?”作为对一种过于复杂的做事方式的回应。在大多数 情况下,有一种更简单/更容易的方法。为本机代码编写了堆栈跟踪器后,我知道它可能会变得混乱。

    现在也许你最终会让一切正常,所以 - 只需我的 $.02

    【讨论】:

    • 感谢您的建议,但问题表明我想延迟符号解析并能够解析非托管帧。这种方法不满足任何一个要求。我已将问题编辑得更明确。
    • 如果您在调试器中运行它,“调用堆栈”窗口可能已经在解析符号。非托管帧 - IMO 本机堆栈已经定义得非常好,并且已经存在 Win32 API 函数来执行此操作。例如。 StackWalk64、SymGetLineFromAddr64 和 SymGetModuleBase64。
    • 我正在修改泄漏检测工具以显示混合模式堆栈,并且不想实际解析所有堆栈。只是那些导致泄漏的。我不想在每次分配时都中断调试器。而且您描述的 dbghlp 函数仅适用于纯本机堆栈。
    • 您正在做的事情需要堆栈的托管端和本机端的合作,并且在双方都有帮助函数记录每一层中堆栈的当前状态。非常杂乱无章的设计,更不用说如果您正在记录已知的损坏状态,可能会导致堆栈展开灾难
    • 你有什么建议?更改软件设计以促进诊断工具的实施?
    猜你喜欢
    • 2011-09-26
    • 1970-01-01
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多