前回の記事(linux: livepatchコードリーディングめも1 〜 patch側のデータ設定とpatchの登録まで - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ)はpatchを登録するを見たので今回はpatchを有効にする部分を見てきます。
まず、livepatch-sample.cでpatchを有効にしている部分を確認すると、klp_enable_patch()がそれだとわかります。
75 ret = klp_enable_patch(&patch); 76 if (ret) { 77 WARN_ON(klp_unregister_patch(&patch)); 78 return ret; 79 }
では、これの処理をkernel/livepatch/core.cで見てみます。
584 int klp_enable_patch(struct klp_patch *patch) 585 { 586 int ret; 587 588 mutex_lock(&klp_mutex); 589 590 if (!klp_is_patch_registered(patch)) { 591 ret = -EINVAL; 592 goto err; 593 } 594 595 ret = __klp_enable_patch(patch); 596 597 err: 598 mutex_unlock(&klp_mutex); 599 return ret; 600 }
ここの処理はpatchが登録されているかを確認して、登録されているなら__klp_enable_patch()を呼んで実際にpatchを有効化していってます。
__klp_enable_patch()はこうなっています。
537 static int __klp_enable_patch(struct klp_patch *patch) 538 { 539 struct klp_object *obj; 540 int ret; 541 542 if (WARN_ON(patch->state != KLP_DISABLED)) 543 return -EINVAL; 544 545 /* enforce stacking: only the first disabled patch can be enabled */ 546 if (patch->list.prev != &klp_patches && 547 list_prev_entry(patch, list)->state == KLP_DISABLED) 548 return -EBUSY; 549 550 pr_notice_once("tainting kernel with TAINT_LIVEPATCH\n"); 551 add_taint(TAINT_LIVEPATCH, LOCKDEP_STILL_OK); 552 553 pr_notice("enabling patch '%s'\n", patch->mod->name); 554 555 for (obj = patch->objs; obj->funcs; obj++) { 556 klp_find_object_module(obj); 557 558 if (!klp_is_object_loaded(obj)) 559 continue; 560 561 ret = klp_enable_object(obj); 562 if (ret) 563 goto unregister; 564 } 565 566 patch->state = KLP_ENABLED; 567 568 return 0; 569 570 unregister: 571 WARN_ON(__klp_disable_patch(patch)); 572 return ret; 573 }
最初はエラーチェックで、次のpr_notice_onece()、pr_notice()でこのようなログがでます。dmesg or journalctl -b -kですね。
Apr 25 12:56:03 miko kernel: livepatch: tainting kernel with TAINT_LIVEPATCH Apr 25 12:56:03 miko kernel: livepatch: enabling patch 'livepatch_sample'
その次からのループがこの関数のメイン処理ですね。ループは登録されている関数分周り、その関数が存在するのがカーネルモジュールならそのカーネルモジュールがロードされているかチェックします。 対象の関数がカーネル本体にある場合は、obj->nameがNULLなはずなのでそれをチェックします。 このチェックをやっているのはklp_find_object_module()とklp_is_object_loaded()です。 そして、チェックが終わったらklp_enable_object()を呼びます。ループを正常に抜けると最後にpatch全体のステータスをKLP_ENABLEDにして完了ですね。
ではklp_enable_object()を見てみます。
454 static int klp_enable_object(struct klp_object *obj) 455 { 456 struct klp_func *func; 457 int ret; 458 459 if (WARN_ON(obj->state != KLP_DISABLED)) 460 return -EINVAL; 461 462 if (WARN_ON(!klp_is_object_loaded(obj))) 463 return -EINVAL; 464 465 for (func = obj->funcs; func->old_name; func++) { 466 ret = klp_enable_func(func); 467 if (ret) 468 goto unregister; 469 } 470 obj->state = KLP_ENABLED; 471 472 return 0; 473 474 unregister: 475 WARN_ON(klp_disable_object(obj)); 476 return ret; 477 }
最初のほうのチェックは飛ばして、struct klp_funcに登録されている関数分のループでklp_enable_func()を呼んでいきます。ループが無事に終了するとオブジェクトのステータスがKLP_ENABLEDになります。
klp_enable_func()ですが、ここでは久しぶりに処理らしい処理が出てきます。 先に書いておくとlivepatchの機能はftraceを使って実現していて、この関数ではpatch対象の古い関数にフィルターが登録されている場合にフィルター登録するということをしています。
実際こんな感じで、patchを当てる前は/sys/kernel/debug/tracing/enabled_functionsは何もありませんが、patchを当てるとcmdline_proc_show()にフィルターがセットされたことがわかります。
[root@miko masami]# cat /sys/kernel/debug/tracing/enabled_functions [root@miko masami]# cd tst_mod/ [root@miko tst_mod]# insmod ./livepatch-sample.ko [root@miko tst_mod]# cat /sys/kernel/debug/tracing/enabled_functions cmdline_proc_show (1) R I ->ftrace_ops_list_func+0x0/0x180 [root@miko tst_mod]#
では関数を見ていきましょう。
377 static int klp_enable_func(struct klp_func *func) 378 { 379 struct klp_ops *ops; 380 int ret; 381 382 if (WARN_ON(!func->old_addr)) 383 return -EINVAL; 384 385 if (WARN_ON(func->state != KLP_DISABLED)) 386 return -EINVAL; 387 388 ops = klp_find_ops(func->old_addr); 389 if (!ops) { 390 ops = kzalloc(sizeof(*ops), GFP_KERNEL); 391 if (!ops) 392 return -ENOMEM; 393 394 ops->fops.func = klp_ftrace_handler; 395 ops->fops.flags = FTRACE_OPS_FL_SAVE_REGS | 396 FTRACE_OPS_FL_DYNAMIC | 397 FTRACE_OPS_FL_IPMODIFY; 398 399 list_add(&ops->node, &klp_ops); 400 401 INIT_LIST_HEAD(&ops->func_stack); 402 list_add_rcu(&func->stack_node, &ops->func_stack); 403 404 ret = ftrace_set_filter_ip(&ops->fops, func->old_addr, 0, 0); 405 if (ret) { 406 pr_err("failed to set ftrace filter for function '%s' (%d)\n", 407 func->old_name, ret); 408 goto err; 409 } 410 411 ret = register_ftrace_function(&ops->fops); 412 if (ret) { 413 pr_err("failed to register ftrace handler for function '%s' (%d)\n", 414 func->old_name, ret); 415 ftrace_set_filter_ip(&ops->fops, func->old_addr, 1, 0); 416 goto err; 417 } 418 419 420 } else { 421 list_add_rcu(&func->stack_node, &ops->func_stack); 422 } 423 424 func->state = KLP_ENABLED; 425 426 return 0; 427 428 err: 429 list_del_rcu(&func->stack_node); 430 list_del(&ops->node); 431 kfree(ops); 432 return ret; 433 }
エラーチェックを飛ばすと最初にklp_find_ops()でpatch対象の関数にlivepatchがすでにフィルターをセットしたかを確認し、 見つかった場合はlist_add_rcu()でfuncをopsのリストに追加。ということで1つのopsに対して複数のfuncが登録できるようですね。これは3個のpatchを当てた時にfoobar()という関数に対して1回目のpatch、2回目のpatchってな具合にスタックしていくということだと思います。で、2回目のpatchを外したら1回目のpatchが動作するようにと。
では、opsが見つからなかった場合(初めて登録するときとか)はハンドラとかftraceのフラグを設定するstruct klp_opsのメモリを確保してデータを設定。
次にファイル内でstaticなリストの変数(klp_ops)に作成したopsを追加。そして、funcをopsのリストに追加。このfuncをリストに追加しているのはopsがあった場合と同様の処理です。
そして、ftrace_set_filter_ip()でpatchを当てる旧関数のアドレスにフィルターを設定します。この時に呼ばれるハンドラはklp_ftrace_handler()です。これは後で見ましょう。
フィルターをセットしたら次にregister_ftrace_function()でfopsを渡してftraceに登録します。ここまでが正常に終わるとfuncのステータスがKLP_ENABLEDになります。あとはreturnしていくだけなので順番としてはfunc -> objs -> patchという順番でKLP_ENABLEDが設定されていくわけですね。
ではftraceに設定したハンドラのklp_ftrace_handler()を見てみます。
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 }
処理としては最初にlist_first_or_null_rcu()でopsのfunc_stackにつながっている最初の要素を取得します。 そして、これにセットされている関数のアドレス(func->new_func)をklp_arch_set_pc()に渡します。
klp_arch_set_pc()はこのようになっています。引数のipは呼び出したい関数のアドレスなのでrip(x86_64なら)をにして、処理を新しい関数に移している感じですね。
38 static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) 39 { 40 regs->ip = ip; 41 }
livepatchの処理としてはこのようになります。livepatchは既存の関数に対してftraceのフィルターを設定し、古い関数の実行時に設定したハンドラから新しい関数を実行するように動作させているということがわかりました。これによってlivepatch自体はpatchのデータの管理だけに注力できるというメリットがありますね。実際core.cも全部で1000行ちょいという短さですし。
あとは実際にこの機能を使った本当のbug fix patchがどのようになるのかは興味ありますね。4.0カーネル自体がまだ出たばっかりなので実運用環境向けのlivepatchが出てくるのはまだ先になるかもですが。
RaspberryPiで学ぶ ARMデバイスドライバープログラミング
- 作者: 米田聡
- 出版社/メーカー: ソシム
- 発売日: 2014/09/24
- メディア: 単行本
- この商品を含むブログ (1件) を見る