kprobe原理解析

简介: kprobe的出现就很有必要,它可以在运行的内核中动态插入探测点,执行你预定义的操作。

kprobe是什么?

    如何高效的调试内核?printk是一种方法,但是printk终归是毫无选择的全量输出,某些场景下不适用,于是你可以试一下tracepoint,我使能tracepoint机制的时候才输出。对于傻傻的放置printk来输出信息的方式,tracepoint是个进步,但是tracepoint只是内核某些特定行为(比如进程切换)上部署的一些静态锚点,这些锚点并不一定是你需要的,所以你仍需要自己部署tracepoint,重现编译内核。那么kprobe的出现就很有必要了,它可以在运行的内核中动态插入探测点,执行你预定义的操作。

kprobe怎么使用?

    kprobe主要有两种使用方法,一是通过模块加载,二是通过debugfs接口。
    模块加载的方式:内核源码下有目录下sample/kprobes,该目录下有许多kprobes的例子,可以仿照这些例子写自己的kprobe模块。以kprobe_example.c为例,首先声明一个kprobe结构体,然后定义其中几个关键成员变量,包括symbol_name,pre_handler,post_handler。其中,symbol_name是函数名(kprobe_example.c中该项为do_fork),告诉内核我的探测点放置在了函数do_fork处,pre_handler和post_handler分别表示在执行探测点之前和之后执行的钩子函数。然后通过register_kprobe函数注册kprobe即可。将kprobe_example.ko inmod进内核之后,每当系统新启动一个进程,比如执行ls,cat等,都会输出:

pre_handler: p->addr = 0x, ip = *.
post_handler: p->addr = 0x, pc = *.

    第一行是执行pre_handler钩子函数的输出,第二行是执行post_handler钩子函数的输出,当然这些都是内核中案例的写法,你可以写自己的钩子函数。
    通过debugfs接口注册kprobe:模块加载的终究不是很方便,尤其对于一些不带gcc的嵌入式系统,需要交叉编译ko,将ko拷贝到单板,然后insmod,不便。debugfs下(确切地说,应该是ftrace)提供了一套注册、使能、注销kprobe的接口,可以很方便的操作kprobe。

用法如下:

    1)cd /sys/kernel/debug/tracing[有些系统没有挂载debugfs,需要先挂载下mount -t debugfs nodev /sys/kernel/debug]
    2)进入tracing目录,这里就是传说中ftrace的天下了,执行:

echo "p:sys_write_event sys_write" > kprobe_events

    向kprobe_events写入“p:sys_write_event sys_write”,注册kprobe事件。你会发现,当前目录下的events下,新增一个kprobes目录,该目录下:

root@station:/sys/kernle/debug/tracing/events/kprobes# ls
enable filter sys_write_event

    即,我们注册的kprobe时间生效了。那么“p:sys_write_event sys_write”是什么意思呢?首先p表示我们要注册一个kprobe,如果要注册retprobe,此处应为r;sys_write_event表示这个kprobe叫什么名字;sys_write表示我们的插入点在哪里。那么,“p:sys_write_event sys_write”的语义就很明显了:在函数sys_write处插入了一个kprobe点,这个点的名字叫做sys_write_event。
    3)使能kprobe。执行:

cd /sys/kernel/debug/tracing/events/kprobes/events/sys_write_event
echo 1 > enable
cd ../../..[退回到/sys/kernel/debug/tracing,查看trace文件的输出]
cat trace
trace文件的输出是如下的:
......
bash-808 [003] d... 42715.347565:sys_write_event:(SyS_write+0x0/0xb0)
......
解释下置红的这条输出:pid为808的进程bash,在自本次开机42715.347565秒的时候,调用了一次函数sys_write。

    4)撤销kprobe。执行

cd /sys/krenel/debug/tracing/events/sys_write_event
echo 0 > enable[首先先关闭kprobe]
cd ../../..
echo "-:kprobes/sys_write_event" >> kprobe_events[注销kprobe]

    以上就是kprobe的两种注册机使用方式:通过模块加载以及通过debugfs注册。
    使用模块加载的方式,是kprobe的一种原始用法:在kprobe结构体里定义插入点、钩子函数,然后通过register_kprobe注册上这个kprobe即可。ftrace接口是kprobe的一种应用,它是一套trace的框架,下面的trace机制包括tracepoint、function trace等,kprobe仅仅是这些trace机制中的一员。上面的讲述我们也已经看出来了,通过ftrace注册的kprobe的输出是在ftrace的输出:trace文件。模块加载模式中我们可以定义kprobe的钩子函数pre_handler和post_handler,但是在ftrace下注册的kprobe的钩子是ftrace接口默认的,我们设置不了,但是具体输出什么,我们可以在echo "p:sys_write_event sys_write"时指定,比如指定x1寄存器的内容等,所以ftrace下注册的kprobe功能同样很强大。同时,由于ftrace下kprobe的输出基于ftrace的输出框架,所以输出信息包含当前进程、CPU、时间戳等信息,对于trace来说非常有用。
    上面和大家简要说明了下kprobe到底应该怎样用,那么现在我们就揭开kprobe神秘的面纱。

kprobe的工作过程大致如下:

1)注册kprobe。注册的每个kprobe对应一个kprobe结构体,该结构体记录着插入点(位置),以及该插入点本来对应的指令original_opcode;
2)替换原有指令。使能kprobe的时候,将插入点位置的指令替换为一条异常(BRK)指令,这样当CPU执行到插入点位置时会陷入到异常态;
3)执行pre_handler。进入异常态后,首先执行pre_handler,然后利用CPU提供的单步调试(single-step)功能,设置好相应的寄存器,将下一条指令设置为插入点处本来的指令,从异常态返回;
4)再次陷入异常态。上一步骤中设置了single-step相关的寄存器,所以original_opcode刚一执行,便会二进宫:再次陷入异常态,此时将signle-step清楚,并且执行post_handler,然后从异常态安全返回。
    步骤2),3),4)便是一次kprobe工作的过程,它的一个基本思路就是将本来执行一条指令扩展成执行kprobe->pre_handler--->指令--->kprobe-->post_handler这样三个过程。下面详细解释每个过程:

image

    指令替换过程:上图中蓝色区域表示内存,红色标明了地址,绿色部分代表一条指令,上图的意思是,内存0xffffffc000162914处存放一条指令是0xa9bd7bfd。那么,现在我注册了一个kprobe,探测点是sys_write函数,该函数的起始位置就是0xffffffc000162914,现在我要使能kprobe了,那么我要做的就是把0xffffffc000162914处原来的指令0xa9bd7bfd替换成一条BRK指令,即上图所表示的一个一花节目过程。你可能会好奇原来的指令0xa9bd7bfd存在哪里?存在kprobe结构体的opcode域!这样当不再使能kprobe的时候,我再恢复回去。
触发BRK指令:
    上面把人家指令给改了,那么CPU执行到BRK必然会引发内核陷入BRK异常状态:

image

    蓝色部分依旧表示内存,绿色部分表示指令,红色表示CPU,上图表示CPU执行到0xffffffc000162914(sys_write)处,该处指令为BRK,于是内核陷入异常态。在异常态中,内核通过BRK指令的错误码判断这是一个kprobe异常,于是进入了kprobe处理函数。kprobe异常处理函数会根据发生异常的地址来找到对应的kprobe(kprobe的addr域记录着地址),执行kprobe的pre_handler函数,然后设置single-step相关的寄存器,为下一步执行原指令时发生single-step异常做准备。那么紧接着就是设置元之灵的地址了,我们知道0xffffffc000162914处已经被替换成了BRK指令,原指令保存在kprobe结构体中,怎么保证下一步执行到原指令呢?最简单的做法是申请一块内存,然后将原指令复制到这块内存开始处,设置PC寄存器位该内存的首地址,这样当代码从异常态返回时,执行的第一条指令便是原指令了!

image

原指令得到执行,二进宫
    经过上面一个步骤,pre_handler得到了执行,从异常态返回之后,原指令也得到了执行,但是由于设置了single-step模式,所以执行完原指令,马上又陷入了异常态,二进宫:

    这次进入异常态后,先清一下single-step相关的寄存器,确保下次从异常返回时的指令不会由于single-step发生三进宫,然后执行post_handler,最后将地址0xffffffc000162918写入到PC寄存器,为什么是这个数值呢?它正是紧接着0xffffffc000162914的下一条指令的地址,有没有发现,至此我们已经完成了pre_handler->原指令->post_handler这样三个阶段,也就是说kprobe要做的事情都做完了,此时的工作就是收拾下残局,返回到正常的指令流程,我们的探测点在0xffffffc000162914处,下一条指令应该就是0xffffffc000162918了,所以把此值写入PC寄存器,让一切恢复正轨!
    kprobe工作结束,走上正轨

image


上面把PC设置成了0xffffffc000162918,所以从异常态返回时,CPU就走上了正轨接着朝下面执行了,一个BRK指令引发的反映再次就搞一段落了。
    但是每次当CPU执行到0xffffffc000162914处,都会触发上面的一连串操作,kprobe的机制也就是从一个BRK指令开始了。

原文发布时间为:2018-07-19
本文来自云栖社区合作伙伴“Linux宝库”,了解相关信息可以关注“Linux宝库”。

相关文章
|
12月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
654 86
|
9月前
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
11月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
339 14
|
8月前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
576 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
8月前
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1018 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
9月前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
223 22
解析静态代理IP改善游戏体验的原理
|
9月前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
584 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
8月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
564 2
|
10月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
11735 46
|
9月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
1084 12

推荐镜像

更多
  • DNS