Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get current value of EIP in managed code?

Tags:

c#

.net

clr

The question seems like a dirty hack you should not do but let me explain first. The ultimate goal is to have method local statics like in C++.

void Func()
{
   static methodLocalObject = new ExpensiveThing();
   // use methodlocal Object
}

What has this to do with the instruction pointer? I want to cache data depending on my caller. To make this fast I walk back in the stack to get the address of my caller and use this as unique key for a dictionary to store the data. That would allow to create a reflection based tracer which does not use Reflection every time to get the name of the current method and type but only once and store the reflection infos in a hash table.

The answers so far were only mono based. I want to try a generic solution which works on .NET 3.5/4.0 32/64 bit. I do know that the calling convention for 64 bit is quite different so it could get challenging to get something reliable. But on the other hand I have full control inside my method how the stack does look like. The stack does look very different between .NET 3.5 and 4.0 and it differs of course also between release builds. I still have to check if NGen does create code with a different stack layout as well. One possibility would be to use a C++ helper method which takes 5 magic integer arguments (on x64 only the 5th will be on the stack) and check where I can find them on the stack. Another possiblity would be to simply use the whole stack until I find my magic marker on the stack as a key and use this part of the stack as unique enough key. But I am not sure if this approach can work at all or if there are better alternatives. I do know I can walk the stack in a safe way via the profiling or debugging apis but neither of them are fast.

For a tracing library the usual approach is to walk the stack using reflection to get the current method name and type.

class Tracer
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public Tracer()
    {
        StackFrame frame = new StackTrace().GetFrame(1); // get caller
        Console.WriteLine("Entered method {0}.{1}", frame.GetMethod().DeclaringType.FullName, frame.GetMethod().Name);
    }

}

But this is very slow. The other solution is to pass the data directly via strings which is much faster but it needs more typing. The alternate solution would be to use the instruction pointer of the calling function (if this can be determined in a very fast way) to get around the expensive reflection calls. Then this would be possible:

class Tracer
{
    static Dictionary<Int64, string> _CachedMethods = new Dictionary<Int64, string>();

    [MethodImpl(MethodImplOptions.NoInlining)]
    public Tracer()
    {
        Int64 eip = GetEIpOfParentFrame();
        string name;
        lock (_CachedMethods)
        {
            if (!_CachedMethods.TryGetValue(eip, out name))
            {
                var callingMethod = new StackTrace().GetFrame(1).GetMethod();
                name =  callingMethod.DeclaringType + "." + callingMethod.Name;
                _CachedMethods[eip] = name;
            }
        }
        Console.WriteLine("Entered method {0}", name);

    }

    Int64 GetEIpOfParentFrame()
    {
        return 0; // todo this is the question how to get it
    }

}

I know that the solution needs to be unmanaged. In C++ there is a compiler intrinsic called _ReturnAddress but according to the docs it does not work with managed code. Another way to ask the same question: Does anybody know the calling convention and stack layout for managed methods for .NET 3.5/4 x32/x64?

Yours, Alois Kraus

like image 766
Alois Kraus Avatar asked Apr 17 '11 19:04

Alois Kraus


People also ask

How do you read the EIP Register?

The only way to read the EIP register is to execute a CALL instruction and then read the value of the return instruction pointer from the procedure stack. The EIP register can be loaded indirectly by modifying the value of a return instruction pointer on the procedure stack and executing a return instruction

Why is the EIP of a function stored on the stack?

The fact that it's stored on stack can help you to find out the EIP at the point of the call of some method which could retrieve that value from stack. It would be the offset in the code segment representing return address from such function. So, you can store this register in another register indirectly.

What is the function of the EIP Register in x86?

EIP is a register in x86 architectures (32bit). It holds the "Extended Instruction Pointer" for the stack. In other words, it tells the computer where to go next to execute the next command and controls the flow of a program. Research Assembly language to get a better understanding of how registers work.

What is the use of EIP and EAX?

The simple part is that value in EIP is used to determine the location of the next instruction whether this sends you back to a previous instruction or forward in straight-line code. The results of what your previous instruction came up with is stored in EAX.


1 Answers

Update This answer is now obsolete for recent version of .NET: see here How to get current value of EIP in managed code?

The real short answer is: the CLR VM is a stack machine, so no EIP there. The slightly longer answer is: if you rely on undocumented implementation-specific details, you could extrapolate a useable ID from the CPU EIP in unmanaged code.

Proof Of Concept

I just managed the following proof of concept, using mono 2.11 on Linux 32-bit. I hope the information might help. This implements unmanaged functions:

extern static string CurrentMethodDisplay();
extern static uint CurrentMethodAddress();

Native source: tracehelper.c [1]:

#include <string.h>

void* CurrentMethodAddress()
{
    void* ip;
    asm ("movl 4(%%ebp),%0" : "=r"(ip) );
    return ip;
}

const char* const MethodDisplayFromAddress(void* ip);
const char* const CurrentMethodDisplay()
{
    return MethodDisplayFromAddress(CurrentMethodAddress());
}

#ifndef USE_UNDOCUMENTED_APIS
extern char * mono_pmip (void *ip);

const char* const MethodDisplayFromAddress(void* ip)
{
    const char* text = mono_pmip(ip);
    return strdup(text? text:"(unknown)");
}
#else

/* 
 * undocumented structures, not part of public API
 *
 * mono_pmip only returns a rather ugly string representation of the stack frame
 * this version of the code tries establish only the actual name of the method
 *
 * mono_pmip understands call trampolines as well, this function skips those
 */
struct _MonoDomain; // forward
struct _MonoMethod; // forward
typedef struct _MonoDomain  MonoDomain;
typedef struct _MonoMethod  MonoMethod;
struct _MonoJitInfo { MonoMethod* method; /* rest ommitted */ };

typedef struct _MonoJitInfo MonoJitInfo;

MonoDomain *mono_domain_get(void);
char* mono_method_full_name(MonoMethod *method, int signature);
MonoJitInfo *mono_jit_info_table_find(MonoDomain *domain, char *addr);

const char* const MethodDisplayFromAddress(void* ip)
{
    MonoJitInfo *ji = mono_jit_info_table_find (mono_domain_get(), ip);
    const char* text = ji? mono_method_full_name (ji->method, 1) : 0;
    return text? text:strdup("(unknown, trampoline?)");
}

#endif

C# Source (client.cs) to call this native library function:

using System;
using System.Runtime.InteropServices;

namespace PoC
{
    class MainClass
    {
        [DllImportAttribute("libtracehelper.so")] extern static string CurrentMethodDisplay();
        [DllImportAttribute("libtracehelper.so")] extern static uint CurrentMethodAddress();

        static MainClass()
        {
            Console.WriteLine ("TRACE 0 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
        }

        public static void Main (string[] args)
        {
            Console.WriteLine ("TRACE 1 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
            {
                var instance = new MainClass();
                instance.OtherMethod();
            }
            Console.WriteLine ("TRACE 2 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
            {
                var instance = new MainClass();
                instance.OtherMethod();
            }
            Console.WriteLine ("TRACE 3 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
            Console.Read();
        }

        private void OtherMethod()
        {
            ThirdMethod();
            Console.WriteLine ("TRACE 4 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
        }

        private void ThirdMethod()
        {
            Console.WriteLine ("TRACE 5 {0:X8} {1}", CurrentMethodAddress(), CurrentMethodDisplay());
        }
    }
}

Compile and link using Makefile:

CFLAGS+=-DUSE_UNDOCUMENTED_APIS
CFLAGS+=-fomit-frame-pointer
CFLAGS+=-save-temps
CFLAGS+=-g -O3

all: client.exe libtracehelper.so

client.exe: client.cs | libtracehelper.so
    gmcs -debug+ -optimize- client.cs 

tracehelper.s libtracehelper.so: tracehelper.c
    gcc -shared $(CFLAGS) -lmono -o $@ tracehelper.c 
#   gcc -g -O0 -shared -fomit-frame-pointer -save-temps -lmono -o $@ tracehelper.c 

test: client.exe
    LD_LIBRARY_PATH=".:..:/opt/mono/lib/" valgrind --tool=memcheck --leak-check=full --smc-check=all --suppressions=mono.supp mono --gc=sgen --debug ./client.exe

clean:
    rm -fv *.so *.exe a.out *.[iso] *.mdb

Running this with LD_LIBRARY_PATH=. ./client.exe results in:

TRACE 0 B57EF34B PoC.MainClass:.cctor ()
TRACE 1 B57EF1B3 PoC.MainClass:Main (string[])
TRACE 5 B57F973B PoC.MainClass:ThirdMethod ()
TRACE 4 B57F96E9 PoC.MainClass:OtherMethod ()
TRACE 2 B57EF225 PoC.MainClass:Main (string[])
TRACE 5 B57F973B PoC.MainClass:ThirdMethod ()
TRACE 4 B57F96E9 PoC.MainClass:OtherMethod ()
TRACE 3 B57EF292 PoC.MainClass:Main (string[])

Note that this is on Mono 2.11. It works on 2.6.7 as well, with and without optimization.

[1] I learned GNU extended asm for this purpose; thanks SO!

Conclusions ?

Delivered a proof of concept; this implementation is specific to Mono. A similar 'trick' could be delivered on MS .Net (using a ::LoadLibrary of SOS.dll, perhaps?) but is left as an exercise for the reader :)

I would personally still go with my other answer, but I suppose I succumbed to the challenge and, like I've said before: YMMV, Here be dragons, TIMTOWTDI, KISS etc.

Good night

like image 112
sehe Avatar answered Nov 07 '22 04:11

sehe