151 lines
6.9 KiB
Plaintext
151 lines
6.9 KiB
Plaintext
|
|
This is version 0.1 of the combined kernel debuggers. The remote
|
|
and local are mostly independent although they need to be aware of one
|
|
another's existence. The remote debugger basically lets you run gdb
|
|
on the kernel as though it were a normal user process. The catch
|
|
is that you need a second computer capable of running GDB hooked up
|
|
to your linux box via a serial line.
|
|
|
|
The first part of local debugger combines some of the features
|
|
of GDB with the features of the old Kernel Debugger and a few others.
|
|
It modifies /dev/cmem to allow GDB to access both global and local
|
|
variables. The catch to this one is you must set a break point at the
|
|
place where you want to examine the local variables, and then wait for
|
|
it to be hit. The breakpoint does not stop, but rather saves a copy
|
|
of the kernel stack and the registers in a location which GDB knows
|
|
about. To use this you must start a modified gdb with the symbol-file
|
|
tools/system and then use target kernel. Beyond that GDB works much
|
|
like you expect, however the kernel is never (not quite see below)
|
|
stopped.
|
|
|
|
The second portion of the local debugger is the kernel
|
|
resident debugger. It allows you to stop the kernel and
|
|
examine/change memory and registers. It has only limited symbol
|
|
capabilities (specified at compile time.) It does however include a
|
|
disassembler and is automatically entered whenever the kernel would
|
|
otherwise die. It can also be entered at any time by pressing the
|
|
SysRq key. The best feature of this debugger is probably the break
|
|
points which there is unfortunately no real interface to. They can
|
|
be one-shot, conditional (bit test only), code/data breakpoints which
|
|
can call a function, save state, send a signal or cause the kernel
|
|
debugger to become active (or any combination.) Currently the only
|
|
interfaces to the break points are GDB which can only set state-saving
|
|
breakpoints, and the kernel debugger which can only set break points
|
|
which cause the debugger to be reentered. You can however edit the
|
|
break point structure using the kernel debugger, and use the
|
|
other features.
|
|
|
|
Installation
|
|
|
|
The diffs are verses .99pl6. If you have added the remote
|
|
kernel debugger patches, you should back them out with patch -R before
|
|
adding these. Afterwards a make config; make dep; make clean will be
|
|
needed to recompile the kernel (you may want to edit the CFLAGS first,
|
|
see below.) To take advantage of the GDB/kernel debugger interface
|
|
you will need to put the file lkernel.c in the gdb-4.6 source
|
|
directory and add lkernel.c and lkernel.o to the makefile, and then do
|
|
a make. That will add the kernel as a debugging target. To use the
|
|
remote kernel debugger you will need to compile the program kpt.c to
|
|
get kpt.
|
|
|
|
Using the Remote GDB.
|
|
|
|
Make sure the machine which will be running GDB has the source
|
|
and tools/system file which you used to compile the kernel on the
|
|
remote machine (you should remote -fomit-frame-pointer and add -g to
|
|
the CFLAGS). Then you initialize the serial device (I use kermit) and
|
|
run kpt (kpt /dev/tty65 (real old device name) in my case). Kpt will
|
|
activate the kernel ptrace facilities and then wait for a signal.
|
|
When it receives a signal it will deactivate the kernel ptrace
|
|
facilities and exit. On the GDB end you start GDB on the file
|
|
tools/system (or use the file or symbol-file command), and then use
|
|
target remote (You need to set your baud rate first. GDB provides a
|
|
way of doing so, but I have not bothered to figure out how yet.) The
|
|
linux kernel should then stop and you should be able to debug it as
|
|
though it were a normal program. When you are done make sure the
|
|
kernel is running, and then ctrl-c out of kpt. I would suggest
|
|
syncing and rebooting at this point in case you did some damage to the
|
|
kernel.
|
|
|
|
Using the Local Kernel Debugger with GDB.
|
|
|
|
To use the local kernel debugger with GDB, recompile the
|
|
kernel with out the -fomit-frame-pointer, and with -g in the CFLAGS.
|
|
Then run GDB on the file tools/system. After it reads the symbol
|
|
table, type target kernel (if you get an error here, you probably did
|
|
not rebuild GDB with the lkernel.c file.) GDB will then claim the
|
|
kernel has stopped at address 0. This actually means that it has not
|
|
yet hit a sate saving breakpoint. In fact if you have already used
|
|
GDB on the kernel in this manner with out rebooting, you should see
|
|
the results of your last break point. You set breaks as you normally
|
|
do with GDB, and then use the continue command. GDB will stop soon
|
|
after the break point has been hit (It may be hit more than once, but
|
|
GDB removes the break point soon after it gets control again so it
|
|
should not occur too many times.) You will then be able to examine
|
|
the state of the local variables at the time the LAST break point was
|
|
hit. You then proceed with GDB as though you were debugging a normal
|
|
program. However you should remember that the kernel has not stopped,
|
|
so global variables may have changed since the break point was
|
|
encountered. (The kernel debugger has the ability to store up to 500
|
|
long words (4000 bytes) of global data when it saves state; however,
|
|
GDB does not yet have the ability to take advantage of this.)
|
|
|
|
Using the Local Kernel Debugger
|
|
|
|
The local kernel debugger is entered whenever you press the
|
|
SysRq key (On most keyboards you must actually press Alt-SysRq.) or
|
|
when something bad happens to the kernel (a panic, or a NULL
|
|
dereference or ...) You can get a list of kernel debugger commands by
|
|
typing help, or more information about a command by prefixing it with
|
|
help. Since the kernel debugger just uses an array of names, and
|
|
addresses to dispatch commands, it is not too difficult to add your own
|
|
commands. It is also possible to add symbols to the internal
|
|
"symbol table" by editing the globals array. I believe most of the
|
|
commands are self explanatory, however I have included a list of them
|
|
here.
|
|
|
|
help -- prints out a list of commands or gives more information about
|
|
a particular command.
|
|
|
|
regs -- Displays or edits the registers.
|
|
|
|
run -- Resumes normal execution of the kernel
|
|
|
|
reboot -- Calls hard_reboot_now which reboots on most systems.
|
|
|
|
tasks -- lists the tasks
|
|
|
|
mem -- prints out memory information
|
|
|
|
dump -- displays a region of memory
|
|
|
|
list -- disassembles a region of memory
|
|
|
|
break -- manipulates the 4 possible break points (numbered 0-4)
|
|
|
|
edit -- Allows the changing of a region of memory
|
|
|
|
stack -- displays the contents of the stack.
|
|
|
|
step -- single step
|
|
|
|
current -- print out the current task
|
|
|
|
outb -- send a byte to an i/o port
|
|
|
|
inb -- read from an i/o port
|
|
|
|
print -- display the contents/value of a symbol/type as defined in the
|
|
globals array.
|
|
|
|
|
|
|
|
It is only necessary to type enough of the command so that it is unique
|
|
(Actually that is not quite true; the debugger executes the first
|
|
command it finds which matches as much as you typed.) A lot more could
|
|
be put into the debugger (like a way to load the full symbol table into
|
|
memory, or keep in on a device such as a floppy.) but there is enough
|
|
here to be very useful. Please send bugs/comments/questions to
|
|
|
|
Ross Biro
|
|
bir7@leland.stanford.edu. |