読者です 読者をやめる 読者になる 読者になる

kernel 3.4-rc3のメモリリーク追っかけ2

kernel linux

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()で解放できるようにするのが正解かなー?