编译内核并用KGDB调试
1. 配置vmware调试机与目标机的串口
[虚拟机上添加串口]
"power off"关闭虚拟机
右击虚拟机
-->选择"settings..."
-->hardware-->Add...
-->Serial Port
-->output to named pipe-->两虚拟机都使用名称"\\.\pipe\com_1",
对调试机使用"This end is the client",
对目标机使用"This end is the server",
两个虚拟机均使用"The other end is a virtual machine"
把Connect at power on 勾选上
-->完成后在"I/O mode" 组合框里把Yield CPU on poll勾选上
如果虚拟机是vSphere配置基本差不多,vSphere的配置更简单些
[sio驱动]
查man sio可知串口设备名
[uart驱动]
查man uart可知串口设备名
[调试串口例子]
目标:
cat /dev/tty*
调试机
echo test > /dev/cua*
在目标机上会出现test则表示串口调试成功
在远程调试开始时,您可能遇到 Ignoring packet error... 错误。
该错误表示您的开发计算机和目标计算机具有不同的波特率。计算机的波特率是指每秒传输的符号数目。波特率与比特率可能并不相同,
因为一个符号可能包含两个以上的状态。需要为两台计算机设置相同的波特率。
使用以下命令可以实现这一目标:
set baudrate 9600
2. 编译内核
注:两种方法,有的版本适合方法一,有一适合用方法二
[方法一]:
官方的教程http://www.freebsd.org/doc/handbook/kernelconfig-building.html
--<在调试机上>
cd /usr/src/sys/i386/conf
mkdir /root/kernels
cp GENERIC /root/kernels/MYKERNEL
ln -s /root/kernels/MYKERNEL
ln -s /vte/86DX
vi DEBUG_KERNEL
加入如下内容:
makeoptions DEBUG=-g
options KDB
options KDB_TRACE
options DDB
options GDB
cd /usr/src
make buildkernel KERNCONF=MYKERNEL
make installkernel KERNCONF=MYKERNEL
完成上述步骤之后,在/boot下原来的kernel内核被放到/boot/kernel.old/kernel下,
安装后的内核即为/boot/kernel
[编译Freebsd8.3内核]
--<在调试机上>
cd /usr/src/sys/i386/conf
mkdir /root/kernels
cp GENERIC /root/kernels/DEBUG_KERNEL
ln -s /root/kernels/DEBUG_KERNEL
vi DEBUG_KERNEL
加入如下内容:
makeoptions DEBUG=-g
options KDB
options KDB_TRACE
options DDB
options GDB
config DEBUG_KERNEL
cd ../compile/DEBUG_KERNEL
make cleandepend
make depend
make
完成上述步骤后,在调试机的当前目录下(/usr/src/sys/i386/compile/DEBUG_KERNEL)
目录下会生成kernel.debug文件夹,把该文件夹拷贝到目标机上,放到/boot/kernel/目录下,
并更名为kernel,并使用strip -x去掉其中的调试符号
3. 内核调试
--<在目标机上>
cp /boot/device.hints /boot/device.hints.bk
vi /boot/device.hints
加入如下内容:
如果串口是sio驱动就修改如下内容为0x80端口:
hint.sio.0.flags="0x80"
如果串口是uart驱动就修改如下内容为0x80端口:
hint.uart.0.flags="0x80"
如果因为freebsd版本在进入DDB模式之后会失去键盘响应,这主要是由于DDB和kbdmux共存还
有问题导致的,解决办法是加入如下内容:
hint.kbdmux.0.disabled="1"
reboot
选择6.Escape to loader prompt
在"OK"提示符后加入boot -d
然后再依次输入"gdb"和"s"命令,使其进入等待远端gdb调试连接的状态
--<在调试机上>
cd /usr/src/sys/i386/compile/DEBUG_KERNEL
如果是sio驱动输入如下命令
kgdb -r /dev/cuad0 kernel.debug
如果是uart驱动输入如下命令
kgdb -r /dev/cuau0 kernel.debug
稍等片刻就可以完成和目标机的链接,并可以调试.
进入调试模式后做如下断点:
(kgdb) b kldload
这一步是为今后调试可动态装载模块即非内核模块的调试方便使用
4. 用 GDB 调试可加载模块 --- 以nullfs为例
[freebsd可加载模块结构]:
所有的可动态加载模块均存放在/usr/src/sys/modules目录对应的模块名文件夹下例如nullfs等,
编译这些模块会自动找到代码所在的位置。
在调试发生于模块中的 panic, 或者使用远程 GDB 调试使用动态模块的机器时, 需要告诉 GDB 如何获取这些模块的符号信息。
首先, 需要在编译模块时加入调试信息:
# cd /sys/modules/nullfs
# make clean; make COPTS=-g
把开发机上编译出来的nullfs.ko拷贝到目标机上的任何位置如/tmp,在目标机上安装nullfs.ko执行命令
# cd /tmp
# kldload ./nullfs.ko
回到开发机上会发现kldload断点出现了,但这时nullfs.ko还没有被加载成功,直接执行gdb命令c继续进行
执行完后在目标机上再加载一次加载命令如下:
# kldload ./nullfs.ko
在开发机上出现断点后通过以下过程加载nullfs的符号链接信息,并具体调试,如下:
如果使用远程 GDB, 您可以在目标机上执行 kldstat 来了解模块的加载位置:
# kldstat
Id Refs Address Size Name
1 4 0xc0100000 1c1678 kernel
2 1 0xc0a9e000 6000 linprocfs.ko
3 1 0xc0ad7000 2000 warp_saver.ko
4 1 0xc0adc000 11000 nullfs.ko
如果您正调试内核崩溃转存数据, 则需要访问 linker_files 表, 从 linker_files->tqh_first 开始,
并沿 link.tqe_next 指针寻找包含您所查找的 filename 的项。 那个项的 address 成员就是模块的加载地址。
接下来, 您需要找出模块中代码节 (text section) 的偏移量:
# objdump --section-headers /sys/modules/nullfs/nullfs.ko | grep text
3 .rel.text 000016e0 000038e0 000038e0 000038e0 2**2
10 .text 00007f34 000062d0 000062d0 000062d0 2**2
您需要的是 .text 节, 在前述例子中, 是 10 号节。第四个十六进制字段 (或者说从左往右数第六个字段) 是代码节在文件中的偏移量。
将这一偏移量与模块的加载地址相加, 就可以得到模块的代码在重定位之后的地址了。
在我们的例子中, 可以得到 0xc0adc000 + 0x62d0 = 0xc0ae22d0。
接下来就可以用 add-symbol-file 来告诉
GDB 关于调试模块的信息:
(kgdb) add-symbol-file /sys/modules/nullfs/nullfs.ko 0xc0ae22d0
add symbol table from file "/sys/modules/nullfs/nullfs.ko" at text_addr = 0xc0ae22d0?
(y or n) y
Reading symbols from /sys/modules/nullfs/nullfs.ko...done.
(kgdb) l nullfs_mount
(kgdb) b nullfs_mount
(kgdb) c
现在就可以使用模块的全部符号了。
在目标机上:
# cd /mnt
# mkdir null
# mkdir nullmount
# mount_nullfs null nullmount
回到开发机上发现断点出现在nullfs_mount上,这时就可以继续调试该模块的函数了。
如果kgdb在运行中要通过执行kldload ./nullfs.ko来实现断点出现,而不是通过ctrl+c命令来实现断点出现,
后面的这个方法实验是不成功的,几乎花费了我一整天的时间摸清楚这个事情。