Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/test-libbpf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ jobs:
make -C src/30-sslsniff
sudo timeout -s 2 3 src/30-sslsniff/sslsniff || if [ $? = 124 ]; then exit 0; else exit $?; fi

- name: test 32 wallclock-profiler
run: |
make -C src/32-wallclock-profiler
# Test individual tools can be built and show help
src/32-wallclock-profiler/oncputime --help || true
src/32-wallclock-profiler/offcputime --help || true

- name: test 33 funclatency
run: |
make -C src/33-funclatency
Expand Down
2 changes: 2 additions & 0 deletions src/16-memleak/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ package.json
package.yaml
ecli
memleak
test_memleak
.output/
22 changes: 14 additions & 8 deletions src/16-memleak/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ CFLAGS := -g -Wall
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)

APPS = # minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tc ksyscall
TEST_APPS = test_memleak

CARGO ?= $(shell which cargo)
ifeq ($(strip $(CARGO)),)
Expand Down Expand Up @@ -69,12 +70,12 @@ $(call allow-override,CC,$(CROSS_COMPILE)cc)
$(call allow-override,LD,$(CROSS_COMPILE)ld)

.PHONY: all
all: $(APPS)
all: $(APPS) $(TEST_APPS)

.PHONY: clean
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(APPS)
$(Q)rm -rf $(OUTPUT) $(APPS) $(TEST_APPS)

$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
Expand All @@ -94,16 +95,16 @@ $(BPFTOOL): | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap


$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
$(Q)cd $(LIBBLAZESYM_SRC) && RUSTFLAGS="-A dangerous_implicit_autorefs" $(CARGO) build --features=cheader,dont-generate-test-files --release
$(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a::
$(Q)cd $(LIBBLAZESYM_SRC)/capi && $(CARGO) build --release

$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a | $(OUTPUT)
$(call msg,LIB, $@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a $@

$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym_c.a | $(OUTPUT)
$(call msg,LIB,$@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
$(Q)cp $(LIBBLAZESYM_SRC)/capi/include/blazesym.h $@

# Build BPF code
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
Expand Down Expand Up @@ -137,5 +138,10 @@ $(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
# delete failed targets
.DELETE_ON_ERROR:

# Test program (no BPF, just plain C)
test_memleak: test_memleak.c
$(call msg,TEST,$@)
$(Q)$(CC) $(CFLAGS) $< -o $@

# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:
31 changes: 31 additions & 0 deletions src/16-memleak/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ Reference: <https://github.com/iovisor/bcc/blob/master/libbpf-tools/memleak.c>

## Compile and Run

This implementation uses the latest **blazesym v0.2.0** library for symbol resolution, which provides improved performance and a modern C API through the `capi` package.

```console
$ make
$ sudo ./memleak
Expand All @@ -438,6 +440,35 @@ Tracing outstanding memory allocs... Hit Ctrl-C to end
...
```

## Testing memleak

This repository includes a test program (`test_memleak.c`) that intentionally leaks memory for testing purposes. You can build and run it to verify that memleak correctly detects memory leaks:

```console
$ make test_memleak
$ ./test_memleak &
$ sudo ./memleak -p $(pidof test_memleak)
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs... Hit Ctrl-C to end
[19:31:49] Top 1 stacks with outstanding allocations:
10240 bytes in 5 allocations from stack
0 [<00005a7ea0a34212>] leak_with_loop+0x1f
1 [<00005a7ea0a3428e>] main+0x6e
2 [<00007b4ea482a1ca>] <null sym>
3 [<00007b4ea482a28b>] __libc_start_main+0x8b
4 [<00005a7ea0a34105>] _start+0x25
```

As shown above, memleak successfully:
- Detected **10240 bytes in 5 allocations** (5 × 2048 bytes) that were leaked
- Identified the leaking function **leak_with_loop+0x1f** with the correct offset
- Provided the complete call stack showing the leak originated from `main` function
- Used the new **blazesym v0.2.0** C API for fast and accurate symbol resolution

The test demonstrates that memleak with the updated blazesym library can effectively trace memory allocations and pinpoint the exact functions responsible for memory leaks.

## Summary

Through this eBPF introductory tutorial, you have learned how to write a Memleak eBPF monitoring program to monitor memory leaks in real time. You have also learned about the application of eBPF in memory monitoring, how to write eBPF programs using the BPF API, create and use eBPF maps, and how to use eBPF tools to monitor and analyze memory leak issues. We have provided a detailed example to help you understand the execution flow and principles of eBPF code.
Expand Down
31 changes: 31 additions & 0 deletions src/16-memleak/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ int attach_uprobes(struct memleak_bpf *skel)

## 编译运行

本实现使用最新的 **blazesym v0.2.0** 库进行符号解析,该库通过 `capi` 包提供了改进的性能和现代化的 C API。

```console
$ make
$ sudo ./memleak
Expand All @@ -478,6 +480,35 @@ Tracing outstanding memory allocs... Hit Ctrl-C to end
...
```

## 测试 memleak

本仓库包含一个测试程序(`test_memleak.c`),该程序会故意泄漏内存以供测试使用。您可以编译并运行它来验证 memleak 是否能正确检测内存泄漏:

```console
$ make test_memleak
$ ./test_memleak &
$ sudo ./memleak -p $(pidof test_memleak)
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs... Hit Ctrl-C to end
[19:31:49] Top 1 stacks with outstanding allocations:
10240 bytes in 5 allocations from stack
0 [<00005a7ea0a34212>] leak_with_loop+0x1f
1 [<00005a7ea0a3428e>] main+0x6e
2 [<00007b4ea482a1ca>] <null sym>
3 [<00007b4ea482a28b>] __libc_start_main+0x8b
4 [<00005a7ea0a34105>] _start+0x25
```

如上所示,memleak 成功地:
- 检测到 **10240 字节的 5 次分配**(5 × 2048 字节)发生了泄漏
- 识别出泄漏函数 **leak_with_loop+0x1f** 及其正确的偏移量
- 提供了完整的调用栈,显示泄漏源自 `main` 函数
- 使用新的 **blazesym v0.2.0** C API 进行快速准确的符号解析

该测试演示了更新 blazesym 库后的 memleak 可以有效地跟踪内存分配,并精确定位导致内存泄漏的函数。

## 总结

通过本篇 eBPF 入门实践教程,您已经学习了如何编写 Memleak eBPF 监控程序,以实时监控程序的内存泄漏。您已经了解了 eBPF 在内存监控方面的应用,学会了使用 BPF API 编写 eBPF 程序,创建和使用 eBPF maps,并且明白了如何用 eBPF 工具监测和分析内存泄漏问题。我们展示了一个详细的例子,帮助您理解 eBPF 代码的运行流程和原理。
Expand Down
94 changes: 50 additions & 44 deletions src/16-memleak/memleak.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ static int event_notify(int fd, uint64_t event);

static pid_t fork_sync_exec(const char *command, int fd);

static void print_stack_frame_by_blazesym(size_t frame, uint64_t addr, const blazesym_csym *sym);
static void print_stack_frame_by_blazesym(size_t frame, uint64_t addr, const struct blaze_sym *sym);
static void print_stack_frames_by_blazesym();
static int print_stack_frames(struct allocation *allocs, size_t nr_allocs, int stack_traces_fd);

Expand Down Expand Up @@ -205,8 +205,7 @@ static struct sigaction sig_action = {

static int child_exec_event_fd = -1;

static blazesym *symbolizer;
static sym_src_cfg src_cfg;
static blaze_symbolizer *symbolizer;
static void (*print_stack_frames_func)();

static uint64_t *stack;
Expand Down Expand Up @@ -302,14 +301,8 @@ int main(int argc, char *argv[])
goto cleanup;
}

if (env.pid < 0) {
src_cfg.src_type = SRC_T_KERNEL;
src_cfg.params.kernel.kallsyms = NULL;
src_cfg.params.kernel.kernel_image = NULL;
} else {
src_cfg.src_type = SRC_T_PROCESS;
src_cfg.params.process.pid = env.pid;
}
// Symbolization source is now set up in print_stack_frames_by_blazesym()
// based on env.kernel_trace and env.pid

// allocate space for storing "allocation" structs
if (env.combined_only)
Expand Down Expand Up @@ -395,9 +388,9 @@ int main(int argc, char *argv[])
}
}

symbolizer = blazesym_new();
symbolizer = blaze_symbolizer_new();
if (!symbolizer) {
fprintf(stderr, "Failed to load blazesym\n");
fprintf(stderr, "Failed to create blazesym symbolizer\n");
ret = -ENOMEM;

goto cleanup;
Expand Down Expand Up @@ -439,7 +432,7 @@ int main(int argc, char *argv[])
}

cleanup:
blazesym_free(symbolizer);
blaze_symbolizer_free(symbolizer);
memleak_bpf__destroy(skel);

free(allocs);
Expand Down Expand Up @@ -636,54 +629,67 @@ pid_t fork_sync_exec(const char *command, int fd)
return pid;
}

void print_stack_frame_by_blazesym(size_t frame, uint64_t addr, const blazesym_csym *sym)
void print_stack_frame_by_blazesym(size_t frame, uint64_t addr, const struct blaze_sym *sym)
{
if (!sym)
if (!sym || !sym->name)
printf("\t%zu [<%016lx>] <%s>\n", frame, addr, "null sym");
else if (sym->path && strlen(sym->path))
printf("\t%zu [<%016lx>] %s+0x%lx %s:%ld\n", frame, addr, sym->symbol, addr - sym->start_address, sym->path, sym->line_no);
else if (sym->code_info.file && strlen(sym->code_info.file))
printf("\t%zu [<%016lx>] %s+0x%zx %s:%u\n", frame, addr, sym->name, sym->offset, sym->code_info.file, sym->code_info.line);
else
printf("\t%zu [<%016lx>] %s+0x%lx\n", frame, addr, sym->symbol, addr - sym->start_address);
printf("\t%zu [<%016lx>] %s+0x%zx\n", frame, addr, sym->name, sym->offset);
}

void print_stack_frames_by_blazesym()
{
const blazesym_result *result = blazesym_symbolize(symbolizer, &src_cfg, 1, stack, env.perf_max_stack_depth);
const struct blaze_syms *syms;

// Choose symbolization source based on kernel_trace flag
if (env.kernel_trace) {
const struct blaze_symbolize_src_kernel src = {
.type_size = sizeof(src),
.kallsyms = NULL,
.vmlinux = NULL,
.debug_syms = false,
.reserved = {0},
};
syms = blaze_symbolize_kernel_abs_addrs(symbolizer, &src, stack, env.perf_max_stack_depth);
} else {
const struct blaze_symbolize_src_process src = {
.type_size = sizeof(src),
.pid = env.pid,
};
syms = blaze_symbolize_process_abs_addrs(symbolizer, &src, stack, env.perf_max_stack_depth);
}

if (!syms) {
fprintf(stderr, "Failed to symbolize addresses\n");
return;
}

for (size_t j = 0; j < result->size; ++j) {
for (size_t j = 0; j < syms->cnt; ++j) {
const uint64_t addr = stack[j];

if (addr == 0)
break;

// no symbol found
if (!result || j >= result->size || result->entries[j].size == 0) {
print_stack_frame_by_blazesym(j, addr, NULL);
const struct blaze_sym *sym = &syms->syms[j];

continue;
}
// Print main symbol
print_stack_frame_by_blazesym(j, addr, sym);

// single symbol found
if (result->entries[j].size == 1) {
const blazesym_csym *sym = &result->entries[j].syms[0];
print_stack_frame_by_blazesym(j, addr, sym);

continue;
}

// multi symbol found
printf("\t%zu [<%016lx>] (%lu entries)\n", j, addr, result->entries[j].size);

for (size_t k = 0; k < result->entries[j].size; ++k) {
const blazesym_csym *sym = &result->entries[j].syms[k];
if (sym->path && strlen(sym->path))
printf("\t\t%s@0x%lx %s:%ld\n", sym->symbol, sym->start_address, sym->path, sym->line_no);
else
printf("\t\t%s@0x%lx\n", sym->symbol, sym->start_address);
// Print inlined function calls if any
if (sym->inlined_cnt > 0) {
for (size_t k = 0; k < sym->inlined_cnt; ++k) {
const struct blaze_symbolize_inlined_fn *inlined = &sym->inlined[k];
if (inlined->code_info.file && strlen(inlined->code_info.file))
printf("\t\t[inlined] %s %s:%u\n", inlined->name, inlined->code_info.file, inlined->code_info.line);
else
printf("\t\t[inlined] %s\n", inlined->name);
}
}
}

blazesym_result_free(result);
blaze_syms_free(syms);
}

int print_stack_frames(struct allocation *allocs, size_t nr_allocs, int stack_traces_fd)
Expand Down
44 changes: 44 additions & 0 deletions src/16-memleak/test_memleak.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Test program for memleak - intentionally leaks memory for testing
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

void leak_small() {
malloc(1024);
}

void leak_large() {
malloc(8192);
}

void leak_with_loop() {
for (int i = 0; i < 5; i++) {
malloc(2048);
}
}

int main() {
printf("Memory leak test starting (PID: %d)\n", getpid());
printf("This program intentionally leaks memory for testing memleak\n");

// Wait a bit for memleak to attach
sleep(2);

// Create various leaks
leak_small();
sleep(1);

leak_large();
sleep(1);

leak_with_loop();
sleep(1);

// Keep running so memleak can observe
printf("Leaks created, sleeping for 30 seconds...\n");
sleep(30);

printf("Test complete\n");
return 0;
}
1 change: 1 addition & 0 deletions src/32-wallclock-profiler/.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
level=Depth
25 changes: 25 additions & 0 deletions src/32-wallclock-profiler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/.output
/bootstrap
/profile
/offcputime
/oncputime
res.txt
*.o
oncpu.txt
offcpu.txt
FlameGraph
pmumon
test_combined
pmumon_advanced
/demo_results.svg
demo_results.folded
test_program
combined_profile_pid*.svg
combined_profile_pid*.folded
combined_profile_pid*.txt
combined_flamegraph.pl
multithread_combined_profile_pid*
demo_results_single_thread_analysis.txt
test_multithread
perf.data*
double_bandwidth
Loading