我的小小要是能用钢琴弹出《二泉映月》,我就要努力用二胡拉出《卡农》!
最近写了三篇和网络技术无关的三篇文章:
Linux内核如何替换内核函数并调用原始函数 :https://blog.youkuaiyun.com/dog250/article/details/84201114
x86_64运行时动态替换函数的hotpatch机制 :https://blog.youkuaiyun.com/dog250/article/details/84258601
x86_64动态替换内核函数的hotpatch模块卸载问题 :https://blog.youkuaiyun.com/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个字节可能并不是一个完整的指令序列,有可能某些函数的开头是:
这样就需要覆盖函数的前6个字节,5个字节为相对跳转指令序列,外加一个0x90作为nop。因此我不得不在替换指令的时候,做一堆的if-else,switch-case的测试,以便决定到底替换多少指令。xx xx xx xx xx xx
非常幸运,我的内核启用了FTRACE相关的编译选项,因此所有的函数开头都是下面的序列:
或者:0x0f 0x1f 0x44 0x00 0x00
这种指令序列是类似nop的变体,就是专门让你做替换做32bit跳转hook用的,直接替换它们为32bit跳转序列即可。非常方便!要是每一条指令后面都有个这样的序列而不仅仅在函数开头,是不是就很容易实现单步了呢?当然,代价就是程序的大小翻倍!不过现在,内存貌似不值钱了。0x66 0x66 0x66 0x66 0x90
不过,还是老老实实用 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指针!
有点绕了…
一缕月光照当头,上下光而相凑,鸡屎也不流,用手殴,净肉球!
浙江温州皮鞋湿,下雨进水不会胖。