kgdbによるkernelデバッグ

久しぶりにkerenlデバッグをする機会があったのでまとめておきます。 環境は以下

  • 物理PC(x86)
  • kernel 4.16.7(OSはRedhat系)
  • 接続はシリアル接続(デバッグ対象を端末A, 接続端末をB)

kernelをデバッグするときは、kernelを一旦止める必要があるため、基本的には外部からアタッチする形になります。
使うツールはgdbです。
qemuなどの仮想環境上で動いている場合は、qemu上のgdbserverが使えるのか、その限りではないです(?)
仮想環境上で試せる場合は先にそちらで試したほうがいいですね。 やり方はkernel Documentに書いてあります。(Documentation/dev-tools/gdb-kernel-debugging.rst)

kernel config

まずは、デバッグ対象のkerenlのconfigで、kgdbによるデバッグができるよう設定を行います。 Documentation/dev-tools/kgdb.rstやネットで書いてある通りです。

# make menuconfig
(kgdbの有効化 & シリアルコンソールからのデバッグ有効化)
Kernel hacking  --->
  [*] Kernel debugging
  [*] KGDB: kernel debugger  --->
    <*>   KGDB: use kgdb over the serial console
(kernelのデバッグシンボルを残す)
  Compile-time checks and compiler options  --->
    [*] Compile the kernel with debug info
    [ ]   Reduce debugging information
    [*]   Provide GDB scripts for kernel debugging   <= 必須ではないが、仮想環境上でのデバッグやkernelのビルド環境上でデバッグする場合は

その他のconfig

上記以外に以下のconfigを設定しますが、正直やり方があっているかわかりません。今の所問題はないようです。

CONFIG_FRAME_POINTER

ファイルにフレーム情報を保存するコードを埋め込むなどするので、スタックトレースなどが正確になる(らしい)ので、これを有効化します。
いろんなところで、

-> Kernel hacking
  -> Compile-time checks and compiler options

の中に設定項目があると書いてありますが見えません。menuconfigで"/"を押し、探してみると、UNWINDER_FRAME_POINTERに依存されているので、

-> Kernel hacking
  (X) Frame pointer unwinder

とします。これにより

CONFIG_FRAME_POINTER=y
CONFIG_UNWINDER_FRAME_POINTER=y

となります。

CONFIG_STRICT_KERNEL_RWX

これはkernelのメモリ空間をroとし、セキュリティを高める設定ですが、これが有効だと、breakpointの設定ができなくなります。 breakpoint自体は貼れるけど、動かすと多分こんな感じになる。

(gdb) c
Cannot inser breakpont 1.
Error accessing memory address 0xffff...: 不明なエラーです-1.

ただ、x86の場合、強制的に有効化されています。私は無理やり有効化しています。

# vim arch/x86/Kconfig
- select ARCH_HAS_STRICT_KERNEL_RWX
- select ARCH_HAS_STRICT_MODULE_RWX
+ #select ARCH_HAS_STRICT_KERNEL_RWX
+ #select ARCH_HAS_STRICT_MODULE_RWX

# make prepare
# make menuconfig

こうすると、.configから設定が消えると思います。

nokaslr

configとは異なりますが、この設定も無効化する必要があります。KASLRとは仮想メモリアドレスをランダム化し、セキュリティを高める仕組みですが、これが有効だとデバッグシンボルを入れてもgdbが関数などのアドレスを見つけられません。
そのためこれを無効化するんですが、これはkernelのブートオプションで無効化できます。(configでも無効化できると思いますがやったことはありません)
kernelのブートオプションにnokaslrを入れるだけです。設定方法は、kgdbの設定と一緒に紹介します。

Build

私はRPMでのパッケージ管理を崩したくない(デバッグ,開発時もリリースと手順を変えないことで、影響を減らしたい)のと、対象PCが貧弱なので、ビルドマシンでRPM化しています。

# make rpm-pkg

このとき、Makefile

VERSION = 4
PATCHLEVEL = 16
SUBLEVEL = 7
EXTRAVERSION =

RPMの名前が決まるので、EXTRAVERSIONにデバッグ番号などをつけています。 出来たRPMは端末Aにインストールします。develパッケージはこの用途では入れる必要はないと思いますが、別途systemtapなど端末A自体でデバッグを行う場合は必要になると思います。
別途端末Bでもvmlinuxが必要なため、RPMを展開します。vmlinuxもbzip2で圧縮されているので展開しておきます。

端末B
# rpm2cpio kernel.rpm | cpio -id
# bunzip2 boot/vmlinuz-xxx

kgdb設定

端末Aでkgdbの接続設定を行います。その前にちゃんとシリアルコンソールで接続できるか確認しておいたほうがいいでしょう。


シリアルコンソール接続方法

端末Aでシリアル接続を有効かします。systemdを利用している場合です。

# systemctl start serial-getty@ttyS0.service
# vi /etc/securetty
+ ttyS0

これで、端末Bからminicomなどで接続すると、端末Aにログインできるはずです。私はこのやり方が簡単なので使っていますが、systemdが起動しないとログインできないので、OS起動時から入りたい場合はgrubに設定することになります。


接続できることがわかったら、kgdbの設定を行います。kernelのブートオプションに設定します。基本的には2つです。

kgdboc=[kms][[,]kbd][[,]serial_device][,baud]
kgdbwait

kgdbocは接続情報、kgdbwaitはOSブート時に、kgdb接続を待ち受けるかです。kgdbwaitが有効だとkernelはできるだけ早くkgdbの待受に入ります。boot時のデバッグや、breakpointを貼ってから起動する場合などに使います。
以下は設定例です。KASLRの無効化設定も入れています。

# vi /etc/default/grub
GRUB_CMDLINE_LINUX="rhgb nokaslr kgdboc=ttyS0,115200 kgdbwait"
# grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

これでOSを再起動したら、remoteのgdbでkernelにアタッチできるようになりました。
接続方法は次の記事に回します。