-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsotrace.py
executable file
·131 lines (105 loc) · 3.02 KB
/
sotrace.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/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, keep_suffix) :
visited.add(tgt)
deps = dep_list(tgt)
lib_map = dep_to_lib(tgt, deps)
for val in lib_map.keys() :
link = (nam, val) if keep_suffix else (nam.split('.so')[0], val.split('.so')[0])
linked.add(link)
for dep in deps:
if dep in lib_map:
m = lib_map[dep]
if not m in visited:
visited.add(m)
dnam = os.path.basename(dep)
traverse_so(m, dnam, f, depth+1, visited, linked, keep_suffix)
# Walk the deps, starting from the mapped files of a process.
def trace_pid(tgt, f, visited, linked, keep_suffix) :
nf = open("/proc/%s/comm" % (tgt,), "r")
nam = nf.readline().strip()
nf.close()
cmd = "ls -l /proc/%s/map_files" % (tgt,)
cf = os.popen(cmd, "r")
lines = [ x.split(" -> ")[1].strip() for x in cf.readlines() if " -> " in x and ".so" in x ]
cf.close()
libs = sorted(set(lines))
print("Tracing shared objects from command", nam, "with", len(lines), "mapped .so files.")
lib_map = {}
depth = 0
for lib in libs:
libname = os.path.basename(lib)
lib_map[libname] = lib
for val in lib_map.keys() :
if keep_suffix :
link = (nam, val)
linked.add(link)
else :
link = (nam.split('.so')[0], val.split('.so')[0])
linked.add(link)
for dep in lib_map.keys() :
if dep in lib_map :
m = lib_map[dep]
if not m in visited :
visited.add(m)
dnam = os.path.basename(dep)
traverse_so(m, dnam, f, depth+1, visited, linked, keep_suffix)
# Main entry point
if __name__ == '__main__' :
if len(sys.argv) != 3 :
print("Usage: ", sys.argv[0], "libfoo.so out.dot")
print("Alt Usage:", sys.argv[0], "<PID> out.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")
linked = set()
visited = set()
if tgt.isnumeric() :
keep_suffix = False
trace_pid(tgt, f, visited, linked, keep_suffix)
else :
keep_suffix = True
traverse_so(tgt, nam, f, 0, visited, linked, keep_suffix)
for link in linked :
f.write('"' + link[0] + '" -> "' + link[1] + '"\n')
f.write("}\n");
f.close()