locked
NotImplementedException when using ICorDebugHeapEnum::Next() or GetCount()

    คำถาม

  • Hi

    I'm busy trying to consume ICorDebugHeapEnum and I've hit a problem: when I try and access any of it's methods I get a NotImplementedException.

    I've got some .Net 4 code that uses ICLRDebugging etc to debug crash dumps. I've updated the code to now target the 4.5 SDK preview and I'm able to get a get a working reference to ICorDebugProcess5. I can call GetGCHeapInformation() without any problems (GC structures are valid, I have 1 heap and my pointer size is 4 bytes). I can also get an enumerator from EnumerateHeapRegions() and use it without any problems (I've got 4 segments: 1 for Gen0, 1 for Gen1 one for Gen2 and one for LOH - all on heap number 0).

    I can successully call EnumerateHeap() and it returns a valid enumerator (well it's not null at least), but if I call GetCount() or Next() I get a NotImplementedException.

    My upgrade to the 4.5 SDK preview and to getting a working reference to ICorDebugProcess5 wasn't smooth, so there's a very good chance I've done something wrong somewhere along the way (more on that in another question).

    Is there any specific reason why calling GetCount() or Next() on ICorDebugHeapEnum (I haven't tried Skip(), Clone() etc) would return a NotImplementedException? Or is it more likely that I've done something wrong?

    I've defined ICorDebugHeapEnum like this:

        [ComImport, Guid("76D7DAB8-D044-11DF-9A15-7E29DFD72085"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface ICorDebugHeapEnum
        {
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            void Skip([In] uint celt);
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            void Reset();
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            void Clone([MarshalAs(UnmanagedType.Interface)] out ICorDebugEnum ppEnum);
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            void GetCount(out uint pcelt);
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            int Next([In] uint celt, [MarshalAs(UnmanagedType.LPArray)] [Out] COR_HEAPOBJECT[] objects, out uint pceltFetched);
        }

    My interop skills aren't amazing, but this is the same kind of definition I've used for ICorDebugHeapSegmentEnum, and it works fine there (although it would be great to know if there is anything I could do better regarding the interop).

    Lastly I'm using Visual Studio 11 preview on 64bit Windows 7 Home Premium. I generated the sample crash dump by running ".dump /map <path>" in the WinDbg included in the Windows 8 SDK preview.

    Thanks

    Greg

    Edit: I've upgraded to the Windows 8 consumer preview and tried EnumerateHeapObjects(), EnumerateGcReferences() and EnumerateHandles(). They seem to return successfully, but I get NotImplementedException whenever I try and access GetCount() or Next().



    • แก้ไขโดย Greg Nagel 11 มีนาคม 2555 8:58
    5 มีนาคม 2555 15:27

คำตอบ

  • Hi Greg,

      My coworker Lee is having some trouble with posting but while we sort it out I can forward on the initial things he told me.

    GetCount() - this is expected to return E_NOTIMPL and will continue to do so at RTM. We don't want to walk the whole heap to provide this answer because it would be very very slow.

    Next() - this one should be working in the beta already, so your E_NOTIMPL result is surprising.

    Taking a quick look at your interop definition I don't know if this will rectify your bug, but I would add [PreserveSig] to the Next() method. This will cause the HRESULT to be returned rather than an exception thrown. Without PreserveSig managed code assumes the managed return value corresponds to an additional out parameter on the native method. Something like:

    HRESULT Next(int celt, COR_HEAPOBJECT* pObjs, int *pceltFetched, int *pSomeOtherOutArgumentThatDoesntExistInThisCase)  

    Also if the [MethodImpl] attributes look messy you can feel free to remove them. Mdbg used to have them as an artifact of the conversion process to managed interface definitions, and that is probably what you used as a template. In fact the runtime just ignores them.

    HTH, and we'll try to get Lee unblocked so he can talk with you directly. Thanks,

     -Noah

    • เสนอเป็นคำตอบโดย Noah Falk - MSFTMicrosoft, Moderator 26 เมษายน 2555 5:52
    • ทำเครื่องหมายเป็นคำตอบโดย Greg Nagel 8 พฤษภาคม 2555 5:33
    19 เมษายน 2555 5:29
    ผู้ดูแล

ตอบทั้งหมด

  • .Net 4.5 are in beta. Please wait and check it after it's RTMed.

    Best Regards,

    Han Xia

    • ทำเครื่องหมายเป็นคำตอบโดย Greg Nagel 16 เมษายน 2555 9:15
    • ยกเลิกการทำเครื่องหมายเป็นคำตอบโดย Noah Falk - MSFTMicrosoft, Moderator 19 เมษายน 2555 5:17
    16 เมษายน 2555 9:12
  • Hi Greg,

      My coworker Lee is having some trouble with posting but while we sort it out I can forward on the initial things he told me.

    GetCount() - this is expected to return E_NOTIMPL and will continue to do so at RTM. We don't want to walk the whole heap to provide this answer because it would be very very slow.

    Next() - this one should be working in the beta already, so your E_NOTIMPL result is surprising.

    Taking a quick look at your interop definition I don't know if this will rectify your bug, but I would add [PreserveSig] to the Next() method. This will cause the HRESULT to be returned rather than an exception thrown. Without PreserveSig managed code assumes the managed return value corresponds to an additional out parameter on the native method. Something like:

    HRESULT Next(int celt, COR_HEAPOBJECT* pObjs, int *pceltFetched, int *pSomeOtherOutArgumentThatDoesntExistInThisCase)  

    Also if the [MethodImpl] attributes look messy you can feel free to remove them. Mdbg used to have them as an artifact of the conversion process to managed interface definitions, and that is probably what you used as a template. In fact the runtime just ignores them.

    HTH, and we'll try to get Lee unblocked so he can talk with you directly. Thanks,

     -Noah

    • เสนอเป็นคำตอบโดย Noah Falk - MSFTMicrosoft, Moderator 26 เมษายน 2555 5:52
    • ทำเครื่องหมายเป็นคำตอบโดย Greg Nagel 8 พฤษภาคม 2555 5:33
    19 เมษายน 2555 5:29
    ผู้ดูแล
  • Thanks for your reply Noah. I did a quick test and it looks like that was the problem!

    I'll do some more thorough testing this week and post the results here.

    Greg

    22 เมษายน 2555 20:17
  • Great, glad that helped. In the meantime I have proposed my response above as the answer and you can confirm or deny after you further testing is complete. Good luck!

     -Noah

    26 เมษายน 2555 5:52
    ผู้ดูแล
  • I've done some more testing and things have gotten more interesting.

    I've run the "dumpheap -stat" command in windbg and it returns a total of 6177 objects in my crash dump.

    When I'm looping through the heap using my code, ICorDebugHeapEnum::Next() fails when it reaches 6173 objects, 4 short of where it should end? pceltFetched is 0, and the HRESULT returned is 1.

    "1" doesn't seem like a typical failure HRESULT, so I thought it might indicate something like "no more items left", but the current MSDN documentation for ICorDebugHeapEnum::Next Method doesn't indicate any special return values for the HRESULT.

    btw - the documention for ICorDebugHeapEnum::Next Method is a bit inaccurate at the moment: the method signature indicates "COR_SEGMENT" as the array type when it should be "COR_HEAPOBJECT".

    I've double checked all of my COM interop and I'm pretty sure I've got it all correct now. Just in case I've still got something wrong here are my current interop definitions:

        [StructLayout(LayoutKind.Sequential)]
        public struct COR_TYPEID
        {
            public ulong token1;
            public ulong token2;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct COR_HEAPOBJECT
        {
            public ulong address;  // The address (in memory) of the object. (CORDB_ADDRESS == ULONG64)
            public ulong size;           // The total size of the object.
            public COR_TYPEID type;        // The fully instantiated type of the object.
        }
    
        [ComImport, Guid("76D7DAB8-D044-11DF-9A15-7E29DFD72085"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface ICorDebugHeapEnum
        {
            [PreserveSig]
            int Skip([In] uint celt);
            [PreserveSig]
            int Reset();
            [PreserveSig]
            int Clone([MarshalAs(UnmanagedType.Interface)] out ICorDebugEnum ppEnum);
            [PreserveSig]
            int GetCount(out uint pcelt);
            [PreserveSig]
            int Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] COR_HEAPOBJECT[] objects, out uint pceltFetched);
        }

    According to this article if I"m using [PreserveSig] then SizeParamIndex should be 1 based, and not 0 based.

    My calling code looks like this at the moment:

            public bool MoveNext()
            {
          	    uint retrievedRecords = 0;
                COR_HEAPOBJECT[] nextRecord = new COR_HEAPOBJECT[1];
            	
           	    int hresult = _heapEnum.Next(1, nextRecord, out retrievedRecords);
                _count += retrievedRecords;
    
                if (hresult != 0) throw new Exception("ICorDebugHeapEnum::Next failed: " + hresult);
    
           	    if (retrievedRecords != 1)
            	return false;
            	
                _current = new CorDebugHeapObject(nextRecord[0]);
            	
                return true;
            }

    Any thoughts on why I'm getting a HRESULT of "1" being returned? Is this expected?



    • แก้ไขโดย Greg Nagel 30 เมษายน 2555 10:50
    30 เมษายน 2555 10:49
  • Hi Greg,

      More info that I am passing along from Lee:

    The HRESULT of "1" is S_FALSE and that is by design. It indicates that we weren't able to fill in the number of objects you requested, ie retrievedRecord < nextRecord.Length. This happens when there are no more objects to return.

    Also dumpheap -stat returns 'free' objects but the ICorDebug interface skips over these objects which might explain the discrepancy. A free object is an implementation detail of the managed heap, it represents an empty space where one or more objects used to live but now it is a temporarily unused gap between objects. With ICorDebug you could do some analysis on the addresses and sizes of live objects but we don't report the gap as a first class object itself.

    Thanks for the info about docs! I sent some mail to our doc writer to let him know. You are absolutely correct, it is the wrong type being mentioned.

    HTH,

     -Noah

    7 พฤษภาคม 2555 22:56
    ผู้ดูแล
  • Awesome, thanks for the help Noah, appreciate it! I'll mark your proposed answer as accepted now.

    btw - I continued on with my code on the assumption that the HRESULT of 1 was actually expected and I've run into problems using ICorDebugObjectValue values (that also implement ICorDebugStringValue) that are returned from ICorDebugProcess5::GetObject(). But I'll put this in a seperate post.

    8 พฤษภาคม 2555 5:33