Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

memleak: the symbol of the function that calls new disappeared #4958

Open
Bojun-Seo opened this issue Apr 8, 2024 · 2 comments
Open

memleak: the symbol of the function that calls new disappeared #4958

Bojun-Seo opened this issue Apr 8, 2024 · 2 comments

Comments

@Bojun-Seo
Copy link
Contributor

Bojun-Seo commented Apr 8, 2024

The symbol of the function that calls new disappeared on memleak tool.

Test file leak_loop_new.cpp

#include <chrono>
#include <thread>
int main(int argc, char* argv[]) {
  while (true) {
    auto p = new int;
    std::this_thread::sleep_for(std::chrono::seconds(5));
  }
  return 0;
}

Compile and run the file leak_loop_new.cpp and run memleak

$ g++ leak_loop_new.cpp
$ ./a.out &
[1] 322617
$ sudo ./memleak -p 322617
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs...  Hit Ctrl-C to end
[9:52:1] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
        0 [<00007f0bb90ae98c>] _Znwm+0x1c
        1 [<00007f0bb8c29d90>] __libc_init_first+0x90
[9:52:6] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<00007f0bb90ae98c>] _Znwm+0x1c
        1 [<00007f0bb8c29d90>] __libc_init_first+0x90
^C[9:52:6] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<00007f0bb90ae98c>] _Znwm+0x1c
        1 [<00007f0bb8c29d90>] __libc_init_first+0x90
done

It is obvious that new is called in main function but it is just disappeared.
_Znwm is a mangled name of operator new by the way.
The result of python version memleak is same, except demangle.

$ sudo python3 memleak.py -p 322617
Attaching to pid 322617, Ctrl+C to quit.
[09:55:28] Top 10 stacks with outstanding allocations:
        4 bytes in 1 allocations from stack
                0x00007f0bb90ae98c      operator new(unsigned long)+0x1c [libstdc++.so.6.0.30]
                0x00007f0bb8c29d90      __libc_start_call_main+0x80 [libc.so.6]
[09:55:33] Top 10 stacks with outstanding allocations:
        8 bytes in 2 allocations from stack
                0x00007f0bb90ae98c      operator new(unsigned long)+0x1c [libstdc++.so.6.0.30]
                0x00007f0bb8c29d90      __libc_start_call_main+0x80 [libc.so.6]

You can get normal symbol(backtrace) if you use malloc instead of new.
See following test.

Test file leak_loop_malloc.cpp

$ cat leak_loop_malloc.cpp
#include <chrono>
#include <thread>
#include <cstdlib>
int main(int argc, char* argv[]) {
  while (true) {
    auto p = static_cast<int*>(malloc(sizeof(int)));
    std::this_thread::sleep_for(std::chrono::seconds(5));
  }
  return 0;
}

Compile and run the file leak_loop_malloc.cpp and run memleak

$ g++ leak_loop_malloc.cpp
$ ./a.out &
[1] 322727
$ sudo ./memleak -p 322727
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs...  Hit Ctrl-C to end
[10:14:43] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
        0 [<000055bddfee31d5>] main+0x2c
        1 [<00007f7298e29d90>] __libc_init_first+0x90
[10:14:48] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<000055bddfee31d5>] main+0x2c
        1 [<00007f7298e29d90>] __libc_init_first+0x90
^C[10:14:49] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<000055bddfee31d5>] main+0x2c
        1 [<00007f7298e29d90>] __libc_init_first+0x90
done

Some may think that it can happened only on special main function.
So I added foo function and you can check that foo is disappeared in this time.

Test file leak_loop_foo.cpp

#include <chrono>
#include <thread>
int* foo() { return new int; }
int main(int argc, char* argv[]) {
  while (true) {
    auto p = foo();
    std::this_thread::sleep_for(std::chrono::seconds(5));
  }
  return 0;
}

Compile and run the file leak_loop_foo.cpp and run memleak

$ sudo ./memleak -p 322693
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs...  Hit Ctrl-C to end
[9:59:28] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
        0 [<00007f88376ae98c>] _Znwm+0x1c
        1 [<0000562fdbf6a1e4>] main+0x27
        2 [<00007f8837229d90>] __libc_init_first+0x90
[9:59:33] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<00007f88376ae98c>] _Znwm+0x1c
        1 [<0000562fdbf6a1e4>] main+0x27
        2 [<00007f8837229d90>] __libc_init_first+0x90
^C[9:59:33] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
        0 [<00007f88376ae98c>] _Znwm+0x1c
        1 [<0000562fdbf6a1e4>] main+0x27
        2 [<00007f8837229d90>] __libc_init_first+0x90
done

You can find main but cannot find foo.
Some may think that maybe compiler optimize out the foo function.
But no. It is not optimized out. It exists inside the binary.
And it is exactly pointing out the return address of foo function(0x11e4).
main function start address(0x11bd) + offset(0x27) = 0x11e4

$ objdump -D a.out
... snip ...
00000000000011bd <main>:
    11bd:       f3 0f 1e fa             endbr64
    11c1:       55                      push   %rbp
    11c2:       48 89 e5                mov    %rsp,%rbp
    11c5:       48 83 ec 30             sub    $0x30,%rsp
    11c9:       89 7d dc                mov    %edi,-0x24(%rbp)
    11cc:       48 89 75 d0             mov    %rsi,-0x30(%rbp)
    11d0:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    11d7:       00 00
    11d9:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    11dd:       31 c0                   xor    %eax,%eax
    11df:       e8 c5 ff ff ff          call   11a9 <_Z3foov>
    11e4:       48 89 45 f0             mov    %rax,-0x10(%rbp)
    11e8:       c7 45 e4 05 00 00 00    movl   $0x5,-0x1c(%rbp)
    11ef:       48 8d 55 e4             lea    -0x1c(%rbp),%rdx
    11f3:       48 8d 45 e8             lea    -0x18(%rbp),%rax
    11f7:       48 89 d6                mov    %rdx,%rsi
    11fa:       48 89 c7                mov    %rax,%rdi
    11fd:       e8 8c 00 00 00          call   128e <_ZNSt6chrono8durationIlSt5ratioILl1ELl1EEEC1IivEERKT_>
    1202:       48 8d 45 e8             lea    -0x18(%rbp),%rax
    1206:       48 89 c7                mov    %rax,%rdi
    1209:       e8 ef 01 00 00          call   13fd <_ZNSt11this_thread9sleep_forIlSt5ratioILl1ELl1EEEEvRKNSt6chrono8durationIT_T0_EE>
    120e:       eb cf                   jmp    11df <main+0x22>

Environment

x86_64

$ cat /etc/issue.net
Ubuntu 22.04.3 LTS
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
@Bojun-Seo Bojun-Seo changed the title memleak: the symbol of the function that called new disappeared memleak: the symbol of the function that callsnew disappeared Apr 8, 2024
@Bojun-Seo Bojun-Seo changed the title memleak: the symbol of the function that callsnew disappeared memleak: the symbol of the function that calls new disappeared Apr 8, 2024
@yonghong-song
Copy link
Collaborator

The current memleak implementation only supports C functions like malloc/calloc/mmap/memalign/free/munmap etc. If you are interested, you could contribute to add C++ support.

@Bojun-Seo
Copy link
Contributor Author

Bojun-Seo commented Apr 18, 2024

The cause of this issue is that Linux kernel cannot unwind the stack frame of operator new function.

Kernel uses rbp registers to unwind the stack.
You can check that on file arch/x86/events/core.c in Linux kernel, at line 2875

2858 void
2859 perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs)
2860 {
2861         struct stack_frame frame;
2862         const struct stack_frame __user *fp;
2863
2864         if (perf_guest_state()) {
2865                 /* TODO: We don't support guest os callchain now */
2866                 return;
2867         }
2868
2869         /*
2870          * We don't know what to do with VM86 stacks.. ignore them for now.
2871          */
2872         if (regs->flags & (X86_VM_MASK | PERF_EFLAGS_VM))
2873                 return;
2874
2875         fp = (void __user *)regs->bp;
2876
2877         perf_callchain_store(entry, regs->ip);
2878

But operator new function doesn't push and pop rbp register on its prologue and epilogue.
_Znwm is a mangled name of operator new.

00000000000ae970 <_Znwm@@GLIBCXX_3.4>:
   ae970:       f3 0f 1e fa             endbr64
   ae974:       48 85 ff                test   %rdi,%rdi
   ae977:       b8 01 00 00 00          mov    $0x1,%eax
   ae97c:       53                      push   %rbx
   ae97d:       48 0f 45 c7             cmovne %rdi,%rax
   ae981:       48 89 c3                mov    %rax,%rbx
   ae984:       48 89 df                mov    %rbx,%rdi
   ae987:       e8 44 07 ff ff          call   9f0d0 <malloc@plt>
   ae98c:       48 85 c0                test   %rax,%rax
   ae98f:       74 02                   je     ae993 <_Znwm@@GLIBCXX_3.4+0x23>
   ae991:       5b                      pop    %rbx
   ae992:       c3                      ret
   ae993:       e8 88 07 ff ff          call   9f120 <_ZSt15get_new_handlerv@plt>
   ae998:       48 85 c0                test   %rax,%rax
   ae99b:       0f 84 dd 3d ff ff       je     a277e <__cxa_throw_bad_array_new_length@@CXXABI_1.3.8+0x132>
   ae9a1:       ff d0                   call   *%rax
   ae9a3:       eb df                   jmp    ae984 <_Znwm@@GLIBCXX_3.4+0x14>
   ae9a5:       66 2e 0f 1f 84 00 00    cs nopw 0x0(%rax,%rax,1)
   ae9ac:       00 00 00
   ae9af:       90                      nop

The first backrace is acquired by pc register.
And others use stack and rbp to unwind the stack.
Since operator new function doesn't touch rbp register.
The value inside rbp register is same as if it is currently in the function that calls operator new.

For example, imagine the call chain looks like this: main -> foo -> operator new -> malloc
As memleak saves stack backtrace information on uretprobe of malloc function.
So current pc is inside operator new function. And the rbp value is same as when the pc register is inside foo function.
perf_callchain_user function acquires top stack by using pc register.
And it uses stack and rbp to unwind others, and at this point, it thinks foo is the top frame.
So it doesn't print top frame because top frame is already printed by using pc.

I found that following feature could resolve this issue.
#4463

I patched memleak like followings, on that branch.
(It seems complicated but it's not. Almost every change is to change skel string to obj)

diff --git a/libbpf-tools/memleak.bpf.c b/libbpf-tools/memleak.bpf.c
index cb13fdd8..9d7d7da3 100644
--- a/libbpf-tools/memleak.bpf.c
+++ b/libbpf-tools/memleak.bpf.c
@@ -7,6 +7,7 @@
 #include "maps.bpf.h"
 #include "memleak.h"
 #include "core_fixes.bpf.h"
+#include "unwind.bpf.h"

 const volatile size_t min_size = 0;
 const volatile size_t max_size = -1;
@@ -122,7 +123,8 @@ static int gen_alloc_exit2(void *ctx, u64 address)
        if (address != 0) {
                info.timestamp_ns = bpf_ktime_get_ns();

-               info.stack_id = bpf_get_stackid(ctx, &stack_traces, stack_flags);
+               //info.stack_id = bpf_get_stackid(ctx, &stack_traces, stack_flags);
+               info.stack_id = uw_get_stackid();

                bpf_map_update_elem(&allocs, &address, &info, BPF_ANY);

diff --git a/libbpf-tools/memleak.c b/libbpf-tools/memleak.c
index a2c7d1cd..bab2b9e0 100644
--- a/libbpf-tools/memleak.c
+++ b/libbpf-tools/memleak.c
@@ -23,6 +23,7 @@
 #include "memleak.h"
 #include "memleak.skel.h"
 #include "trace_helpers.h"
+#include "unwind_helpers.h"

 #ifdef USE_BLAZESYM
 #include "blazesym.h"
@@ -86,38 +87,38 @@ struct allocation {
        struct allocation_node* allocations;
 };

-#define __ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe) \
+#define __ATTACH_UPROBE(obj, sym_name, prog_name, is_retprobe) \
        do { \
                LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, \
                                .func_name = #sym_name, \
                                .retprobe = is_retprobe); \
-               skel->links.prog_name = bpf_program__attach_uprobe_opts( \
-                               skel->progs.prog_name, \
+               obj->links.prog_name = bpf_program__attach_uprobe_opts( \
+                               obj->progs.prog_name, \
                                env.pid, \
                                env.object, \
                                0, \
                                &uprobe_opts); \
        } while (false)

-#define __CHECK_PROGRAM(skel, prog_name) \
+#define __CHECK_PROGRAM(obj, prog_name) \
        do { \
-               if (!skel->links.prog_name) { \
+               if (!obj->links.prog_name) { \
                        perror("no program attached for " #prog_name); \
                        return -errno; \
                } \
        } while (false)

-#define __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, is_retprobe) \
+#define __ATTACH_UPROBE_CHECKED(obj, sym_name, prog_name, is_retprobe) \
        do { \
-               __ATTACH_UPROBE(skel, sym_name, prog_name, is_retprobe); \
-               __CHECK_PROGRAM(skel, prog_name); \
+               __ATTACH_UPROBE(obj, sym_name, prog_name, is_retprobe); \
+               __CHECK_PROGRAM(obj, prog_name); \
        } while (false)

-#define ATTACH_UPROBE(skel, sym_name, prog_name) __ATTACH_UPROBE(skel, sym_name, prog_name, false)
-#define ATTACH_URETPROBE(skel, sym_name, prog_name) __ATTACH_UPROBE(skel, sym_name, prog_name, true)
+#define ATTACH_UPROBE(obj, sym_name, prog_name) __ATTACH_UPROBE(obj, sym_name, prog_name, false)
+#define ATTACH_URETPROBE(obj, sym_name, prog_name) __ATTACH_UPROBE(obj, sym_name, prog_name, true)

-#define ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, false)
-#define ATTACH_URETPROBE_CHECKED(skel, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(skel, sym_name, prog_name, true)
+#define ATTACH_UPROBE_CHECKED(obj, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(obj, sym_name, prog_name, false)
+#define ATTACH_URETPROBE_CHECKED(obj, sym_name, prog_name) __ATTACH_UPROBE_CHECKED(obj, sym_name, prog_name, true)

 static void sig_handler(int signo);

@@ -147,11 +148,11 @@ static int print_outstanding_allocs(int allocs_fd, int stack_traces_fd);
 static int print_outstanding_combined_allocs(int combined_allocs_fd, int stack_traces_fd);

 static bool has_kernel_node_tracepoints();
-static void disable_kernel_node_tracepoints(struct memleak_bpf *skel);
-static void disable_kernel_percpu_tracepoints(struct memleak_bpf *skel);
-static void disable_kernel_tracepoints(struct memleak_bpf *skel);
+static void disable_kernel_node_tracepoints(struct memleak_bpf *obj);
+static void disable_kernel_percpu_tracepoints(struct memleak_bpf *obj);
+static void disable_kernel_tracepoints(struct memleak_bpf *obj);

-static int attach_uprobes(struct memleak_bpf *skel);
+static int attach_uprobes(struct memleak_bpf *obj);

 const char *argp_program_version = "memleak 0.1";
 const char *argp_program_bug_address =
@@ -229,7 +230,7 @@ static const char default_object[] = "libc.so.6";
 int main(int argc, char *argv[])
 {
        int ret = 0;
-       struct memleak_bpf *skel = NULL;
+       struct memleak_bpf *obj = NULL;

        static const struct argp argp = {
                .options = argp_options,
@@ -331,51 +332,52 @@ int main(int argc, char *argv[])

        libbpf_set_print(libbpf_print_fn);

-       skel = memleak_bpf__open();
-       if (!skel) {
+       obj = memleak_bpf__open();
+       if (!obj) {
                fprintf(stderr, "failed to open bpf object\n");
                ret = 1;

                goto cleanup;
        }

-       skel->rodata->min_size = env.min_size;
-       skel->rodata->max_size = env.max_size;
-       skel->rodata->page_size = env.page_size;
-       skel->rodata->sample_rate = env.sample_rate;
-       skel->rodata->trace_all = env.trace_all;
-       skel->rodata->stack_flags = env.kernel_trace ? 0 : BPF_F_USER_STACK;
-       skel->rodata->wa_missing_free = env.wa_missing_free;
+       obj->rodata->min_size = env.min_size;
+       obj->rodata->max_size = env.max_size;
+       obj->rodata->page_size = env.page_size;
+       obj->rodata->sample_rate = env.sample_rate;
+       obj->rodata->trace_all = env.trace_all;
+       obj->rodata->stack_flags = env.kernel_trace ? 0 : BPF_F_USER_STACK;
+       obj->rodata->wa_missing_free = env.wa_missing_free;

-       bpf_map__set_value_size(skel->maps.stack_traces,
+       bpf_map__set_value_size(obj->maps.stack_traces,
                                env.perf_max_stack_depth * sizeof(unsigned long));
-       bpf_map__set_max_entries(skel->maps.stack_traces, env.stack_map_max_entries);
+       bpf_map__set_max_entries(obj->maps.stack_traces, env.stack_map_max_entries);

+       UW_INIT(obj, 128, 10240);
        // disable kernel tracepoints based on settings or availability
        if (env.kernel_trace) {
                if (!has_kernel_node_tracepoints())
-                       disable_kernel_node_tracepoints(skel);
+                       disable_kernel_node_tracepoints(obj);

                if (!env.percpu)
-                       disable_kernel_percpu_tracepoints(skel);
+                       disable_kernel_percpu_tracepoints(obj);
        } else {
-               disable_kernel_tracepoints(skel);
+               disable_kernel_tracepoints(obj);
        }

-       ret = memleak_bpf__load(skel);
+       ret = memleak_bpf__load(obj);
        if (ret) {
                fprintf(stderr, "failed to load bpf object\n");

                goto cleanup;
        }

-       const int allocs_fd = bpf_map__fd(skel->maps.allocs);
-       const int combined_allocs_fd = bpf_map__fd(skel->maps.combined_allocs);
-       const int stack_traces_fd = bpf_map__fd(skel->maps.stack_traces);
+       const int allocs_fd = bpf_map__fd(obj->maps.allocs);
+       const int combined_allocs_fd = bpf_map__fd(obj->maps.combined_allocs);
+       const int stack_traces_fd = bpf_map__fd(obj->maps.stack_traces);

        // if userspace oriented, attach upbrobes
        if (!env.kernel_trace) {
-               ret = attach_uprobes(skel);
+               ret = attach_uprobes(obj);
                if (ret) {
                        fprintf(stderr, "failed to attach uprobes\n");

@@ -383,7 +385,7 @@ int main(int argc, char *argv[])
                }
        }

-       ret = memleak_bpf__attach(skel);
+       ret = memleak_bpf__attach(obj);
        if (ret) {
                fprintf(stderr, "failed to attach bpf program(s)\n");

@@ -476,7 +478,7 @@ cleanup:
        if (ksyms)
                ksyms__free(ksyms);
 #endif
-       memleak_bpf__destroy(skel);
+       memleak_bpf__destroy(obj);

        free(allocs);
        free(stack);
@@ -786,14 +788,7 @@ int print_stack_frames(struct allocation *allocs, size_t nr_allocs, int stack_tr
                        }
                }

-               if (bpf_map_lookup_elem(stack_traces_fd, &alloc->stack_id, stack)) {
-                       if (errno == ENOENT)
-                               continue;
-
-                       perror("failed to lookup stack trace");
-
-                       return -errno;
-               }
+               uw_map_lookup_elem(&alloc->stack_id, env.pid, stack, 32);

                (*print_stack_frames_func)();
        }
@@ -1004,68 +999,68 @@ bool has_kernel_node_tracepoints()
                tracepoint_exists("kmem", "kmem_cache_alloc_node");
 }

-void disable_kernel_node_tracepoints(struct memleak_bpf *skel)
+void disable_kernel_node_tracepoints(struct memleak_bpf *obj)
 {
-       bpf_program__set_autoload(skel->progs.memleak__kmalloc_node, false);
-       bpf_program__set_autoload(skel->progs.memleak__kmem_cache_alloc_node, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmalloc_node, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmem_cache_alloc_node, false);
 }

-void disable_kernel_percpu_tracepoints(struct memleak_bpf *skel)
+void disable_kernel_percpu_tracepoints(struct memleak_bpf *obj)
 {
-       bpf_program__set_autoload(skel->progs.memleak__percpu_alloc_percpu, false);
-       bpf_program__set_autoload(skel->progs.memleak__percpu_free_percpu, false);
+       bpf_program__set_autoload(obj->progs.memleak__percpu_alloc_percpu, false);
+       bpf_program__set_autoload(obj->progs.memleak__percpu_free_percpu, false);
 }

-void disable_kernel_tracepoints(struct memleak_bpf *skel)
+void disable_kernel_tracepoints(struct memleak_bpf *obj)
 {
-       bpf_program__set_autoload(skel->progs.memleak__kmalloc, false);
-       bpf_program__set_autoload(skel->progs.memleak__kmalloc_node, false);
-       bpf_program__set_autoload(skel->progs.memleak__kfree, false);
-       bpf_program__set_autoload(skel->progs.memleak__kmem_cache_alloc, false);
-       bpf_program__set_autoload(skel->progs.memleak__kmem_cache_alloc_node, false);
-       bpf_program__set_autoload(skel->progs.memleak__kmem_cache_free, false);
-       bpf_program__set_autoload(skel->progs.memleak__mm_page_alloc, false);
-       bpf_program__set_autoload(skel->progs.memleak__mm_page_free, false);
-       bpf_program__set_autoload(skel->progs.memleak__percpu_alloc_percpu, false);
-       bpf_program__set_autoload(skel->progs.memleak__percpu_free_percpu, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmalloc, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmalloc_node, false);
+       bpf_program__set_autoload(obj->progs.memleak__kfree, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmem_cache_alloc, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmem_cache_alloc_node, false);
+       bpf_program__set_autoload(obj->progs.memleak__kmem_cache_free, false);
+       bpf_program__set_autoload(obj->progs.memleak__mm_page_alloc, false);
+       bpf_program__set_autoload(obj->progs.memleak__mm_page_free, false);
+       bpf_program__set_autoload(obj->progs.memleak__percpu_alloc_percpu, false);
+       bpf_program__set_autoload(obj->progs.memleak__percpu_free_percpu, false);
 }

-int attach_uprobes(struct memleak_bpf *skel)
+int attach_uprobes(struct memleak_bpf *obj)
 {
-       ATTACH_UPROBE_CHECKED(skel, malloc, malloc_enter);
-       ATTACH_URETPROBE_CHECKED(skel, malloc, malloc_exit);
+       ATTACH_UPROBE_CHECKED(obj, malloc, malloc_enter);
+       ATTACH_URETPROBE_CHECKED(obj, malloc, malloc_exit);

-       ATTACH_UPROBE_CHECKED(skel, calloc, calloc_enter);
-       ATTACH_URETPROBE_CHECKED(skel, calloc, calloc_exit);
+       ATTACH_UPROBE_CHECKED(obj, calloc, calloc_enter);
+       ATTACH_URETPROBE_CHECKED(obj, calloc, calloc_exit);

-       ATTACH_UPROBE_CHECKED(skel, realloc, realloc_enter);
-       ATTACH_URETPROBE_CHECKED(skel, realloc, realloc_exit);
+       ATTACH_UPROBE_CHECKED(obj, realloc, realloc_enter);
+       ATTACH_URETPROBE_CHECKED(obj, realloc, realloc_exit);

-       ATTACH_UPROBE_CHECKED(skel, mmap, mmap_enter);
-       ATTACH_URETPROBE_CHECKED(skel, mmap, mmap_exit);
+       ATTACH_UPROBE_CHECKED(obj, mmap, mmap_enter);
+       ATTACH_URETPROBE_CHECKED(obj, mmap, mmap_exit);

-       ATTACH_UPROBE_CHECKED(skel, posix_memalign, posix_memalign_enter);
-       ATTACH_URETPROBE_CHECKED(skel, posix_memalign, posix_memalign_exit);
+       ATTACH_UPROBE_CHECKED(obj, posix_memalign, posix_memalign_enter);
+       ATTACH_URETPROBE_CHECKED(obj, posix_memalign, posix_memalign_exit);

-       ATTACH_UPROBE_CHECKED(skel, memalign, memalign_enter);
-       ATTACH_URETPROBE_CHECKED(skel, memalign, memalign_exit);
+       ATTACH_UPROBE_CHECKED(obj, memalign, memalign_enter);
+       ATTACH_URETPROBE_CHECKED(obj, memalign, memalign_exit);

-       ATTACH_UPROBE_CHECKED(skel, free, free_enter);
-       ATTACH_UPROBE_CHECKED(skel, munmap, munmap_enter);
+       ATTACH_UPROBE_CHECKED(obj, free, free_enter);
+       ATTACH_UPROBE_CHECKED(obj, munmap, munmap_enter);

        // the following probes are intentinally allowed to fail attachment

        // deprecated in libc.so bionic
-       ATTACH_UPROBE(skel, valloc, valloc_enter);
-       ATTACH_URETPROBE(skel, valloc, valloc_exit);
+       ATTACH_UPROBE(obj, valloc, valloc_enter);
+       ATTACH_URETPROBE(obj, valloc, valloc_exit);

        // deprecated in libc.so bionic
-       ATTACH_UPROBE(skel, pvalloc, pvalloc_enter);
-       ATTACH_URETPROBE(skel, pvalloc, pvalloc_exit);
+       ATTACH_UPROBE(obj, pvalloc, pvalloc_enter);
+       ATTACH_URETPROBE(obj, pvalloc, pvalloc_exit);

        // added in C11
-       ATTACH_UPROBE(skel, aligned_alloc, aligned_alloc_enter);
-       ATTACH_URETPROBE(skel, aligned_alloc, aligned_alloc_exit);
+       ATTACH_UPROBE(obj, aligned_alloc, aligned_alloc_enter);
+       ATTACH_URETPROBE(obj, aligned_alloc, aligned_alloc_exit);


        return 0;

Now memleak report prints foo function.

root@ubuntu-Standard-PC-i440FX-PIIX-1996:~# ./memleak -p 8081
using default object: libc.so.6
using page size: 4096
tracing kernel: false
libbpf: Error in bpf_create_map_xattr(uw_stacks):Invalid argument(-22). Retrying
without BTF.
Tracing outstanding memory allocs... Hit Ctrl-C to end
^C[1:43:4] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
get_entries, err: 0
0 [<00007f348d6e1dad>] _Znwm+0x1d [/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19]
1 [<000055863a200798>] _Z3foov+0xe [/home/worker/a.out]
2 [<000055863a2007de>] main+0x44 [/home/worker/a.out]
3 [<00007f348d2dfec5>] __libc_start_main+0xf5 [/lib/x86_64-linux-gnu/libc-2.19.so]
4 [<000055863a2006aa>] _start+0x2a [/home/worker/a.out]
done
root@ubuntu-Standard-PC-i440FX-PIIX-1996:~#

In this test program

#include <chrono>
#include <thread>
int* foo() { return new int; }
int main(int argc, char* argv[]) {
  while (true) {
    auto p = foo();
    std::this_thread::sleep_for(std::chrono::seconds(5));
  }
  return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants