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
130
131
132
133
134
135
136
137
|
#!/usr/bin/env python
import os, sys
import re, glob
from optparse import OptionParser
from elftools import __version__
from elftools.common.exceptions import ELFError
from elftools.common.py3compat import bytes2str
from elftools.elf.elffile import ELFFile
from elftools.elf.dynamic import DynamicSection
from elftools.elf.descriptions import describe_ei_class
class ReadElf(object):
def __init__(self, file):
""" file: stream object with the ELF file to read
"""
self.elffile = ELFFile(file)
def elf_class(self):
""" Return the ELF Class
"""
header = self.elffile.header
e_ident = header['e_ident']
return describe_ei_class(e_ident['EI_CLASS'])
def dynamic_dt_needed(self):
""" Return a list of the DT_NEEDED
"""
dt_needed = []
for section in self.elffile.iter_sections():
if not isinstance(section, DynamicSection):
continue
for tag in section.iter_tags():
if tag.entry.d_tag == 'DT_NEEDED':
dt_needed.append(bytes2str(tag.needed))
#sys.stdout.write('\t%s\n' % bytes2str(tag.needed) )
return dt_needed
def ldpaths(ld_so_conf='/etc/ld.so.conf'):
""" Generate paths to search for libraries from ld.so.conf. Recursively
parse included files. We assume correct syntax and the ld.so.cache
is in sync with ld.so.conf.
"""
with open(ld_so_conf, 'r') as path_file:
lines = path_file.read()
lines = re.sub('#.*', '', lines) # kill comments
lines = list(re.split(':+|\s+|\t+|\n+|,+', lines)) # man 8 ldconfig
paths = []
include_globs = []
for i in range(0,len(lines)):
if lines[i] == '':
continue
if lines[i] == 'include':
f = lines[i + 1]
include_globs.append(f)
continue
if lines[i] not in include_globs:
real_path = os.path.realpath(lines[i])
if os.path.exists(real_path):
paths.append(real_path)
include_files = []
for g in include_globs:
include_files = include_files + glob.glob('/etc/' + g)
for c in include_files:
paths = paths + ldpaths(os.path.realpath(c))
paths = list(set(paths))
paths.sort()
return paths
def dynamic_dt_needed_paths( dt_needed, eclass, paths):
""" Search library paths for the library file corresponding
to the DT_NEEDED and ELF Class.
"""
dt_needed_paths = {}
for n in dt_needed:
for p in paths:
lib = p + os.sep + n
if os.path.exists(lib):
with open(lib, 'rb') as file:
try:
readlib = ReadElf(file)
if eclass == readlib.elf_class():
dt_needed_paths[n] = lib
except ELFError as ex:
sys.stderr.write('ELF error: %s\n' % ex)
sys.exit(1)
return dt_needed_paths
SCRIPT_DESCRIPTION = 'Print shared library dependencies'
VERSION_STRING = '%%prog: based on pyelftools %s' % __version__
def main():
optparser = OptionParser(
usage='usage: %prog <elf-file>',
description=SCRIPT_DESCRIPTION,
add_help_option=False, # -h is a real option of readelf
prog='ldd.py',
version=VERSION_STRING)
optparser.add_option('-h', '--help',
action='store_true', dest='help',
help='Display this information')
options, args = optparser.parse_args()
if options.help or len(args) == 0:
optparser.print_help()
sys.exit(0)
paths = ldpaths()
for f in args:
with open(f, 'rb') as file:
try:
readelf = ReadElf(file)
if len(args) > 1:
sys.stdout.write('%s : \n' % f)
eclass = readelf.elf_class()
# This needs to be iterated until we traverse the entire linkage tree
dt_needed = readelf.dynamic_dt_needed()
dt_needed_paths = dynamic_dt_needed_paths( dt_needed, eclass, paths)
for n, lib in dt_needed_paths.items():
sys.stdout.write('\t%s => %s\n' % (n, lib))
except ELFError as ex:
sys.stderr.write('ELF error: %s\n' % ex)
sys.exit(1)
if __name__ == '__main__':
main()
|