Chapter 16. Отладка ядра

Table of Contents
16.1. Отладка аварийных образов ядра при помощи gdb
16.2. Отладка аварийного дампа с помощью DDD
16.3. Посмертный анализ дампа
16.4. Отладка ядра в режиме реального времени с помощью DDB
16.5. Отладка ядра в режиме реального времени при помощи удаленного GDB
16.6. Отладка загружаемых модулей с помощью GDB
16.7. Отладка драйвера консоли

Текст предоставили Paul Richards и Jörg Wunsch

16.1. Отладка аварийных образов ядра при помощи gdb

Вот некоторые указания по работе с отладкой ядра с аварийными образами памяти. При этом предполагается, что у вас достаточно места на разделе подкачки для аварийного образа памяти. Если у вас есть несколько разделов подкачки и первый слишком мал для размещения образа памяти, вы можете настроить ядро на использование другого устройства для сброса образа памяти (в строке config kernel или указать его при помощи команды dumpon(8). Лучше всего использовать dumpon(8), задав переменную dumpdev в файле /etc/rc.conf. Как правило, вам нужно будет задать одно из устройств подкачки, перечисленных в файле /etc/fstab. Сброс образов памяти на устройства, не являющиеся устройствами подкачки, напрмер, ленты, в данный момент не поддерживаются. Настройте ваше ядро при помощи команды config -g. Обратитесь к Руководству по FreeBSD за более подробной информацией о настройке ядра FreeBSD.

Используйте команду dumpon(8) для указания ядру места, куда нужно помещать образ памяти (отметьте, что это будут сделано после настройки соответствующего раздела как раздел подкачки через swapon(8)). Обычно это делается через /etc/rc.conf и /etc/rc. Либо вы можете задать устройство для сброса образа памяти явно через параметр dump в строке config конфигурационного файла вашего ядра. Такой способ использовать не рекомендуется и он должен использоваться, только если вы хотите получать аварийные образы памяти ядра, которое аварийно завершает свою работу при загрузке.

Note: Далее термин gdb означает отладчик gdb, запущенный в "режиме отладки ядра". Переход в этот режим достигается запуском gdb с параметром -k. В режиме отладки ядра gdb изменяет своё приглашение на (kgdb).

Tip: Если вы используете FreeBSD версии 3 или более раннюю, вы должны выполнить усечение отладочного ядра командой strip, а не устанавливать большое отладочное ядро:

    # cp kernel kernel.debug
    # strip -g kernel

Этот шаг не так уж и необходим, но рекомендуем. (Во FreeBSD 4 и более поздних релизах этот шаг выполняется автоматически в конце процесса построения ядра make.) Когда ядро усечено, автоматически или при помощи команд выше, вы можете установить его обычным образом, набрав make install.

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

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

    # 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
       

Эта последовательность указывает программе savecore(8) на использование другого ядра для извлечения символических имен. Иначе она будет использовать ядро, работающее в данный момент и, скорее всего, ничего не сделает, потому что аварийный образ памяти и символы ядра будут отличаться.

А теперь, после сброса аварийного дампа, перейдите в каталог /sys/compile/WHATEVER и запустите команду gdb -k. Из программы gdb сделайте вот что:

    symbol-file kernel.debug
    exec-file /var/crash/kernel.0
    core-file /var/crash/vmcore.0
и вуаля - вы можете отлаживать аварийный дамп, используя исходные тексты ядра точно также, как вы это делаете с любой другой программой.

Вот журнал команд сеанса работы gdb, иллюстрирующий эту процедуру. Длинные строки были разорваны для улучшения читабельности и для удобства строки были пронумерованы. Все остальное является трассировкой ошибки, реально возникнувшей во время работы над драйвером консоли pcvt.

     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) 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:# exit
    78:exit
    79:
    80:Script done on Fri Dec 30 23:18:04 1994

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

строка 6:

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

строка 20:

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

строка 36:

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

строка 52:

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

строка 56:

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