---
title: Глава 10. Отладка ядра
authors:
  - author: Paul Richards
  - author: Jörg Wunsch
  - author: Robert Watson
---

[[kerneldebug]]
= Отладка ядра
:doctype: book
:toc: macro
:toclevels: 1
:icons: font
:sectnums:
:sectnumlevels: 6
:sectnumoffset: 10
:partnums:
:source-highlighter: rouge
:experimental:
:images-path: books/developers-handbook/

ifdef::env-beastie[]
ifdef::backend-html5[]
:imagesdir: ../../../../images/{images-path}
endif::[]
ifndef::book[]
include::shared/authors.adoc[]
include::shared/mirrors.adoc[]
include::shared/releases.adoc[]
include::shared/attributes/attributes-{{% lang %}}.adoc[]
include::shared/{{% lang %}}/teams.adoc[]
include::shared/{{% lang %}}/mailing-lists.adoc[]
include::shared/{{% lang %}}/urls.adoc[]
toc::[]
endif::[]
ifdef::backend-pdf,backend-epub3[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]
endif::[]

ifndef::env-beastie[]
toc::[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]

[[kerneldebug-obtain]]
== Получение аварийного дампа ядра

При работе ядра, находящегося в разработке (например: FreeBSD-CURRENT), при критичных условиях (к примеру: очень высокая средняя нагрузка, десятки тысяч соединений, исключительно большое количество одновременно работающих пользователей, сотни процессов man:jail[8] и так далее) или при использовании новой возможности в драйвере устройства в FreeBSD-STABLE (пример: PAE) оно иногда будет завершать свою работу аварийно. В случае, если это произошло, в этой главе показано, как извлекать полезную информацию из произошедшего сбоя.

При аварийном завершении работы ядра перезагрузка системы неизбежна. После перезагрузки системы содержимое физической памяти системы (RAM) теряется, так же как и всё содержимое раздела подкачки перед сбоем. Для сохранения состояния физической памяти ядро использует устройство подкачки в качестве места временного хранения содержимого оперативной памяти после перезагрузки, следующей за аварийным завершением работы. С этой целью при загрузке FreeBSD после аварийного останова образ ядра может быть извлечён и применяться для отладки.

[NOTE]
====
Устройство подкачки, которое было отконфигурировано в качестве устройства для дампа продолжает выступать в роли устройства подкачки. В настоящее время выполнение дампов на устройства, не предназначенные для организации подкачки (например, ленты или CDRW), не поддерживается. Понятие "устройство подкачки" является синонимом "раздела подкачки."
====

Чтобы суметь извлечь образ, который можно использовать, требуется, чтобы по крайней мере один раздел подкачки был достаточно большим, чтобы разместить на нём весь объём физической памяти. Когда ядро аварийно завершает работу, перед перезагрузкой системы, ядро достаточно умно, чтобы проверить, было ли отконфигурировано устройство подкачки в качестве устройства для хранения дампов. Если оно является устройством, подходящим для сброса дампа, то ядро сбрасывает содержимое физической памяти на устройство подкачки.

[[config-dumpdev]]
=== Конфигурация устройства хранения дампов

До того, как ядро начнёт сбрасывать содержимое физической памяти на устройство хранения дампов, последнее должно быть отконфигурировано. Устройство хранения дампов задаётся при помощи команды man:dumpon[8], указывающей ядру, куда сохранять аварийные дампы ядра. Программа man:dumpon[8] должна быть вызвана после конфигурации раздела подкачки по команде man:swapon[8]. Обычно это осуществляется установкой переменной `dumpdev` в файле man:rc.conf[5] в значение, соответствующее пути к устройству подкачки (рекомендованный способ извлечения дампа ядра).

Либо устройство для сброса образа памяти может быть задано явно в параметре `dump` строки man:config[5] конфигурационного файла вашего ядра. Такой способ использовать не рекомендуется и он должен использоваться, только если ядро аварийно завершает свою работу до того, как можно было бы запустить man:dumpon[8].

[TIP]
====

Проверьте содержимое файла [.filename]#/etc/fstab# или выдачу man:swapinfo[8] на предмет наличия устройств подкачки.
====

[IMPORTANT]
====
Удостоверьтесь, что каталог `dumpdir`, указанный в man:rc.conf[5], существует до аварийного останова ядра!

[source,shell]
....
# mkdir /var/crash
# chmod 700 /var/crash
....

Запомните также, что содержимое [.filename]#/var/crash# является важной информацией, весьма вероятно, содержащей конфиденциальную информацию, в частности, пароли.
====

[[extract-dump]]
=== Извлечение дампа ядра

После того, как аварийный образ был записан на соответствующее устройство, его нужно извлечь до момента монтирования устройства подкачки. Для извлечения дампа из устройства его сохранения, воспользуйтесь утилитой man:savecore[8]. Если в файле man:rc.conf[5] было задано устройство `dumpdev`, то man:savecore[8] будет запущена автоматически при первой после аварийного останова загрузке в многопользовательском режиме и до монтирования устройства подкачки. Местоположение извлечённого образа памяти определяется значением переменной `dumpdir` из файла man:rc.conf[5], которое по умолчанию указывает на каталог [.filename]#/var/crash#, а файл будет называться [.filename]#vmcore.0#.

В случае, если в каталоге [.filename]#/var/crash# (или в том, на который указывает `dumpdir`) уже существует файл с именем [.filename]#vmcore.0#, то ядро будет увеличивать порядковый номер для каждого аварийного останова, чтобы избежать перезаписи существующих файлов [.filename]#vmcore# (к примеру, [.filename]#vmcore.1#). В процессе отладки скорее всего, в качестве нужного [.filename]#vmcore# вы будете использовать версию [.filename]#vmcore# с наибольшим номером в [.filename]#/var/crash#.

[TIP]
====

Если вы тестируете новое ядро, но вам нужно загрузить и работать с другим ядром, чтобы получить нормально функционирующую систему, то загрузите его в однопользовательском режиме при помощи флага `-s`, указываемого при загрузке, а затем выполните такие шаги:

[source,shell]
....
# fsck -p
# mount -a -t ufs
# доступность /var/crash для записи
# savecore /var/crash /dev/ad0s1b
# exit
....

Эти команды указывают man:savecore[8] извлечь дамп ядра из устройства [.filename]#/dev/ad0s1b# и поместить его содержимое в каталог [.filename]#/var/crash#. Не забудьте проверить, что в целевом каталоге [.filename]#/var/crash# достаточно места для хранения дампа. Кроме того, не забудьте проверить правильность маршрута к вашему устройству подкачки, так как он, скорее всего, отличается от [.filename]#/dev/ad0s1b#!
====

Рекомендуемым и определённо самым простым способом автоматизации формирования аварийных образов является указание переменной `dumpdev` в файле man:rc.conf[5].

[[kerneldebug-gdb]]
== Отладка аварийного образа памяти ядра при помощи `kgdb`

[NOTE]
====
В этом разделе описывается утилита man:kgdb[1], поставляемая с FreeBSD 5.3 и более поздними версиями. В предыдущих версиях для чтения файла аварийного дампа ядра необходимо использовать команду `gdb -k`.
====

После извлечения дампа памяти получение из него полезной информации для решения простых проблем является сравнительно лёгкой задачей. Перед тем, как погрузиться во внутренний интерфейс man:kgdb[1] для отладки аварийного образа памяти, найдите отладочную версию вашего ядра (обычно она имеет название [.filename]#kernel.debug#) и выясните маршрут к файлам исходных текстов, использованных для построения вашего ядра (обычно это [.filename]#/usr/obj/usr/src/sys/KERNCONF#), где в качестве _KERNCONF_ выступает значение `ident`, указанное конфигуратору ядра man:config[5]). Имея на руках эти два параметра, начнём отладку!

Чтобы войти в отладчик и начать получать информацию из дампа, как минимум необходимо сделать следующие шаги:

[source,shell]
....
# cd /usr/obj/usr/src/sys/KERNCONF
# kgdb kernel.debug /var/crash/vmcore.0
....

Вы можете отлаживать аварийный дамп, используя исходные тексты ядра точно также, как вы это делаете с любой другой программой.

Этот первый дамп взят из ядра 5.2-BETA, а сбой произошёл где-то глубоко внутри ядра. Нижеследующая выдача была модифицирована, в неё слева добавлены номера строк. При первой трассировке проверяется указатель команд и выдаётся обратная трассировка. Адрес, используемый в строке 41 для команды `list`, является указателем команд и он может быть найден в строке 17. Большинство разработчиков будут требовать предоставления им по крайней мере этой информации, если вы не можете отследить проблему самостоятельно. Если, однако, вы решите проблему, то обязательно добейтесь включения вашего патча в дерево исходных текстов, прислав его через сообщение об ошибке, списки рассылки или даже его непосредственным коммитом!

[source,shell]
....
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
....

Во второй трассировке используется более старый дамп из времён FreeBSD 2, но он более сложный и показывает больше возможностей `gdb`. Длинные строки были усечены ради повышения читабельности, а также пронумерованы для того, чтобы ссылаться на них. Кроме этих отличий, это реальная трассировка ошибки, выполненная в процессе разработки консольного драйвера pcvt.

[source,shell]
....
1:Script started on Fri Dec 30 23:15:22 1994
 2:#  cd /sys/compile/URIAH
 3:#  gdb -k 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) backtrace
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
....

Комментарии к вышеприведенному журналу:

строка 6:::
Это дамп, взятый при помощи DDB (смотри ниже), поэтому комментарий к аварийному останову имеет именно вид "because you said to!" и трассировка стека глубока; однако изначальной причиной перехода в DDB была аварийная остановка при возникновению ошибки страницы памяти.

строка 20:::
Это местонахождение функции `trap()` в трассировке стека.

строка 36:::
Принудительное использование новой границы стека; теперь это не нужно. Предполагается, что границы стека указывают на правильное расположение, даже в случае аварийного останова. Глядя на строку исходного кода 403, можно сказать, что весьма вероятно, что либо виноват доступ по указателю "tp", либо был выход за границы массива.

строка 52:::
Похоже, что виноват указатель, но он является допустимым адресом.

строка 56:::
Однако, очевидно, что он указывает на мусор, так что мы нашли нашу ошибку! (Для тех, кто не знаком с этой частью кода: `tp->t_line` служит для хранения режима канала консольного устройства, и это должно быть достаточно маленькое целое число.)

[TIP]
====

Если в вашей системе регулярно происходят аварийные остановы, и вам не хватает места на диске, удаление старых файлов [.filename]#vmcore# в каталоге [.filename]#/var/crash# может сэкономить вам значительный объём дискового пространства!
====

[[kerneldebug-ddd]]
== Отладка аварийного дампа с помощью DDD

Возможно также и исследование аварийного дампа ядра при помощи такого графического отладчика, как `ddd` (вам потребуется установить порт [.filename]#devel/ddd#, чтобы использовать отладчик `ddd`). Добавьте флаг `-k` к командной строке `ddd`, которую вы обычно используете для его вызова. Например;

[source,shell]
....
# ddd -k /var/crash/kernel.0 /var/crash/vmcore.0
....

После этого у вас должно получиться исследование аварийного дампа при помощи графического интерфейса `ddd`.

[[kerneldebug-post-mortem]]
== Посмертный анализ дампа

Что делать, если ядро аварийно завершает работу, хотя этого вы не хотели и поэтому командой `config -g` его не компилировали? Здесь не всё ещё потеряно. Не паникуйте!

Конечно, вам нужно включить создание аварийных дампов. Смотрите выше, что вы должны для этого сделать.

Перейдите в каталог конфигурации ядра ([.filename]#/usr/src/sys/arch/conf#) и отредактируйте ваш конфигурационный файл. Раскомментируйте (или добавьте, если она не существует) такую строку:

[.programlisting]
....
makeoptions    DEBUG=-g                #Build kernel with gdb(1) debug symbols
....

Перестройте ядро. Из-за изменения метки времени в Makefile будут перестроены и некоторые другие объектные файлы, например, [.filename]#trap.o#. К некоторому счастью, добавление опции `-g` не изменит все и вся в генерируемом коде, так что в конце концов вы получите новое ядро с тем же кодом, что и сбоящее ядро, но с отладочной информацией. По крайней мере, вы можете сравнить старый и новый размеры ядер командой man:size[1]. Если они не совпадают, то вам придется отказаться от вашей затеи.

Исследуйте дамп так, как это описано выше. Отладочной информации может не хватать в некоторых местах, как это можно видеть в трассировке стека примера выше, когда некоторые функции выводятся без номеров строк и списка аргументов. Если вам нужно больше отладочной информации, удалите соответствующие объектные файлы, снова перекомпилируйте ядро и повторите сеанс работы `gdb -k`, пока не получите достаточно подробную информацию.

Не гарантируется, что всё это будет работать, однако в большинстве случаев всё работает прекрасно.

[[kerneldebug-online-ddb]]
== Отладка ядра в режиме реального времени с помощью DDB

Хотя `gdb -k` является отладчиком не реального времени с высокоуровневым пользовательским интерфейсом, есть несколько вещей, которые он сделать не сможет. Самыми важными из них являются точки останова и пошаговое выполнение кода ядра.

Если вам нужно выполнять низкоуровневую отладку вашего ядра, то на этот случай имеется отладчик реального времени, который называется DDB. Он позволяет устанавливать точки останова, выполнять функции ядра по шагам, исследовать и изменять переменные ядра и прочее. Однако он не может использовать исходные тексты ядра и имеет доступ только к глобальным и статическим символам, а не ко всей отладочной информации, как в `gdb`.

Чтобы отконфигурировать ваше ядро для включения DDB, добавьте строчку с параметром 

[.programlisting]
....
options DDB
....

в ваш конфигурационный файл, и перестройте ядро. (Обратитесь к extref:{handbook}[Руководству по FreeBSD] для выяснения подробностей о конфигурации ядра FreeBSD).

[NOTE]
====
Если у вас устаревшая версия загрузочных блоков, то отладочная информация может оказаться не загруженной. Обновите блоки загрузки; самые новые загружают символы для DDB автоматически.
====

После того, как ядро с DDB запущено, есть несколько способов войти в DDB. Первый, и самый простой, способ заключается в наборе флага загрузки `-d` прямо в приглашении загрузчика. Ядро будет запущено в режиме отладки и войдет в DDB до выполнения процедуры распознавания каких бы то ни было устройств. Поэтому вы можете выполнить отладку даже функций распознавания/присоединения устройств.

Вторым способом является переход в режим отладчика сразу после загрузки системы. Есть два простых способа этого добиться. Если вы хотите перейти в отладчик из командной строки, просто наберите команду:

[source,shell]
....
# sysctl debug.enter_debugger=ddb
....

Либо, если вы работаете за системной консолью, можете воспользоваться определенной комбинацией клавиш. По умолчанию для перехода в отладчик используется комбинация kbd:[Ctrl+Alt+ESC]. Для драйвера syscons эта последовательность может быть изменена, и в некоторых распространяемых раскладках это сделано, так что обязательно выясните правильную комбинацию. Для последовательных консолей имеется параметр, позволяющий использовать последовательность BREAK на канале консоли для входа в DDB (`options BREAK_TO_DEBUGGER` в конфигурационном файле ядра). По умолчанию этого не делается, так как существует множество последовательных адаптеров, которые ошибочно генерируют последовательность BREAK, к примеру, при отключении кабеля.

Третий способ заключается во входе в DDB при возникновении любой аварийной ситуации, если ядро его использует. По этой причине не очень умно конфигурировать ядро с DDB для машины, которая работает без присмотра.

Команды DDB примерно повторяют некоторые команды `gdb`. Первым делом вам, наверное, нужно задать точку останова:

[source,shell]
....
 b function-name
 b address
....

Значения по умолчанию воспринимаются в шестнадцатеричном виде, но чтобы отличать их от имен символов; шестнадцатеричные числа, начинающиеся с букв `a-f`, должны предваряться символами `0x` (это опционально для других чисел). Разрешены простые выражения, например: `function-name + 0x103`.

Чтобы продолжить работу прерванного ядра, просто наберите:

[source,shell]
....
 c
....

Чтобы получить трассировку стека, задайте:

[source,shell]
....
 trace
....

[NOTE]
====
Заметьте, что при входе в DDB по специальной комбинации, ядро в данный момент обслуживает прерывание, так что трассировка стека может не дать вам много информации.
====

Если вы хотите убрать точку останова, введите

[source,shell]
....
 del
 del address-expression
....

В первом варианте команда будет исполнена сразу же по достижении точки останова, а текущая точка останова будет удалена. Во второй форме можно удалить любую точку останова, однако вам нужно будет указать ее точный адрес; его можно получить из:

[source,shell]
....
 show b
....

Чтобы выполнить один шаг ядра, попробуйте:

[source,shell]
....
 s
....

При этом будет осуществляться пошаговое выполнение функций, однако вы можете трассировать их с помощью DDB, пока не будет достигнуто соответствие возвращаемому значению:

[source,shell]
....
 n
....

[NOTE]
====
Это отличается от команды `next` отладчика `gdb`; это похоже на команду `gdb finish`.
====

Чтобы выводить значения в памяти, используйте, (к примеру): 

[source,shell]
....
 x/wx 0xf0133fe0,40
 x/hd db_symtab_space
 x/bc termbuf,10
 x/s stringbuf
....

для доступа к данным типа слово/полуслово/байт и вывода в шестнадцатеричном/десятичном/символьном виде. Число после запятой означает счетчик объектов. Чтобы вывести следующие 0x10 объектов, просто укажите:

[source,shell]
....
 x ,10
....

Подобным же образом используйте 

[source,shell]
....
 x/ia foofunc,10
....

для дизассемблирования и вывода первых 0x10 инструкций функции `foofunc` вместе с их адресом относительно начала `foofunc`.

Чтобы изменить значения в памяти, используйте команду write:

[source,shell]
....
 w/b termbuf 0xa 0xb 0
 w/w 0xf0010030 0 0
....

Модификатор команды (`b`/`h`/`w`) указывает на размер записываемых данных, первое следующее за ним выражение является адресом для записи, а оставшаяся часть интерпретируется как данные для записи в доступные области памяти.

Если вам нужно узнать текущее содержимое регистров, используйте:

[source,shell]
....
 show reg
....

Альтернативно вы можете вывести содержимое одного регистра по команде, скажем, 

[source,shell]
....
 p $eax
....

и изменить его по:

[source,shell]
....
 set $eax new-value
....

Если вам нужно вызвать некоторую функцию ядра из DDB, просто укажите:

[source,shell]
....
 call func(arg1, arg2, ...)
....

Будет выведено возвращаемое значение.

Для вывода суммарной статистики по всем работающим процессам в стиле команды man:ps[1] воспользуйтесь такой командой:

[source,shell]
....
 ps
....

Теперь вы узнали, почему ядро работает с ошибками и хотите выполнить перезагрузку. Запомните, что в зависимости от влияния предыдущих ошибок, не все части ядра могут работать так, как ожидается. Выполните одно из следующих действий для закрытия и перезагрузки вашей системы:

[source,shell]
....
 panic
....

Это приведет к созданию дампа ядра и перезагрузке, так что позже вы можете проанализировать дамп на более высоком уровне при помощи `gdb`. Как правило, эта команда должна следовать за другой командой `continue`.

[source,shell]
....
 call boot(0)
....

Это может оказаться хорошим способом для корректного закрытия работающей системы, `sync()` для всех дисков и напоследок перезагрузка. Пока интерфейсы диска и файловой системы в ядре не повреждены, это может быть самым правильным способом закрытия системы.

[source,shell]
....
 call cpu_reset()
....

Это последнее средство при аварии и практически то же самое, что нажатие Большой Красной Кнопки.

Если вам нужен краткий справочник по командам, просто наберите:

[source,shell]
....
 help
....

Однако настоятельно рекомендуем отпечатать копию страницы справочника по man:ddb[4] при подготовке к сеансу отладки. Помните, что трудно читать онлайновое руководство при пошаговом выполнении ядра.

[[kerneldebug-online-gdb]]
== Отладка ядра в режиме реального времени при помощи удалённого GDB

Эта возможность поддерживается во FreeBSD начиная с версии 2.2, и она на самом деле очень удобна.

В GDB уже давно имеется поддержка _удаленной отладки_. Это делается при помощи весьма простого протокола по последовательному каналу. В отличие от других методов, описанных выше, для этого вам требуется наличие двух машин. Одна из них является хостом, предоставляющим ресурсы для отладки, включая все исходные тексты и копию ядра со всеми символами в нем, а другая является целевой машиной, на которой запущена та же копия того же ядра (но без отладочной информации).

Вы должны настроить исследуемое ядро при помощи команды `config -g`, включить `DDB` в конфигурацию и откомпилировать его обычным образом. Это даст большой бинарный файл из-за отладочной информации. Скопируйте это ядро на целевую машину, усеките отладочную информацию командой `strip -x` и загрузите это ядро с использованием параметра загрузки `-d`. Подключите последовательный канал целевой машины, имеющий установленные флаги "flags 080" на соответствующем устройстве sio к любому последовательному каналу отладочного хоста. А теперь на отладочной машине перейдите в каталог компиляции целевого ядра и запустите `gdb`:

[source,shell]
....
% gdb -k kernel
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.16 (i386-unknown-freebsd),
Copyright 1996 Free Software Foundation, Inc...
(kgdb)
....

Проинициализируйте сеанс удаленной отладки (предполагается, что используется первый последовательный порт) такой командой:

[source,shell]
....
(kgdb) target remote /dev/cuaa0
....

Теперь на целевом хосте (тот, который перешел в DDB даже до начала процесса обнаружения устройств) наберите:

[source,shell]
....
Debugger("Boot flags requested debugger")
Stopped at Debugger+0x35: movb	$0, edata+0x51bc

db> gdb
....

DDB ответит следующим:

[source,shell]
....
Next trap will enter GDB remote protocol mode
....

Каждый раз, когда вы будете набирать `gdb`, режим будет меняться между удаленным GDB и локальным DDB. Чтобы немедленно вызвать следующее прерывание, просто наберите `s` (step). Ваш хостирующий GDB получит управление над целевым ядром:

[source,shell]
....
Remote debugging using /dev/cuaa0
Debugger (msg=0xf01b0383 "Boot flags requested debugger")
    at ../../i386/i386/db_interface.c:257
(kgdb)
....

Вы можете работать в этом сеансе точно также, как и в любом другом сеансе GDB, включая полный доступ к исходным текстам, запуск его в режиме gud-mode внутри окна Emacs (что даёт вам автоматический вывод исходного кода в другом окне Emacs) и тому подобное.

[[kerneldebug-kld]]
== Отладка загружаемых модулей с помощью GDB

При отладке аварийного останова системы, которое произошло в модуле, или при использовании GDB в режиме удаленного доступа к машине, использующей динамические модули, вам нужно указать GDB, как получить информацию о символах в этих модулях.

Первым делом вам нужно построить модуль (или модули) с включением отладочной информации:

[source,shell]
....
# cd /sys/modules/linux
# make clean; make COPTS=-g
....

Если вы используете GDB в режиме удаленного доступа, то для определения того, куда был загружен модуль, можете запустить команду `kldstat` на целевой машине:

[source,shell]
....
# 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    linux.ko
....

Если вы отлаживаете аварийный дамп, вам потребуется просмотреть список `linker_files` начиная с `linker_files->tqh_first` и следовать указателям `link.tqe_next` до тех пор, пока не найдете запись с тем `filename`, который вы ищете. Элемент `address` этой записи является адресом загрузки модуля.

Затем вам нужно определить смещение текстового сегмента модуля:

[source,shell]
....
# objdump --section-headers /sys/modules/linux/linux.ko | grep text
3 .rel.text     000016e0  000038e0  000038e0  000038e0  2**2
 10 .text         00007f34  000062d0  000062d0  000062d0  2**2
....

То, что вы ищете, является секцией `.text`, в примере выше это секция 10. Четвертое числовое поле (всего шестое по счёту) является смещением текстовой секции внутри файла. Добавьте это смещение к адресу загрузки, чтобы получить адрес, на который был перемещён код модуля. В нашем примере мы получим 0xc0adc000 + 0x62d0 = c0ae22d0. Воспользуйтесь командой `add-symbol-file` в GDB для указания отладчику на модуль:

[source,shell]
....
(kgdb) add-symbol-file /sys/modules/linux/linux.ko 0xc0ae22d0
add symbol table from file "/sys/modules/linux/linux.ko" at text_addr = 0xc0ae22d0?
(y or n)
(kgdb) y
Reading symbols from /sys/modules/linux/linux.ko...done.
(kgdb)
....

Теперь вы должны получить доступ ко всем символам в модуле.

[[kerneldebug-console]]
== Отладка драйвера консоли

Так как для работы DDB вам требуется драйвер консоли, то в случае неисправностей самого драйвера консоли все становится гораздо сложнее. Вы можете вспомнить об использовании последовательной консоли (либо с исправленными загрузочными блоками, либо при указании флага `-h` в приглашении `Boot:`) и подключить обычный терминал к первому последовательному порту. DDB работает с любым отконфигурированным драйвером консоли, в том числе с последовательной консолью.
