Copyright 1997-2005 Providenza
& Boekelheide, Inc.
April 6, 2005
March 19, 2003
August 2, 2001
V3.5 has a set of bug fixes. No major new features.
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:
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.
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.
|-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|
|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:
set DBG_VENDOR=1131If 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.
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
CDBG uses BARs to access the different memory spaces that many PCI
utilize. For example, many graphics devices will use one BAR for
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
physical address for each command. This is very useful since the PCI
is allowed to map these two BARs in different memory locations each
the system is booted. Thus, if you know that the FOO register is at
$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
space - this allows easy access to peripherals (such as VGA) that use
of the old DOS memory area. BAR 7 is used to allow access to other
of physical memory as specified by the user - see the BAR7
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:
cmd>The  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:
cmd> 1dmem 0will 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.
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.
|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
will also be aborted on an error.
|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|
|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() )|
|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:
The dump command allows you to dump a range of memory locations. Again, bytes, words, and dwords variants are supported.
|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
The fill command is used to fill a range of memory locations with a constant value. Again, bytes, words, and dwords variants are supported.
|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|
The test commands (byte, word, dword) provide rudimentary memory tests.
|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:
The crc command is provided to compute a 32 bit CRC of the specified memory range.
|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.
|csum $addr1 $addr2||compute 16 bit csum on the range of bytes|
|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|
|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 134ffYou could create an alias to allow less typing:
alias foo ddmp 13478 134ffTyping ‘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|
|btst, wtst, dtst||number of memory test errors|
|bcmp, wcmp, dcmp||value read from memory|
|bwait, wwait, dwait||value read from memory|
|!||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)|
|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' cdbg.zip 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
I encourage you to visit the EiC
site to learn more about it. Not a steep learning curve, but
the site does describe the extensions, limitations, etc. of EiC.
----------------------------------------------------------------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:
EiC-COMMAND SUMMARY DESCRIPTION
:-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'
|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
to read files into the C interpreter!
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 file EiChist.lst is read into CDBG at startup.
- the '!' prefix is used to recall previous commands
- several new 'arrow key' actions are defined
The ':load' command has been added as a shortcut to reset the interpreter and then load a file. This is similar to the sequence
|: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
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 allowPlease 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.
// 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) ;
|// include the standard headers...
// routine to display the 1st 16 (decimal) bytes in BAR 1
cp = BarPtr(1) ;
|// 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 *)(BarPtri + 0x1234)) ;
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:
|cmd> 3run dump||dbg_run(3, "run");|
As CDBG starts up, it tries to load the file
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.
|#define QT :quit
#define HI :history
|Shortcut for quitting
Shortcut for printing 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
- 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