Copyright 1997-2005 Providenza & Boekelheide, Inc.
April 6, 2005
March 19, 2003
August 2, 2001

What's New
V3.5 has a set of bug fixes.  No major new features.

Known Problems...

CDBG is a hardware debugger designed for use on a x86 based PCI system. It includes an extensive set of debug commands as well as a C interpreter to allow flexible scripting and interactive diagnostic development.  CDBG is derrived from DBG - the major change is that instead of Forth, CDBG includes a C interpreter.

CDBG is a DOS based application designed to run in a variety of OS configurations. We’ve successfully used CDBG in:

Why doesn’t CDBG use a fancy Windows interface with dialog boxes and pop-up menus? CDBG needs to be able to run in a simple environment to help debug hardware. The Windows OS may make the debug environment too fragile to use. Speed is also a concern. We’ve used CDBG on PC’s where the Pentium is running at about 2% of its normal speed - you just don’t want to wait for Windows to boot when the computer is this slow.

CDBG is constantly in development - your feedback is welcome.  The revision history section can show you what's new.

*** BE CAREFUL - CDBG gives you low level access to h/w - you can easily trash memory and crash your system.

Throughout this document, hex numbers are used unless otherwise noted. Note that CDBG (unlike its C interpreter) is case insensitive. We’ll frequently use the style $reg to emphasize that the parameter us a hex value.

Bytes are 8 bits, Words are 16 bits, Dwords are 32 bits.

Note that when specifying a memory address...

Thanks to Ed Breen for the EiC interpreter source code.

Starting CDBG
CDBG is started from the DOS prompt by typing:

cdbg [options]
If no options are specified, CDBG finds all the devices on the PCI bus and prompts the user to select one for debugging. CDBG then uses the selected device to initialize internal Base Address Registers (BARs) that are used to access the selected device. You can skip this interactive prompt by using both the -d and -v options to manually specify a PCI device. Multiple command line options can be used.
Command Line Options
Flag Description
-d$pci_device_id specify the target PCI device ID
-v$pci_vendor_id specify the target PCI vendor ID
-nfunction specify the target PCI function
-ccommand run a debugger command
-i start in interactive C mode
-ifilename load the specified file, and start in interactive C mode
-sfilename specify a script to run
-q quit the debugger

Command Line Examples
cdbg -d1234 -v5678 Start the debugger and use the PCI device with vendor ID 5678 (hex) and device ID 1234 (hex).
cdbg "-c 1dmem 0" -cbye Start the debugger, display dword 0 in BAR 1, then stop the debugger. Note the quotes.
cdbg -d1234 -v5678 -i Start the debugger and use the PCI device with vendor ID 5678 (hex) and device ID 1234 (hex). Start in interpreted C mode.

Command line options can be used to specify the desired target PCI device:

The vendor/device IDs and function can also be specified using the environment variables DBG_VENDOR, DBG_DEVICE, and DBG_FUNCTION.  (Note that many PCI device are single function, so you can skip setting the function variable.) These would typically be set using the DOS 'set' command:
set DBG_VENDOR=1131
set DBG_DEVICE=8500
If neither valid command line options or environment variables have been used to specify the PCI device/vendor values, DBG reverts to a simple interactive prompt to force the user to select an appropriate device.

Using CDBG
CDBG can either operate in native mode or in C mode. Native mode presents a traditional user interface with a limited command set. C mode provides access to an interpreted inplementation of the C programming language that has several pre-defined C functions to allow low level access to H/W.

Core to CDBG is the concept of  Base Address Registers (BARs).  CDBG uses BARs to access the different memory spaces that many PCI devices utilize. For example, many graphics devices will use one BAR for registers and another BAR to access the video memory. CDBG’s BAR registers allow you to specify offsets into each BAR rather than having to specify an actual physical address for each command. This is very useful since the PCI BIOS is allowed to map these two BARs in different memory locations each time the system is booted. Thus, if you know that the FOO register is at offset $14 in BAR 1, you can access FOO by simply using the number $14 rather than an absolute physical address. CDBG treats BARs 7 and 9 specially. BAR 9 is pre-initialized to allow access to the bottom 1MB of PC address space - this allows easy access to peripherals (such as VGA) that use portions of the old DOS memory area. BAR 7 is used to allow access to other areas of physical memory as specified by the user - see the BAR7 command below.

'Native' CDBG Commands
CDBG has numerous 'native' commands.  These commands allow you to examine memory, IO, and configuration spaces, test memory, etc.  The commands can be broken into the following categories:

The native mode CDBG prompt looks like:
The [0] in this case indicates that, by default, BAR 0 will be used for all memory oriented commands. You can change this default by using the BAR command.

Sometimes, you want to access a memory location in a different BAR than the default for only one or two commands. CDBG allows you to do this by prepending the desired BAR to the command. For example:

[0]cmd> 1dmem 0
will use BAR 1 for the dmem command.

A command history feature has been added to allow previous commands to be recalled, edited, and re-executed.

Basic Access Commands
The following are basic memory, I/O, and PCI commands. Each command begins with either a b (byte), w (word), or d (dword) to specify the size of the access to perform. Thus, the bpci command is used to access individual bytes of pci configuration registers for the current device.

The pci, io, and mem commands are used to access a single memory, I/O, or PCI configuration location. Each command is followed by an address and, optionally, a data value. If no data value is specified, the value at the specified address is displayed. If a data value is specified, it is written to the location.

Command Action
bpci $reg <$data> byte read or write a PCI register 
wpci $reg <$data> word read or write a PCI register
dpci $reg <$data> dword read or write a PCI register
bio $port <$data> byte read/write a I/O port
wio $port <$data> word read/write a I/O port
dio $port <$data> dword read/write a I/O port
bmem $addr <$data> byte read/write a memory location
wmem $addr <$data> word read/write a memory location
dmem $addr <$data> dword read/write a memory location

The compare commands are used to compare memory to an expected value.  If the value does not match, an error message is printed.  If the halt flag is set, repeat_loops and scripts will also be aborted on an error.

Command Action
bcmp $addr $data compare byte memory location to an expected value
wcmp $addr $data compare word memory location to an expected value
wcmp $addr $data compare dword memory location to an expected value

The wait commands are used to wait for an event to occur as indicated by a value in memory changing to an expected value.  The wait can be interrupted by a keyboard hit - if so, an error message is printed and repeat_loops/scripts will be aborted if the halt flag is set.
Command Action
bwait $addr $value $mask wait until ( (byte_memory & $mask) == $value || kbhit() )
wwait $addr $value $mask wait until ( (word_memory & $mask) == $value || kbhit() )
dwait $addr $value $mask wait until ( (dword_memory & $mask) == $value || kbhit() )

The examine command allows you to interactively examine and modify memory locations. Like the previous commands, byte, word, and dword variants allow access to different sized locations.
Command Action
bx $addr examine bytes in memory
wx $addr examine words in memory
dx $addr examine dwords in memory

After entering the command, CDBG displays the current value in the memory location, then waits for user input:

  • hex numbers are used to alter the value in memory
  • space  advances to the next location. If any hex numbers have been entered prior to the space key, they are written to the current location prior to advancing.
  • enter  re-reads the current location. If any hex numbers have been entered prior to the enter key, they are written to the current location prior to re-reading.
  • back-space  advances to the previous location. If any hex numbers have been entered prior to the space key, they are written to the current location prior to advancing.
  • other aborts current examine command.
  • This command does not have a graceful way to correct typing errors - be careful! If you mis-type a hex value, type eight zeroes then the value you intended.

    The dump command allows you to dump a range of memory locations. Again, bytes, words, and dwords variants are supported.

    Command Action
    bdmp $addr1 [$addr2] dump bytes of memory
    wdmp $addr1 [$addr2] dump words of memory
    ddmp $addr1 [$addr2] dump dwords of memory

    If no second address is specified, it is assumed to be 3Fh greater than addr1.

    The fill command is used to fill a range of memory locations with a constant value. Again, bytes, words, and dwords variants are supported.

    Command Action
    bfil $addr1 $addr2 $data fill byte memory range with data
    wfil $addr1 $addr2 $data fill word memory range with data
    dfil $addr1 $addr2 $data fill dword memory range with data


    CDBG has a set of commands designed to help in testing memory and registers.

    The test commands (byte, word, dword) provide rudimentary memory tests.

    Command Action
    btst [$addr1 $addr2] run byte oriented memory tests
    wtst [$addr1 $addr2] run word oriented memory tests
    bdst [$addr1 $addr2] run dword oriented memory tests

    The memory tests operate in several phases:

    If any errors are found during testing, the error message is displayed with the address that failed, the data read from memory, the expected data, a -re-read of the bad location, and an XOR of the bad data against the expected data.

    The crc command is provided to compute a 32 bit CRC of the specified memory range.

    Command Action
    bcrc $addr $addr [$value] compute byte CRC, optionally compare to expected value
    wcrc $addr $addr [$value] compute word CRC, optionally compare to expected value
    dcrc $addr $addr [$value] compute dword CRC, optionally compare to expected value

    The csum command is used to compute a 16 bit checksum of the specified memory range.

    Command Action
    csum $addr1 $addr2 compute 16 bit csum on the range of bytes


    Script/DOS Commands
    CDBG can read commands from a script file. The following commands are useful when invoking or writing scripts.
    Command Action
    loop rewind the current script and run it again
    loop $value $mask loop script if ((last_read & mask) == value) This is not well supported since Forth is far more flexible for looping, etc.
    s file_name run the script
    p <name> run the DOS program. If no name, shell to a DOS prompt


    Misc. Commands
    The following commands are useful for you to know.
    Command Action
    help print a help menu
    h print a help menu
    ? print a help menu
    alias name string define an alias for 'name'. This is a nice way to create a short-cut to avoid re-typing the same long boring string over and over.
    bar $reg specify default BAR register for memory base
    bar7 $phys_addr $size create pseudo BAR 7 for system memory access
    log <name> open/close log file
    bye quit the debugger
    quit quit the debugger
    q quit the debugger
    halt 0|1 set/clear halt on error flag.  If an error is detected, scripts and repeat loops will be aborted if this flag is set.
    stat print status (error count, pass count, etc.)
    kwait wait for keyboard hit
    count [$value] set or increment pass counter
    test $value $mask set error flag if ((last_read & mask) != value)
    c transfer into C interpreter

    Several of these commands deserve a little extra description.

    BAR7 is used to access physical memory - it is very dangerous if you’re not careful. When working on devices that perform bus mastering, you frequently need to obtain access to a chunk of physical memory. BAR7 does not allocate memory - it simply lets you access it. You can do bad things to the system memory with this. The command takes two parameters: a physical address to access, and the size of the area to access. Once you have initialized BAR7, you can use normal CDBG commands to examine and change values in the physical memory. You cannot use BAR7 a second time to change the memory aperture - get it right the first time!

    One trick we’ve used when working with bus mastering devices is to use a PC with a reasonable amount of memory, say 32MB. We can guess that DOS will probably not be using the upper 16MB, so, big_gulp, we assume that we can use this area as a scratch-pad for bus mastering experiments using BAR 7 to peek and poke the locations. You can get in big trouble doing this, so good luck! Remember, BAR7 just provides a way to access physical memory, it doesn’t allocate it.
    It’s easy to forget that BAR7 is a different command from BAR   7. The former creates an aperture to access physical memory, the latter specifies a default BAR for subsequent commands.


    ALIAS is a way to create text short-cuts. For example, if you are repeatedly typing:

    ddmp 13478 134ff
    You could create an alias to allow less typing:
    alias foo ddmp 13478 134ff
    Typing ‘foo’ will now execute the memory dump command.

    HALT allows a ‘halt on error’ flag to be set or cleared. This will terminate commands that have been invoked with the ‘!’ prefix.

    LAST_READ is a variable set during the execution of several of the commands. It is useful in aborting scripts, but has been largely supplanted by the flexibility provided by the C interpreter. The following commands set last_read:

    Command Value set in last_read
    bio, wio, dio value read from IO port
    bpci, wpci, dpci value read from PCI cfg register
    bmem, wmem, dmem value read from memory
    bcrc, wcrc, dcrc crc computed
    csum checksum computed
    btst, wtst, dtst number of memory test errors
    bcmp, wcmp, dcmp value read from memory
    bwait, wwait, dwait value read from memory

    Command Prefixes
    Commands can have simple prefixes prepended to them to slightly modify the normal behavior of the command.
    The valid prefixes are:
    Prefix Action
    ! repeat command until keyboard hit or error
    0..9 over-ride default PCI BAR for this command
    / start of non-printing comment (useful in scripts)
    # start of printing comment (useful in scripts)

    Graphics Commands
    CDBG includes a number of commands to assist us in debugging graphics cards.  One of these days I'll document them here.  To work properly, you need to use the mode command to set the screen width (stride) in pixels, height, and color depth.  Good Luck.  If you actually try to use these commands, let me know and I'll try to help you out.

    C Interpreter
    CDBG contains the EiC interpreter that is extremely useful for writing routines to aid in hardware debug. The c command is used to switch from the native debugger into the C interpreter while the :dbgcommand switches back.  A set of  pre-defined low level C routines are also provided to access hardware for writing tests or diagnostics without having to understand X86 memory mapping.  Note that C can be used to add new commands to the native debugger command set - see the section on Extending CDBG.
    NOTE: CDBG is currently distributed in a zip archive that contains cdbg.exe and a bunch of .h include files  The include files are very important if you plan to use the C interpreter.  The simplest setup is to 'unzip' in a directory.already in your search path.  By default, cdbg is supposed to look for include files in the current directory and also in the directory dbg_inc if it exists where cdbg was installed.  Since EiC is fairly strict about function prototypes, it has to be able to find dbg.h, stdio.h, etc. if you plan to access h/w or use nice C functions like printf.

    If you're at all interested in using the C interpreter built into CDBG, I encourage you to visit the EiC web site to learn more about it.  Not a steep learning curve, but the site does describe the extensions, limitations, etc. of EiC.

    Using the EiC Interpreter...
    When C is active, the debugger prompt changes to:
           EiC 1>
    The number before the > indicates the command number for the next C command.  At this point you have several choices... Some things to note when using the C interpreter... C Interpreter Commands
    The C interpreter has a number of built-in commands some of which are summarized below.  Note that all these commands start with a colon (:).  In addition to these commands, the normal #include provided by the C pre-processor in the interpreter can be used to read files into the interpreter.
    :-I path       Append path to the include-file search list.
    :-L            List search paths.
    :clear fname   Removes the contents of file fname from EiC.
    :dbg           Return to core CDBG debugger.
    :exit          Terminates an EiC session.
    :files         Display the names of all included files.
    :files fname   Summarize the contents of the included file `fname'.
    :help          Display summary of EiC commands.
    :history [N]   List the history of (N or all) input commands.
    :load fname    reset the interpreter, then load the specified file.
    :reset         Reset EiC back to its start state.
    :reset here    Set the `reset' state to EiC's current state.
    :show name     Show information about item called 'name'
    At the EiC command prompt you can enter either an EiC command or invoke an existing C function by typing the function name with the proper parameters:
    printf("%08x", 1234) ; // Print the number 1234 in hex format
    integer i ;\
    for(i=0; i<10; i++) \
        printf("I=%d\n", i) ;
    // count & print - the backslashes are improtant!
    dbg ("1dmem 0") ; // ask dbg to execute "1dem 0"

    Remember to use #include "filename" or :load filename to read files into the C interpreter!

    Extensions/Changes to EiC
    The EiC interpreter used in CDBG has some extensions that may or may not be adopted into the standard EiC distribution:
    The Command History mechanism has been extended in several ways: The ':show' command has been extended to list similar command names if an exact match is not found.

    The ':load' command has been added as a shortcut to reset the interpreter and then load a file.  This is similar to the sequence
      #include fname

    Command History
    Previously executed C and CDBG commands are maintained in a history buffer.  The history buffer is saved in the file EiChist.lst and it is read in at the start of the program.  The following commands/keystrokes are used to manipulate the history buffer:
    :history display history buffer
    :history 10 display the last 10 entries in the history buffer
    !123 re-execute command number 123
    !:hi re-execute the most recent command that started with the letters ':hi'
    <up-arrow> or ctrl-P scroll to an older (previous) command
    <down-arrow> or ctrl-N  scroll to a newer command
    <left arrow> move cursor left one character
    <right arrow> move cursor right one character
    <ctrl-left-arrow> move cursor left one word
    <ctrl-right-arrow> move cursor right one word
    <enter> execute current command
    ctrl-A moves to the beginning of the line
    ctrl-E moves to the end of the line
    ctrl-K kills from current position to the end of line
    ctrl-H delete the previous character
    DEL delete current character
    ctrl-L or ctrl-R re-draw line in case it gets trashed
    ctrl-U kills the entire line
    ctrl-W kills last word

    Note that several of these history buffer control keys are extensions to EiC.

    C Hardware Functions
    CDBG has a number of pre-defined C functions that can be used to access H/W.  The fuctions, as defined in dbg.h, allow you to access x86 I/O ports, PCI configuration registers for the current PCI device, and memory apertures for the current PCI device.

    As defined in dbg.h, the following words have been added as extensions to C to aid in accessing hardware.

    // Declarations of builtin C Functions exposed to EiC to allow
    // interpreted C code access to h/w resources...

    // our std shortcuts for bytes, words, dwords
    #define UCHAR unsigned char
    #define USHORT unsigned short
    #define ULONG unsigned int

    // print to terminal and log file
    void log_print(char *msg) ;

    // PCI config reg write access...
    void WrCfg8(UCHAR regnum, UCHAR value) ;
    void WrCfg16(UCHAR regnum, USHORT value) ;
    void WrCfg32(UCHAR regnum, ULONG value) ;

    // PCI config reg read access...
    UCHAR RdCfg8(UCHAR regnum) ;
    USHORT RdCfg16(UCHAR regnum) ;
    ULONG RdCfg32(UCHAR regnum) ;

    // x86 i/o routines
    UCHAR inb (int port) ;
    USHORT inw (int port) ;
    ULONG ind (int port) ;
    void outb (int port, UCHAR data) ;
    void outw (int port, USHORT data) ;
    void outd (int port, ULONG data) ;

    // Provide a 'pointer' to access the specified the memory region
    // allocated to the specified BAR.  Since CDBG works in a DPMI
    // memory management environment, the pointers is NOT the same
    // as the physical address specified in the BAR!
    char *BarPtr(int bar) ;

    // size of the current bar
    int BarSize(int bar) ;

    // core debug function calling...
    int dbg(char *cmd_string) ;

    // keyboard i/f extensions to EiC
    int kbhit(void) ;
    int getch(void) ;

    Please note that C pointers are NOT in the same address space as the values in the BAR registers.  BAR values are physical memory pointers while the C intepreter use virtual memory pointers.  The function BarPtr is used to return a virtual (C) pointer for the desired BAR address region.  The C pointer provided by BarPtr is an "EiC safe pointer" that should prevent accesses outside of the BAR's address space.  At least, that's the theory.

    C Example
    The following (trivial) example gives a flavor of using C to aid in debug.
    // include the standard headers...
    #include <stdio.h>
    #include "dbg.h"

    // routine to display the 1st 16 (decimal) bytes in BAR 1
    void first16 (void)
        UCHAR *cp ;
        int i ;

        cp = BarPtr(1) ;
        for(i=0; i<16; i++)
            printf("%02x ", cp[i]) ;
        printf("\n") ;

    // routine to loop until the dword at 0x1234 is non-0 or a
    // key is hit on the keyboard
    void Wait1234 (void)
        ULONG *lp ;

        lp = ( (ULONG *)(BarPtr[0]i + 0x1234)) ;
        while(*lp == 0) {
            if (kbhit() {
                getch() ;
                break ;

    Extending CDBG with C
    Under certain circumstances, it is useful to add custom commands to CDBG.  Recently, one of our clients was debugging an I2C device. By writing a simple set of C functions, we were able to extend CDBG with a simple user interface to allow peeking and poking registers in his device.

    When the native debugger receives a command, it first checks to see if a C routine has been defined with a dbg_ pre-pended to the command name.  If so, the C interpreter is called to execute the command.  If the dbg_command has not been defined, CDBG tries to execute the command from it's native command set.  This allows 'native' debugger commands to be re-defined in interpreted C.  For example:

    You Type
    CDBG tells C
    [1] cmd> foo 1 2 3
    dbg_foo(1, "1 2 3") ;
    [1]cmd> 3run dump  dbg_run(3, "run");
    Note that CDBG passes 2 parameters to C: the target BAR as an interger and the command argument(s) as a string.  This implies that the C code implementing the command extension needs to use a C function like sscanf or atoi to convert the arguments into integers.

    As CDBG starts up, it tries to load the file dbg_ext.c to automatically add any extensions that you may have defined.  It first looks in the current directory, then, if needed, it scans through its default include search path.

    Tips & Hints
    1. If you have a sequence of EiC commands that you type frequently, you can use #define and add the commands into starteic.h file. For example:
    2. #define QT :quit
      #define HI :history

      Shortcut for quitting
      Shortcut for printing history
    3. The interpreter automatically defines 2 names to help C code recognize when it's running under CDBG/EiC:
    4. _CDBG

    Revision History
     8-28-01 Initial release to web.  No warrenties expressed or implied.
    Added dbg_ext.c auto load
    - changed 'assert()' to exit the function instead of returning
    - enabled 'microtime()' in unistd.h - it's only approximate
    - minor robustness improvements
    - changed up/down arrow keys to skip duplicated history lines
    - fixed problem with stat() and fstat() library functions
    - fixed a crashing problem when reloading a program - purge ARgarbage collector on reset so we don't  keep stale data around
    - fixed problem with exit() and abort() library functions

    Bug Reports & Comments

    Please email bug reports or comments to johnp (@t) probo dot com