托管调试中的对象标识
问题是:
也许您在调试时浏览了全局、局部或参数,然后通过一些丑陋的对象引用(如哈希表)来查找对象引用。你希望能在那个物体上获得一些身份,这样你就可以很容易地再次找到它。以后重新查找它可能不实际,特别是如果程序状态自上次以来已更改,以致原始步骤不再生成相同的对象。如果一个数据结构被更改,一个变量被重新分配,或者一个函数返回并使其所有的局部变量无效,那么很容易发生这种情况。事实上,在这种情况下,回溯原始步骤甚至可能产生完全不同的对象。
解决方案是提供独立于其发现方式的“对象标识”。然后,不管程序状态发生了什么变化,都可以在以后根据其标识检索对象
本机代码中的对象标识
在本机代码中,通过“this”指针,每个对象都有一个内部标识。由于本机代码中没有GC移动对象,所以对象的地址是常量,只要对象还活着,就可以用来引用对象。
例如,如果我知道地址0x0012eeff有一个Foo类型的对象,我可以通过检查“((Foo*)0x0012eeff)”随时查看它。地址提供了一个非常方便的固有对象标识。
托管代码中的问题
由于托管代码有一个GC来移动对象,所以转换地址不一定是安全的。地址可以是0x0012eeff,然后GC可以将其移动到0x44556677。托管代码的任何对象标识解决方案都需要与垃圾回收器协作。
如何调试仅我代码?
有时开发人员只想调试他们编写的代码,而不想调试应用程序中的第三方代码(如框架和库)。当用户和非用户代码在彼此之间来回调用时,这一点尤其有用。clr调试服务有许多新特性来支持这一点,我们称之为“Just My Code”(JMC)调试。
关于std::length_error异常
什么是std::length_error异常
长度错误。它报告由于试图超出某些对象的实现定义的长度限制而导致的错误。一般由std::basic_string和std::vector::reserve等成员函数抛出。
继承关系
异常结构填充
ExceptionAddress: 747cc5af (KERNELBASE!RaiseException+0x00000058)
ExceptionCode: e06d7363 (C++ EH exception)
ExceptionFlags: 00000001
NumberParameters: 3
Parameter[0]: 19930520
Parameter[1]: 0039a178//std::length_error对象指针
Parameter[2]: 66805744
0:000> dt std::length_error 0039a178
DIYHome!std::length_error
+0x000 __VFN_table : 0x667c147c
+0x004 _Mywhat : 0x154882d8 "vector<T> too long"
+0x008 _Mydofree
备注
这个异常既不是内存不够,也不是越界,而是要生成的容器size超过了容器最大值(max_size())而引发。当然引起超过最大值的原因就多种多样了。
如何判断函数是否是托管代码?
对于纯C#应用来说,这是一个没有实际意义的问题,但是如果你用MC++(或其他一些“混合”语言)编写,并且你想知道一个函数是被编译成托管代码还是本机代码呢?
您可以尝试检查源代码并根据语言规则进行推断。例如,在MC++中,查找#pragma managed/#pragma unmanaged。然而,这是有风险的,因为可能有一些你不知道的语言规则。(小测验:你知道MC++将函数编译成本机代码而不是IL的所有规则吗?)
所以如果你想要更多偏执的验证。。。
- 使用ILDasm查看函数是否实际为IL。
- 如果它在仅托管调试时出现,则它是托管代码。所以请检查您是否正在进行互操作调试,如果不是,您看到的任何内容都是托管的。
- 在调用堆栈上查找托管2本机标记。
- 如果调试时实际在函数中停止,请查看反汇编。
VS显示从偏移量0开始的托管调试反汇编,例如:
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");00000000push edi00000001push esi00000002 push ebx
查找非托管异常的来源
有时,您会查看异常抛出后处理程序中的调用堆栈。如果附加到弹出watson对话框的未处理异常,这是非常常见的。
它可能看起来像:
kernel32!WaitForSingleObject+0xf
devenv!DwCreateProcess+0xbb
devenv!fExceptionHandling+0x1cb
devenv!DwExceptionFilter+0x8b
0x535ef48
那没什么用。您真正想要的是查看抛出异常时的调用堆栈。在x86上执行此操作有一个技巧。(这可以调整为在64位平台上工作。)
它可以在实时调试和小型转储中工作,甚至在没有任何符号的情况下也可以工作。我将首先给出如何做到这一点的快速步骤,然后我将解释它的工作原理。
我怎么找到它?