Search This Blog

Thursday, December 4, 2014

Different ways of using windbg: Learn the code flow and verify the change.

Recently I switched to a different group and hence, I've been trying to ramp up quickly and started to work on bugs to learn the product.
While working on bugs, I learned that debugger is really helpful to understand the code flow and verify the change. Typically, people might use debugger to debug the issue and that's correct. However, there is another advantage of using a debugger. That is to learn how the code flows and verify the change you are making.
What do I mean by that?
First, let me illustrate with an example to show how debugger can be helpful to learn the code.
Say, you understand what the certain function does and noticed that this function is called several places but not sure exactly where the call is coming from and where it goes.
Then, set a breakpoint on that function and try to reproduce the issue. Sure enough, this will hit the function but at the same time, you might be surprised to find how this function is called from different places for other purposes.
As an example, we are learning NtCreateFile function and wanted to see how this is used. Then, set a breakpoint at NtCreateFile and see the callstack as follows:
 0:000> kcn8  
  # Call Site  
 00 ntdll!NtCreateFile  
 01 KERNELBASE!CreateFileInternal  
 02 KERNELBASE!CreateFileW  
 03 KERNEL32!GetDefaultSortFileMapping  
 04 KERNEL32!SetupDefaultSortTables  
 05 KERNEL32!InternalSortGetHandle  
 06 KERNEL32!SortGetHandle  
 07 KERNELBASE!GetSortVersionHandle  

As a trick, you can also give certain commands to breakpoints. For example, you can say, each time we hit NtCreateFile, display callstack and continue.
 bp ntdll!ntcreatefile "kcn; gc"  

Or if we are only interested in NtCreateFile() coming from certain function like CreateFileInternal as in the above example, we can do the following trick.
 0:000> bl  
  0 e 00007ff8`9b641bc0   0001 (0001) 0:**** ntdll!NtCreateFile "bd 0; x; gc"  
  1 e 00007ff8`98af72d0   0001 (0001) 0:**** KERNELBASE!CreateFileInternal "be 0;gc"  

Basically, what we are doing here is that we enable breakpoint #0 when we hit breakpoint #1 and as soon as we hit breakpoint #0, we display local variables and disable itself. This way we can suppress any other noise and quickly understand the code flow for your purpose.

Now that you understand the code and came up with the fix but sometimes it might be pretty difficult to reproduce the issue. Sometimes we can use debugger to change the code flow.
For instance, you have a function that returns boolean value and in order to hit the newly added code path, you need true from the function but you can't really reproduce the issue locally. In that case, you can again take advantage of breakpoint.
Here is how. Say you have a function and try to disassemble the function and see the address of 'ret' instruction. Once you find it, you know that the return value is passed via eax register so you can do the following.
Here is the disassembled code:
 notepad!IsTextUTF8+0x3c:  
 00007ff6`59596e0e 488b5c2408   mov   rbx,qword ptr [rsp+8]  
 00007ff6`59596e13 c3       ret  

Here is breakpoint:
 0:000> bl  
  2 e 00007ff6`59596e13   0001 (0001) 0:**** notepad!IsTextUTF8+0x41 "r eax=1;gc"  

Of course, this may not work all the time but sometimes this could be very handy to verify your change quickly.

In summary, a debugger can be very helpful in learning the code and verifying the fix. Spend some time learning how to use the debugger. It can save much of your time.

No comments:

Post a Comment