xref: /aosp_15_r20/external/bcc/examples/lua/memleak.lua (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
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