Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I step into a Call instruction during Debug / Disassembly?

The Disassembly looks like:

                methShort( ref x, ref y );
000007FF00163F67  lea         r8,[rsp+34h]  
000007FF00163F6C  lea         rdx,[rsp+30h]  
000007FF00163F71  mov         rcx,qword ptr [rsp+20h]  
000007FF00163F76  mov         rcx,qword ptr [rcx+8]  
000007FF00163F7A  mov         rax,qword ptr [rsp+20h]  
000007FF00163F7F  call        qword ptr [rax+18h]  

The method "methShort" is dynamically created in .NET using Reflection.Emit. It takes two Int32 parameters as "byRef" values. This is being debugged as a "release-mode" build.

I can step through the assembly right up to the "call" instruction. The contents of the memory pointed to by R8 and RDX (the parameters) look fine. I don't know what sort of magic allowed the JIT to use registers for the call instead of the stack, but that's beside the point.

When I attempt to "Step Into" the call instruction, the debugger "steps over" it instead. The routine is indeed called- the method performed its function correctly. But I cannot seem to disassemble nor step into the method.

At the point immediately before the call, RAX contains the value 00000000025C67A8h. When 18h is added to that, the address for the indirection becomes 00000000025C67C0h. The QWORD at this address is 000000001b64dc48h.

If I try to disassemble this address (000000001b64dc48h), the debugger comes back with "The specified address cannot be displayed. There is no code at the supplied location".

As a Hail Mary attempt, I tried disassembling the code at RAX without the indirection, but as I expected this failed as well.

Can anyone tell me how to get to whatever code is at the address, or if something akin to LEA needs to be performed on the address (RAX+18h) before disassembling the code there?

like image 663
Veldaeven Avatar asked Apr 30 '15 16:04

Veldaeven


1 Answers

You need to keep in mind that the debugger is trying to save you from wasting several hours of your life. methShort() is a delegate call, there still needs to be a lot of work done before that call can complete. You are not going to enjoy single-stepping through the jitter compiling your dynamic method to machine code, CAS checking the link demand, the CLR creating the delegate stub and binding it to the call site.

I'll answer the question as posted, the debugger won't show you the call target code because it is unmanaged code. You can tell from the address, far removed from the location of your jitted code. Convincing it that you want to see it requires several tricks:

  • Project + Properties, Debug tab, tick the "Enable native code debugging" option
  • You have to switch the debugger from managed to unmanaged mode. There is no explicit command for that, simplest way I know how to do that is to use the Call Stack window. Double-click the bottom frame (__RtlUserThreadStart@8).
  • Click the "view disassembly" link in the Source Not Available popup so you're back in the Disassembly window. The debugger is now in unmanaged mode. Beware that you can't easily tell anymore with the new debugging engine since it now displays proper code addresses.
  • Now you can enter the address you discovered in the Address box. Be sure to put "0x" in front of it.

Not otherwise likely to be useful to debug a dynamic method, albeit that I pretty quickly threw the towel when I tried it. You are probably ahead by calling the method twice so you can bypass all the overhead on the second call.

A completely different approach is to temporarily emit a call to Debugger.Break() into the method, now it is a lot easier.

like image 64
Hans Passant Avatar answered Dec 07 '22 20:12

Hans Passant