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 07f6d8f9e7fc7209b3ee816cb30ae6c9a748eba8

Generally more usable version with args
Author: xaizek
Author date (UTC): 2016-01-14 19:00
Committer name: xaizek
Committer date (UTC): 2016-01-14 19:01
Parent(s): 00cdac438ef4dfeaf0f18b06cfbb81b2d21e661d
Signing key:
Tree: 18bbc308ebc53278b886685063de6e96b7955b20
File Lines added Lines deleted
deps.py 115 69
ideas 1 1
File deps.py changed (mode: 100755) (index 5b2ce5d..3bc84e3)
1 1 #!/usr/bin/env python #!/usr/bin/env python
2 2 #-*- coding: utf-8 -*- #-*- coding: utf-8 -*-
3 3
4 import argparse
4 5 import os import os
5 import sys
6 6
7 7 EXPORT_EDGE = "edge [style=filled,weight=1];" EXPORT_EDGE = "edge [style=filled,weight=1];"
8 8 IMPORT_EDGE = "edge [style=dashed];" IMPORT_EDGE = "edge [style=dashed];"
 
... ... IMPORT_EDGE = "edge [style=dashed];"
10 10 INVALID = -1 INVALID = -1
11 11 EXPORT = 1 EXPORT = 1
12 12 IMPORT = 2 IMPORT = 2
13 OBJECT = 3
13 OBJECT_EXPORT = 3
14 OBJECT_IMPORT = 4
14 15
15 16 symbols = {} symbols = {}
16 17 objects = [] objects = []
 
... ... class objModule:
25 26 self._importTable += [entry] self._importTable += [entry]
26 27 entry.addImport(self) entry.addImport(self)
27 28 def remImport(self, entry): def remImport(self, entry):
28 self._importTable -= [entry]
29 self._importTable.remove(entry)
29 30 def addExport(self, entry): def addExport(self, entry):
30 31 self._exportTable += [entry] self._exportTable += [entry]
31 32 entry.addExport(self) entry.addExport(self)
32 33 def remExport(self, entry): def remExport(self, entry):
33 self._exportTable -= [entry]
34 self._exportTable.remove(entry)
34 35 def interactsWith(self, mod): def interactsWith(self, mod):
35 36 for entry in self._importTable: for entry in self._importTable:
36 37 if entry.belongsTo(mod): if entry.belongsTo(mod):
 
... ... class objModule:
49 50 def getAllExports(self): def getAllExports(self):
50 51 return self._exportTable return self._exportTable
51 52 def printMe(self): def printMe(self):
53 if len(self.getImports()) == 0 and len(self.getExports()) == 0:
54 return
55
52 56 if len(self.getImports()) == 0: if len(self.getImports()) == 0:
53 57 color = 'green' color = 'green'
54 58 elif len(self.getExports()) == 0: elif len(self.getExports()) == 0:
 
... ... class function:
71 75 self._exportCounter = 0 self._exportCounter = 0
72 76 self._importCounter = 0 self._importCounter = 0
73 77 self._mod = None self._mod = None
74 def __del__(self, name):
75 for mod in self._importCounter:
78 def remove(self):
79 for mod in self.importedBy:
76 80 mod.remImport(self) mod.remImport(self)
77 81 self._mod.remExport(self) self._mod.remExport(self)
78 82 def addImport(self, mod): def addImport(self, mod):
79 83 self._importCounter += 1 self._importCounter += 1
80 84 self.importedBy += [mod] self.importedBy += [mod]
81 85 def addExport(self, mod): def addExport(self, mod):
82 self._exportCounter += 1
86 assert self._mod is None
87 self._exportCounter = 1
83 88 self._mod = mod; self._mod = mod;
84 89 def isUsers(self): def isUsers(self):
85 90 return self._exportCounter > 0 and self._importCounter > 0 return self._exportCounter > 0 and self._importCounter > 0
 
... ... def getFuncObjdump(line):
110 115 if len(parts) == 6 and parts[1] == 'g' and parts[2] == 'F': if len(parts) == 6 and parts[1] == 'g' and parts[2] == 'F':
111 116 return (EXPORT, parts[5]) return (EXPORT, parts[5])
112 117 elif len(parts) == 6 and parts[1] == 'g' and parts[2] == 'O': elif len(parts) == 6 and parts[1] == 'g' and parts[2] == 'O':
113 return (OBJECT, parts[5])
118 return (OBJECT_EXPORT, parts[5])
114 119 elif len(parts) == 5 and parts[1] == 'O' and parts[2] == '*COM*': elif len(parts) == 5 and parts[1] == 'O' and parts[2] == '*COM*':
115 return (OBJECT, parts[4])
120 return (OBJECT_IMPORT, parts[4])
116 121 elif len(parts) == 4 and parts[1] == '*UND*': elif len(parts) == 4 and parts[1] == '*UND*':
117 122 return (IMPORT, parts[3]) return (IMPORT, parts[3])
118 123 else: else:
 
... ... def getTables(objFile):
126 131 (lineType, name) = getFuncObjdump(line) (lineType, name) = getFuncObjdump(line)
127 132 if lineType == INVALID: if lineType == INVALID:
128 133 continue continue
129 if lineType == OBJECT:
134 if lineType == OBJECT_EXPORT or lineType == OBJECT_IMPORT:
130 135 objects += [name] objects += [name]
131 continue
132 136
133 137 if symbols.has_key(name): if symbols.has_key(name):
134 138 func = symbols[name] func = symbols[name]
 
... ... def getTables(objFile):
136 140 func = function(name) func = function(name)
137 141 symbols[name] = func symbols[name] = func
138 142
139 if lineType == IMPORT:
143 if lineType == IMPORT or lineType == OBJECT_IMPORT:
140 144 objFile.addImport(func) objFile.addImport(func)
141 else:
145 elif lineType == EXPORT or lineType == OBJECT_EXPORT:
142 146 objFile.addExport(func) objFile.addExport(func)
143 147
144 148 def printFunctions(mod, funcs): def printFunctions(mod, funcs):
145 149 for entry in funcs: for entry in funcs:
146 150 print '"%s"->"%s";' % (mod, entry) print '"%s"->"%s";' % (mod, entry)
147 151
148 def printFuncGraph(objModules):
152 def printSymGraph(objModules):
149 153 for mod in objModules: for mod in objModules:
150 154 mod.printMe() mod.printMe()
151 155
 
... ... def analyzeMods(x, y):
202 206 def printModGraph(objModules): def printModGraph(objModules):
203 207 [analyzeMods(x, y) for x in objModules for y in objModules] [analyzeMods(x, y) for x in objModules for y in objModules]
204 208
205 def printOneModGraph(mod, modules):
209 def printSingleModGraph(mod, modules):
206 210 for x in modules: for x in modules:
207 211 if x != mod: if x != mod:
208 212 analyzeMods(x, mod) analyzeMods(x, mod)
 
... ... def printOneInvModGraph(mod, modules):
212 216 if x != mod: if x != mod:
213 217 analyzeMods(mod, x) analyzeMods(mod, x)
214 218
215 def printOneFuncGraph(func):
219 def printSymbolGraph(func):
216 220 func.getMod().printMe() func.getMod().printMe()
217 221
218 222 # export # export
 
... ... def printLegend():
237 241
238 242 def listUnusedFuncs(mods): def listUnusedFuncs(mods):
239 243 for mod in mods: for mod in mods:
240 funcs = [x for x in mod.getAllExports()
241 if x.isUnused() and str(x) != 'main']
244 funcs = [x for x in mod.getAllExports() if x.isUnused()]
242 245 if len(funcs) == 0: if len(funcs) == 0:
243 246 continue continue
244 247
 
... ... def listUserFuncs():
257 260 userFuncs = sorted(userFuncs, None, lambda x: x.name) userFuncs = sorted(userFuncs, None, lambda x: x.name)
258 261 print 'Name ExportedFrom ImportedBy' print 'Name ExportedFrom ImportedBy'
259 262 for func in userFuncs: for func in userFuncs:
260 lst = ''
261 for mod in func.importedBy:
262 if lst == '':
263 lst = str(mod)
264 else:
265 lst += ', ' + str(mod)
263 lst = ', '.join(sorted(map(lambda x: str(x), func.importedBy)))
266 264 print '%s %s %s (%s)' % (func.name, func.getMod(), len(func.importedBy), print '%s %s %s (%s)' % (func.name, func.getMod(), len(func.importedBy),
267 265 lst) lst)
268 266
269 267 def main(): def main():
268 parser = argparse.ArgumentParser(description='Object file analyzer')
269 parser.add_argument('--moddeps', dest='moddeps', action='store_true',
270 help='Generate module dependency graph')
271 parser.add_argument('--symdeps', dest='symdeps', action='store_true',
272 help='Generate symbol dependency graph')
273 parser.add_argument('--module', dest='module',
274 help='Generate graph for this module (its users)')
275 parser.add_argument('--invert', dest='invert', action='store_true',
276 help='Invert dependencies for --module')
277 parser.add_argument('--symbols', dest='symbols', action='store_true',
278 help='Detail symbols used by --module')
279 parser.add_argument('--objects', dest='objects', action='store_true',
280 help='Display information on objects')
281 parser.add_argument('--symbol', dest='symbol',
282 help='Generate graph for this symbol')
283 parser.add_argument('--system', dest='system', action='store_true',
284 help='List references to system symbols')
285 parser.add_argument('--user', dest='user', action='store_true',
286 help='List table of exported user symbols')
287 parser.add_argument('--unused', dest='unused', action='store_true',
288 help='List exported, but unused symbols')
289 parser.add_argument('obj', nargs='+',
290 help='Object file to get information from')
291 args = parser.parse_args()
292
293 if not args.unused and not args.system and not args.user and \
294 not args.symbol and not args.moddeps and not args.symdeps and \
295 not args.module:
296 print 'No action selected'
297 exit(1)
298
270 299 # parse object files # parse object files
271 modulesList = [makeModule(x) for x in sys.argv[1:]]
300 modulesList = [makeModule(x) for x in args.obj]
272 301 objModules = {} objModules = {}
273 302 for mod in modulesList: for mod in modulesList:
274 303 objModules[mod.getName()] = mod objModules[mod.getName()] = mod
 
... ... def main():
276 305 # add special system module # add special system module
277 306 sysMod = objModule('system') sysMod = objModule('system')
278 307 objModules['system'] = sysMod objModules['system'] = sysMod
279 for func in symbols.values():
280 if func.isSystem():
281 func._mod = sysMod
282
283 # remove objects from list of symbols
284 for obj in objects:
285 if symbols.has_key(obj):
286 func = symbols[obj]
287 del symbols[obj]
288 del func
289
290 # print 'digraph "funcdeps" {'
291 # printFuncGraph(objModules.values())
292 # print '}'
293
294 # print 'digraph "moddeps" {'
295 # printModGraph(objModules.values())
296 # print '}'
297
298 # print 'digraph "onemoddeps" {'
299 # printOneModGraph(objModules['file_info.o'], objModules.values())
300 # print '}'
301
302 # print 'digraph "oneinvmoddeps" {'
303 # printOneInvModGraph(objModules['vifm.o'], objModules.values())
304 # print '}'
305
306 # print 'digraph "onemodfuncdeps" {'
307 # printOneModFuncGraph(objModules['utils.o'])
308 # print '}'
309
310 # print 'digraph "onemodinvfuncdeps" {'
311 # printOneInvModFuncGraph(objModules['utils.o'])
312 # print '}'
313
314 # print 'digraph "onefuncdeps" {'
315 # printOneFuncGraph(symbols['is_dir'])
316 # print '}'
308 for sym in symbols.values():
309 if sym.isSystem():
310 sysMod.addExport(sym)
311
312 if 'main' in symbols:
313 sysMod.addImport(symbols['main'])
314
315 if args.objects:
316 # remove non-objects from list of symbols
317 for symname in symbols.keys():
318 if symname not in objects:
319 symbols[symname].remove()
320 del symbols[symname]
321 else:
322 # remove objects from list of symbols
323 for obj in objects:
324 if obj not in symbols:
325 symbols[obj].remove()
326 del symbols[obj]
327
328 if args.symdeps:
329 print 'digraph "symdeps" {'
330 printSymGraph(objModules.values())
331 print '}'
332
333 if args.moddeps:
334 print 'digraph "moddeps" {'
335 printModGraph(objModules.values())
336 print '}'
337
338 if args.module:
339 if args.symbols:
340 if args.invert:
341 print 'digraph "singlemodfuncdeps" {'
342 printOneInvModFuncGraph(objModules[args.module])
343 print '}'
344 else:
345 print 'digraph "singlemodfuncusers" {'
346 printOneModFuncGraph(objModules[args.module])
347 print '}'
348 else:
349 if args.invert:
350 print 'digraph "singlemoddeps" {'
351 printOneInvModGraph(objModules[args.module],
352 objModules.values())
353 print '}'
354 else:
355 print 'digraph "singlemodusers" {'
356 printSingleModGraph(objModules[args.module],
357 objModules.values())
358 print '}'
317 359
318 listUnusedFuncs(objModules.values())
360 if args.symbol:
361 print 'digraph "onesymdeps" {'
362 printSymbolGraph(symbols[args.symbol])
363 print '}'
319 364
320 # listSystemFuncs()
365 if args.unused:
366 listUnusedFuncs(objModules.values())
321 367
322 # listUserFuncs()
368 if args.system:
369 listSystemFuncs()
323 370
324 if len(sys.argv) == 1:
325 print 'Usage: deps objfile...'
326 sys.exit(1)
371 if args.user:
372 listUserFuncs()
327 373
328 374 main() main()
File ideas changed (mode: 100644) (index 1b24830..59536c8)
1 1 TODO: TODO:
2 - parse arguments;
3 2 - parse sources; - parse sources;
4 3 - find absent #include directives (all needed header files should be imported - find absent #include directives (all needed header files should be imported
5 4 explicitly); explicitly);
6 5 - find unused #include directives; - find unused #include directives;
6 - ability to group modules by their location (e.g. meta-module "utils" for all modules under utils/).
7 7
8 8 Arguments: Arguments:
9 9 - list of object files - list of object files
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