Kprobe实现原理

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web题解 Easy_SQLi or 雏形系统 (解题方法思想)

1.Easy_SQLi 1&#xff09;打开题目环境&#xff0c;如下是一个类似弱密码的格式&#xff0c;但是它又说是sql&#xff0c;还是按sql注入来 2&#xff09;.这里我尝试判断它的注入类型&#xff0c;但是一只不对&#xff0c;我便想着用万能密码试试&#xff0c;怎料直接登录成功…

Tableau解包与版本兼容性

Tableau解包与版本兼容性 1、背景描述2、Tableau解包3、Tableau版本兼容性 1、背景描述 有时&#xff0c;在使用Tableau Desktop打开.twbx打包工作簿时&#xff0c;可能会出现如下弹框&#xff1a; 通常考虑以下两种处理情况 2、Tableau解包 解包打包的.twbx工作簿&#xff0c…

电脑显示由于找不到msvcr110.dll 无法继续执行如何处理?最简单的修复msvcr110.dll文件方法

电脑显示由于找不到msvcr110.dll 无法继续执行&#xff1f;当你看到这种提示的时候&#xff0c;请不要紧张&#xff0c;这种是属于dll文件丢失&#xff0c;解决起来还是比较简单的&#xff0c;下面会详细的列明多种找不到msvcr110.dll的解决方法。 一.找不到msvcr110.dll是怎么…

HAL库+LWIP+LAN8720+热插拔

定时任务中&#xff0c;查询LAN8720的状态寄存器 PHY_BSR 0x01&#xff0c;成功读取后&#xff0c;检查16位数据的BIT2&#xff0c;即可获取网线连接状态 uint32_t phyreg 0;if(HAL_ETH_ReadPHYRegister(&g_eth_handler, PHY_BSR, &phyreg) HAL_OK){if(((phyreg >…

【Python Cookbook】S01E01 将长度为N的序列分解为N个单独的变量

目录 问题解决方案讨论 问题 将一个包含 N N N 个元素的元组或者序列&#xff0c;现在想将其分解为 N N N 个单独的变量。 解决方案 任何序列都可以通过简单的赋值操作分解为单独的变量&#xff1a; p (4, 5) x, y p print("x", x) print("y", y)唯…

v4l2抓取rv1126图像

0.准备工作 本文是基于正点原子的rv1126开发板使用mx415摄像头对不同节点的图像进行抓取 1.数据流向 图1 mx415采集到的数据为原始的拜尔格式&#xff08;也就是raw格式&#xff09;&#xff0c;我们需要通过isp进行图像的调节才符合视觉&#xff0c;其中isp和ispp是两个处理的…

智能监控技术助力山林生态养鸡:打造智慧安全的养殖新模式

随着现代科技的不断发展&#xff0c;智能化、自动化的养殖方式逐渐受到广大养殖户的青睐。特别是在山林生态养鸡领域&#xff0c;智能化监控方案的引入不仅提高了养殖效率&#xff0c;更有助于保障鸡只的健康与安全。视频监控系统EasyCVR视频汇聚/安防监控视频管理平台在山林生…

GD32F103RCT6/GD32F303RCT6(10)独立看门狗/窗口看门狗实验

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布&#xff1a; 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转&#xff1a; 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…

vscode中更改 git托管的项目里的文件 不显示在 修改项 changes里面

目录 一、问题 二、原因及解决方法 三、总结 tiips:如嫌繁琐&#xff0c;直接移步总结即可&#xff01; 一、问题 1.在vscode中修改 从 git拉取下来的代码&#xff0c;本地不显示被修改的文件&#xff1b;文件夹只有最外层显示红色修改图标;但是里面的被修改的文件也没有被…

在Windows安装Flutter

一、安装 Android Studio 官网&#xff1a; 下载 Android Studio 和应用工具 - Android 开发者 | Android Developers 教程&#xff1a;Android Studio 安装配置教程 - Windows(详细版)-CSDN博客 Flutter 官网&#xff1a;Windows | Flutter 中文文档 - Flutter 中文开发…

为什么AI企业需要联盟营销?

AI工具市场正在迅速发展&#xff0c;现仍有不少企业陆续涌出&#xff0c;那么如何让你的工具受到目标群体的关注呢&#xff1f;这相比是AI工具营销人员一直在思考的问题。 即使这个市场正蓬勃发展&#xff0c;也无法保证营销就能轻易成功。AI工具虽然被越来越多人认可和接受&a…

自动化测试实践:揭秘WebSocket在接口测试中的应用

如何写接口自动化&#xff1f;这个问题&#xff0c;但凡涉足过自动化测试的人员都能娓娓道来。Requests、urlib、jmeter、curl等等&#xff0c;不在话下。那么&#xff0c;如何获取接口的url、参数、响应等信息呢&#xff1f;&#xff01;答案就更是随口而出&#xff1a;看接口…

深入理解Python中的包与模块

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、包的概述与功能 代码案例&#xff1a;包的结构 二、模块的划分与组合 划分模块的方法…

# 解决 win11 连接共享打印机,报错 0x00000709 问题

解决 win11 连接共享打印机&#xff0c;报错 0x00000709 问题 一、问题描述&#xff1a; 当我们连接一台共享打印机&#xff0c;出现报错 0x00000709 时&#xff0c;这是由于本机注册表本配置 RPC 远程调用&#xff0c;我们需要对自己的电脑进行修改&#xff0c;而不是主机&a…

必看项目|多维度揭示心力衰竭患者生存关键因素(生存分析、统计检验、随机森林)

1.项目背景 心力衰竭是一种严重的公共卫生问题,影响着全球数百万人的生活质量和寿命,心力衰竭的病因复杂多样,既有个体生理因素的影响,也受到环境和社会因素的制约,个体的生活方式、饮食结构和医疗状况在很大程度上决定了其心力衰竭的风险。在现代社会,随着生活水平的提…

设计软件有哪些?建模和造型工具篇(4),渲染100邀请码1a12

建模使用到的工具有很多&#xff0c;这次我们接着介绍。 1、PolyBoost PolyBoost是由Digimation公司开发的3ds Max插件&#xff0c;旨在增强软件的多边形建模功能。该插件提供了一系列强大的建模工具&#xff0c;如边缘控制、顶点编辑、面片调整等&#xff0c;使用户能够更加…

【Unity2D 2022:Particle System】添加粒子特效

一、创建粒子系统游戏物体 1. 创建粒子系统游戏物体Smog Effect 2. 给粒子特效添加精灵贴图 &#xff08;1&#xff09;启用Texture Sheet Animation&#xff08;纹理表动画&#xff09; &#xff08;2&#xff09;点击加号添加一个纹理&#xff0c;并将两张厌恶图片导入到纹理…

运维笔记:流编辑器sed命令用法解析

运维笔记 sed命令用法解析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/arti…

大模型助力企业提效,九章云极DataCanvas公司联合腾讯搜狗输入法发布私有化解决方案

近日&#xff0c;九章云极DataCanvas公司与腾讯搜狗输入法的合作再次升级。在搜狗输入法开发者中心正式推出之际&#xff0c;九章云极DataCanvas公司作为搜狗输入法的首批开发合作伙伴&#xff0c;双方联合发布“企业知识管理助手”私有化解决方案。 “企业知识管理助手”整体私…

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

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