Skip to content

Commit

Permalink
libbpf-tools: Add new feature lsan on libbpf-tools
Browse files Browse the repository at this point in the history
Add leaksanitizer(lsan) feature on libbpf-tools
lsan feature originally comes from llvm-project
https://github.com/llvm/llvm-project
This tool detect and report unreachable memory periodically

USAGE:

  $ ./lsan -h
  Usage: lsan [OPTION...]
  Detect memory leak resulting from unreachable pointers.

  Either -c or -p is a mandatory option
  EXAMPLES:
      lsan -p 1234             # Detect leaks on process id 1234
      lsan -c a.out            # Detect leaks on a.out
      lsan -c 'a.out arg'      # Detect leaks on a.out with argument

    -c, --command=COMMAND      Execute and detect memory leak on the specified
                               command
    -i, --interval=INTERVAL    Set interval in second to detect leak
    -p, --pid=PID              Detect memory leak on the specified process
    -s, --suppressions=SUPPRESSIONS
                               Suppressions file name
    -T, --top=TOP              Report only specified amount of backtraces
    -v, --verbose              Verbose debug output
    -w, --stop-the-world       Stop the target process during tracing
    -?, --help                 Give this help list
        --usage                Give a short usage message
    -V, --version              Print program version

  Mandatory or optional arguments to long options are also mandatory or optional
  for any corresponding short options.

  Report bugs to https://github.com/iovisor/bcc/tree/master/libbpf-tools.

Report example:

  $ sudo ./lsan -p 28346

  [2024-05-22 14:44:58] Print leaks:
  44 bytes direct leak found in 1 allocations from stack id(57214)
          iovisor#1 0x00583bca1b2250 baz+0x1c (/home/bojun/lsan/libbpf-tools/a.out+0x1250)
          iovisor#2 0x00583bca1b22d7 main+0x73 (/home/bojun/lsan/libbpf-tools/a.out+0x12d7)
          iovisor#3 0x007470c7c2a1ca [unknown] (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a1ca)
          iovisor#4 0x007470c7c2a28b __libc_start_main+0x8b (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a28b)
          iovisor#5 0x00583bca1b2105 _start+0x25 (/home/bojun/lsan/libbpf-tools/a.out+0x1105)

  [2024-05-22 14:45:08] Print leaks:
  132 bytes direct leak found in 3 allocations from stack id(57214)
          iovisor#1 0x00583bca1b2250 baz+0x1c (/home/bojun/lsan/libbpf-tools/a.out+0x1250)
          iovisor#2 0x00583bca1b22d7 main+0x73 (/home/bojun/lsan/libbpf-tools/a.out+0x12d7)
          iovisor#3 0x007470c7c2a1ca [unknown] (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a1ca)
          iovisor#4 0x007470c7c2a28b __libc_start_main+0x8b (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a28b)
          iovisor#5 0x00583bca1b2105 _start+0x25 (/home/bojun/lsan/libbpf-tools/a.out+0x1105)

Source code of test program:

  $ cat leak_test.c
  #include <stdlib.h>
  #include <unistd.h>

  int *arr[10000];

  int *foo(size_t size) {
          int *tmp = malloc(size);
          *tmp = 99;
          return tmp;
  }

  int *bar(size_t nmemb, size_t size) {
          int *tmp = calloc(nmemb, size);
          *tmp = 22;
          return tmp;
  }

  int *baz(size_t size) {
          int *tmp = valloc(size);
          *tmp = 11;
          return tmp;
  }

  int main(int argc, char* argv[]) {
          int *a;
          int i = 0;
          while (1) {
                  a = foo(4);
                  arr[i++] = a;
                  a = bar(4, 4);
                  free(a);
                  a = baz(44);
                  sleep(5);
          }
          return 0;
  }
  • Loading branch information
Bojun-Seo committed Jul 31, 2024
1 parent 7fdc129 commit 0d25050
Show file tree
Hide file tree
Showing 5 changed files with 1,420 additions and 0 deletions.
1 change: 1 addition & 0 deletions libbpf-tools/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
/klockstat
/ksnoop
/llcstat
/lsan
/memleak
/mdflush
/mountsnoop
Expand Down
1 change: 1 addition & 0 deletions libbpf-tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ APPS = \
klockstat \
ksnoop \
llcstat \
lsan \
mdflush \
mountsnoop \
numamove \
Expand Down
214 changes: 214 additions & 0 deletions libbpf-tools/lsan.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright 2022 LG Electronics Inc. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "lsan.h"

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_THREAD_NUM);
__type(key, u32);
__type(value, u64);
} memptrs SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_THREAD_NUM);
__type(key, u32);
__type(value, u64);
} sizes SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, u64);
__type(value, struct lsan_info_t);
} allocs SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
__uint(max_entries, MAX_ENTRIES);
__type(key, u32);
} stack_traces SEC(".maps");

static int gen_alloc_enter(struct pt_regs *ctx, size_t size)
{
u64 size64 = size;
u32 tid = bpf_get_current_pid_tgid();

bpf_map_update_elem(&sizes, &tid, &size64, BPF_ANY);

return 0;
}

static int gen_alloc_exit(struct pt_regs *ctx, u64 address)
{
struct lsan_info_t info = {};
u32 tid = bpf_get_current_pid_tgid();
u64 *size64 = bpf_map_lookup_elem(&sizes, &tid);

if (!size64)
return 0;

info.size = *size64;
bpf_map_delete_elem(&sizes, &tid);

if (!address)
return 0;

info.stack_id = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK);
info.tag = DIRECTLY_LEAKED;
bpf_map_update_elem(&allocs, &address, &info, BPF_ANY);

return 0;
}

static int gen_free_enter(struct pt_regs *ctx, void *address)
{
u64 addr = (u64)address;

bpf_map_delete_elem(&allocs, &addr);

return 0;
}

SEC("uprobe")
int BPF_KPROBE(malloc_entry, size_t size)
{
return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(malloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(free_entry, void *address)
{
return gen_free_enter(ctx, address);
}

SEC("uprobe")
int BPF_KPROBE(calloc_entry, size_t nmemb, size_t size)
{
return gen_alloc_enter(ctx, nmemb * size);
}

SEC("uretprobe")
int BPF_KRETPROBE(calloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(realloc_entry, void *ptr, size_t size)
{
gen_free_enter(ctx, ptr);

return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(realloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(posix_memalign_entry, void **memptr, size_t alignment, size_t size)
{
u64 memptr64 = (u64)(size_t)memptr;
u32 tid = bpf_get_current_pid_tgid();

bpf_map_update_elem(&memptrs, &tid, &memptr64, BPF_ANY);

return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(posix_memalign_return)
{
void *addr = NULL;
u64 addr64 = 0;
u32 tid = bpf_get_current_pid_tgid();
u64 *memptr64 = bpf_map_lookup_elem(&memptrs, &tid);

if (!memptr64)
return 0;

bpf_map_delete_elem(&memptrs, &tid);
if (bpf_probe_read_user(&addr, sizeof(void *), (void *)(size_t)*memptr64))
return 0;

addr64 = (u64)(size_t)addr;

return gen_alloc_exit(ctx, addr64);
}

SEC("uprobe")
int BPF_KPROBE(aligned_alloc_entry, size_t alignment, size_t size)
{
return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(aligned_alloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(valloc_entry, size_t size)
{
return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(valloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(memalign_entry, size_t alignment, size_t size)
{
return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(memalign_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(pvalloc_entry, size_t size)
{
return gen_alloc_enter(ctx, size);
}

SEC("uretprobe")
int BPF_KRETPROBE(pvalloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(reallocarray_entry, void *ptr, size_t nmemb, size_t size)
{
gen_free_enter(ctx, ptr);

return gen_alloc_enter(ctx, nmemb * size);
}

SEC("uretprobe")
int BPF_KRETPROBE(reallocarray_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

char _license[] SEC("license") = "Dual BSD/GPL";
Loading

0 comments on commit 0d25050

Please sign in to comment.