Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1597127
  • 博文数量: 92
  • 博客积分: 2002
  • 博客等级: 大尉
  • 技术积分: 4717
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-01 17:09
文章分类

全部博文(92)

文章存档

2013年(1)

2012年(6)

2011年(85)

分类: LINUX

2011-05-25 14:21:06


项目中需要优化内核协议栈中某一函数,每次修改都得重新烧内核,麻烦的要死。
搜到一篇牛文《动态替换Linux核心函数的原理和实现》,基于i386平台的,我改了改写了个powerpc版本的


一、准备工作

比如我要替换内核中的 i2c_get_adapter()
先找到它在内核中的EA(有效地址)
打开内核根目录的System.map文件 (也就是说你要改的内核函数必须EXPORT_SYMBOL过才行)
  1. c01f939c T i2c_transfer
  2. c01f94c0 T i2c_master_send
  3. c01f9520 T i2c_master_recv
  4. c01f958c T i2c_get_adapter
  5. c01f9658 T i2c_put_adapter
  6. c01f967c t i2c_smbus_pec
  7. c01f96d0 t i2c_smbus_msg_pec
  8. c01f9728 T i2c_smbus_xfer
  9. c01f9d40 T i2c_smbus_write_i2c_block_data
  10. c01f9dac T i2c_smbus_read_i2c_block_data
  11. c01f9e2c T i2c_smbus_write_block_data
找到了i2c_get_adapter()的EA地址为 c01f958c ,我们要改这个地址上的代码

二、编写替换代码


没啥说的,上代码
  1. #include <linux/spinlock.h>
  2. #include <linux/i2c.h>
  3. #include <linux/types.h>
  4. #include <linux/smp_lock.h>
  5. #define CODESIZE 44 

  6. char code_buf[CODESIZE];       //用来存放内核被替换函数的原始代码
  7. extern struct i2c_adapter* i2c_get_adapter(int id);       //内核中此函数被EXPORT_SYMBOL过

  8. typedef struct i2c_adapter* (*kernel_func_t)(int id);
  9. kernel_func_t kernel_func_addr = (kernel_func_t)0xc01f958c; //我们要修改这个地址的代码

  10. struct i2c_adapter* new_func(int id)  //我们自己的函数,用来替换i2c_get_adapter,形参列表要一致
  11. {
  12.     printk("leonlalal\n");
  13.     return NULL;
  14. }

  15. void hack_init(void)//用来替换 i2c_get_adapter 实现
  16. {
  17.     uint8_t run_code[CODESIZE] =  //的核心所在 !!
  18.             "\x94\x21\xff\xf0"    //stwu r1,-16(r1)        //修改栈指针
  19.             "\x7c\x08\x02\xa6"    //mflr r0               //保存 LR 到 r0
  20.              "\x90\x01\x00\x14"   //stw  r0,20(r1)        //保存 r0 到 原栈地址的上部

  21.             "\x3d\x20\x00\x00"    //lis  r9  MY_ADDR@h   //绿色部分放我们函数的地址
  22.             "\x61\x29\x00\x00"    //ori  r9, r9, MY_ADDR@l
  23.             "\x7d\x29\x03\xa6"    //mtctr r9             //保存 r9 到 CTR
  24.             "\x4e\x80\x04\x21"    //bctrl                //跳转到CTR所存EA处执行
  25.                                                          //注意,LR be modified !
  26.                                                          //b跳转范围有限,所以我们用CTR跳转

  27.             "\x80\x01\x00\x14"    //lwz  r0,20(r1)         //从原栈地址的上部 载入到 r0
  28.             "\x38\x21\x00\x10"    //addi r1,r1,16          //修复栈指针
  29.             "\x7c\x08\x03\xa6"    //mtlr r0                //保存 r0 到 LR
  30.             "\x4e\x80\x00\x20";   //blr                    //跳到LR所存EA处执行

  31.     *(uint16_t*)(run_code+14) = ((uint32_t)new_func) >> 16;  //函数地址的高16bit
  32.     *(uint16_t*)(run_code+18) = ((uint32_t)new_func);        //函数地址的低16bit

  33.     lock_kernel();
  34.     memcpy(code_buf, (uint8_t *)kernel_func_addr, CODESIZE); //kernel code ---> code buf
  35.     memcpy(kernel_func_addr, (char*)&run_code, CODESIZE);    //our code ---> kernel code
  36.     unlock_kernel();

  37.     printk("replace finished!\n");
  38.     return 0;
  39. }

  40. void hack_run(void //用来测试,看看替换成功了没。若替换成功,此处就会调用new_func
  41. {     
  42.    i2c_get_adapter(1);
  43. }

  44. void hack_exit (void)//用来恢复 i2c_get_adapter 实现
  45. {
  46.     lock_kernel();
  47.     memcpy(kernel_func_addr, code_buf, CODESIZE)//buf ---> kernel code
  48.     unlock_kernel();
  49.     printk("restore finished!\n");
  50. }
把这两个函数加到你的模块中,通过ioctl什么的调这两个函数就可以实现动态修改/复原内核函数实现
调试时发现,在hack_init()和hack_exit中,如果你调用i2c_get_adapter(1),不会调用最新的函数实现,具体原因有空再查 ⊙﹏⊙

三、缺点


优点就不说了,只说说此种替换内核函数方法的缺点:
1.与硬件平台相关,因为使用了汇编....
2.与特定的linux内核相关,即使同一版本的linux内核,修改后的它的System.map也不一样....
3.所替换的函数必须在内核代码中被EXPORT_SYMBOL过.....

其实还有一些其他方法来动态替换内核函数,与硬件平台无关的
比如,文件操作时,替换file_operations钩子函数的指针
      网络处理时,在钩子函数链表中加一个函数特殊处理一下。。。。

处理方法视情况而定,百度搜《系统调用劫持》一大堆。




-------------------------------- 华丽的分割线 -------------------------------------
PowerPC GPR通用寄存器详解
Reg     Class       Notes
r0      local       commonly used to hold the old link register when building the stack frame
r1      dedicated   stack pointer
r2      dedicated   table of contents pointer
r3      local       commonly used as the return value of function, and also the 1st argument
r4–r10 local       commonly used to send in arguments 2 through 8 into a function
r11–r12 local      
r13–r31 global     

LR      dedicated   link register; cannot be used as a general register.
                    Use mflr (move from link register) or
                    mtlr (move to link register) to get at, e.g., mtlr r0
CR      dedicated   condition register


r0 - r31都是32位长度的寄存器,其中使用上各有区别:

r0       通常用来存储 连接寄存器 lr的值
r1       堆栈指针--通俗点叫sp,程序在内存中运行时当前的地址指针
r2       目录表的指针?
r3 - r10  形参,r3是函数第一个参数,r4是函数第二个参数,依次类推...,r3也用在函数返回值时。



阅读(5449) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

leonwang2022012-01-16 10:20:42

kaijile: 32位和64位都一样?.....
不一样。
因为此方法使用了汇编,CPU或者OS改变了,代码就得跟着变

kaijile2012-01-15 23:39:49

32位和64位都一样?