项目中需要优化内核协议栈中某一函数,每次修改都得重新烧内核,麻烦的要死。
搜到一篇牛文
《动态替换Linux核心函数的原理和实现》,基于i386平台的,我改了改写了个powerpc版本的
一、准备工作
比如我要替换内核中的 i2c_get_adapter()
先找到它在内核中的EA(有效地址)
打开内核根目录的System.map文件 (也就是说你要改的内核函数必须EXPORT_SYMBOL过才行)
- c01f939c T i2c_transfer
-
c01f94c0 T i2c_master_send
-
c01f9520 T i2c_master_recv
-
c01f958c T i2c_get_adapter
-
c01f9658 T i2c_put_adapter
-
c01f967c t i2c_smbus_pec
-
c01f96d0 t i2c_smbus_msg_pec
-
c01f9728 T i2c_smbus_xfer
-
c01f9d40 T i2c_smbus_write_i2c_block_data
-
c01f9dac T i2c_smbus_read_i2c_block_data
-
c01f9e2c T i2c_smbus_write_block_data
找到了i2c_get_adapter()的EA地址为
c01f958c ,我们要改这个地址上的代码
二、编写替换代码没啥说的,上代码
- #include <linux/spinlock.h>
-
#include <linux/i2c.h>
-
#include <linux/types.h>
-
#include <linux/smp_lock.h>
-
#define CODESIZE 44
-
char code_buf[CODESIZE]; //用来存放内核被替换函数的原始代码
-
extern struct i2c_adapter* i2c_get_adapter(int id); //内核中此函数被EXPORT_SYMBOL过
-
typedef struct i2c_adapter* (*kernel_func_t)(int id);
-
kernel_func_t kernel_func_addr = (kernel_func_t)0xc01f958c; //我们要修改这个地址的代码
-
- struct i2c_adapter* new_func(int id) //我们自己的函数,用来替换i2c_get_adapter,形参列表要一致
-
{
-
printk("leonlalal\n");
-
return NULL;
-
}
-
-
void hack_init(void)//用来替换 i2c_get_adapter 实现
-
{
-
uint8_t run_code[CODESIZE] = //本文的核心所在 !!
-
"\x94\x21\xff\xf0" //stwu r1,-16(r1) //修改栈指针
-
"\x7c\x08\x02\xa6" //mflr r0 //保存 LR 到 r0
-
"\x90\x01\x00\x14" //stw r0,20(r1) //保存 r0 到 原栈地址的上部
-
-
"\x3d\x20\x00\x00" //lis r9 MY_ADDR@h //绿色部分放我们函数的地址
-
"\x61\x29\x00\x00" //ori r9, r9, MY_ADDR@l
-
"\x7d\x29\x03\xa6" //mtctr r9 //保存 r9 到 CTR
-
"\x4e\x80\x04\x21" //bctrl //跳转到CTR所存EA处执行
- //注意,LR be modified !
- //b跳转范围有限,所以我们用CTR跳转
-
-
"\x80\x01\x00\x14" //lwz r0,20(r1) //从原栈地址的上部 载入到 r0
-
"\x38\x21\x00\x10" //addi r1,r1,16 //修复栈指针
-
"\x7c\x08\x03\xa6" //mtlr r0 //保存 r0 到 LR
- "\x4e\x80\x00\x20"; //blr //跳到LR所存EA处执行
-
-
*(uint16_t*)(run_code+14) = ((uint32_t)new_func) >> 16; //函数地址的高16bit
-
*(uint16_t*)(run_code+18) = ((uint32_t)new_func); //函数地址的低16bit
-
-
lock_kernel();
-
memcpy(code_buf, (uint8_t *)kernel_func_addr, CODESIZE); //kernel code ---> code buf
-
memcpy(kernel_func_addr, (char*)&run_code, CODESIZE); //our code ---> kernel code
-
unlock_kernel();
-
-
printk("replace finished!\n");
-
return 0;
-
}
-
-
void hack_run(void) //用来测试,看看替换成功了没。若替换成功,此处就会调用new_func
- {
- i2c_get_adapter(1);
-
}
-
-
void hack_exit (void)//用来恢复 i2c_get_adapter 实现
-
{
-
lock_kernel();
-
memcpy(kernel_func_addr, code_buf, CODESIZE); //buf ---> kernel code
-
unlock_kernel();
-
printk("restore finished!\n");
-
}
把这两个函数加到你的模块中,通过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) |