静态定义探针 - USDT
Linux
Dtrace, Linux, USDT, eBPF
字数统计: 930(字)
阅读时长: 4(分)
一 概述
用户静态定义探针简称 USDT(User Statically Defined Tracing), 支持用户在代码中添加探针, 实现对程序的观测.
二 USDT 探针实现概述
在用户代码中使用 DTRACE_PROBE
系列宏添加探针(需要引入 sys/sdt.h
头文件, 由 systemtap-sdt-devel
提供), 代码编译后源码中添加的 DTRACE_PROBE
会编译为 nop
指令, 并在 ELF 文件中添加 .note.stapstd
段.
此时代码在执行过程中与无探针代码不同, 仅增加了一条 nop
指令(每个探针增加一个 nop
指令).
当用户需要对应用程序进行观测, 注册 USDT 探针时, USDT 工具会读取 ELF 中 nop
指令修改为调试中断(int 3
). 此时, 当程序执行到 DTRACE_PROBE
代码处时会触发中断, 内核会执行与此探针关联的 eBPF 程序.
在用户对应用程序观测结束时, ELF 被修改的 nop
指令会被恢复.
三 测试验证
1. 安装依赖
测试环境是 CentOS-7, 安装依赖 USDT 软件包:
1
| yum install -y systemtap systemtap-runtime systemtap-devel systemtap-sdt-devel
|
使用 bcc 对应用程序注册探针并进行观测, 安装 bcc 工具包:
1
| yum install -y kernel-devel kernel-debug-devel bpftrace bpftrace-tools bpftrace-docs bcc-static bcc-tools
|
2. 代码示例
应用程序代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <stdio.h> #include <stdlib.h> #include <unistd.h>
#include <sys/sdt.h>
void foo(int a, int b) { DTRACE_PROBE2(demo, foo, a, b);
printf("a:%d b:%d\n", a, b); }
int main() {
int a = 10; int b = 20;
int i = 0; for (i = 0; i<10000; i++) { foo(a, b); sleep(5); } }
|
使用 bcc 对应用程序进行观测, 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| bpf_text = """ ...
BPF_PERF_OUTPUT(events);
typedef struct msg_s { int a; int b; } msg_t;
int do_trace(struct pt_regs *ctx) { msg_t msg = {}; bpf_usdt_readarg(1, ctx, &msg.a); bpf_usdt_readarg(2, ctx, &msg.b);
events.perf_submit(ctx, &msg, sizeof(msg));
return 0; }; """
u = USDT(path="./demo") u.enable_probe(probe="foo", fn_name="do_trace")
b = BPF(text=bpf_text, usdt_contexts=[u])
class Msg(ctypes.Structure): _fields_ = [("a", ctypes.c_int), ("b", ctypes.c_int)]
def print_event(cpu, data, size): event = ctypes.cast(data, ctypes.POINTER(Msg)).contents print("print event a:%d b:%d" % (event.a, event.b))
b["events"].open_perf_buffer(print_event)
while True: try: b.perf_buffer_poll() except KeyboardInterrupt: break
|
3. 测试操作
1 2 3 4 5 6 7 8 9
| ./demo >/dev/null &
python trace.py
gdb attach -p [pid] disass foo
|
应用程序在被观测时汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| (gdb) disassemble foo Dump of assembler code for function foo: 0x000000000040057d <+0>: push %rbp 0x000000000040057e <+1>: mov %rsp,%rbp 0x0000000000400581 <+4>: sub $0x10,%rsp 0x0000000000400585 <+8>: mov %edi,-0x4(%rbp) 0x0000000000400588 <+11>: mov %esi,-0x8(%rbp) 0x000000000040058b <+14>: int3 // <--- here 0x000000000040058c <+15>: mov -0x8(%rbp),%edx 0x000000000040058f <+18>: mov -0x4(%rbp),%eax 0x0000000000400592 <+21>: mov %eax,%esi 0x0000000000400594 <+23>: mov $0x400690,%edi 0x0000000000400599 <+28>: mov $0x0,%eax 0x000000000040059e <+33>: callq 0x400450 <printf@plt> 0x00000000004005a3 <+38>: leaveq 0x00000000004005a4 <+39>: retq End of assembler dump.
|
注意地址 0x000000000040058b
处指令变为 int3
.
关闭观测程序, 再次进行反汇编查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| (gdb) disassemble foo Dump of assembler code for function foo: 0x000000000040057d <+0>: push %rbp 0x000000000040057e <+1>: mov %rsp,%rbp 0x0000000000400581 <+4>: sub $0x10,%rsp 0x0000000000400585 <+8>: mov %edi,-0x4(%rbp) 0x0000000000400588 <+11>: mov %esi,-0x8(%rbp) 0x000000000040058b <+14>: nop // <--- here 0x000000000040058c <+15>: mov -0x8(%rbp),%edx 0x000000000040058f <+18>: mov -0x4(%rbp),%eax 0x0000000000400592 <+21>: mov %eax,%esi 0x0000000000400594 <+23>: mov $0x400690,%edi 0x0000000000400599 <+28>: mov $0x0,%eax 0x000000000040059e <+33>: callq 0x400450 <printf@plt> 0x00000000004005a3 <+38>: leaveq 0x00000000004005a4 <+39>: retq End of assembler dump.
|
此时地址 0x000000000040058b
处指令变为 nop
.
在生产代码中应该使用 dtrace 生产辅助代码, 而非直接使用 DTRACE 宏, 参考代码链接.
四 参考链接