How to get current value of EIP in managed code?

杀马特。学长 韩版系。学妹 提交于 2019-12-01 04:05:47

With C# 5.0 there is a new, well-hidden feature that enables this.

Caller Info attributes

Note Apparently, there is also the Microsoft BCL Portability Pack 1.1.3 Nuget package so you can use the Caller Info Attributes in .NET 4.0.

What this does, is make your optional parameters magically have caller-dependent default values. It has

It has some pretty nifty features:

  • Caller Info values are emitted as literals into the Intermediate Language (IL) at compile time.
  • Unlike the results of the StackTrace property for exceptions, the results aren't affected by obfuscation.

The documentation sample looks like this:

// using System.Runtime.CompilerServices 
// using System.Diagnostics; 

public void DoProcessing()
{
    TraceMessage("Something happened.");
}

public void TraceMessage(string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
{
    Trace.WriteLine("message: " + message);
    Trace.WriteLine("member name: " + memberName);
    Trace.WriteLine("source file path: " + sourceFilePath);
    Trace.WriteLine("source line number: " + sourceLineNumber);
}

// Sample Output: 
//  message: Something happened. 
//  member name: DoProcessing 
//  source file path: c:\Users\username\Documents\Visual Studio 2012\Projects\CallerInfoCS\CallerInfoCS\Form1.cs 
//  source line number: 31 
sehe

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

sehe

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

Your best bet will be StackFrame(Int32):

Console.WriteLine(new System.Diagnostics.StackFrame(0).GetMethod().Name);
Console.WriteLine(new System.Diagnostics.StackFrame(0).GetNativeOffset());

More ideas

  • Use AOP (attribute based) instrumentation
  • Use Linfu or Cecil to dynamically emit the useful IDs

If you must, you can use code generator that will fill in the ids before compilation.

I would use the profiler API's, but if you want more performance try Enter/Leave Hooks.

I think your trying to have your cake and eat it too, performance + portability don't always go together. Link in some MASM64 for performance :)

I have another, (though highly experimental) idea, that is based on using expression trees to do the invocations to your method via a invoker and a facade.

Instead of normally calling your method, you would create an expression tree to invoke th facade from a given location at your code. This expression tree is passed to an invoker, that caches the compiled expression tree along with the caller information. The caller information can be retrieved once via StackTrace.GetMethod and cached against the expression tree.

From personal experience, since you only need a key for your invocation, you should store a MethodHandle only, instead of the full MethodBase object (dramatically reduces memory consumption).

To perform the real invocation, you can now examine the expression tree and build a new one to call the real implementation with either a dictionary containing your method level statics or pass it the caller method key.


Wow, this is really cool and fast as hell. Please provide feedback on the gist: https://gist.github.com/1047616

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace MethodStaticLocals
{
    class ExpensiveObject 
    {
        public ExpensiveObject()
        {
            Console.WriteLine( "Creating Expensive object" );
        }
    };

    class MainClass
    {
        public static void Main( string[] args )
        {
            Expression<Action> call = () => Func( "hello" );

            Invoke( call );
            Invoke( call );
        }

        // caches handles for expresisons, as they are expensive to find.
        static Dictionary<Expression, RuntimeMethodHandle> handleCache = new Dictionary<Expression, RuntimeMethodHandle>();
        // static locals are managed per method handle
        static Dictionary<RuntimeMethodHandle, Dictionary<string, object>> staticLocals = new Dictionary<RuntimeMethodHandle, Dictionary<string, object>>();
        // redirects are individual for each expression tree
        static Dictionary<Expression, Delegate> redirects = new Dictionary<Expression, Delegate>();

        static void Invoke( Expression<Action> call )
        {
            if (call.Parameters != null && call.Parameters.Any())
                throw new InvalidOperationException();

            if (call.Body.NodeType != ExpressionType.Call)
                throw new InvalidOperationException();

            Delegate redirectedInvocation = SetupRedirectedInvocation( call );

            redirectedInvocation.DynamicInvoke();

        }

        private static Delegate SetupRedirectedInvocation( Expression<Action> call )
        {
            Delegate redirectedInvocation;
            if (!redirects.TryGetValue( call, out redirectedInvocation ))
            {
                RuntimeMethodHandle caller = SetupCaller( call );

                Console.WriteLine( "Creating redirect for {0}", caller.Value );
                MethodCallExpression callExpression = (MethodCallExpression)call.Body;

                // add staticLocals dictionary as argument
                var arguments = callExpression.Arguments.ToList();
                arguments.Add( Expression.Constant( staticLocals[caller] ) );

                // todo: dynamically find redirect
                var redirect = MethodOf( () => Func( default( string ), default( Dictionary<string, object> ) ) );

                LambdaExpression redirectedExpression = Expression.Lambda( Expression.Call( callExpression.Object, redirect, arguments ), new ParameterExpression[0] );

                redirectedInvocation = redirectedExpression.Compile();
                redirects.Add( call, redirectedInvocation );
            }
            return redirectedInvocation;
        }

        private static RuntimeMethodHandle SetupCaller( Expression<Action> call )
        {
            RuntimeMethodHandle caller;
            if (!handleCache.TryGetValue( call, out caller ))
            {
                caller = new StackFrame( 1 ).GetMethod().MethodHandle;
                handleCache.Add( call, caller );
                staticLocals.Add( caller, new Dictionary<string, object>() );
            }
            return caller;
        }

        public static MethodInfo MethodOf( Expression<Action> expression )
        {
            MethodCallExpression body = (MethodCallExpression)expression.Body;
            return body.Method;
        }

        [Obsolete( "do not call directly" )]
        public static void Func( string arg )
        {
        }

        private static void Func( string arg, Dictionary<string, object> staticLocals )
        {
            if (!staticLocals.ContainsKey( "expensive"))
            {
                staticLocals.Add( "expensive", new ExpensiveObject() );
            }

            ExpensiveObject obj = (ExpensiveObject)staticLocals["expensive"];
            Console.WriteLine( "Func invoked: arg: {0}; expensive: {1}", arg, obj );
        }
    }
}

The output of which is:

Creating redirect for 92963900
Creating Expensive object
Func invoked: arg: hello; expensive: MethodStaticLocals.ExpensiveObject
Func invoked: arg: hello; expensive: MethodStaticLocals.ExpensiveObject
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!