tags:

views:

725

answers:

3

When using the unmanaged API for the .NET framework to profile a .NET process in-process, is it possible to look up the IL instruction pointer that correlates to the native instruction pointer provided to the StackSnapshotCallback function?

As is probably obvious, I am taking a snapshot of the current stack, and would like to provide file and line number information in the stack dump. The Managed Stack Explorer does this by querying ISymUnmanagedMethod::GetSequencePoints. This is great, but the sequence points are associated to offsets, and I have so far assumed these are offsets from the beginning of the method ( in intermediate language ).

In a follow-up comment to his blog post Profiler stack walking: Basics and beyond, David Broman indicates that this mapping can be achieved using ICorDebugCode::GetILToNativeMapping. However, this is not ideal as getting this interface requires attaching to my process from another, debugger process.

I would like to avoid that step because I would like to continue to be able to run my application from within the visual studio debugger while I am taking these snapshots. It makes it easier to click on the line number in the output window and go to the code in question.

The functionality is possible.... you can spit out a line-numbered stack trace at will inside of managed code, the only question, is it accessible. Also, I don't want to use the System::Diagnostics::StackTrace or System::Environment::StackTrace functionality because, for performance reasons, I need to delay the actual dump of the stack.... so saving the cost for resolution of method names and code location for later is desirable... along with the ability to intermix native and managed frames.

A: 
Console.WriteLine("StackTrace: '{0}'", Environment.StackTrace);

Make sure your build generates symbols.

Expanding on the discussion:

As is probably obvious, I am taking a snapshot of the current stack, and would like to provide file and line number information in the stack dump.

Given this - It looks like the only reason you're not attaching to process is so that you can debug your tool , or parts of it , easily, as you're developing it. That IMO is a poor excuse for not choosing a better design (ICorDebug or w/e ) when its available. The reason its poor design is because your code executes in the process space of (presumably) external binaries , causing nasty ('sometimes' rare) side effects (including corrupting somebody else data) in known (or worse - unknown) corrupt process states. That should be enough to begin with, but even otherwise, there are several edge cases with multi-threaded code, etc where the design needs to be worked around.

Most people generally ask "What are you really trying to do?" as a reply to an overtly complex way of doing things. In most cases there is a simpler/easier way. Having written a stack tracer for native code, I know it can get messy.

Now maybe you might end up making everything work , so - Just my $.02

Kapil Kapre
Thanks for the suggestion, but the question states that I would like to delay resolution of symbols and be able to resolve unmanaged frames. This approach doesn't meet either requirement. I have edited the question to be a bit more explicit.
Steven
If you're running it in a debugger, chances are the 'call stack' window is already resolving symbols. Unmanaged frames - IMO native stack is already very well defined and there already exist Win32 API functions to do so. E.g. StackWalk64, SymGetLineFromAddr64 and SymGetModuleBase64.
Kapil Kapre
I am modifying a leak-detection tool to display mixed-mode stacks and don't want to actually resolve all stacks. Just those that lead to leaks. I don't want to break in the debugger on every allocation. And the dbghlp functions you describe only work on pure native stacks.
Steven
Kapil Kapre
What would you suggest? Change the design of the software to facilitate the implementation of a diagnostic tool?
Steven
A: 

You should post this question in the profiler msdn forum... where David Broman might be able to answer.

Peli
I was kinda hoping he read SO :)
Steven
+3  A: 

In order to translate from a native instruction pointer as provided by ICorProfilerInfo2::DoStackSnapshot to an intermediate language method offset, you must take two steps since DoStackSnapshot provides a FunctionID and native instruction pointer as a virtual memory address.

Step 1, is to convert the instruction pointer to a native code method offset. ( an offset from the beginning of the JITed method). This can be done with 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;
}

Step 2. Once you have an offset from the begining of the natvie code method, you can use this to convert to an offset from the begining of the intermediate language method using 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;
}

This can then be used to map the code location to a file and line number using the symbol APIs

Thanks to Mithun Shanbhag for direction in finding the solution.

Steven
Small mistake here - frame.pc in the line 17 of the first code fragment should be "instructionPtr", which is the UINT_PTR ip parameter of your DoStackSnapshot callback.Thanks a bunch Steven! You really helped me out :)
Omer Raviv
Hrm... good point. I don't remember where frame.pc came from. Maybe I was trying to make it obvious what was being used. In any case, the actual value with a comment about where it comes from seems prudent. Edited. Thanks for the pointer.
Steven