Linuxのlivepatchはftraceを使って古い関数へのアクセスをフックして新しい関数を呼ぶようにしているというのは以前の記事で調べたんですが、 じゃあ、セットしたIP(Instruction Pointer)をどのように使って新しい関数に飛ばしているのか?というのが知りたかったことです。
ftraceにセットするハンドラの関数はkernel/livepatch/core.cにあるklp_ftrace_handler()で325行目から332行目までで呼び出したい関数のアドレスを取得します。
317 static void notrace klp_ftrace_handler(unsigned long ip, 318 unsigned long parent_ip, 319 struct ftrace_ops *fops, 320 struct pt_regs *regs) 321 { 322 struct klp_ops *ops; 323 struct klp_func *func; 324 325 ops = container_of(fops, struct klp_ops, fops); 326 327 rcu_read_lock(); 328 func = list_first_or_null_rcu(&ops->func_stack, struct klp_func, 329 stack_node); 330 if (WARN_ON_ONCE(!func)) 331 goto unlock; 332 333 klp_arch_set_pc(regs, (unsigned long)func->new_func); 334 unlock: 335 rcu_read_unlock(); 336 }
そして、333行目で呼んでいるklp_arch_set_pc()にてstruct pt_regsのipをfunc->new_funcに設定します。
38 static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) 39 { 40 regs->ip = ip; 41 }
ここまでは特に問題なくて、じゃあ、regs->ipをどのように使ってnew_funcを呼んでいるのかというのが今回のポイントです。 そして、調べたところ答えは平松さんがylugの第110回で発表したkpatchのスライドにありました。 リンク先のスライドを見ていただければ分かるように、ハンドラの呼び出しから戻った後にスタックフレームにある戻りアドレスをregs->ipに変えているようです。
そこでklp_ftrace_handler()にWARN_ON_ONCE()を入れてスタックトレースを取ったのがこちらです。使ったのはlivepatchのサンプルコードそのままです。
[ 171.642145] Call Trace: [ 171.642149] [<ffffffff81588791>] dump_stack+0x4c/0x6e [ 171.642151] [<ffffffff810789fa>] warn_slowpath_common+0x8a/0xc0 [ 171.642164] [<ffffffff8124ff10>] ? cmdline_proc_open+0x20/0x20 [ 171.642167] [<ffffffff812037a5>] ? seq_read+0xf5/0x3d0 [ 171.642169] [<ffffffff81078b2a>] warn_slowpath_null+0x1a/0x20 [ 171.642171] [<ffffffff810dfb01>] klp_ftrace_handler+0xb1/0xc0 [ 171.642173] [<ffffffff81124810>] ftrace_ops_list_func+0xb0/0x180 [ 171.642177] [<ffffffff81590425>] ftrace_regs_call+0x5/0x72 [ 171.642178] [<ffffffff8124ff15>] ? cmdline_proc_show+0x5/0x30 [ 171.642180] [<ffffffff8124ff15>] cmdline_proc_show+0x5/0x30 [ 171.642181] [<ffffffff812037a5>] seq_read+0xf5/0x3d0 [ 171.642183] [<ffffffff8124ff15>] ? cmdline_proc_show+0x5/0x30 [ 171.642184] [<ffffffff812037a5>] ? seq_read+0xf5/0x3d0 [ 171.642187] [<ffffffff81247fe8>] proc_reg_read+0x48/0x70 [ 171.642190] [<ffffffff811de617>] __vfs_read+0x37/0x100 [ 171.642193] [<ffffffff8128204a>] ? security_file_permission+0x8a/0xa0 [ 171.642195] [<ffffffff811def0a>] vfs_read+0x8a/0x140 [ 171.642197] [<ffffffff811dfd69>] SyS_read+0x59/0xd0 [ 171.642199] [<ffffffff81066f97>] ? trace_do_page_fault+0x37/0xf0 [ 171.642201] [<ffffffff8158df2e>] system_call_fastpath+0x12/0x71 [ 171.642202] ---[ end trace 19cacbab0add2e74 ]---
patch対象のcmdline_proc_show()が呼ばれた後にftrace_regs_call()が呼ばれてますね。 ftrace_regs_call()はアセンブラの関数でarch/x86/kernel/mcount_64.Sにあります。
222 GLOBAL(ftrace_regs_call) 223 call ftrace_stub 224 225 /* Copy flags back to SS, to restore them */ 226 movq EFLAGS(%rsp), %rax 227 movq %rax, MCOUNT_REG_SIZE(%rsp) 228 229 /* Handlers can change the RIP */ 230 movq RIP(%rsp), %rax 231 movq %rax, MCOUNT_REG_SIZE+8(%rsp) 232 233 /* restore the rest of pt_regs */ 234 movq R15(%rsp), %r15 235 movq R14(%rsp), %r14 236 movq R13(%rsp), %r13 237 movq R12(%rsp), %r12 238 movq R10(%rsp), %r10 239 movq RBX(%rsp), %rbx 240 241 restore_mcount_regs 242 243 /* Restore flags */ 244 popfq 245 246 /* 247 * As this jmp to ftrace_return can be a short jump 248 * it must not be copied into the trampoline. 249 * The trampoline will add the code to jump 250 * to the return. 251 */ 252 GLOBAL(ftrace_regs_caller_end) 253 254 jmp ftrace_return
スライドのソースから多少変わっていますが230、231行目がリターンアドレスを書き換えているところでしょうか。 それプラス、その他のレジスタを元に戻してあげるとリターンアドレスが新しい関数のアドレスになっているで、そちらに遷移しかつレジスタはその関数を呼ぶのに適切な設定に戻っているのでなんら問題なく新しい関数にたどり着いて処理が続行できるということですね。
Raspberry Piで学ぶ電子工作 超小型コンピュータで電子回路を制御する (ブルーバックス)
- 作者: 金丸隆志
- 出版社/メーカー: 講談社
- 発売日: 2015/03/20
- メディア: Kindle版
- この商品を含むブログを見る