#!/usr/bin/python
#
# mallocstacks  Trace malloc() calls in a process and print the full
#               stack trace for all callsites.
#               For Linux, uses BCC, eBPF. Embedded C.
#
# This script is a basic example of the new Linux 4.6+ BPF_STACK_TRACE
# table API.
#
# Copyright 2016 GitHub, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")

from __future__ import print_function
from bcc import BPF, ProcessSymbols
from time import sleep
import sys

if len(sys.argv) < 2:
    print("USAGE: mallocstacks PID")
    exit()
pid = int(sys.argv[1])

# load BPF program
b = BPF(text="""
#include <uapi/linux/ptrace.h>

BPF_HASH(calls, int);
BPF_STACK_TRACE(stack_traces, 1024)

int alloc_enter(struct pt_regs *ctx, size_t size) {
    int key = stack_traces.get_stackid(ctx,
        BPF_F_USER_STACK|BPF_F_REUSE_STACKID);
    if (key < 0)
        return 0;

    u64 zero = 0, *val;
    val = calls.lookup_or_init(&key, &zero);
    (*val) += size;
    return 0;
};
""")

b.attach_uprobe(name="c", sym="malloc", fn_name="alloc_enter", pid=pid)
print("Attaching to malloc in pid %d, Ctrl+C to quit." % pid)

decoder = ProcessSymbols(pid)

# sleep until Ctrl-C
try:
    sleep(99999999)
except KeyboardInterrupt:
    pass

calls = b.get_table("calls")
stack_traces = b.get_table("stack_traces")

for k, v in reversed(sorted(calls.items(), key=lambda c: c[1].value)):
    print("%d bytes allocated at:" % v.value)
    for addr in stack_traces.walk(k.value):
        print("\t%s (%x)" % (decoder.decode_addr(addr), addr))
