blob: e9856ea3853d1f966c63ffbbc2f746b3549ec11f [file] [log] [blame]
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -08001#!/usr/bin/env python
Sasha Goldshtein50459642016-02-10 08:35:20 -08002#
Sasha Goldshtein0e856f42016-03-21 07:26:52 -07003# memleak Trace and display outstanding allocations to detect
4# memory leaks in user-mode processes and the kernel.
Sasha Goldshtein50459642016-02-10 08:35:20 -08005#
Sasha Goldshtein29e37d92016-02-14 06:56:07 -08006# USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
Sasha Goldshtein0e856f42016-03-21 07:26:52 -07007# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE]
8# [-Z MAX_SIZE]
9# [interval] [count]
Sasha Goldshtein50459642016-02-10 08:35:20 -080010#
Sasha Goldshtein43fa0412016-02-10 22:17:26 -080011# Licensed under the Apache License, Version 2.0 (the "License")
Sasha Goldshtein50459642016-02-10 08:35:20 -080012# Copyright (C) 2016 Sasha Goldshtein.
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080013
Sasha Goldshtein8737c6e2016-03-21 10:05:29 -070014from bcc import BPF, ProcessSymbols
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080015from time import sleep
Sasha Goldshteinc8148c82016-02-09 11:15:41 -080016from datetime import datetime
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080017import argparse
18import subprocess
Sasha Goldshteincfce3112016-02-07 11:09:36 -080019import os
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080020
Vicent Martie25ae032016-03-25 17:14:34 +010021class KStackDecoder(object):
22 def refresh(self):
23 pass
24
25 def __call__(self, addr):
26 return "%s [kernel] (%x)" % (BPF.ksym(addr), addr)
27
28class UStackDecoder(object):
Sasha Goldshtein0e856f42016-03-21 07:26:52 -070029 def __init__(self, pid):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080030 self.pid = pid
Vicent Martie25ae032016-03-25 17:14:34 +010031 self.proc_sym = ProcessSymbols(pid)
Sasha Goldshtein29228612016-02-07 12:20:19 -080032
Sasha Goldshtein0e856f42016-03-21 07:26:52 -070033 def refresh(self):
Vicent Martie25ae032016-03-25 17:14:34 +010034 self.proc_sym.refresh_code_ranges()
Sasha Goldshtein33522d72016-02-08 03:39:44 -080035
Vicent Martie25ae032016-03-25 17:14:34 +010036 def __call__(self, addr):
37 return "%s (%x)" % (self.proc_sym.decode_addr(addr), addr)
38
39class Allocation(object):
40 def __init__(self, stack, size):
41 self.stack = stack
42 self.count = 1
43 self.size = size
44
45 def update(self, size):
46 self.count += 1
47 self.size += size
Sasha Goldshtein29228612016-02-07 12:20:19 -080048
Sasha Goldshtein751fce52016-02-08 02:57:02 -080049def run_command_get_output(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080050 p = subprocess.Popen(command.split(),
51 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
52 return iter(p.stdout.readline, b'')
Sasha Goldshtein29228612016-02-07 12:20:19 -080053
Sasha Goldshtein751fce52016-02-08 02:57:02 -080054def run_command_get_pid(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080055 p = subprocess.Popen(command.split())
56 return p.pid
Sasha Goldshtein751fce52016-02-08 02:57:02 -080057
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080058examples = """
59EXAMPLES:
60
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080061./memleak -p $(pidof allocs)
Sasha Goldshtein33522d72016-02-08 03:39:44 -080062 Trace allocations and display a summary of "leaked" (outstanding)
63 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080064./memleak -p $(pidof allocs) -t
Sasha Goldshtein33522d72016-02-08 03:39:44 -080065 Trace allocations and display each individual call to malloc/free
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080066./memleak -ap $(pidof allocs) 10
Sasha Goldshtein33522d72016-02-08 03:39:44 -080067 Trace allocations and display allocated addresses, sizes, and stacks
68 every 10 seconds for outstanding allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080069./memleak -c "./allocs"
Sasha Goldshtein33522d72016-02-08 03:39:44 -080070 Run the specified command and trace its allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080071./memleak
Sasha Goldshtein33522d72016-02-08 03:39:44 -080072 Trace allocations in kernel mode and display a summary of outstanding
73 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080074./memleak -o 60000
Sasha Goldshtein33522d72016-02-08 03:39:44 -080075 Trace allocations in kernel mode and display a summary of outstanding
76 allocations that are at least one minute (60 seconds) old
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080077./memleak -s 5
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -080078 Trace roughly every 5th allocation, to reduce overhead
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080079"""
80
81description = """
82Trace outstanding memory allocations that weren't freed.
83Supports both user-mode allocations made with malloc/free and kernel-mode
84allocations made with kmalloc/kfree.
85"""
86
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080087parser = argparse.ArgumentParser(description=description,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080088 formatter_class=argparse.RawDescriptionHelpFormatter,
89 epilog=examples)
Sasha Goldshteind2241f42016-02-09 06:23:10 -080090parser.add_argument("-p", "--pid", type=int, default=-1,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080091 help="the PID to trace; if not specified, trace kernel allocs")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080092parser.add_argument("-t", "--trace", action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -080093 help="print trace messages for each alloc/free call")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080094parser.add_argument("interval", nargs="?", default=5, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080095 help="interval in seconds to print outstanding allocations")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080096parser.add_argument("count", nargs="?", type=int,
97 help="number of times to print the report before exiting")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080098parser.add_argument("-a", "--show-allocs", default=False, action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -080099 help="show allocation addresses and sizes as well as call stacks")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800100parser.add_argument("-o", "--older", default=500, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800101 help="prune allocations younger than this age in milliseconds")
Sasha Goldshtein29228612016-02-07 12:20:19 -0800102parser.add_argument("-c", "--command",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800103 help="execute and trace the specified command")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800104parser.add_argument("-s", "--sample-rate", default=1, type=int,
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800105 help="sample every N-th allocation to decrease the overhead")
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800106parser.add_argument("-T", "--top", type=int, default=10,
107 help="display only this many top allocating stacks (by size)")
Sasha Goldshtein50459642016-02-10 08:35:20 -0800108parser.add_argument("-z", "--min-size", type=int,
109 help="capture only allocations larger than this size")
110parser.add_argument("-Z", "--max-size", type=int,
111 help="capture only allocations smaller than this size")
Maria Kacik9389ab42017-01-18 21:43:41 -0800112parser.add_argument("-O", "--obj", type=str, default="c",
113 help="attach to malloc & free in the specified object")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800114
115args = parser.parse_args()
116
Sasha Goldshteind2241f42016-02-09 06:23:10 -0800117pid = args.pid
Sasha Goldshtein29228612016-02-07 12:20:19 -0800118command = args.command
119kernel_trace = (pid == -1 and command is None)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800120trace_all = args.trace
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800121interval = args.interval
122min_age_ns = 1e6 * args.older
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800123sample_every_n = args.sample_rate
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800124num_prints = args.count
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800125top_stacks = args.top
Sasha Goldshtein50459642016-02-10 08:35:20 -0800126min_size = args.min_size
127max_size = args.max_size
Maria Kacik9389ab42017-01-18 21:43:41 -0800128obj = args.obj
Sasha Goldshtein50459642016-02-10 08:35:20 -0800129
130if min_size is not None and max_size is not None and min_size > max_size:
131 print("min_size (-z) can't be greater than max_size (-Z)")
132 exit(1)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800133
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800134if command is not None:
135 print("Executing '%s' and tracing the resulting process." % command)
136 pid = run_command_get_pid(command)
Sasha Goldshtein29228612016-02-07 12:20:19 -0800137
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800138bpf_source = """
139#include <uapi/linux/ptrace.h>
140
141struct alloc_info_t {
142 u64 size;
143 u64 timestamp_ns;
Vicent Martie25ae032016-03-25 17:14:34 +0100144 int stack_id;
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800145};
146
147BPF_HASH(sizes, u64);
148BPF_HASH(allocs, u64, struct alloc_info_t);
Vicent Martie25ae032016-03-25 17:14:34 +0100149BPF_STACK_TRACE(stack_traces, 1024)
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800150
151int alloc_enter(struct pt_regs *ctx, size_t size)
152{
153 SIZE_FILTER
154 if (SAMPLE_EVERY_N > 1) {
155 u64 ts = bpf_ktime_get_ns();
156 if (ts % SAMPLE_EVERY_N != 0)
157 return 0;
158 }
159
160 u64 pid = bpf_get_current_pid_tgid();
161 u64 size64 = size;
162 sizes.update(&pid, &size64);
163
164 if (SHOULD_PRINT)
165 bpf_trace_printk("alloc entered, size = %u\\n", size);
166 return 0;
167}
168
169int alloc_exit(struct pt_regs *ctx)
170{
Naveen N. Rao4afa96a2016-05-03 14:54:21 +0530171 u64 address = PT_REGS_RC(ctx);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800172 u64 pid = bpf_get_current_pid_tgid();
173 u64* size64 = sizes.lookup(&pid);
174 struct alloc_info_t info = {0};
175
176 if (size64 == 0)
177 return 0; // missed alloc entry
178
179 info.size = *size64;
180 sizes.delete(&pid);
181
182 info.timestamp_ns = bpf_ktime_get_ns();
Vicent Martie25ae032016-03-25 17:14:34 +0100183 info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800184 allocs.update(&address, &info);
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700185
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800186 if (SHOULD_PRINT) {
Vicent Martie25ae032016-03-25 17:14:34 +0100187 bpf_trace_printk("alloc exited, size = %lu, result = %lx\\n",
188 info.size, address);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800189 }
190 return 0;
191}
192
193int free_enter(struct pt_regs *ctx, void *address)
194{
195 u64 addr = (u64)address;
196 struct alloc_info_t *info = allocs.lookup(&addr);
197 if (info == 0)
198 return 0;
199
200 allocs.delete(&addr);
201
202 if (SHOULD_PRINT) {
203 bpf_trace_printk("free entered, address = %lx, size = %lu\\n",
204 address, info->size);
205 }
206 return 0;
207}
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700208"""
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800209bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800210bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))
Sasha Goldshtein50459642016-02-10 08:35:20 -0800211
212size_filter = ""
213if min_size is not None and max_size is not None:
214 size_filter = "if (size < %d || size > %d) return 0;" % \
215 (min_size, max_size)
216elif min_size is not None:
217 size_filter = "if (size < %d) return 0;" % min_size
218elif max_size is not None:
219 size_filter = "if (size > %d) return 0;" % max_size
220bpf_source = bpf_source.replace("SIZE_FILTER", size_filter)
221
Vicent Martie25ae032016-03-25 17:14:34 +0100222stack_flags = "BPF_F_REUSE_STACKID"
223if not kernel_trace:
224 stack_flags += "|BPF_F_USER_STACK"
225bpf_source = bpf_source.replace("STACK_FLAGS", stack_flags)
226
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800227bpf_program = BPF(text=bpf_source)
228
229if not kernel_trace:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800230 print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800231 bpf_program.attach_uprobe(name=obj, sym="malloc",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800232 fn_name="alloc_enter", pid=pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800233 bpf_program.attach_uretprobe(name=obj, sym="malloc",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800234 fn_name="alloc_exit", pid=pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800235 bpf_program.attach_uprobe(name=obj, sym="free",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800236 fn_name="free_enter", pid=pid)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800237else:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800238 print("Attaching to kmalloc and kfree, Ctrl+C to quit.")
239 bpf_program.attach_kprobe(event="__kmalloc", fn_name="alloc_enter")
240 bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit")
241 bpf_program.attach_kprobe(event="kfree", fn_name="free_enter")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800242
Vicent Martie25ae032016-03-25 17:14:34 +0100243decoder = KStackDecoder() if kernel_trace else UStackDecoder(pid)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800244
245def print_outstanding():
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800246 print("[%s] Top %d stacks with outstanding allocations:" %
247 (datetime.now().strftime("%H:%M:%S"), top_stacks))
Vicent Martie25ae032016-03-25 17:14:34 +0100248 alloc_info = {}
249 allocs = bpf_program["allocs"]
250 stack_traces = bpf_program["stack_traces"]
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800251 for address, info in sorted(allocs.items(), key=lambda a: a[1].size):
Sasha Goldshtein60c41922017-02-09 04:19:53 -0500252 if BPF.monotonic_time() - min_age_ns < info.timestamp_ns:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800253 continue
Vicent Martie25ae032016-03-25 17:14:34 +0100254 if info.stack_id < 0:
255 continue
256 if info.stack_id in alloc_info:
257 alloc_info[info.stack_id].update(info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800258 else:
Vicent Martie25ae032016-03-25 17:14:34 +0100259 stack = list(stack_traces.walk(info.stack_id, decoder))
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300260 alloc_info[info.stack_id] = Allocation(stack,
261 info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800262 if args.show_allocs:
263 print("\taddr = %x size = %s" %
264 (address.value, info.size))
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300265 to_show = sorted(alloc_info.values(),
266 key=lambda a: a.size)[-top_stacks:]
Vicent Martie25ae032016-03-25 17:14:34 +0100267 for alloc in to_show:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800268 print("\t%d bytes in %d allocations from stack\n\t\t%s" %
Vicent Martie25ae032016-03-25 17:14:34 +0100269 (alloc.size, alloc.count, "\n\t\t".join(alloc.stack)))
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800270
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800271count_so_far = 0
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800272while True:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800273 if trace_all:
Brenden Blancoc94ab7a2016-03-11 15:34:29 -0800274 print(bpf_program.trace_fields())
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800275 else:
276 try:
277 sleep(interval)
278 except KeyboardInterrupt:
279 exit()
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700280 decoder.refresh()
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800281 print_outstanding()
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800282 count_so_far += 1
283 if num_prints is not None and count_so_far >= num_prints:
284 exit()