diff --git a/srcxray.py b/srcxray.py new file mode 100755 index 0000000..203d7b7 --- /dev/null +++ b/srcxray.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 +# +# srcxray - source code X-ray +# +# Analyzes interconnections between functions and structures in source code. +# +# Uses cscope and git grep --show-function to +# reveal references between identifiers. +# +# 2018 Constantine Shulyupin, const@MakeLinux.com +# + +import inspect, random, os, sys, collections, subprocess, re + +black_list = ['aligned', '__attribute__', 'unlikely', 'typeof', + 'u32', + 'PVOP_CALLEE0', 'PVOP_VCALLEE0', 'PVOP_VCALLEE1', 'trace_hardirqs_off'] + +level_limit = 7 +limit = 10000 +n = 0 + +def print_limited(a): + print(a) + global n + n +=1 + if n > limit: + print('Reached limit') + sys.exit(1) + +def log(*args, **kwargs): + print(inspect.stack()[1][3], str(*args).rstrip(), file=sys.stderr, **kwargs) + pass + +def popen(p): + return subprocess.Popen(p, shell=True, stdout=subprocess.PIPE, encoding="utf-8").stdout + +def extract_referer(line): + line = re.sub(r'__ro_after_init', '', line) + line = re.sub(r'FNAME\((\w+)\)', r'\1', line) + line = re.sub(r'.*TRACE_EVENT.*', '', line) + m = re.match(r'^[^\s]+=[^,]*\(\*(\b\w+)\)\s*[\(\[=][^;]*$', line) + if not m: + m = re.match(r'^[^\s]+=[^,]*(\b\w+)\s*[\(\[=][^;]*$', line) + if m: + return m.group(1) + +def extract_referer_test(): + for a in { + "fs=good2()", + "f=static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *))", + "f=int good(a, bad (*func)(arg))", + "f=EXPORT_SYMBOL_GPL(bad);", + "f=bad (*good)()", + "f=int FNAME(good)(a)", + "f=TRACE_EVENT(a)", + "f: a=in bad()" }: + print(a, '->', extract_referer(a)) + +def func_referers_git_grep(name): + res = [] + r = None + for line in popen(r'git grep --no-index --word-regexp --show-function "^\s.*\b%s"'%(name)): + # Filter out names in comment afer function, when comment start from ' *' + # To see the problem try "git grep -p and" + if re.match(r'.*: \* ', line): + r = None + if r and r != name and not r in black_list: + res.append(r) + r = None + r = extract_referer(line) + return res + +cscope_warned = False + +def func_referers_cscope(name): + global cscope_warned + if not os.path.isfile('cscope.out'): + if not cscope_warned: + print("Recommended: cscope -bkR", file=sys.stderr) + cscope_warned = True + return [] + res = [ l.split()[1] for l in popen(r'cscope -d -L3 "%s"'%(name)) if not l in black_list] + if not res: + res = func_referers_git_grep(name) + return res + +def func_referers_all(name): + return set(func_referers_git_grep(name) + func_referers_cscope(name)) + +def referers_tree(name, referer = None, printed = None, level = 0): + if not referer: + if os.path.isfile('cscope.out'): + referer = func_referers_cscope + else: + print("Using git grep only, recommended to run: cscope -bkR", file=sys.stderr) + referer = func_referers_git_grep + if isinstance(referer, str): + referer = eval(referer) + if not printed: printed = set() + if name in printed: + print_limited(level*'\t' + name + ' ^') + return + else: + print_limited(level*'\t' + name) + printed.add(name) + if level > level_limit - 2: + print_limited((level + 1)*'\t' + '...') + return '' + listed = set() + for a in referer(name): + if a in listed: + continue + referers_tree(a, referer, printed, level + 1) + listed.add(a) + return '' + +def call_tree(node, printed = None, level = 0): + if not os.path.isfile('cscope.out'): + print("Please run: cscope -bkR", file=sys.stderr) + return False + if printed == None: printed = set() + if node in printed: + limit= - 1 + print_limited(level*'\t' + node + '^') + return + else: + print_limited(level*'\t' + node) + printed.add(node) + if level > level_limit - 2 : + print_limited((level + 1)*'\t' + '...') + return '' + local_printed = set() + for line in popen('cscope -d -L2 "%s"'%(node)): + I = line.split()[1]; + if I in local_printed or I in black_list: continue; + local_printed.add(I) + try: + call_tree(line.split()[1], printed, level + 1); + except: + pass + return '' + +me = os.path.basename(sys.argv[0]) + +def usage(): + print(me, "referers_tree", "") + print(me, "call_tree", "") + print("Try this:") + print("cd linux/init") + print(me, "referers_tree nfs_root_data") + print(me, "call_tree start_kernel") + print(me, "Emergency termination: ^Z, kill %1") + +def main(): + try: + ret = False + if len(sys.argv) == 1: + print('Run', me, 'usage') + else: + if '(' in sys.argv[1]: + ret = eval(sys.argv[1]) + else: + ret = eval(sys.argv[1] + '(' + ', '.join("'%s'"%(a) for a in sys.argv[2:]) + ')') + if type(ret) == type(False) and ret == False: + sys.exit(os.EX_CONFIG) + print(ret) + except KeyboardInterrupt: + warning("\nInterrupted") + +if __name__ == "__main__": + main()