xaizek / objdeps (License: Apache-2.0) (since 2020-09-11)
This is a python script that analyzes output of `objdump` to understand connections between object files.
Commit 8d8e32234dfdb1aab912ec79a12aa6fe3bb2960f

Some version
Author: xaizek
Author date (UTC): 2016-01-13 18:49
Committer name: xaizek
Committer date (UTC): 2016-01-13 18:49
Parent(s):
Signing key:
Tree: 85befcffd7e54b6f260df6d90c0abfba41f0f65c
File Lines added Lines deleted
deps.py 329 0
ideas 14 0
run.sh 3 0
File deps.py added (mode: 100755) (index 0000000..b5db766)
1 #!/usr/bin/env python
2 #-*- coding: utf-8 -*-
3
4 import os
5 import sys
6
7 EXPORT_EDGE = "edge [style=filled,weight=1];"
8 IMPORT_EDGE = "edge [style=dashed];"
9
10 INVALID = -1
11 EXPORT = 1
12 IMPORT = 2
13 OBJECT = 3
14
15 symbols = {}
16 objects = []
17
18 class objModule:
19 def __init__(self, fileName):
20 self._fileName = fileName
21 self._name = os.path.basename(fileName)
22 self._importTable = []
23 self._exportTable = []
24 def addImport(self, entry):
25 self._importTable += [entry]
26 entry.addImport(self)
27 def remImport(self, entry):
28 self._importTable -= [entry]
29 def addExport(self, entry):
30 self._exportTable += [entry]
31 entry.addExport(self)
32 def remExport(self, entry):
33 self._exportTable -= [entry]
34 def interactsWith(self, mod):
35 for entry in self._importTable:
36 if entry.belongsTo(mod):
37 return True
38 return False
39 def bindsCount(self, mod):
40 count = 0
41 for entry in self._importTable:
42 if entry.belongsTo(mod):
43 count += 1
44 return count
45 def getImports(self):
46 return [x for x in self._importTable if x.isUsers()]
47 def getExports(self):
48 return [x for x in self._exportTable if x.isUsers()]
49 def getAllExports(self):
50 return self._exportTable
51 def printMe(self):
52 if len(self.getImports()) == 0:
53 color = 'green'
54 elif len(self.getExports()) == 0:
55 color = 'burlywood'
56 else:
57 color = 'yellow'
58 print '"%s" [shape=rectangle,style=filled,fillcolor=%s];' \
59 % (self._name, color)
60 def getName(self):
61 return self._name
62 def __str__(self):
63 return self._name
64 def getFileName(self):
65 return self._fileName
66
67 class function:
68 def __init__(self, name):
69 self.name = name
70 self.importedBy = []
71 self._exportCounter = 0
72 self._importCounter = 0
73 self._mod = None
74 def __del__(self, name):
75 for mod in self._importCounter:
76 mod.remImport(self)
77 self._mod.remExport(self)
78 def addImport(self, mod):
79 self._importCounter += 1
80 self.importedBy += [mod]
81 def addExport(self, mod):
82 self._exportCounter += 1
83 self._mod = mod;
84 def isUsers(self):
85 return self._exportCounter > 0 and self._importCounter > 0
86 def isSystem(self):
87 return self._exportCounter == 0 and self._importCounter > 0
88 def isUnused(self):
89 return self._exportCounter > 0 and self._importCounter == 0
90 def belongsTo(self, mod):
91 return self._mod == mod
92 def getMod(self):
93 return self._mod
94 def __str__(self):
95 return self.name
96
97 def getFuncNm(line):
98 parts = line.split()
99 if len(parts) == 3 and parts[1] == 'T':
100 return (IMPORT, parts[2])
101 elif len(parts) == 3 and parts[1] == 't':
102 return (EXPORT, parts[2])
103 elif len(parts) == 2 and parts[0] == 'U':
104 return (IMPORT, parts[1])
105 else:
106 return (INVALID, None)
107
108 def getFuncObjdump(line):
109 parts = line.split()
110 if len(parts) == 6 and parts[1] == 'g' and parts[2] == 'F':
111 return (EXPORT, parts[5])
112 elif len(parts) == 6 and parts[1] == 'g' and parts[2] == 'O':
113 return (OBJECT, parts[5])
114 elif len(parts) == 5 and parts[1] == 'O' and parts[2] == '*COM*':
115 return (OBJECT, parts[4])
116 elif len(parts) == 4 and parts[1] == '*UND*':
117 return (IMPORT, parts[3])
118 else:
119 return (INVALID, None)
120
121 def getTables(objFile):
122 global objects
123 with os.popen('objdump -t ' + objFile.getFileName()) as f:
124 lines = f.readlines()
125 for line in lines:
126 (lineType, name) = getFuncObjdump(line)
127 if lineType == INVALID:
128 continue
129 if lineType == OBJECT:
130 objects += [name]
131 continue
132
133 if symbols.has_key(name):
134 func = symbols[name]
135 else:
136 func = function(name)
137 symbols[name] = func
138
139 if lineType == IMPORT:
140 objFile.addImport(func)
141 else:
142 objFile.addExport(func)
143
144 def printFunctions(mod, funcs):
145 for entry in funcs:
146 print '"%s"->"%s";' % (mod, entry)
147
148 def printFuncGraph(objModules):
149 for mod in objModules:
150 mod.printMe()
151
152 # exports
153 print EXPORT_EDGE
154 printFunctions(mod, mod.getExports())
155
156 # imports
157 print IMPORT_EDGE
158 printFunctions(mod, mod.getImports())
159
160 def printOneModFuncGraph(mod):
161 mod.printMe()
162
163 modImports = mod.getImports()
164
165 # imports
166 print IMPORT_EDGE
167 for entry in modImports:
168 print '"%s"->"%s";' % (mod, entry)
169
170 # exports
171 print EXPORT_EDGE
172 for entry in modImports:
173 entry.getMod().printMe()
174 print '"%s"->"%s";' % (entry.getMod(), entry)
175
176 def printOneInvModFuncGraph(mod):
177 mod.printMe()
178
179 modExports = mod.getExports()
180
181 # exports
182 print EXPORT_EDGE
183 for entry in modExports:
184 print '"%s"->"%s";' % (mod, entry)
185
186 # imports
187 print IMPORT_EDGE
188 for entry in modExports:
189 for mod in entry.importedBy:
190 mod.printMe()
191 print '"%s"->"%s";' % (mod, entry)
192
193 def analyzeMods(x, y):
194 if not x.interactsWith(y):
195 return
196
197 x.printMe()
198 y.printMe()
199 print 'edge [label="%d"];' % x.bindsCount(y)
200 print '"%s"->"%s";' % (x, y)
201
202 def printModGraph(objModules):
203 [analyzeMods(x, y) for x in objModules for y in objModules]
204
205 def printOneModGraph(mod, modules):
206 for x in modules:
207 if x != mod:
208 analyzeMods(x, mod)
209
210 def printOneInvModGraph(mod, modules):
211 for x in modules:
212 if x != mod:
213 analyzeMods(mod, x)
214
215 def printOneFuncGraph(func):
216 func.getMod().printMe()
217
218 # export
219 print EXPORT_EDGE
220 print '"%s"->"%s";' % (func.getMod(), func)
221
222 # imports
223 print IMPORT_EDGE
224 for mod in func.importedBy:
225 mod.printMe()
226 print '"%s"->"%s";' % (mod, func)
227
228 def makeModule(objFileName):
229 objFile = objModule(objFileName)
230 getTables(objFile)
231 return objFile
232
233 def printLegend():
234 print '"export only" [shape=rectangle,style=filled,fillcolor=green];'
235 print '"import only" [shape=rectangle,style=filled,fillcolor=burlywood];'
236 print '"export and import" [shape=rectangle,style=filled,fillcolor=yellow];'
237
238 def listUnusedFuncs(mods):
239 for mod in mods:
240 funcs = [x for x in mod.getAllExports() if x.isUnused()]
241 if len(funcs) == 0:
242 continue
243 if len(funcs) == 1 and funcs[0] == 'main':
244 continue
245
246 print '--- unused from ' + str(mod)
247 for func in funcs:
248 print func
249
250 def listSystemFuncs():
251 funcNames = [x.name for x in symbols.values() if x.isSystem()]
252 funcNames.sort()
253 for funcName in funcNames:
254 print funcName
255
256 def listUserFuncs():
257 userFuncs = [x for x in symbols.values() if x.isUsers() or x.isUnused()]
258 userFuncs = sorted(userFuncs, None, lambda x: x.name)
259 print 'Name ExportedFrom ImportedBy'
260 for func in userFuncs:
261 lst = ''
262 for mod in func.importedBy:
263 if lst == '':
264 lst = str(mod)
265 else:
266 lst += ', ' + str(mod)
267 print '%s %s %s (%s)' % (func.name, func.getMod(), len(func.importedBy),
268 lst)
269
270 def main():
271 # parse object files
272 modulesList = [makeModule(x) for x in sys.argv[1:]]
273 objModules = {}
274 for mod in modulesList:
275 objModules[mod.getName()] = mod
276
277 # add special system module
278 sysMod = objModule('system')
279 objModules['system'] = sysMod
280 for func in symbols.values():
281 if func.isSystem():
282 func._mod = sysMod
283
284 # remove objects from list of symbols
285 for obj in objects:
286 if symbols.has_key(obj):
287 func = symbols[obj]
288 del symbols[obj]
289 del func
290
291 # print 'digraph "funcdeps" {'
292 # printFuncGraph(objModules.values())
293 # print '}'
294
295 # print 'digraph "moddeps" {'
296 # printModGraph(objModules.values())
297 # print '}'
298
299 # print 'digraph "onemoddeps" {'
300 # printOneModGraph(objModules['file_info.o'], objModules.values())
301 # print '}'
302
303 # print 'digraph "oneinvmoddeps" {'
304 # printOneInvModGraph(objModules['vifm.o'], objModules.values())
305 # print '}'
306
307 # print 'digraph "onemodfuncdeps" {'
308 # printOneModFuncGraph(objModules['utils.o'])
309 # print '}'
310
311 # print 'digraph "onemodinvfuncdeps" {'
312 # printOneInvModFuncGraph(objModules['utils.o'])
313 # print '}'
314
315 # print 'digraph "onefuncdeps" {'
316 # printOneFuncGraph(symbols['is_dir'])
317 # print '}'
318
319 listUnusedFuncs(objModules.values())
320
321 # listSystemFuncs()
322
323 # listUserFuncs()
324
325 if len(sys.argv) == 1:
326 print 'Usage: deps objfile...'
327 sys.exit(1)
328
329 main()
File ideas added (mode: 100644) (index 0000000..1b24830)
1 TODO:
2 - parse arguments;
3 - parse sources;
4 - find absent #include directives (all needed header files should be imported
5 explicitly);
6 - find unused #include directives;
7
8 Arguments:
9 - list of object files
10 - list of unused functions (e.g. main)
11 - operation (graph type and it's arguments or operation type)
12
13 Future arguments:
14 - list of source files
File run.sh added (mode: 100755) (index 0000000..3526541)
1 ./deps.py `find ~/repos/vifm/src/ -name '*.o'` > tst.gv && \
2 circo -Tpng tst.gv -o tst.png && \
3 sxiv tst.png
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/objdeps

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/objdeps

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master