Moved user symbol decoding from memleak into bcc module
diff --git a/tools/memleak.py b/tools/memleak.py
index b5f272d..cd91372 100755
--- a/tools/memleak.py
+++ b/tools/memleak.py
@@ -1,12 +1,12 @@
#!/usr/bin/env python
#
-# memleak Trace and display outstanding allocations to detect
-# memory leaks in user-mode processes and the kernel.
+# memleak Trace and display outstanding allocations to detect
+# memory leaks in user-mode processes and the kernel.
#
# USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
-# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE]
-# [-Z MAX_SIZE]
-# [interval] [count]
+# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE]
+# [-Z MAX_SIZE]
+# [interval] [count]
#
# Licensed under the Apache License, Version 2.0 (the "License")
# Copyright (C) 2016 Sasha Goldshtein.
@@ -45,88 +45,14 @@
return t.tv_sec * 1e9 + t.tv_nsec
class StackDecoder(object):
- def __init__(self, pid, bpf):
+ def __init__(self, pid):
self.pid = pid
- self.bpf = bpf
- self.ranges_cache = {}
- self.refresh_code_ranges()
+ if pid != -1:
+ self.proc_sym = BPF.ProcessSymbols(pid)
- def refresh_code_ranges(self):
- if self.pid == -1:
- return
- self.code_ranges = self._get_code_ranges()
-
- @staticmethod
- def _is_binary_segment(parts):
- return len(parts) == 6 and \
- parts[5][0] != '[' and 'x' in parts[1]
-
- def _get_code_ranges(self):
- ranges = {}
- raw_ranges = open("/proc/%d/maps" % self.pid).readlines()
- # A typical line from /proc/PID/maps looks like this:
- # 7f21b6635000-7f21b67eb000 r-xp ... /usr/lib64/libc-2.21.so
- # We are looking for executable segments that have a .so file
- # or the main executable. The first two lines are the range of
- # that memory segment, which we index by binary name.
- for raw_range in raw_ranges:
- parts = raw_range.split()
- if not StackDecoder._is_binary_segment(parts):
- continue
- binary = parts[5]
- range_parts = parts[0].split('-')
- addr_range = (int(range_parts[0], 16),
- int(range_parts[1], 16))
- ranges[binary] = addr_range
- return ranges
-
- @staticmethod
- def _is_function_symbol(parts):
- return len(parts) == 6 and parts[3] == ".text" \
- and parts[2] == "F"
-
- def _get_sym_ranges(self, binary):
- if binary in self.ranges_cache:
- return self.ranges_cache[binary]
- sym_ranges = {}
- raw_symbols = run_command_get_output("objdump -t %s" % binary)
- for raw_symbol in raw_symbols:
- # A typical line from objdump -t looks like this:
- # 00000000004007f5 g F .text 000000000000010e main
- # We only care about functions in the .text segment.
- # The first number is the start address, and the second
- # number is the length.
- parts = raw_symbol.split()
- if not StackDecoder._is_function_symbol(parts):
- continue
- sym_start = int(parts[0], 16)
- sym_len = int(parts[4], 16)
- sym_name = parts[5]
- sym_ranges[sym_name] = (sym_start, sym_len)
- self.ranges_cache[binary] = sym_ranges
- return sym_ranges
-
- def _decode_sym(self, binary, offset):
- sym_ranges = self._get_sym_ranges(binary)
- # Find the symbol that contains the specified offset.
- # There might not be one.
- for name, (start, length) in sym_ranges.items():
- if offset >= start and offset <= (start + length):
- return "%s+0x%x" % (name, offset - start)
- return "%x" % offset
-
- def _decode_addr(self, addr):
- code_ranges = self._get_code_ranges()
- # Find the binary that contains the specified address.
- # For .so files, look at the relative address; for the main
- # executable, look at the absolute address.
- for binary, (start, end) in code_ranges.items():
- if addr >= start and addr <= end:
- offset = addr - start \
- if binary.endswith(".so") else addr
- return "%s [%s]" % (self._decode_sym(binary,
- offset), binary)
- return "%x" % addr
+ def refresh(self):
+ if self.pid != -1:
+ self.proc_sym.refresh_code_ranges()
def decode_stack(self, info, is_kernel_trace):
stack = ""
@@ -136,13 +62,10 @@
addr = info.callstack[i]
if is_kernel_trace:
stack += " %s [kernel] (%x) ;" % \
- (self.bpf.ksym(addr), addr)
+ (BPF.ksym(addr), addr)
else:
- # At some point, we hope to have native BPF
- # user-mode symbol decoding, but for now we
- # have to use our own.
stack += " %s (%x) ;" % \
- (self._decode_addr(addr), addr)
+ (self.proc_sym.decode_addr(addr), addr)
return stack
def run_command_get_output(command):
@@ -302,7 +225,7 @@
info.timestamp_ns = bpf_ktime_get_ns();
info.num_frames = grab_stack(ctx, &info) - 2;
allocs.update(&address, &info);
-
+
if (SHOULD_PRINT) {
bpf_trace_printk("alloc exited, size = %lu, result = %lx, frames = %d\\n",
info.size, address, info.num_frames);
@@ -325,7 +248,7 @@
}
return 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("GRAB_ONE_FRAME", max_stack_size *
@@ -358,7 +281,7 @@
bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit")
bpf_program.attach_kprobe(event="kfree", fn_name="free_enter")
-decoder = StackDecoder(pid, bpf_program)
+decoder = StackDecoder(pid)
def print_outstanding():
stacks = {}
@@ -391,7 +314,7 @@
sleep(interval)
except KeyboardInterrupt:
exit()
- decoder.refresh_code_ranges()
+ decoder.refresh()
print_outstanding()
count_so_far += 1
if num_prints is not None and count_so_far >= num_prints: