kprobe 内核实现原理

kprobe是linux内核的一个重要的特性,是其他内核调试工具(perf,systemtap)的基础设施,同时内核BPF也是依赖于kprobe。

Kprobe结构体

< include/linux/kprobe.h >

struct kprobe {struct hlist_node hlist;        /* 所有注册的kprobe都会添加到kprobe_table哈希表中,hlist成员用来链接到某个槽位中 *//* list of kprobes for multi-handler support */struct list_head list;                   /* 链接一个地址上注册的多个kprobe *//*count the number of times this probe was temporarily disarmed */unsigned long nmissed;         * 记录当前的probe没有被处理的次数 *//* 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;                                          /* 状态标记 */
};

源码分析

init_kprobes()

初始化入口

< kernel/kprobe.c >

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]);                    /* 初始化用于存储 kprobe 模块的哈希表 */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;
#endiferr = arch_init_kprobes();                                         /* 初始化CPU架构相关 */if (!err)err = register_die_notifier(&kprobe_exceptions_nb);           /* 注册die通知链*/if (!err)err = register_module_notifier(&kprobe_module_nb);         /* 注册模块通知链 */kprobes_initialized = (err == 0);kprobe_sysctls_init();return err;
}

注册kprobe总体流程

以arm为例:

register_kprobe() -> arm_kprobe() -> __arm_kprobe() -> arch_arm_kprobe()

register_kprobe()

< kernel/kprobe.c >

int register_kprobe(struct kprobe *p)
{int ret;struct kprobe *old_p;struct module *probed_mod;kprobe_opcode_t *addr;bool on_func_entry;/* Adjust probe address from symbol */addr = _kprobe_addr(p->addr, p->symbol_name, p->offset, &on_func_entry);if (IS_ERR(addr))return PTR_ERR(addr);p->addr = addr;ret = warn_kprobe_rereg(p);        /* 防止同一个kprobe被重复注册 */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);/* 1. 判断被注册的函数是否位于内核的代码段内,或位于不能探测的kprobe实现路径中 * 2. 判断被探测的地址是否属于某一个模块,并且位于模块的text section内* 3. 如果被探测的地址位于模块的init地址段内,但该段代码区间已被释放,则直接退出 */ret = check_kprobe_address_safe(p, &probed_mod);if (ret)return ret;mutex_lock(&kprobe_mutex);if (on_func_entry)p->flags |= KPROBE_FLAG_ON_FUNC_ENTRY;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)]);               /* 将kprobe加入到相应的hash表内 */if (!kprobes_all_disarmed && !kprobe_disabled(p)) {ret = arm_kprobe(p);                                          /* 将探测点的指令码修改为arm_kprobe */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;
}

arm_kprobe()

< kernel/kprobes.c >

static int arm_kprobe(struct kprobe *kp)
{if (unlikely(kprobe_ftrace(kp)))return arm_kprobe_ftrace(kp);cpus_read_lock();mutex_lock(&text_mutex);__arm_kprobe(kp);mutex_unlock(&text_mutex);cpus_read_unlock();return 0;
}

__arm_kprobe()

< kernel/kprobes.c >

/* Put a breakpoint for a probe. */
static void __arm_kprobe(struct kprobe *p)
{struct kprobe *_p;lockdep_assert_held(&text_mutex);/* Find the overlapping optimized kprobes. */_p = get_optimized_kprobe(p->addr);if (unlikely(_p))/* Fallback to unoptimized kprobe */unoptimize_kprobe(_p, true);arch_arm_kprobe(p);                                  /* 替换探测点指令为BRK64_OPCODE_KPROBES指令 */optimize_kprobe(p);	/* Try to optimize (add kprobe to a list) */
}

在函数arch_arm_kprobe(p);之前,都是通用的注册流程,然后就是和体系结构相关的实现了

prepare_kprobe()

< kernel/kprobes.c >

static int prepare_kprobe(struct kprobe *p)
{/* Must ensure p->addr is really on ftrace */if (kprobe_ftrace(p))return arch_prepare_kprobe_ftrace(p);return arch_prepare_kprobe(p);
}

arch_prepare_kprobe()

< arch/arm64/kernel/kprobes/kprobes.c >

int __kprobes arch_prepare_kprobe(struct kprobe *p)
{unsigned long probe_addr = (unsigned long)p->addr;if (probe_addr & 0x3)return -EINVAL;/* copy instruction */p->opcode = le32_to_cpu(*p->addr);if (search_exception_tables(probe_addr))return -EINVAL;/* decode instruction */switch (arm_kprobe_decode_insn(p->addr, &p->ainsn)) {case INSN_REJECTED:	/* insn not supported */return -EINVAL;case INSN_GOOD_NO_SLOT:	/* insn need simulation */p->ainsn.api.insn = NULL;break;case INSN_GOOD:	/* instruction uses slot */p->ainsn.api.insn = get_insn_slot();if (!p->ainsn.api.insn)return -ENOMEM;break;}/* prepare the instruction */if (p->ainsn.api.insn)arch_prepare_ss_slot(p);                     /* 将指令存放到slot中,记录下一条指令到p->ainsn.api.insn */elsearch_prepare_simulate(p);return 0;
}

arch_arm_kprobe()

< arch/arm64/kernel/kprobes/kprobes.c >

/* arm kprobe: install breakpoint in text */
void __kprobes arch_arm_kprobe(struct kprobe *p)
{void *addr = p->addr;                                              /* 原地址 */u32 insn = BRK64_OPCODE_KPROBES;          /* 替换后的指令 */aarch64_insn_patch_text(&addr, &insn, 1);
}

X86系统实现方式

x86系统kprobe是通过arch_arm_kprobe实现最终注册功能

这里留意下细节,名字里带着arm,难怪搜arch_x86_kprobe 搜不到

arch_arm_kprobe()

< arch/x86/kernel/kprobes/core.c >

void arch_arm_kprobe(struct kprobe *p)
{u8 int3 = INT3_INSN_OPCODE;        /* 替换后的指令 */text_poke(p->addr, &int3, 1);text_poke_sync();perf_event_text_poke(p->addr, &p->opcode, 1, &int3, 1);
}

这个函数主要作用是要安装断点,这里安装断点的原理和GDB断点应该是一样的,

GDB安装断点的原理

在执行gdb ./test后,gdb就会fork出一个子进程,这个子进程首先调用ptrace然后执test程序,这样就准备好调试环境了。

gdb在断点中执行两件事

  1. 对源码所对应断点处代码存储到断点链表中。

  2. 然后插入中断指令INT3,也就是说:汇编代码中的原来指令被替换为INT3。

addr对应位置的指令修改为INT3指令,进程在执行时,发现是INT3指令,于是操作系统就发送一个SIGTRAP信号给test进程。

操作系统发给test的任何信号,都被gdb接管了,也就是说gdb会首先接收到这SIGTRAP个信号,gdb发现当前汇编代码执行的是INT3指令,

于是gdb又做了2个操作:

  1. 将INT3指令替换为断点处注册的执行函数,执行完成后,再执行被INT3替换的指令。

  2. x86系统,INT3_INSN_OPCODE是要替换的指令,然后进一步替换为要执行的函数地址

这些都执行完成后,再执行最初被替换的指令

触发kprobe探测和回调

debug_traps_init()

kprobe的触发和处理是通过brk exception和single step单步exception执行的

< arch/arm/kernel/debug-monitors.c >

void __init debug_traps_init(void)
{hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,TRAP_TRACE, "single-step handler");hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,TRAP_BRKPT, "BRK handler");
}

kprobe_handler()

< arch/arm64/kernel/kprobes/kprobes.c >

static void __kprobes kprobe_handler(struct pt_regs *regs)
{struct kprobe *p, *cur_kprobe;struct kprobe_ctlblk *kcb;unsigned long addr = instruction_pointer(regs);kcb = get_kprobe_ctlblk();cur_kprobe = kprobe_running();p = get_kprobe((kprobe_opcode_t *) addr);if (p) {if (cur_kprobe) {if (reenter_kprobe(p, regs, kcb))return;} else {/* Probe hit */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)) {setup_singlestep(p, regs, kcb, 0);} elsereset_current_kprobe();}}/** The breakpoint instruction was removed right* after we hit it.  Another cpu has removed* either a probepoint or a debugger breakpoint* at this address.  In either case, no further* handling of this interrupt is appropriate.* Return back to original instruction, and continue.*/
}

kprobe的使用方法

方法一:

要编写一个 kprobe 内核模块。

方法二:

基于Ftrace的/sys/kernel/debug/tracing/kprobe_events接口。

方法三:

通过perf工具。

方法四:

使用eBPF。

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

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

相关文章

深度学习服务器(Linux)开发环境搭建教程

当你拿到一台服务器的使用权时&#xff0c;最头疼的莫过于登陆服务区并配置开发环境。本文将从0开始&#xff0c;讲述一台刚申请的服务器远程登陆并配置开发环境的全过程。希望对你有所帮助 1.登陆服务器 打开MobaXterm软件&#xff0c;创建一个新的Session&#xff0c;选择S…

Linux 编译链接那些事儿(02)C++链接库std::__cxx11::basic_string和std::__1::basic_string链接问题总结

1 问题背景说明 在自己的项目源码中引用libeasysqlite.so时编译成功&#xff0c;但运行时遇到问题直接报错&#xff0c;找不到符号 symbol&#xff1a;_ZN3sql5FieldC1ENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_10field_typeEi。 2 问题描述和解…

图及谱聚类商圈聚类中的应用

背景 在O2O业务场景中&#xff0c;有商圈的概念&#xff0c;商圈是业务运营的单元&#xff0c;有对应的商户BD负责人以及配送运力负责任。这些商圈通常是一定地理围栏构成的区域&#xff0c;区域内包括商户和用户&#xff0c;商圈和商圈之间就通常以道路、河流等围栏进行分隔。…

MySQL EXPLAIN查看执行计划

MySQL 执⾏计划是 MySQL 查询优化器分析 SQL 查询时⽣成的⼀份详细计划&#xff0c;包括表如何连 接、是否⾛索引、表扫描⾏数等。通过这份执⾏计划&#xff0c;我们可以分析这条 SQL 查询中存在的 问题&#xff08;如是否出现全表扫描&#xff09;&#xff0c;从⽽进⾏针对优化…

双十一运动健身好物推荐,这几款健身好物一定不要错过!

双十一购物狂欢节又要到了&#xff0c;又要到买买买的时候了&#xff01;相信有很多想健身的小白还在发愁不知道买啥装备&#xff1f;别急&#xff0c;三年健身达人这就给你们分享我的年度健身好物&#xff01; 第一款&#xff1a;南卡Runner Pro4s骨传导耳机 推荐理由&#…

新概念汽车3d720度全景vr制作尽可能还原汽车的真实细节

一、什么是VR全景看车 VR全景看车是通过虚拟现实技术实现逼真的汽车观赏和试乘体验。消费者可以通过智能手机或者电脑pc端&#xff0c;进入写实的虚拟现实的汽车展厅或者场景&#xff0c;进行车辆的透彻了解和体验。这种技术让消费者能够更加方便地了解汽车的外观、内饰和功能特…

独立IP主机怎么样?对网站有什么影响

对于现在企业来说&#xff0c;搭建网站是必不可少的&#xff0c;而大部分企业网站都会选择使用虚拟主机搭建&#xff0c;且使用的也是共享IP的这样会 有许多的弊端&#xff0c;所以部分站长会选择独立IP搭建。那么到底独立IP主机怎么样呢&#xff1f;使用独立IP主机搭建对网站有…

VSCode 连接不上 debian 的问题

之前一台笔记本上安装了 debian12&#xff0c;当时用 vscode 是可以连接上的&#xff0c;但今天连接突然就失败了&#xff0c;失败信息是这样的&#xff1a; 查看失败信息 因为 debian 是自动获取 ip 地址的&#xff0c;以前能连接上时&#xff0c;ip 地址是 104&#xff0c;然…

OpenCloudOS9操作系统搭建Confluence8.0.4+Jira企业级WIKI

OpenCloudOS9操作系统搭建Confluence8.0.4+Jira企业级WIKI 1. 概要2. 系统基础环境配置3. 安装并配置MySQL3.1. 安装MySQL3.2. MySQL基本配置3.3. 创建Confluence数据库4. 安装并配置jira5. 破解jira6. 安装并配置Confluence7. 破解Confluence8. 优化配置Confluence9. confluen…

红队专题-新型webshell的研究

新型webshell的研究 招募六边形战士队员webshell与MemoryShell内存马新型一句话木马之Java篇 AES加密Class二进制解析友军防护为什么会被拦截SO waf防护规则END 一劳永逸绕过waf实现篇服务端实现 前言&#xff1a;你马没了利用JavaAgent技术发现并清除系统中的内存马介绍安全行…

centos7安装nginx-阿里云服务器

1.背景 2.准备工作步骤 2.1.安装gcc 阿里云服务器一般默认是安装了的 检查是否已安装 gcc -v 出现如下信息表示已安装: 如果没有安装,执行 yum -y install gcc 2.2.安装pcre,pcre-devel yum install -y pcre pcre-devel 2.3.安装zlib yum install -y zlib zlib-devel…

PS Raw中文增效工具Camera Raw 16

Camera Raw 16 for mac&#xff08;PS Raw增效工具&#xff09;的功能特色包括强大的图像调整工具。例如&#xff0c;它提供白平衡、曝光、对比度、饱和度等调整选项&#xff0c;帮助用户优化图像的色彩和细节。此外&#xff0c;Camera Raw 16的界面简洁易用&#xff0c;用户可…

Python + Selenium,分分钟搭建 Web 自动化测试框架!

在程序员的世界中&#xff0c;一切重复性的工作&#xff0c;都应该通过程序自动执行。「自动化测试」就是一个最好的例子。 随着互联网应用开发周期越来越短&#xff0c;迭代速度越来越快&#xff0c;只会点点点&#xff0c;不懂开发的手工测试&#xff0c;已经无法满足如今的…

【小白专用】PHP中的JSON转换操作指南 23.11.06

一、JSON的基础知识 1.1JSON数据格式 JSON数据格式是一组键值对的集合&#xff0c;通过逗号分隔。键值对由“键”和“值”组成&#xff0c;中间使用冒号分隔。JSON数据格式可以嵌套&#xff0c;而且可以使用数组 二、PHP中的JSON函数 JSON的操作需要使用编程语言进行处理&am…

从零开始搭建微服务

人狠话不多,直接开始少点屁话本着共同学习进步的目的和大家交流如有不对的地方望铁子们多多谅解 准备工具 开发工具 idea Java环境 jdk.18 Maven 3.8.6 仓库镜像阿里云 <mirror><id>alimaven</id><name>aliyun maven</name><url>https:…

.NET Core 中插件式开发实现

在 .NET Framework 中&#xff0c;通过AppDomain实现动态加载和卸载程序集的效果&#xff1b;但是.NET Core 仅支持单个默认应用域&#xff0c;那么在.NET Core中如何实现【插件式】开发呢&#xff1f; 一、.NET Core 中 AssemblyLoadContext的使用 1、AssemblyLoadContext简…

Javaweb之HTML,CSS的详细解析

2.4 表格标签 场景&#xff1a;在网页中以表格&#xff08;行、列&#xff09;形式整齐展示数据&#xff0c;我们在一些管理类的系统中&#xff0c;会看到数据通常都是以表格的形式呈现出来的&#xff0c;比如&#xff1a;班级表、学生表、课程表、成绩表等等。 标签&#xff…

输电线路AR可视化巡检降低作业风险

随着现代工业的快速发展&#xff0c;各行业的一线技术工人要处理的问题越来越复杂&#xff0c;一些工作中棘手的问题迫切需要远端专家的协同处理。但远端专家赶来现场往往面临着专家差旅成本高、设备停机损失大、专业支持滞后、突发故障无法立即解决等痛点。传统的远程协助似乎…

OFDM同步--载波频率偏差CFO

参考书籍&#xff1a;《MIMO-OFDM无线通信技术及MATLAB实现》 实验图基本都截取自该本书 一、什么是CFO OFDM解调是采用同步检波的方式&#xff0c;需要在接收机使用与发射机相同的载波信号进行向下变换恢复出基带信号。但在实际使用中无法获得完全相同的载波信号&#xff0c;…

Mysql之多表查询上篇

Mysql之多表查询上篇 多表查询什么是多表查询笛卡尔积(交叉连接)产生笛卡尔积的条件避免笛卡尔积的方法 多表查询的分类1.等值连接 VS 非等值连接等值连接非等值连接扩展1表的别名扩展2&#xff1a;连接多个表 2.自连接与非自连接扩展3&#xff1a;SQL语法标准 内连接SQL92语法…