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,

这下就明白设计意图了,我们告诉内核两个信息

  1. 要trace的程序的路径
  2. 程序加载到进程内存空间之后,断点距离加载起始地址的偏移

有了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的使用浅析的更多相关文章

  1. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  4. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

  5. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  6. 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler

    熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...

  7. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  8. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  9. word-break|overflow-wrap|word-wrap——CSS英文断句浅析

    ---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结 ...

  10. 编写轻量ajax组件02-AjaxPro浅析

    前言 上一篇介绍了在webform平台实现ajax的一些方式,并且实现一个基类.这一篇我们来看一个开源的组件:ajaxpro.虽然这是一个比较老的组件,不过实现思想和源码还是值得我们学习的.通过上一篇 ...

随机推荐

  1. 免费使用TasteWP一键搭建线上临时WordPress网站

    虽然用宝塔面板或者1Panel面板可以非常快速的搭建一个WordPress网站,但是有时候只想测试下我设计的页面或者开发的主题和插件,又得买服务器,绑定域名,安装程序,搭建起来也过于浪费时间了:再或者 ...

  2. 【Linux】11 RPM & YUM 管理工具 介绍

    rpm包的管理 介绍: 一种用于互联网下载包的打包及安装工具,它包含在某些Linux分发版中. 它生成具有.RPM扩展名的文件.RPM是RedHat Package Manager(RedHat软件包 ...

  3. 【Tutorial C】01 概述

    历史 History 欢迎来到C语言的世界!C语言是一种强大的专业化编程语言,深受业余和专业编程人员的欢迎. 在学习之前先让我们了解和认识它! C语言的原型是A语言(ALGOL 60语言). 1963 ...

  4. 【Layui】02 图标 Icon

    官网下载地址: https://www.layui.com/ 学习参考: https://www.bilibili.com/video/BV1ct411n7SN [Layui的文件结构] 我们只需要这 ...

  5. 如何租GPU:一个价格还算OK的云GPU服务器租赁公司

    一个价格还算OK的云GPU服务器租赁公司. 地址: https://www.gpushare.com/

  6. 【转载】 一块GPU顶数千个CPU内核,英伟达的这个强化学习利器技术细节终于公开了

    原文地址: https://mp.weixin.qq.com/s/FmFqmIqmknkpBQbNb2ioDA ============================================ ...

  7. 数据库存储时间数据用timestamp 好还是 varchar好

    表示日期数据基本是date型,只有年月的用varchar2或者char,好处见下:1.数据规范.date对合法日期型会校验,包括闰年2月这种.避免字符型变量产生的某月32号,日期长度不对,日期格式不统 ...

  8. 亚信科技基于 Apache SeaTunnel 的二次开发应用实践

    亚信科技在Apache SeaTunnel的实践分享 自我介绍 各位同学好,很荣幸通过Apache SeaTunnel社区和大家进行分享交流.我是来自亚信科技的潘志宏,主要负责公司内部数据中台产品的开 ...

  9. 使用 Nuxt 3 的 defineRouteRules 进行页面级别的混合渲染

    title: 使用 Nuxt 3 的 defineRouteRules 进行页面级别的混合渲染 date: 2024/8/12 updated: 2024/8/12 author: cmdragon ...

  10. WPF 怎么把checkbox改成开关样式

    先看一下效果吧: isChecked = false 的时候的效果 isChecked = true 的时候的效果 然后我们来实现一下这个效果吧 第一步:创建一个空的wpf项目: 第二步:在项目里面添 ...