Debugger
I finally decided to write a source/symbolic debugger utility. With my experience of interfacing
GDB to EMMY, I thought I would just do that. But with EMMY, GDB runs under
Linux and still has all the Linux-like system calls it can make. Plus the fact that it is huge
and very (perhaps overly) complex lead me to write my own.
The basic idea is that the debugger runs in its own separate process and will 'attach' threads to be
debugged in a process. It will then run them as commanded. When a thread gets an exception, it will
send a message to the debugger then wait for the debugger to resume it (or abort it or whatever).
Attaching consists of the debugger making a system
call, oz_sys_thread_attach, that tells the system to notify the caller whenever the target
thread signals an exception. The target thread will then set an event flag that the caller specified
then it will wait for another event flag the caller specified.
Another kernel mechanism used by the debugger is one where the debugger can tell a target thread to
halt. The system does this by queuing an express ast to the target that causes it to signal an
OZ_HALTED exception. The attach mechanism above is invoked and the debugger is then notified
when the thread halts and the target thread then waits for the debugger to resume it.
A final kernel mechanism is peek and poke. The debugger uses oz_sys_process_peek and
oz_sys_process_poke to access the target process' address space.
Probably the hardest part of the debugger was interpreting the stabs entries. There are a few things
that have to be programmed around.
- There is no void type, they just have an infinite loop self-reference.
- It defines the char type as being 7 bits (an integer in the range 0..127) instead of 8 bits.
- Stab entries don't follow source file order, especially with respect to variable declarations. Like globals
can be defined anywhere in the executable, and static vars can be defined at the end of a module and
stack variables are defined before the enclosing left brace.
The debugger will only work with threads of one process at a time. Also, each time
it gets halted, it will scan a process for newly created threads and attach them.
Command line:
- debug [-nokernel] [-resume] <thread-id or process-id>
- thread-id or process-id is the thread or process to be debugged. All threads
in the target process will be attached and halted. If a thread-id is given, all
threads in the same process will be attached and halted.
- -nokernel tells it not to load the kernel image's symbol table on startup
- -resume tells it to resume the target thread after attaching and halting it.
If you use this option, you must specify a thread-id, not a process-id.
Also, the cli has an option, -debug, that you can put
on the front of commands which will start the debugger running on the given command. So
for example, -debug run somethingimtryingtofix will run the image, suspended, then
start the debugger on it.
Commands:
- break - manage breakpoints
- continue - continue all threads
- directory - specify source file directory
- down - go down some call levels
- dump - dump a range of memory
- file - manage executable (symbol table) files
- help - print help messages
- instruction - disassemble instructions
- print - print data
- quit - quit out of debugger
- tcontinue - continue current thread (all others stay halted)
- thread - manage threads
- tnext - step current thread by source line over calls (all others stay halted)
- tstep - step current thread by source line into calls (all others stay halted)
- tstepi - step current thread by machine instruction into calls (all others stay halted)
- up - go up some call levels
- where - print out call frame trace
Things to do:
Thread and Process
As with all of OZONE, the debugger uses the term thread to mean virtual CPU, a register set or execution context,
however you like to think of it. The debugger uses the term process to mean address space or just about everthing
else in the computer system that a thread has access to.
In the case where the debugger is being used with EMMY, thread means CPU and process means everything
else, like memory and IO devices.
I also made a separate download for the base code.
Rev date: July 10, 2004, approx 52K
This code can 'easily' be ported to other platforms. It has four directories:
- common - contains all code common to every port
- m_* - contains machine-specific code, like m_486 is for the x86 code
- o_* - contains the OS-specific code, like o_emmy is for the emmy code, o_ozone is for the ozone code
- x_* - contains the executable-specific code, like x_elf is for the elf code
So if you are porting to some x86 environment that uses elf-format executables, theoretically, 'all' you
would have to do is create a new o_<platform> directory and the corresponding routines.
The 'o' directory routines you would have to supply are:
- db_cmd_quit - process the 'quit' command
- db_getsourcefile - open a source file and mmap it to memory
- db_haltthreads - halt all threads in the target process
- db_insertbreakpoints - insert breakpoints in the target process
- db_openandmap - open an executable file and mmap it to memory
- db_readprompt - display prompt string and read command line
- db_readmem - read target process memory
- db_removebreakpoints - remove breakpoints from target process
- db_setup - perform initial debugger startup
- db_singlestepthread - singlestep a target thread one instruction
- db_startthread - start thread running (not singlestepping)
- db_waitforanyhalt - wait for any thread to halt (get exception)
- db_waitforthreadtohalt - wait for specific thread to halt (get exception) and read its registers
- db_writemem - write target process memory
- db_writethreadregs - write the registers of a given target thread