diff --git a/crates/memtrack/src/ebpf/c/allocator.h b/crates/memtrack/src/ebpf/c/allocator.h index 440d7a87..648786ec 100644 --- a/crates/memtrack/src/ebpf/c/allocator.h +++ b/crates/memtrack/src/ebpf/c/allocator.h @@ -90,6 +90,57 @@ UPROBE_ARG_RET(aligned_alloc, PT_REGS_PARM2(ctx), UPROBE_ARG_RET(memalign, PT_REGS_PARM2(ctx), { return submit_aligned_alloc_event(arg0, ret_val); }) +/* + * posix_memalign(void** memptr, size_t alignment, size_t size) + * + * Unlike memalign/aligned_alloc, it returns int 0 on SUCCESS (nonzero errno on + * failure) and delivers the pointer through the memptr out-parameter rather + * than the return register. So the size lives in PARM3 (not PARM2), success is + * ret == 0 (not a non-NULL return), and the address must be read back from + * *memptr once the call returns. + */ +struct posix_memalign_args_t { + __u64 memptr; + __u64 size; +}; +BPF_HASH_MAP(posix_memalign_args, __u64, struct posix_memalign_args_t, 10000); + +SEC("uprobe") +int uprobe_posix_memalign(struct pt_regs* ctx) { + __u64 tid = bpf_get_current_pid_tgid(); + __u32 pid = tid >> 32; + if (!is_tracked(pid)) { + return 0; + } + + struct posix_memalign_args_t args = {.memptr = PT_REGS_PARM1(ctx), .size = PT_REGS_PARM3(ctx)}; + bpf_map_update_elem(&posix_memalign_args, &tid, &args, BPF_ANY); + return 0; +} + +SEC("uretprobe") +int uretprobe_posix_memalign(struct pt_regs* ctx) { + __u64 tid = bpf_get_current_pid_tgid(); + struct posix_memalign_args_t* args = bpf_map_lookup_elem(&posix_memalign_args, &tid); + if (!args) { + return 0; + } + + struct posix_memalign_args_t a = *args; + bpf_map_delete_elem(&posix_memalign_args, &tid); + + if (PT_REGS_RC(ctx) != 0) { + return 0; + } + + __u64 addr = 0; + if (bpf_probe_read_user(&addr, sizeof(addr), (void*)a.memptr) != 0 || addr == 0) { + return 0; + } + + return submit_aligned_alloc_event(a.size, addr); +} + struct mmap_args { __u64 addr; __u64 len; diff --git a/crates/memtrack/src/ebpf/memtrack/allocator.rs b/crates/memtrack/src/ebpf/memtrack/allocator.rs index 410914c5..e858fc58 100644 --- a/crates/memtrack/src/ebpf/memtrack/allocator.rs +++ b/crates/memtrack/src/ebpf/memtrack/allocator.rs @@ -16,6 +16,11 @@ impl MemtrackBpf { uretprobe_aligned_alloc ); attach_uprobe_uretprobe!(attach_memalign, uprobe_memalign, uretprobe_memalign); + attach_uprobe_uretprobe!( + attach_posix_memalign, + uprobe_posix_memalign, + uretprobe_posix_memalign + ); attach_uprobe!(attach_free, uprobe_free); /// Attach probes for every discovered allocator library. @@ -151,7 +156,7 @@ impl MemtrackBpf { &format!("{prefix}memalign{suffix}"), offsets, )?; - self.attach_memalign_if_found( + self.attach_posix_memalign_if_found( lib_path, &format!("{prefix}posix_memalign{suffix}"), offsets, diff --git a/crates/memtrack/testdata/alloc_cpp/src/main.cpp b/crates/memtrack/testdata/alloc_cpp/src/main.cpp index 14b5f4ad..ec726d7f 100644 --- a/crates/memtrack/testdata/alloc_cpp/src/main.cpp +++ b/crates/memtrack/testdata/alloc_cpp/src/main.cpp @@ -52,5 +52,12 @@ int main() { black_box(aligned); free(aligned); + // posix_memalign: some allocators forward it to another exported symbol + // that is probed too, so this also guards against reporting it twice. + void* aligned_out = nullptr; + posix_memalign(&aligned_out, 128, 64 * 256); + black_box(aligned_out); + free(aligned_out); + emit_marker(); } diff --git a/crates/memtrack/testdata/posix_memalign_einval.c b/crates/memtrack/testdata/posix_memalign_einval.c new file mode 100644 index 00000000..465e0c74 --- /dev/null +++ b/crates/memtrack/testdata/posix_memalign_einval.c @@ -0,0 +1,17 @@ +#include +#include + +/* + * posix_memalign returns nonzero (EINVAL) when the alignment is not a power of + * two multiple of sizeof(void*), leaving *memptr untouched. The uretprobe must + * drop such calls (ret != 0). The trailing malloc proves tracking is still live, + * so an empty AlignedAlloc means "dropped", not "never attached". + */ +int main() { + sleep(1); + void* p = NULL; + posix_memalign(&p, 3, 768); + void* q = malloc(512); + free(q); + return 0; +} diff --git a/crates/memtrack/testdata/posix_memalign_test.c b/crates/memtrack/testdata/posix_memalign_test.c new file mode 100644 index 00000000..c1238135 --- /dev/null +++ b/crates/memtrack/testdata/posix_memalign_test.c @@ -0,0 +1,16 @@ +#include +#include + +/* + * posix_memalign has an inverted ABI vs memalign/aligned_alloc: it returns + * int 0 on SUCCESS and delivers the pointer through the memptr out-parameter. + * The memalign uretprobe skips any call returning 0 (a NULL malloc-style + * return = failure), so successful posix_memalign calls must not be dropped. + */ +int main() { + sleep(1); + void* p = NULL; + posix_memalign(&p, 128, 768); + free(p); + return 0; +} diff --git a/crates/memtrack/tests/c_tests.rs b/crates/memtrack/tests/c_tests.rs index 736e1a6a..d9952a77 100644 --- a/crates/memtrack/tests/c_tests.rs +++ b/crates/memtrack/tests/c_tests.rs @@ -69,6 +69,14 @@ const ALLOCATION_TEST_CASES: &[AllocationTestCase] = &[ name: "alloc_size", source: include_str!("../testdata/alloc_size.c"), }, + AllocationTestCase { + name: "posix_memalign_test", + source: include_str!("../testdata/posix_memalign_test.c"), + }, + AllocationTestCase { + name: "posix_memalign_einval", + source: include_str!("../testdata/posix_memalign_einval.c"), + }, ]; #[test_with::env(GITHUB_ACTIONS)] @@ -81,6 +89,8 @@ const ALLOCATION_TEST_CASES: &[AllocationTestCase] = &[ #[case(&ALLOCATION_TEST_CASES[5])] #[case(&ALLOCATION_TEST_CASES[6])] #[case(&ALLOCATION_TEST_CASES[7])] +#[case(&ALLOCATION_TEST_CASES[8])] +#[case(&ALLOCATION_TEST_CASES[9])] #[test_log::test] fn test_allocation_tracking( #[case] test_case: &AllocationTestCase, diff --git a/crates/memtrack/tests/snapshots/c_tests__posix_memalign_einval.snap b/crates/memtrack/tests/snapshots/c_tests__posix_memalign_einval.snap new file mode 100644 index 00000000..be8f5f32 --- /dev/null +++ b/crates/memtrack/tests/snapshots/c_tests__posix_memalign_einval.snap @@ -0,0 +1,8 @@ +--- +source: crates/memtrack/tests/c_tests.rs +expression: formatted_events +--- +[ + "Malloc { size: 512 }", + "Free", +] diff --git a/crates/memtrack/tests/snapshots/c_tests__posix_memalign_test.snap b/crates/memtrack/tests/snapshots/c_tests__posix_memalign_test.snap new file mode 100644 index 00000000..70c2294a --- /dev/null +++ b/crates/memtrack/tests/snapshots/c_tests__posix_memalign_test.snap @@ -0,0 +1,8 @@ +--- +source: crates/memtrack/tests/c_tests.rs +expression: formatted_events +--- +[ + "AlignedAlloc { size: 768 }", + "Free", +] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_dynamic.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_dynamic.snap index ced328f7..153dfb5e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_dynamic.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_dynamic.snap @@ -10,4 +10,6 @@ expression: formatted_events "Malloc { size: 88888 }", "AlignedAlloc { size: 32768 }", "Free", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_static.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_static.snap index ced328f7..153dfb5e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_static.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_jemalloc_static.snap @@ -10,4 +10,6 @@ expression: formatted_events "Malloc { size: 88888 }", "AlignedAlloc { size: 32768 }", "Free", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_dynamic.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_dynamic.snap index 8c180624..2428c93e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_dynamic.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_dynamic.snap @@ -11,4 +11,7 @@ expression: formatted_events "Malloc { size: 32768 }", "AlignedAlloc { size: 32768 }", "Free", + "Malloc { size: 16384 }", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_static.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_static.snap index 8c180624..2428c93e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_static.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_mimalloc_static.snap @@ -11,4 +11,7 @@ expression: formatted_events "Malloc { size: 32768 }", "AlignedAlloc { size: 32768 }", "Free", + "Malloc { size: 16384 }", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_system.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_system.snap index ced328f7..153dfb5e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_system.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_system.snap @@ -10,4 +10,6 @@ expression: formatted_events "Malloc { size: 88888 }", "AlignedAlloc { size: 32768 }", "Free", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_dynamic.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_dynamic.snap index ced328f7..153dfb5e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_dynamic.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_dynamic.snap @@ -10,4 +10,6 @@ expression: formatted_events "Malloc { size: 88888 }", "AlignedAlloc { size: 32768 }", "Free", + "AlignedAlloc { size: 16384 }", + "Free", ] diff --git a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_static.snap b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_static.snap index ced328f7..153dfb5e 100644 --- a/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_static.snap +++ b/crates/memtrack/tests/snapshots/cpp_tests__alloc_cpp_tcmalloc_static.snap @@ -10,4 +10,6 @@ expression: formatted_events "Malloc { size: 88888 }", "AlignedAlloc { size: 32768 }", "Free", + "AlignedAlloc { size: 16384 }", + "Free", ]