Linuxカーネルハックに興味があるけど特にネタが無いんだよな〜って人向けの小ネタ

Linuxカーネルに興味があるんだけど特に作りたいものってないんだよなーなんて割とあると思う訳です。俺とか。。。 まあ、kernelnewbiesのメーリングリストでもよく見る話題かと思います。この辺なんかもそうですね。

で、そんな時にオススメできるのがkmemleakカーネルに組み込まれたメモリーリーク検出ツールです。 使い方は至って簡単でカーネルのコンフィグレーションにあるKernel memory leak detectorを有効にしたカーネルを普通に使えばOK。カーネルはメインラインのrcでもtipでもlinux-nextでも何でも良いと思います。

設定の場所はKernel Hacking -> Memory Debugging -> Kernel memory leak detectorにチェックをするのと、

f:id:masami256:20140509232229p:plain

その下のMaximum kmemleak early log entriesを適度な大きさにするだけ。前は4096でも問題なかったんだけど何時かのバージョンからそれじゃ足りなくなったので一気に32768って設定にしてます。

f:id:masami256:20140509232239p:plain

設定したら後は普通にカーネルをビルドしてそのカーネルで起動するだけ。 kmemleakを使うにはdebugfsが有効になってないといけないんだけど、これは今どきのディストリビューションならデフォルトで使えるようになってるんじゃないでしょうか。。とりあえずArch LinuxFedoraでは有効になってます。

メモリーリークのチェックはバックグラウンドで行われるので起動して適当に使いつづdmesgでログを確認します。メモリーリークが検出されると以下のように表示されるのでログに書かれているように/sys/kernel/debug/kmemleakを見る。

[  699.495516] kmemleak: 3 new suspected memory leaks (see /sys/kernel/debug/kmemleak)

チェックをすぐ行いたい場合は文字列scanを/sys/kernel/debug/kmemleakに書き込みます。

# echo scan > /sys/kernel/debug/kmemleak

/sys/kernel/debug/kmemleakを読むとこんな感じで1オブジェクトごとに検出した内容が書かれます。

unreferenced object 0xffff880036ca84c0 (size 16):
  comm "swapper/0", pid 1, jiffies 4294877407 (age 4434.633s)
  hex dump (first 16 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff  ................
  backtrace:
    [<ffffffff814ed01e>] kmemleak_alloc+0x4e/0xb0
    [<ffffffff8118913c>] __kmalloc+0x1fc/0x290
    [<ffffffff81302c9e>] bit_cursor+0x24e/0x6c0
    [<ffffffff812ff2f4>] fbcon_cursor+0x154/0x1d0
    [<ffffffff813675d8>] hide_cursor+0x28/0xa0
    [<ffffffff81368acf>] update_region+0x6f/0x90
    [<ffffffff81300268>] fbcon_switch+0x518/0x550
    [<ffffffff813695b9>] redraw_screen+0x189/0x240
    [<ffffffff8136a0e0>] do_bind_con_driver+0x360/0x380
    [<ffffffff8136a6e4>] do_take_over_console+0x114/0x1c0
    [<ffffffff812fdc83>] do_fbcon_takeover+0x63/0xd0
    [<ffffffff813023e5>] fbcon_event_notify+0x605/0x720
    [<ffffffff81501dcc>] notifier_call_chain+0x4c/0x70
    [<ffffffff81087f8d>] __blocking_notifier_call_chain+0x4d/0x70
    [<ffffffff81087fc6>] blocking_notifier_call_chain+0x16/0x20
    [<ffffffff812f201b>] fb_notifier_call_chain+0x1b/0x20

これの見方を簡単に説明すると、

  • 一番上はメモリーリークになったポインタのアドレスと、アローケートしたサイズ
  • 2行目はプログラム名(comm)、pidと時刻
  • 次はメモリのダンプ(32bytes分)
  • 最後はバックトレースで、どのような流れでメモリが確保されたかがわかります。 バックトレースを追いかける場合、inline展開されたものとかはさすがにバックトレースに出ないのでその辺は注意で。 このバックトレースはoops時の時と一緒なので関数名の後ろは機械語での命令の位置/関数のサイズって感じです。

メモリがリークしたタイミングというのはkmemleakではわからないのでそこはソースを読みつつ調べる必要があります。。。 これが面倒くさいんですけどね/(^o^)\ コードを追うにはlxrなりctagsなり好みのツールを使いましょう。

あとは、メモリーリークを起こしたプロセスによってデバッグのしやすさは変わるかも。kgdbとか使えるとより楽とかあるかもしれないですね。 自分が見つけてるのは大概pid 1だったりするのでデバッグの準備も面倒なのでコード読んである程度範囲を絞りながらprintkデバッグしてますが。。。 kgdbとか使うと楽なのかなーとは思いますけどね。

普通に使っててメモリーリークが出ない場合は他のテストツールと組み合わせても良いと思います。例えばファイルシステムのxfstestsっていうファイルシステムのテストツールとか。xfstestsはext4とかも対応しているのでxfs用って訳ではありません。

レポートされたとしてもfalse positiveな可能性もあるのでお忘れなく。

kmemleakを使ったバグの発見と修正の場合、特定のサブシステムに詳しくなるというよりは色んな所を見ざるを得ないというのがあります。フレームバッファのドライバにリークがあればそのドライバを読むし、ext4にメモリーリークがあればext4のコードを読むって感じになりますからね。これは良し悪しあると思いますが利点のほうが大きいかなーとも思います。

ここで例として出したメモリーリークは実際にうちで起きてたやつですが、どんな感じでコードを追いかけて修正したかっていうのは「lkmlにフレームバッファのメモリーリーク修正パッチを投げたのでそのデバッグ過程メモ - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ」にまとめてあるのですが、これなんかはディレクトリだけでもdrivers/video/console、drivers/video/tty/vtdrivers/video/kernel/の4つにまたがってコードを読んでいるのでフレームバッファのドライバの構造もなんとなくわかってきます。ちなみに自分はこの辺に興味が合ったわけではなくて、そこにメモリーリークが合ったからコードを読んだというだけなんですけども、まあそのへんは良しとしましょう。

メモリーリークも大概は修正できると思いますが稀にそんな簡単にはいかないって場合もあります(´・ω・`) 例えばこれとか。これは自分も追いかけている時に他の人がlkmlにパッチを投げて話が進んでたんですが、よくよく調べていくとi2cのデバイスドライバの問題じゃなくてデバイスドライバフレームワークも含めた話だったので難易度がかなり高いという。。

なにはともあれ、kmemleakでリークがレポートされたらLinuxカーネルに貢献のチャンスキタ━(゚∀゚)━!ってことなのでデバッグ頑張りましょう( ´∀`)bグッ!