Kprobe实现原理

news2024/11/16 5:38:12

kprobe其实就是将某个要检测的指令备份,再替换成int3(x86)或者未定义指令(arm)来触发异常,再调用对应体系的异常处理函数来执行我们自定义的hook,执行完我们自定义的hook,再将备份的指令放回原来的位置继续往下执行

下面我们就来看下linux内核版本为5.17.5的arm64的kprobe代码架构,首先看下probe这个结构体

struct kprobe {
    struct hlist_node hlist;

    /* list of kprobes for multi-handler support */
    struct list_head list;

    /*count the number of times this probe was temporarily disarmed */
    unsigned long nmissed;

    /* location of the probe point */
    kprobe_opcode_t *addr;

    /* Allow user to indicate symbol name of the probe point */
    const char *symbol_name;

    /* Offset into the symbol */
    unsigned int offset;

    /* Called before addr is executed. */
    kprobe_pre_handler_t pre_handler;

    /* Called after addr is executed, unless... */
    kprobe_post_handler_t post_handler;

    /* Saved opcode (which has been replaced with breakpoint) */
    kprobe_opcode_t opcode;

    /* copy of the original instruction */
    struct arch_specific_insn ainsn;

    /*
     * Indicates various status flags.
     * Protected by kprobe_mutex after this kprobe is registered.
     */
    u32 flags;
};

替换成未定义指令后,就会触发的未定义指令异常处理函数

void do_undefinstr(struct pt_regs *regs)
{
    /* check for AArch32 breakpoint instructions */
    if (!aarch32_break_handler(regs))
        return;

    if (call_undef_hook(regs) == 0)
        return;

    BUG_ON(!user_mode(regs));
    force_signal_inject(SIGILL, ILL_ILLOPC, regs->pc, 0);
}

里面的会调用call_undef_hook,这个函数的参数就是记录在堆栈中的寄存器组,下面一个函数我们也可以看到用到了程序计数器pc

static int call_undef_hook(struct pt_regs *regs)
{
    struct undef_hook *hook;
    unsigned long flags;
    u32 instr;
    int (*fn)(struct pt_regs *regs, u32 instr) = NULL;
    unsigned long pc = instruction_pointer(regs);

    if (!user_mode(regs)) {
        __le32 instr_le;
        if (get_kernel_nofault(instr_le, (__le32 *)pc))
            goto exit;
        instr = le32_to_cpu(instr_le);
    } else if (compat_thumb_mode(regs)) {
        /* 16-bit Thumb instruction */
        __le16 instr_le;
        if (get_user(instr_le, (__le16 __user *)pc))
            goto exit;
        instr = le16_to_cpu(instr_le);
        if (aarch32_insn_is_wide(instr)) {
            u32 instr2;

            if (get_user(instr_le, (__le16 __user *)(pc + 2)))
                goto exit;
            instr2 = le16_to_cpu(instr_le);
            instr = (instr << 16) | instr2;
        }
    } else {
        /* 32-bit ARM instruction */
        __le32 instr_le;
        if (get_user(instr_le, (__le32 __user *)pc))
            goto exit;
        instr = le32_to_cpu(instr_le);
    }

    raw_spin_lock_irqsave(&undef_lock, flags);
    list_for_each_entry(hook, &undef_hook, node)
        if ((instr & hook->instr_mask) == hook->instr_val &&
            (regs->pstate & hook->pstate_mask) == hook->pstate_val)
            fn = hook->fn;

    raw_spin_unlock_irqrestore(&undef_lock, flags);
exit:
    return fn ? fn(regs, instr) : 1;
}

从最后几行我们看到他会便利整个undef_hook,通过对比instr_mask等找到对应的undef_hook的fn:其实也就是 kprobe_trap_handler;

在系统初始化kprobe子系统时

static int __init init_kprobes(void)
{
    int i, err = 0;

    /* FIXME allocate the probe table, currently defined statically */
    /* initialize all list heads */
    for (i = 0; i < KPROBE_TABLE_SIZE; i++)
        INIT_HLIST_HEAD(&kprobe_table[i]);

    err = populate_kprobe_blacklist(__start_kprobe_blacklist,
                    __stop_kprobe_blacklist);
    if (err)
        pr_err("Failed to populate blacklist (error %d), kprobes not restricted, be careful using them!\n", err);

    if (kretprobe_blacklist_size) {
        /* lookup the function address from its name */
        for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
            kretprobe_blacklist[i].addr =
                kprobe_lookup_name(kretprobe_blacklist[i].name, 0);
            if (!kretprobe_blacklist[i].addr)
                pr_err("Failed to lookup symbol '%s' for kretprobe blacklist. Maybe the target function is removed or renamed.\n",
                       kretprobe_blacklist[i].name);
        }
    }

    /* By default, kprobes are armed */
    kprobes_all_disarmed = false;

#if defined(CONFIG_OPTPROBES) && defined(__ARCH_WANT_KPROBES_INSN_SLOT)
    /* Init 'kprobe_optinsn_slots' for allocation */
    kprobe_optinsn_slots.insn_size = MAX_OPTINSN_SIZE;
#endif

    err = arch_init_kprobes();
    if (!err)
        err = register_die_notifier(&kprobe_exceptions_nb);
    if (!err)
        err = register_module_notifier(&kprobe_module_nb);

    kprobes_initialized = (err == 0);
    kprobe_sysctls_init();
    return err;
}
early_initcall(init_kprobes);

会调用arch_init_kprobes来注册体系架构的kprobe的hook

static struct undef_hook kprobes_arm_break_hook = {
    .instr_mask    = 0x0fffffff,
    .instr_val    = KPROBE_ARM_BREAKPOINT_INSTRUCTION,
    .cpsr_mask    = MODE_MASK,
    .cpsr_val    = SVC_MODE,
    .fn        = kprobe_trap_handler,
};

#endif /* !CONFIG_THUMB2_KERNEL */

int __init arch_init_kprobes(void)
{
    arm_probes_decode_init();
#ifdef CONFIG_THUMB2_KERNEL
    register_undef_hook(&kprobes_thumb16_break_hook);
    register_undef_hook(&kprobes_thumb32_break_hook);
#else
    register_undef_hook(&kprobes_arm_break_hook);
#endif
    return 0;
}
kprobe_trap_handler会调用kprobe_handler
void __kprobes kprobe_handler(struct pt_regs *regs)
{
    struct kprobe *p, *cur;
    struct kprobe_ctlblk *kcb;

    kcb = get_kprobe_ctlblk();
    cur = kprobe_running();

#ifdef CONFIG_THUMB2_KERNEL
    /*
     * First look for a probe which was registered using an address with
     * bit 0 set, this is the usual situation for pointers to Thumb code.
     * If not found, fallback to looking for one with bit 0 clear.
     */
    p = get_kprobe((kprobe_opcode_t *)(regs->ARM_pc | 1));
    if (!p)
        p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc);

#else /* ! CONFIG_THUMB2_KERNEL */
    p = get_kprobe((kprobe_opcode_t *)regs->ARM_pc);
#endif

    if (p) {
        if (!p->ainsn.insn_check_cc(regs->ARM_cpsr)) {
            /*
             * Probe hit but conditional execution check failed,
             * so just skip the instruction and continue as if
             * nothing had happened.
             * In this case, we can skip recursing check too.
             */
            singlestep_skip(p, regs);
        } else if (cur) {
            /* Kprobe is pending, so we're recursing. */
            switch (kcb->kprobe_status) {
            case KPROBE_HIT_ACTIVE:
            case KPROBE_HIT_SSDONE:
            case KPROBE_HIT_SS:
                /* A pre- or post-handler probe got us here. */
                kprobes_inc_nmissed_count(p);
                save_previous_kprobe(kcb);
                set_current_kprobe(p);
                kcb->kprobe_status = KPROBE_REENTER;
                singlestep(p, regs, kcb);
                restore_previous_kprobe(kcb);
                break;
            case KPROBE_REENTER:
                /* A nested probe was hit in FIQ, it is a BUG */
                pr_warn("Failed to recover from reentered kprobes.\n");
                dump_kprobe(p);
                fallthrough;
            default:
                /* impossible cases */
                BUG();
            }
        } else {
            /* Probe hit and conditional execution check ok. */
            set_current_kprobe(p);
            kcb->kprobe_status = KPROBE_HIT_ACTIVE;

            /*
             * If we have no pre-handler or it returned 0, we
             * continue with normal processing. If we have a
             * pre-handler and it returned non-zero, it will
             * modify the execution path and no need to single
             * stepping. Let's just reset current kprobe and exit.
             */
            if (!p->pre_handler || !p->pre_handler(p, regs)) {
                kcb->kprobe_status = KPROBE_HIT_SS;
                singlestep(p, regs, kcb);
                if (p->post_handler) {
                    kcb->kprobe_status = KPROBE_HIT_SSDONE;
                    p->post_handler(p, regs, 0);
                }
            }
            reset_current_kprobe();
        }
    } else {
        /*
         * The probe was removed and a race is in progress.
         * There is nothing we can do about it.  Let's restart
         * the instruction.  By the time we can restart, the
         * real instruction will be there.
         */
    }
}

 上面这个函数我们可以看到,会调用kprobe的pre_handler和post_handler,这两个函数被我们实现好,装填进kprobe,并用register_kprobe注册到kprobe子系统;当注册好之后,系统执行到这个位置就会陷入异常,异常处理函数就会来处理我们的kprobe

int register_kprobe(struct kprobe *p)
{
    int ret;
    struct kprobe *old_p;
    struct module *probed_mod;
    kprobe_opcode_t *addr;

    /* Adjust probe address from symbol */
    addr = kprobe_addr(p);
    if (IS_ERR(addr))
        return PTR_ERR(addr);
    p->addr = addr;

    ret = warn_kprobe_rereg(p);
    if (ret)
        return ret;

    /* User can pass only KPROBE_FLAG_DISABLED to register_kprobe */
    p->flags &= KPROBE_FLAG_DISABLED;
    p->nmissed = 0;
    INIT_LIST_HEAD(&p->list);

    ret = check_kprobe_address_safe(p, &probed_mod);
    if (ret)
        return ret;

    mutex_lock(&kprobe_mutex);

    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;
    }

    cpus_read_lock();
    /* Prevent text modification */
    mutex_lock(&text_mutex);
    ret = prepare_kprobe(p);
    mutex_unlock(&text_mutex);
    cpus_read_unlock();
    if (ret)
        goto out;

    INIT_HLIST_NODE(&p->hlist);
    hlist_add_head_rcu(&p->hlist,
               &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);

    if (!kprobes_all_disarmed && !kprobe_disabled(p)) {
        ret = arm_kprobe(p);
        if (ret) {
            hlist_del_rcu(&p->hlist);
            synchronize_rcu();
            goto out;
        }
    }

    /* Try to optimize kprobe */
    try_to_optimize_kprobe(p);
out:
    mutex_unlock(&kprobe_mutex);

    if (probed_mod)
        module_put(probed_mod);

    return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1714081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

拍视频麦克风什么牌子好?户外无线麦克风哪个牌子好,看本期文章

​无线领夹麦克风&#xff0c;作为现代音频技术的重要代表&#xff0c;已经广泛应用于各种场合。它不仅能提高演讲者的声音质量&#xff0c;还能增加演讲的互动性和生动性。然而&#xff0c;面对市场上众多的无线领夹麦克风产品&#xff0c;如何选择一款适合自己的设备成为了一…

TPshop商城的保姆教程(windows)

提前准备 phpStudy下载&#xff1a;https://www.xp.cn/download.html 选择适合自己的版本下载 TPshop商城源文件下载链接&#xff1a; https://pan.baidu.com/s/143fLrxbwe9CTMCbyx7mXJQ?pwd6666 开始安装 安装完phpstudy后 以管理员的身份启动phpstudy.exe 选择合适自己…

C语言 指针——指针变量的定义、初始化及解引用

目录 指针 内存如何编址&#xff1f; 如何对变量进行寻址&#xff1f; 用什么类型的变量来存放变量的地址? 如何显示变量的地址?​编辑 使用未初始化的指针会怎样&#xff1f; NULL是什么&#xff1f; 如何访问指针变量指向的存储单元中的数据&#xff1f; 指针变量的…

二刷算法训练营Day15 | 二叉树(2/9)

目录 详细布置&#xff1a; 1. 层序遍历 2. 226. 翻转二叉树 3. 101. 对称二叉树 详细布置&#xff1a; 1. 层序遍历 昨天练习了几种二叉树的深度优先遍历&#xff0c;包括&#xff1a; ​​​​​​前中后序的递归法前中后序的迭代法前中后序迭代的统一写法 今天&…

糖尿病视网膜病变分级新方法:卷积网络做分割和诊断 + 大模型生成详细的测试和治疗建议

糖尿病视网膜病变分级新方法&#xff1a;卷积网络做分割和诊断 大模型生成详细的测试和治疗建议 提出背景相关工作3.1 数据集3.1.1 病变分割 3.1.2 图像分级3.1.3 大型语言模型&#xff08;LLMs&#xff09; 解法 数据预处理 数据增强 网络架构 训练过程 测试过程子解法1…

spdlog日志库源码:线程池thread_pool

线程池 线程池本质上一组事先创建的子线程&#xff0c;用于并发完成特定任务的机制&#xff0c;避免运行过程中频繁创建、销毁线程&#xff0c;从而降低程序运行效率。通常&#xff0c;线程池主要涉及到以下几个方面问题&#xff1a; 如何创建线程池&#xff1f;线程池如何执…

FPGA基础:触发器和锁存器

目录 锁存器&#xff08;Latch&#xff09;D触发器&#xff08;Flip-Flop&#xff09;最基本时序电路时序块&#xff08;Sequential blocks&#xff09;:同步与异步触发器概念触发器分类触发器的Verilog实现1. 上升沿触发的触发器2. 带异步复位、上升沿触发的触发器3. 带异步复…

Pandas-中axis的用法

在Pandas中&#xff0c;min(axis)方法是计算DataFrame或Series中每行或每列的最小值的函数。该函数可以接受一个参数axis&#xff0c;用于指定计算最小值的方向。当axis0时&#xff0c;表示沿着行的方向计算最小值&#xff1b;当axis1时&#xff0c;表示沿着列的方向计算最小值…

奶奶也能看懂的耦合协调度分析

不会计算&#xff1f;跟着文献学起来~ 案例数据连接&#xff08;复制链接后粘贴到浏览器中&#xff09;&#xff1a; 耦合协调度数据​spssau.com/spssaudata.html?shareDataF363000CD033FF15E557BB75B9B0D412 假如你有这样一组数据&#xff1a; 如何进行计算分析耦合协调度…

python中pow是什么意思

pow()方法返回xy&#xff08;x的y次方&#xff09;的值。 语法 以下是math模块pow()方法的语法&#xff1a; import math math.pow( x, y ) 内置的pow()方法 pow(x, y[, z]) 函数是计算x的y次方&#xff0c;如果z在存在&#xff0c;则再对结果进行取模&#xff0c;其结果等效…

【后端开发】服务开发场景之分布式(CAP,Raft,Gossip | API网关,分布式ID与锁 | RPC,Dubbo,Zookeeper)

【后端开发】服务开发场景之分布式&#xff08;CAP&#xff0c;Raft&#xff0c;Gossip | API网关&#xff0c;分布式ID与锁 | RPC&#xff0c;Dubbo&#xff0c;Zookeeper&#xff09; 文章目录 1、如何设计一个分布式系统&#xff1f;&#xff08;底层原理&#xff09;理论&a…

企业网站有必要进行软件测试吗?网站测试有哪些测试流程?

企业网站在现代商业中扮演着重要的角色&#xff0c;它不仅是企业形象的重要体现&#xff0c;也是与客户、合作伙伴进行沟通与交流的重要渠道。然而&#xff0c;由于企业网站的复杂性和关键性&#xff0c;其中可能存在各种潜在的问题和隐患。因此&#xff0c;对企业网站进行软件…

网页中的音视频裁剪拼接合并

一、需求描述 项目中有一个配音需求&#xff1a; 1&#xff09;首先&#xff0c;前台会拿到一个英语视频&#xff0c;视频的内容是A和B用英语交流&#xff1b; 2&#xff09;然后&#xff0c;用户可以选择为某一个角色配音&#xff0c;假如选择为A配音&#xff0c;那么视频在播…

Centos7时区设置及手动修改时间

一、修改系统时区 1、查看时区命令 timedatectl 2、设置时区命令 #下面将时区设置为上海时区 timedatectl set-timezone Asia/Shanghai 3、查看时区看一下新时区有没有生效 timedatectl 二、手动修改系统时间 修改系统时间 date -s "2023-12-25 16:05:10" 查…

display: none 和 visibility: hidden 的共性与区别

display: none 和 visibility: hidden 的共性与区别 共性&#xff1a;display: none 和 visibility: hidden 都是用于设置元素可见性的样式 区别 display: none 使元素及其占位完全消失&#xff1a;元素及其所有子元素将从文档流和布局中完全消失&#xff0c;就像它们不存在一…

对于高速信号完整性,一块聊聊啊(17)

再来对前仿和后仿的仿真内容回顾一下&#xff1a; 从概念上有个根本的理解 前仿真又可以分为布局前仿真和布局后仿真。前者是在设计的最初阶段&#xff0c;建立和验证详细的电气拓扑结构并以此制定出详细的约束规则。后者是在布局完成的状态下&#xff0c;在布线过程中遇到的…

论文阅读:Correcting Motion Distortion for LIDAR HD-Map Localization

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;http://arxiv.org/pdf/2308.13694.pdf 代码地址&#xff1a;https://github.com/mcdermatt/VICET 概要 激光雷达的畸变矫正是一个非常重要的工作。由于扫描式激光雷达传感器需要有限的时间来创建…

python如何安装tar.gz

首先我们到官网下载tar.gz。 然后解压我们下载的pip-9.0.1文件&#xff0c;我的解压后放在d&#xff1a;/p下 运行cmd&#xff0c;输入cd d:\p&#xff0c;按回车键&#xff0c;随后再次输入d: 在d:\p>的光标处输入pip-9.0.1\setup.py install&#xff0c;然后按回车键。 最…

图片转excel表格工具的工具,分享3个专业的识别软件!

在数字化时代&#xff0c;我们时常面临将图片中的表格数据转换为可编辑的Excel表格的需求。无论是工作中的数据整理&#xff0c;还是学习中的资料汇总&#xff0c;这一需求都显得尤为迫切。幸运的是&#xff0c;市面上已经涌现出众多优秀的图片转Excel表格工具&#xff0c;它们…

长方形边框 上方中间有缺口 css

<div class"text_6">大234234师掌4234柜</div><div class"text-wrapper_1"><span class"paragraph_1">四川慧创云戈科技有限公司推出的“大师掌柜”&#xff0c;是一个以餐饮外卖为切入口&#xff0c;专注实体小店新零售…