Visual Studio 调试器不会进入 "call" 指令

Visual Studio debugger does not step into a "call" instruction

我一直在研究为某些典型的 C# 构造执行的汇编代码的一些细节。在调试简单的 C# 代码时,我在 Visual Studio 反汇编 window 中跟踪它的执行(它是具有完整调试信息的应用程序的发布版本)。

我们有以下代码片段:

return Interlocked.Increment(ref _boxedInt);

00007FFD6CFB5522  sub         esp,30h  
00007FFD6CFB5525  lea         rbp,[rsp+30h]  
00007FFD6CFB552A  mov         qword ptr [rbp+10h],rcx  
00007FFD6CFB552E  cmp         dword ptr [7FFD6C166370h],0  
00007FFD6CFB5535  je          00007FFD6CFB553C  
00007FFD6CFB5537  call        00007FFDCBCFFCC0  
00007FFD6CFB553C  mov         rcx,qword ptr [rbp+10h]  
00007FFD6CFB5540  cmp         dword ptr [rcx],ecx  
00007FFD6CFB5542  mov         rcx,qword ptr [rbp+10h]  
00007FFD6CFB5546  add         rcx,8  
00007FFD6CFB554A  call        00007FFDCA6624B0  
00007FFD6CFB554F  mov         dword ptr [rbp-4],eax  
00007FFD6CFB5552  mov         eax,dword ptr [rbp-4]  
00007FFD6CFB5555  lea         rsp,[rbp]  
00007FFD6CFB5559  pop         rbp  
00007FFD6CFB555A  ret  

00007FFD6CFB554A 地址的 call 指令有问题(实际上是对 Interlocked.Increment 的调用)因为 Visual Studio 调试器只是简单地跳过调用而不是跟随子例程的执行。

我本来打算看看Interlocked.Increment执行时执行的是什么代码。

  1. 为什么调试器不跟随执行进入被调用的子程序?

  2. 如何强制它进入该调用(已经为 C# 项目启用了混合调试)?

感谢汉斯,它成功了……不知何故 ;)

没有 JIT 优化它看起来像:

00007FFDCA6624B0  nop         dword ptr [rax+rax]  
00007FFDCA6624B5  mov         eax,1  
00007FFDCA6624BA  lock xadd   dword ptr [rcx],eax  
00007FFDCA6624BE  inc         eax  
00007FFDCA6624C0  ret  

有了 JIT 优化,一切都变得更加复杂。这只是一段代码,其结构难以接受,但它就在那里:

00007FFD6CD7219F  lea         rax,[rsi+8]  
00007FFD6CD721A3  mov         edx,1  
00007FFD6CD721A8  lock xadd   dword ptr [rax],edx  
00007FFD6CD721AC  lea         eax,[rdx+1]  

看起来 returns 增加了 eax 中的值。

虽然我已经实现了我的目标,但我遇到了一些困难。

  1. 当关闭 "Suppress JIT optimization on module load" 并在代码中放置一个断点时,我无法在程序集 window 中跟踪执行。进入第一个调用指令时进程终止(访问冲突)。 我不得不采取不同的方法,并在 C# 代码中切换到 Interlocked.Increment 之前的 Debugger.Break() 调用,并附加调试器强制它将我的 .NET 进程作为本机进程处理:

    • 无需调试即可启动我的应用程序
    • 附加调试器,就好像我的应用程序是本机的一样
    • 触发 Interlocked.Increment 执行(调试器中断就在它之前)。

而且我能够追踪到我要找的东西。但是,如果我的应用程序直接在 VS 中调试启动,为什么会全部崩溃?我想调试器没有附加到应用程序,就好像它是本机的一样。但是,如果我们只关心汇编 window 中的指令流,那又有什么关系呢?

  1. 考虑到我们保持 "Suppress JIT optimization on module load" 启用,为什么调试器不进入调用并显示 Interlocked.Increment 例程中的代码?再一次 - 这些只是 CPU 说明。没有托管指令和本机指令,对吗?

  2. 评论中提到Interlocked.Increment是非托管代码。由于所有内容都归结为少数 CPU 指令,因此它以何种方式不受管理?是什么让它不受管理,为什么?它不是系统调用或任何依赖于非托管资源的东西。它所指和使用的一切实际上都是受管理的。那为什么?