概要
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スクリプトモードがなかった。。。ショボーン(´・ω・`)