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执行时执行的是什么代码。
为什么调试器不跟随执行进入被调用的子程序?
如何强制它进入该调用(已经为 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 中的值。
虽然我已经实现了我的目标,但我遇到了一些困难。
当关闭 "Suppress JIT optimization on module load" 并在代码中放置一个断点时,我无法在程序集 window 中跟踪执行。进入第一个调用指令时进程终止(访问冲突)。
我不得不采取不同的方法,并在 C# 代码中切换到 Interlocked.Increment 之前的 Debugger.Break() 调用,并附加调试器强制它将我的 .NET 进程作为本机进程处理:
- 无需调试即可启动我的应用程序
- 附加调试器,就好像我的应用程序是本机的一样
- 触发 Interlocked.Increment 执行(调试器中断就在它之前)。
而且我能够追踪到我要找的东西。但是,如果我的应用程序直接在 VS 中调试启动,为什么会全部崩溃?我想调试器没有附加到应用程序,就好像它是本机的一样。但是,如果我们只关心汇编 window 中的指令流,那又有什么关系呢?
考虑到我们保持 "Suppress JIT optimization on module load" 启用,为什么调试器不进入调用并显示 Interlocked.Increment 例程中的代码?再一次 - 这些只是 CPU 说明。没有托管指令和本机指令,对吗?
评论中提到Interlocked.Increment是非托管代码。由于所有内容都归结为少数 CPU 指令,因此它以何种方式不受管理?是什么让它不受管理,为什么?它不是系统调用或任何依赖于非托管资源的东西。它所指和使用的一切实际上都是受管理的。那为什么?
我一直在研究为某些典型的 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执行时执行的是什么代码。
为什么调试器不跟随执行进入被调用的子程序?
如何强制它进入该调用(已经为 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 中的值。
虽然我已经实现了我的目标,但我遇到了一些困难。
当关闭 "Suppress JIT optimization on module load" 并在代码中放置一个断点时,我无法在程序集 window 中跟踪执行。进入第一个调用指令时进程终止(访问冲突)。 我不得不采取不同的方法,并在 C# 代码中切换到 Interlocked.Increment 之前的 Debugger.Break() 调用,并附加调试器强制它将我的 .NET 进程作为本机进程处理:
- 无需调试即可启动我的应用程序
- 附加调试器,就好像我的应用程序是本机的一样
- 触发 Interlocked.Increment 执行(调试器中断就在它之前)。
而且我能够追踪到我要找的东西。但是,如果我的应用程序直接在 VS 中调试启动,为什么会全部崩溃?我想调试器没有附加到应用程序,就好像它是本机的一样。但是,如果我们只关心汇编 window 中的指令流,那又有什么关系呢?
考虑到我们保持 "Suppress JIT optimization on module load" 启用,为什么调试器不进入调用并显示 Interlocked.Increment 例程中的代码?再一次 - 这些只是 CPU 说明。没有托管指令和本机指令,对吗?
评论中提到Interlocked.Increment是非托管代码。由于所有内容都归结为少数 CPU 指令,因此它以何种方式不受管理?是什么让它不受管理,为什么?它不是系统调用或任何依赖于非托管资源的东西。它所指和使用的一切实际上都是受管理的。那为什么?