eBPF示例:x86-64平台上的uprobe

本文讲述了如何在x86-64平台上使用eBPF的uprobe功能,以及在遇到可执行文件strip后如何解决接口符号丢失的问题,包括备份、反汇编和调整libbpf-bootstrap示例代码的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

eBPF示例:x86-64平台上的uprobe

视频讲解

eBPF示例:x86-64平台上的uprobe

重点:示例代码中只适用于uprobe没有strip的可执行文件,如果可执行文件被strip了,怎么办?

回顾

上节视频详细讲解了kprobe的原理和在 libbpf-bootstrap 框架上的示例代码分析;

这节视频讲下 libbpf-bootstrap 框架上的 uprobe 示例代码;

libbpf-bootstrap 中的 uprobe 示例代码

# 代码路径: libbpf-bootstrap/examples/c/
uprobe.bpf.c  uprobe.c

uprobe 要能正常工作,需要满足2个条件:

  • 被attach的接口名称
  • 接口所在可执行文件路径

在 libbpf-bootstrap 框架中,有2种写法:

  1. 在内核层的ebpf程序中,通过SEC() 给ebpf提供 被attach的接口名称和接口所在的文件路径;
  2. 在用户层的ebpf程序中,通过 bpf_program__attach_uprobe_opts 接口提供 被attach的接口名称和接口所在的文件路径;

改造示例代码

为了简单起见,uprobed_sub 改成和 uprobe_add 同一种写法;

为了模拟更真实的使用场景,把示例代码中的 uprobed_adduprobed_sub 独立到一个测试代码中:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* It's a global function to make sure compiler doesn't inline it. */
int uprobed_add(int a, int b)
{
	return a + b;
}

int uprobed_sub(int a, int b)
{
	return a - b;
}

int main(int argc, char * argv[])
{
    int i = 0;

    for (i = 0;; i++) {
		/* trigger our BPF programs */
		uprobed_add(i, i + 1);
		uprobed_sub(i * i, i);

		/* 为了好验证结果 */
		if (i >= 10) {
			i = 0;
		}

		sleep(1);
	}
}

再改造下 uprobe.c 代码,把 uprobed_adduprobed_sub 所在的测试代码进程ID通过参数传进来:

// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "uprobe.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	return vfprintf(stderr, format, args);
}

int main(int argc, char **argv)
{
	struct uprobe_bpf *skel;
	int err, i, attach_pid;
	char binary_path[256] = {};
	LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);

	if (2 != argc) {
		fprintf(stderr, "usage:%s attach_pid\n", argv[0]);
		return -1;
	}

	attach_pid = atoi(argv[1]);
	sprintf(binary_path, "/proc/%d/exe", attach_pid);

	/* Set up libbpf errors and debug info callback */
	libbpf_set_print(libbpf_print_fn);

	/* Load and verify BPF application */
	skel = uprobe_bpf__open_and_load();
	if (!skel) {
		fprintf(stderr, "Failed to open and load BPF skeleton\n");
		return 1;
	}

	/* Attach tracepoint handler */
	uprobe_opts.func_name = "uprobed_add";
	uprobe_opts.retprobe = false;
	/* uprobe/uretprobe expects relative offset of the function to attach
	 * to. libbpf will automatically find the offset for us if we provide the
	 * function name. If the function name is not specified, libbpf will try
	 * to use the function offset instead.
	 */
	skel->links.uprobe_add = bpf_program__attach_uprobe_opts(skel->progs.uprobe_add,
								 attach_pid, binary_path,
								 0 /* offset for function */,
								 &uprobe_opts /* opts */);
	if (!skel->links.uprobe_add) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* we can also attach uprobe/uretprobe to any existing or future
	 * processes that use the same binary executable; to do that we need
	 * to specify -1 as PID, as we do here
	 */
	uprobe_opts.func_name = "uprobed_add";
	uprobe_opts.retprobe = true;
	skel->links.uretprobe_add = bpf_program__attach_uprobe_opts(
		skel->progs.uretprobe_add, attach_pid, binary_path,
		0 /* offset for function */, &uprobe_opts /* opts */);
	if (!skel->links.uretprobe_add) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* Attach tracepoint handler */
	uprobe_opts.func_name = "uprobed_sub";
	uprobe_opts.retprobe = false;
	/* uprobe/uretprobe expects relative offset of the function to attach
	 * to. libbpf will automatically find the offset for us if we provide the
	 * function name. If the function name is not specified, libbpf will try
	 * to use the function offset instead.
	 */
	skel->links.uprobe_sub = bpf_program__attach_uprobe_opts(skel->progs.uprobe_sub,
								 attach_pid, binary_path,
								 0 /* offset for function */,
								 &uprobe_opts /* opts */);
	if (!skel->links.uprobe_sub) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* we can also attach uprobe/uretprobe to any existing or future
	 * processes that use the same binary executable; to do that we need
	 * to specify -1 as PID, as we do here
	 */
	uprobe_opts.func_name = "uprobed_sub";
	uprobe_opts.retprobe = true;
	skel->links.uretprobe_sub = bpf_program__attach_uprobe_opts(
		skel->progs.uretprobe_sub, attach_pid, binary_path,
		0 /* offset for function */, &uprobe_opts /* opts */);
	if (!skel->links.uretprobe_sub) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* Let libbpf perform auto-attach for uprobe_sub/uretprobe_sub
	 * NOTICE: we provide path and symbol info in SEC for BPF programs
	 */
	err = uprobe_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err);
		goto cleanup;
	}

	printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
	       "to see output of the BPF programs.\n");

	for (i = 0;; i++) {
		/* trigger our BPF programs */
		fprintf(stderr, ".");
		sleep(1);
	}

cleanup:
	uprobe_bpf__destroy(skel);
	return -err;
}

问题:可执行文件被strip,找不到被attach的接口符号,怎么办?

  1. 在 strip 可执行文件前,先备份一下;
  2. 对没有strip的可执行文件进行反汇编,并查找 uprobe_add 和 uprobe_sub 符号的汇编指令地址:
objdump -d test_uprobe.unstriped

000000000000064a <uprobed_add>:
000000000000065e <uprobed_sub>:
  1. 通过命令 cat /proc/attach_pid/maps (attach_pid 是uprobe需要跟踪的进程ID) 获取有可执行属性的attach_pid 的偏移地址:
起始地址     -结束地址       属性 偏移地址  主从设备号 inode编号                文件名
55fde9ec5000-55fde9ec6000 r-xp 00000000 08:01 32672410                   test_uprobe
55fdea0c5000-55fdea0c6000 r--p 00000000 08:01 32672410                   test_uprobe
55fdea0c6000-55fdea0c7000 rw-p 00001000 08:01 32672410                   test_uprobe
7fb077d9b000-7fb077f82000 r-xp 00000000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb077f82000-7fb078182000 ---p 001e7000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078182000-7fb078186000 r--p 001e7000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078186000-7fb078188000 rw-p 001eb000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078188000-7fb07818c000 rw-p 00000000 00:00 0 
7fb07818c000-7fb0781b5000 r-xp 00000000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb07838b000-7fb07838d000 rw-p 00000000 00:00 0 
7fb0783b5000-7fb0783b6000 r--p 00029000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb0783b6000-7fb0783b7000 rw-p 0002a000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb0783b7000-7fb0783b8000 rw-p 00000000 00:00 0 
7ffed92ea000-7ffed930b000 rw-p 00000000 00:00 0                          [stack]
7ffed9383000-7ffed9386000 r--p 00000000 00:00 0                          [vvar]
7ffed9386000-7ffed9388000 r-xp 00000000 00:00 0                          [vdso]
7fffffffe000-7ffffffff000 --xp 00000000 00:00 0                          [uprobes]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
  1. uprobe.c 文件中的 bpf_program__attach_uprobe_opts 接口:
uprobe_opts.func_name 不要赋值,因为二进制文件中已经找不到函数对应的符号
func_offset 参数赋值 = 第2步获取的函数符号汇编指令地址 + 第3步获取的偏移地址
<think>好的,用户现在需要关于内存泄漏检测的脚本或解决方案示例代码。根据用户之前的提问和引用内容,我需要整理出不同方法的脚本示例,并确保回答结构清晰,满足用户的实际需求。 首先,用户提到他们之前已经询问过相关的问题,并且引用了多种方法,比如使用Python脚本分析meminfo、C++工具、eBPF、Valgrind等。我需要参考这些引用内容,提供具体的代码示例。 用户可能需要不同语言或环境的解决方案,所以应该分类说明,比如Shell脚本、Python脚本、C++方法、eBPF工具等。每个部分需要给出具体的代码片段,并简要说明优缺点,这样用户可以根据实际情况选择。 在引用部分,用户提供的引用1是使用脚本统计meminfo,引用2是eBPF的实现,引用3提到了Valgrind等工具,引用4是Java的检测,引用5是Qt的内存泄漏检测。但用户当前的问题集中在脚本或代码示例,所以可能需要重点在Shell、Python、C++等方面展开,而Java和Qt可能不是当前的重点,但可以简要提及。 需要注意用户提到的“生成相关问题”,所以最后需要列出相关问题,帮助用户深入探索。 需要确保代码格式正确,使用$...$和$$...$$的数学表达式,但当前问题可能不涉及数学公式,所以可能不需要。但需要检查是否有需要的地方。 最后,确保回答使用中文,结构清晰,分点说明,每个部分有标题和代码示例,并注明引用来源。</think>根据需求提供三种主流内存泄漏检测脚本及代码实现,涵盖基础脚本分析、可视化监控和高级追踪技术: ### 一、Shell+Python可视化方案 **实现原理**:通过循环抓取进程的$PSS$值并可视化 ```bash # start_monitor.sh #!/bin/bash pid=$1 interval=5 output="meminfo_$pid.txt" while true; do adb shell dumpsys meminfo $pid | grep "TOTAL" >> $output sleep $interval done ``` ```python # visualize.py from pyecharts.charts import Line import re pss_values = [] with open("meminfo_1234.txt") as f: for line in f: match = re.search(r'(\d+)K', line) if match: pss_values.append(int(match.group(1))) line = Line().add_xaxis(range(len(pss_values))).add_yaxis("PSS", pss_values) line.render("memory_trend.html") ``` **优点**:快速验证内存增长趋势[^1] **缺点**:无法定位具体泄漏点 ### 二、C++内存跟踪方案 通过重载运算符记录内存分配: ```cpp // memleak_detector.cpp #include <iostream> #include <unordered_map> std::unordered_map<void*, size_t> alloc_map; void* operator new(size_t size) { void* ptr = malloc(size); alloc_map[ptr] = size; std::cout << "Allocated " << size << " bytes at " << ptr << std::endl; return ptr; } void operator delete(void* ptr) noexcept { auto it = alloc_map.find(ptr); if(it != alloc_map.end()) { std::cout << "Freed " << it->second << " bytes from " << ptr << std::endl; alloc_map.erase(it); } free(ptr); } ``` 编译时链接该文件即可检测未释放内存[^3] ### 三、eBPF高级检测方案 基于Linux内核的实时内存追踪: ```c // memleak.bpf.c SEC("uprobe//lib/x86_64-linux-gnu/libc.so.6:malloc") int malloc_probe(struct pt_regs *ctx) { size_t size = PT_REGS_PARM1(ctx); bpf_printk("malloc %zu bytes\n", size); return 0; } SEC("uretprobe//lib/x86_64-linux-gnu/libc.so.6:free") int free_probe(struct pt_regs *ctx) { void *ptr = PT_REGS_PARM1(ctx); bpf_printk("free %p\n", ptr); return 0; } ``` 配合启动脚本实时监控[^2]: ```bash # start_memleak.sh #!/bin/bash sudo bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { printf("Alloc: %d\n", arg0); }' & PID=$! ./your_program kill $PID ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值