winIDEA SDK
analyze_call_tree.py
1# This script is licensed under BSD License, see file LICENSE.txt.
2#
3# (c) TASKING Germany GmbH, 2023
4
5"""
6This script generates a call tree for a function from its disassembly
7information. This script is ony a proof of concept. Use testIDEA to
8generate scripts, which produce more readable call graphs.
9"""
10
11import sys
12import isystem.connect as ic
13import subprocess as sp
14
15winidea_id = ''
16
17
18graph = ""
19vertex = set()
20
21
22def createGraphImage(graphFileName):
23 print('Creating call graph image ...')
24 cmd = 'dot -Tpng -O ' + graphFileName
25 try:
26 sp.check_call(cmd, shell=True)
27 except Exception as ex:
28 print("\nERROR: Can not run the graphviz 'dot' utility. Command: ", cmd,
29 '\n\n' + str(ex), file=sys.stderr)
30 raise
31 return graphFileName + ".png"
32
33
34def writeGraphFile(outFileName, graphStr):
35 print('Writing graph calls to file: ', outFileName)
36 with open(outFileName, 'w') as outf:
37 outf.write(graphStr)
38 outf.write('\n')
39 outf.close()
40 return outFileName
41
42
43def printFunction(cmgr, functionName):
44 global graph
45
46 print("Printing: " + functionName)
47
48 graph += "\"" + functionName + "\" " + \
49 "[shape=circle, style=bold, label=\"" + \
50 str(functionName) + "\"];\n"
51
52 debugCtrl = ic.CDebugFacade(cmgr)
53 try:
54 functionInfo = debugCtrl.getSymbolInfo(ic.IConnectDebug.fCacheCode | ic.IConnectDebug.fRealTime | ic.IConnectDebug.gafFunctions, functionName)
55 except Exception:
56 print("Not a known function")
57 return
58
59 dataCtrl2 = ic.CDataController2(cmgr)
60 disassembly = dataCtrl2.getDisassembly(0,
61 functionInfo.getMemArea(),
62 functionInfo.getAddress(),
63 functionInfo.getSizeMAUs())
64
65 daLines = disassembly.Lines()
66
67 numLines = daLines.size()
68
69 for idx in range(numLines):
70
71 disassemblyLine = daLines.at(idx)
72
73 if disassemblyLine.IsCall():
74 symbolName: str = debugCtrl.getSymbolAtAddress(ic.IConnectDebug.sFunctions,
75 functionInfo.getMemArea(),
76 disassemblyLine.BranchTarget())
77
78 if not symbolName:
79 symbolName = hex(disassemblyLine.BranchTarget())
80
81 # some qualified symbols have names like "debug.c#"static_func
82 # and quotes interfere with dot syntax, so replace them
83 symbolName = symbolName.replace('"', "'")
84
85 graph += "\"" + str(functionName) + "\" -> \"" + str(symbolName) + "\"\n"
86
87 if symbolName not in vertex:
88 vertex.add(symbolName)
89 printFunction(cmgr, symbolName)
90
91 # It is important that we release the allocated resource
92 dataCtrl2.release(disassembly)
93
94
95def analyzeFunction(cmgr, functionName):
96 global graph
97
98 graph += "digraph {\n"
99 graph += "graph [rank=max]\n"
100
101 printFunction(cmgr, functionName)
102
103 graph += "}\n\n"
104
105 return graph
106
107
108def main():
109 global graph
110
111 if len(sys.argv) != 2:
112 print("Usage: ", sys.argv[0], " <function>")
113 print('defaulting to "main"')
114 function = 'main'
115 else:
116 function = sys.argv[1]
117
118 try:
119 cmgr = ic.ConnectionMgr()
120 cmgr.connect(ic.CConnectionConfig().instanceId(winidea_id))
121
122 graph = analyzeFunction(cmgr, function)
123 graphFileName = writeGraphFile(str(function) + ".tree.dot", graph)
124 imageFileName = createGraphImage(graphFileName)
125 try:
126 # this opens default png viewer on Windows
127 sp.check_call('start ' + imageFileName, shell=True)
128 except Exception:
129 # try with default viewer on KUbuntu
130 sp.check_call('gwenview ' + imageFileName, shell=True)
131
132 except Exception as ex:
133 print(str(ex), file=sys.stderr)
134
135
136if __name__ == "__main__":
137 main()