Skip to content

Commit

Permalink
memleak: Add workaround to alleviate misjudgments when free is missing
Browse files Browse the repository at this point in the history
Profiling in memory part is hard to be accurate because of BPF infrastructure.
memleak keeps misjudging memory leak on the complicated environment which has
the action of free in hard/soft irq.

For example, in my misjudged case:

640 bytes in 10 allocations from stack
--
__kmalloc+0x178 [kernel]
__kmalloc+0x178 [kernel]
xhci_urb_enqueue+0x140 [kernel]
usb_hcd_submit_urb+0x5e0 [kernel]

This result looks like kernel doesn't free urb_priv. However, it's not true.
The reason for this leak is because xhci hw irq interrupts during the BPF program.
BPF program is not finished on that CPU, and xhci_irq() will call xhci_urb_free_priv()
before the end. But the kernel doesn't permit this isr to go into BPF program again.
Because BPF infrastructure(trace_call_bpf) denied this action.
So we miss this free action and cause memory leak misjudgment.

Side-effect:
- Increase overhead for each memory allocation.
- A higher chance to be interrupted at the allocation part causes ignore more allocations.

This workaround doesn't solve all misjudgments, the improvement in BPF infrastructure
is the only solution.
  • Loading branch information
netedwardwu authored and yonghong-song committed Jul 8, 2020
1 parent 95c9229 commit cd81f13
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 6 deletions.
10 changes: 8 additions & 2 deletions man/man8/memleak.8
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
memleak \- Print a summary of outstanding allocations and their call stacks to detect memory leaks. Uses Linux eBPF/bcc.
.SH SYNOPSIS
.B memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] [--combined-only]
[-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ] [INTERVAL]
[COUNT]
[--wa-missing-free] [-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE]
[-O OBJ] [INTERVAL] [COUNT]
.SH DESCRIPTION
memleak traces and matches memory allocation and deallocation requests, and
collects call stacks for each allocation. memleak can then print a summary
Expand Down Expand Up @@ -53,6 +53,9 @@ Use statistics precalculated in kernel space. Amount of data to be pulled from
kernel significantly decreases, at the cost of losing capabilities of time-based
false positives filtering (\-o).
.TP
\-\-wa-missing-free
Make up the action of free to alleviate misjudgments when free is missing.
.TP
\-s SAMPLE_RATE
Record roughly every SAMPLE_RATE-th allocation to reduce overhead.
.TP
Expand Down Expand Up @@ -109,6 +112,9 @@ Additionally, option \-\-combined-only saves processing time by reusing already
calculated allocation statistics from kernel. It's faster, but lacks information
about particular allocations.

Also, option \-\-wa-missing-free makes memleak more accuracy in the complicated
environment.

To determine the rate at which your application is calling malloc/free, or the
rate at which your kernel is calling kmalloc/kfree, place a probe with perf and
collect statistics. For example, to determine how many calls to __kmalloc are
Expand Down
18 changes: 16 additions & 2 deletions tools/memleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# memory leaks in user-mode processes and the kernel.
#
# USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
# [--combined-only] [-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE]
# [-Z MAX_SIZE] [-O OBJ]
# [--combined-only] [--wa-missing-free] [-s SAMPLE_RATE]
# [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ]
# [interval] [count]
#
# Licensed under the Apache License, Version 2.0 (the "License")
Expand Down Expand Up @@ -88,6 +88,8 @@ def run_command_get_pid(command):
help="execute and trace the specified command")
parser.add_argument("--combined-only", default=False, action="store_true",
help="show combined allocation statistics only")
parser.add_argument("--wa-missing-free", default=False, action="store_true",
help="Workaround to alleviate misjudgments when free is missing")
parser.add_argument("-s", "--sample-rate", default=1, type=int,
help="sample every N-th allocation to decrease the overhead")
parser.add_argument("-T", "--top", type=int, default=10,
Expand Down Expand Up @@ -330,11 +332,15 @@ def run_command_get_pid(command):
bpf_source_kernel = """
TRACEPOINT_PROBE(kmem, kmalloc) {
if (WORKAROUND_MISSING_FREE)
gen_free_enter((struct pt_regs *)args, (void *)args->ptr);
gen_alloc_enter((struct pt_regs *)args, args->bytes_alloc);
return gen_alloc_exit2((struct pt_regs *)args, (size_t)args->ptr);
}
TRACEPOINT_PROBE(kmem, kmalloc_node) {
if (WORKAROUND_MISSING_FREE)
gen_free_enter((struct pt_regs *)args, (void *)args->ptr);
gen_alloc_enter((struct pt_regs *)args, args->bytes_alloc);
return gen_alloc_exit2((struct pt_regs *)args, (size_t)args->ptr);
}
Expand All @@ -344,11 +350,15 @@ def run_command_get_pid(command):
}
TRACEPOINT_PROBE(kmem, kmem_cache_alloc) {
if (WORKAROUND_MISSING_FREE)
gen_free_enter((struct pt_regs *)args, (void *)args->ptr);
gen_alloc_enter((struct pt_regs *)args, args->bytes_alloc);
return gen_alloc_exit2((struct pt_regs *)args, (size_t)args->ptr);
}
TRACEPOINT_PROBE(kmem, kmem_cache_alloc_node) {
if (WORKAROUND_MISSING_FREE)
gen_free_enter((struct pt_regs *)args, (void *)args->ptr);
gen_alloc_enter((struct pt_regs *)args, args->bytes_alloc);
return gen_alloc_exit2((struct pt_regs *)args, (size_t)args->ptr);
}
Expand Down Expand Up @@ -385,6 +395,10 @@ def run_command_get_pid(command):
else:
bpf_source += bpf_source_kernel

if kernel_trace:
bpf_source = bpf_source.replace("WORKAROUND_MISSING_FREE", "1"
if args.wa_missing_free else "0")

bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))
bpf_source = bpf_source.replace("PAGE_SIZE", str(resource.getpagesize()))
Expand Down
26 changes: 24 additions & 2 deletions tools/memleak_example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,33 @@ Note that even though the application leaks 16 bytes of memory every second,
the report (printed every 5 seconds) doesn't "see" all the allocations because
of the sampling rate applied.

Profiling in memory part is hard to be accurate because of BPF infrastructure.
memleak keeps misjudging memory leak on the complicated environment which has
the action of free in hard/soft irq.
Add workaround to alleviate misjudgments when free is missing:

# ./memleak --wa-missing-free
Attaching to kernel allocators, Ctrl+C to quit.
...
248 bytes in 4 allocations from stack
bpf_prog_load [kernel]
sys_bpf [kernel]

328 bytes in 1 allocations from stack
perf_mmap [kernel]
mmap_region [kernel]
do_mmap [kernel]
vm_mmap_pgoff [kernel]
sys_mmap_pgoff [kernel]
sys_mmap [kernel]


USAGE message:

# ./memleak -h
usage: memleak.py [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
[--combined-only] [-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE]
[-Z MAX_SIZE] [-O OBJ]
[--combined-only] [--wa-missing-free] [-s SAMPLE_RATE]
[-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ]
[interval] [count]

Trace outstanding memory allocations that weren't freed.
Expand All @@ -177,6 +197,8 @@ optional arguments:
-c COMMAND, --command COMMAND
execute and trace the specified command
--combined-only show combined allocation statistics only
--wa-missing-free Workaround to alleviate misjudgments when free is
missing
-s SAMPLE_RATE, --sample-rate SAMPLE_RATE
sample every N-th allocation to decrease the overhead
-T TOP, --top TOP display only this many top allocating stacks (by size)
Expand Down

0 comments on commit cd81f13

Please sign in to comment.