当前时间,周五晚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
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
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的管辖范围,请继续举报!
浙江温州皮鞋湿,下雨进水不会胖。