Commit 751fce5a authored by Sasha Goldshtein's avatar Sasha Goldshtein

Added -c switch to run a command and trace that process

parent 29228618
...@@ -8,6 +8,8 @@ import ctypes ...@@ -8,6 +8,8 @@ import ctypes
import os import os
class Time(object): class Time(object):
# BPF timestamps come from the monotonic clock. To be able to filter
# and compare them from Python, we need to invoke clock_gettime from librt.
# Adapted from http://stackoverflow.com/a/1205762 # Adapted from http://stackoverflow.com/a/1205762
CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h> CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
...@@ -23,9 +25,6 @@ class Time(object): ...@@ -23,9 +25,6 @@ class Time(object):
@staticmethod @staticmethod
def monotonic_time(): def monotonic_time():
"""monotonic_time()
Returns the reading of the monotonic clock, in nanoseconds.
"""
t = Time.timespec() t = Time.timespec()
if Time.clock_gettime(Time.CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0: if Time.clock_gettime(Time.CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
errno_ = ctypes.get_errno() errno_ = ctypes.get_errno()
...@@ -34,18 +33,12 @@ class Time(object): ...@@ -34,18 +33,12 @@ class Time(object):
class StackDecoder(object): class StackDecoder(object):
def __init__(self, pid, bpf): def __init__(self, pid, bpf):
"""
TODO
"""
self.pid = pid self.pid = pid
self.bpf = bpf self.bpf = bpf
self.ranges_cache = {} self.ranges_cache = {}
self.refresh_code_ranges() self.refresh_code_ranges()
def refresh_code_ranges(self): def refresh_code_ranges(self):
"""
TODO
"""
if self.pid == -1: if self.pid == -1:
return return
self.code_ranges = self._get_code_ranges() self.code_ranges = self._get_code_ranges()
...@@ -54,6 +47,11 @@ class StackDecoder(object): ...@@ -54,6 +47,11 @@ class StackDecoder(object):
ranges = {} ranges = {}
raw_ranges = open("/proc/%d/maps" % self.pid).readlines() raw_ranges = open("/proc/%d/maps" % self.pid).readlines()
for raw_range in raw_ranges: for raw_range in raw_ranges:
# A typical line from /proc/PID/maps looks like this:
# 7f21b6635000-7f21b67eb000 r-xp 00000000 fd:00 1442606 /usr/lib64/libc-2.21.so
# We are looking for executable segments that have a binary (.so
# or the main executable). The first two lines are the range of
# that memory segment, which we index by binary name.
parts = raw_range.split() parts = raw_range.split()
if len(parts) < 6 or parts[5][0] == '[' or not 'x' in parts[1]: if len(parts) < 6 or parts[5][0] == '[' or not 'x' in parts[1]:
continue continue
...@@ -67,8 +65,12 @@ class StackDecoder(object): ...@@ -67,8 +65,12 @@ class StackDecoder(object):
if binary in self.ranges_cache: if binary in self.ranges_cache:
return self.ranges_cache[binary] return self.ranges_cache[binary]
sym_ranges = {} sym_ranges = {}
raw_symbols = run_command("objdump -t %s" % binary) raw_symbols = run_command_get_output("objdump -t %s" % binary)
for raw_symbol in raw_symbols: 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 (F) in the .text segment. The first
# number is the start address, and the second number is the length.
parts = raw_symbol.split() parts = raw_symbol.split()
if len(parts) < 6 or parts[3] != ".text" or parts[2] != "F": if len(parts) < 6 or parts[3] != ".text" or parts[2] != "F":
continue continue
...@@ -81,6 +83,7 @@ class StackDecoder(object): ...@@ -81,6 +83,7 @@ class StackDecoder(object):
def _decode_sym(self, binary, offset): def _decode_sym(self, binary, offset):
sym_ranges = self._get_sym_ranges(binary) 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(): for name, (start, length) in sym_ranges.items():
if offset >= start and offset <= (start + length): if offset >= start and offset <= (start + length):
return "%s+0x%x" % (name, offset - start) return "%s+0x%x" % (name, offset - start)
...@@ -88,31 +91,37 @@ class StackDecoder(object): ...@@ -88,31 +91,37 @@ class StackDecoder(object):
def _decode_addr(self, addr): def _decode_addr(self, addr):
code_ranges = self._get_code_ranges() 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(): for binary, (start, end) in code_ranges.items():
if addr >= start and addr <= end: if addr >= start and addr <= end:
offset = addr - start if binary.endswith(".so") else addr offset = addr - start if binary.endswith(".so") else addr
return "%s [%s]" % (self._decode_sym(binary, offset), binary) return "%s [%s]" % (self._decode_sym(binary, offset), binary)
return "%x" % addr return "%x" % addr
def decode_stack(self, info): def decode_stack(self, info, is_kernel_trace):
"""
TODO
"""
stack = "" stack = ""
if info.num_frames <= 0: if info.num_frames <= 0:
return "???" return "???"
for i in range(0, info.num_frames): for i in range(0, info.num_frames):
addr = info.callstack[i] addr = info.callstack[i]
if kernel_trace: if is_kernel_trace:
stack += " %s [kernel] (%x) ;" % (self.bpf.ksym(addr), addr) stack += " %s [kernel] (%x) ;" % (self.bpf.ksym(addr), addr)
else: 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) stack += " %s (%x) ;" % (self._decode_addr(addr), addr)
return stack return stack
def run_command(command): def run_command_get_output(command):
p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return iter(p.stdout.readline, b'') return iter(p.stdout.readline, b'')
def run_command_get_pid(command):
p = subprocess.Popen(command.split())
return p.pid
examples = """ examples = """
EXAMPLES: EXAMPLES:
...@@ -166,7 +175,8 @@ interval = int(args.interval) ...@@ -166,7 +175,8 @@ interval = int(args.interval)
min_age_ns = 1e6*int(args.older) min_age_ns = 1e6*int(args.older)
if not command is None: if not command is None:
pass # TODO Run command, get its pid and trace that print("Executing '%s' and tracing the resulting process." % command)
pid = run_command_get_pid(command)
bpf_source = open("memleak.c").read() bpf_source = open("memleak.c").read()
bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0") bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
...@@ -190,19 +200,19 @@ def print_outstanding(): ...@@ -190,19 +200,19 @@ def print_outstanding():
stacks = {} stacks = {}
print("*** Outstanding allocations:") print("*** Outstanding allocations:")
allocs = bpf_program.get_table("allocs") allocs = bpf_program.get_table("allocs")
for address, info in sorted(allocs.items(), key=lambda a: -a[1].size): for address, info in sorted(allocs.items(), key=lambda a: a[1].size):
if Time.monotonic_time() - min_age_ns < info.timestamp_ns: if Time.monotonic_time() - min_age_ns < info.timestamp_ns:
continue continue
stack = decoder.decode_stack(info) stack = decoder.decode_stack(info, kernel_trace)
if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size) if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size)
else: stacks[stack] = (1, info.size) else: stacks[stack] = (1, info.size)
if args.show_allocs: if args.show_allocs:
print("\taddr = %x size = %s" % (address.value, info.size)) print("\taddr = %x size = %s" % (address.value, info.size))
for stack, (count, size) in sorted(stacks.items(), key=lambda s: -s[1][1]): for stack, (count, size) in sorted(stacks.items(), key=lambda s: s[1][1]):
print("\t%d bytes in %d allocations from stack\n\t\t%s" % (size, count, stack.replace(";", "\n\t\t"))) print("\t%d bytes in %d allocations from stack\n\t\t%s" % (size, count, stack.replace(";", "\n\t\t")))
while True: while True:
if trace_all: if trace_all:
print bpf_program.trace_fields() print bpf_program.trace_fields()
else: else:
try: try:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment