以下は、Linux カーネル文書、Documentation/kmemcheck.txt の kanda.motohiro@gmail.com による訳です。原文と同じ、GPL v2 で公開します。
KMEMCHECK の始め方
==============================
Vegard Nossum <vegardno@ifi.uio.no>
Contents
========
0. はじめに
1. ダウンロード
2. 設定とコンパイル
3. 使い方
3.1. ブート
3.2. 実行時の有効、無効化
3.3. デバッグ
3.4. 実際には問題ない警告を注記する
4. 問題を報告する
5. 技術的説明
0. はじめに
===============
kmemcheck は、 Linux カーネルのためのデバッグ機能です。初期化されていないメモリーの使用を検出、警告するダイナミックなチェッカーです。
ユーザ空間のプログラマーならは、 Valgrind's memcheck をご存知かもしれません。memcheck と kmemcheck の最大の違いは、前者はユーザ空間のプログラムに、後者はカーネルにしか使えないということです。実装も当然、とても違います。このため、 kmemcheck は、 memcheck ほど正確ではありません。しかし、実際に使ってみると、それはコンパイラが静的解析で検出することができないプログラマーのエラーを見つけるのに十分役に立つことがわかります。
kmemcheck を有効にしたカーネルは、とても遅くなるので、対話的デスクトップなどの通常の負荷のもとで使用に耐えないでしょう。また、必要メモリーも2倍になります。なので、kmemcheck はデバッグ専用機能です。
1. ダウンロード
==============
バージョン 2.6.31-rc1 時点で、 kmemcheck はメインラインカーネルに含まれます。
2. 設定とコンパイル
============================
kmemcheck は、 x86 (32 and 64-bit) プラットフォームでのみ動きます。 kmemcheck メニューが "menuconfig" に現れるだけのためにも、多くの構成変数の正しい設定が必要です。
o CONFIG_CC_OPTIMIZE_FOR_SIZE=n
このオプションは、 "General setup" / "Optimize for size" の下にあります。
これがないと、 gcc の行うある種の最適化の結果、 kmemcheck が実際には問題ない警告(false positive warning)を出すことがあります。例えば、構造体に 16-bit フィールドがあるとき、 gcc は 32 bit をロードして、上の 16 bit を捨てることがあります。 kmemcheck は 32-bit ロードを見て、上の 16 bit が初期化されていないという警告を出します。
o CONFIG_SLAB=y or CONFIG_SLUB=y
このオプションは、 "General setup" / "Choose SLAB
allocator" の下にあります。
o CONFIG_FUNCTION_TRACER=n
このオプションは、 "Kernel hacking" / "Tracers" / "Kernel
Function Tracer" の下にあります。
コンパイル時に関数トレース機能が有効だと、 gcc はすべての関数の最初に、別の関数の呼び出しを追加します。この結果、ページフォールトハンドラーが呼ばれた時に、 ftrace フレームワークが、 kmemcheck がフォールトを処理する前に呼ばれるということです。そして、 ftrace が、 kmemcheck が追跡しているメモリーを変更したならば、再帰的な無限のページフォールトが起きます。
o CONFIG_DEBUG_PAGEALLOC=n
このオプションは、 "Kernel hacking" / "Debug page memory
allocations" の下にあります。
これに加えて、 CONFIG_DEBUG_INFO=y にすることを強くお勧めします。これは、 "Kernel hacking" の下にあります。これがあると、 kmemcheck の警告に行番号が入るので、問題のデバッグにとても有益です。ただ、このオプションはコンパイル処理を遅くして、カーネルイメージをずっと大きくするので、必須ではありません。
さて、これで、 kmemcheck メニューが、 "Kernel hacking" / "Memory
Debugging" / "kmemcheck: trap use of uninitialized memory"の下に見えるようになったはずです。 kmemcheck の構成変数について以下に述べます。
o CONFIG_KMEMCHECK
kmemcheck を使うなら、オンにして下さい。。。
o CONFIG_KMEMCHECK_[DISABLED | ENABLED | ONESHOT]_BY_DEFAULT
ブート時のふるまいを決めます。
"Enabled" は、最初から kmemcheck を有効にします。 "disabled" は、普通にカーネルをブートします。 (しかし、 kmemcheck コードはコンパイル時に追加されているので、ブート後に有効にできます。) "one-shot" は特別なモードで、初期化されていないメモリーの使用を最初に検出したときに、 kmemcheck を自動で無効にします。
kmemcheck を使って、アクティブに問題のデバッグをしたいならは、 "enabled" がよいでしょう。
one-shot モードは、自動テスト環境で便利かもしれません。山のような警告を出さないで済みますし、本当に何かが変な時にマシンが動作し続ける確率を上げるでしょう。それ以外の環境では、ワンショットモードは非生産的です。最初のエラーが起きた時に、自動で無効になってしまいますから。その警告が誤りで、本当に調べたい問題が次に続くということもありえます。
通常通りカーネルを使っていて、何か変な時に kmemcheck を有効にするには、 "disabled" がよいでしょう。kmemcheck が無効なときには、ほとんどの実行時オーバーヘッドは発生しないので、カーネルは通常と同じくらい高速なはずです。
o CONFIG_KMEMCHECK_QUEUE_SIZE
内部的な、(固定長)バッファに保存するエラー報告の最大長。
エラーは、あらゆる場所、あらゆるコンテキストで起きることがありますから、アクセスした時に、決してそれ以上のページフォールトを起こさないと保証される一時的な保存領域が必要です。タスクレットがスケジュールされたとき、直ちにキューは空になります。キューが一杯だと、新しいエラー報告は失われます。
デフォルトの64が、たぶん、良いでしょう。割り込み禁止のセクションで、64以上のエラーを起こすコードは、きっと、もっともっとたくさんのエラーを起こすと思われますから、それ以上の報告は、まず役に立ちません。(最初の報告が一番重要です。)
この値は、あなたが、シリアルコンソールなどを使ってカーネルログを保存しない時にはもっと小さくしなくてはいけません。"dmesg" コマンドでログを保存するとき、 kmemcheck の警告が多すぎて、カーネルログ自身をオーバーフローさせることがあります。その時、最初の方の報告は失われます。こういうときは、この値を10程度にするのが良いでしょう。
o CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT
エラー報告キューのエントリーごとに退避されるシャドウバイト数。
このバイトは、アロケーションのどの部分が初期化、未初期化であるかを示し、エラーが検出された時にその問題をデバッグできるように、一緒に表示されます。
この指定は、退避されるバイト数のログですから、例えばここで5を指定すると、2^5 = 32バイト退避されます。
デフォルト値は、ほとんどの問題をデバッグするのに最適と思われます。80カラムにちょうど収まります。
o CONFIG_KMEMCHECK_PARTIAL_OK
有効にされると、16ビット変数を32ビット読出して、上の16ビットを捨てるという、GCC の最適化を回避します。
デフォルト(有効)をお勧めします。これにより、もちろん、本当のエラーが隠れることもありますが、無効にすると多くの不要な警告が出ます。
o CONFIG_KMEMCHECK_BITOPS_OK
ビットフィールドをアクセスする時に、すべてのビットが同時に初期化されていないと出る警告を止めます。
これにより本当のバグが隠れることもあります。
このオプションは、たぶん、もう不要で、そのコードで、 kmemcheck ビットフィールド注記を代わりに使うのが良いでしょう。なので、デフォルト値のままでよいです。
さて、カーネルを通常通りコンパイルします。
3. 使い方
=============
3.1. ブート
============
まず、コマンドラインオプションの説明です。 kmemcheck のオプションは1つだけで、「 kmemcheck」と呼ばれます。CONFIG_KMEMCHECK_*_BY_DEFAULT で選択されたデフォルトモードをオーバーライドします。指定可能な値は、
o kmemcheck=0 (disabled)
o kmemcheck=1 (enabled)
o kmemcheck=2 (one-shot mode)
SLUB デバッギングが有効な場合、kmemcheck の指定にかかわらず、SLUB デバッギングされている slab キャッシュは kmemcheck に追跡されません。そうでなくするには、(デフォルトでは、そうするべきではないのですが) SLUB のブートオプション、 "slub_debug" を使って下さい。例えば、 slub_debug=-
実は、このオプションは、 SLUB と kmemcheck の間の粒度の細かい制御をするためにも使うことができます。例えば、コマンドラインに "kmemcheck=1 slub_debug=,dentry" とあると、SLUB デバッギングは "dentry" slab キャッシュだけに使われ、 kmemcheck がそれ以外のすべてのキャッシュを追跡します。これは高度な使い方で、一般的にはお勧めできません。
3.2. 実行時の有効、無効化
============================
カーネルがブートした後、 kmemcheck を実行時に有効、無効、にできます。注意:この機能は、いまだに実験的で、実際には問題ない警告を出すかもしれません。だから、使わないほうがいいです。うまく動かない(とんでもなく多くの警告が出るなど)ときには、私は喜んでバグ報告をお受けします。
/proc/sys/kernel/kmemcheck ファイルを使います。
$ echo 0 > /proc/sys/kernel/kmemcheck # disables kmemcheck
数字は、 kmemcheck= コマンドラインオプションと同じです。
3.3. デバッグ
==============
典型的な報告は、こうです。
WARNING: kmemcheck: Caught 32-bit read from uninitialized memory (ffff88003e4a2024)
80000000000000000000000000000000000000000088ffff0000000000000000
i i i i u u u u i i i i i i i i u u u u u u u u u u u u u u u u
^
Pid: 1856, comm: ntpdate Not tainted 2.6.29-rc5 #264 945P-A
RIP: 0010:[<ffffffff8104ede8>] [<ffffffff8104ede8>] __dequeue_signal+0xc8/0x190
RSP: 0018:ffff88003cdf7d98 EFLAGS: 00210002
RAX: 0000000000000030 RBX: ffff88003d4ea968 RCX: 0000000000000009
RDX: ffff88003e5d6018 RSI: ffff88003e5d6024 RDI: ffff88003cdf7e84
RBP: ffff88003cdf7db8 R08: ffff88003e5d6000 R09: 0000000000000000
R10: 0000000000000080 R11: 0000000000000000 R12: 000000000000000e
R13: ffff88003cdf7e78 R14: ffff88003d530710 R15: ffff88003d5a98c8
FS: 0000000000000000(0000) GS:ffff880001982000(0063) knlGS:00000
CS: 0010 DS: 002b ES: 002b CR0: 0000000080050033
CR2: ffff88003f806ea0 CR3: 000000003c036000 CR4: 00000000000006a0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000ffff4ff0 DR7: 0000000000000400
[<ffffffff8104f04e>] dequeue_signal+0x8e/0x170
[<ffffffff81050bd8>] get_signal_to_deliver+0x98/0x390
[<ffffffff8100b87d>] do_notify_resume+0xad/0x7d0
[<ffffffff8100c7b5>] int_signal+0x12/0x17
[<ffffffffffffffff>] 0xffffffffffffffff
この報告で、最も重要な情報は、 RIP (あるいは、32ビットでは EIP)値です。これが、警告を出すに至った命令です。
カーネルが CONFIG_DEBUG_INFO=y 付きでコンパイルされているなら、このアドレスを addr2line プログラムに与えれば、それで終わりです。
$ addr2line -e vmlinux -i ffffffff8104ede8
arch/x86/include/asm/string_64.h:12
include/asm-generic/siginfo.h:287
kernel/signal.c:380
kernel/signal.c:410
"-e vmlinux" は、 addr2line が見るべきファイルを指定します。重要:これは、警告を出したカーネルの vmlinux でなくてはいけません。そうでないと、行番号はまず、誤りです。
"-i" は addr2line に、インラインされた関数の行番号も表示するように指定します。今のケースでは、このフラグはとても重要です。そうしないと、最初の行を表示するだけで、それは memcpy() 呼び出しなので、カーネル内の数千の箇所から呼ばれるために、あまり役に立ちません。こういったインラインされた関数は上記のスタックトレースには現れません。カーネルが追加のデバッグ情報をロードしないためです。このテクニックは、通常のカーネル oops に対してももちろん使うことができます。
このケースでは、memcpy() 呼び出し元が重要です。それは、ここにあります。
include/asm-generic/siginfo.h, line 287:
281 static inline void copy_siginfo(struct siginfo *to, struct siginfo *from)
282 {
283 if (from->si_code < 0)
284 memcpy(to, from, sizeof(*to));
285 else
286 /* _sigchld is currently the largest know union member */
287 memcpy(to, from, __ARCH_SI_PREAMBLE_SIZE + sizeof(from->_sifields._sigchld));
288 }
リードの警告だったので、 (kmemcheck は通常は、リードの警告しか出しません。未初期化あるいは解放されたメモリーへのライトの警告を出すこともできますが。)"from" 引数が、未初期化のバイトを含んでいたのではないでしょうか。さらに呼び出しチェーンを上にたどって、"from" が確保あるいは初期化されたところを探しましょう。
kernel/signal.c, line 380:
359 static void collect_signal(int sig, struct sigpending *list, siginfo_t *info)
360 {
...
367 list_for_each_entry(q, &list->list, list) {
368 if (q->info.si_signo == sig) {
369 if (first)
370 goto still_pending;
371 first = q;
...
377 if (first) {
378 still_pending:
379 list_del_init(&first->list);
380 copy_siginfo(info, &first->info);
381 __sigqueue_free(first);
...
392 }
393 }
ここで、copy_siginfo() に渡されるのは、 &first->info です。 "first" 変数は、リストに見えます。collect_signal() の2つめの引数でもらったものです。 "list" のアイテムが確保あるいは初期化されたところを求めて、スタックの上の旅は続きます。
line 410:
395 static int __dequeue_signal(struct sigpending *pending, sigset_t *mask,
396 siginfo_t *info)
397 {
...
410 collect_signal(sig, pending, info);
...
414 }
さて、 "pending" ポインターをたどりましょう。これが、collect_signal() に "list" として渡されたものだからです。この時点で、"addr2line" 出力の外に出てしまいました。ご心配なく。kmemcheck スタックダンプの次のアドレスをペーストすればよいです。
[<ffffffff8104f04e>] dequeue_signal+0x8e/0x170
[<ffffffff81050bd8>] get_signal_to_deliver+0x98/0x390
[<ffffffff8100b87d>] do_notify_resume+0xad/0x7d0
[<ffffffff8100c7b5>] int_signal+0x12/0x17
$ addr2line -e vmlinux -i ffffffff8104f04e ffffffff81050bd8 \
ffffffff8100b87d ffffffff8100c7b5
kernel/signal.c:446
kernel/signal.c:1806
arch/x86/kernel/signal.c:805
arch/x86/kernel/signal.c:871
arch/x86/kernel/entry_64.S:694
これらのアドレスは、スタックから見つかったのであって、RIP 値からではないため、実際には、「次の」命令を指すことに注意下さい。(戻り番地です。) line 446 のコードを読めば、明らかです。
422 int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
423 {
...
431 signr = __dequeue_signal(&tsk->signal->shared_pending,
432 mask, info);
433 /*
434 * itimer signal ?
435 *
436 * itimers are process shared and we restart periodic
437 * itimers in the signal delivery path to prevent DoS
438 * attacks in the high resolution timer case. This is
439 * compliant with the old way of self restarting
440 * itimers, as the SIGALRM is a legacy signal and only
441 * queued once. Changing the restart behaviour to
442 * restart the timer in the signal dequeue path is
443 * reducing the timer noise on heavy loaded !highres
444 * systems too.
445 */
446 if (unlikely(signr == SIGALRM)) {
...
489 }
So instead of looking at 446, we should be looking at 431, which is the line
that executes just before 446. Here we see that what we are looking for is
&tsk->signal->shared_pending.
Our next task is now to figure out which function that puts items on this
"shared_pending" list. A crude, but efficient tool, is git grep:
$ git grep -n 'shared_pending' kernel/
...
kernel/signal.c:828: pending = group ? &t->signal->shared_pending : &t->pending;
kernel/signal.c:1339: pending = group ? &t->signal->shared_pending : &t->pending;
...
There were more results, but none of them were related to list operations,
and these were the only assignments. We inspect the line numbers more closely
and find that this is indeed where items are being added to the list:
816 static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
817 int group)
818 {
...
828 pending = group ? &t->signal->shared_pending : &t->pending;
...
851 q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
852 (is_si_special(info) ||
853 info->si_code >= 0)));
854 if (q) {
855 list_add_tail(&q->list, &pending->list);
...
890 }
and:
1309 int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
1310 {
....
1339 pending = group ? &t->signal->shared_pending : &t->pending;
1340 list_add_tail(&q->list, &pending->list);
....
1347 }
In the first case, the list element we are looking for, "q", is being returned
from the function __sigqueue_alloc(), which looks like an allocation function.
Let's take a look at it:
187 static struct sigqueue *__sigqueue_alloc(struct task_struct *t, gfp_t flags,
188 int override_rlimit)
189 {
190 struct sigqueue *q = NULL;
191 struct user_struct *user;
192
193 /*
194 * We won't get problems with the target's UID changing under us
195 * because changing it requires RCU be used, and if t != current, the
196 * caller must be holding the RCU readlock (by way of a spinlock) and
197 * we use RCU protection here
198 */
199 user = get_uid(__task_cred(t)->user);
200 atomic_inc(&user->sigpending);
201 if (override_rlimit ||
202 atomic_read(&user->sigpending) <=
203 t->signal->rlim[RLIMIT_SIGPENDING].rlim_cur)
204 q = kmem_cache_alloc(sigqueue_cachep, flags);
205 if (unlikely(q == NULL)) {
206 atomic_dec(&user->sigpending);
207 free_uid(user);
208 } else {
209 INIT_LIST_HEAD(&q->list);
210 q->flags = 0;
211 q->user = user;
212 }
213
214 return q;
215 }
We see that this function initializes q->list, q->flags, and q->user. It seems
that now is the time to look at the definition of "struct sigqueue", e.g.:
14 struct sigqueue {
15 struct list_head list;
16 int flags;
17 siginfo_t info;
18 struct user_struct *user;
19 };
And, you might remember, it was a memcpy() on &first->info that caused the
warning, so this makes perfect sense. It also seems reasonable to assume that
it is the caller of __sigqueue_alloc() that has the responsibility of filling
out (initializing) this member.
さて、その構造体のどのフィールドが初期化されてなかったのでしょう?kmemcheck の報告をもう一度見ましょう。
WARNING: kmemcheck: Caught 32-bit read from uninitialized memory (ffff88003e4a2024)
80000000000000000000000000000000000000000088ffff0000000000000000
i i i i u u u u i i i i i i i i u u u u u u u u u u u u u u u u
^
最初の2行は、それぞれ、メモリーオブジェクト自身のダンプと、シャドウバイトマップです。メモリーオブジェクト自身とは、今のケースでは &first->info です。ダンプの開始点はオブジェクト自身の開始点では「ない」ことに注意ください。キャレット(^) の位置は、リード(ffff88003e4a2024)のアドレスに対応します。
シャドウバイトマップダンプはこういう意味です。
i - 初期化済み
u - 未初期化
a - アロケートされていない (メモリーは slab 層によって確保されましたが、誰にも与えられていません。)
f - 解放 (メモリーは slab 層によって確保されましたが、以前の所有者によって解放されました。)
オブジェクトの開始点から相対的にどこに未初期化メモリーがあるかを知るためには、ディスアセンブリーを見る必要があります。そのためには、 RIP アドレスがまた必要です。
RIP: 0010:[<ffffffff8104ede8>] [<ffffffff8104ede8>] __dequeue_signal+0xc8/0x190
$ objdump -d --no-show-raw-insn vmlinux | grep -C 8 ffffffff8104ede8:
ffffffff8104edc8: mov %r8,0x8(%r8)
ffffffff8104edcc: test %r10d,%r10d
ffffffff8104edcf: js ffffffff8104ee88 <__dequeue_signal+0x168>
ffffffff8104edd5: mov %rax,%rdx
ffffffff8104edd8: mov $0xc,%ecx
ffffffff8104eddd: mov %r13,%rdi
ffffffff8104ede0: mov $0x30,%eax
ffffffff8104ede5: mov %rdx,%rsi
ffffffff8104ede8: rep movsl %ds:(%rsi),%es:(%rdi)
ffffffff8104edea: test $0x2,%al
ffffffff8104edec: je ffffffff8104edf0 <__dequeue_signal+0xd0>
ffffffff8104edee: movsw %ds:(%rsi),%es:(%rdi)
ffffffff8104edf0: test $0x1,%al
ffffffff8104edf2: je ffffffff8104edf5 <__dequeue_signal+0xd5>
ffffffff8104edf4: movsb %ds:(%rsi),%es:(%rdi)
ffffffff8104edf5: mov %r8,%rdi
ffffffff8104edf8: callq ffffffff8104de60 <__sigqueue_free>
予想通り、 memcpy() の "rep movsl" 命令が警告を出しています。REP MOVSL は、レジスター RCX を、残りの繰り返し数に使います。kmemcheck 報告にあるレジスターダンプをもう一度見ると、コピーのときにあと何バイト残っていたかわかります。
RAX: 0000000000000030 RBX: ffff88003d4ea968 RCX: 0000000000000009
ディスアセンブリーを見ると、ffffffff8104edd8 で、 %ecx に、 $0xc が入っています。ラッキーでした。これは、バイト数でなくて、繰り返しの数だということに注意ください。これは、"long" 命令ですから、バイト数は、4倍します。ということは、未初期化値は、オブジェクトの先頭から、4 * (0xc - 0x9) = 12 バイトのところにあったのです。
さて、"struct siginfo" のどのフィールドが初期化されていなかったかを調べましょう。以下は、その構造体の先頭です。
40 typedef struct siginfo {
41 int si_signo;
42 int si_errno;
43 int si_code;
44
45 union {
..
92 } _sifields;
93 } siginfo_t;
64ビットでは、 int は4バイト長ですから、初期化されていないのは、ユニオンのメンバーに違いありません。 gdb で確かめましょう。
$ gdb vmlinux
...
(gdb) p &((struct siginfo *) 0)->_sifields
$1 = (union {...} *) 0x10
ごらんのように、ユニオンのメンバーはオフセット 0x10 にあります。ということは、 gcc が、4バイトのパディングを、メンバー si_code と _sifields の間に入れたのです。これで、メモリーダンプの完全な絵が描けました。
_----------------------------=> si_code
/ _--------------------=> (padding)
| / _------------=> _sifields(._kill._pid)
| | / _----=> _sifields(._kill._uid)
| | | /
-------|-------|-------|-------|
80000000000000000000000000000000000000000088ffff0000000000000000
i i i i u u u u i i i i i i i i u u u u u u u u u u u u u u u u
これにより、もうひとつの重要な事実がわかります。 si_code は、0x80 が入っています。x86 はリトルエンディアンなので、最初の4バイト "80000000" は、実際には、 0x00000080 です。少し調べれば、これが include/asm-generic/siginfo.h で定義される定数、SI_KERNEL であることがわかります。
144 #define SI_KERNEL 0x80 /* sent by the kernel from somewhere */
このマクロが x86 で使われるのは、たった1箇所だけです。kernel/signal.c の send_signal() です。
816 static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
817 int group)
818 {
...
828 pending = group ? &t->signal->shared_pending : &t->pending;
...
851 q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
852 (is_si_special(info) ||
853 info->si_code >= 0)));
854 if (q) {
855 list_add_tail(&q->list, &pending->list);
856 switch ((unsigned long) info) {
...
865 case (unsigned long) SEND_SIG_PRIV:
866 q->info.si_signo = sig;
867 q->info.si_errno = 0;
868 q->info.si_code = SI_KERNEL;
869 q->info.si_pid = 0;
870 q->info.si_uid = 0;
871 break;
...
890 }
これは、 .si_code メンバーとも合いますし、以前に見つけた、 siginfo_t オブジェクトが "shared_pending" リストにエンキューされる場所にも一致します。
さて、まとめると、構造体の2つのフィールドの間にコンパイラが挿入したパディングが、初期化されておらず、これが構造体を memcpy() したときに報告されたようです。つまり、私達が見つけたのは、実際には問題でない警告でした。
普通は、kmemcheck は、コピー元とコピー先アドレスが追跡されているときには、 memcpy() で起きる未初期化アクセスを報告しません。(その代わりに、シャドウバイトマップをコピーします。)このケースでは、明らかに、コピー先アドレスは追跡されていませんでした。上記のスタックトレースをさらに掘り進めましょう。
arch/x86/kernel/signal.c:805
arch/x86/kernel/signal.c:871
arch/x86/kernel/entry_64.S:694
コピー先の siginfo オブジェクトは、スタックにあります。
782 static void do_signal(struct pt_regs *regs)
783 {
784 struct k_sigaction ka;
785 siginfo_t info;
...
804 signr = get_signal_to_deliver(&info, &ka, regs, NULL);
...
854 }
そして、この &info が、最終的に copy_siginfo() の、コピー先として渡されたのです。
さて、ここでは、実際のエラーは見つけられなかったのですが、この例は、報告が示すものがなんであるかを追跡するやり方を示したという点でよくできたものだったと言えましょう。
3.4. 実際には問題ない警告を注記する
===============================
ソースコードに注記することで kmemcheck が特定のアロケーションをチェックしたり報告したりすることを止める、いくつかの異なる方法があります。それは:
o __GFP_NOTRACK_FALSE_POSITIVE
このフラグを、 kmalloc() or kmem_cache_alloc() (および、それらを呼ぶことになるそれ以外のいくつかの関数も)に与えることで、アロケーションの追跡をやめさせることができます。これは、kmemcheck を黙らせる「大きなカナヅチ」方法で、その構造体のあるフィールドの、実際には問題ない警告を出さなくすることができても、その構造体の他の部分で本当のエラーを見つけることもできなくなります。
Example:
/* No warnings will ever trigger on accessing any part of x */
x = kmalloc(sizeof *x, GFP_KERNEL | __GFP_NOTRACK_FALSE_POSITIVE);
o kmemcheck_bitfield_begin(name)/kmemcheck_bitfield_end(name) and
kmemcheck_annotate_bitfield(ptr, name)
これら3つのマクロの最初の2つを、構造体定義の中で使うと、ビットフィールドの開始と終了を示すことができます。さらに、ビットフィールドには、マクロの引数に与えた名前がつきます。
これらのマーカーを作った後、アロケーションの時に、kmemcheck_annotate_bitfield() を使ってアロケーションのどの部分がビットフィールドの一部であるかを指定できます。
Example:
struct foo {
int x;
kmemcheck_bitfield_begin(flags);
int flag_a:1;
int flag_b:1;
kmemcheck_bitfield_end(flags);
int y;
};
struct foo *x = kmalloc(sizeof *x);
/* No warnings will trigger on accessing the bitfield of x */
kmemcheck_annotate_bitfield(x, flags);
kmemcheck_annotate_bitfield() は、 kmalloc() の戻り値をチェックする前に使うことができることに注意下さい。つまり、最初の引数に NULL を与えるのは正常で、何もしません。
4. 問題を報告する
===================
これまで述べたように、 kmemcheck は、実際には問題ない警告を出すことがあります。なので、その警告をそのまま、メーリングリストやメインテナーに送るのはあまり良いことではありません。その代わりに、メインテナーや開発者の方々は、ご自分のコードの問題をご自分で見つけられることをお勧めします。警告が出たら、回避したり、それが本当の問題かを考えたり、あるいは、無視するか、決めて下さい。開発者というものは、自分のコードをよく知っているはずですから、 kmemcheck の報告の根本原因を調べるのは即座に、かつ、効率よくできることでしょう。これが、 kmemcheck を使う最も効率的なやり方と言えます。
それはつまり、私達、( kmemcheck メインテナー)も、問題ない警告を注記したり、無効にしたりする義務があるということです。なので、あなたが何か見つけたら、こっそり私達に教えて下さい!カーネルのコンフィグと再現手順(もし可能なら)があると、とても助かります。
ハッピイハッキング!
5. 技術的説明
========================
kmemcheck は、メモリーのページを不在とマークすることで動作します。つまり、誰かがそのページをアクセスしようとすると必ず、ページフォールトが起きます。ページフォールトハンドラーは、そのページが実は隠されているだけだと気が付き、 kmemcheck コードを呼んで、さらなる調査をします。
調査が終わったら、 kmemcheck はページの存在ビットを立てて「見えるように」します。しかし、命令を実行し終わった後、またページを見えなくして、次のアクセスをつかまえる必要があります。このために、 kmemcheck は、プロセッサーのデバッグ機能、つまり、シングルステップ実行を使います。プロセッサーが、メモリアクセスを生成した1つの命令をシングルステップ実行した後、デバッグ例外があがります。その後、私たちはページを単純に隠して、処理を続行します。このときは、シングルステップ機能は使われません。
kmemcheck が動くためには、メモリーアロケータの助けが必要です。
メモリーアロケータは、
1. kmemcheck に、新しく割り当てられたページと、これから解放されるページを伝える必要があります。これにより、 kmemcheck は問題のページのシャドウメモリーを用意あるいは後始末できます。シャドウメモリーは、ページ内のそれぞれのバイトの状態、つまり、初期化済みかそうでないか、を格納します。
2. kmemcheck に、メモリーのどの部分を初期化されていないとマークするかを伝えます。実際には、「まだ確保されていない」や、「最近解放された」など、それ以外の状態もいくつかあります。
スラブキャッシュが、 SLAB_NOTRACK フラグつきでセットアップされた場合、それは、kmemcheck によってページフォールトを起こす可能性のあるメモリーを返すことはありません。
そうでない場合でも、スラブの呼び出し元は、 __GFP_NOTRACK or __GFP_NOTRACK_FALSE_POSITIVE フラグを指定してメモリーを確保することができます。これは、ページフォールトが起きるのを防ぐことはできませんが、問題のオブジェクトを初期化済みとマークするため、そのオブジェクトに関して、警告は出なくなります。
現在、 SLAB and SLUB アロケータが、 kmemcheck でサポートされます。