Refactored StackDecoder, added 'run command' option with no implementation yet
diff --git a/tools/memleak.py b/tools/memleak.py
index cc56a21..34e9ae6 100755
--- a/tools/memleak.py
+++ b/tools/memleak.py
@@ -32,6 +32,87 @@
raise OSError(errno_, os.strerror(errno_))
return t.tv_sec*1e9 + t.tv_nsec
+class StackDecoder(object):
+ def __init__(self, pid, bpf):
+ """
+ TODO
+ """
+ self.pid = pid
+ self.bpf = bpf
+ self.ranges_cache = {}
+ self.refresh_code_ranges()
+
+ def refresh_code_ranges(self):
+ """
+ TODO
+ """
+ if self.pid == -1:
+ return
+ self.code_ranges = self._get_code_ranges()
+
+ def _get_code_ranges(self):
+ ranges = {}
+ raw_ranges = open("/proc/%d/maps" % self.pid).readlines()
+ for raw_range in raw_ranges:
+ parts = raw_range.split()
+ if len(parts) < 6 or parts[5][0] == '[' or not 'x' in parts[1]:
+ 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
+
+ def _get_sym_ranges(self, binary):
+ if binary in self.ranges_cache:
+ return self.ranges_cache[binary]
+ sym_ranges = {}
+ raw_symbols = run_command("objdump -t %s" % binary)
+ for raw_symbol in raw_symbols:
+ parts = raw_symbol.split()
+ if len(parts) < 6 or parts[3] != ".text" or parts[2] != "F":
+ 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)
+ 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()
+ 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 decode_stack(self, info):
+ """
+ TODO
+ """
+ stack = ""
+ if info.num_frames <= 0:
+ return "???"
+ for i in range(0, info.num_frames):
+ addr = info.callstack[i]
+ if kernel_trace:
+ stack += " %s [kernel] (%x) ;" % (self.bpf.ksym(addr), addr)
+ else:
+ stack += " %s (%x) ;" % (self._decode_addr(addr), addr)
+ return stack
+
+def run_command(command):
+ p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ return iter(p.stdout.readline, b'')
+
examples = """
EXAMPLES:
@@ -43,6 +124,8 @@
./memleak.py -p $(pidof allocs) -a -i 10
Trace allocations and display allocated addresses, sizes, and stacks
every 10 seconds for outstanding allocations
+./memleak.py -c "./allocs"
+ Run the specified command and trace its allocations
./memleak.py
Trace allocations in kernel mode and display a summary of outstanding
allocations every 5 seconds
@@ -70,16 +153,21 @@
help="show allocation addresses and sizes as well as call stacks")
parser.add_argument("-o", "--older", default=500,
help="prune allocations younger than this age in milliseconds")
-# TODO Run a command and trace that command (-c)
+parser.add_argument("-c", "--command",
+ help="execute and trace the specified command")
args = parser.parse_args()
pid = -1 if args.pid is None else int(args.pid)
-kernel_trace = (pid == -1)
+command = args.command
+kernel_trace = (pid == -1 and command is None)
trace_all = args.trace
interval = int(args.interval)
min_age_ns = 1e6*int(args.older)
+if not command is None:
+ pass # TODO Run command, get its pid and trace that
+
bpf_source = open("memleak.c").read()
bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
@@ -96,66 +184,7 @@
bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit")
bpf_program.attach_kprobe(event="kfree", fn_name="free_enter")
-def get_code_ranges(pid):
- ranges = {}
- raw_ranges = open("/proc/%d/maps" % pid).readlines()
- for raw_range in raw_ranges:
- parts = raw_range.split()
- if len(parts) < 6 or parts[5][0] == '[' or not 'x' in parts[1]:
- 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
-
-def run_command(command):
- p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- return iter(p.stdout.readline, b'')
-
-ranges_cache = {}
-
-def get_sym_ranges(binary):
- if binary in ranges_cache:
- return ranges_cache[binary]
- sym_ranges = {}
- raw_symbols = run_command("objdump -t %s" % binary)
- for raw_symbol in raw_symbols:
- parts = raw_symbol.split()
- if len(parts) < 6 or parts[3] != ".text" or parts[2] != "F":
- 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)
- ranges_cache[binary] = sym_ranges
- return sym_ranges
-
-def decode_sym(binary, offset):
- sym_ranges = get_sym_ranges(binary)
- 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(code_ranges, addr):
- 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]" % (decode_sym(binary, offset), binary)
- return "%x" % addr
-
-def decode_stack(info):
- stack = ""
- if info.num_frames <= 0:
- return "???"
- for i in range(0, info.num_frames):
- addr = info.callstack[i]
- if kernel_trace:
- stack += " %s [kernel] (%x) ;" % (bpf_program.ksym(addr), addr)
- else:
- stack += " %s (%x) ;" % (decode_addr(code_ranges, addr), addr)
- return stack
+decoder = StackDecoder(pid, bpf_program)
def print_outstanding():
stacks = {}
@@ -164,7 +193,7 @@
for address, info in sorted(allocs.items(), key=lambda a: -a[1].size):
if Time.monotonic_time() - min_age_ns < info.timestamp_ns:
continue
- stack = decode_stack(info)
+ stack = decoder.decode_stack(info)
if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size)
else: stacks[stack] = (1, info.size)
if args.show_allocs:
@@ -180,7 +209,6 @@
sleep(interval)
except KeyboardInterrupt:
exit()
- if not kernel_trace:
- code_ranges = get_code_ranges(pid)
+ decoder.refresh_code_ranges()
print_outstanding()