设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 创业者 手机 数据
当前位置: 首页 > 服务器 > 系统 > 正文

Linux 内核动态追踪技术的完成

发布时间:2021-11-22 14:37 所属栏目:52 来源:互联网
导读:之前的文章介绍了基于 tracepoint 静态追踪技术的实现,本文再介绍基于 kprobe 的动态追踪即使的实现。同样,动态追踪也是排查问题的利器。 kprobe 是内核提供的动态追踪技术机制,它允许动态安装内核模块的方式安装系统钩子,非常强大。下面先看一个内核中的
之前的文章介绍了基于 tracepoint 静态追踪技术的实现,本文再介绍基于 kprobe 的动态追踪即使的实现。同样,动态追踪也是排查问题的利器。
 
kprobe 是内核提供的动态追踪技术机制,它允许动态安装内核模块的方式安装系统钩子,非常强大。下面先看一个内核中的例子。
 
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
 
#define MAX_SYMBOL_LEN  64
// 要 hanck 的内核函数名
static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
module_param_string(symbol, symbol, sizeof(symbol), 0644);
static struct kprobe kp = {
    .symbol_name    = symbol,
};
 
// 执行系统函数前被执行的钩子
static int __kprobes handler_pre(struct kprobe *p, struct pt_regs *regs){
    // ...
}
 
// 执行系统函数的单条指令后执行的钩子(不是执行完系统函数)
static void __kprobes handler_post(struct kprobe *p, struct pt_regs *regs,
                unsigned long flags){
    // ...
}
 
// 钩子执行出错或者单条执行执行出错时被执行函数static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr){
    // ...
}
 
static int __init kprobe_init(void){
    int ret;
    // 设置钩子
    kp.pre_handler = handler_pre;
    kp.post_handler = handler_post;
    kp.fault_handler = handler_fault;
    // 安装钩子
    register_kprobe(&kp);
    return 0;
}
 
static void __exit kprobe_exit(void){
    unregister_kprobe(&kp);
    pr_info("kprobe at %p unregistered\n", kp.addr);
}
 
// 安装进内核后的初始化和注销函数
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
设置完 kprobe 后,通过 register_kprobe 注册到内核。
 
int register_kprobe(struct kprobe *p){
    int ret;
    struct kprobe *old_p;
    struct module *probed_mod;
    kprobe_opcode_t *addr;
 
    // 通过系统函数名找到对应的地址,内核维护了这个数据
    addr = kprobe_addr(p);
    // 记录这个地址
    p->addr = addr;
    p->flags &= KPROBE_FLAG_DISABLED;
    p->nmissed = 0;
    INIT_LIST_HEAD(&p->list);
    // 之前是否已经存在钩子,是的话就插入存在的列表,否则插入一个新的记录
    old_p = get_kprobe(p->addr);
    if (old_p) {
        /* Since this may unoptimize old_p, locking text_mutex. */
        ret = register_aggr_kprobe(old_p, p);
        goto out;
    }
    // 把被 hack 的系统函数的指令保存到 probe 结构体,因为下面要覆盖这块内存
    /*
        prepare_kprobe =>
            unsigned long addr = (unsigned long) p->addr;
            unsigned long *kprobe_addr = (unsigned long *)(addr & ~0xFULL);
            memcpy(&p->opcode, kprobe_addr, sizeof(kprobe_opcode_t));
            memcpy(p->ainsn.insn, kprobe_addr, sizeof(kprobe_opcode_t));
    */
    ret = prepare_kprobe(p);
 
    INIT_HLIST_NODE(&p->hlist);
    // 插入内核维护的哈希表
    hlist_add_head_rcu(&p->hlist,
               &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
    // hack 掉系统函数所在内存的内容
    arm_kprobe(p);
}
注册一个 probe,首先是通过被 hack 的函数名找到对应的地址,然后保存这个地址对应内存的信息,接着把 probe 插入哈希表,最后调用 arm_kprobe 函数 hack 掉系统函数所在内存的内容。看一下 arm_kprobe。
 
void arch_arm_kprobe(struct kprobe *p){
    // #define INT3_INSN_OPCODE 0xCC
    u8 int3 = INT3_INSN_OPCODE;
    // 把 int3 的内存复制到 addr
    text_poke(p->addr, &int3, 1);
    text_poke_sync();
    perf_event_text_poke(p->addr, &p->opcode, 1, &int3, 1);
}
0xCC 是 intel 架构下 int3 对应的指令。所以这里就是把被 hack 函数对应指令的前面部分改成 int3。完成 hack。当执行到系统函数的时候,就会执行 int3,从而触发 trap,并执行对应的处理函数 do_int3(这里比较复杂,我也没有深入分析,大概是这个流程)。
 
static bool do_int3(struct pt_regs *regs){
    kprobe_int3_handler(regs);}int kprobe_int3_handler(struct pt_regs *regs){
    kprobe_opcode_t *addr;
    struct kprobe *p;
    struct kprobe_ctlblk *kcb;
    addr = (kprobe_opcode_t *)(regs->ip - sizeof(kprobe_opcode_t));
 
    kcb = get_kprobe_ctlblk();
    // 通过地址从 probe  哈希表拿到对应的 probe 结构体
    p = get_kprobe(addr);
 
    set_current_kprobe(p, regs, kcb);
    kcb->kprobe_status = KPROBE_HIT_ACTIVE;
 
    // 执行 pre_handler 钩子  
    if (!p->pre_handler || !p->pre_handler(p, regs))
        setup_singlestep(p, regs, kcb, 0);
}
执行完。pre_handler 钩子后,会通过 setup_singlestep 设置单步执行 flag。
 
static void setup_singlestep(struct kprobe *p, struct pt_regs *regs,
                 struct kprobe_ctlblk *kcb, int reenter){
    // 修改寄存器的值
    // 设置 eflags 寄存器的 tf 位,允许单步调试
    regs->flags |= X86_EFLAGS_TF;
    regs->flags &= ~X86_EFLAGS_IF;
    // 设置下一条指令为系统函数的指令
    if (p->opcode == INT3_INSN_OPCODE)
        regs->ip = (unsigned long)p->addr;
    else
        regs->ip = (unsigned long)p->ainsn.insn;
}
setup_singlestep 首先设置了允许单步调试,也就是说执行下一条指令后会触发一个 trap,从而执行一个处理函数。并设置了下一条指令为被 hack 函数对应的指令,这是在注册 probe 时保存下来的。触发单步调试的 trap 后,最终会执行到 kprobe_debug_handler
 
int kprobe_debug_handler(struct pt_regs *regs){
    struct kprobe *cur = kprobe_running();
    struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
    // 恢复指令为系统函数的指令
    resume_execution(cur, regs, kcb);
    regs->flags |= kcb->kprobe_saved_flags;
    // 执行 post 钩子
    if ((kcb->kprobe_status != KPROBE_REENTER) && cur->post_handler) {
        kcb->kprobe_status = KPROBE_HIT_SSDONE;
        cur->post_handler(cur, regs, 0);
    }

(编辑:ASP站长网)

    网友评论
    推荐文章
      热点阅读