当前时间,周五晚10点45分左右。

我的需求是用crash工具dump出Netfilter的某个hook点所有hook所属模块的名字。

我的方法如下,首先找到模块地址:

crash px nf_hooks[2][0] =>var
crash list nf_hook_ops.list -s nf_hook_ops.owner -H $var |awk -F '=' '/owner/{print $2}'

如此会得到一个列表,比如:

0x1234
0x6678
0x8641
0x4570
...

显然,这些地址都是module结构体,我需要执行下面的命令得到其name字段:

crash module.name $addr_above

然而,我必须一个一个输入,或者使用文件redirect:

crash list nf_hook_ops.list -s nf_hook_ops.owner -H $var |awk -F '=' '/owner/{print $2}' >./aa
crash module.name <./aa

这太麻烦了。

我想知道能不能在crash命令行编写脚本或者使用类似xargs,awk BEGIN,END之类的,比如使用下面这种:

crash list nf_hook_ops.list -s nf_hook_ops.owner -H $var |awk -F '=' '/owner/{print $2}' | xargs -i    module.name {}

从而一次性获得所有结果,免除手工输入的麻烦。

我试了,crash命令没有办法被管道化。

经过询问,了解到crash有一个extend命令可以加载extension扩展插件,研究了一下,比较简单且有意义。

说白了就是基于crash的API写一个共享库,在crash命令行输入help extend将会显示这样的共享库如何来编码。我基于上述需求简单写了一个,可以用:

// nfhooks.c
#include <crash/defs.h>

static int get_field(unsigned long addr, char *name, char *field, void* buf)
{
	unsigned long off = MEMBER_OFFSET(name, field);

	if (!readmem(addr + off, KVADDR, buf, MEMBER_SIZE(name, field), name, FAULT_ON_ERROR))
		return 0;
	return 1;
}

void do_cmd(void)
{
	unsigned long ops_addr, owner_addr;
	unsigned long list_addr, base, next;
	char name[64];

	optind++;
	base = next = list_addr = htol(args[optind], FAULT_ON_ERROR, NULL);

	do {
		// 由于list就是nf_hook_ops的第一个字段,因此就不炫技了,强转即可。
		//get_field(list_addr - MEMBER_OFFSET("nf_hook_ops", "list"), "list_head", "next", &ops_addr);
		ops_addr = list_addr = next;
		get_field(ops_addr, "nf_hook_ops", "owner", &owner_addr);
		get_field(owner_addr, "module", "name", &name[0]);
		if (list_addr != base)
			fprintf(fp, "--%s\n", name);
		} while(get_field(list_addr, "list_head", "next", &next) && next != base);
}

static struct command_table_entry command_table[] = {
	{ "nfhooks", do_cmd, NULL, 0},
	{ NULL },
};

void __attribute__((constructor)) nfhooks_init(void)
{
	register_extension(command_table);
}

void __attribute__((destructor)) nfhooks_fini(void) { }

编译命令如下:

gcc -fPIC -shared nfhooks.c -o nfhooks.so

将生成的so放在执行crash命令的目录下,在crash命令行加载之:

crash> extend nfhooks.so
./nfhooks.so: shared object loaded

随后就可以用了:

# 首先dump出INET IPv4的PREROUTING点的list地址
crash> px &nf_hooks[2][0]
$1 = (struct list_head *) 0xffffffff81a71000 <nf_hooks+256>
crash> nfhooks 0xffffffff81a71000
--bridge
--nf_defrag_ipv4
--iptable_raw
--nf_conntrack_ipv4
--iptable_mangle
--iptable_nat
crash>

是不是很有意思呢。

有了这个机制,就可以非常方便地使用readmem来进行Linux内核任意地址任意结构体字段的解析了,你就再也不用抱怨crash命令不够用了,不够用就自己写一个,而自己的写一个的成本非常低,无非就是readmem不断解析结构体,这一切的背后,只需要你对内核足够熟悉即可。

我之前写过一个手工解析/dev/mem或者vmcore的程序,但和crash插件扩展相比,太low太麻烦了,有了这个机制,以后再也不用干手工活儿了。

将上面的代码稍微扩展一下,就可以dump出所有协议族,所有hook点的所有模块名字了:

#include <crash/defs.h>

static int get_field(unsigned long addr, char *name, char *field, void* buf)
{
	unsigned long off = MEMBER_OFFSET(name, field);

	if (!readmem(addr + off, KVADDR, buf, MEMBER_SIZE(name, field), name, FAULT_ON_ERROR))
		return 0;
	return 1;
}

#define FAMILY	12
#define TYPE	8

struct dummy_list {
	struct dummy_list *next, *prev;
};

struct dummy_list *iter;
void do_cmd(void)
{
	unsigned long ops_addr, owner_addr;
	unsigned long list_addr, base, next;
	char name[64];
	int i, j;

	optind++;
	iter = (struct dummy_list *)htol(args[optind], FAULT_ON_ERROR, NULL);

	for (i = 0; i < FAMILY; i++) {
		for (j = 0; j < TYPE; j++) {
			fprintf(fp, "at PF: %d  hooknum: %d \n", i, j);
			base = next = list_addr = (unsigned long)&iter[i*TYPE + j];
			do {
				ops_addr = list_addr = next;
				get_field(ops_addr, "nf_hook_ops", "owner", &owner_addr);
				get_field(owner_addr, "module", "name", &name[0]);
				if (list_addr != base)
					fprintf(fp, "  ----%s\n", name);
				} while(get_field(list_addr, "list_head", "next", &next) && next != base);
		}
	}
}

static struct command_table_entry command_table[] = {
	{ "allnfhooks", do_cmd, NULL, 0},
	{ NULL },
};

void __attribute__((constructor)) nfhooks_init(void)
{
	register_extension(command_table);
}

void __attribute__((destructor)) nfhooks_fini(void) { }

是不是很简单呢?我从来不记录复杂的东西,简单的才是真美。

当前时间,周五晚10点45分左右。已经不在996的管辖范围,请继续举报!