找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

671

积分

0

好友

99

主题
发表于 昨天 05:54 | 查看: 0| 回复: 0

Kprobe 相比,uprobe 的核心区别在于其追踪的目标位置不同:它作用于用户空间,而非内核空间。这意味着 uprobe 只能访问用户进程的虚拟内存,通常需要借助 bpf_probe_read_user 等辅助函数来安全地读取用户栈、堆等内存区域的数据。

挂载点依赖

Uprobe 的挂载高度依赖于目标应用程序的符号表(Symbol Table)。如果程序在编译时移除了符号表(即 Stripped binary),定位具体的函数挂载点将变得困难。此时,可以使用 readelfobjdumpIDA 等工具分析二进制文件,通过计算匿名符号的相对偏移量来确定挂载位置。

样例代码分析

我们以 libbpf-bootstrap 项目中的 uprobe 示例进行分析:

eBPF 程序部分的关键定义如下:

SEC("uprobe")
int BPF_KPROBE(uprobe_add, int a, int b)
{
    bpf_printk("uprobed_add ENTRY: a = %d, b = %d", a, b);
    return 0;
}

需要注意的是,SEC("uprobe") 这个段定义本身并未包含两个关键信息:目标文件路径目标函数偏移量。因此,这个 eBPF 程序不会自动挂载,必须依靠用户态代码进行手动挂载配置。

如果目标文件的路径绝对确定且不会改变,可以尝试将路径直接写入 SEC 宏(例如 SEC("uprobe//usr/bin/bash:readline")),但这通常不够灵活。更通用的做法是在用户态手动挂载:

LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
skel->links.uprobe_add = bpf_program__attach_uprobe_opts(
    skel->progs.uprobe_add,
    0 /* self pid */,
    "/proc/self/exe",
    0 /* offset for function */,
    &uprobe_opts /* opts */
);

代码解析:

  • LIBBPF_OPTS 宏用于初始化 bpf_uprobe_opts 结构体变量。
  • bpf_program__attach_uprobe_opts 是 libbpf 提供的用于手动挂载 uprobe 的核心函数。其内部实现通常包含两种方式:
    1. 现代方式:直接调用 perf_event_open 系统调用,通过传递特定的配置参数来创建一个匿名的 uprobe 事件。
    2. 传统方式:需要先向 /sys/kernel/debug/tracing/uprobe_events 文件写入一行格式化命令来注册探针,这源自 Ftrace 系统。

在 eBPF 技术流行之前,Ftrace 是 Linux 系统主要的动态追踪框架。所有的 kprobe 和 uprobe 事件都需要在 Ftrace 中“登记”。Ftrace 将所有的追踪点都抽象为文件系统接口:

  1. 添加 uprobe:向 uprobe_events 文件写入指令。
  2. 事件生成:Ftrace 会在 events/uprobes/ 目录下创建对应的子目录。
  3. 查看输出:读取 trace_pipe 文件获取文本格式的追踪日志。

uprobe 的实现经历了从依赖 Ftrace 到支持 perf_event_open 的演进。

实战工具解析

1. bashreadline

该工具的用户态代码关键部分展示了如何动态解析 ELF 文件以获取函数偏移:

func_off = get_elf_func_offset(readline_so_path, find_readline_function_name(readline_so_path));
if (func_off < 0) {
    warn("cound not find readline in %s\n", readline_so_path);
    goto cleanup;
}
  • find_readline_function_name 函数会读取 ELF 文件的符号表,尝试定位 readline_internal_teardown 符号,若未找到则回退到 readline 符号。
  • get_elf_func_offset 函数则负责计算找到的符号在文件内的偏移量。这是一种实用的动态解析方法,但需注意许多发布版的共享库会剥离符号表。

2. gethostlatency

该工具的 eBPF 程序使用了 uretprobe(uprobe 的返回探针):

SEC("uretprobe")
int BPF_URETPROBE(handle_return)
{
    return probe_return(ctx);
}

其用户态挂载代码清晰地展示了如何同时挂载入口(uprobe)和返回(uretprobe)探针到同一个函数(getaddrinfo)上,这是实现函数耗时统计的常见模式:

// 挂载函数入口探针
links[0] = bpf_program__attach_uprobe(obj->progs.handle_entry, false,
           target_pid ?: -1, libc_path, func_off);
// 挂载函数返回探针
links[1] = bpf_program__attach_uprobe(obj->progs.handle_return, true,
           target_pid ?: -1, libc_path, func_off);

getaddrinfo 是当前最推荐、最标准的 POSIX 接口,用于将域名解析为 IP 地址。在实际的网络编程或性能分析工具开发中,为了全面覆盖,开发者有时需要采用“广撒网”策略,对多个相关的网络解析函数进行追踪。




上一篇:网络安全前沿技术动态:Apache Tika高危XXE漏洞与PatchFuzz模糊测试突破
下一篇:Python依赖倒置原则实践指南:用ABC与Protocol构建低耦合系统
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-12 08:56 , Processed in 0.094985 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表