Partager via


Advanced debugging: change your program execution without Edit and Continue

Last time (Improve your managed debugging skills: examining registers and memory) we examined some debugging techniques to understand the behavior of managed code.

 

It might take a long time or many manual steps to reproduce a particular software behavior in an application. Changing the instructions that are executed by the target program while it’s executing under the debugger is a powerful technique.

 

Today we’ll look at changing the code we’re debugging without having to do all these steps:

1. stopping the debug session

2. making the change

3. Building the change

4. deploying the change (VS often does this for you automatically)

5. restarting the debug session

6. repeating the App steps to get to the original place.

 

Edit And Continue is a feature that allows you to change the code you’re developing. Use E&C if you can. However, there are several limitations to E&C:

1. You might be stepping through code that you don’t own (like the CLR or a 3rd party library)

2. You might have attached to a running instance of your code, rather than starting your application under the debugger

3. You might be stepping through 64 bit code

4. You might be stepping through native and managed code

5. You might be changing something which violates E&C rules like editing a LINQ expression or a Lambda or adding a method to a type. (well, I admit for this one, you have to do the rebuild steps above)

 

 

There are a few ways to do this (most of these apply to both managed and native code):

1. Use the Set Next Statement menu option (Right-click, “Set Next Statement” will change set the next instruction to execute)

a. This is so powerful, everybody should learn to use it: you might have stepped past a point of interest: you can just Set Next Statement back

b. You can skip over instructions: perhaps there’s a function call that throws an exception: you can just skip it

c. You can also Step Out of the function (Shift-F11), Set Next Statement to execute the same function again.

d. There are some limitations to this command:

                                                    i. you can’t Set Next Statement out of the currently executing function easily

                                                   ii. Set Next Statement with Try…Catch..Except blocks can give warnings

                                                  iii. Be careful of register usage in optimized builds: the register contents may not be what you expect, especially with optimizations such as out of order execution, etc.

2. Change the value of a variable in the locals or watch window: the program might have a conditional on that value

a. E.G. change the loop control variable to reduce or increase the number of times through a loop

3. Change the return value of a function in the EAX or RAX register directly.

a. A function returning an INT puts the value in EAX (an HRESULT is just an INT)

4. Change a value in memory directly: use the Memory window to display and change a value

5. Change the instruction pointer directly. After all, the Instruction Pointer is just a register

 

Start VS 2010

File->New->Project->C# Windows->Console Application: Paste the code below.

 

 

Repeat F10 to step into the code to the For loop. Hit Ctrl-F11 (Disassembly):

 

            for (int i = 0; i < 10; i++)

00000061 xor edx,edx Clears the EDX register by XORing it with itself

00000063 mov dword ptr [ebp-44h],edx Moves the EDX to [ebp-44]

00000066 nop NOP means no operation: does nothing

00000067 jmp 000000B1 JMP to location B1

 

 

You can see that the For loop initialization was translated into a few instructions.

 

Local variables, like the iteration variable “I” are stored as an offset from the EBP (base pointer) register, which points to the current stack frame. “I” happens to be stored at EBP-0x44 bytes.

While viewing Disassembly, you can single step these instructions (ensure you can see the Registers window)

Here I stepped to “B1”, and RightClicked->Show code bytes to show the actual hex values of the instructions being executed.

 

 

            for (int i = 0; i < 10; i++)

000000ae FF 45 BC inc dword ptr [ebp-44h] Increment “I”

000000b1 83 7D BC 0A cmp dword ptr [ebp-44h],0Ah Compare “I” with 10 (0xA)

000000b5 0F 9C C0 setl al Set al if lower

000000b8 0F B6 C0 movzx eax,al

000000bb 89 45 B4 mov dword ptr [ebp-4Ch],eax

000000be 83 7D B4 00 cmp dword ptr [ebp-4Ch],0

000000c2 75 A5 jne 00000069

 

 

 

 

At location B1, my registers look like:

EAX = 022456F8 EBX = 00000000 ECX = 022456F8 EDX = 00000000 ESI = 022456D4 EDI = 0616E898 EIP = 003424F1 ESP = 0616E84C EBP = 0616E8A4 EFL = 00000297

 

0616E860 = 00000000

 

Notice that bolded last line: it’s a helper. The IP is on the b1 line, which references “I”, which is at [ebp-44]. So this line just conveniently shows that [ebp-44] = 0616E860, which currently contains our initial 00000000.

 

At this point, you can drag/drop the EIP register (mine currently says 003424F1) to the memory window which shows:

0x003424F1 83 7d bc 0a 0f 9c c0 0f b6 c0 89 45 b4 83 7d b4 ƒ}...œÀ.¶À.E´ƒ}´

0x00342501 00 75 a5 90 8d 65 f4 5b 5e 5f 5d c3 00 00 00 a0 .u¥..eô[^_]Ã...

0x00342511 7b 35 00 00 00 00 00 c0 7b 35 00 f8 25 46 00 55 {5.....À{5.ø%F.U

 

 

Notice that the code bytes match this memory dump. In particular the “83 7d bc 0a” is the compare with 10 instruction. We can change the end loop value from 10 to any integer just by changing that 0a. Go ahead and change it to 1: the display shows the changed byte in red

0x003424F1 83 7d bc 01 0f 9c c0 0f b6 c0 89 45 b4 83 7d b4 ƒ}...œÀ.¶À.E´ƒ}´

 

And the code bytes change too (but not in red)!

000000b1 83 7D BC 01 cmp dword ptr [ebp-44h],1

 

 

The changes I’ve made (both native and managed) using this technique:

1. Change a value in memory or a register

2. Change an op code

a. Usually removing code by replacing each code byte with a NOP (0x90)

b. Sometimes reversing or changing a jump, like je (0x74 jump if equal) to jne (0x75 jump if not equal)

3. Change a constant in code (as we changed the “10” constant above)

 

It’s also very useful to change the value of parameters passed to a routine: perhaps an ENUM is passed. As an exercise, change the value passed to the method foo below.

 

Another technique: step out of the module executing the code (so it’s out of memory and can thus be rebuilt) and rebuild the binary on disk, then Set Next Statement back to reload, execute the changed code.

 

I use this technique in developing native DLLs and FoxPro (Edit and Continue in VFP can save you time)

 

Try these variations: Project->Properties->Build->Advanced->Debug Info->”pdb-only”, “None”, “Full”

See also:

https://blogs.msdn.com/b/calvin_hsia/archive/tags/debugging/

 

 

<Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

  {

            var oMyClass = new MyClass();

            for (int i = 0; i < 10; i++)

            {

                var result = oMyClass.foo(MyEnum.two);

                if (result == "one")

                {

                    Environment.Exit(0);

                }

            }

        }

    }

    public class MyClass

    {

        public string foo(MyEnum myenum)

        {

            var retstr = string.Empty;

            if (myenum == MyEnum.one)

            {

                retstr = "one";

  }

            return retstr;

        }

    }

    public enum MyEnum

    {

        one,

        two

    }

}

 

 

</Code>

Comments