Skip to content

Commit

Permalink
First version.
Browse files Browse the repository at this point in the history
  • Loading branch information
stolk committed Apr 4, 2024
1 parent 3f6e1e2 commit 7ff9950
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

out.dot
out.svg
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TGT=`which glxgears`

all:
./sotrace.py $(TGT) out.dot
dot -Tsvg -o out.svg out.dot
-display out.svg

32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# sotrace
Traces the shared-object dependencies of a binary, and graphs them.

## Usage

```
./sotrace.py /path/to/foo out.dot
dot -Tout.svg out.dot
eog out.svg
```

![example output](images/out-glxgears.svg "glxgears dependencies")

## Rationale

Dynamically linked binaries pull in a large amount of dependencies.
It is often hard to get a good idea of what gets pulled in, recursively.

This tool will create a graphic that clearly shows the dependencies.

It is also a useful tool to identify software bloat.

## Limitations

Currently, it will only find dependencies that are dynamically linked.

This means it will miss:
* statically linked dependencies.
* plugins (.so files that are loaded at run-time, not load-time.)

## Author

Bram Stolk [email protected]

223 changes: 223 additions & 0 deletions images/out-glxgears.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions sotrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/env python3
#
# sotrace.py
#
# shared object trace
#
# Usage:
# sotrace.py /path/to/binary out.dot
# dot -Tout.svg out.dot
#
# (c)2024 Bram Stolk


import os # For popen
import sys # For argv


# Given a target library name, see what this library directly depends on.
def dep_list(tgt) :
cmd = "readelf -d " + tgt + " | grep NEEDED"
f = os.popen(cmd, 'r')
lines = f.readlines()
f.close()
vals = [ x.split(": ")[1].strip() for x in lines ]
deps = [ x[1:-1] for x in vals ]
return deps


# Given a set of dependency names, check to what path the are resolved using ldd
def dep_to_lib(tgt, deps) :
cmd = "ldd " + tgt
f = os.popen(cmd, 'r')
lines = f.readlines()
f.close()
mapping = {}
for line in lines:
if "=>" in line:
parts = line.strip().split(" => ")
nam = parts[0].strip()
if nam in deps :
mapping[nam] = parts[1].split(" (")[0]
return mapping


# Walk the dependencies of the target, and write the graph to file.
def traverse_so(tgt, nam, f, depth, visited, linked) :
deps = dep_list(tgt)
lib_map = dep_to_lib(tgt, deps)

for val in lib_map.keys() :
if nam != val and (nam,val) not in linked:
linked.append( (nam,val) )
f.write('"' + nam + '" -> "' + val + '"\n')

for dep in deps:
if dep in lib_map:
m = lib_map[dep]
if not m in visited:
visited.append(m)
dnam = os.path.basename(dep)
traverse_so(m, dnam, f, depth+1, visited, linked)

# Main entry point
if __name__ == '__main__' :

if len(sys.argv) != 3 :
print("Usage:", sys.argv[0], "libfoo.so graph.dot")
sys.exit(1)

tgt = sys.argv[1]
out = sys.argv[2]
nam = os.path.basename(tgt)

f = open(out, "w")
f.write("digraph G {\n")
f.write(" rankdir = LR;\n")
traverse_so(tgt, nam, f, 0, [], [])
f.write("}\n");
f.close()


0 comments on commit 7ff9950

Please sign in to comment.