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这个文件看看。

[root@VM-0-13-centos uprobe]# cat /sys/kernel/debug/tracing/uprobe_events
p:uprobes/func1 ./uprobe_test:0x0000000000001126
r:uprobes/func1_ret ./uprobe_test:0x0000000000001126

那么规则是什么样呢,之前说了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中的偏移,就得到了具体断点在进程空间中的位置。

这样说还不够直观,我们看一下uprobe_test程序运行起来后的内存布局。我们用gdb将程序停在main处,然后查看/proc中的maps

[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这个其实地址

我们使用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
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值