如何调试非托管 BCL (InternalCall) 方法?

发布于 2024-09-24 11:42:11 字数 791 浏览 1 评论 0原文

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

但是,Visual Studio 调试器拒绝单步执行该方法。我可以在此调用上设置断点:

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

然后调出 Debug >窗口>反汇编,单步执行 Equals 调用,然后单步执行,直到到达 call x86 指令。但是,当我尝试在该调用上使用“Step Into”(我从 Reflector 知道是 nativeCompareOrdinal 调用)时,它不会像我想要的那样单步执行到 nativeCompareOrdinal 内的第一条指令 - 它会步进而是直接进入 Equals 中的下一个 x86 指令。

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

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

I want to debug into the implementation of a [MethodImpl(MethodImplOptions.InternalCall)] BCL method, which is presumably implemented in C++. (In this particular case, I'm looking at System.String.nativeCompareOrdinal.) This is mainly because I'm nosy and want to know how it's implemented.

However, the Visual Studio debugger is refusing to step into that method. I can set a breakpoint on this call:

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

then bring up Debug > Windows > Disassembly, step into the Equals call, and step until it gets to the call x86 instruction. But when I try to use "Step Into" on that call (which I know from Reflector is the nativeCompareOrdinal call), it doesn't step to the first instruction inside nativeCompareOrdinal like I want -- it steps over instead, and goes straight to the next x86 instruction in Equals.

I'm building as x86, since mixed-mode debugging isn't supported for x64 apps. I've unchecked "Just My Code" in Tools > Options > Debugging, and I have "Enable unmanaged code debugging" checked in project properties > Debug tab, but it still steps over the call. I also tried starting the process and then attaching the debugger, and explicitly attaching both the managed and native debuggers, but it still won't step into that InternalCall method.

How can I get the Visual Studio debugger to step into an unmanaged method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

独夜无伴 2024-10-01 11:42:11

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

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

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

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

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

调试 + Windows + 调用堆栈并双击非托管堆栈帧。现在您可以在反汇编窗口的地址框中输入计算出的地址,前缀为 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 使用的内部名称。

Yes, it is tricky. The offset you see for the CALL instruction is bogus. Plus it won't let you navigate to an unmanaged code address when the current focus is on a managed function.

Start by enabling unmanaged code debugging and setting a breakpoint on the call. Run the code and when the break point hits use Debug + Windows + Disassembly:

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

The debugger tries to display the absolute address but gets it wrong because it uses the bogus incremental address instead of the real instruction address. So first recover the true relative value: 0x6E53D5D0 - 0x2A = 0x6E53D5A6.

Next you need to find the real code address. Debug + Windows + Registers and look at the value of the EIP register. 0x009A0095 in my case. Add 5 to get to the nop, then add the relative offset: 0x9A0095 + 5 + 0x6E53D5A6 = 0x6EEDD640. The real address of the function.

Debug + Windows + Call Stack and double-click an unmanaged stack frame. Now you can enter the calculated address in the Disassembly window's Address box, prefix with 0x.

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

Bingo, you're know you're good if you see the stack frame setup code. Set a breakpoint on it and press F5.


Of course, you'll be stepping machine code since there's no source code available. You'll get much better insight in what this code is doing by looking at the SSCLI20 source code. No guarantee that it will be a match for the actual code in your current version of the CLR but my experience is that these low-level code chunks that have been around since 1.0 are highly conserved. The implementation is in clr\src\classlibnative\nls, not sure which source code file. It won't be named "nativeCompareOrdinal", that's just an internal name used by ecall.cpp.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文