From 49df9944aac3005bdef4242abe16721afdfbfb9a Mon Sep 17 00:00:00 2001 From: Sasha Goldshtein Date: Wed, 8 Feb 2017 23:22:06 -0500 Subject: [PATCH] memleak: Migrate to new symbols resolution API Remove usyms.py dependency and replace with new symbols API. --- tools/memleak.py | 31 ++++------------ tools/memleak_example.txt | 74 +++++++++++++++++++-------------------- tools/old/memleak.py | 38 ++++++-------------- 3 files changed, 54 insertions(+), 89 deletions(-) diff --git a/tools/memleak.py b/tools/memleak.py index e9856ea3853d..e5034cfb3711 100755 --- a/tools/memleak.py +++ b/tools/memleak.py @@ -11,31 +11,13 @@ # Licensed under the Apache License, Version 2.0 (the "License") # Copyright (C) 2016 Sasha Goldshtein. -from bcc import BPF, ProcessSymbols +from bcc import BPF from time import sleep from datetime import datetime import argparse import subprocess import os -class KStackDecoder(object): - def refresh(self): - pass - - def __call__(self, addr): - return "%s [kernel] (%x)" % (BPF.ksym(addr), addr) - -class UStackDecoder(object): - def __init__(self, pid): - self.pid = pid - self.proc_sym = ProcessSymbols(pid) - - def refresh(self): - self.proc_sym.refresh_code_ranges() - - def __call__(self, addr): - return "%s (%x)" % (self.proc_sym.decode_addr(addr), addr) - class Allocation(object): def __init__(self, stack, size): self.stack = stack @@ -240,8 +222,6 @@ def run_command_get_pid(command): bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit") bpf_program.attach_kprobe(event="kfree", fn_name="free_enter") -decoder = KStackDecoder() if kernel_trace else UStackDecoder(pid) - def print_outstanding(): print("[%s] Top %d stacks with outstanding allocations:" % (datetime.now().strftime("%H:%M:%S"), top_stacks)) @@ -256,8 +236,12 @@ def print_outstanding(): if info.stack_id in alloc_info: alloc_info[info.stack_id].update(info.size) else: - stack = list(stack_traces.walk(info.stack_id, decoder)) - alloc_info[info.stack_id] = Allocation(stack, + stack = list(stack_traces.walk(info.stack_id)) + combined = [] + for addr in stack: + combined.append(bpf_program.sym(addr, pid, + show_module=True, show_address=True)) + alloc_info[info.stack_id] = Allocation(combined, info.size) if args.show_allocs: print("\taddr = %x size = %s" % @@ -277,7 +261,6 @@ def print_outstanding(): sleep(interval) except KeyboardInterrupt: exit() - decoder.refresh() print_outstanding() count_so_far += 1 if num_prints is not None and count_so_far >= num_prints: diff --git a/tools/memleak_example.txt b/tools/memleak_example.txt index 5b6f8d4ac862..accc74f1b41f 100644 --- a/tools/memleak_example.txt +++ b/tools/memleak_example.txt @@ -10,13 +10,13 @@ For example: Attaching to malloc and free in pid 5193, Ctrl+C to quit. [11:16:33] Top 2 stacks with outstanding allocations: 80 bytes in 5 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] [11:16:34] Top 2 stacks with outstanding allocations: 160 bytes in 10 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] Each entry printed is a set of allocations that originate from the same call @@ -40,8 +40,8 @@ Attaching to malloc and free in pid 5193, Ctrl+C to quit. addr = 948d30 size = 16 addr = 948cf0 size = 16 64 bytes in 4 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] [11:16:34] Top 2 stacks with outstanding allocations: addr = 948d50 size = 16 @@ -55,8 +55,8 @@ Attaching to malloc and free in pid 5193, Ctrl+C to quit. addr = 948d70 size = 16 addr = 948df0 size = 16 160 bytes in 10 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] When using the -p switch, memleak traces the allocations of a particular @@ -67,35 +67,35 @@ For example: Attaching to kmalloc and kfree, Ctrl+C to quit. ... 248 bytes in 4 allocations from stack - bpf_prog_load [kernel] (ffffffff8118c471) - sys_bpf [kernel] (ffffffff8118c8b5) + bpf_prog_load [kernel] + sys_bpf [kernel] 328 bytes in 1 allocations from stack - perf_mmap [kernel] (ffffffff811990fd) - mmap_region [kernel] (ffffffff811df5d4) - do_mmap [kernel] (ffffffff811dfb83) - vm_mmap_pgoff [kernel] (ffffffff811c494f) - sys_mmap_pgoff [kernel] (ffffffff811ddf02) - sys_mmap [kernel] (ffffffff8101b0ab) + perf_mmap [kernel] + mmap_region [kernel] + do_mmap [kernel] + vm_mmap_pgoff [kernel] + sys_mmap_pgoff [kernel] + sys_mmap [kernel] 464 bytes in 1 allocations from stack - traceprobe_command [kernel] (ffffffff81187cf2) - traceprobe_probes_write [kernel] (ffffffff81187d86) - probes_write [kernel] (ffffffff81181580) - __vfs_write [kernel] (ffffffff812237b7) - vfs_write [kernel] (ffffffff81223ec6) - sys_write [kernel] (ffffffff81224b85) - entry_SYSCALL_64_fastpath [kernel] (ffffffff8178182e) + traceprobe_command [kernel] + traceprobe_probes_write [kernel] + probes_write [kernel] + __vfs_write [kernel] + vfs_write [kernel] + sys_write [kernel] + entry_SYSCALL_64_fastpath [kernel] 8192 bytes in 1 allocations from stack - alloc_and_copy_ftrace_hash.constprop.59 [kernel] (ffffffff8115d17e) - ftrace_set_hash [kernel] (ffffffff8115e767) - ftrace_set_filter_ip [kernel] (ffffffff8115e9a8) - arm_kprobe [kernel] (ffffffff81148600) - enable_kprobe [kernel] (ffffffff811486f6) - kprobe_register [kernel] (ffffffff81182399) - perf_trace_init [kernel] (ffffffff8117c4e0) - perf_tp_event_init [kernel] (ffffffff81192479) + alloc_and_copy_ftrace_hash.constprop.59 [kernel] + ftrace_set_hash [kernel] + ftrace_set_filter_ip [kernel] + arm_kprobe [kernel] + enable_kprobe [kernel] + kprobe_register [kernel] + perf_trace_init [kernel] + perf_tp_event_init [kernel] Here you can see that arming the kprobe to which our eBPF program is attached @@ -129,18 +129,18 @@ seconds, 3 times before quitting: Attaching to malloc and free in pid 2614, Ctrl+C to quit. [11:16:33] Top 2 stacks with outstanding allocations: 16 bytes in 1 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fdc11ce8790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] [11:16:38] Top 2 stacks with outstanding allocations: 16 bytes in 1 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fdc11ce8790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] [11:16:43] Top 2 stacks with outstanding allocations: 32 bytes in 2 allocations from stack - main+0x6d [/home/vagrant/allocs] (400862) - __libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fdc11ce8790) + main+0x6d [allocs] + __libc_start_main+0xf0 [libc-2.21.so] 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 diff --git a/tools/old/memleak.py b/tools/old/memleak.py index bff7cfd25106..0e42a4fab3b0 100755 --- a/tools/old/memleak.py +++ b/tools/old/memleak.py @@ -11,36 +11,21 @@ # Licensed under the Apache License, Version 2.0 (the "License") # Copyright (C) 2016 Sasha Goldshtein. -from bcc import BPF, ProcessSymbols +from bcc import BPF, SymbolCache from time import sleep from datetime import datetime import argparse import subprocess import os -class StackDecoder(object): - def __init__(self, pid): - self.pid = pid - if pid != -1: - self.proc_sym = ProcessSymbols(pid) - - def refresh(self): - if self.pid != -1: - self.proc_sym.refresh_code_ranges() - - def decode_stack(self, info, is_kernel_trace): - stack = "" - if info.num_frames <= 0: - return "???" - for i in range(0, info.num_frames): - addr = info.callstack[i] - if is_kernel_trace: - stack += " %s [kernel] (%x) ;" % \ - (BPF.ksym(addr), addr) - else: - stack += " %s (%x) ;" % \ - (self.proc_sym.decode_addr(addr), addr) - return stack +def decode_stack(bpf, pid, info): + stack = "" + if info.num_frames <= 0: + return "???" + for i in range(0, info.num_frames): + addr = info.callstack[i] + stack += " %s ;" % bpf.sym(addr, pid, show_address=True) + return stack def run_command_get_output(command): p = subprocess.Popen(command.split(), @@ -255,8 +240,6 @@ def run_command_get_pid(command): bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit") bpf_program.attach_kprobe(event="kfree", fn_name="free_enter") -decoder = StackDecoder(pid) - def print_outstanding(): stacks = {} print("[%s] Top %d stacks with outstanding allocations:" % @@ -265,7 +248,7 @@ def print_outstanding(): for address, info in sorted(allocs.items(), key=lambda a: a[1].size): if BPF.monotonic_time() - min_age_ns < info.timestamp_ns: continue - stack = decoder.decode_stack(info, kernel_trace) + stack = decode_stack(bpf_program, pid, info) if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size) @@ -288,7 +271,6 @@ def print_outstanding(): sleep(interval) except KeyboardInterrupt: exit() - decoder.refresh() print_outstanding() count_so_far += 1 if num_prints is not None and count_so_far >= num_prints: