托管调试中的对象标识

问题是:
也许您在调试时浏览了全局、局部或参数,然后通过一些丑陋的对象引用(如哈希表)来查找对象引用。你希望能在那个物体上获得一些身份,这样你就可以很容易地再次找到它。以后重新查找它可能不实际,特别是如果程序状态自上次以来已更改,以致原始步骤不再生成相同的对象。如果一个数据结构被更改,一个变量被重新分配,或者一个函数返回并使其所有的局部变量无效,那么很容易发生这种情况。事实上,在这种情况下,回溯原始步骤甚至可能产生完全不同的对象。
解决方案是提供独立于其发现方式的“对象标识”。然后,不管程序状态发生了什么变化,都可以在以后根据其标识检索对象

本机代码中的对象标识

在本机代码中,通过“this”指针,每个对象都有一个内部标识。由于本机代码中没有GC移动对象,所以对象的地址是常量,只要对象还活着,就可以用来引用对象。


例如,如果我知道地址0x0012eeff有一个Foo类型的对象,我可以通过检查“((Foo*)0x0012eeff)”随时查看它。地址提供了一个非常方便的固有对象标识。

托管代码中的问题

由于托管代码有一个GC来移动对象,所以转换地址不一定是安全的。地址可以是0x0012eeff,然后GC可以将其移动到0x44556677。托管代码的任何对象标识解决方案都需要与垃圾回收器协作。

托管代码中的对象标识示例
class Program

{

    static void Main()

    {

        string s = "A test";

        Foo();

    }

    static void Foo()

    {

        object x = null;

        System.Console.WriteLine("In Foo:" + x); // <-- set Breakpoint here

    }

}

在Visual Studio 中试用:
1) 运行到断点。
2) 在断点处,将当前堆栈帧切换到Main()。
3) 打开“Local”窗口。你应该看看当地的“s”。它的价值是“a test”。
4) 右键单击“局部变量”窗口中的“s”,然后单击上下文窗口上的“生成对象ID”。该值现在显示“A test”{1}。VS创建了一个伪变量“1”,并将其别名为“s”。
5) 现在切换回Foo()。
6) 在locals窗口中,您将看到值为null的“x”。
7) 将“x”的值设置为“1”。
8) x的值现在是“A test”{1}!
9) 跨过writeline,看看它实际上使用了这个新的x值。

ICorDebug的工作原理:

interface ICorDebugHeapValue2 : IUnknown

{

    /*

      * Creates a handle of the given type for this heap value.

      *

      */

    HRESULT CreateHandle([in] CorDebugHandleType type, [out] ICorDebugHandleValue ** ppHandle);

};

这将返回从ICorDebugReferenceValue派生的ICorDebugHandleValue。HandleValue跟踪正在检查的对象的GC句柄。(回想起来,我认为我们应该将此ICorDebugStrongReferenceValue而不是HandleValue。HandleValue表示它对应于System.Runtime.InteropServices.GCHandle结构,但它不是)。
调试器可以保留伪变量(如“1”)到ICorDebugHandleValue对象的映射。
“type”指定句柄是“strong”还是“weak”。“Strong”(又名“normal”)句柄使对象保持活动状态,而“弱”句柄则无法保持对象的活动状态。这样做的一个副作用是,调试器现在可以创建强句柄来更改对象的寿命,从而允许调试器更改程序行为。(因此,只在调试器下重新编写bug的问题更加严重)。由于GCs和寿命已经是不确定的,这种扰动类似于调试器改变时间和暴露竞争条件的情况。
V1.1试图在ICorDebug级别解决这个问题(通过ICorDebugReferenceValue::DereferenceStrong),但这是一个失败的设计。我们不赞成取消引用strong,而倾向于使用ICorDebugHandleValue。

 

posted on 2020-12-01 10:27  活着的虫子  阅读(78)  评论(0编辑  收藏  举报

导航