6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Linux kernel debug用のプロセス一覧出力gdbコマンド

Posted at

概要

LinuxKernel勉強会のもくもくタイムに、プロセスの一覧を取得するgdbコマンドを書いた。
せっかくなので忘れないうちにqiitaに上げておく。

調査

Kernel内で全プロセスに対して処理を行う場合、
for_each_processマクロ関数を使うようなので、その実装を確認。

# define next_task(p) \
	list_entry_rcu((p)->tasks.next, struct task_struct, tasks)

# define for_each_process(p) \
	for (p = &init_task ; (p = next_task(p)) != &init_task ; )

上記から、init_taskはグローバル変数として参照できることと、struct task_structがもつtasks list_headメンバをたどっていくことで全タスクを参照できそうなことがわかる。
※はじめ名前から勝手にchildren list_headメンバを辿ろうとして迷子になりかけた汗

ということで、それをgdbコマンドとして定義してみた。

gdbコマンド(gdbスクリプト版)

define ktasks
  set $offset = (long)&init_task.tasks - (long)&init_task
  set $task = &init_task
  printf "  PID COMM\n"
  while 1
    printf "%5d %s\n", $task->pid, $task->comm
    set $task = (struct task_struct *)((long)$task->tasks.next - $offset)
    if $task == &init_task
      loop_break
    end
  end
end
document ktasks
Show kernel task list.
end

出力はこんな感じ。

(gdb) ktasks
  PID COMM
    0 swapper/0
    1 init
    2 kthreadd
    3 ksoftirqd/0
   (中略)
   92 sh
   93 kworker/u2:1

中身はfor_each_processマクロ関数をそのままgdbスクリプトにしただけ。

強いてポイントを挙げるとすれば、

  • list_headから元の構造体のアドレスを算出するためにoffsetを求めている
  • そのoffsetを使って構造体のアドレスを算出するときに、純粋なアドレスの演算にするためにlongにキャストしてる(キャストしないと「list_headのサイズ」×offsetだけ戻してしまう)
    ぐらいかな。

gdbコマンド(python版)

もくもくで書いたのはgdbスクリプト版だったけど、もしかしたら読みやすくなるかもという期待と復習を兼ねてpython版も作ってみた。
それがこちら。

import gdb

class InfoKtasks(gdb.Command):
  """Show kernel task list."""

  def __init__(self):
    super(InfoKtasks, self).__init__("info ktasks", gdb.COMMAND_STATUS)

  def invoke(self, arg, from_tty):
    t_long = gdb.lookup_type("long")
    t_task = gdb.lookup_type("struct task_struct")
    init = gdb.parse_and_eval("init_task")
    offset = init["tasks"].address.cast(t_long) - init.address.cast(t_long)
    task = init.address
    print("{0:>5} {1:}".format("PID", "COMM"))
    while True:
      print("{0:>5} {1:}".format(str(task["pid"]),
                                 task["comm"].string()))
      task = (task["tasks"]["next"].cast(t_long) - offset).cast(t_task.pointer())
      if task == init.address:
        break

InfoKtasks()

※出力はgdbスクリプトと変わらないので略。

うーん。。。
なんとなくかっちょよくなったような厨二的満足感は得られるものの、読みやすくなったかというと微妙。

  • いちいちgdb.Typeとかgdb.Valueとかをgdbモジュールを使って取得しないといけないのが煩わしい
  • アドレス取得・キャスト・構造体のメンバ参照などのシンタックスが当然Cと全然違うので、頭の中で1ステップ変換を挟まないといけない
  • 今回のようにベタベタに特定のシンボルの値を出力するコマンドの場合、素直にgdbスクリプトを使った方が筋が良さそう。文字操作や複雑なロジックを表現するpythonで書くメリットが出てくると思うが。

感想

  • ぼけーっとコード読んでるとなかなか頭に入らないけど、動かしつつデータを解釈するコマンドを作ってると理解が進む(気がする)。
  • qiitaにgdbスクリプトモードがなかった。。。ショボーン(´・ω・`)

リンク

github

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?