x86_64_start_kernel()がコールフローの最初にいるパターンとしてこの2パターンがありまして、
unreferenced object 0xffff880257005a80 (size 192): comm "swapper/0", pid 0, jiffies 4294667303 (age 504.517s) hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace: [<ffffffff815d655b>] kmemleak_alloc+0x5b/0xc0 [<ffffffff81168138>] __kmalloc+0x138/0x1a0 [<ffffffff811ea6d0>] __register_sysctl_paths+0x120/0x1e0 [<ffffffff811ea7ab>] register_sysctl_paths+0x1b/0x20 [<ffffffff811ea7c8>] register_sysctl_table+0x18/0x20 [<ffffffff81d0c48c>] sysctl_init+0x10/0x14 [<ffffffff81d17cea>] proc_sys_init+0x2f/0x31 [<ffffffff81d17ac7>] proc_root_init+0xa5/0xa7 [<ffffffff81cf4bf2>] start_kernel+0x399/0x3d3 [<ffffffff81cf4346>] x86_64_start_reservations+0x131/0x135 [<ffffffff81cf444a>] x86_64_start_kernel+0x100/0x10f [<ffffffffffffffff>] 0xffffffffffffffff unreferenced object 0xffff88025706f180 (size 96): comm "swapper/0", pid 0, jiffies 4294667304 (age 504.516s) hex dump (first 32 bytes): 40 90 e7 81 ff ff ff ff 00 00 00 00 01 00 00 00 @............... 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace: [<ffffffff815d655b>] kmemleak_alloc+0x5b/0xc0 [<ffffffff81168138>] __kmalloc+0x138/0x1a0 [<ffffffff811e9f6a>] __register_sysctl_table+0x5a/0x490 [<ffffffff811ea525>] register_leaf_sysctl_tables+0x185/0x1f0 [<ffffffff811ea458>] register_leaf_sysctl_tables+0xb8/0x1f0 [<ffffffff811ea458>] register_leaf_sysctl_tables+0xb8/0x1f0 [<ffffffff811ea702>] __register_sysctl_paths+0x152/0x1e0 [<ffffffff811ea7ab>] register_sysctl_paths+0x1b/0x20 [<ffffffff811ea7c8>] register_sysctl_table+0x18/0x20 [<ffffffff81d0c48c>] sysctl_init+0x10/0x14 [<ffffffff81d17cea>] proc_sys_init+0x2f/0x31 [<ffffffff81d17ac7>] proc_root_init+0xa5/0xa7 [<ffffffff81cf4bf2>] start_kernel+0x399/0x3d3 [<ffffffff81cf4346>] x86_64_start_reservations+0x131/0x135 [<ffffffff81cf444a>] x86_64_start_kernel+0x100/0x10f [<ffffffffffffffff>] 0xffffffffffffffff
いづれのパターンも__register_sysctl_paths()がポイントになっています。
struct ctl_table_header *__register_sysctl_paths( struct ctl_table_set *set, const struct ctl_path *path, struct ctl_table *table) { struct ctl_table *ctl_table_arg = table; int nr_subheaders = count_subheaders(table); struct ctl_table_header *header = NULL, **subheaders, **subheader; const struct ctl_path *component; char *new_path, *pos; pos = new_path = kmalloc(PATH_MAX, GFP_KERNEL); if (!new_path) return NULL; pos[0] = '\0'; for (component = path; component->procname; component++) { pos = append_path(new_path, pos, component->procname); if (!pos) goto out; } while (table->procname && table->child && !table[1].procname) { pos = append_path(new_path, pos, table->procname); if (!pos) goto out; table = table->child; } if (nr_subheaders == 1) { header = __register_sysctl_table(set, new_path, table); if (header) header->ctl_table_arg = ctl_table_arg; } else { header = kzalloc(sizeof(*header) + sizeof(*subheaders)*nr_subheaders, GFP_KERNEL); if (!header) goto out; subheaders = (struct ctl_table_header **) (header + 1); subheader = subheaders; header->ctl_table_arg = ctl_table_arg; if (register_leaf_sysctl_tables(new_path, pos, &subheader, set, table)) goto err_register_leaves; } out: kfree(new_path); return header; err_register_leaves: while (subheader > subheaders) { struct ctl_table_header *subh = *(--subheader); struct ctl_table *table = subh->ctl_table_arg; unregister_sysctl_table(subh); kfree(table); } kfree(header); header = NULL; goto out; }
この関数の中でif (nr_subheaders == 1) のif文でどっちのほうのリークするパターンになるかが変わる訳です。 それはちょっと置いておいて、__register_sysctl_paths()に至る流れはsysctl_init() -> register_sysctl_table() -> __register_sysctl_paths()です。実際のコードはこのようになってます。これをコードで見てみるこういう感じです。
kernel/sysctl.c
int __init sysctl_init(void) { register_sysctl_table(sysctl_base_table); return 0; } fs/proc/proc_sysctl.c struct ctl_table_header *register_sysctl_table(struct ctl_table *table) { static const struct ctl_path null_path[] = { {} }; return register_sysctl_paths(null_path, table); } struct ctl_table_header *register_sysctl_paths(const struct ctl_path *path, struct ctl_table *table) { return __register_sysctl_paths(&sysctl_table_root.default_set, path, table); }
ここで気づくのはsysctl_init()以降の関数の戻り値はstruct ctl_table_header *なんだけど誰も受け取ってないよねっとこです。__register_sysctl_paths()が戻すのはheaderって変数でこれはkzalloc()でメモリを確保するか、__register_sysctl_table()の戻り値でこちらもkzallocでメモリが確保された変数です。
if (nr_subheaders == 1) { header = __register_sysctl_table(set, new_path, table); if (header) header->ctl_table_arg = ctl_table_arg; } else { header = kzalloc(sizeof(*header) + sizeof(*subheaders)*nr_subheaders, GFP_KERNEL);
__register_sysctl_paths()の処理を見てるとheaderがkfree()で解放されるのは何かエラーがあった場合だけなので、エラー無しで終了する場合はheaderの所有者がいなくなってkmemleakがレポートしてくるのだろうなというのが、今のとこ解った部分。 unregister_sysctl_table()で解放できるようにするのが正解かなー?