Partager via


Very Advanced Debugging tips

While debugging code, it might take very many complicated steps to reproduce an issue. The following applies to debugging both Visual FoxPro and Visual Studio Native code debugging, except where noted. The VFP debugger is modeled after the VS debugger, with the same plethora of windows: call stack, output, watch, locals, etc.)

Stepping through from breakpoint to breakpoint, you might accidentally step over a function/method call rather than into it. Just right click on the desired line and choose Set Next Statement (or Shift-Ctrl-F10 in VS) This is changing the Instruction Pointer (IP). Keep in mind this executes the function again, and it may have had side effects. Also, you can’t just change the Instruction Pointer arbitrarily. Both VS and VFP give a warning when you try to do something bad, like Set Next Statement into a different function, bypassing function initialization, local variable declarations, etc.

The Call stack is your friend. It’s very important to see how the code you’re looking at got called. You can click on the call stack at various levels to examine variables and code at those levels.

Many times, debugging causes the application to behave differently. For example, put a breakpoint on the VFP Activate Method or the Window Procedure WM_ACTIVATE and it will be fired every time you resume from the debugger. In this and many other cases, it’s important to use the Debug Output window to show values dynamically.

In VFP, “DEBUGOUT "this is a test",_vfp.Caption” will output to the Debug Output window. For native code, the OutputDebugString API will do the trick.

Another useful technique is to modify the code and continue debugging. In certain versions of VB and VC# you could do that with native VS support called “Edit and Continue”. In FoxPro, you can do that by stepping out of the compiled code (PRG or VCX) , recompiling just that part, then stepping back in (via Set Next Statement). FoxPro does not require you to build an APP or EXE to run your code, so your code compile granularity is quite small. In VS Native code debugging, you can do that by stepping out until the module you want to modify is unloaded, then it can be recompiled and reloaded. Same idea as VFP, but the compiled unit granularity is quite coarse. However, in VS there is another simple way if the code you want to c change is fairly minor.

The rest of this blog concerns only VS debugging: not VFP.

(Tip: Make sure you have debug symbols loaded: here’s how)

Just right click and choose Go To Disassembly(Ctrl-F11) and make sure addresses and code bytes are displayed (context menu).

Here’s an example

                     if (sysusr >= MNPUSHFROMDEFAULT_MENU_BAR) {

0094E284 83 7D 10 64 cmp dword ptr [sysusr],64h

0094E288 7C 14 jl MNPushMenu2+1AEh (94E29Eh)

                           miPtr->iStatus &= ~(IONSELSET | IONENTRYSET | IONEXITSET);

0094E28A 8B 45 EC mov eax,dword ptr [miPtr]

0094E28D 8B 48 04 mov ecx,dword ptr [eax+4]

0094E290 81 E1 7F CF FF FF and ecx,0FFFFCF7Fh

0094E296 8B 55 EC mov edx,dword ptr [miPtr]

0094E299 89 4A 04 mov dword ptr [edx+4],ecx

                     } else {

0094E29C EB 12 jmp MNPushMenu2+1C0h (94E2B0h)

                           niPtr->iStatus &= ~(IONSELSET | IONENTRYSET | IONEXITSET);

0094E29E 8B 45 E8 mov eax,dword ptr [niPtr]

0094E2A1 8B 48 04 mov ecx,dword ptr [eax+4]

0094E2A4 81 E1 7F CF FF FF and ecx,0FFFFCF7Fh

0094E2AA 8B 55 E8 mov edx,dword ptr [niPtr]

0094E2AD 89 4A 04 mov dword ptr [edx+4],ecx

                     }

In this code, I want to change the code to do the code in the else clause always (remove the “else” line). It’s a simple modification without recompiling.

Open a memory window via Debug->Window->Memory or Ctrl-Alt-M1 for Whidbey (I think it’s Ctrl-Alt-M for VS.Net 2003, which only had 1 memory window, instead of the 4 for Whidbey).

Drag the desired address and drop it on the memory window.

0x0094E29C eb 12 8b 45 e8 8b 48 04

0x0094E2A4 81 e1 7f cf ff ff 8b 55

0x0094E2AC e8 89 4a 04 e9 28 ff ff

0x0094E2B4 ff 8b 45 f4 83 38 02 74

You see the “else” being a 2 byte “jmp” instruction.

We just want to replace these 2 bytes with a NOP (No Operation: Op Code= 0x90) which does nothing. Just change the “eb 12” in the memory window (it even has UnDo) to “90 90”

The disassembly reflects this nicely:

0094E299 89 4A 04 mov dword ptr [edx+4],ecx

                     } else {

0094E29C 90 nop

0094E29D 90 nop

Now the currently loaded code has been changed and the modified version of this code will be executed. The disk version of the module has not been changed.

Not only can you change OP codes, but you can change data too. For example, the 0FFFFCF7F from above can be changed: you can see the value in the memory window.

Armed with this technique, you can reduce the number of times you need to execute the repro scenario.

Another technique is to keep your register window open. (Debug->Windows->Registers). The return value of a native code function call is in register EAX. If you’re stepping through assembly code, you can see it easily. If you’re stepping through C++ code, it might not be seen as easily (destructors or overloaded operators might fire after the function call)

If the IP is on the first line above (0094E284) the register window shows this: (you might have to use the context menu to show Effective Address)

EAX = 022D0B0C EBX = 7FFDF000 ECX = 00080B00

EDX = 022D0B0C ESI = 0012DA14 EDI = 0012D618

EIP = 0094E284 ESP = 0012CC84 EBP = 0012CCA4

EFL = 00000206

CS = 0000 DS = 0023 ES = 0023 SS = 0023 FS = 003B

GS = 0000

0012CCB4 = 00000065

The red values show the changes each time you step. The last value shows the effective address. Since this is a CMP instruction, it shows the value of “dword ptr [sysusr]” which is 0x65

Another example is “dword ptr [eax+4]”, which is handy to see in the register window.

You can do other fancy things in the register window, like change the IP register directly (EIP means Extended Instruction Pointer, the 32 bit version of the 16 bit IP register found on the 8080. Same all the registers starting with “E”, like EAX, EBX, etc.)

41561

Comments

  • Anonymous
    February 06, 2006
    As a software developer, I spend much of my time looking at code, learning how it works, and figuring...

  • Anonymous
    May 03, 2006
    I received a comment on this post: Will GetLastError ever work properly in VFP8.0?.  I was consistently...

  • Anonymous
    August 16, 2006
    In case of debugging watson crash dumps ... what do we need to look for in the disassembly! I am new at this, so please explain in detail...

    Thanks!

  • Anonymous
    October 10, 2006
    Does modifying the registers only work in native debugging, or can you modify them in managed debugging as well?  If so, I can't manage to figure out how to work it, so perhaps I'm missing something?  It'd be horribly handy for what I'm doing right now.

  • Anonymous
    February 26, 2007
    Let’s log all the calls that Excel makes to open or create a file. Start Visual Studio (any version),

  • Anonymous
    March 02, 2007
    While I am dubugging a very large code (15000 lines) in VC++, for some of the local variables, it does NOT show the value while debugging. Is there some setting I need to do so that I can see the values of all the variables while debugging?

  • Anonymous
    January 19, 2009
    There are various leak detection methods for memory allocators. A popular one is to tag each allocation