uprobe的使用浅析
uprobe是linux内核提供的一种trace用户态函数的机制
可以在不对二进制重新编译的情况下进行trace特定函数
本文描述了uprobe的基本使用方法
使用方法
- 官方的指引是这样的, 详细的可以看kernel代码中的文档Documentation/trace/uprobetracer.rst
p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a uprobe
r[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a return uprobe (uretprobe)
- p 代表trace函数
- r 代表trace函数的返回
先不描述其它参数,我们从例子入手,这样能更好理解其它参数的作用
比如我有一个main.c程序
#include <stdio.h>
int func1() {
printf("1\n");
}
int main() {
func1();
return 0;
}
编译之
gcc main.c -O0 -o uprobe_test
我们想要trace uprobe_test启动之后,什么时候调用的func1, 什么时候从func1返回的
这时我们使用这样的命令
echo 'p:func1 ./uprobe_test:0x1126' >> /sys/kernel/debug/tracing/uprobe_events
echo 'r:func1_ret ./uprobe_test:0x1126' >> /sys/kernel/debug/tracing/uprobe_events
分几个部分解释这两个命令,首先,从输出的目标文件可以猜出,uprobe_events应该是一个列表,这是一个和内核沟通的通道,里面存储着我们期望trace的规则,规则描述了我们想要trace哪些函数的进入,想要trace哪些函数的返回,我们可以cat这个文件看看。
那么规则是什么样呢,之前说了p代表trace函数进入,r代表trace函数退出
从上面cat的结果可以看到p:后面跟了一个uprobes,这是这个事件的grp名称,由于我们在命令中直接指定了事件名称“func1”(注意这里的func1是紧跟在p:后面,是一个名称,与要跟踪的函数名称可以不同),如果不指定grp,系统会把我们的事件自动分配到uprobes组。
接着看命令
:func1 代表一个自定义的消息名称
./uprobe_test 就是一个文件的相对路径,用来供内核找到对应的inode节点
之后的:0x1126代表我们要在映射了这个inode对应的数据块的起始偏移0x1126处设置一个断点,一旦运行到了这个断点,就会进入我们设定好的trace逻辑(默认是向/sys/kernel/debug/tracing/trace和/sys/kernel/debug/tracing/trace_pipe中写日志)
那么疑点来了,:0x1126是哪来的呢,看起来像是某种地址,跟func1有关。
我们来看一下func1的符号地址
[root@VM-0-13-centos uprobe]# nm uprobe_test | grep func1
0000000000401126 T func1
0x401126 看起来和0x1126差了一个0x400000
而官方描述里面这个对应的参数叫offset,
这下就明白设计意图了,我们告诉内核两个信息
- 要trace的程序的路径
- 程序加载到进程内存空间之后,断点距离加载起始地址的偏移
有了1,系统就可以根据进程里面的maps分布找到起始地址,再加上2中的偏移,就得到了具体断点在进程空间中的位置。
[root@VM-0-13-centos uprobe]# cat /proc/121052/maps
00400000-00401000 r--p 00000000 fd:01 1721806 /root/linux_learn_diary/uprobe/uprobe_test
00401000-00402000 r-xp 00001000 fd:01 1721806 /root/linux_learn_diary/uprobe/uprobe_test
00402000-00403000 r--p 00002000 fd:01 1721806 /root/linux_learn_diary/uprobe/uprobe_test
00403000-00404000 r--p 00002000 fd:01 1721806 /root/linux_learn_diary/uprobe/uprobe_test
00404000-00405000 rw-p 00003000 fd:01 1721806 /root/linux_learn_diary/uprobe/uprobe_test
7ffff7a0b000-7ffff7bc7000 r-xp 00000000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7bc7000-7ffff7dc6000 ---p 001bc000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dc6000-7ffff7dca000 r--p 001bb000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dca000-7ffff7dcc000 rw-p 001bf000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dcc000-7ffff7dd0000 rw-p 00000000 00:00 0
7ffff7dd0000-7ffff7dfc000 r-xp 00000000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffff7fef000-7ffff7ff1000 rw-p 00000000 00:00 0
7ffff7ff8000-7ffff7ffb000 r--p 00000000 00:00 0 [vvar]
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 0002c000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffff7ffd000-7ffff7fff000 rw-p 0002d000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
这里可以看出,uprobe_test是被映射到了0x400000这个其实地址
uprobe使用的偏移地址为0x400000是因为该地址是ELF文件在内存中的加载地址。ELF文件是可执行和共享对象文件的标准格式,其中包含有关程序加载和执行的信息。加载器在将ELF文件加载到内存中时,会将文件中的各个段(如代码段、数据段)映射到相应的虚拟地址空间中。而0x400000是Linux系统默认的ELF加载地址,它提供了一个通用的起始地址,可以避免与其他系统或库冲突。因此,uprobe在使用偏移地址时选择了0x400000作为默认值。当然,根据具体情况,也可以根据需要修改偏移地址。
我们使用readelf -l 也能看到这样的信息
[root@VM-0-13-centos uprobe]# readelf -l uprobe_test | grep LOAD
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
LOAD 0x0000000000002e00 0x0000000000403e00 0x0000000000403e00
可以看到第一个需要被加载到内存中的段的偏移是0x400000
这是可能会有一个疑问,为什么不直接告诉内核断点的真实位置是0x401126呢,让内核去找起始地址再加上偏移,得到的不也是这个值吗,这不是多此一举吗。
的确,当前编译出来的uprobe_test的文件中的符号地址确实就是0x401126,但如果我们用地址无关的方式编译,效果会是怎样呢?
gcc main.c -pie -fPIE -O0 -o uprobe_test
[root@VM-0-13-centos uprobe]# readelf -l uprobe_test | grep LOAD
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
LOAD 0x0000000000002de0 0x0000000000003de0 0x0000000000003de0
可以看到这样编译之后起始偏移变成了0而不是0x400000
但是当程序加载起来,我们看看真正加载在哪
[root@VM-0-13-centos linux_learn_diary]# cat /proc/124222/maps
555555554000-555555555000 r--p 00000000 fd:01 1721809 /root/linux_learn_diary/uprobe/uprobe_test
555555555000-555555556000 r-xp 00001000 fd:01 1721809 /root/linux_learn_diary/uprobe/uprobe_test
555555556000-555555557000 r--p 00002000 fd:01 1721809 /root/linux_learn_diary/uprobe/uprobe_test
555555557000-555555558000 r--p 00002000 fd:01 1721809 /root/linux_learn_diary/uprobe/uprobe_test
555555558000-555555559000 rw-p 00003000 fd:01 1721809 /root/linux_learn_diary/uprobe/uprobe_test
7ffff7a0b000-7ffff7bc7000 r-xp 00000000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7bc7000-7ffff7dc6000 ---p 001bc000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dc6000-7ffff7dca000 r--p 001bb000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dca000-7ffff7dcc000 rw-p 001bf000 fd:01 142256 /usr/lib64/libc-2.28.so
7ffff7dcc000-7ffff7dd0000 rw-p 00000000 00:00 0
7ffff7dd0000-7ffff7dfc000 r-xp 00000000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffff7fef000-7ffff7ff1000 rw-p 00000000 00:00 0
7ffff7ff8000-7ffff7ffb000 r--p 00000000 00:00 0 [vvar]
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 0002c000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffff7ffd000-7ffff7fff000 rw-p 0002d000 fd:01 142249 /usr/lib64/ld-2.28.so
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
可以看到加载到一个我们未预见到的地址,也就是说,有时我们没法从一个二进制程序中的符号计算出它真正在地址空间中的地址,我们只能知道它距离映射起始地址处的偏移量。
所以这时inode和offset就变得缺一不可了。
这样,第一条命令解释完毕。
第二条命令,r开头,代表trace函数的退出。
写入规则后,debugfs中的目录结构也会发生变化,由于我们使用的是默认的uprobes组,所以会在tracing/events/uprobes/下面多出两个目录
[root@VM-0-13-centos events]# cd /sys/kernel/debug/tracing/events/
[root@VM-0-13-centos events]# tree uprobes/
uprobes/
├── enable
├── filter
├── func1
│ ├── enable
│ ├── filter
│ ├── format
│ ├── id
│ └── trigger
└── func1_ret
├── enable
├── filter
├── format
├── id
└── trigger
func1 func1_ret这两个目录和我们的命令一一对应。
通过执行
echo 1 >/sys/kernel/debug/tracing/events/uprobes/enable
开启trace
这时我们运行./uprobe_test 再cat /sys/kernel/debug/tracing/trace
[root@VM-0-13-centos ~]# cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 2/2 #P:2
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
uprobe_test-127256 [001] d... 49487.340206: func1: (0x401126)
uprobe_test-127256 [001] d... 49487.340271: func1_ret: (0x401145 <- 0x401126)
就可以看到trace信息了
这里,perf给我们提供了一个简便的工具添加uprobe而不用自己计算偏移量
perf probe -x ./uprobe_test func1
perf probe -x ./uprobe_test func1%return
只不过这时grp变成了probe_uprobe_test
[root@VM-0-13-centos uprobe]# cat /sys/kernel/debug/tracing/uprobe_events
p:probe_uprobe_test/func1 /root/linux_learn_diary/uprobe/uprobe_test:0x0000000000001126
r:probe_uprobe_test/func1__return /root/linux_learn_diary/uprobe/uprobe_test:0x0000000000001126
uprobe的使用浅析的更多相关文章
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 高性能IO模型浅析
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- netty5 HTTP协议栈浅析与实践
一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler
熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...
- 【深入浅出jQuery】源码浅析2--奇技淫巧
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 浅析匿名函数、lambda表达式、闭包(closure)区别与作用
浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...
- word-break|overflow-wrap|word-wrap——CSS英文断句浅析
---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结 ...
- 编写轻量ajax组件02-AjaxPro浅析
前言 上一篇介绍了在webform平台实现ajax的一些方式,并且实现一个基类.这一篇我们来看一个开源的组件:ajaxpro.虽然这是一个比较老的组件,不过实现思想和源码还是值得我们学习的.通过上一篇 ...
随机推荐
- JDK安装教程(Windows)
Windows 端: 1. 安装 首先进入官网:https://www.oracle.com/java/technologies/javase-downloads.html 因为我已经安装了 JDK8 ...
- 常用的adb命令(重要)
常用的adb命令(重要)
- 使用Java对稀疏数组的压缩与还原
稀疏矩阵的压缩与还原 稀疏数组中元素个数很少或者有大量的重复值,如果直接保存保存,会浪费很多空间,这时,就可以考虑对数组进行压缩存储. 先定义一个稀疏数组 //创建一个二维数组 11 * 11 int ...
- 【JavaScript】前端算法题 40道题+解析
前言 最近练习了一些前端算法题,现在做个总结,以下题目都是个人写法,并不是标准答案,如有错误欢迎指出,有对某道题有新的想法的友友也可以在评论区发表想法,互相学习 题目 题目一: 二维数组中的查找: 在 ...
- Jax框架的static与Traced Operations —— Static vs Traced Operations
相关: Jax框架的jit编译是否可以使用循环结构,如果使用循环结构需要注意什么 Jax的static和Traced都是指jit编译的函数内的对象的属性的,jit装饰的函数其输入参数和输出参数都是Tr ...
- (待续)【转载】 Deep Reinforcement Learning Doesn't Work Yet(这里有一篇深度强化学习劝退文)
原文: https://www.alexirpan.com/2018/02/14/rl-hard.html ============================================== ...
- pip install ale_python_interface 安装报错,ModuleNotFoundError: No module named 'ale_python_interface'——fatal error: ale_c_wrapper.h
参考: https://www.cnblogs.com/hasakei/p/10035198.html https://blog.csdn.net/senjie_wang/article/detail ...
- 面向分布式强化学习的经验回放框架(使用例子Demo)——Reverb: A Framework for Experience Replay
相关前文: 面向分布式强化学习的经验回放框架--Reverb: A Framework for Experience Replay 论文题目: Reverb: A Framework for Expe ...
- [CEOI2007] 树的匹配 Treasury 题解
前言 题目链接:洛谷. 题目简述 给一棵树,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配. 题目分析 先来考虑较简单的最大匹配数.对于某一个结点,它有以下三种状态: 不参与匹配: 和某一个儿 ...
- SMU Summer 2023 Contest Round 12
SMU Summer 2023 Contest Round 12 A. K-divisible Sum 分类讨论: \(n > k\)时: \(n\)能整除\(k\)就全是\(1\) 不能整除, ...