Ubuntu Weekly Recipe

第674回 カーネルのクラッシュ情報を解析する

この記事を読むのに必要な時間:およそ 9.5 分

ソースコードも参照できるようにする

GDBと同じように,crashコマンドでも解析箇所のソースコードの情報を表示できます。そのためにはまずカーネルのソースコードを入手する必要があります。

crash> dis write_sysrq_trigger
0xffffffffa21615d0 <write_sysrq_trigger>:       nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa21615d5 <write_sysrq_trigger+5>:     push   %rbp
0xffffffffa21615d6 <write_sysrq_trigger+6>:     mov    %rsp,%rbp
0xffffffffa21615d9 <write_sysrq_trigger+9>:     push   %rbx
0xffffffffa21615da <write_sysrq_trigger+10>:    mov    %rdx,%rbx
0xffffffffa21615dd <write_sysrq_trigger+13>:    test   %rdx,%rdx
0xffffffffa21615e0 <write_sysrq_trigger+16>:    je     0xffffffffa21615f8 <write_sysrq_trigger+40>
0xffffffffa21615e2 <write_sysrq_trigger+18>:    mov    %rsi,%rax
0xffffffffa21615e5 <write_sysrq_trigger+21>:    callq  0xffffffffa20238f0 <__get_user_1>
0xffffffffa21615ea <write_sysrq_trigger+26>:    test   %eax,%eax
0xffffffffa21615ec <write_sysrq_trigger+28>:    jne    0xffffffffa2161601 <write_sysrq_trigger+49>
0xffffffffa21615ee <write_sysrq_trigger+30>:    movsbl %dl,%edi
0xffffffffa21615f1 <write_sysrq_trigger+33>:    xor    %esi,%esi
0xffffffffa21615f3 <write_sysrq_trigger+35>:    callq  0xffffffffa2161110 <__handle_sysrq>
0xffffffffa21615f8 <write_sysrq_trigger+40>:    mov    %rbx,%rax
0xffffffffa21615fb <write_sysrq_trigger+43>:    mov    -0x8(%rbp),%rbx
0xffffffffa21615ff <write_sysrq_trigger+47>:    leaveq
0xffffffffa2161600 <write_sysrq_trigger+48>:    retq
0xffffffffa2161601 <write_sysrq_trigger+49>:    mov    $0xfffffffffffffff2,%rax
0xffffffffa2161608 <write_sysrq_trigger+56>:    jmp    0xffffffffa21615fb <write_sysrq_trigger+43>
0xffffffffa216160a <write_sysrq_trigger+58>:    int3
0xffffffffa216160b <write_sysrq_trigger+59>:    int3
0xffffffffa216160c <write_sysrq_trigger+60>:    int3
0xffffffffa216160d <write_sysrq_trigger+61>:    int3
0xffffffffa216160e <write_sysrq_trigger+62>:    int3
0xffffffffa216160f <write_sysrq_trigger+63>:    int3

Ubuntuでカーネルのソースコードを入手するには主に3種類の方法が存在します。

  1. Kernel Teamのgitリポジトリからリリースごとの最新のコードをcloneする
  2. sudo apt install linux-sourceを実行する
  3. apt source linux-image-unsigned-$(uname -r)を実行する

開発版も含めて最新のソースコードの変更履歴が必要な場合は1を使います。リポジトリ名は「ubuntu/ubuntu-focal.git」のような名前になっているので,それを検索すると良いでしょう。ただしこれは大抵の場合,実行中のカーネルのソースコードとは必ずしも一致しないので注意が必要です。

リポジトリからインストールできるカーネルパッケージの最新版で問題が起きているのであれば,2の方法でソースコードを取得できます。この方法の利点のひとつは,linux-sourceパッケージは常に最新のカーネルバージョンに向いているため,新しいバージョンがリリースされたら自動的に新しいソースコードも入手できる点です。逆に言うと,古いカーネルバージョンで問題が起きている場合,この方法では該当するソースコードを取得できません。

特定のカーネルパッケージのバージョンのソースコードを取得したいなら,3の方法が確実です。ただしこれを実行するためにはソースパッケージリポジトリを有効化しておく必要があり,さらにソースコードパッケージを展開できなくてはなりません。今回はこの方法を紹介します。

まずはソースパッケージリポジトリの有効化方法です。デスクトップ版なら「ソフトウェアとアップデート」を起動して,⁠Ubuntuのソフトウェア」タブの「ソースコード」にチェックを入れるだけです。サーバー版なら,次のように/etc/apt/sources.listのdeb-srcの行を有効化しましょう。

$ sudo sed -i 's/^# \(deb-src.*ubuntu.com.*\)$/\1/'  /etc/apt/sources.list
$ sudo apt update
$ sudo apt dpkg-dev

最後のdpkg-devパッケージは,ソースパッケージを展開するために必要なパッケージです。次に適当なディレクトリを作成し,そこにソースパッケージをダウンロードします。

$ mkdir -p ~/Packages/linux && cd $_
$ apt source linux-image-unsigned-$(uname -r)
$ ls
linux-5.11.0  linux_5.11.0-22.23.diff.gz  linux_5.11.0-22.23.dsc  linux_5.11.0.orig.tar.gz

今回は現在実行中のバージョンを指定するために$(uname -r)を使いましたが,特定のカーネルパッケージにしたい場合はバージョンごとの文字列列に変更してください。

crashは本来--srcオプションでソースコードのディレクトリを指定できることになっています。これはバックエンドのgdbにdirectoryコマンドでソースコードディレクトリを指定するためのオプションです。しかしながら,Ubuntuのカーネルはソースツリーをフルパスで書いています。よって,--srcで指定するディレクトリ以下がフルパス相当になっていなくてはなりません。フルパスを取得する手っ取り早い方法は,crashでdisコマンドを打つことです。

crash> dis -s write_sysrq_trigger
FILE: /build/linux-HvkI5B/linux-5.11.0/drivers/tty/sysrq.c
LINE: 1151

dis: write_sysrq_trigger: source code is not available

コードが/build/linux-HvkI5B/linux-5.11.0以下であると記録されていることがわかります※2⁠。

※2
crashの起動には時間がかかるため,stringsコマンドで文字列を出してそれっぽいものをgrepで検出するという方法もあるでしょう。何かもっとうまいやり方がありそうな気もするのですが,すぐには思いつきませんでした。

次のコマンドで上記のパスに合わせて,ツリーを移動してしまいます。

$ mkdir -p ~/Packages/linux/build/linux-HvkI5B/
$ mv linux-5.11.0 ~/Packages/linux/build/linux-HvkI5B/

これでソースコードの準備は整いました。カーネルのソースコードディレクトリはcrashコマンド起動時に--srcオプションを指定することで反映されます。

$ crash --src ~/Packages/linux/ \
  /usr/lib/debug/boot/vmlinux-5.11.0-22-generic /var/crash/202107040335/dump.202107040335
(略)

disコマンドに-sオプションを付けると,今度こそ指定したシンボルに該当するソースコードを表示してれます。

crash> dis -s write_sysrq_trigger
FILE: /build/linux-HvkI5B/linux-5.11.0/drivers/tty/sysrq.c
LINE: 1151

  1146  /*
  1147   * writing 'C' to /proc/sysrq-trigger is like sysrq-C
  1148   */
  1149  static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
  1150                                     size_t count, loff_t *ppos)
* 1151  {
  1152          if (count) {
  1153                  char c;
  1154
  1155                  if (get_user(c, buf))
  1156                          return -EFAULT;
  1157                  __handle_sysrq(c, false);
  1158          }
  1159
  1160          return count;
  1161  }

disコマンドの引数は,-sオプションを付けるとgdbの「list」の引数として解釈されます。よってアドレス指定も可能です。

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。