diff options
author | Anthony G. Basile <blueness@gentoo.org> | 2014-07-30 15:40:36 -0400 |
---|---|---|
committer | Anthony G. Basile <blueness@gentoo.org> | 2014-07-30 15:40:36 -0400 |
commit | c206aab32dce7db944996628f160a3dcbaeb6c6f (patch) | |
tree | 808d1e0bd0877f367182b1ffc072fa9c4f9a5da0 /pocs/ldd | |
parent | configure.ac: replace tabs with 4 spaces (diff) | |
download | elfix-c206aab32dce7db944996628f160a3dcbaeb6c6f.tar.gz elfix-c206aab32dce7db944996628f160a3dcbaeb6c6f.tar.bz2 elfix-c206aab32dce7db944996628f160a3dcbaeb6c6f.zip |
Split misc/ into misc/ for production and poc/ for experimental stuff.
Diffstat (limited to 'pocs/ldd')
-rwxr-xr-x | pocs/ldd/ldd.py | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/pocs/ldd/ldd.py b/pocs/ldd/ldd.py new file mode 100755 index 0000000..447614e --- /dev/null +++ b/pocs/ldd/ldd.py @@ -0,0 +1,161 @@ +#!/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 + + +# We cache the dependencies for speed. The structure is +# { ELFClass : { SONAME : library, ... }, ELFClass : ... } +cache = {} + +def dynamic_dt_needed_paths( dt_needed, eclass, paths): + """ Search library paths for the library file corresponding + to the given DT_NEEDED and ELF Class. + """ + global cache + if not eclass in cache: + cache[eclass] = {} + + dt_needed_paths = {} + for n in dt_needed: + if n in cache[eclass].keys(): + dt_needed_paths[n] = cache[eclass][n] + else: + 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 + cache[eclass][n] = lib + except ELFError as ex: + sys.stderr.write('ELF error: %s\n' % ex) + sys.exit(1) + + return dt_needed_paths + + +def all_dynamic_dt_needed_paths(f, paths): + """ Return a dictionary of all the DT_NEEDED => Library Paths for + a given ELF file obtained by recursively following linkage. + """ + with open(f, 'rb') as file: + try: + readelf = ReadElf(file) + 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(): + dt_needed_paths = dict(all_dynamic_dt_needed_paths(lib, paths), **dt_needed_paths) + 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: + if len(args) > 1: + sys.stdout.write('%s : \n' % f) + all_dt_needed_paths = all_dynamic_dt_needed_paths(f, paths) + for n, lib in all_dt_needed_paths.items(): + sys.stdout.write('\t%s => %s\n' % (n, lib)) + +if __name__ == '__main__': + main() |