Глава 10. Отладка ядра | FreeBSD Documentation Portal
Description: Отладка ядра FreeBSD
Keywords: Debugging,Dump,kgdb,DDB,GDB

# Глава 10. Отладка ядра

Этот перевод может быть устаревшим. Для того, чтобы помочь с переводом, пожалуйста, обратитесь к Сервер переводов FreeBSD.

## 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), чтобы сообщить ядру, куда сохранять аварийные дампы. Программа dumpon(8) должна быть вызвана после настройки раздела подкачки с помощью swapon(8). Обычно это обрабатывается установкой переменной `dumpdev` в rc.conf(5) в путь к устройству подкачки (рекомендуемый способ извлечения дампа ядра) или в значение `AUTO` для использования первого настроенного устройства подкачки. По умолчанию `dumpdev` имеет значение `AUTO` в HEAD и изменено на `NO` в ветках RELENG_* (за исключением RELENG_7, где оставлено значение `AUTO`). Начиная с FreeBSD 9.0-RELEASE и более поздних версий, bsdinstall будет спрашивать, следует ли включить аварийные дампы на целевой системе во время процесса установки.

| Проверьте /etc/fstab или swapinfo(8) для получения списка устройств подкачки.

| Убедитесь, что каталог `dumpdir`, указанный в rc.conf(5), существует перед аварией ядра!
    # mkdir /var/crash
    # chmod 700 /var/crash
Также помните, что содержимое /var/crash является конфиденциальным и, скорее всего, содержит секретную информацию, такую как пароли.

### 10.1.2. Извлечение дампа ядра

После записи дампа на устройство дампа, дамп должен быть извлечен до монтирования устройства подкачки. Для извлечения дампа с устройства дампа используйте программу savecore(8). Если в rc.conf(5) установлен параметр `dumpdev`, savecore(8) будет автоматически вызван при первой загрузке в многопользовательском режиме после сбоя и до монтирования устройства подкачки. Расположение извлеченного ядра указывается в параметре `dumpdir` файла rc.conf(5), по умолчанию это /var/crash, а имя файла будет vmcore.0.

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

Утилита crashinfo(8) создаёт текстовый файл, содержащий сводную информацию из полного дампа памяти или минидампа. Если параметр `dumpdev` установлен в rc.conf(5), crashinfo(8) будет автоматически вызван после savecore(8). Результат сохраняется в файл с именем core.txt.N в каталоге `dumpdir`.

| Если вы тестируете новое ядро, но вам нужно загрузить другое, чтобы снова запустить систему, загрузите его только в однопользовательском режиме, используя флаг `-s` при загрузке, а затем выполните следующие шаги:
    # fsck -p
    # mount -a -t ufs       # make sure /var/crash is writable
    # savecore /var/crash /dev/ad0s1b
    # exit                  # exit to multi-user
Это указывает savecore(8) извлечь дамп ядра из /dev/ad0s1b и поместить содержимое в /var/crash. Не забудьте убедиться, что целевой каталог /var/crash имеет достаточно места для дампа. Также не забудьте указать правильный путь к вашему swap-устройству, так как он, скорее всего, отличается от /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(1). Последняя версия включена в пакет devel/gdb. Более старая версия также присутствует в FreeBSD 11 и более ранних версиях.

Чтобы войти в отладчик и начать получение информации из дампа, запустите 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

| Если ваша система регулярно завершается аварийно и у вас заканчивается место на диске, удаление старых файлов vmcore в /var/crash может освободить значительное количество дискового пространства!

## 10.3. Онлайн-отладка ядра с использованием DDB

В то время как `kgdb` как автономный отладчик предоставляет очень высокий уровень пользовательского интерфейса, есть некоторые вещи, которые он не может выполнить. Наиболее важные из них — установка точек останова и пошаговое выполнение кода ядра.

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

Для настройки ядра с включенной поддержкой DDB добавьте параметры

    options KDB

    options DDB

в ваш конфигурационный файл, и пересоберите. (Подробности о настройке ядра FreeBSD см. в Руководстве FreeBSD).

После загрузки ядра DDB существует несколько способов войти в него. Первый и самый ранний способ — использовать флаг загрузки `-d`. Ядро запустится в режиме отладки и перейдет в DDB до начала обнаружения любого из устройств. Таким образом, можно отлаживать даже функции обнаружить (probe)/ присоединить (attach) устройств. Для использования этого метода выйдите из меню загрузки загрузчика и введите `boot -d` в командной строке загрузчика.

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

    # sysctl debug.kdb.enter=1

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

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

Для получения неинтерактивной функциональности добавьте:

    options	KDB_UNATTENDED

в файл конфигурации ядра и пересоберите/переустановите ядро.

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

     break function-name address

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

Для выхода из отладчика и продолжения выполнения введите:

     continue

Для получения трассировки стека текущего потока используйте:

     trace

Для получения трассировки стека произвольного потока укажите идентификатор процесса или идентификатор потока в качестве второго аргумента команды `trace`.

Если вы хотите удалить точку останова, используйте

     del
     del address-expression

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

     show b

или:

     show break

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

     s

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

     n

| Это отличается от оператора `next` в `gdb`; это похоже на `finish` в `gdb`. Нажатие `n` более одного раза приведёт к продолжению.

Для просмотра данных в памяти используйте (например):

     x/wx 0xf0133fe0,40
     x/hd db_symtab_space
     x/bc termbuf,10
     x/s stringbuf

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

     x ,10

Аналогично, используйте

     x/ia foofunc,10

для дизассемблирования первых 0x10 инструкций функции `foofunc` и их отображения вместе с их смещением от начала `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

Обратите внимание, что опция `GDB` отключена по умолчанию в ядрах `GENERIC` для веток -STABLE и -RELEASE, но включена в -CURRENT.

После сборки скопируйте ядро на целевую машину и загрузите его. Подключите последовательный порт целевой машины, у которого на устройстве uart установлены флаги "080", к любому последовательному порту отладочной машины. Подробности о настройке флагов на устройстве uart смотрите в uart(4).

Целевая машина должна быть переведена в режим отладчика GDB, либо из-за паники, либо путем преднамеренного перехода в отладчик. Перед этим выберите бэкенд отладчика GDB:

    # sysctl debug.kdb.current=gdb
    debug.kdb.current: ddb -> gdb

| Поддерживаемые бэкенды можно вывести с помощью sysctl `debug.kdb.available`. Если конфигурация ядра включает `options DDB`, то ddb(4) будет выбран по умолчанию. Если `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)

| В зависимости от используемого компилятора, некоторые локальные переменные могут отображаться как `<optimized out>`, что не позволяет их напрямую исследовать с помощью `gdb`. Если это вызывает проблемы при отладке, можно собрать ядро с пониженным уровнем оптимизации, что может улучшить видимость некоторых переменных. Это можно сделать, передав `COPTFLAGS=-O1` в make(1). Однако определённые классы ошибок в ядре могут проявляться иначе (или вообще не проявляться) при изменении уровня оптимизации.

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

## 10.5. Отладка драйвера консоли

Поскольку для работы DDB требуется драйвер консоли, ситуация усложняется, если сам драйвер консоли неисправен. Возможно, вы вспомните о возможности использования последовательной консоли (либо с модифицированными загрузочными блоками, либо указав `-h` в строке `Boot:`), подключив стандартный терминал к первому последовательному порту. 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. Dcons через FireWire®

Большинство контроллеров FireWire® (IEEE1394) основаны на спецификации OHCI, которая поддерживает физический доступ к памяти хоста. Это означает, что после инициализации контроллера хоста мы можем получить доступ к памяти хоста без помощи программного обеспечения (ядра). Мы можем использовать эту возможность для взаимодействия с dcons(4). dcons(4) предоставляет функциональность, аналогичную последовательной консоли. Он эмулирует два последовательных порта: один для консоли и DDB, другой для GDB. Поскольку удалённый доступ к памяти полностью обрабатывается аппаратным обеспечением, буфер dcons(4) остаётся доступным даже при крахе системы.

Устройства FireWire® не только встраиваются в материнские платы. Для настольных компьютеров существуют PCI-карты, а для ноутбуков можно приобрести интерфейс CardBus.

#### 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.

- Добавьте параметры для отладки.

- Добавьте `dcons_gdb=1` в /boot/loader.conf, если вы используете GDB через FireWire®.

- Включите `dcons` в /etc/ttys.

- Это необязательно: чтобы принудительно сделать `dcons` высокоуровневой консолью, добавьте `hw.firewire.dcons_crom.force_console=1` в loader.conf.

Чтобы включить поддержку FireWire® и Dcons в loader(8) на i386 или amd64:

Добавьте `LOADER_FIREWIRE_SUPPORT=YES` в /etc/make.conf и пересоберите 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

Определите EUI64 (уникальный 64-битный идентификатор) контроллера FireWire® и используйте fwcontrol(8) или `dmesg`, чтобы найти EUI64 целевой машины.

Запустите dconschat(8), с:

    # dconschat -e \# -br -G 12345 -t 00-11-22-33-44-55-66-77

Следующие комбинации клавиш могут быть использованы после запуска dconschat(8):

`~`+`.` | Отсоединиться
`~` | ALT BREAK
`~` | ПЕРЕЗАГРУЗИТЬ (RESET) целевую машину
`~` | Приостановить dconschat

Присоедините удаленный GDB, запустив kgdb(1) с сеансом удаленной отладки:

     kgdb -r :12345 kernel

#### 10.7.1.3. Некоторые общие рекомендации

Вот несколько общих советов:

Чтобы в полной мере использовать скорость FireWire®, отключите другие медленные драйверы консоли:

    # conscontrol delete ttyd0	     # serial console
    # conscontrol delete consolectl	# video/keyboard

Существует режим GDB для emacs(1); вот что нужно добавить в ваш .emacs:

    (setq gud-gdba-command-name "kgdb -a -a -a -r :12345")
    (setq gdb-many-windows t)
    (xterm-mouse-mode 1)
    M-x gdba

### 10.7.2. Dcons с KVM

Мы можем напрямую читать буфер dcons(4) через /dev/mem для работающих систем и в дампе памяти для систем после аварии. Это даёт аналогичный вывод команде `dmesg -a`, но буфер dcons(4) содержит больше информации.

#### 10.7.2.1. Использование Dcons с KVM

Для использования dcons(4) с KVM:

Дамп буфера dcons(4) работающей системы:

    # dconschat -1

Дамп буфера dcons(4) аварийного дампа:

    # dconschat -1 -M vmcore.XX

Отладка ядра в реальном времени может быть выполнена через:

    # fwcontrol -m target_eui64
    # kgdb kernel /dev/fwmem0.2

## 10.8. Глоссарий параметров ядра для отладки

В этом разделе представлен краткий глоссарий параметров ядра, указываемых при компиляции и относящихся к отладке:

- `options KDB`: включает фреймворк отладки ядра. Необходим для `options DDB` и `options GDB`. Практически не влияет на производительность. По умолчанию отладчик будет запущен при панике вместо автоматической перезагрузки.

- `options KDB_UNATTENDED`: изменяет значение по умолчанию системной настройки `debug.debugger_on_panic` на 0, что управляет входом в отладчик при панике. Если `options KDB` не вкомпилировано в ядро, поведение по умолчанию — автоматическая перезагрузка при панике; если оно вкомпилировано в ядро, поведение по умолчанию — переход в отладчик, если не вкомпилирована опция `options KDB_UNATTENDED`. Если вы хотите оставить отладчик ядра вкомпилированным в ядро, но желаете, чтобы система перезагружалась, пока вы не готовы использовать отладчик для диагностики, используйте эту опцию.

- `options KDB_TRACE`: изменяет значение по умолчанию системной настройки `debug.trace_on_panic` на 1, что управляет автоматическим выводом трассировки стека при панике. Особенно полезно при использовании с `options KDB_UNATTENDED`, так как позволяет собрать базовую отладочную информацию на последовательной консоли или консоли FireWire, продолжая перезагрузку для восстановления.

- `options DDB`: включает поддержку консольного отладчика DDB. Этот интерактивный отладчик работает на активной низкоуровневой консоли системы, включая видеоконсоль, последовательную консоль или консоль FireWire. Он предоставляет базовые встроенные средства отладки, такие как трассировка стека, список процессов и потоков, вывод состояния блокировок, состояния виртуальной памяти, состояния файловой системы и управления ядром памяти. DDB не требует работы программного обеспечения на второй машине или возможности создания дампа памяти или полных символов отладки ядра, а также предоставляет детальную диагностику ядра во время выполнения. Многие ошибки могут быть полностью диагностированы с использованием только вывода DDB. Эта опция зависит от `options KDB`.

- `options GDB`: включает поддержку удалённого отладчика GDB, который может работать через последовательный кабель или FireWire. При входе в отладчик можно подключить GDB для проверки содержимого структур, генерации трассировки стека и т.д. Некоторые состояния ядра сложнее исследовать, чем в DDB, который способен автоматически создавать полезные сводки состояния ядра, например, автоматически обходить структуры отладки блокировок или управления памятью ядра, но для этого требуется вторая машина с запущенным отладчиком. С другой стороны, GDB объединяет информацию из исходного кода ядра и полных отладочных символов, знает полные определения структур данных, локальные переменные и поддерживает написание скриптов. Эта опция не требуется для запуска GDB на дампе памяти ядра. Данная опция зависит от `options KDB`.

- `options BREAK_TO_DEBUGGER`, `options ALT_BREAK_TO_DEBUGGER`: позволяют сигналу прерывания или альтернативному сигналу на консоли войти в отладчик. Если система зависает без паники, это полезный способ попасть в отладчик. Из-за текущей блокировки ядра сигнал прерывания, сгенерированный на последовательной консоли, значительно надёжнее для входа в отладчик и обычно рекомендуется. Данная опция оказывает незначительное или нулевое влияние на производительность.

- `options INVARIANTS`: включает в ядро большое количество проверок и тестов во время выполнения, которые постоянно проверяют целостность структур данных ядра и инварианты алгоритмов ядра. Эти тесты могут быть затратными, поэтому по умолчанию не включены, но они помогают обеспечить полезное поведение "fail stop", при котором определённые классы нежелательного поведения попадают в отладчик до возникновения повреждения данных ядра, что упрощает их отладку. Тесты включают в себя очистку памяти и проверку использования после освобождения, что является одним из наиболее значимых источников накладных расходов. Эта опция зависит от `options INVARIANT_SUPPORT`.

- `options INVARIANT_SUPPORT`: многие тесты, присутствующие в `options INVARIANTS`, требуют модифицированных структур данных или определения дополнительных символов ядра.

- `options WITNESS`: эта опция включает отслеживание и проверку порядка блокировок во время выполнения, что является неоценимым инструментом для диагностики взаимоблокировок. WITNESS поддерживает граф полученных порядков блокировок по типам блокировок и проверяет граф на каждом получении на наличие циклов (явных или неявных). Если цикл обнаружен, на консоль выводится предупреждение и трассировка стека, указывающие на возможное возникновение взаимоблокировки. WITNESS необходим для использования команд DDB `show locks`, `show witness` и `show alllocks`. Эта отладочная опция создает значительную нагрузку на производительность, которую можно несколько уменьшить с помощью `options WITNESS_SKIPSPIN`. Подробная документация доступна в witness(4).

- `options WITNESS_SKIPSPIN`: отключает проверку порядка блокировки spinlock во время выполнения с WITNESS. Поскольку spin-блокировки чаще всего захватываются в планировщике, а события планировщика происходят часто, эта опция может значительно ускорить системы, работающие с WITNESS. Эта опция зависит от `options WITNESS`.

- `options WITNESS_KDB`: изменяет значение по умолчанию системной настройки `debug.witness.kdb` на 1, что приводит к входу в отладчик при обнаружении нарушения порядка блокировок вместо простого вывода предупреждения. Эта опция зависит от `options WITNESS`.

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

- `options DEBUG_VFS_LOCKS`: отслеживает точки получения блокировок для lockmgr/vnode, расширяя объём информации, отображаемой командой `show lockedvnods` в DDB. Данная опция оказывает заметное влияние на производительность.

- `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).

Изменено: 15 марта 2026 г. by Vladlen Popolitov

Назад
Главная
Далее