Since we have started writing code and it is going to be more and more complex, I think it is right time to understand a bit more about the process anatomy. We should learn to debug our own code so we can at least find and fix bugs.

We will use code published here to learn native code debugging.

Build code with /Zi option to get the symbols (*.pdb).

cl /Zi Tables.c

     1,287 Tables.c
   543,232 Tables.exe
 2,755,296 Tables.ilk
     5,641 Tables.obj
 7,081,984 Tables.pdb


Get Windows Debugger from Microsoft Store. This one is an app that works on Windows 10. There are other ways to get a debugger (WinDbg and KD), you can install Debugging Tools for Windows in Windows SDK/WDK.

In WinDbg, Go to File > Launch Executable (Advanced),
select Tables.exe and in argument pass a value, 5.

This will launch the application in the debugger.


This is an early breakpoint in the loader which is hit if a debugger is attached. At this point, exe is loaded in memory and ready to run.

0:000> kc
 # Call Site
00 ntdll!LdrpDoDebuggerBreak
01 ntdll!LdrpInitializeProcess
02 ntdll!_LdrpInitialize
03 ntdll!LdrpInitialize
04 ntdll!LdrInitializeThunk

k‘ command print stack, there are several variations. The one I used is ‘kc‘ which prints just the function names.

0:000> lm
start             end               module name
00007ff6`a6230000 00007ff6`a62ba000 Tables     (deferred)             
00007fff`09eb0000 00007fff`0a123000 KERNELBASE (deferred)             
00007fff`0b730000 00007fff`0b7e2000 KERNEL32   (deferred)             
00007fff`0d7c0000 00007fff`0d9a1000 ntdll      (pdb symbols)

lm‘ lists the modules that are loaded in process memory. Modules, here, means dlls and exe. This command also gives the start and end address of each module. Now, if you remember the PE header of the executable, it has a DOS header and a PE Signature (PE00).

We can dump each module to find this. I used “db” to dump each byte from the given address.

0:000> db 00007ff6`a6230000 L100
00007ff6`a6230000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
00007ff6`a6230010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
00007ff6`a6230020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00007ff6`a6230030  00 00 00 00 00 00 00 00-00 00 00 00 f0 00 00 00  ................
00007ff6`a6230040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
00007ff6`a6230050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
00007ff6`a6230060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS 
00007ff6`a6230070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......
00007ff6`a6230080  fc fb af 9a b8 9a c1 c9-b8 9a c1 c9 b8 9a c1 c9  ................
00007ff6`a6230090  d7 fe c5 c8 b2 9a c1 c9-d7 fe c2 c8 bd 9a c1 c9  ................
00007ff6`a62300a0  d7 fe c4 c8 34 9a c1 c9-d7 fe c0 c8 bb 9a c1 c9  ....4...........
00007ff6`a62300b0  b8 9a c0 c9 e9 9a c1 c9-ea f2 c2 c8 b0 9a c1 c9  ................
00007ff6`a62300c0  ea f2 c4 c8 9e 9a c1 c9-ea f2 c5 c8 a8 9a c1 c9  ................
00007ff6`a62300d0  d1 f2 c5 c8 b9 9a c1 c9-d1 f2 c3 c8 b9 9a c1 c9  ................
00007ff6`a62300e0  52 69 63 68 b8 9a c1 c9-00 00 00 00 00 00 00 00  Rich............
00007ff6`a62300f0  50 45 00 00 64 86 07 00-2f 0a 9e 5b 00 00 00 00  PE..d.../..[....

You can use “!dh” to dump header of any loaded module from memory.

0:000> !dh 00007ff6`a6230000

    8664 machine (X64)
       7 number of sections
5B9E0A2F time date stamp Sun Sep 16 13:15:51 2018


     20B magic #
   14.15 linker version
   68600 size of code
   1E200 size of initialized data
       0 size of uninitialized data
    120D address of entry point   <<---This is the address to main()
    1000 base of code
         ----- new -----
       0 [       0] address [size] of Export Directory
   87360 [      28] address [size] of Import Directory
       0 [       0] address [size] of Resource Directory
   82000 [    45C0] address [size] of Exception Directory
       0 [       0] address [size] of Security Directory
   89000 [     7B8] address [size] of Base Relocation Directory
   76820 [      38] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
   76860 [     100] address [size] of Load Configuration Directory
       0 [       0] address [size] of Bound Import Directory
   87000 [     360] address [size] of Import Address Table Directory
       0 [       0] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory

   .text name
   684A6 virtual size
    1000 virtual address
   68600 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         (no align specified)
         Execute Read


Now, there is an import table. This import table has information of all the external functions that will be called from the code in this binary and which dll they come from.

I’ll use “dps” to dump memory, iterating by pointer size (s to resolve symbols). I’ll add the offset of import table to base address and use that to start dumping memory.

0:000> dps 00007ff6`a6230000+87000
00007ff6`a62b7000  00007fff`0b743c80 KERNEL32!QueryPerformanceCounterStub
00007ff6`a62b7008  00007fff`0b750680 KERNEL32!GetCurrentProcessId
00007ff6`a62b7010  00007fff`0b743c50 KERNEL32!GetCurrentThreadId
00007ff6`a62b7018  00007fff`0b747a00 KERNEL32!GetSystemTimeAsFileTimeStub
00007ff6`a62b7020  00007fff`0d835b80 ntdll!RtlInitializeSListHead
00007ff6`a62b7028  00007fff`0b750570 KERNEL32!RtlCaptureContext
00007ff6`a62b7030  00007fff`0b74aa60 KERNEL32!RtlLookupFunctionEntryStub
00007ff6`a62b7038  00007fff`0b731010 KERNEL32!RtlVirtualUnwindStub
00007ff6`a62b7040  00007fff`0b74dc30 KERNEL32!IsDebuggerPresentStub
00007ff6`a62b7048  00007fff`0b765e70 KERNEL32!UnhandledExceptionFilterStub
00007ff6`a62b7050  00007fff`0b74d530 KERNEL32!SetUnhandledExceptionFilterStub
00007ff6`a62b7058  00007fff`0b74aad0 KERNEL32!GetStartupInfoWStub
00007ff6`a62b7060  00007fff`0b74a300 KERNEL32!IsProcessorFeaturePresentStub
00007ff6`a62b7068  00007fff`0b74a1c0 KERNEL32!GetModuleHandleWStub
00007ff6`a62b7070  00007fff`0b751170 KERNEL32!WriteConsoleW
00007ff6`a62b7078  00007fff`0b74d880 KERNEL32!RtlUnwindExStub

If you examine output of “!dh”, you will find a lot on interesting information embedded in the executable that helps Windows setup process address space for your application. Go on, explore it… Ask, what doesn’t make sense.


Only if you have symbols configured, i.e. debugger knows where to load symbols from, more information will be available. Like, source path and line numbers.

You can use “.sympath” to list what symbol path is configured. Default path is to download and cache symbols from

0:000> .sympath 
Symbol search path is: cache*;SRV*
Expanded Symbol search path is: cache*;srv*

If your symbol path is empty, you can restore default path with “.symfix” command.

To add your symbol path to default path list, use “.sympath+ <path_to_symbol>“.

0:000> .sympath+ c:devsourcemcu-indiacplbasicsachindra_mca_2005debugging101
Symbol search path is: cache*;SRV*;c:devsourcemcu-indiacplbasicsachindra_mca_2005debugging101
Expanded Symbol search path is: cache*;srv*;c:devsourcemcu-indiacplbasicsachindra_mca_2005debugging101

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       cache*
Deferred                                       SRV*
OK                                             c:devsourcemcu-indiacplbasicsachindra_mca_2005debugging101


.reload” will reload all symbols.


Let’s put a breakpoint on main(). Breakpoint is an instruction that will stop execution on a specific address.

Below, I used ‘x‘ command to examine a given symbol. It tells me about the address and any information it has about it. In this case, it is a function, hence I am getting its prototype.

The other command is “bp” which sets a breakpoint on a given address. I used “bl” to list all breakpoints that I have set. “bd” will disable, “be” will enable and “bc” will clear  a given breakpoint.

0:000> x Tables!main
00007ff6`a6236c40 Tables!main (int, char **)
0:000> bp Tables!main
0:000> bl
     0 e Disable Clear  00007ff6`a6236c40  [c:devsourcemcu-indiacplbasicsachindra_mca_2005debugging101tables.c @ 21]     0001 (0001)  0:**** Tables!main

TIP: You can put breakpoint on an address (00007ff6`a6236c40) or a symbol (Tables!main).

Command “g“, will let debugger go and it will break-in when breakpoint is hit.

0:000> g
Breakpoint 0 hit
00007ff6`a6236c40 4889542410      mov     qword ptr [rsp+10h],rdx ss:00000078`24affe78=0000000000000000


On the right, you can see the stack (or use command “k” to print the current stack). On the left, you can see the source code. In the center I have my debugger.

F9” will toggle break point (on/off) at a given line of code that you have selected in the code window. Try it, click on atoi() in the source page and press F9. A break point is set. You can use command “bl” to see it. Now, disable it with “bd 1” (1 or number corresponding to the breakpoint you want to disable. “bc 1” to clear it.

F10” or “p” to execute one line at a time. With functions, this steps over the function call. i.e. if you press “F10”, it will break after the function has completed execution.

F11” or “t” to execute one instruction at a time. With function, this let’s you step into a function.

g” anytime will make debugger continue until it hits a breakpoint or end of program.

If accidentally, you hit “g” and you program terminates, you can “.restart” to restart debugging.

0:000> p
00007ff6`a6236c4d c744242000000000 mov     dword ptr [rsp+20h],0 ss:00000078`24affe50=00000000

0:000> dv
           argc = 0n1
           argv = 0x000001d1`99d13bd0
       retValue = 0n0

I used a new command above, “dv“, it gets you all variables in scope.

In the above output argc is 1, i.e. I forgot input parameter. So I restarted and provided an input argument.


Checkout stack that leads to program’s main function!

0:000> kc
 # Call Site
00 Tables!main
01 Tables!invoke_main
02 Tables!__scrt_common_main_seh
03 KERNEL32!BaseThreadInitThunk
04 ntdll!RtlUserThreadStart

A thread starts in ntdll.dll, it calls into Kernel32.dll which then calls a function __scrt_common_main_seh in your program. You have not written this code but while compiling and linking, this wrapper function is put in, if your code generates an exception, this function handles that (SEH == Structured Exception Handler). This function calls an inline function – invoke_main() which then calls your main().


F10, till you reach PrintTableFor(). Now, press F11 or ‘t’ to enter the function. You can trace through this function using F10 and inspect local variables with dv.

When you trace over the for-loop, notice when the value of “i” is updated (i++). Rewrite this code with ++i and trace to find how that is different.

If your loop is too big to trace, and you want to jump ahead, click on a line of code where you want to jump to, press F9 to put a breakpoint there and press ‘g’.

If you want to jump to exit of function, i.e. from somewhere in PrintTableFor() to main function where it was called from, you press Shift+F11 or “g @$ra”. Try it from inside PrintTableFor().

Give it a try and we shall go deeper with more commands and more deeper dissecting the process memory.