突然回想起了往事,那是2007年的冬天的一个周五,我在看我的老湿调试Linux协议栈的IP层,只见他修改了路由查找的逻辑,然后直接make install了一下就即时生效了,当时我只知道的是,修改了这个逻辑需要重新编译内核,而他并没有重新编译,好像只是编译了一个文件...编译内核这个耗时又无聊的工作阻碍了我对Linux内核的探索进度,直到今天,我依然对编译内核有相当的恐惧,不怕出错,而是怕磁盘空间不够,initrd的组装拆解之类,太繁琐了。我之所以知道2007年的那天是周五,是因为第二天我要加班,没有谁逼我,我自愿的,因为我想知道师父是怎么做到不重新编译内核就能改变非模块的内核代码处理逻辑的,第二天的收获很多,不但知道了他使用了“镜像协议栈”,还额外赚了一天的加班费,我还记得周六加完班我和老婆去吃了一家叫做石工坊的羊排火锅,人家赠送了一只绿色的兔子玩偶。现在那个玩偶还在,我家小小特别喜欢,就是这么一堆看似无关却又巧合的事,让我在这个周末觉得必须写下一点什么。 好吧,从kprobe开始吧。如果我面试一个搞Linux内核的人,问他怎么调试内核,他回答先加入printk然后重新编译最后载入新内核运行,看dmesg,我会让他先等上几分钟,然后人事就会告诉他让他回去等通知。幸运的是,我没有碰到这样的人让我面试来展现我五十步笑百步的半瓶子晃荡作风,也从来没有碰到过如此不仁慈的面试者,我曾经在一次找工作的时候真的就是这么说的,人家也真的让我去等通知,然而我真的就等到了通知,通知入职的时间以及体检事宜...说这些的目的是想展示一个调试内核的利器,kprobe。它可以动态修改内核地址空间代码的二进制指令,然后执行任意你想让它执行的代码段,这也许应该可以称为二进制动态编程!多么黑的技术,完全无视源代码的逻辑,完全无视编译器的苦功,直接就这么把二进制机器码给改了。 kprobe的工作原理很简单,比如你有一个函数func,你可以在func被调用前和调用后各插入一段代码,我们假设func指令是 begin
go
end kprobe要做的就是替换掉begin,将其变为: jmp prefunc 当然在替换前还要保存原有的,以便执行完我们的钩子函数prefunc还能跳回原来的逻辑,至于复杂的jmp细节(长短跳,相对绝对跳之类的)以及Intel的INT 3调试模式单步模式本文不再赘述,赘这个字用得好,因为所有这些细节都是累赘,你换个非Intel平台的话,你就知道这些是多么累赘了,不过对一辈子不换平台的那些人来讲,理解这些细节就成了资本,因此想了解这些,还是去看雪吧,找级别高态度好的问,或者潜水也行,我觉得看雪的信息量已经够大了,基本上都能找到现成的。 虽然我不提倡在本文讲Intel的细节,但是有一个除外,那就是prefunc钩子函数的参数问题,比如我想钩住vfs_write函数,它的声明如下: