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