freebsd-dev/share/FAQ/kernel-debug.FAQ
1995-01-03 15:54:08 +00:00

418 lines
15 KiB
Plaintext

Kernel debugging FAQ for FreeBSD
$Id: kernel-debug.FAQ,v 1.3 1995/01/02 12:01:59 joerg Exp $
*** Debugging a kernel crash dump with kgdb ***
Here are some instructions for getting kernel debugging working on a
crash dump, it assumes that you have enough swap space for a crash
dump. If you happen to have multiple swap partitions with the first
one being too small to keep the dump, you can configure your kernel to
use an alternate dump device (in the ``kernel'' line). Dumps to non-
swap devices (e.g. tapes) are currently not supported.
Config your kernel using config -g
Remember that you need to specify ``options DODUMP'' in your config
file in order to get kernel core dumps.
When the kernel's been built make a copy of it, say kernel.debug, and
then run strip -x on the original. Install the original as normal.
You may also install the unstripped kernel, but symtab lookup time
for some programs might drastically increase.
If you are testing a new kernel (e.g. by typing the new kernel's
name at the boot prompt), but need to boot a different one in order
to get your system up & running again, do boot it only into single
user state (the -s flag at the boot prompt), and then perform the
following steps:
fsck -p
mount -a -t ufs # so your file system for /var/crash is writable
savecore -N /kernel.panicked /var/crash
exit # ...to multi-user
This instructs savecore to use another kernel for symbol name
extraction; it would default to the currently running kernel
otherwise.
Now, after a crash dump, go to /sys/compile/WHATEVER and run
kgdb. From kgdb do:
symbol-file kernel.debug
exec-file /var/crash/system.0
core-file /var/crash/ram.0
and voila, you can debug the crash dump using the kernel sources
just like you can for any other program.
If your kernel panicked due to a trap (perhaps the most common case
for getting a core dump), the following trick might help you. Examine
the stack (`where') and look for the stack frame in the function
trap(). Go `up' to that frame, and then type:
frame frame->tf_ebp frame->tf_eip
This will tell kgdb to go to the stack frame explicitly named by a
frame pointer and instruction pointer, which is the location where
the trap occured. There are still some bugs in kgdb (you can go
`up' from there, but not `down'; the stack trace will still remain
as it was before going to here), but generally this method will lead
you much closer to the failing piece of code.
Here's a script log of a kgdb session illustrating the above. Long
lines have been folded to improve readability, and the lines are
numbered for reference. Despite of this, it's a real-world error
trace taken during the development of the pcvt console driver.
1:Script started on Fri Dec 30 23:15:22 1994
2:uriah # cd /sys/compile/URIAH
3:uriah # kgdb kernel /var/crash/vmcore.1
4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel...done.
5:IdlePTD 1f3000
6:panic: because you said to!
7:current pcb at 1e3f70
8:Reading in symbols for ../../i386/i386/machdep.c...done.
9:(kgdb) where
10:#0 boot (arghowto=256) (../../i386/i386/machdep.c line 767)
11:#1 0xf0115159 in panic ()
12:#2 0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698)
13:#3 0xf010185e in db_fncall ()
14:#4 0xf0101586 in db_command (-266509132, -266509516, -267381073)
15:#5 0xf0101711 in db_command_loop ()
16:#6 0xf01040a0 in db_trap ()
17:#7 0xf0192976 in kdb_trap (12, 0, -272630436, -266743723)
18:#8 0xf019d2eb in trap_fatal (...)
19:#9 0xf019ce60 in trap_pfault (...)
20:#10 0xf019cb2f in trap (...)
21:#11 0xf01932a1 in exception:calltrap ()
22:#12 0xf0191503 in cnopen (...)
23:#13 0xf0132c34 in spec_open ()
24:#14 0xf012d014 in vn_open ()
25:#15 0xf012a183 in open ()
26:#16 0xf019d4eb in syscall (...)
27:(kgdb) up 10
28:Reading in symbols for ../../i386/i386/trap.c...done.
29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\
30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\
31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\
32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\
33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\
34:ss = -266427884}) (../../i386/i386/trap.c line 283)
35:283 (void) trap_pfault(&frame, FALSE);
36:(kgdb) frame frame->tf_ebp frame->tf_eip
37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done.
38:#0 0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\
39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403)
40:403 return ((*linesw[tp->t_line].l_open)(dev, tp));
41:(kgdb) list
42:398
43:399 tp->t_state |= TS_CARR_ON;
44:400 tp->t_cflag |= CLOCAL; /* cannot be a modem (:-) */
45:401
46:402 #if PCVT_NETBSD || (PCVT_FREEBSD >= 200)
47:403 return ((*linesw[tp->t_line].l_open)(dev, tp));
48:404 #else
49:405 return ((*linesw[tp->t_line].l_open)(dev, tp, flag));
50:406 #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */
51:407 }
52:(kgdb) print tp
53:Reading in symbols for ../../i386/i386/cons.c...done.
54:$1 = (struct tty *) 0x1bae
55:(kgdb) print tp->t_line
56:$2 = 1767990816
57:(kgdb) up
58:#1 0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\
59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126)
60: return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p));
61:(kgdb) up
62:#2 0xf0132c34 in spec_open ()
63:(kgdb) up
64:#3 0xf012d014 in vn_open ()
65:(kgdb) up
66:#4 0xf012a183 in open ()
67:(kgdb) up
68:#5 0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\
69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\
70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \
71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \
72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673)
73:673 error = (*callp->sy_call)(p, args, rval);
74:(kgdb) up
75:Initial frame selected; you cannot go up.
76:(kgdb) quit
77:uriah # exit
78:exit
79:
80:Script done on Fri Dec 30 23:18:04 1994
Comments to the above script:
line 6: this is a dump taken from within DDB (see below), hence the
panic comment ``because you said to!'', and a rather long
stack trace; the initial reason for going into DDB has been
a page fault trap though
line 20: the location of function ``trap()'' in the stack trace
line 36: force usage of a new stack frame, kgdb responds and displays
the source line where the trap happened; from looking at the
code, there's a high probability that either the pointer
access for ``tp'' was messed up, or the array access was
out of bounds
line 52: the pointer looks suspicious, but happens to be a valid
address...
line 56: ... but obviously points to garbage, so we have found our
error, sigh! [For those uncommon with that particular piece
of code: tp->t_line refers to the line discipline of the
console device here, which must be a rather small integer
number.]
*** Post-mortem analysis of a dump ***
What to do if a kernel dumped core but you didn't expect it, and it's
therefore not compiled using config -g?
Not everything is lost here. Don't panic. :-)
Of course, you still need to configure all your kernels with the
DODUMP option being set, otherwise you won't get a core dump at all.
(This is for safety reasons in the default kernels, to avoid them
trying to dump e.g. during system installation where there's no
FreeBSD partition at all and valuable data on the disk could be
destroyed.)
Go to your kernel compile directory, and edit the line containing
COPTFLAGS?=-O. Add the `-g' option there (but DON'T change anything
on the level of optimization). If you do already know roughly the
probable location of the failing piece of code (e.g., the `pcvt'
driver in the example above), remove all the object files for this
code. Rebuild the kernel. Due to the time stamp change on the
Makefile, there will be some other object files rebuild, e.g.
trap.o. With a bit of luck, the added -g option won't change
anything for the generated code, so you'll finally get a new kernel
with similiar code to the faulting one but some debugging symbols.
You should at least verify the old and new sizes with the `size'
command; if they mismatch, you probably need to give up here.
Go and examine the dump as described above. The debugging symbols
might be incomplete for some places (as can be seen in the stack trace
in the example above: some functions are displayed without line
numbers and argument lists). If you need more debugging symbols,
remove the appropriate object files and repeat the kgdb session until
you know enough.
All this is not guaranteed to work, but most likely will do it fine.
*** On-line kernel debugging using DDB ***
While kgdb as an offline debugger provides a very high level of user
interface (e.g. it can lookup source files, display C structures
etc.), there are some things it cannot do. The most important ones
being breakpointing and single-stepping kernel code.
If you need to do low-level debugging on your kernel, there's an on-
line debugger available called DDB. It allows to set breakpoints,
single-step kernel functions, examine and change kernel variables
etc. It can however not access kernel source files, and it does
only have access to the global and static symbols, but not to the
full debug information (including type and line number information)
like kgdb.
To configure your kernel to include DDB, add the option lines
options DDB
options "SYMTAB_SPACE=XXXX"
to your config file, and rebuild. XXXX is the amount of space to be
reserved into a global array DDB examines to find its symbols at run
time. It must be large enough to hold all symbols, but not too
large at all to avoid wasting space. 100000 Bytes are a good first
bet for a ``normal'' kernel. The link stage will tell you about the
usage of the symtab space, you'll see something like:
dbsym: need 98765; avail 100000
If the amount of allocated space has been too small, the above
message is accompanied by the following error message:
not enough room in db_symtab array
and the link stage fails. You then need to increase the number,
reconfig and recompile. If your config(8) has been compiled to not
remove the old compile directory before continuing (this is a
compile-time option [CONFIG_DONT_CLOBBER]), you need to remove
db_aout.o prior to recompilation; this is the only file being
affected by the SYMTAB_SPACE option.
Once your DDB kernel is running, there are several ways to enter
DDB. The first (and most early) way is to set the boot flag `-d'
(right at the boot prompt). The kernel will start up in debug mode
and enter DDB prior to any device probing. Hence you are able to
even debug the device probe/attach functions.
The second scenario is a hot-key on the keyboard, usually Ctrl-Alt-
ESC. (For syscons, this can be remapped, and some of the
distributed maps do this, so watch out.) There are patches
available for a COMCONSOLE kernel, ask me (joerg@FreeBSD.org) for
them.
The third way is that any panic condition will branch to DDB if the
kernel is configured to use it. (Thus it is not wise to configure a
kernel with DDB for a machine running unattended.)
The DDB commands roughly resemble some gdb commands. The first you
probably need is to set a breakpoint:
b function-name
b address
Numbers are taken hexadecimal by default, but to make them distinct
from symbol names, hex numbers starting with the letters `a' - `f'
need to be preceded with `0x' (for other numbers, this is optional).
Simple expressions are allowed, e.g. ``function-name + 0x103''.
To continue the operation of an interrupted kernel, simply type
c
To get a stack trace, use
trace
Note that when entering DDB via a hot-key, the kernel is currently
servicing an interrupt, so the stack trace might be not of much use
for you.
If you want to remove a breakpoint, use
del
del address-expression
The first form will be accepted immediately after a breakpoint hit,
and deletes the current breakpoint. The second form can remove any
breakpoint, but you need to specify the exact address, as it can be
obtained from
show b
To single-step the kernel, try
s
This will step into functions, but you can make DDB trace them until
the matching return statement is reached by
n
NOTE: this is different from gdb's ``next'' statement, it's like
gdb's ``finish''.
To examine data from memory, use e.g.
x/wx 0xf0133fe0,40
x/hd db_symtab_space
x/bc termbuf,10
x/s stringbuf
for word/halfword/byte access, and hexadecimal/decimal/character/
string display. The number after the comma is the object count.
To display the next 0x10 items, simply use
x ,10
Similiarly, use
x/ia foofunc,10
to disassemble the first 0x10 instructions of foofunc, and display
them along with their offset from the beginning of foofunc.
To modify the memory, use the write command:
w/b termbuf 0xa 0xb 0
w/w 0xf0010030 0 0
The command modifier (b/h/w) specifies the size of the data to be
writtten, the first following expression is the address to write to,
the remainder is interpreted as data to write to successive memory
locations.
If you need to know the current registers, use
show reg
Alternatively, you can display a single register value by e.g.
print $eax
and modify it by
set $eax new-value
Should you need to call some kernel functions from DDB, simply
say
call func(arg1, arg2, ...)
The return value will be printed.
For a ps-style summary of all running processes, use
ps
Well, you've now examined why your kernel failed, and you wish to
reboot. Remember that, depending on the severity of previous
malfunctioning, not all parts of the kernel might still be working
as expected. Perform one of the following actions to shut down and
reboot your system:
call diediedie()
(must usually be followed by another ``c[ontinue]'' statement),
will cause your kernel to dump core and reboot, so you can later
analyze the core on a higher level with kgdb.
call boot(0)
might be a good way to cleanly shut down the running system, sync()
all disks, and finally reboot. As long as the disk and file system
interfaces of the kernel are not damaged, this might be a good way
for an almost clean shutdown.
call cpu_reset()
...is the final way out of the desaster, almost similiar to hitting
the Big Red Button.
*** What to do if i want to debug a console driver? ***
Since you need a console driver to run DDB on, things are more
complicated if the console driver itself is flakey. You might
remember the ``options COMCONSOLE'' line, and hook up a standard
terminal onto your first serial port. DDB works on any configured
console driver, of course it also works on a COMCONSOLE.
Paul Richards, FreeBSD core team member. (paul@FreeBSD.org)
J"org Wunsch (joerg@FreeBSD.org)