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.虽然这是一个比较老的组件,不过实现思想和源码还是值得我们学习的.通过上一篇 ...
随机推荐
- 2023/4/17 SCRUM个人博客
1.我昨天的任务 学习了easydict库的基本操作 2.遇到了什么困难 没有找到合适的人脸识别库 3.我今天的任务 初步学习dlib的安装,了解dlib的基础组件
- Human-centric Computing and Information Sciences期刊基本信息
letpub 地址: https://www.letpub.com.cn/index.php?page=journalapp&view=detail&journalid=10450&a ...
- Vue-方法与事件
基本用法 监听点击事件 v-on 缩写:@ 预期:Function | Inline Statement | Object 参数:event 修饰符: .stop - 调用 event.stopPro ...
- Floyd判联通(传递闭包) & poj1049 sorting it all out
Floyd判联通(传递闭包) Floyd传递闭包顾名思义就是把判最短路的代码替换成了判是否连通的代码,它可以用来判断图中两点是否连通.板子大概是这个样的: for(int k=1; k<=n; ...
- Java中如何以文本方式输出"\"
1. 转义符使用 "\"在 java中是一个转义符,只要有它的出现往往有他独特的意义,如下图: 那么,在输出文本时,需要输出"\"怎么办呢,其实很简单,只要多加 ...
- bx lr
bx lr 的作用等同于 mov pc,lr 即跳转到lr中存放的地址处. 那么lr存放的是什么地址呢? lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两 ...
- docker离线环境下部署时间同步服务
服务端部署 在线环境 docker run --name=ntp-server \ --restart=always \ --detach \ --publish=123:123/udp \ --re ...
- 强!34.1K star! 再见Postman,新一代API测试利器,功能强大、颜值爆表!
1.引言 在当今的互联网时代,API(应用程序编程接口)已经成为连接不同软件系统的桥梁.作为一名开发者,掌握API测试技能至关重要.市面上的API测试工具琳琅满目,今天我们要介绍的是一款开源.跨平台的 ...
- .NET Core3.1 跨域 Cors 找不到 “Access-Control-Allow-Origin”
今天在做项目的时候遇到了调用WebAPI跨域的问题 No 'Access-Control-Allow-Origin' header is present on the requested resour ...
- C# 全局异常捕获(转载)
C# 全局异常捕获 原文地址:https://www.cnblogs.com/tomahawk/articles/5993874.html 开发界有那么一个笑话,说是"「我爱你」三个字,讲出 ...