Lecture 1a: Debugging

I. Introduction

There are several debuggers made available without charge by Microsoft for the various Windows operating systems. They include:

  1. Command line debuggers:
    1. ntsd (or cdb) for user mode debugging. ntsd is included as part of the standard install of Windows XP.
    2. kd for kernel debugging. It is possible for kd to control ntsd for such tasks as the windowing system itself (csrss.exe). You need administrative rights in order to do kernel debugging.
  2. GUI based debugger: windbg provides a GUI front end to the command line debuggers for debugging both user mode programs as well as the operating system. For most purposes, windbg is the easiest of the tools to use.

The debuggers can be downloaded in a single package called Debugging Tools for Windows from http://www.microsoft.com/ddk/debugging/. To do kernel debugging, the debuggers need access to the symbol table information for the exact patch level release of the operating system to be debugged. You can either download symbol files or use the microsoft symbol server; information about both is available at the same link.

Extensive documentation comes with the debuggers and is located in the debugger install directory. From that directory, it can be invoked with the command: hh debugger.chm. The material described below is only a small subset of the functionality of these programs.

II. Invoking the debugger

Although it is possible to do some debugging without symbol information, one normally needs to have debugging symbol table information in order to find ones way around a program. Debugging information will be saved in .pdb files if you compile your program using the /Zi switch to the C compiler cl.

Some of the parameters for the debuggers can be specified in multiple ways: environment variables, command line parameters, or debugger commands. For example, debuggers find symbols by using the symbol path. One can set the symbol path is a semicolon separated list of directories specified by:

  1. the _NT_SYMBOL_PATH environment variable,
  2. the -y command line option followed by the symbol path,
  3. the File | Symbol File Path command in windbg, or
  4. the .sympath debugger command.
In searching for symbols for a particular binary, the debuggers look through the symbol path. If dir is in the path, they look in dir\Symbols\dll, dir\dll, and dir. They also search the current working directory and the dll subdirectory of the current working directory. The binaries as well as the symbol information are time and date stamped to make it unlikely that the wrong version of the symbols are used.

Similarly, the location of the source files can be specified by:

  1. the _NT_SOURCE_PATH environment variable,
  2. the -srcpath command line parameter,
  3. the File | Source File Path windbg command, or
  4. the .srcpath debugger command.

Debuggers can either start up programs to be debugged or can attach to already running programs.

  1. To attach to a running process, one might use:
     ntsd -p ProcessID
    or
     ntsd -pn ProcessName
    The process name is the name of the executable including the extension. If there is more than one instance of a program running at the time, you need to use the process id, a decimal number which you can obtain from taskmgr.exe. (To start it, you can right click on the task bar, select task manager and search in the Processes tab. If the PID column is not displayed, you can cause it to display using the View | Display Columns menu entry.)

    The same command line options can be used with windbg, but you can also use the windbg File | Attach to Process menu item for the same purpose.

  2. To start up a process and attach to the new process for debugging, one can use:
    ntsd [-o] ProgramName [Arguments]
    or
    windbg [-o] ProgramName [Arguments]
    For windbg, one also has the File | Open Executable menu item which for the same purpose. Although on local drives (like C:), you can often put in a relative path name, the correct syntax is to use a full path name -- in the case of network drives, the path name should not be a drive letter, but must specify the machine, e.g. \\msc.uky.edu\t\Users\Inslab\CS470-001\user_name\myprog.exe and not just u:\myprog.exe.

    By default, the debugger will start the process and stop at an initial breakpoint as soon as it is loaded. Usually, one sets a breakpoint at the main routine and starts execution so as to skip over the process initialization code.

    III. Basic Debugger Commands

    The program being debugged is either running or stopped. If it is running, it can be stopped by using Ctrl-C (for ntsd) or Ctrl-Break (for windbg) or the windbg Debug | Break menu item.

    If the program is stopped, one can start it running:

    CommandOperation
    GoStart execution
    Step IntoExecute a single instruction
    Step OverExecute a single subroutine
    Step OutExecute until the end of the current subroutine

    These commands can be invoked in a number of ways:

    Commandntsdwindbg menuwindbg shortcut
    GogDebug | GoF5
    Step IntotDebug | Step IntoF8 or F11
    Step OverpDebug | Step OverF10
    Step Outg @$raDebug | Step Outshift-F11

    The steps are either C source code steps or assembly language instruction steps depending on whether or not Source Mode is in effect. In windbg, when there is source code available to the debugger, e.g. after you reach main, steps will be C source code steps, and the source will be displayed in a separate window. To go to assembly mode, issue the l-t command; the l+t turns on source mode again. The ASM label on the status line is grayed out when you are in source mode; you can also see this in the menus as Debug | Source Mode.

    Breakpoints are either code or data locations where execution will stop just before the code at the location is executed or when the data location is accessed. The location can be specified by virtual adddress, by module andd routine offsets, or by source file and line number (when in source mode).

    CommandOperation
    BL (Breakpoint List)List the current breakpoints and their status
    BP (Set Breakpoint)Set a new code breakpoint
    BA (Break on Access)Set a new data breakpoint
    BC (Breakpoint Clear)Remove a breakpoint
    BD (Breakpoint DisableTemporarily disable a breakpoint
    BE (Breakpoint EnableRe-enable a disabled breakpoint

    Here are some examples:

    bp 41112c
    bp main
    bp main+2c
    bp `fact.c:15`
    

    Reading and Writing Memory: The Display Memory command can display memory contents. The command is !dd or !db depending on whether you want the display unit to be double words or bytes. To Enter Values command can be used to change values. The command is !ed or !eb depending on whether it is a double word or a byte address. There are also commands for moving, filling, comparing, or searching blocks of memory.

    These commands take addresses as parameters. So, to get a value of a global variable, simply use the Evaluate Expression command as in

     ? variable_name
    to get the address and then use the display memory command to get the value. The dv command can be used to display addresses of local variables; but you need to toggle verbose mode on before it gives the addresses (Ctrl-V in ntsd or Alt-Ctrl-V in windbg).

    There are many more formats for displaying memory. For example dc displays memory as both double words as well as ANSI characters. Also, the u command disassembles code. (Try it on some code as well as on some data so you can recognize code when you see it.)

    Although many of the commands take an address as a parameter, you can often simply repeat the command without the address parameter and it will continue from where it was previously.

    Registers: Every time the debugger stops the program, the values of the registers are displayed. However, you can also use the r command to display the values of the registers. For example:

    0:000> r
    eax=00410e20 ebx=7ffdf000 ecx=00409ab8 edx=00000003 esi=00000000 edi=000aec00
    eip=0040105a esp=0012ff84 ebp=0012ffc0 iopl=0         nv up ei pl nz na po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
    fact2!main:
    0040105a 55               push    ebp
    
    You can also use it to set values of registers as in
    r eax=12c
    
    (If you want to set the default radix to be base 10, then use the command: n 10. Then r eax=12 will set the eax register to the value 0xc.)

    Setting values of the flags in EFLAGS has a special syntax, e.g. to set the carry bit, you use:

    r cf=1
    

    Stack Backtrace: One can get information about the contents of the stack in several forms:

    CommandDescription
    K (Display Stack)ebp, return address, and function name
    KP (Display Stack Parameters)Adds parameters to the list
    KD (Display Stack Data)Raw data in the stack