使用hotfix技术来HOOK任意位置代码的手艺

假设一个内核函数,test,如下:

void test()
{
	condition = update_something();
	if (condition == 1) {
		M1();
	} else {
		return;
	}
}

现在需要统计在test中进入M1函数的次数,怎么办?

很简单,使用kpatch技术即可,或者,使用kprobe技术,亦可。但这些成熟的技术没有可玩性,它们属于工程的范畴,而不属于手艺。说白了,就是对于手艺人而言,kpatch,kprobe这种技术,没意思。

这里介绍一种有意思的方法,当然,这只是对于工人而不是对于经理来说的。

要点如下:

  • 单独分配一段足够大且可执行的内存p,将test函数整体拷贝过去。
  • 将原始test的开头5个字节替换成 32位相对跳转指令 ,即 0xe9 $rel_offset
  • 在p中找到需要插入统计计数的位置,增加下面的7个字节指令 asm (“incl 0xffffffff81977890” ::: );

0xffffffff81977890这个地址是内核中panic_on_oops变量的地址。

如果真的试着这样做了,发现绝大多数情况下,这是不行的,哪里错了呢?这里有两个问题需要注意:

  1. 如何分配一段足够的内存来承载拷贝过来的test函数的指令?
  2. 处理拷贝后的函数内相对偏移变更。

这两点其实很好理解。

首先,我们要明白,为了修改原始函数的前5个字节为相对跳转,新内存必须和原始函数test之间的间隔在s32类型可以表示的范围以内,也就是前后2G总共4G的范围。这一点,我们以do_fork为例:

0xffffffff81073840 <do_fork>:   nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffff81073845 <do_fork+5>: push   %rbp

可以发现,只有5个字节的空间。

所以,我们的新内存的位置必须确保在test前后2G的范围内。

显然,kmalloc很难满足这个需求,我们不妨换个思路,直接借用一个模块中的stub函数。但是考虑到模块中的stub函数可能大小并不够,所以我们采用属性定义其按照足够的大小对齐:

void test_stub1(void) __attribute__ ((aligned (1024)));
void test_stub2(void) __attribute__ ((aligned (1024)));
void test_stub1(void)
{
	printk("yes\n");
}
void test_stub2(void)
{
	printk("yes yes\n");
}

这样,test_stub1就有1024个字节可用了,一般的函数,足够了。我们只需要用test来覆盖这个test_stub1就可以了。通常情况下,模块中的函数是在0xffffffff00000000以上的,这个和内核函数处在相对跳转可以够得到的范围内。

此外,当我们在原始函数内部插入了inc指令后,会改变拷贝后的函数的相对偏移,甚至将原始函数指令拷贝到新的地方,也会改变原有的相对偏移,这个不得不察。

所以,必须扫描所有的拷贝后指令,修改其相对跳转偏移:

  • 函数内的相对偏移需改改变7个指令的增量。
  • 函数外的相对偏移在7个指令增量的前提额外需要改变p - test的增量。

采用这个方法插入一些统计计数,比如系统中半连接数量的统计计数,对于工人们来讲,还是很有意思的。


浙江温州皮鞋湿,下雨进水不会胖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值