Reputation: 341
I have been researching some details of the assembly code which is executed for some typical C# constructs. When debugging simple C# code I follow its execution in the Visual Studio Disassembly window (it is a release build of the app with full debug info).
We have a following fragment of code:
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
There is a problem with a call instruction at 00007FFD6CFB554A address (which is in fact a call to Interlocked.Increment) because Visual Studio Debugger simply steps over the call and does not follow the execution into the subroutine.
It was my intention to have a look what code is executed when Interlocked.Increment is executed.
Why debugger does not follow the execution into a called subroutine?
How to force it to step into that call (there is already mixed debugging enabled for the C# projects)?
Upvotes: 2
Views: 1181
Reputation: 341
Thanks Hans it worked... somehow ;)
Without JIT optimization it looks like:
00007FFDCA6624B0 nop dword ptr [rax+rax]
00007FFDCA6624B5 mov eax,1
00007FFDCA6624BA lock xadd dword ptr [rcx],eax
00007FFDCA6624BE inc eax
00007FFDCA6624C0 ret
With JIT optimization everything is much more complicated. It is just a piece of code which structure is hard to embrace but it is there:
00007FFD6CD7219F lea rax,[rsi+8]
00007FFD6CD721A3 mov edx,1
00007FFD6CD721A8 lock xadd dword ptr [rax],edx
00007FFD6CD721AC lea eax,[rdx+1]
It looks that it returns incremented value in eax.
Although I've managed to achieve my goal I have had some difficulties.
When turned off "Suppress JIT optimization on module load" and placed a breakpoint in the code I could not track execution in Assembly window. Process was terminated (access violation) when stepped into first call instruction. I had to take a different approach and switch to Debugger.Break() call just before Interlocked.Increment in C# code and attach debugger forcing it to handle my .NET process as a native process:
And I was able to track what I was looking for. But why did it all crash if my app was started directly with debugging in VS? I suppose that debugger was not attached to the app as if it was native. But why would it matter if all we care about is a stream of instructions in Assembly window?
Considering we keep "Suppress JIT optimization on module load" enabled why does debugger not step into the call and reveal the code inside Interlocked.Increment routine? Once again - these are just CPU instructions. There are no managed and native instructions, right?
It was mentioned in the comment that Interlocked.Increment is unmanaged code. In which way it is unmanaged since all boils down to a handful CPU instructions? What makes it unmanaged and why? It is not a system call or anything that depends on unmanaged resources. Everything it refers to and uses is in fact managed. Then why?
Upvotes: 1