aboutsummaryrefslogtreecommitdiff
blob: 5a1578627311afc0d5855641fe22c76753bb9d03 (plain)
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()