第673回の「カーネルのクラッシュ情報を取得する」では、カーネルクラッシュ時に情報を収集する仕組みを有効化しました。得られた情報は活用しないと意味がありません。今回はその中身を解析する方法を紹介します。
デバッグパッケージのインストール
第673回では、意図的にシステムをクラッシュさせることで、dmesgとvmcoreを取得しました。カーネルが今際の際に次につながる情報を残してくれたのです。「しかしながらあのクラッシュが最後のpanicだとは思えない。もし、同じカーネルが続けて使われるとしたら、あのpanicの同類が、また世界のどこかへ現れてくるかもしれない……」
最初に行うべきなのは、前回紹介したように問題発生時のdmesgを読むことです。これである程度、状況の絞り込みは行えますし、運が良ければ原因がわかることもあります。しかしながら、dmesgだけだと「問題が起きた場所」はわかっても、「なぜそこで問題が起きたのか」まではわからないことも多いです。そこで利用できるのがクラッシュダンプです。これは現象発生したあと、実際にpanicが発生したタイミングでのカーネルメモリーの内容が保存されています。つまり、実行中のタスクは何かや、他のタスクの状態、各種変数の内容などを一通り確認できるのです。
というわけで第673回で取得した、クラッシュダンプの内容を精査しましょう。第673回ではsysrq-triggerを用いて意図的にクラッシュさせて、/var/crash/以下にdmesgとクラッシュダンプ(vmcoreファイル)を取得していました。今回解析するのはこのクラッシュダンプのほうです。解析にはcrash
コマンドを利用します。crashはカーネルダンプの調査に特化した、gdbのラッパーツールです。
ただしカーネルをデバッガで調査するのであれば、vmlinuxやstripされていないカーネルモジュール群が必要です。実はこれらのファイルは非常にサイズが大きいため、通常のリポジトリには置かれていません。デバッグシンボル用のリポジトリを取り込む必要があります。まずは次のコマンドで、リポジトリを有効化してください。
ちなみに上記はUbuntu 18.04 LTS以降でのみ有効です。18.04より前にはubuntu-dbgsym-keyringパッケージがないため、リポジトリにある鍵を別途取り込むようにしてください。また今回の手順はUbuntuのリリースカーネルにのみ有効です。Mainlineビルドや、その他サードパーティのカーネルの場合は、別の手段でデバッグシンボル付きカーネルを入手してください[1]。
さて、今回は現在実行中のカーネルのデバッグパッケージをインストールしましょう。
おそらく1GiBほどのサイズをダウンロードすることになるので注意してください。またクラッシュダンプ取得時のカーネルバージョンが、実行中のカーネルではない「$(uname -r)
」の部分を適宜書き換えてください。クラッシュダンプのカーネルバージョンは、たとえばdmesgファイルの以下の部分で確認可能です。
ここの「5.11.0-22-generic
」が「$(uname -r)
」のそれに該当します。
ちなみにcrashコマンドとデバッグパッケージさえ用意できるなら、「クラッシュが発生したマシン」である必要はありません。運用中のサーバーはそのままクラッシュ情報収集したら、再度運用に戻して、別のマシンで解析することも可能です。
なお、別のアーキテクチャーの解析は、Ubuntuのcrashコマンドではできません。たとえば、Raspberry Piで発生したクラッシュダンプは、x86マシンでは解析できないのです。ただしこれはUbuntuのcrashコマンドのビルド方法に起因するものであり、crashコマンドを自前でクロスビルドするなら、別アーキテクチャーのダンプを解析することは可能です。
クラッシュダンプの解析
crashコマンドを使ってクラッシュダンプを解析するには「デバッグ用のカーネル本体」と「対象のvmcoreファイル」が必要になります。このうちデバッグパッケージによってインストールされるデバッグ用のカーネル一式は「/usr/lib/debug
」以下にダウンロードされます。そこで、次のようなコマンドでcrashマンドを起動しましょう。
第一引数がvmlinuxファイルで、第二引数がクラッシュダンプイメージです。システムによってはロードに時間がかかるかもしれません。必要なファイルが揃っているなら、上記のように情報が表示されるはずです。上記の内容自体は、dmesgファイルにもある情報ですね。teeコマンドによるタスクがCPU 3で動いているときに、sysrq-triggerによってpanicしたことがわかります。
crashをうまく起動できたら、あとは普通のデバッガとして使うだけです。たとえばバックトレースなら次のように表示できます。
このあたりはdmesgで表示されているものと同じですね。タスクのリストはpsコマンドで確認できます。
「>
」が付いているのが実行中のタスクです。上記は2コア4スレッドのCPUなので、4個表示されています。たとえばtmuxサーバーが動いていたPID=3797の情報は次のように表示できます。
psやtaskのように行数が多い場合はページャー経由での表示になります。もしページャーを無効化したい場合は「set scroll off
」を実行してください。
グローバル変数などはpコマンドで内容を確認できます。またアドレスがわかっていれば、シンボル名指定以外の方法も可能です。
タスクごとのファイルディスクリプター一覧は次のように確認できます。
crashでは他にもさまざまなコマンドで、情報を確認できます。詳細はhelpコマンドやhelpページを参照してください。
ソースコードも参照できるようにする
GDBと同じように、crashコマンドでも解析箇所のソースコードの情報を表示できます。そのためにはまずカーネルのソースコードを入手する必要があります。
Ubuntuでカーネルのソースコードを入手するには主に3種類の方法が存在します。
- Kernel Teamのgitリポジトリからリリースごとの最新のコードをcloneする
- 「
sudo apt install linux-source
」を実行する
- 「
apt source linux-image-unsigned-$(uname -r)
」を実行する
開発版も含めて最新のソースコードの変更履歴が必要な場合は1を使います。リポジトリ名は「ubuntu/ubuntu-focal.git」のような名前になっているので、それを検索すると良いでしょう。ただしこれは大抵の場合、実行中のカーネルのソースコードとは必ずしも一致しないので注意が必要です。
リポジトリからインストールできるカーネルパッケージの最新版で問題が起きているのであれば、2の方法でソースコードを取得できます。この方法の利点のひとつは、linux-sourceパッケージは常に最新のカーネルバージョンに向いているため、新しいバージョンがリリースされたら自動的に新しいソースコードも入手できる点です。逆に言うと、古いカーネルバージョンで問題が起きている場合、この方法では該当するソースコードを取得できません。
特定のカーネルパッケージのバージョンのソースコードを取得したいなら、3の方法が確実です。ただしこれを実行するためにはソースパッケージリポジトリを有効化しておく必要があり、さらにソースコードパッケージを展開できなくてはなりません。今回はこの方法を紹介します。
まずはソースパッケージリポジトリの有効化方法です。デスクトップ版なら「ソフトウェアとアップデート」を起動して、「Ubuntuのソフトウェア」タブの「ソースコード」にチェックを入れるだけです。サーバー版なら、次のように「/etc/apt/sources.list
」のdeb-srcの行を有効化しましょう。
最後のdpkg-devパッケージは、ソースパッケージを展開するために必要なパッケージです。次に適当なディレクトリを作成し、そこにソースパッケージをダウンロードします。
今回は現在実行中のバージョンを指定するために「$(uname -r)
」を使いましたが、特定のカーネルパッケージにしたい場合はバージョンごとの文字列列に変更してください。
crashは本来「--src
」オプションでソースコードのディレクトリを指定できることになっています。これはバックエンドのgdbに「directory
」コマンドでソースコードディレクトリを指定するためのオプションです。しかしながら、Ubuntuのカーネルはソースツリーをフルパスで書いています。よって、「--src
」で指定するディレクトリ以下がフルパス相当になっていなくてはなりません。フルパスを取得する手っ取り早い方法は、crashでdisコマンドを打つことです。
コードが「/build/linux-HvkI5B/linux-5.11.0
」以下であると記録されていることがわかります[2]。
次のコマンドで上記のパスに合わせて、ツリーを移動してしまいます。
これでソースコードの準備は整いました。カーネルのソースコードディレクトリはcrashコマンド起動時に「--src
」オプションを指定することで反映されます。
disコマンドに「-s
」オプションを付けると、今度こそ指定したシンボルに該当するソースコードを表示してれます。
disコマンドの引数は、「-s
」オプションを付けるとgdbの「list」の引数として解釈されます。よってアドレス指定も可能です。
クラッシュダンプの保存先をネットワーク上に変更する
前回はクラッシュダンプをローカルのファイルシステムに保存していました。しかしながらリモートで解析する予定がある、もしくは、ローカルには十分なサイズのストレージが存在しないのなら、クラッシュダンプを直接リモートに保存したいところです。Ubuntuのクラッシュダンプの設定は「/etc/default/kdump-tools
」にまとまっています。ここを変更すれば、SSH接続先のマシンやNFS上にクラッシュダンプを保存することが可能です。
設定方法自体は上記ファイルにコメントが記載されているため、そこまで迷うことはないでしょう。ここでは例として、SSH接続で保存する方法を紹介します。まず「/etc/default/kdump-tools
」の末尾のほうを次のように変更します。
最低限必要な設定は、「SSH
」に「ユーザー名@アドレス」を記載するだけです。上記はIPv4アドレスですが、もちろんホスト名等も可能です。「SSH_KEY
」にはSSH接続する際の秘密鍵を指定します。何も指定しない場合は、「/root/.ssh/kdump_id_rsa
」を自動生成してくれます。
設定ファイルの準備ができたら、一度SSHサーバー先にアクセスして公開鍵を登録します。これは「kdump-config propagate
」コマンドで実現できます。
これだけで準備完了です。あとはシステムをクラッシュさせてみましょう。クラッシュしたマシンの「/var/crash
」ではなく、接続先として設定したSSHサーバー上の「/var/crash/
」以下に「(クラッシュしたマシンのIPアドレス)-(クラッシュ日時)」なディレクトリが生成され、そこにクラッシュダンプが保存されているはずです。
前述のソースパッケージのダウンロード先に保存するようにしておけば、ターゲットマシンのストレージを消費することなく、詳細なデバッグが可能になります。
Ubuntu WikiのCrashdumpRecipeにはクラッシュダンプの解析に関する、いくつかのノウハウが掲載されています。カーネル関連の問題について、ケースごとの参考文献のリストもあるのでそちらも参考になるでしょう。単に不具合の出所を探りたいなら「カーネルのバージョンを切り替えつつ問題を絞り込む方法」もあります。
カーネルのクラッシュは致命的な問題につながることが多々あります。カーネルクラッシュに遭遇したら、将来の自分のためにも、恐れず原因を追求するよう心がけましょう。