This commit is contained in:
Costa Shulyupin 2020-07-20 22:31:40 +03:00
parent c0d03886cb
commit 331e1cd619
1 changed files with 49 additions and 37 deletions

View File

@ -67,7 +67,7 @@ ignores = ('aligned unlikely typeof u32 '
'current_user_ns spin_lock_irq spin_unlock_irq prepare_creds ' 'current_user_ns spin_lock_irq spin_unlock_irq prepare_creds '
'tasklist_lock commit_creds read_lock read_unlock SIGKILL SIGSTOP abort_creds fd_install ' 'tasklist_lock commit_creds read_lock read_unlock SIGKILL SIGSTOP abort_creds fd_install '
'real_mount FMODE_WRITE tv_nsec putname ' 'real_mount FMODE_WRITE tv_nsec putname '
).split() ).split() # TODO: move to file
level_limit = 10 level_limit = 10
@ -81,6 +81,7 @@ files = collections.defaultdict(list)
def print_limited(a, out=None): def print_limited(a, out=None):
# exits when reaches limit of printed lines
out = out if out else sys.stdout out = out if out else sys.stdout
out.write(str(a) + '\n') out.write(str(a) + '\n')
global n global n
@ -92,6 +93,7 @@ def print_limited(a, out=None):
def log(*args, **kwargs): def log(*args, **kwargs):
# log with context function
if not verbose: if not verbose:
return return
s = str(*args).rstrip() s = str(*args).rstrip()
@ -102,11 +104,15 @@ def log(*args, **kwargs):
def popen(p): def popen(p):
# shortcut for reading output of subcommand
log(p) log(p)
return check_output(p, shell=True).decode('utf-8').splitlines() return check_output(p, shell=True).decode('utf-8').splitlines()
def extract_referrer(line): def extract_referrer(line):
# Extract referrer function from oupput of
# git grep --show-function.
# With quirks for linux kernel
line = re.sub(r'__ro_after_init', '', line) line = re.sub(r'__ro_after_init', '', line)
line = re.sub(r'FNAME\((\w+)\)', r'\1', line) line = re.sub(r'FNAME\((\w+)\)', r'\1', line)
line = re.sub(r'.*TRACE_EVENT.*', '', line) line = re.sub(r'.*TRACE_EVENT.*', '', line)
@ -122,6 +128,7 @@ def extract_referrer(line):
def extract_referrer_test(): def extract_referrer_test():
# unittest of extract_referrer
passed = 0 passed = 0
for a in { for a in {
"f=1=good2()", "f=1=good2()",
@ -147,6 +154,10 @@ def extract_referrer_test():
def func_referrers_git_grep(name): def func_referrers_git_grep(name):
# Subfunction for searching referrers with
# git grep --show-function.
# Works slowly.
# Obsoleted by doxygen_xml.
res = list() res = list()
r = None r = None
for line in popen(r'git grep --threads 1 --no-index --word-regexp ' for line in popen(r'git grep --threads 1 --no-index --word-regexp '
@ -179,6 +190,9 @@ cscope_warned = False
def func_referrers_cscope(name): def func_referrers_cscope(name):
# Subfunction for searching referrers with cscope.
# Works fast.
# Obsoleted by doxygen_xml.
global cscope_warned global cscope_warned
if not os.path.isfile('cscope.out'): if not os.path.isfile('cscope.out'):
if not cscope_warned: if not cscope_warned:
@ -201,14 +215,11 @@ def func_referrers_cscope(name):
return res return res
def func_referrers_all(name):
return list(dict.fromkeys(func_referrers_git_grep(name) + func_referrers_cscope(name)))
def referrers_tree(name, referrer=None, printed=None, level=0): def referrers_tree(name, referrer=None, printed=None, level=0):
''' '''
Arg: <identifier> Arg: <identifier> - prints text referrers tree.
Ex: nfs_root_data Ex: nfs_root_data
Obsoleted by doxygen_xml.
''' '''
if not referrer: if not referrer:
if os.path.isfile('cscope.out'): if os.path.isfile('cscope.out'):
@ -239,16 +250,17 @@ def referrers_tree(name, referrer=None, printed=None, level=0):
def referrers(name): def referrers(name):
''' '''
Arg: <identifier> Arg: <identifier> - simply greps referrers of a symbol
Ex: nfs_root_data Ex: nfs_root_data
Prefer to use doxygen_xml.
''' '''
# for a in func_referrers_git_grep(name):
# print("%s:%s: %s"%(a[0],a[1],a[2]))
print(' '.join([a[2] for a in func_referrers_git_grep(name)])) print(' '.join([a[2] for a in func_referrers_git_grep(name)]))
def referrers_dep(name, referrer=None, printed=None, level=0): def referrers_dep(name, referrer=None, printed=None, level=0):
# Arg: <identifier> # Arg: <identifier> - prints referrers tree in compact format of
# dependency of make
# Obsoleted by doxygen_xml.
if not referrer: if not referrer:
if os.path.isfile('cscope.out'): if os.path.isfile('cscope.out'):
referrer = func_referrers_cscope referrer = func_referrers_cscope
@ -278,8 +290,9 @@ def referrers_dep(name, referrer=None, printed=None, level=0):
def call_tree(node, printed=None, level=0): def call_tree(node, printed=None, level=0):
''' '''
Arg: <identifier> Arg: <identifier> - prints call tree of a function
Ex: start_kernel Ex: start_kernel
Obsoleted by doxygen_xml.
''' '''
if not os.path.isfile('cscope.out'): if not os.path.isfile('cscope.out'):
print("Please run: cscope -Rcbk", file=sys.stderr) print("Please run: cscope -Rcbk", file=sys.stderr)
@ -308,6 +321,8 @@ def call_tree(node, printed=None, level=0):
def call_dep(node, printed=None, level=0): def call_dep(node, printed=None, level=0):
# prints call tree in compact format of dependency of make
# Obsoleted by doxygen_xml.
if not os.path.isfile('cscope.out'): if not os.path.isfile('cscope.out'):
print("Please run: cscope -Rcbk", file=sys.stderr) print("Please run: cscope -Rcbk", file=sys.stderr)
return False return False
@ -334,6 +349,7 @@ def call_dep(node, printed=None, level=0):
def my_graph(name=None): def my_graph(name=None):
# common subfunction
g = nx.DiGraph(name=name) g = nx.DiGraph(name=name)
# g.graph.update({'node': {'shape': 'none', 'fontsize': 50}}) # g.graph.update({'node': {'shape': 'none', 'fontsize': 50}})
# g.graph.update({'rankdir': 'LR', 'nodesep': 0, }) # g.graph.update({'rankdir': 'LR', 'nodesep': 0, })
@ -354,7 +370,9 @@ def reduce_graph(g, m=None):
return g return g
def includes(a): def includes(sym):
# subfunction, used in syscalls
# extracts include files of a symbol
res = [] res = []
# log(a) # log(a)
for a in popen('man -s 2 %s 2> /dev/null |' for a in popen('man -s 2 %s 2> /dev/null |'
@ -376,12 +394,12 @@ def includes(a):
if res and len(res) > 1: if res and len(res) > 1:
r = set() r = set()
for f in res: for f in res:
# log('grep " %s \+\(" --include "%s" -r /usr/include/'%(a, f)) # log('grep " %s \+\(" --include "%s" -r /usr/include/'%(sym, f))
# log(os.system( # log(os.system(
# 'grep -w "%s" --include "%s" -r /usr/include/'%(a, f))) # 'grep -w "%s" --include "%s" -r /usr/include/'%(sym, f)))
if 0 != os.system( if 0 != os.system(
'grep " %s *(" --include "%s" -r /usr/include/ -q' 'grep " %s *(" --include "%s" -r /usr/include/ -q'
% (a, os.path.basename(f))): % (sym, os.path.basename(f))):
r.add(f) r.add(f)
res = res.difference(r) res = res.difference(r)
log(res) log(res)
@ -389,6 +407,11 @@ def includes(a):
def syscalls(): def syscalls():
# Experimental function for exporting syscalls info
# from various sources.
# Used in creation of
# https://en.wikibooks.org/wiki/The_Linux_Kernel/Syscalls
# Ex: srcxray.py "write_dot(syscalls(), 'syscalls.dot')"
sc = my_graph('syscalls') sc = my_graph('syscalls')
inc = 'includes.list' inc = 'includes.list'
if not os.path.isfile(inc): if not os.path.isfile(inc):
@ -454,20 +477,9 @@ def syscalls():
return sc return sc
# DiGraph
# write_dot to_agraph AGraph
# agwrite
# srcxray.py 'write_dot(syscalls(), "syscalls.dot")'
# write_graphml
# srcxray.py "most_used(read_dot('a.dot'))"
# srcxray.py "digraph_print(read_dot('a.dot'))"
# srcxray.py "write_dot(reduce_graph(read_dot('no-loops.dot')),'reduced.dot')"
# a=sys_clone;srcxray.py "write_dot(rank_couples(reduce_graph(remove_loops(read_dot('$a.dot')))),'$a.dot')"
# srcxray.py "pprint(most_used(read_dot('a.dot')))"
# srcxray.py "write_dot(add_rank('reduced.dot'), 'ranked.dot')"
# srcxray.py "write_dot(remove_loops(read_dot('reduced.dot')), 'no-loops.dot')"
def cleanup(a): def cleanup(a):
# cleanups graph file
# wrapper for remove_nodes_from
log('') log('')
g = to_dg(a) g = to_dg(a)
print(dg.number_of_edges()) print(dg.number_of_edges())
@ -480,15 +492,7 @@ def sort_dict(d):
return [a for a, b in sorted(d.items(), key=lambda k: k[1], reverse=True)] return [a for a, b in sorted(d.items(), key=lambda k: k[1], reverse=True)]
def most_used(dg, ins=10, outs=10): def starts(dg): # roots of trees in a graph
# return {a: b for a, b in sorted(dg.in_degree, key=lambda k: k[1]) if b > 1 and}
# return [(x, dg.in_degree(x), dg.out_degree(x))
return [x
for x in dg.nodes()
if dg.in_degree(x) >= ins and dg.out_degree(x) >= outs]
def starts(dg): # roots
return {n: dg.out_degree(n) for (n, d) in dg.in_degree if not d} return {n: dg.out_degree(n) for (n, d) in dg.in_degree if not d}
@ -559,6 +563,7 @@ def digraph_tree(dg, starts=None, ignores=ignores):
def digraph_print(dg, starts=None, dst_fn=None, sort=False): def digraph_print(dg, starts=None, dst_fn=None, sort=False):
''' '''
Arg: <graph> - print graphs as text tree Arg: <graph> - print graphs as text tree
Ex2: \"digraph_print(read_dot('a.dot'))\"
''' '''
dst = open(dst_fn, 'w') if dst_fn else None dst = open(dst_fn, 'w') if dst_fn else None
printed = set() printed = set()
@ -777,6 +782,7 @@ def write_dot(g, dot):
''' '''
Arg: <graph> <file> - writes a graph into a file with custom attributes Arg: <graph> <file> - writes a graph into a file with custom attributes
''' '''
# Other similar external functions to_agraph agwrite
dot = str(dot) dot = str(dot)
dot = open(dot, 'w') dot = open(dot, 'w')
dot.write('strict digraph "None" {\n') dot.write('strict digraph "None" {\n')
@ -867,6 +873,7 @@ def to_dg(a):
def remove_loops(dg): def remove_loops(dg):
# srcxray.py "write_dot(remove_loops(read_dot('reduced.dot')), 'no-loops.dot')"
rm = [] rm = []
visited = set() visited = set()
path = [object()] path = [object()]
@ -903,6 +910,7 @@ def remove_couples(dg):
def rank_couples(dg): def rank_couples(dg):
# a=sys_clone;srcxray.py "write_dot(rank_couples(reduce_graph(remove_loops(read_dot('$a.dot')))),'$a.dot')"
couples = [] couples = []
ranked = set() ranked = set()
for n in dg: for n in dg:
@ -1048,6 +1056,7 @@ def dot_expand(a, b):
def add_rank(g): def add_rank(g):
# srcxray.py "write_dot(add_rank('reduced.dot'), 'ranked.dot')"
g = to_dg(g) g = to_dg(g)
passed1 = set() passed1 = set()
passed2 = set() passed2 = set()
@ -1079,6 +1088,7 @@ def add_rank(g):
def import_symbols(): def import_symbols():
# extracts and import symbols from shared libraries
sym = my_graph('symbols') sym = my_graph('symbols')
for l in popen('(shopt -s globstar; nm -D -C -A **/*.so.*)'): for l in popen('(shopt -s globstar; nm -D -C -A **/*.so.*)'):
q = l.split(maxsplit=2) q = l.split(maxsplit=2)
@ -1165,6 +1175,7 @@ def doxygen(*input):
def doxygen_xml(a): def doxygen_xml(a):
''' '''
Arg: <xml directory generated by doxygen> - extracts call graph Arg: <xml directory generated by doxygen> - extracts call graph
Ex2: \"write_dot(doxygen_xml('xml'), 'doxygen.dot')\"
''' '''
g = my_graph() g = my_graph()
for x in list(glob.glob(os.path.join(a, "*.xml")) + [a]): for x in list(glob.glob(os.path.join(a, "*.xml")) + [a]):
@ -1234,6 +1245,7 @@ def usage():
class _unittest_autotest(unittest.TestCase): class _unittest_autotest(unittest.TestCase):
def test_1(self): def test_1(self):
extract_referrer_test()
write_dot(nx.DiGraph([(1, 2), (2, 3), (2, 4)]), 'test.dot') write_dot(nx.DiGraph([(1, 2), (2, 3), (2, 4)]), 'test.dot')
g = read_dot("test.dot") g = read_dot("test.dot")
self.assertEqual(list(g.successors("2")), ["3", "4"]) self.assertEqual(list(g.successors("2")), ["3", "4"])