Skip to content

Instantly share code, notes, and snippets.

@d0c-s4vage
Last active August 29, 2015 14:25
Show Gist options
  • Save d0c-s4vage/3bc1d5caab0556e76e99 to your computer and use it in GitHub Desktop.
Save d0c-s4vage/3bc1d5caab0556e76e99 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# encoding: utf-8
import clang
import clang.cindex
from clang.cindex import CursorKind as CK
import os
import pprint
import sys
class Function(object):
def __init__(self, cursor, name, params, callees=None, callers=None):
# self.cursor.location.line
# self.cursor.location.file.name
self.cursor = cursor
self.name = name
self.params = params
self.callees = [] if callees is None else callees
self.callers = [] if callers is None else callers
self.signature = "{}__{}".format(
name,
"_".join(str(param) for param in self.params)
)
class FunctionCall(object):
def __init__(self, cursor, target_name, params, calling_function):
self.cursor = cursor
self.target_name = target_name
self.params = params
self.calling_function = calling_function
def __repr__(self):
return "<{}({}) from {} at {}:{}>".format(
self.target_name,
", ".join(["X"] * len(self.params)),
self.calling_function,
self.cursor.location.file.name,
self.cursor.location.line
)
class FunctionTracer(object):
def __init__(self, tu):
self._tu = tu
self.cursor_switch = {
CK.TRANSLATION_UNIT : self._handle_translation_unit,
CK.FUNCTION_DECL : self._handle_function_decl,
CK.CALL_EXPR : self._handle_call_expr
}
self._level = -1
self._functions = {}
self._curr_function = None
self._handle_cursor(tu.cursor)
def trace_func(self, func_name):
if func_name not in self._functions:
raise Exception("No information for func '{}' recorded".format(func_name))
res = []
for func in self._functions[func_name]:
for x in self._do_trace_func(func):
res.append(x)
return res
# -----------------------------------------------
def _do_trace_func(self, func):
res = []
for caller in func.callers:
res.append((caller, self.trace_func(caller.calling_function)))
return res
# -----------------------------------------------
def _add_function(self, func):
self._functions.setdefault(func.name, []).append(func)
def _set_curr_func(self, func):
self._curr_function = func
def _add_func_caller(self, func_call):
if func_call.target_name not in self._functions:
raise Exception("Function '{}' not defined?".format(func_call.target_name))
called_func_opts = self._functions[func_call.target_name]
for matched_func in self._functions[func_call.target_name]:
if len(matched_func.params) <> len(func_call.params):
continue
matched_func.callers.append(func_call)
self._curr_function.callees.append(func_call)
def _add_curr_func_callee(self, callee):
self._curr_function.callees.setdefault(callee.name, []).append(callee)
# -----------------------------------------------
def _handle_call_expr(self, cursor):
params = []
for param in cursor.get_arguments():
params.append(param)
called_func_name = cursor.displayname
func_call = FunctionCall(cursor, called_func_name, params, self._curr_function.name)
self._add_func_caller(func_call)
def _handle_function_decl(self, cursor):
params = []
for arg in cursor.get_arguments():
params.append(arg.displayname)
func = Function(cursor, cursor.displayname.split("(")[0], params)
self._set_curr_func(func)
self._add_function(func)
for child in cursor.get_children():
self._handle_cursor(child)
def _handle_translation_unit(self, cursor):
for child in cursor.get_children():
self._handle_cursor(child)
def _handle_cursor(self, cursor):
self._level += 1
#print("{}visiting {}".format(" "*self._level, cursor.kind))
res = None
if cursor.kind in self.cursor_switch:
res = self.cursor_switch[cursor.kind](cursor)
else:
for child in cursor.get_children():
self._handle_cursor(child)
self._level -= 1
return res
def print_callstacks(stack, *curr_stack):
for func_call, callers in stack:
formatted = "{}:{} in {}".format(
func_call.cursor.location.file.name,
func_call.cursor.location.line,
func_call.calling_function
)
if len(callers) == 0:
print(formatted)
if len(curr_stack) > 0:
print("\n".join(curr_stack))
print("-------------------")
else:
print_callstacks(callers, formatted, *curr_stack)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("USAGE: {} <filename> <functionname>".format(sys.argv[0]))
exit(1)
filename = sys.argv[1]
function_name = sys.argv[2]
clang.cindex.Config.set_library_file("/Library/Developer/CommandLineTools/usr/lib/libclang.dylib")
index = clang.cindex.Index.create()
tu = index.parse(filename, args=["-x", "c++"])
diagnostics = list(tu.diagnostics)
if len(diagnostics) > 0:
print("There were parse errors")
pprint.pprint(diagnostics)
exit(1)
tracer = FunctionTracer(tu)
call_stacks = tracer.trace_func(function_name)
print_callstacks(call_stacks)
@d0c-s4vage
Copy link
Author

This is a test script of using python clang bindings to find call stacks to a specific function. Running:

python clang_function_tracer.py test.cpp target

Results in the output:

hostname [ pyclang ]: python clang_function_tracer.py test.cpp target
test.cpp:20 in main
./test.h:7 in test
-------------------
test.cpp:19 in main
test.cpp:9 in blah
test.cpp:4 in target2
-------------------
test.cpp:18 in main
test.cpp:13 in blah2
test.cpp:4 in target2
-------------------
test.cpp:19 in main
test.cpp:8 in blah
-------------------
test.cpp:17 in main
-------------------
test.cpp:19 in main
test.cpp:9 in blah
test.cpp:4 in target2
-------------------
test.cpp:18 in main
test.cpp:13 in blah2
test.cpp:4 in target2
-------------------
test.cpp:19 in main
test.cpp:8 in blah
-------------------
test.cpp:17 in main
-------------------
#include "test.h"

int target2(int arg1, int arg2, int arg3) {
    return target(arg1, arg2, arg3);
}

void blah() {
    target(3, 4, 5);
    target2(1,2,34);
}

void blah2() {
    target2(3, 4, 5);
}

int main(int argc, char **argv) {
    target(1, 2, 3);
    blah2();
    blah();
    test(1, 3);
}

and test.h as

#ifndef _TEST_H_
#define _TEST_H_

int target(int arg1, int arg2, int arg3);

int test(int arg1, int arg2) {
    return target(arg1, arg2, arg1+arg2);
}

int target(int arg1, int arg2, int arg3) {
    return arg1 + arg2 + arg3;
}


#endif

@d0c-s4vage
Copy link
Author

eh, looks like I have an error where it shows the same path multiple times. Oh well, it's just a simple test script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment