- 论坛徽章:
- 0
|
我用FreeBSD提供的内核内存接口kvm写了一个例子。作为示范,这个简单的例子仅仅打印了内核ipv4 radix路由表中的中间根节点的内容,只需稍加扩展,即可打印完整的路由表内容,相当于netstat命令。
使用kvm按符号地址探察内核的一些要点:
1、用kvm_openfiles()函数获取后续操作所需的文件描述符。
2、用kvm_nlist()函数获取所关心的符号在内核中的地址。
3、用kvm_read()函数逐步将所关心的内容从内核空间复制到用户空间。
只要你知道内核中的数据是如何组织的,就可以通过kvm接口一步一步地把它挖出来。我原来分析内核路由表的时候用的是内核调试的方法,相比之下,kvm要简单方便多了。详细资料请参考man 3 kvm。
在编译的时候,需要使用-lkvm选项链接kvm库。另外,这个程序执行的时候需要root权限。
- #include <fcntl.h>
- #include <kvm.h>
- #include <sys/param.h>
- #include <sys/socket.h>
- #include <net/route.h>
- /* 这个数组用于向kvm_nlist()函数传递需要查找其地址的所有内核符号,
- * 数组最后一个元素须设为NULL。
- */
- struct nlist nl[] = {
- { "_rt_tables"},
- { NULL },
- };
- /* 这个数组用于复制内核中的rt_tables[]数组的内容 */
- struct radix_node_head *rt_tables[AF_MAX+1];
- int main(void)
- {
- kvm_t *kvmd;
- char buf[100];
- struct radix_node_head v4head;
- struct radix_node rnode;
- /* 获取kvm文件描述符 */
- if ((kvmd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, buf)) == NULL) {
- printf("kvm_openfiles fail! %s\n", kvm_geterr(kvmd));
- exit(0);
- }
- /* 获取内核中的rt_tables符号的地址 */
- if (kvm_nlist(kvmd, nl) < 0) {
- printf("kvm_nlist fail! %s\n", kvm_geterr(kvmd));
- exit(0);
- }
- /* 将内核中的路由表头指针数组复制到rt_tables[]指针数组中 */
- if (kvm_read(kvmd, nl[0].n_value, &rt_tables, sizeof(rt_tables))
- != sizeof(rt_tables)) {
- printf("kvm_read rt_tables fail! %s\n", kvm_geterr(kvmd));
- exit(0);
- }
- /* 将内核中的ipv4路由表头节点复制到v4head结构体中 */
- if (kvm_read(kvmd, (int)rt_tables[AF_INET], &v4head, sizeof(v4head))
- != sizeof(v4head)) {
- printf("kvm_read v4 route headfail! %s\n", kvm_geterr(kvmd));
- exit(0);
- }
- printf("v4head.rnh_treetop = %p\n", v4head.rnh_treetop);
- /* 将内核中的ipv4路由表的中间根节点的内容复制到rnode结构体中 */
- if (kvm_read(kvmd, (int)v4head.rnh_treetop, &rnode, sizeof(rnode))
- != sizeof(rnode)) {
- printf("kvm_read v4 root node fail! %s\n", kvm_geterr(kvmd));
- exit(0);
- }
- printf("rnode.rn_mklist = %p\n", rnode.rn_mklist);
- printf("rnode.rn_parent = %p\n", rnode.rn_parent);
- printf("rnode.rn_bit = %d\n", rnode.rn_bit);
- printf("rnode.rn_bmask = %x\n", rnode.rn_bmask);
- printf("rnode.rn_flags = %x\n", rnode.rn_flags);
- printf("rnode.rn_offset = %d\n", rnode.rn_offset);
- printf("rnode.rn_left = %p\n", rnode.rn_left);
- printf("rnode.rn_right = %p\n", rnode.rn_right);
- }
复制代码
打印结果示例:
v4head.rnh_treetop = 0xc1afc24c
rnode.rn_mklist = 0xc1afb4b0
rnode.rn_parent = 0xc1afc24c
rnode.rn_bit = 32
rnode.rn_bmask = ffffff80
rnode.rn_flags = 6
rnode.rn_offset = 4
rnode.rn_left = 0xc1c5cc78
rnode.rn_right = 0xc1afc264
v4路由表头的rnh_treetop字段应该指向的是radix树的中间根节点,这里是0xc1afc24c。而我们下面所打印的也正是中间根节点,它的rn_parent字段应该指向它自己,即0xc1afc24c,结果正确。这个根节点的rn_bit表示它的bit测试位置,这里等于32,正是ipv4地址在socket地址结构体中的实际起始bit偏移量。字节掩码为0x80,也表示它测试的是最左面的那个bit。rn_flags等于6,这是由RNF_ROOT(2)和RNF_ACTIVE(4)相或的结果。rn_offset为4,表示ipv4地址在socket地址结构体中的实际起始位置的字节偏移为4,这和前面的32bit是相对应的。从上面的分析可以看出,通过kvm接口读出的ipv4路由表中间根节点的内容完全正确,这确实是一个探察内核空间的好方法。

[ 本帖最后由 雨丝风片 于 2006-4-30 13:07 编辑 ] |
|