1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/env bcc-lua 2*387f9dfdSAndroid Build Coastguard Worker--[[ 3*387f9dfdSAndroid Build Coastguard WorkerCopyright 2016 GitHub, Inc 4*387f9dfdSAndroid Build Coastguard Worker 5*387f9dfdSAndroid Build Coastguard WorkerLicensed under the Apache License, Version 2.0 (the "License"); 6*387f9dfdSAndroid Build Coastguard Workeryou may not use this file except in compliance with the License. 7*387f9dfdSAndroid Build Coastguard WorkerYou may obtain a copy of the License at 8*387f9dfdSAndroid Build Coastguard Worker 9*387f9dfdSAndroid Build Coastguard Workerhttp://www.apache.org/licenses/LICENSE-2.0 10*387f9dfdSAndroid Build Coastguard Worker 11*387f9dfdSAndroid Build Coastguard WorkerUnless required by applicable law or agreed to in writing, software 12*387f9dfdSAndroid Build Coastguard Workerdistributed under the License is distributed on an "AS IS" BASIS, 13*387f9dfdSAndroid Build Coastguard WorkerWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*387f9dfdSAndroid Build Coastguard WorkerSee the License for the specific language governing permissions and 15*387f9dfdSAndroid Build Coastguard Workerlimitations under the License. 16*387f9dfdSAndroid Build Coastguard Worker]] 17*387f9dfdSAndroid Build Coastguard Worker 18*387f9dfdSAndroid Build Coastguard Workerlocal bpf_source = [[ 19*387f9dfdSAndroid Build Coastguard Worker#include <uapi/linux/ptrace.h> 20*387f9dfdSAndroid Build Coastguard Worker 21*387f9dfdSAndroid Build Coastguard Workerstruct alloc_info_t { 22*387f9dfdSAndroid Build Coastguard Worker u64 size; 23*387f9dfdSAndroid Build Coastguard Worker u64 timestamp_ns; 24*387f9dfdSAndroid Build Coastguard Worker int stack_id; 25*387f9dfdSAndroid Build Coastguard Worker}; 26*387f9dfdSAndroid Build Coastguard Worker 27*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(sizes, u64); 28*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(allocs, u64, struct alloc_info_t); 29*387f9dfdSAndroid Build Coastguard WorkerBPF_STACK_TRACE(stack_traces, 10240); 30*387f9dfdSAndroid Build Coastguard Worker 31*387f9dfdSAndroid Build Coastguard Workerint alloc_enter(struct pt_regs *ctx, size_t size) 32*387f9dfdSAndroid Build Coastguard Worker{ 33*387f9dfdSAndroid Build Coastguard Worker SIZE_FILTER 34*387f9dfdSAndroid Build Coastguard Worker if (SAMPLE_EVERY_N > 1) { 35*387f9dfdSAndroid Build Coastguard Worker u64 ts = bpf_ktime_get_ns(); 36*387f9dfdSAndroid Build Coastguard Worker if (ts % SAMPLE_EVERY_N != 0) 37*387f9dfdSAndroid Build Coastguard Worker return 0; 38*387f9dfdSAndroid Build Coastguard Worker } 39*387f9dfdSAndroid Build Coastguard Worker 40*387f9dfdSAndroid Build Coastguard Worker u64 pid = bpf_get_current_pid_tgid(); 41*387f9dfdSAndroid Build Coastguard Worker u64 size64 = size; 42*387f9dfdSAndroid Build Coastguard Worker sizes.update(&pid, &size64); 43*387f9dfdSAndroid Build Coastguard Worker 44*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) 45*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("alloc entered, size = %u\n", size); 46*387f9dfdSAndroid Build Coastguard Worker return 0; 47*387f9dfdSAndroid Build Coastguard Worker} 48*387f9dfdSAndroid Build Coastguard Worker 49*387f9dfdSAndroid Build Coastguard Workerint alloc_exit(struct pt_regs *ctx) 50*387f9dfdSAndroid Build Coastguard Worker{ 51*387f9dfdSAndroid Build Coastguard Worker u64 address = PT_REGS_RC(ctx); 52*387f9dfdSAndroid Build Coastguard Worker u64 pid = bpf_get_current_pid_tgid(); 53*387f9dfdSAndroid Build Coastguard Worker u64* size64 = sizes.lookup(&pid); 54*387f9dfdSAndroid Build Coastguard Worker struct alloc_info_t info = {0}; 55*387f9dfdSAndroid Build Coastguard Worker 56*387f9dfdSAndroid Build Coastguard Worker if (size64 == 0) 57*387f9dfdSAndroid Build Coastguard Worker return 0; // missed alloc entry 58*387f9dfdSAndroid Build Coastguard Worker 59*387f9dfdSAndroid Build Coastguard Worker info.size = *size64; 60*387f9dfdSAndroid Build Coastguard Worker sizes.delete(&pid); 61*387f9dfdSAndroid Build Coastguard Worker 62*387f9dfdSAndroid Build Coastguard Worker info.timestamp_ns = bpf_ktime_get_ns(); 63*387f9dfdSAndroid Build Coastguard Worker info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS); 64*387f9dfdSAndroid Build Coastguard Worker 65*387f9dfdSAndroid Build Coastguard Worker allocs.update(&address, &info); 66*387f9dfdSAndroid Build Coastguard Worker 67*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) { 68*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("alloc exited, size = %lu, result = %lx\n", 69*387f9dfdSAndroid Build Coastguard Worker info.size, address); 70*387f9dfdSAndroid Build Coastguard Worker } 71*387f9dfdSAndroid Build Coastguard Worker return 0; 72*387f9dfdSAndroid Build Coastguard Worker} 73*387f9dfdSAndroid Build Coastguard Worker 74*387f9dfdSAndroid Build Coastguard Workerint free_enter(struct pt_regs *ctx, void *address) 75*387f9dfdSAndroid Build Coastguard Worker{ 76*387f9dfdSAndroid Build Coastguard Worker u64 addr = (u64)address; 77*387f9dfdSAndroid Build Coastguard Worker struct alloc_info_t *info = allocs.lookup(&addr); 78*387f9dfdSAndroid Build Coastguard Worker if (info == 0) 79*387f9dfdSAndroid Build Coastguard Worker return 0; 80*387f9dfdSAndroid Build Coastguard Worker 81*387f9dfdSAndroid Build Coastguard Worker allocs.delete(&addr); 82*387f9dfdSAndroid Build Coastguard Worker 83*387f9dfdSAndroid Build Coastguard Worker if (SHOULD_PRINT) { 84*387f9dfdSAndroid Build Coastguard Worker bpf_trace_printk("free entered, address = %lx, size = %lu\n", 85*387f9dfdSAndroid Build Coastguard Worker address, info->size); 86*387f9dfdSAndroid Build Coastguard Worker } 87*387f9dfdSAndroid Build Coastguard Worker return 0; 88*387f9dfdSAndroid Build Coastguard Worker} 89*387f9dfdSAndroid Build Coastguard Worker]] 90*387f9dfdSAndroid Build Coastguard Worker 91*387f9dfdSAndroid Build Coastguard Workerreturn function(BPF, utils) 92*387f9dfdSAndroid Build Coastguard Worker local parser = utils.argparse("memleak", "Catch memory leaks") 93*387f9dfdSAndroid Build Coastguard Worker parser:flag("-t --trace") 94*387f9dfdSAndroid Build Coastguard Worker parser:flag("-a --show-allocs") 95*387f9dfdSAndroid Build Coastguard Worker parser:option("-p --pid"):convert(tonumber) 96*387f9dfdSAndroid Build Coastguard Worker 97*387f9dfdSAndroid Build Coastguard Worker parser:option("-i --interval", "", 5):convert(tonumber) 98*387f9dfdSAndroid Build Coastguard Worker parser:option("-o --older", "", 500):convert(tonumber) 99*387f9dfdSAndroid Build Coastguard Worker parser:option("-s --sample-rate", "", 1):convert(tonumber) 100*387f9dfdSAndroid Build Coastguard Worker 101*387f9dfdSAndroid Build Coastguard Worker parser:option("-z --min-size", ""):convert(tonumber) 102*387f9dfdSAndroid Build Coastguard Worker parser:option("-Z --max-size", ""):convert(tonumber) 103*387f9dfdSAndroid Build Coastguard Worker parser:option("-T --top", "", 10):convert(tonumber) 104*387f9dfdSAndroid Build Coastguard Worker 105*387f9dfdSAndroid Build Coastguard Worker local args = parser:parse() 106*387f9dfdSAndroid Build Coastguard Worker 107*387f9dfdSAndroid Build Coastguard Worker local size_filter = "" 108*387f9dfdSAndroid Build Coastguard Worker if args.min_size and args.max_size then 109*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size < %d || size > %d) return 0;" % {args.min_size, args.max_size} 110*387f9dfdSAndroid Build Coastguard Worker elseif args.min_size then 111*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size < %d) return 0;" % args.min_size 112*387f9dfdSAndroid Build Coastguard Worker elseif args.max_size then 113*387f9dfdSAndroid Build Coastguard Worker size_filter = "if (size > %d) return 0;" % args.max_size 114*387f9dfdSAndroid Build Coastguard Worker end 115*387f9dfdSAndroid Build Coastguard Worker 116*387f9dfdSAndroid Build Coastguard Worker local stack_flags = "0" 117*387f9dfdSAndroid Build Coastguard Worker if args.pid then 118*387f9dfdSAndroid Build Coastguard Worker stack_flags = stack_flags .. "|BPF_F_USER_STACK" 119*387f9dfdSAndroid Build Coastguard Worker end 120*387f9dfdSAndroid Build Coastguard Worker 121*387f9dfdSAndroid Build Coastguard Worker local text = bpf_source 122*387f9dfdSAndroid Build Coastguard Worker text = text:gsub("SIZE_FILTER", size_filter) 123*387f9dfdSAndroid Build Coastguard Worker text = text:gsub("STACK_FLAGS", stack_flags) 124*387f9dfdSAndroid Build Coastguard Worker text = text:gsub("SHOULD_PRINT", args.trace and "1" or "0") 125*387f9dfdSAndroid Build Coastguard Worker text = text:gsub("SAMPLE_EVERY_N", tostring(args.sample_rate)) 126*387f9dfdSAndroid Build Coastguard Worker 127*387f9dfdSAndroid Build Coastguard Worker local bpf = BPF:new{text=text, debug=0} 128*387f9dfdSAndroid Build Coastguard Worker local syms = nil 129*387f9dfdSAndroid Build Coastguard Worker local min_age_ns = args.older * 1e6 130*387f9dfdSAndroid Build Coastguard Worker 131*387f9dfdSAndroid Build Coastguard Worker if args.pid then 132*387f9dfdSAndroid Build Coastguard Worker print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % args.pid) 133*387f9dfdSAndroid Build Coastguard Worker bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_enter", pid=args.pid} 134*387f9dfdSAndroid Build Coastguard Worker bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_exit", pid=args.pid, retprobe=true} 135*387f9dfdSAndroid Build Coastguard Worker bpf:attach_uprobe{name="c", sym="free", fn_name="free_enter", pid=args.pid} 136*387f9dfdSAndroid Build Coastguard Worker else 137*387f9dfdSAndroid Build Coastguard Worker print("Attaching to kmalloc and kfree, Ctrl+C to quit.") 138*387f9dfdSAndroid Build Coastguard Worker bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_enter"} 139*387f9dfdSAndroid Build Coastguard Worker bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_exit", retprobe=true} -- TODO 140*387f9dfdSAndroid Build Coastguard Worker bpf:attach_kprobe{event="kfree", fn_name="free_enter"} 141*387f9dfdSAndroid Build Coastguard Worker end 142*387f9dfdSAndroid Build Coastguard Worker 143*387f9dfdSAndroid Build Coastguard Worker local syms = BPF.SymbolCache(args.pid) 144*387f9dfdSAndroid Build Coastguard Worker local allocs = bpf:get_table("allocs") 145*387f9dfdSAndroid Build Coastguard Worker local stack_traces = bpf:get_table("stack_traces") 146*387f9dfdSAndroid Build Coastguard Worker 147*387f9dfdSAndroid Build Coastguard Worker local function resolve(addr) 148*387f9dfdSAndroid Build Coastguard Worker local sym = syms:resolve(addr) 149*387f9dfdSAndroid Build Coastguard Worker if args.pid == nil then 150*387f9dfdSAndroid Build Coastguard Worker sym = sym .. " [kernel]" 151*387f9dfdSAndroid Build Coastguard Worker end 152*387f9dfdSAndroid Build Coastguard Worker return string.format("%s (%p)", sym, addr) 153*387f9dfdSAndroid Build Coastguard Worker end 154*387f9dfdSAndroid Build Coastguard Worker 155*387f9dfdSAndroid Build Coastguard Worker local function print_outstanding() 156*387f9dfdSAndroid Build Coastguard Worker local alloc_info = {} 157*387f9dfdSAndroid Build Coastguard Worker local now = utils.posix.time_ns() 158*387f9dfdSAndroid Build Coastguard Worker 159*387f9dfdSAndroid Build Coastguard Worker print("[%s] Top %d stacks with outstanding allocations:" % 160*387f9dfdSAndroid Build Coastguard Worker {os.date("%H:%M:%S"), args.top}) 161*387f9dfdSAndroid Build Coastguard Worker 162*387f9dfdSAndroid Build Coastguard Worker for address, info in allocs:items() do 163*387f9dfdSAndroid Build Coastguard Worker if now - min_age_ns >= tonumber(info.timestamp_ns) then 164*387f9dfdSAndroid Build Coastguard Worker local stack_id = tonumber(info.stack_id) 165*387f9dfdSAndroid Build Coastguard Worker 166*387f9dfdSAndroid Build Coastguard Worker if stack_id >= 0 then 167*387f9dfdSAndroid Build Coastguard Worker if alloc_info[stack_id] then 168*387f9dfdSAndroid Build Coastguard Worker local s = alloc_info[stack_id] 169*387f9dfdSAndroid Build Coastguard Worker s.count = s.count + 1 170*387f9dfdSAndroid Build Coastguard Worker s.size = s.size + tonumber(info.size) 171*387f9dfdSAndroid Build Coastguard Worker else 172*387f9dfdSAndroid Build Coastguard Worker local stack = stack_traces:get(stack_id, resolve) 173*387f9dfdSAndroid Build Coastguard Worker alloc_info[stack_id] = { stack=stack, count=1, size=tonumber(info.size) } 174*387f9dfdSAndroid Build Coastguard Worker end 175*387f9dfdSAndroid Build Coastguard Worker end 176*387f9dfdSAndroid Build Coastguard Worker 177*387f9dfdSAndroid Build Coastguard Worker if args.show_allocs then 178*387f9dfdSAndroid Build Coastguard Worker print("\taddr = %p size = %s" % {address, tonumber(info.size)}) 179*387f9dfdSAndroid Build Coastguard Worker end 180*387f9dfdSAndroid Build Coastguard Worker end 181*387f9dfdSAndroid Build Coastguard Worker end 182*387f9dfdSAndroid Build Coastguard Worker 183*387f9dfdSAndroid Build Coastguard Worker local top = table.values(alloc_info) 184*387f9dfdSAndroid Build Coastguard Worker table.sort(top, function(a, b) return a.size > b.size end) 185*387f9dfdSAndroid Build Coastguard Worker 186*387f9dfdSAndroid Build Coastguard Worker for n, alloc in ipairs(top) do 187*387f9dfdSAndroid Build Coastguard Worker print("\t%d bytes in %d allocations from stack\n\t\t%s" % 188*387f9dfdSAndroid Build Coastguard Worker {alloc.size, alloc.count, table.concat(alloc.stack, "\n\t\t")}) 189*387f9dfdSAndroid Build Coastguard Worker if n == args.top then break end 190*387f9dfdSAndroid Build Coastguard Worker end 191*387f9dfdSAndroid Build Coastguard Worker end 192*387f9dfdSAndroid Build Coastguard Worker 193*387f9dfdSAndroid Build Coastguard Worker if args.trace then 194*387f9dfdSAndroid Build Coastguard Worker local pipe = bpf:pipe() 195*387f9dfdSAndroid Build Coastguard Worker while true do 196*387f9dfdSAndroid Build Coastguard Worker print(pipe:trace_fields()) 197*387f9dfdSAndroid Build Coastguard Worker end 198*387f9dfdSAndroid Build Coastguard Worker else 199*387f9dfdSAndroid Build Coastguard Worker while true do 200*387f9dfdSAndroid Build Coastguard Worker utils.posix.sleep(args.interval) 201*387f9dfdSAndroid Build Coastguard Worker syms:refresh() 202*387f9dfdSAndroid Build Coastguard Worker print_outstanding() 203*387f9dfdSAndroid Build Coastguard Worker end 204*387f9dfdSAndroid Build Coastguard Worker end 205*387f9dfdSAndroid Build Coastguard Workerend 206