首页 > 解决方案 > 编写调试器时跟踪对象的最佳方法

问题描述

背景:

我正在为 C# 中的解释语言编写调试器。我只写了一个“调试器服务器”来处理特定于语言的任务,比如列出局​​部变量。然后将结果发送到“调试器客户端”(显示列出的变量的文本编辑器),例如 VS Code。

调试器使用 Microsoft 的语言服务协议,因此需要特定格式。为了在调试器窗口中正确显示复杂的数据类型(对象、数组),每个对象都需要分配一个variableReference,这是分配给该对象的唯一整数 ID。

当用户单击一个对象以便在“变量”显示中“展开”它时,会向调试器服务器发送一个带有变量引用的请求,并用该对象内的值将响应发送回客户端。

到目前为止我的进展:

为了识别哪个对象对应哪个 ID,我在调试器跟踪的对象及其 ID(实现为 2 个字典)之间创建了一个双向映射。

当我在调试器中看到一个对象时,我会尝试在此映射中查找它以获取它的 ID(如果存在)。如果不是,我为其分配一个新 ID,并将其保存到地图中以备后用。当前端请求对象中的值时(通过用户单击它),我通过它的 ID 在地图中查找对象并解决请求。

问题:

如何将对象保存在地图中,以便我可以查找现有对象的 ID,或者轻松判断对象是否尚未注册?

我尝试使用哈希图(字典),其中哈希是从对象的地址计算出来的,并且相等性被实现为引用相等性。请注意,无法根据对象内容计算散列,因为对象的内容在调试过程中可能会发生变化,因此在散列更改后,将无法在字典中找到该对象。

我需要一个保持不变的哈希,即使对象的内容发生变化。对象地址似乎非常适合,但是我找不到可靠工作的解决方案,例如,这个:

public int GetHashCode(object obj)
{
    GCHandle gch = GCHandle.Alloc(obj, GCHandleType.Pinned);
    IntPtr ptr = gch.AddrOfPinnedObject();
    return ptr.ToInt32();
}

抛出Object contains non-primitive or non-blittable data.异常。

然而,这不是唯一的问题。据我了解,GC 可以重新分配内存的整个部分并修复它这样做时使用的所有地址。这将使地址发生变化,并破坏哈希码。

可能的解决方案:

  1. 摆脱hashmap(字典):在搜索对象的现有ID时,手动引用它与所有现有对象进行比较。这将解决问题,但会相当慢。
  2. 在创建时手动将 ID 直接添加到对象:这似乎最有意义,但我将调试器作为一个 mod,所以这需要对 mod IMO 的代码进行太多更改。
  3. 修复 GetAddress 方法:即使地址发生变化,这也是一个有效的解决方案,因为对象可以在地图中出现两次,并且调试器仍然可以正常工作。
  4. 为每次看到对象分配新的 ID:这意味着从 ID 到对象只有一个方向的映射,并且每个对象将多次使用不同的 ID。它会起作用,但对内存来说非常沉重。
  5. 完全摆脱地图:这不仅需要修复GetAddress方法,而且直接将地址用作ID,然后将ID(地址)直接转换为对象。这可能很危险,因为在 ID 请求到来时地址可能包含任意二进制数据,而且不会与long地址一起使用,因为 ID 只是一个整数。

或者任何其他可能的解决方案?调试器通常如何解决这个问题?

标签: c#debuggingreferencehashmap

解决方案


推荐阅读