# mkdir /var/crash
# chmod 700 /var/crash
第10章 内核调试
目录
10.1. 获取内核崩溃转储
当运行开发内核(例如,FreeBSD-CURRENT),例如在极端条件下运行的内核(例如,非常高的负载平均值、数万个连接、极高数量的并发用户、数百个 jail(8) 等),或在 FreeBSD-STABLE 上使用新功能或设备驱动程序(例如,PAE)时,有时内核会发生恐慌。如果发生这种情况,本章将演示如何从崩溃中提取有用的信息。
内核恐慌后,系统重新启动是不可避免的。系统重新启动后,系统物理内存 (RAM) 的内容将丢失,以及恐慌前交换设备上的任何位。为了保留物理内存中的位,内核利用交换设备作为临时存储位置,以在崩溃后跨越重新启动存储 RAM 中的位。通过这样做,当 FreeBSD 在崩溃后启动时,现在可以提取内核映像并进行调试。
配置为转储设备的交换设备仍然充当交换设备。目前不支持向非交换设备(例如磁带或 CDRW)进行转储。 “交换设备”与“交换分区”同义。 |
有几种类型的内核崩溃转储可用
- 完整内存转储
保存物理内存的完整内容。
- 迷你转储
仅保存内核正在使用的内存页(FreeBSD 6.2 及更高版本)。
- 文本转储
保存捕获的、脚本化的或交互式调试器输出(FreeBSD 7.1 及更高版本)。
从 FreeBSD 7.0 开始,迷你转储是默认的转储类型,并且在大多数情况下,它将捕获完整内存转储中存在的所有必要信息,因为大多数问题只能使用内核状态隔离。
10.1.1. 配置转储设备
在内核将其物理内存的内容转储到转储设备之前,必须配置转储设备。转储设备是使用 dumpon(8) 命令指定,以告诉内核在何处保存内核崩溃转储。必须在使用 swapon(8) 配置交换分区后调用 dumpon(8) 程序。这通常通过在 rc.conf(5) 中将 dumpdev
变量设置为交换设备的路径(提取内核转储的推荐方法)或 AUTO
来使用第一个配置的交换设备来处理。在 HEAD 中,dumpdev
的默认值为 AUTO
,并在 RELENG_* 分支上更改为 NO
(RELENG_7 除外,它保留为 AUTO
)。在 FreeBSD 9.0-RELEASE 及更高版本中,bsdinstall 将在安装过程中询问是否应在目标系统上启用崩溃转储。
检查 /etc/fstab 或 swapinfo(8) 以获取交换设备列表。 |
确保在内核崩溃之前 rc.conf(5) 中指定的 此外,请记住 /var/crash 的内容是敏感的,并且很可能包含机密信息,例如密码。 |
10.1.2. 提取内核转储
将转储写入转储设备后,必须在挂载交换设备之前提取转储。要从转储设备提取转储,请使用 savecore(8) 程序。如果已在 rc.conf(5) 中设置了 dumpdev
,则在崩溃后的第一次多用户启动时,以及在挂载交换设备之前,将自动调用 savecore(8)。提取的核心位置放置在 rc.conf(5) 值 dumpdir
中,默认为 /var/crash,并将命名为 vmcore.0。
如果 /var/crash(或 dumpdir
设置到的任何位置)中已经存在名为 vmcore.0 的文件,则内核将为每次崩溃递增尾随数字,以避免覆盖现有的 vmcore(例如,vmcore.1)。在保存转储后,savecore(8) 将始终创建一个名为 vmcore.last 的符号链接到 /var/crash 中。此符号链接可用于查找最近转储的名称。
crashinfo(8) 实用程序生成一个文本文件,其中包含来自完整内存转储或迷你转储的信息摘要。如果已在 rc.conf(5) 中设置了 dumpdev
,则在 savecore(8) 之后将自动调用 crashinfo(8)。输出保存到 dumpdir
中名为 core.txt.N 的文件中。
如果您正在测试一个新内核,但需要引导另一个内核才能使系统重新运行,请仅使用引导提示符处的
这指示 savecore(8) 从 /dev/ad0s1b 提取内核转储,并将内容放置在 /var/crash 中。不要忘记确保目标目录 /var/crash 有足够的转储空间。此外,不要忘记指定交换设备的正确路径,因为它可能与 /dev/ad0s1b 不同! |
10.1.3. 测试内核转储配置
内核包含一个 sysctl(8) 节点,用于请求内核恐慌。这可用于验证系统是否已正确配置以保存内核崩溃转储。您可能希望在触发崩溃之前以只读方式重新挂载现有文件系统到单用户模式,以避免数据丢失。
# shutdown now
...
Enter full pathname of shell or RETURN for /bin/sh:
# mount -a -u -r
# sysctl debug.kdb.panic=1
debug.kdb.panic:panic: kdb_sysctl_panic
...
重新启动后,系统应将转储保存到 /var/crash 中,以及来自 crashinfo(8) 的匹配摘要。
10.2. 使用kgdb
调试内核崩溃转储
要进入调试器并开始从转储中获取信息,请启动 kgdb
# kgdb -n N
其中N是要检查的vmcore.N的后缀。要打开最新的转储,请使用
# kgdb -n last
通常,kgdb(1)应该能够找到生成转储时运行的内核。如果它无法找到正确的内核,请将内核和转储的文件路径作为两个参数传递给 kgdb
# kgdb /boot/kernel/kernel /var/crash/vmcore.0
您可以使用内核源代码调试崩溃转储,就像调试任何其他程序一样。
此转储来自 5.2-BETA 内核,崩溃发生在内核深处。下面的输出已修改为在左侧包含行号。此第一个跟踪检查指令指针并获取回溯。第 41 行用于list
命令的地址是指令指针,可以在第 17 行找到。如果无法自行调试问题,大多数开发人员会要求至少发送此信息给他们。但是,如果您确实解决了问题,请确保您的补丁通过问题报告、邮件列表或能够提交的方式进入源代码树!
1:# cd /usr/obj/usr/src/sys/KERNCONF
2:# kgdb kernel.debug /var/crash/vmcore.0
3:GNU gdb 5.2.1 (FreeBSD)
4:Copyright 2002 Free Software Foundation, Inc.
5:GDB is free software, covered by the GNU General Public License, and you are
6:welcome to change it and/or distribute copies of it under certain conditions.
7:Type "show copying" to see the conditions.
8:There is absolutely no warranty for GDB. Type "show warranty" for details.
9:This GDB was configured as "i386-undermydesk-freebsd"...
10:panic: page fault
11:panic messages:
12:---
13:Fatal trap 12: page fault while in kernel mode
14:cpuid = 0; apic id = 00
15:fault virtual address = 0x300
16:fault code: = supervisor read, page not present
17:instruction pointer = 0x8:0xc0713860
18:stack pointer = 0x10:0xdc1d0b70
19:frame pointer = 0x10:0xdc1d0b7c
20:code segment = base 0x0, limit 0xfffff, type 0x1b
21: = DPL 0, pres 1, def32 1, gran 1
22:processor eflags = resume, IOPL = 0
23:current process = 14394 (uname)
24:trap number = 12
25:panic: page fault
26 cpuid = 0;
27:Stack backtrace:
28
29:syncing disks, buffers remaining... 2199 2199 panic: mi_switch: switch in a critical section
30:cpuid = 0;
31:Uptime: 2h43m19s
32:Dumping 255 MB
33: 16 32 48 64 80 96 112 128 144 160 176 192 208 224 240
34:---
35:Reading symbols from /boot/kernel/snd_maestro3.ko...done.
36:Loaded symbols for /boot/kernel/snd_maestro3.ko
37:Reading symbols from /boot/kernel/snd_pcm.ko...done.
38:Loaded symbols for /boot/kernel/snd_pcm.ko
39:#0 doadump () at /usr/src/sys/kern/kern_shutdown.c:240
40:240 dumping++;
41:(kgdb) list *0xc0713860
42:0xc0713860 is in lapic_ipi_wait (/usr/src/sys/i386/i386/local_apic.c:663).
43:658 incr = 0;
44:659 delay = 1;
45:660 } else
46:661 incr = 1;
47:662 for (x = 0; x < delay; x += incr) {
48:663 if ((lapic->icr_lo & APIC_DELSTAT_MASK) == APIC_DELSTAT_IDLE)
49:664 return (1);
50:665 ia32_pause();
51:666 }
52:667 return (0);
53:(kgdb) backtrace
54:#0 doadump () at /usr/src/sys/kern/kern_shutdown.c:240
55:#1 0xc055fd9b in boot (howto=260) at /usr/src/sys/kern/kern_shutdown.c:372
56:#2 0xc056019d in panic () at /usr/src/sys/kern/kern_shutdown.c:550
57:#3 0xc0567ef5 in mi_switch () at /usr/src/sys/kern/kern_synch.c:470
58:#4 0xc055fa87 in boot (howto=256) at /usr/src/sys/kern/kern_shutdown.c:312
59:#5 0xc056019d in panic () at /usr/src/sys/kern/kern_shutdown.c:550
60:#6 0xc0720c66 in trap_fatal (frame=0xdc1d0b30, eva=0)
61: at /usr/src/sys/i386/i386/trap.c:821
62:#7 0xc07202b3 in trap (frame=
63: {tf_fs = -1065484264, tf_es = -1065484272, tf_ds = -1065484272, tf_edi = 1, tf_esi = 0, tf_ebp = -602076292, tf_isp = -602076324, tf_ebx = 0, tf_edx = 0, tf_ecx = 1000000, tf_eax = 243, tf_trapno = 12, tf_err = 0, tf_eip = -1066321824, tf_cs = 8, tf_eflags = 65671, tf_esp = 243, tf_ss = 0})
64: at /usr/src/sys/i386/i386/trap.c:250
65:#8 0xc070c9f8 in calltrap () at {standard input}:94
66:#9 0xc07139f3 in lapic_ipi_vectored (vector=0, dest=0)
67: at /usr/src/sys/i386/i386/local_apic.c:733
68:#10 0xc0718b23 in ipi_selected (cpus=1, ipi=1)
69: at /usr/src/sys/i386/i386/mp_machdep.c:1115
70:#11 0xc057473e in kseq_notify (ke=0xcc05e360, cpu=0)
71: at /usr/src/sys/kern/sched_ule.c:520
72:#12 0xc0575cad in sched_add (td=0xcbcf5c80)
73: at /usr/src/sys/kern/sched_ule.c:1366
74:#13 0xc05666c6 in setrunqueue (td=0xcc05e360)
75: at /usr/src/sys/kern/kern_switch.c:422
76:#14 0xc05752f4 in sched_wakeup (td=0xcbcf5c80)
77: at /usr/src/sys/kern/sched_ule.c:999
78:#15 0xc056816c in setrunnable (td=0xcbcf5c80)
79: at /usr/src/sys/kern/kern_synch.c:570
80:#16 0xc0567d53 in wakeup (ident=0xcbcf5c80)
81: at /usr/src/sys/kern/kern_synch.c:411
82:#17 0xc05490a8 in exit1 (td=0xcbcf5b40, rv=0)
83: at /usr/src/sys/kern/kern_exit.c:509
84:#18 0xc0548011 in sys_exit () at /usr/src/sys/kern/kern_exit.c:102
85:#19 0xc0720fd0 in syscall (frame=
86: {tf_fs = 47, tf_es = 47, tf_ds = 47, tf_edi = 0, tf_esi = -1, tf_ebp = -1077940712, tf_isp = -602075788, tf_ebx = 672411944, tf_edx = 10, tf_ecx = 672411600, tf_eax = 1, tf_trapno = 12, tf_err = 2, tf_eip = 671899563, tf_cs = 31, tf_eflags = 642, tf_esp = -1077940740, tf_ss = 47})
87: at /usr/src/sys/i386/i386/trap.c:1010
88:#20 0xc070ca4d in Xint0x80_syscall () at {standard input}:136
89:---Can't read userspace from dump, or kernel process---
90:(kgdb) quit
如果您的系统经常崩溃并且磁盘空间不足,删除/var/crash中旧的vmcore文件可以节省大量的磁盘空间! |
10.3. 使用 DDB 进行在线内核调试
虽然kgdb
作为离线调试器提供了非常高级的用户界面,但它无法执行某些操作。其中最重要的是内核代码的断点和单步执行。
如果您需要对内核进行低级调试,则可以使用名为 DDB 的在线调试器。它允许设置断点、单步执行内核函数、检查和更改内核变量等。但是,它无法访问内核源文件,并且只能访问全局和静态符号,而不能访问像kgdb
那样的完整调试信息。
要配置内核以包含 DDB,请添加以下选项
options KDB
options DDB
到您的配置文件中,然后重新构建。(有关配置 FreeBSD 内核的详细信息,请参阅FreeBSD 手册)。
一旦您的 DDB 内核运行,有多种方法可以进入 DDB。第一种也是最早的方法是使用引导标志-d
。内核将在调试模式下启动并在任何设备探测之前进入 DDB。因此,您甚至可以调试设备探测/附加函数。要使用此功能,请退出加载程序的引导菜单,并在加载程序提示符下输入boot -d
。
第二种情况是在系统启动后进入调试器。有两种简单的方法可以实现这一点。如果您想从命令提示符处中断到调试器,只需键入以下命令
# sysctl debug.kdb.enter=1
或者,如果您在系统控制台上,可以使用键盘上的热键。默认的断点到调试器序列是Ctrl+Alt+ESC。对于 syscons,此序列可以重新映射,并且某些分发的映射会执行此操作,因此请确保您知道要使用的正确序列。对于串行控制台,有一个选项允许使用控制台线路上的串行线路 BREAK 进入 DDB(内核配置文件中的options BREAK_TO_DEBUGGER
)。它不是默认值,因为有很多串行适配器会随意生成 BREAK 条件,例如拔掉电缆时。
第三种方法是,如果内核配置为使用 DDB,任何恐慌条件都将分支到 DDB。出于这个原因,不建议为无人值守的机器配置使用 DDB 的内核。
要获得无人值守功能,请添加
options KDB_UNATTENDED
到内核配置文件中并重新构建/重新安装。
DDB 命令大致类似于一些gdb
命令。您可能首先需要做的是设置断点
break function-name address
默认情况下,数字采用十六进制表示,但为了使它们与符号名称区分开来;以字母a-f
开头的十六进制数字需要以0x
开头(对于其他数字,这是可选的)。允许使用简单的表达式,例如:function-name + 0x103
。
要退出调试器并继续执行,请键入
continue
要获取当前线程的堆栈跟踪,请使用
trace
要获取任意线程的堆栈跟踪,请将进程 ID 或线程 ID 作为第二个参数指定给trace
。
如果要删除断点,请使用
del
del address-expression
第一种形式将在断点命中后立即被接受,并删除当前断点。第二种形式可以删除任何断点,但您需要指定确切的地址;这可以通过以下方式获得
show b
或
show break
要单步执行内核,请尝试
s
这将进入函数,但您可以让 DDB 跟踪它们,直到到达匹配的 return 语句
n
这与 |
要检查内存中的数据,请使用(例如)
x/wx 0xf0133fe0,40
x/hd db_symtab_space
x/bc termbuf,10
x/s stringbuf
用于字/半字/字节访问,以及十六进制/十进制/字符/字符串显示。逗号后的数字是对象计数。要显示接下来的 0x10 个项目,只需使用
x ,10
同样,使用
x/ia foofunc,10
反汇编foofunc
的前 0x10 条指令,并将其与它们相对于foofunc
开头的偏移量一起显示。
要修改内存,请使用 write 命令
w/b termbuf 0xa 0xb 0
w/w 0xf0010030 0 0
命令修饰符(b
/h
/w
)指定要写入的数据的大小,第一个后续表达式是要写入的地址,其余部分被解释为要写入连续内存位置的数据。
如果您需要知道当前寄存器,请使用
show reg
或者,您可以通过例如以下方式显示单个寄存器值
p $eax
并通过以下方式修改它
set $eax new-value
如果您需要从 DDB 调用一些内核函数,只需说
call func(arg1, arg2, ...)
返回值将被打印。
要获取所有正在运行进程的ps(1)样式摘要,请使用
ps
现在您已经检查了内核失败的原因,并且希望重新引导。请记住,根据之前故障的严重程度,内核的并非所有部分都可能按预期工作。执行以下操作之一以关闭并重新引导系统
panic
这将导致您的内核转储核心并重新引导,以便您稍后可以使用kgdb(1)在更高层次上分析核心。
call boot(0)
这可能是干净关闭正在运行的系统、sync()
所有磁盘并在某些情况下重新引导的好方法。只要内核的磁盘和文件系统接口没有损坏,这可能是几乎干净关闭的好方法。
reset
这是摆脱灾难的最后方法,几乎与按下红色大按钮相同。
如果您需要简短的命令摘要,只需键入
help
强烈建议准备一份ddb(4)手册页的打印副本以备调试会话使用。请记住,在单步执行内核时很难阅读联机手册。
10.4. 使用远程 GDB 进行在线内核调试
FreeBSD 内核为在线调试提供了第二个 KDB 后端:gdb(4)。此功能自 FreeBSD 2.2 以来一直受支持,它实际上非常不错。
GDB 长期以来一直支持远程调试。这是通过串行线路上的非常简单的协议完成的。与上面描述的其他调试方法不同,您将需要两台机器来执行此操作。一台是提供调试环境的主机,包括所有源代码和带有所有符号的内核二进制文件的副本。另一台是目标机器,它运行完全相同的内核副本(可选地剥离调试信息)。
为了使用远程 GDB,请确保内核配置中存在以下选项
makeoptions DEBUG=-g options KDB options GDB
请注意,在 -STABLE 和 -RELEASE 分支上的GENERIC
内核中,GDB
选项默认情况下是关闭的,但在 -CURRENT 上是启用的。
构建完成后,将内核复制到目标机器并启动它。将目标机器的串行线路(其 uart 设备上设置了“flags 080”)连接到调试主机的任何串行线路。有关如何在 uart 设备上设置标志的信息,请参阅uart(4)。
必须使目标机器进入 GDB 后端,无论是由于恐慌还是通过有目的地进入调试器的陷阱。在执行此操作之前,选择 GDB 调试器后端
# sysctl debug.kdb.current=gdb
debug.kdb.current: ddb -> gdb
受支持的后端可以通过 |
然后,强制进入调试器
# sysctl debug.kdb.enter=1
debug.kdb.enter: 0KDB: enter: sysctl debug.kdb.enter
目标机器现在等待来自远程 GDB 客户端的连接。在调试机器上,转到目标内核的编译目录,并启动gdb
# cd /usr/obj/usr/src/amd64.amd64/sys/GENERIC/
# kgdb kernel
GNU gdb (GDB) 10.2 [GDB v10.2 for FreeBSD]
Copyright (C) 2021 Free Software Foundation, Inc.
...
Reading symbols from kernel...
Reading symbols from /usr/obj/usr/src/amd64.amd64/sys/GENERIC/kernel.debug...
(kgdb)
通过以下方式初始化远程调试会话(假设正在使用第一个串行端口)
(kgdb) target remote /dev/cuau0
您的主机 GDB 现在将获得对目标内核的控制权
Remote debugging using /dev/cuau0
kdb_enter (why=<optimized out>, msg=<optimized out>) at /usr/src/sys/kern/subr_kdb.c:506
506 kdb_why = KDB_WHY_UNSET;
(kgdb)
根据使用的编译器,某些局部变量可能会显示为 |
您可以将此会话用作任何其他 GDB 会话,包括完全访问源代码,在 Emacs 窗口中以 gud 模式运行它(这会在另一个 Emacs 窗口中为您提供自动源代码显示)等。
10.5. 调试控制台驱动程序
由于您需要一个控制台驱动程序才能在上面运行 DDB,因此如果控制台驱动程序本身出现故障,事情会变得更加复杂。您可能还记得使用串行控制台(使用修改后的引导块或在Boot:
提示符处指定-h
),并将标准终端连接到您的第一个串行端口。DDB 在任何已配置的控制台驱动程序上都能工作,包括串行控制台。
10.6. 调试死锁
您可能会遇到所谓的死锁,即系统停止执行有用工作的情况。在这种情况下,要提供有用的错误报告,请使用上一节中描述的ddb(4)。在报告中包含可疑进程的ps
和trace
的输出。
如果可能,请考虑进行进一步调查。如果您怀疑 VFS 层中发生了死锁,则以下方法特别有用。将这些选项添加到内核配置文件中。
makeoptions DEBUG=-g options INVARIANTS options INVARIANT_SUPPORT options WITNESS options WITNESS_SKIPSPIN options DEBUG_LOCKS options DEBUG_VFS_LOCKS options DIAGNOSTIC
当发生死锁时,除了输出ps
命令的结果外,还请提供show pcpu
、show allpcpu
、show locks
、show alllocks
、show lockedvnods
和alltrace
的信息。
要获取线程化进程的有意义的回溯信息,请使用thread thread-id
切换到线程栈,然后使用where
进行回溯。
10.7. 使用 Dcons 进行内核调试
dcons(4) 是一个非常简单的控制台驱动程序,它不直接连接到任何物理设备。它只是从内核或加载程序中的缓冲区读取和写入字符。由于其简单的特性,它对于内核调试非常有用,尤其是在使用 FireWire® 设备时。目前,FreeBSD 提供了两种方法,可以使用 dconschat(8) 从内核外部与缓冲区交互。
10.7.1. 通过 FireWire® 使用 Dcons
大多数 FireWire® (IEEE1394) 主机控制器都基于 OHCI 规范,该规范支持对主机内存进行物理访问。这意味着一旦主机控制器初始化,我们就可以在没有软件(内核)帮助的情况下访问主机内存。我们可以利用此功能与 dcons(4) 进行交互。 dcons(4) 提供类似于串行控制台的功能。它模拟两个串行端口,一个用于控制台和 DDB,另一个用于 GDB。由于远程内存访问完全由硬件处理,因此即使系统崩溃, dcons(4) 缓冲区也可访问。
FireWire® 设备不仅限于集成到主板上的设备。台式机存在 PCI 卡,笔记本电脑可以购买卡槽接口卡。
10.7.1.1. 在目标机器上启用 FireWire® 和 Dcons 支持
要在目标机器的内核中启用 FireWire® 和 Dcons 支持
确保您的内核支持
dcons
、dcons_crom
和firewire
。Dcons
应该静态链接到内核。对于dcons_crom
和firewire
,模块应该是可以的。确保启用了物理 DMA。您可能需要将
hw.firewire.phydma_enable=1
添加到/boot/loader.conf中。添加调试选项。
如果您通过 FireWire® 使用 GDB,请在/boot/loader.conf中添加
dcons_gdb=1
。在/etc/ttys中启用
dcons
。可选地,要强制
dcons
成为高级控制台,请将hw.firewire.dcons_crom.force_console=1
添加到loader.conf中。
在 i386 或 amd64 上的 loader(8) 中启用 FireWire® 和 Dcons 支持
在/etc/make.conf中添加LOADER_FIREWIRE_SUPPORT=YES
并重新构建loader(8)
# cd /sys/boot/i386 && make clean && make && make install
要启用 dcons(4) 作为活动低级控制台,请将boot_multicons="YES"
添加到/boot/loader.conf中。
以下是一些配置示例。一个示例内核配置文件将包含
device dcons
device dcons_crom
options KDB
options DDB
options GDB
options ALT_BREAK_TO_DEBUGGER
一个示例/boot/loader.conf将包含
dcons_crom_load="YES"
dcons_gdb=1
boot_multicons="YES"
hw.firewire.phydma_enable=1
hw.firewire.dcons_crom.force_console=1
10.7.1.2. 在主机上启用 FireWire® 和 Dcons 支持
要在主机的内核中启用 FireWire® 支持
# kldload firewire
找出 FireWire® 主机控制器的 EUI64(唯一的 64 位标识符),并使用 fwcontrol(8) 或dmesg
查找目标机器的 EUI64。
运行 dconschat(8),使用
# dconschat -e \# -br -G 12345 -t 00-11-22-33-44-55-66-77
一旦 dconschat(8) 运行,可以使用以下键组合
~+. | 断开连接 |
~ | ALT BREAK |
~ | 重置目标 |
~ | 挂起 dconschat |
通过使用远程调试会话启动 kgdb(1) 附加远程 GDB
kgdb -r :12345 kernel
10.7.1.3. 一些一般提示
以下是一些一般提示
要充分利用 FireWire® 的速度,请禁用其他较慢的控制台驱动程序
# conscontrol delete ttyd0 # serial console
# conscontrol delete consolectl # video/keyboard
存在用于 emacs(1) 的 GDB 模式;这是您需要添加到您的.emacs中的内容
(setq gud-gdba-command-name "kgdb -a -a -a -r :12345")
(setq gdb-many-windows t)
(xterm-mouse-mode 1)
M-x gdba
以及 DDD (devel/ddd)
# remote serial protocol
LANG=C ddd --debugger kgdb -r :12345 kernel
# live core debug
LANG=C ddd --debugger kgdb kernel /dev/fwmem0.2
10.8. 调试内核选项词汇表
本节简要介绍了用于调试的编译时内核选项词汇表
options KDB
:编译内核调试器框架。options DDB
和options GDB
所必需。性能开销很小或没有。默认情况下,调试器将在 panic 而不是自动重启时进入。options KDB_UNATTENDED
:将debug.debugger_on_panic
sysctl 的默认值更改为 0,该值控制是否在 panic 时进入调试器。当未将options KDB
编译到内核中时,行为是在 panic 时自动重启;当将其编译到内核中时,默认行为是进入调试器,除非编译了options KDB_UNATTENDED
。如果您想将内核调试器编译到内核中,但希望系统恢复除非您在场使用调试器进行诊断,请使用此选项。options KDB_TRACE
:将debug.trace_on_panic
sysctl 的默认值更改为 1,该值控制调试器是否在 panic 时自动打印堆栈跟踪。尤其是在使用options KDB_UNATTENDED
运行时,这有助于在串行或 firewire 控制台上收集基本的调试信息,同时仍然重新引导以恢复。options DDB
:编译对控制台调试器 DDB 的支持。此交互式调试器在系统的任何活动低级控制台上运行,包括视频控制台、串行控制台或 firewire 控制台。它提供了基本的集成调试功能,例如堆栈跟踪、进程和线程列表、锁状态、VM 状态、文件系统状态和内核内存管理的转储。DDB 不需要在第二台机器上运行软件或能够生成核心转储或完整的调试内核符号,并提供运行时内核的详细诊断。许多错误只需使用 DDB 输出即可完全诊断。此选项依赖于options KDB
。options GDB
:编译对远程调试器 GDB 的支持,该调试器可以通过串行电缆或 firewire 操作。当调试器进入时,可以附加 GDB 以检查结构内容、生成堆栈跟踪等。某些内核状态比 DDB 更难以访问,DDB 能够自动生成有用的内核状态摘要,例如自动遍历锁调试或内核内存管理结构,并且需要运行调试器的第二台机器。另一方面,GDB 结合了来自内核源代码和完整调试符号的信息,并且了解完整的数据结构定义、局部变量并且是可脚本化的。此选项不需要在内核核心转储上运行 GDB。此选项依赖于options KDB
。options BREAK_TO_DEBUGGER
、options ALT_BREAK_TO_DEBUGGER
:允许控制台上的中断信号或替代信号进入调试器。如果系统挂起且没有 panic,这是一种到达调试器的有用方法。由于当前的内核锁定,在串行控制台上生成的中断信号在进入调试器方面更加可靠,并且通常建议使用。此选项几乎没有或没有性能影响。options INVARIANTS
:将大量运行时断言检查和测试编译到内核中,这些检查和测试不断测试内核数据结构的完整性和内核算法的不变性。这些测试可能代价高昂,因此默认情况下不会编译,但有助于提供有用的“故障停止”行为,其中某些类别的不需要的行为在内核数据损坏发生之前进入调试器,从而使它们更容易调试。测试包括内存清理和使用后释放测试,这是开销的主要来源之一。此选项依赖于options INVARIANT_SUPPORT
。options INVARIANT_SUPPORT
:options INVARIANTS
中存在的许多测试都需要修改数据结构或定义其他内核符号。options WITNESS
:此选项启用运行时锁顺序跟踪和验证,是死锁诊断的宝贵工具。WITNESS 通过锁类型维护获取锁顺序的图形,并在每次获取时检查图形是否存在循环(隐式或显式)。如果检测到循环,则会向控制台生成警告和堆栈跟踪,指示可能发生了潜在的死锁。WITNESS 是使用show locks
、show witness
和show alllocks
DDB 命令所必需的。此调试选项会产生明显的性能开销,可以通过使用options WITNESS_SKIPSPIN
来稍微缓解。详细文档可以在 witness(4) 中找到。options WITNESS_SKIPSPIN
:使用 WITNESS 禁用自旋锁锁顺序的运行时检查。由于自旋锁最常在调度程序中获取,并且调度程序事件经常发生,因此此选项可以显着加快运行 WITNESS 的系统的速度。此选项依赖于options WITNESS
。options WITNESS_KDB
:将debug.witness.kdb
sysctl 的默认值更改为 1,这会导致 WITNESS 在检测到锁顺序违规时进入调试器,而不是简单地打印警告。此选项依赖于options WITNESS
。options SOCKBUF_DEBUG
:对套接字缓冲区执行广泛的运行时一致性检查,这对于调试套接字错误以及与套接字交互的协议和设备驱动程序中的竞争条件很有用。此选项会显着影响网络性能,并可能更改设备驱动程序竞争中的时序。options DEBUG_VFS_LOCKS
:跟踪 lockmgr/vnode 锁的锁获取点,扩展 DDB 中show lockedvnods
显示的信息量。此选项会对性能产生可衡量的影响。options DEBUG_MEMGUARD
:替换 malloc(9) 内核内存分配器,使用 VM 系统检测在释放后对已分配内存的读取或写入操作。详细信息可在 memguard(9) 中找到。此选项会对性能产生重大影响,但在调试内核内存损坏错误时非常有用。options DIAGNOSTIC
:启用更多额外的、更昂贵的诊断测试,类似于options INVARIANTS
。options KASAN
:启用内核地址清理器(Kernel Address Sanitizer)。这将启用编译器插桩,可用于检测内核中的无效内存访问,例如释放后使用和缓冲区溢出。这在很大程度上取代了options DEBUG_MEMGUARD
。有关详细信息以及当前支持的平台,请参阅 kasan(9)。options KMSAN
:启用内核内存清理器(Kernel Memory Sanitizer)。这将启用编译器插桩,可用于检测未初始化内存的使用。有关详细信息以及当前支持的平台,请参阅 kmsan(9)。
最后修改时间:2024年3月9日,由 Danilo G. Baio 修改