blob: e5034cfb3711f5498893e6ca1c0aa63a26e87604 [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 Goldshtein49df9942017-02-08 23:22:06 -050014from bcc import BPF
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 Allocation(object):
22 def __init__(self, stack, size):
23 self.stack = stack
24 self.count = 1
25 self.size = size
26
27 def update(self, size):
28 self.count += 1
29 self.size += size
Sasha Goldshtein29228612016-02-07 12:20:19 -080030
Sasha Goldshtein751fce52016-02-08 02:57:02 -080031def run_command_get_output(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080032 p = subprocess.Popen(command.split(),
33 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
34 return iter(p.stdout.readline, b'')
Sasha Goldshtein29228612016-02-07 12:20:19 -080035
Sasha Goldshtein751fce52016-02-08 02:57:02 -080036def run_command_get_pid(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080037 p = subprocess.Popen(command.split())
38 return p.pid
Sasha Goldshtein751fce52016-02-08 02:57:02 -080039
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080040examples = """
41EXAMPLES:
42
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080043./memleak -p $(pidof allocs)
Sasha Goldshtein33522d72016-02-08 03:39:44 -080044 Trace allocations and display a summary of "leaked" (outstanding)
45 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080046./memleak -p $(pidof allocs) -t
Sasha Goldshtein33522d72016-02-08 03:39:44 -080047 Trace allocations and display each individual call to malloc/free
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080048./memleak -ap $(pidof allocs) 10
Sasha Goldshtein33522d72016-02-08 03:39:44 -080049 Trace allocations and display allocated addresses, sizes, and stacks
50 every 10 seconds for outstanding allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080051./memleak -c "./allocs"
Sasha Goldshtein33522d72016-02-08 03:39:44 -080052 Run the specified command and trace its allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080053./memleak
Sasha Goldshtein33522d72016-02-08 03:39:44 -080054 Trace allocations in kernel mode and display a summary of outstanding
55 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080056./memleak -o 60000
Sasha Goldshtein33522d72016-02-08 03:39:44 -080057 Trace allocations in kernel mode and display a summary of outstanding
58 allocations that are at least one minute (60 seconds) old
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080059./memleak -s 5
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -080060 Trace roughly every 5th allocation, to reduce overhead
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080061"""
62
63description = """
64Trace outstanding memory allocations that weren't freed.
65Supports both user-mode allocations made with malloc/free and kernel-mode
66allocations made with kmalloc/kfree.
67"""
68
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080069parser = argparse.ArgumentParser(description=description,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080070 formatter_class=argparse.RawDescriptionHelpFormatter,
71 epilog=examples)
Sasha Goldshteind2241f42016-02-09 06:23:10 -080072parser.add_argument("-p", "--pid", type=int, default=-1,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080073 help="the PID to trace; if not specified, trace kernel allocs")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080074parser.add_argument("-t", "--trace", action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -080075 help="print trace messages for each alloc/free call")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080076parser.add_argument("interval", nargs="?", default=5, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080077 help="interval in seconds to print outstanding allocations")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080078parser.add_argument("count", nargs="?", type=int,
79 help="number of times to print the report before exiting")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080080parser.add_argument("-a", "--show-allocs", default=False, action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -080081 help="show allocation addresses and sizes as well as call stacks")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080082parser.add_argument("-o", "--older", default=500, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -080083 help="prune allocations younger than this age in milliseconds")
Sasha Goldshtein29228612016-02-07 12:20:19 -080084parser.add_argument("-c", "--command",
Sasha Goldshtein33522d72016-02-08 03:39:44 -080085 help="execute and trace the specified command")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -080086parser.add_argument("-s", "--sample-rate", default=1, type=int,
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -080087 help="sample every N-th allocation to decrease the overhead")
Sasha Goldshteinc8148c82016-02-09 11:15:41 -080088parser.add_argument("-T", "--top", type=int, default=10,
89 help="display only this many top allocating stacks (by size)")
Sasha Goldshtein50459642016-02-10 08:35:20 -080090parser.add_argument("-z", "--min-size", type=int,
91 help="capture only allocations larger than this size")
92parser.add_argument("-Z", "--max-size", type=int,
93 help="capture only allocations smaller than this size")
Maria Kacik9389ab42017-01-18 21:43:41 -080094parser.add_argument("-O", "--obj", type=str, default="c",
95 help="attach to malloc & free in the specified object")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080096
97args = parser.parse_args()
98
Sasha Goldshteind2241f42016-02-09 06:23:10 -080099pid = args.pid
Sasha Goldshtein29228612016-02-07 12:20:19 -0800100command = args.command
101kernel_trace = (pid == -1 and command is None)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800102trace_all = args.trace
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800103interval = args.interval
104min_age_ns = 1e6 * args.older
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800105sample_every_n = args.sample_rate
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800106num_prints = args.count
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800107top_stacks = args.top
Sasha Goldshtein50459642016-02-10 08:35:20 -0800108min_size = args.min_size
109max_size = args.max_size
Maria Kacik9389ab42017-01-18 21:43:41 -0800110obj = args.obj
Sasha Goldshtein50459642016-02-10 08:35:20 -0800111
112if min_size is not None and max_size is not None and min_size > max_size:
113 print("min_size (-z) can't be greater than max_size (-Z)")
114 exit(1)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800115
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800116if command is not None:
117 print("Executing '%s' and tracing the resulting process." % command)
118 pid = run_command_get_pid(command)
Sasha Goldshtein29228612016-02-07 12:20:19 -0800119
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800120bpf_source = """
121#include <uapi/linux/ptrace.h>
122
123struct alloc_info_t {
124 u64 size;
125 u64 timestamp_ns;
Vicent Martie25ae032016-03-25 17:14:34 +0100126 int stack_id;
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800127};
128
129BPF_HASH(sizes, u64);
130BPF_HASH(allocs, u64, struct alloc_info_t);
Vicent Martie25ae032016-03-25 17:14:34 +0100131BPF_STACK_TRACE(stack_traces, 1024)
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800132
133int alloc_enter(struct pt_regs *ctx, size_t size)
134{
135 SIZE_FILTER
136 if (SAMPLE_EVERY_N > 1) {
137 u64 ts = bpf_ktime_get_ns();
138 if (ts % SAMPLE_EVERY_N != 0)
139 return 0;
140 }
141
142 u64 pid = bpf_get_current_pid_tgid();
143 u64 size64 = size;
144 sizes.update(&pid, &size64);
145
146 if (SHOULD_PRINT)
147 bpf_trace_printk("alloc entered, size = %u\\n", size);
148 return 0;
149}
150
151int alloc_exit(struct pt_regs *ctx)
152{
Naveen N. Rao4afa96a2016-05-03 14:54:21 +0530153 u64 address = PT_REGS_RC(ctx);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800154 u64 pid = bpf_get_current_pid_tgid();
155 u64* size64 = sizes.lookup(&pid);
156 struct alloc_info_t info = {0};
157
158 if (size64 == 0)
159 return 0; // missed alloc entry
160
161 info.size = *size64;
162 sizes.delete(&pid);
163
164 info.timestamp_ns = bpf_ktime_get_ns();
Vicent Martie25ae032016-03-25 17:14:34 +0100165 info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800166 allocs.update(&address, &info);
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700167
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800168 if (SHOULD_PRINT) {
Vicent Martie25ae032016-03-25 17:14:34 +0100169 bpf_trace_printk("alloc exited, size = %lu, result = %lx\\n",
170 info.size, address);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800171 }
172 return 0;
173}
174
175int free_enter(struct pt_regs *ctx, void *address)
176{
177 u64 addr = (u64)address;
178 struct alloc_info_t *info = allocs.lookup(&addr);
179 if (info == 0)
180 return 0;
181
182 allocs.delete(&addr);
183
184 if (SHOULD_PRINT) {
185 bpf_trace_printk("free entered, address = %lx, size = %lu\\n",
186 address, info->size);
187 }
188 return 0;
189}
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700190"""
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800191bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800192bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))
Sasha Goldshtein50459642016-02-10 08:35:20 -0800193
194size_filter = ""
195if min_size is not None and max_size is not None:
196 size_filter = "if (size < %d || size > %d) return 0;" % \
197 (min_size, max_size)
198elif min_size is not None:
199 size_filter = "if (size < %d) return 0;" % min_size
200elif max_size is not None:
201 size_filter = "if (size > %d) return 0;" % max_size
202bpf_source = bpf_source.replace("SIZE_FILTER", size_filter)
203
Vicent Martie25ae032016-03-25 17:14:34 +0100204stack_flags = "BPF_F_REUSE_STACKID"
205if not kernel_trace:
206 stack_flags += "|BPF_F_USER_STACK"
207bpf_source = bpf_source.replace("STACK_FLAGS", stack_flags)
208
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800209bpf_program = BPF(text=bpf_source)
210
211if not kernel_trace:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800212 print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800213 bpf_program.attach_uprobe(name=obj, sym="malloc",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800214 fn_name="alloc_enter", pid=pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800215 bpf_program.attach_uretprobe(name=obj, sym="malloc",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800216 fn_name="alloc_exit", pid=pid)
Maria Kacik9389ab42017-01-18 21:43:41 -0800217 bpf_program.attach_uprobe(name=obj, sym="free",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800218 fn_name="free_enter", pid=pid)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800219else:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800220 print("Attaching to kmalloc and kfree, Ctrl+C to quit.")
221 bpf_program.attach_kprobe(event="__kmalloc", fn_name="alloc_enter")
222 bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit")
223 bpf_program.attach_kprobe(event="kfree", fn_name="free_enter")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800224
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800225def print_outstanding():
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800226 print("[%s] Top %d stacks with outstanding allocations:" %
227 (datetime.now().strftime("%H:%M:%S"), top_stacks))
Vicent Martie25ae032016-03-25 17:14:34 +0100228 alloc_info = {}
229 allocs = bpf_program["allocs"]
230 stack_traces = bpf_program["stack_traces"]
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800231 for address, info in sorted(allocs.items(), key=lambda a: a[1].size):
Sasha Goldshtein60c41922017-02-09 04:19:53 -0500232 if BPF.monotonic_time() - min_age_ns < info.timestamp_ns:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800233 continue
Vicent Martie25ae032016-03-25 17:14:34 +0100234 if info.stack_id < 0:
235 continue
236 if info.stack_id in alloc_info:
237 alloc_info[info.stack_id].update(info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800238 else:
Sasha Goldshtein49df9942017-02-08 23:22:06 -0500239 stack = list(stack_traces.walk(info.stack_id))
240 combined = []
241 for addr in stack:
242 combined.append(bpf_program.sym(addr, pid,
243 show_module=True, show_address=True))
244 alloc_info[info.stack_id] = Allocation(combined,
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300245 info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800246 if args.show_allocs:
247 print("\taddr = %x size = %s" %
248 (address.value, info.size))
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300249 to_show = sorted(alloc_info.values(),
250 key=lambda a: a.size)[-top_stacks:]
Vicent Martie25ae032016-03-25 17:14:34 +0100251 for alloc in to_show:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800252 print("\t%d bytes in %d allocations from stack\n\t\t%s" %
Vicent Martie25ae032016-03-25 17:14:34 +0100253 (alloc.size, alloc.count, "\n\t\t".join(alloc.stack)))
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800254
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800255count_so_far = 0
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800256while True:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800257 if trace_all:
Brenden Blancoc94ab7a2016-03-11 15:34:29 -0800258 print(bpf_program.trace_fields())
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800259 else:
260 try:
261 sleep(interval)
262 except KeyboardInterrupt:
263 exit()
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800264 print_outstanding()
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800265 count_so_far += 1
266 if num_prints is not None and count_so_far >= num_prints:
267 exit()