1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# uflow Trace method execution flow in high-level languages. 5# For Linux, uses BCC, eBPF. 6# 7# USAGE: uflow [-C CLASS] [-M METHOD] [-v] {java,perl,php,python,ruby,tcl} pid 8# 9# Copyright 2016 Sasha Goldshtein 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 27-Oct-2016 Sasha Goldshtein Created this. 13 14from __future__ import print_function 15import argparse 16from bcc import BPF, USDT, utils 17import ctypes as ct 18import time 19import os 20 21languages = ["java", "perl", "php", "python", "ruby", "tcl"] 22 23examples = """examples: 24 ./uflow -l java 185 # trace Java method calls in process 185 25 ./uflow -l ruby 134 # trace Ruby method calls in process 134 26 ./uflow -M indexOf -l java 185 # trace only 'indexOf'-prefixed methods 27 ./uflow -C '<stdin>' -l python 180 # trace only REPL-defined methods 28""" 29parser = argparse.ArgumentParser( 30 description="Trace method execution flow in high-level languages.", 31 formatter_class=argparse.RawDescriptionHelpFormatter, 32 epilog=examples) 33parser.add_argument("-l", "--language", choices=languages, 34 help="language to trace") 35parser.add_argument("pid", type=int, help="process id to attach to") 36parser.add_argument("-M", "--method", 37 help="trace only calls to methods starting with this prefix") 38parser.add_argument("-C", "--class", dest="clazz", 39 help="trace only calls to classes starting with this prefix") 40parser.add_argument("-v", "--verbose", action="store_true", 41 help="verbose mode: print the BPF program (for debugging purposes)") 42parser.add_argument("--ebpf", action="store_true", 43 help=argparse.SUPPRESS) 44args = parser.parse_args() 45 46usdt = USDT(pid=args.pid) 47 48program = """ 49struct call_t { 50 u64 depth; // first bit is direction (0 entry, 1 return) 51 u64 pid; // (tgid << 32) + pid from bpf_get_current... 52 char clazz[80]; 53 char method[80]; 54}; 55 56BPF_PERF_OUTPUT(calls); 57BPF_HASH(entry, u64, u64); 58""" 59 60prefix_template = """ 61static inline bool prefix_%s(char *actual) { 62 char expected[] = "%s"; 63 for (int i = 0; i < sizeof(expected) - 1; ++i) { 64 if (expected[i] != actual[i]) { 65 return false; 66 } 67 } 68 return true; 69} 70""" 71 72if args.clazz: 73 program += prefix_template % ("class", args.clazz) 74if args.method: 75 program += prefix_template % ("method", args.method) 76 77trace_template = """ 78int NAME(struct pt_regs *ctx) { 79 u64 *depth, zero = 0, clazz = 0, method = 0 ; 80 struct call_t data = {}; 81 82 READ_CLASS 83 READ_METHOD 84 bpf_probe_read(&data.clazz, sizeof(data.clazz), (void *)clazz); 85 bpf_probe_read(&data.method, sizeof(data.method), (void *)method); 86 87 FILTER_CLASS 88 FILTER_METHOD 89 90 data.pid = bpf_get_current_pid_tgid(); 91 depth = entry.lookup_or_init(&data.pid, &zero); 92 data.depth = DEPTH; 93 UPDATE 94 95 calls.perf_submit(ctx, &data, sizeof(data)); 96 return 0; 97} 98""" 99 100def enable_probe(probe_name, func_name, read_class, read_method, is_return): 101 global program, trace_template, usdt 102 depth = "*depth + 1" if not is_return else "*depth | (1ULL << 63)" 103 update = "++(*depth);" if not is_return else "if (*depth) --(*depth);" 104 filter_class = "if (!prefix_class(data.clazz)) { return 0; }" \ 105 if args.clazz else "" 106 filter_method = "if (!prefix_method(data.method)) { return 0; }" \ 107 if args.method else "" 108 program += trace_template.replace("NAME", func_name) \ 109 .replace("READ_CLASS", read_class) \ 110 .replace("READ_METHOD", read_method) \ 111 .replace("FILTER_CLASS", filter_class) \ 112 .replace("FILTER_METHOD", filter_method) \ 113 .replace("DEPTH", depth) \ 114 .replace("UPDATE", update) 115 usdt.enable_probe_or_bail(probe_name, func_name) 116 117usdt = USDT(pid=args.pid) 118 119language = args.language 120if not language: 121 language = utils.detect_language(languages, args.pid) 122 123if language == "java": 124 enable_probe("method__entry", "java_entry", 125 "bpf_usdt_readarg(2, ctx, &clazz);", 126 "bpf_usdt_readarg(4, ctx, &method);", is_return=False) 127 enable_probe("method__return", "java_return", 128 "bpf_usdt_readarg(2, ctx, &clazz);", 129 "bpf_usdt_readarg(4, ctx, &method);", is_return=True) 130elif language == "perl": 131 enable_probe("sub__entry", "perl_entry", 132 "bpf_usdt_readarg(2, ctx, &clazz);", 133 "bpf_usdt_readarg(1, ctx, &method);", is_return=False) 134 enable_probe("sub__return", "perl_return", 135 "bpf_usdt_readarg(2, ctx, &clazz);", 136 "bpf_usdt_readarg(1, ctx, &method);", is_return=True) 137elif language == "php": 138 enable_probe("function__entry", "php_entry", 139 "bpf_usdt_readarg(4, ctx, &clazz);", 140 "bpf_usdt_readarg(1, ctx, &method);", is_return=False) 141 enable_probe("function__return", "php_return", 142 "bpf_usdt_readarg(4, ctx, &clazz);", 143 "bpf_usdt_readarg(1, ctx, &method);", is_return=True) 144elif language == "python": 145 enable_probe("function__entry", "python_entry", 146 "bpf_usdt_readarg(1, ctx, &clazz);", # filename really 147 "bpf_usdt_readarg(2, ctx, &method);", is_return=False) 148 enable_probe("function__return", "python_return", 149 "bpf_usdt_readarg(1, ctx, &clazz);", # filename really 150 "bpf_usdt_readarg(2, ctx, &method);", is_return=True) 151elif language == "ruby": 152 enable_probe("method__entry", "ruby_entry", 153 "bpf_usdt_readarg(1, ctx, &clazz);", 154 "bpf_usdt_readarg(2, ctx, &method);", is_return=False) 155 enable_probe("method__return", "ruby_return", 156 "bpf_usdt_readarg(1, ctx, &clazz);", 157 "bpf_usdt_readarg(2, ctx, &method);", is_return=True) 158 enable_probe("cmethod__entry", "ruby_centry", 159 "bpf_usdt_readarg(1, ctx, &clazz);", 160 "bpf_usdt_readarg(2, ctx, &method);", is_return=False) 161 enable_probe("cmethod__return", "ruby_creturn", 162 "bpf_usdt_readarg(1, ctx, &clazz);", 163 "bpf_usdt_readarg(2, ctx, &method);", is_return=True) 164elif language == "tcl": 165 enable_probe("proc__args", "tcl_entry", 166 "", # no class/file info available 167 "bpf_usdt_readarg(1, ctx, &method);", is_return=False) 168 enable_probe("proc__return", "tcl_return", 169 "", # no class/file info available 170 "bpf_usdt_readarg(1, ctx, &method);", is_return=True) 171else: 172 print("No language detected; use -l to trace a language.") 173 exit(1) 174 175if args.ebpf or args.verbose: 176 if args.verbose: 177 print(usdt.get_text()) 178 print(program) 179 if args.ebpf: 180 exit() 181 182bpf = BPF(text=program, usdt_contexts=[usdt]) 183print("Tracing method calls in %s process %d... Ctrl-C to quit." % 184 (language, args.pid)) 185print("%-3s %-6s %-6s %-8s %s" % ("CPU", "PID", "TID", "TIME(us)", "METHOD")) 186 187class CallEvent(ct.Structure): 188 _fields_ = [ 189 ("depth", ct.c_ulonglong), 190 ("pid", ct.c_ulonglong), 191 ("clazz", ct.c_char * 80), 192 ("method", ct.c_char * 80) 193 ] 194 195start_ts = time.time() 196 197def print_event(cpu, data, size): 198 event = ct.cast(data, ct.POINTER(CallEvent)).contents 199 depth = event.depth & (~(1 << 63)) 200 direction = "<- " if event.depth & (1 << 63) else "-> " 201 print("%-3d %-6d %-6d %-8.3f %-40s" % (cpu, event.pid >> 32, 202 event.pid & 0xFFFFFFFF, time.time() - start_ts, 203 (" " * (depth - 1)) + direction + \ 204 event.clazz.decode('utf-8', 'replace') + "." + \ 205 event.method.decode('utf-8', 'replace'))) 206 207bpf["calls"].open_perf_buffer(print_event) 208while 1: 209 try: 210 bpf.perf_buffer_poll() 211 except KeyboardInterrupt: 212 exit() 213