分析动态 pinvoe

Profiling a dynamic pinvoke

本文关键字:pinvoe 动态      更新时间:2023-10-16

我正在使用 MSIL 分析器,遇到了ICorProfilerCallback接口的ManagedToUnmanagedTransitionUnmanagedToManagedTransition回调问题。

我要检索的是有关被调用方法的信息(它所在的名称和模块名称)。

到目前为止,它工作正常。直到所谓的动态 pinvoke 发生(详细描述:http://blogs.msdn.com/b/jonathanswift/archive/2006/10/03/dynamically-calling-an-unmanaged-dll-from-.net-_2800_c_23002900_.aspx)

在这种情况下,IMetaDataImport::GetPinvokeMap失败。此外,IMetaDataAssemblyImport::GetAssemblyProps返回"dynamic_pinvoke"作为程序集的名称。

profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataImport, (IUnknown**) &imd_import, &md_token);
imd_import->GetPinvokeMap(md_token, &mapping, module_name, buffer_size, &chars_read, &md_module_ref);
// here the fail occurs
profiler_1_0->GetTokenAndMetaDataFromFunction(function_id, IID_IMetaDataAssemblyImport, (IUnknown**) &imd_assembly_import, &md_token);
imd_assembly_import->GetAssemblyFromScope(&md_assembly);
imd_assembly_import->GetAssemblyProps(md_assembly, 0, 0, 0, assembly_name, buffer_size, &chars_read, 0, 0);
// assembly_name is set to "dynamic_pinvoke"

如何获取模块名称(.dll)和通过动态调用调用的函数名称?

探查器 API 通常通过 DllImportAttribute 返回托管代码中指定的元数据。在使用 Marshal.GetDelegateForFunctionPointer 方法的"动态 pinvoke"的情况下,模块和函数名称从未指定为元数据且不可用。包含所需元数据的动态 pinvoke 声明的替代方法可能会避免此问题。尝试使用 System.Reflection.Emit API,如 TypeBuilder.DefinePInvokeMethod 作为一种解决方案。

下面是一个使用 System.Reflection.Emit 的示例,它确实适用于探查器 API。

using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Reflection;
namespace DynamicCodeCSharp
{
    class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
        private delegate int MessageBoxFunc(IntPtr hWnd, string text, string caption, int options);
        static readonly Type[] MessageBoxArgTypes = new Type[] { typeof(IntPtr), typeof(string), typeof(string), typeof(int)};
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);
        static MethodInfo BuildMessageBoxPInvoke(string module, string proc)
        {
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(module), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(module);
            TypeBuilder typeBuilder = moduleBuilder.DefineType(proc);
            typeBuilder.DefinePInvokeMethod(proc, module, proc,
                       MethodAttributes.Static | MethodAttributes.PinvokeImpl,
                       CallingConventions.Standard, typeof
                       (int), MessageBoxArgTypes, 
                       CallingConvention.StdCall, CharSet.Auto);
            Type type = typeBuilder.CreateType();
            return type.GetMethod(proc, BindingFlags.Static | BindingFlags.NonPublic); ;
        }
        static MessageBoxFunc CreateFunc()
        {
            MethodInfo methodInfo = BuildMessageBoxPInvoke("user32.dll", "MessageBox");
            return (MessageBoxFunc)Delegate.CreateDelegate(typeof(MessageBoxFunc), methodInfo);
        }
        static void Main(string[] args)
        {
            MessageBoxFunc func = CreateFunc();
            func(IntPtr.Zero, "Hello World", "From C#", 0);
        }
    }
}

通过几个示例来说明当前方法的问题。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
static void Main(string[] args)
{
     MessageBox(IntPtr.Zero, "Hello World", "From C#", 0);
}

没有从用户 32.dll 导出的消息框函数。它只包含MessageBoxA和MessageBoxW。由于我们没有在 DllImport 属性中指定 ExactSpelling=false,并且我们的 CharSet 是 Unicode,.Net 也会在 user32.dll 中搜索附加 W 的入口点。这意味着 MessageBoxW 实际上是我们正在调用的本机函数。但是,GetPinvokeMap 返回"MessageBox"作为函数名称(代码中的module_name变量)。

现在让我们通过序号而不是名称调用函数。在 Windows SDK 中使用 dumpbin 程序:

dumpbin /exports C:WindowsSysWOW64user32.dll
...
2046  215 0006FD3F MessageBoxW
...

2046 是 MessageBoxW 的序号。调整我们的 DllImport 声明以使用我们得到的入口点字段:

[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "#2046")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);

这次 GetPInvokeMap 返回 "#2046"。我们可以看到探查器对正在调用的本机函数的"名称"一无所知。

更进一步,被调用的本机代码甚至可能没有名称。在以下示例中,在运行时在可执行内存中创建"Add"函数。从未将任何函数名称或库与正在执行的本机代码相关联。

using System;
using System.Runtime.InteropServices;
namespace DynamicCodeCSharp
{
    class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate int AddFunc(int a, int b);
        [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr VirtualAlloc(IntPtr addr, IntPtr size, int allocType, int protectType);
        const int MEM_COMMIT = 0x1000;
        const int MEM_RESERVE = 0x2000;
        const int PAGE_EXECUTE_READWRITE = 0x40;
        static readonly byte[] buf = 
            {
                // push ebp
                0x55,
                // mov ebp, esp
                0x8b, 0xec,
                // mov eax, [ebp + 8]
                0x8b, 0x45, 0x08,
                // add eax, [ebp + 8]
                0x03, 0x45, 0x0c,
                // pop ebp
                0x5d,
                // ret
                0xc3
            };
        static AddFunc CreateFunc()
        {
            // allocate some executable memory
            IntPtr code = VirtualAlloc(IntPtr.Zero, (IntPtr)buf.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            // copy our add function implementation into the memory
            Marshal.Copy(buf, 0, code, buf.Length);
            // create a delegate to this executable memory
            return (AddFunc)Marshal.GetDelegateForFunctionPointer(code, typeof(AddFunc));
        }
        static void Main(string[] args)
        {
            AddFunc func = CreateFunc();
            int value = func(10, 20);
            Console.WriteLine(value);
        }
    }
}