5

我想调试[MethodImpl(MethodImplOptions.InternalCall)]BCL 方法的实现,该方法可能是用 C++ 实现的。(在这种特殊情况下,我正在查看 System.String.nativeCompareOrdinal。)这主要是因为我很爱管闲事,想知道它是如何实现的。

但是,Visual Studio 调试器拒绝进入该方法。我可以在这个调用上设置一个断点:

"Hello".Equals("hello", StringComparison.OrdinalIgnoreCase);

然后调出 Debug > Windows > Disassembly,单步执行 Equals 调用,然后单步执行,直到找到callx86 指令。但是,当我尝试对其使用“Step Into” call(我从 Reflector 知道是 nativeCompareOrdinal 调用)时,它并没有像我想要的那样进入 nativeCompareOrdinal 中的第一条指令——而是跨步,直接进入Equals 中的下一条 x86 指令。

我正在构建为 x86,因为 x64 应用程序不支持混合模式调试。我在“工具”>“选项”>“调试”中取消选中“仅我的代码”,并在“项目属性”>“调试”选项卡中选中“启用非托管代码调试”,但它仍然跳过call. 我还尝试启动该过程,然后附加调试器,并显式附加托管调试器和本机调试器,但它仍然不会进入该 InternalCall 方法。

如何让 Visual Studio 调试器单步执行非托管方法?

4

1 回答 1

5

是的,这很棘手。您看到的 CALL 指令的偏移量是虚假的。此外,当当前焦点位于托管函数上时,它不会让您导航到非托管代码地址。

首先启用非托管代码调试并在调用上设置断点。运行代码,当断点命中时使用 Debug + Windows + Disassembly:

            "Hello".Equals("hello", StringComparison.OrdinalIgnoreCase);
00000025  call        6E53D5D0 
0000002a  nop              

调试器尝试显示绝对地址,但由于它使用虚假的增量地址而不是真实的指令地址而出错。所以首先恢复真实的相对值:0x6E53D5D0 - 0x2A = 0x6E53D5A6。

接下来需要找到真实的代码地址。Debug + Windows + Registers 并查看 EIP 寄存器的值。0x009A0095 在我的情况下。加 5 得到 nop,然后加上相对偏移量:0x9A0095 + 5 + 0x6E53D5A6 = 0x6EEDD640。函数的真实地址。

Debug + Windows + Call Stack 并双击非托管堆栈帧。现在您可以在反汇编窗口的地址框中输入计算的地址,前缀为 0x。

6EEDD640  push        ebp  
6EEDD641  mov         ebp,esp 
6EEDD643  push        edi  
6EEDD644  push        esi  
6EEDD645  push        ebx  
6EEDD646  sub         esp,18h 
etc...

宾果游戏,如果您看到堆栈框架设置代码,您就知道自己很好。在其上设置断点并按 F5。


当然,由于没有可用的源代码,因此您将单步执行机器代码。通过查看 SSCLI20 源代码,您将更好地了解此代码的作用。不能保证它与您当前版本的 CLR 中的实际代码匹配,但我的经验是,这些自 1.0 以来一直存在的低级代码块是高度保守的。实现在 clr\src\classlibnative\nls,不确定是哪个源代码文件。它不会被命名为“nativeCompareOrdinal”,这只是 ecall.cpp 使用的内部名称。

于 2010-09-23T15:14:33.037 回答