12

I am trying to track down a bug in the mono runtime where a variable appears to be allocated to one valid object, and then is reassigned later to a bogus object, specifically

//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.

I would like to know when the reference to "o" becomes nonsense, and to do this am looking for a way to determine the address of "o" at various timepoints within the C# code. I know is similar to other questions with answers "don't do that there is a GC", but the GC doesn't work so I need a workaround.

Does anyone know how I can determine the address of a mono object in C#? Am fine to link in unmanaged code or whatever. (Any other clues to ways to diagnose the main issue appreciated to).

evolvedmicrobe
  • 2,672
  • 2
  • 22
  • 30
  • 2
    What is the *exact* error message? Is it about being disposed rather than "being nonsense"? – Jeroen Vannevel Aug 20 '14 at 16:44
  • 1
    Instead of locking on an `object`, could you create a disposable class with a finalizer -- but no actual resources -- then lock on that, and set breaks in the debugger when it is finalized? – dbc Aug 20 '14 at 16:48
  • The complete error is described here and happens downstream: https://bugzilla.xamarin.com/show_bug.cgi?id=21939 Basically, the GC treats a bad reference as an object, leading to a sigsegv – evolvedmicrobe Aug 20 '14 at 16:57
  • Is this all within the same method? – TyCobb Aug 20 '14 at 17:00
  • @dbc, sadly the relevant code is in the runtime class libraries (particularly Lazy.cs), so I am trying to fix the underlying bug rather than change the correct C# – evolvedmicrobe Aug 20 '14 at 17:00
  • the object allocation is in the constructor, the lock is taken further downstream in a method. Lines 87 and 150 in this file (https://github.com/mono/mono/blob/master/mcs/class/corlib/System/Lazy.cs) – evolvedmicrobe Aug 20 '14 at 17:03
  • This is an undebuggable problem, the address of an object constantly changes while the GC compacts the heap. You need to pursue the kind of bug you can fix and assume that this is caused by heap corruption. The kind produced by buffer overflow in unmanaged code. – Hans Passant Aug 20 '14 at 17:33
  • @HansPassant, hardest bug I have seen so far and certainly seems due to memory corruption. Though the address changes with each GC, it appears that when it does change to the bad value it is always assigned to the exact same bad (and bogus) value, so I am checking for when that occurs. My hope was to narrow down the interval when the corruption occurs by doing so. If you know of anything else that is useful for debugging mem corruption, would be grateful to hear it. – evolvedmicrobe Aug 20 '14 at 17:40

5 Answers5

14

You should be able to use the GCHandle construct to accomplish this.

GCHandle objHandle = GCHandle.Alloc(obj,GCHandleType.WeakTrackResurrection);
int address = GCHandle.ToIntPtr(objHandle).ToInt32(); 

Where 'obj' is the object whose address you're trying to get.

s.burns
  • 186
  • 4
  • Thanks, I tried this and it appears the "Address" does not correspond to a physical address, however it does change through time. I gather this is just the internal representation of the item. – evolvedmicrobe Aug 21 '14 at 14:03
  • 2
    The 'address' int should correspond to the current pointer in memory which may change due to standard internal memory management by the GC. If you need a fixed address you will have to make sure it is a pinned object in memory and use 'GCHandle.AddrOfPinnedObject(objHandle);' instead of GCHandle.ToIntPtr(objHandle);. Keep in mind, the GCHandle.Alloc() function makes sure that the object doesn't get garbage collected by internal memory management, so you'll have to manually deallocate that memory when it is no longer needed to prevent leaks. – s.burns Aug 21 '14 at 14:49
  • I believe the GCHandle.ToIntPtr points to the address of the handle, and not the object – evolvedmicrobe Aug 21 '14 at 16:08
  • Yeah, it will point to the integer of the GCHandle object itself. THat was an oversight on my part. However, the AddrOfPinndedObject will return the address of the object itself (which, again, has to be pinned in memory so that its address isn't changed by internal memory management). That should provide what you're working for. – s.burns Aug 21 '14 at 16:22
  • Sadly, trying to get handle leads to ObjectContains non-primitive or non-blittable data – evolvedmicrobe Aug 21 '14 at 16:57
6

Turns out this is not possible in .NET directly, but can be accomplished by altering the mono runtime code. To create a C# method that can read the memory address, make the following changes to the mono source code:

Alter gc-internal.h to add

gpointer    ves_icall_System_GCHandle_GetAddrOfObject (MonoObject *obj) MONO_INTERNAL;

Alter gc.c to add:

gpointer    ves_icall_System_GCHandle_GetAddrOfObject (MonoObject *obj) {
    return (char*)obj;
}

Alter GCHandle.cs to add:

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

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

Alter icall-def.h to add

ICALL(GCH_6, "GetAddrOfObject", ves_icall_System_GCHandle_GetAddrOfObject)

Note that these must be in order, so add it above the GetAddrOfPinnedObject line Rebuild

Finally, call it from C#

for (int i = 0; i < 100; i++) {
    object o = new object ();
    var ptr = GCHandle.AddrOfObject (o);
    Console.WriteLine ("Address: " + ptr.ToInt64().ToString ("x"));
}
evolvedmicrobe
  • 2,672
  • 2
  • 22
  • 30
2

My alternatives... Also @ This similar question

#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
Community
  • 1
  • 1
Jay
  • 3,276
  • 1
  • 28
  • 38
1

You can't get the address of a manged object in managed code, in general. If the object has a field like an int, you could take it's address with the fixed C# statement and then you'd have a pointer inside the object. For debugging purposes, you could make some assumptions and get the offset to the base pointer of the object (on 32 bit platforms the object header size on mono is 8 bytes, 16 bytes on 64 bit architectures, at this time).

Your bug report claims you're using the Boehm collector, though, and that collector doesn't move objects in memory, the bug could be caused by some unrelated memory corruption, by the object being incorrectly freed or some other logic bug in the GC (I'm not sure the zero size you pointed out is relevant, since a managed object has at least the 8-16 byte header).

lupus
  • 3,963
  • 1
  • 18
  • 13
  • Thanks, do you know of any method to get the address in unmanaged code (perhaps a method I could add to the mono source)? Knowing that the collector doesn't move objects is very helpful. I like the idea of using a simple type with an int, but unfortunately the error disappears when a type other than object is used. It does seem to be due to memory corruption, and the size_zero_object is only relevant as the memory is always written with that value, though it does not appear to be allocated that value originally. – evolvedmicrobe Aug 21 '14 at 14:17
1

There is a quick way to view the memory address allocated to a variable is:

Code

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

Output

Memory address:45687608

Explanation

The method GCHandle.AddrOfPinnedObject retrieves the address of an object in a Pinned handle.

Disassembly

You can view EVERY memory address allocated to each method and variable you should analize the JIT-compiled code with the Disassembly window in Visual Studio.

Enable the Disassembly by selecting Enable address-level debugging, under Tools > Options > Debugging > General.

Set the a brake point at the beginning of the application and start the debug. Once the application hit the brake-point open the Disassembly window by selecting Debug > Windows > Disassembly.

--- 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  
GTRekter
  • 905
  • 11
  • 21