x86_64体系结构动态替换内核函数hotpatch之完结篇

版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! https://blog.csdn.net/dog250/article/details/84572893

我的小小要是能用钢琴弹出《二泉映月》,我就要努力用二胡拉出《卡农》!


最近写了三篇和网络技术无关的三篇文章:
Linux内核如何替换内核函数并调用原始函数https://blog.csdn.net/dog250/article/details/84201114
x86_64运行时动态替换函数的hotpatch机制https://blog.csdn.net/dog250/article/details/84258601
x86_64动态替换内核函数的hotpatch模块卸载问题https://blog.csdn.net/dog250/article/details/84555025
主要目的是总结一下碰到的令人蛋疼的问题。

实在是有点恶心了,昨天我就说,这个到此为止了,不搞了,真的搞恶心了。然而昨天那篇文章在写完后发现遗漏了几个细节,我又不想修改原文将问题掩盖掉让我忘记曾经是遗忘过那些问题的,所以今天写最后一篇总结性的文章,作为备忘给以后的自己看看,以便以后万一再做这个时,能回忆起一些思考的过程。

  • 细节1:使用读写锁的text poke机制依然存在问题
    参见《x86_64动态替换内核函数的hotpatch模块卸载问题》。
    非常简单的case,如果我在成功注册kprobe的时候,有CPU的thread进入了hook函数的中间,岂不是在该thread退出hook函数的时候,在没有read_lock(因为进入函数的时候kprobe还没有注册)的前提下调用了read_unlock?这会搞乱rwlock的计数器的!
    所以说,还要引入复杂度。即定义一个percpu的全局变量数组,其元素为一个atomic类型,这个percpu变量是为了控制每个CPU上只有read lock调用过才能read unlock。该变量初始值为0,进入hold这个前处理函数时执行inc,在post后处理中只有该atomic非0时才调用unlock!
    static int hold(...)
    {
    	atomic_t var = per_cpu(hook_var, this_cpu);
    	inc(var);
    	read_lock(&hook_lock);
    	return 0;
    }
    
    static int release(...)
    {
    	atomic_t var = per_cpu(hook_var, this_cpu);
    	if (var) {
    		read_unlock(&hook_lock);
    	}
    	return 0;
    }
    static void hotpatch_poke_text(...)
    {
    	register(&probe); // 借用kprobe的pre/post机制
    	// 大概等待一个“函数执行完毕的最长间隔”,以防止没有read lock
    	// 的thread在post后处理执行前被当前CPU抢掉读写锁。
    	usleep(..);  
    	write_lock(&hook_lock);
    ...
    
  • 细节2:启用了ftrace编译选项的函数前5个字节
    原理上,32bit相对跳转指令序列只需要5个字节,但是函数的前5个字节可能并不是一个完整的指令序列,有可能某些函数的开头是:
    xx xx xx
    xx xx xx
    
    这样就需要覆盖函数的前6个字节,5个字节为相对跳转指令序列,外加一个0x90作为nop。因此我不得不在替换指令的时候,做一堆的if-else,switch-case的测试,以便决定到底替换多少指令。
    非常幸运,我的内核启用了FTRACE相关的编译选项,因此所有的函数开头都是下面的序列:
    0x0f 0x1f 0x44 0x00 0x00
    
    或者:
    0x66 0x66 0x66 0x66 0x90
    
    这种指令序列是类似nop的变体,就是专门让你做替换做32bit跳转hook用的,直接替换它们为32bit跳转序列即可。非常方便!要是每一条指令后面都有个这样的序列而不仅仅在函数开头,是不是就很容易实现单步了呢?当然,代价就是程序的大小翻倍!不过现在,内存貌似不值钱了。
    不过,还是老老实实用 while(!OK) {stop_machine} 的版本吧!
  • 细节3:nop指令的多样性
    是的,nop不仅仅只有0x90,它有很多变体,比如1字节的,2字节的,3字节的…满足你各种长度的替换操作!
  • 细节4:最直接的安全卸载hook模块的方案
    折腾了这么久,是不是把问题想复杂了呢?其实在模块的exit函数中,直接调用text_poke_smp来恢复函数的前几个字节,出现程序跑飞的概率非常低,那么完全可以这么玩。
    此外,如果担心hook函数的text被free掉,那么在恢复替换完成后,sleep一段足够的时间,保证所有在替换当时的thread均已经出去该函数,就OK了,至于到底sleep多久,预估一个函数执行时间的经验值乘以2应该是安全的!

这是最后一篇关于hotpatch的了。但今后也不一定会写网络方面的。

所以,一个人到底要干什么其实是无法预知的,一切都只是概率。会有三个自己出现:

  • 别人眼中的你自己
  • 自己受到别人影响后认识的你自己
  • 不受任何外界影响的你自己

今天和同事讨论关于“皮鞋湿,不会胖”的意义,其实没有意义,我把它当作发语词了。最后就聊到了“无”和“空”的区别,“无”就是“所有”,可以任意发挥,“空”就是“空”,确定性的“什么都没有”。

关于皮鞋,皮鞋只是瞬间情境的载体,与识别性以及语言无关,不能纠结于“皮鞋在句中的作用到底是什么?”,皮鞋的意义就是“无”。类似“物哀”和“禅宗公案”,包括“尼采为什么发疯”的答案,其实都涵盖了一个意思,即在解释“什么是意义”。

练过斗鸡眼的人都明白,开始的时候,你要用手指竖立在两眼之间,然后两眼盯着指尖,再往后不需要手指了,想象指尖的位置有一粒灰尘,就能成功对眼,但是到最后,那粒灰尘也不需要了,也能成功!为什么需要那粒根本就不存在的灰尘呢?虽然它本来就不存在,但是确实帮助你成功对眼。

同事给出关于“无”和“空”的一个神例:

char *p1;
char *p2 = NULL;

万恶的C指针!

有点绕了…

一缕月光照当头,上下光而相凑,鸡屎也不流,用手殴,净肉球!

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

猜你喜欢

转载自blog.csdn.net/dog250/article/details/84572893