获取 .NET 对象的内存地址 (C#)

12
我试图追踪单进程运行时中的一个漏洞,其中一个变量似乎被分配给了一个有效的对象,然后稍后又被重新分配给了一个虚假的对象,具体如下:
//early in code I allocate, fine
var o = new object(); // valid allocation
// later in code this is called, not fine
lock(o) // <- is triggering bug due to "o" now referencing a nonsense memory location.

我想知道何时将“o”的引用变得无意义,并寻找一种确定C#代码中各个时间点上“o”的地址的方法。我知道这类似于其他问题,答案是“不要这样做,因为有垃圾回收”,但垃圾回收不起作用,所以我需要一个解决方法。

有人知道如何在C#中确定mono对象的地址吗?我可以链接未管理的代码或任何其他提示,以诊断主要问题。


2
确切的错误信息是什么?它是关于被处理而不是“胡说八道”吗? - Jeroen Vannevel
1
你可以创建一个可处理的类,没有实际资源,然后在该类上加锁,并在调试器中设置断点来代替锁定一个“对象”吗? - dbc
完整的错误描述在这里,并且发生在下游:https://bugzilla.xamarin.com/show_bug.cgi?id=21939 基本上,GC将一个坏引用视为对象,导致sigsegv。 - evolvedmicrobe
@dbc,不幸的是相关代码在运行时类库中(特别是Lazy.cs),所以我正在尝试修复根本性的错误而不是更改正确的C#。 - evolvedmicrobe
这是一个无法调试的问题,当GC压缩堆时,对象的地址不断变化。你需要追踪那些可以修复的错误,并假设这是由非托管代码中的缓冲区溢出引起的堆损坏。 - Hans Passant
显示剩余3条评论
5个回答

14
你可以使用GCHandle结构来实现这一点。
GCHandle objHandle = GCHandle.Alloc(obj,GCHandleType.WeakTrackResurrection);
int address = GCHandle.ToIntPtr(objHandle).ToInt32(); 

其中'obj'是您想要获取地址的对象。


谢谢,我尝试了一下,似乎“地址”并不对应于物理地址,但它会随着时间改变。我猜这只是该项的内部表示。 - evolvedmicrobe
2
“address” int 应该对应于内存中的当前指针,由于 GC 的标准内部内存管理可能会发生变化。如果您需要一个固定的地址,您必须确保它是内存中的固定对象,并使用“GCHandle.AddrOfPinnedObject(objHandle);”而不是“GCHandle.ToIntPtr(objHandle);”。请记住,GCHandle.Alloc() 函数确保对象不会被内部内存管理垃圾回收,因此当不再需要时,您必须手动释放该内存以防止泄漏。 - s.burns
我相信 GCHandle.ToIntPtr 指向句柄的地址,而不是对象的地址。 - evolvedmicrobe
是的,它将指向GCHandle对象本身的整数。那是我的疏忽。但是,AddrOfPinnedObject将返回对象本身的地址(再次强调,必须将其固定在内存中,以便其地址不会被内部内存管理更改)。这应该提供了您正在寻找的内容。 - s.burns
遗憾的是,尝试获取句柄会导致ObjectContains非原始或非可平坦数据。 - evolvedmicrobe

6

事实证明,直接在.NET中不可能实现这一点,但可以通过修改mono运行时代码来实现。要创建一个可以读取内存地址的C#方法,请对mono源代码进行以下更改:

修改gc-internal.h文件以添加:

gpointer    ves_icall_System_GCHandle_GetAddrOfObject (MonoObject *obj) MONO_INTERNAL;

修改gc.c文件,添加以下内容:
gpointer    ves_icall_System_GCHandle_GetAddrOfObject (MonoObject *obj) {
    return (char*)obj;
}

修改 GCHandle.cs 添加以下内容:

MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern static IntPtr GetAddrOfObject(object obj);

public static IntPtr AddrOfObject(object o)
{
    IntPtr res = GetAddrOfObject(o);
    return res;
}

请修改icall-def.h文件以添加以下内容:

ICALL(GCH_6, "GetAddrOfObject", ves_icall_System_GCHandle_GetAddrOfObject)

请注意,这些必须按顺序排列,因此请将其添加到GetAddrOfPinnedObject行之前 重新构建
最后,从C#中调用它
for (int i = 0; i < 100; i++) {
    object o = new object ();
    var ptr = GCHandle.AddrOfObject (o);
    Console.WriteLine ("Address: " + ptr.ToInt64().ToString ("x"));
}

2

我的替代方案... 还有 @ 这个类似的问题


#region AddressOf

    /// <summary>
    /// Provides the current address of the given object.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOf(object obj)
    {
        if (obj == null) return System.IntPtr.Zero;

        System.TypedReference reference = __makeref(obj);

        System.TypedReference* pRef = &reference;

        return (System.IntPtr)pRef; //(&pRef)
    }

    /// <summary>
    /// Provides the current address of the given element
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    /// <returns></returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOf<T>(T t)
        //refember ReferenceTypes are references to the CLRHeader
        //where TOriginal : struct
    {
        System.TypedReference reference = __makeref(t);

        return *(System.IntPtr*)(&reference);
    }

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    static System.IntPtr AddressOfRef<T>(ref T t)
    //refember ReferenceTypes are references to the CLRHeader
    //where TOriginal : struct
    {
        System.TypedReference reference = __makeref(t);

        System.TypedReference* pRef = &reference;

        return (System.IntPtr)pRef; //(&pRef)
    }

    /// <summary>
    /// Returns the unmanaged address of the given array.
    /// </summary>
    /// <param name="array"></param>
    /// <returns><see cref="IntPtr.Zero"/> if null, otherwise the address of the array</returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOfByteArray(byte[] array)
    {
        if (array == null) return System.IntPtr.Zero;

        fixed (byte* ptr = array)
            return (System.IntPtr)(ptr - 2 * sizeof(void*)); //Todo staticaly determine size of void?
    }

    #endregion

1
一般情况下,您无法在托管代码中获取托管对象的地址。如果该对象具有像 int 之类的字段,则可以使用 fixed C# 语句获取其地址,然后您将拥有对象内部的指针。为了调试目的,您可以做出一些假设,并获取对象基指针的偏移量(在 32 位平台上,mono 上的对象头大小为 8 字节,在 64 位架构上为 16 字节,此时)。
然而,您的错误报告声称您正在使用 Boehm 收集器,该收集器不会移动内存中的对象,但是该 bug 可能是由一些与内存损坏无关的原因引起的,例如对象被错误释放或 GC 中存在其他逻辑 bug(我不确定您指出的零尺寸是否相关,因为托管对象至少有 8-16 字节的头部)。

谢谢,你知道获取非托管代码中地址的方法吗(也许我可以将其添加到mono源代码中)?知道收集器不会移动对象非常有帮助。我喜欢使用一个带有int的简单类型的想法,但不幸的是,当使用除object以外的类型时,错误会消失。它似乎是由于内存损坏引起的,而size_zero_object只与内存始终写入该值有关,尽管最初似乎没有分配该值。 - evolvedmicrobe

1

有一种快速查看变量分配的内存地址的方法是:

代码

string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");

输出

Memory address:45687608

说明

方法GCHandle.AddrOfPinnedObject检索固定句柄中对象的地址。

反汇编

您可以使用Visual Studio中的反汇编窗口查看每个方法和变量分配的每个内存地址,应该分析JIT编译代码。

通过在工具>选项>调试>常规下选择启用地址级调试来启用反汇编。

在应用程序开头设置断点并开始调试。一旦应用程序达到断点,请通过选择调试>窗口>反汇编来打开反汇编窗口。

--- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
        {
0066084A  in          al,dx  
0066084B  push        edi  
0066084C  push        esi  
0066084D  push        ebx  
0066084E  sub         esp,4Ch  
00660851  lea         edi,[ebp-58h]  
00660854  mov         ecx,13h  
00660859  xor         eax,eax  
0066085B  rep stos    dword ptr es:[edi]  
0066085D  cmp         dword ptr ds:[5842F0h],0  
00660864  je          0066086B  
00660866  call        744CFAD0  
0066086B  xor         edx,edx  
0066086D  mov         dword ptr [ebp-3Ch],edx  
00660870  xor         edx,edx  
00660872  mov         dword ptr [ebp-48h],edx  
00660875  xor         edx,edx  
00660877  mov         dword ptr [ebp-44h],edx  
0066087A  xor         edx,edx  
0066087C  mov         dword ptr [ebp-40h],edx  
0066087F  nop  
            Sealed sealedClass = new Sealed();
00660880  mov         ecx,584E1Ch  
00660885  call        005730F4  
0066088A  mov         dword ptr [ebp-4Ch],eax  
0066088D  mov         ecx,dword ptr [ebp-4Ch]  
00660890  call        00660468  
00660895  mov         eax,dword ptr [ebp-4Ch]  
00660898  mov         dword ptr [ebp-3Ch],eax  
            sealedClass.DoStuff();
0066089B  mov         ecx,dword ptr [ebp-3Ch]  
0066089E  cmp         dword ptr [ecx],ecx  
006608A0  call        00660460  
006608A5  nop  
            Derived derivedClass = new Derived();
006608A6  mov         ecx,584F3Ch  
006608AB  call        005730F4  
006608B0  mov         dword ptr [ebp-50h],eax  
006608B3  mov         ecx,dword ptr [ebp-50h]  
006608B6  call        006604A8  
006608BB  mov         eax,dword ptr [ebp-50h]  
006608BE  mov         dword ptr [ebp-40h],eax  
            derivedClass.DoStuff();
006608C1  mov         ecx,dword ptr [ebp-40h]  
006608C4  mov         eax,dword ptr [ecx]  
006608C6  mov         eax,dword ptr [eax+28h]  
006608C9  call        dword ptr [eax+10h]  
006608CC  nop  
            Base BaseClass = new Base();
006608CD  mov         ecx,584EC0h  
006608D2  call        005730F4  
006608D7  mov         dword ptr [ebp-54h],eax  
006608DA  mov         ecx,dword ptr [ebp-54h]  
006608DD  call        00660490  
006608E2  mov         eax,dword ptr [ebp-54h]  
006608E5  mov         dword ptr [ebp-44h],eax  
            BaseClass.DoStuff();
006608E8  mov         ecx,dword ptr [ebp-44h]  
006608EB  mov         eax,dword ptr [ecx]  
006608ED  mov         eax,dword ptr [eax+28h]  
006608F0  call        dword ptr [eax+10h]  
006608F3  nop  
        }
0066091A  nop  
0066091B  lea         esp,[ebp-0Ch]  
0066091E  pop         ebx  
0066091F  pop         esi  
00660920  pop         edi  
00660921  pop         ebp  

00660922  ret  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接