Search code examples
.netdebuggingprofilingclr

How to get source code information from a raw address of jitted .Net function?


I am working on a debugger that should store current stack traces when some function (for example, kernel32.dll!CreateFileW) is being called in the context of a process being debugged.

The debugger injects some code into the process, installs a hook. Then the hook gets stack trace using RtlCaptureStackBackTrace and the saves it somewhere.

Then debugger resolves entries of each stack trace using function SymFromAddr.

It works good for unmanaged code.

Today I have tried this approach for an MFC application built with CLR support. Now it's unmanaged code. The hook still gets a stack trace, but the debugger can't resolve some stack entries. I guess these entries belong to the code made by JIT compiler.

I am a bit familiar with CLR profilers and can get notifications like JITCompilationStarted.

The question is how to resolve (i.e. get source file name and line number) addresses of a code made by JIT compiler?

I can't just call DoStackSnapshot because the function being hooked can be called really a lot of time, so DoStackSnapshot would make the process slooow.

I could use FunctionEnter2/FunctionLeave, but they are called when execution enters/leaves a function and I can't get information about exact code line.

Thank you!


Solution

  • Although it's stated explicitly, it sounds like your question boils down to getting the source position from an instruction pointer, from within a managed profiler.

    Getting the Module, metadata token and IL offset.

    1. The first step is to get the FunctionID, in most cases this will already be given to you, but if not, you can get it from the instruction pointer using ICorProfilerInfo::GetFunctionFromIP

    2. Next, you can get the native <--> IL mappings for the function by passing your FunctionID into ICorProfilerInfo::GetILToNativeMapping. I vaguely recall that the mappings are returned in order, so you might be able to perform a binary search here.

    3. Pass the FunctionID into ICorProfilerInfo::GetFunctionInfo to get the ModuleID and method metadata token.

    4. Pass the ModuleID into ICorProfilerInfo3::GetModuleInfo2 to get the path. IIRC, the path will be returned via the name argument, but you should check pdwModuleFlags to make sure the module exists on disk, otherwise the name won't be meaningful (eg. if the module was emitted).

    Getting the filename and line number.

    This is performed using the symbol store api I would suggest doing this part in a monitoring application or post-processing step to avoid holding up the profiler.

    1. Get the IMetaDataImport for the module. You can use CorProfilerInfo::GetModuleMetaData if you are doing this from within the profiler or using IMetaDataDispenser if you are not in the profiler.

    2. Use ISymUnmanagedBinder::GetReaderForFile, ISymUnmanagedReader::GetMethod and ISymUnmanagedMethod::GetSequencePoints to get the IL <--> source mappings.