kgdbによるkernelデバッグ
久しぶりにkerenlデバッグをする機会があったのでまとめておきます。 環境は以下
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にアタッチできるようになりました。
接続方法は次の記事に回します。