.NET托管指针

文摘   2024-06-07 15:09   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

尽管大部分时候对于底层更喜欢C/C++和汇编,它们对于软/硬(件)的操控可以精确到bit。但是有些场景依然要用到托管指针,可以混合提高开发效率。本篇简略看下。

例子说明

一个最简单操作即是IntPtr类型,它虽然是一个nint,但却是一个货真价实的指针,类似于C语言的*符号。

声明:

IntPtr ptr;

如果要把托管def函数变成指针:

  示例函数:  public static void def()  {      Console.WriteLine("def");  }  函数委托:  delegate void delegatedef();  委托实例:  static delegatedef deldef ;

可以

IntPtr f = Marshal.GetFunctionPointerForDelegate(def);

把指针变成托管函数呢?

deldef = Marshal.GetDelegateForFunctionPointer(f, typeof(delegatedef)) as delegatedef;

然后直接调用即可:

deldef();

def函数是静态的,这里在.NET8里面会提示如下错误:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

此时可以通过反射获取函数指针:

 MethodInfo methodInfoo = typeof(Program).GetMethod("def", BindingFlags.Public | BindingFlags.Static); IntPtr functionPointer = methodInfoo.MethodHandle.GetFunctionPointer();

然后如上调用即可,这时候就不报错了,代码如下:

deldef = Marshal.GetDelegateForFunctionPointer(functionPointer , typeof(delegatedef)) as delegatedef;deldef();

上面是一个简单的操作,再来看操作IntPtr指针,,以上面指针functionPointer为例,向IntPtr指针指向的地址赋值:

 Marshal.WriteIntPtr(functionPointer,value);

读取IntPtr指针指向的地址值:

IntPtr ptr = Marshal.ReadIntPtr(functionPointer);

有时候读取/写入的指针指向的内存受到保护,比如不能读或者不能写,或者不能执行,这时候可以用API:VirtualProtect改写IntPtr指向内存的属性:

它的声明如下:

 [DllImport("kernel32.dll", SetLastError = true)] private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

比如我要让写入functionPointer的地方的内存属性能读,能写,能执行,代码如下,0x40即表示PAGE_EXECUTE_READWRITE。

 VirtualProtect(ptr1, 80x40out oldProtect); Marshal.WriteIntPtr(functionPointer,value); VirtualProtect(ptr1, 8, oldProtect, out oldProtect);

综合例子

上面大致介绍了托管指针的操作,下面看一个操作JIT的例子。通过托管和非托管互操,利用托管/非托管指针等知识。

JIT导出函数getJit

[DllImport("clrjit.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr getJit();

读取到JIT的编译函数CompileMethod,然后对这个函数进行def函数替换,也即是上面的functionPointer。这样的话,可以直接操作内存。代码如下:

 IntPtr ptr = getJit(); uint oldProtect; IntPtr ptr1 =  Marshal.ReadIntPtr(Marshal.ReadIntPtr(ptr)); VirtualProtect(ptr1, 8, 0x40, out oldProtect); Marshal.WriteIntPtr(ptr1, functionPointer); VirtualProtect(ptr1, 8, oldProtect, out oldProtect);

下面再来看下非托管的C++

//ConsoleApplication4.cpp#include <cstdint>#include<Windows.h>
void* GlobalPtr;DWORD OldProtect;
typedef int(*CompileMethodDelegate)(long long* compHnd, long long* methodInfo, unsigned flags, uint8_t** entryAddress, uint32_t* nativeSizeOfCode);
extern "C"  __declspec(dllexport)  int CompileMethodZhuanZhe(long long* compHnd, long long* methodInfo, unsigned flags, uint8_t** entryAddress, uint32_t* nativeSizeOfCode){ /* byte* ilcode = (byte*)(methodInfo+0x02);    byte* codesize = (byte*)(methodInfo + 0x3);*
VirtualProtect(GlobalPtr, 8, 0x40, &OldProtect); CompileMethodDelegate def = (CompileMethodDelegate)GlobalPtr;    VirtualProtect(GlobalPtr, 8, OldProtect,  &OldProtect); int nRet = def(compHnd, methodInfo, flags, entryAddress, nativeSizeOfCode); return 0;}
extern "C" __declspec(dllexport) compileMethod_def hook(void * intptr){ VirtualProtect(GlobalPtr, 8, 0x40, &OldProtect); GlobalPtr = intptr; VirtualProtect(GlobalPtr, 8, OldProtect, &OldProtect);
CompileMethodDelegate def = &CompileMethodZhuanZhe; return def;}

extern "C" __declspec(dllexport) int Add(int a, int b,int c,int d,int e) { //return Compile(a, b+1); return 0;}

此时C#就可以

 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr hook(IntPtr intptr);

调用

IntPtr ptr = getJit();uint oldProtect;ptr1 = Marshal.ReadIntPtr(ptr);IntPtr ptr2 = Marshal.ReadIntPtr(ptr1);IntPtr hookptr = hook(ptr2);VirtualProtect(ptr1, 80x40out oldProtect);Marshal.WriteIntPtr(ptr1, hookptr);VirtualProtect(ptr1, 8, oldProtect, out oldProtect);

这样托管指针,非托管指针,托管/非托管都进行了操作。以上简单的例子。

结尾

托管的指针同样可以达到非托管的效果,但是托管依然需要经过JIT编译,不如非托管来的直接。某些方面可以和非托管形成互补,已完成需要的需求以及项目疑难点,提高效率。

往期精彩回顾

.NET8安全漏洞,注意了

2024年5月编程语言排行榜:Go会挤掉C#吗?


江湖评谈
记录,分享,自由。
 最新文章