diff options
Diffstat (limited to 'checklibs.py')
-rw-r--r-- | checklibs.py | 386 |
1 files changed, 0 insertions, 386 deletions
diff --git a/checklibs.py b/checklibs.py deleted file mode 100644 index 18aa11e93..000000000 --- a/checklibs.py +++ /dev/null @@ -1,386 +0,0 @@ -############################################################################# -## -## Copyright (C) 2017 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# - -#!/usr/bin/env python -# -# checklibs.py -# -# Check Mach-O dependencies. -# -# See http://www.entropy.ch/blog/Developer/2011/03/05/2011-Update-to-checklibs-Script-for-dynamic-library-dependencies.html -# -# Written by Marc Liyanage <http://www.entropy.ch> -# -# - -import subprocess, sys, re, os.path, optparse, collections -from pprint import pprint - - -class MachOFile: - - def __init__(self, image_path, arch, parent = None): - self.image_path = image_path - self._dependencies = [] - self._cache = dict(paths = {}, order = []) - self.arch = arch - self.parent = parent - self.header_info = {} - self.load_info() - self.add_to_cache() - - def load_info(self): - if not self.image_path.exists(): - return - self.load_header() - self.load_rpaths() - - def load_header(self): - # Get the mach-o header info, we're interested in the file type - # (executable, dylib) - cmd = 'otool -arch {0} -h "{1}"' - output = self.shell(cmd, [self.arch, self.image_path.resolved_path], - fatal = True) - if not output: - print("Unable to load mach header for {} ({}), architecture " - "mismatch? Use --arch option to pick architecture".format( - self.image_path.resolved_path, self.arch), file=sys.stderr) - exit() - (keys, values) = output.splitlines()[2:] - self.header_info = dict(zip(keys.split(), values.split())) - - def load_rpaths(self): - output = self.shell('otool -arch {0} -l "{1}"', - [self.arch, self.image_path.resolved_path], fatal = True) - # skip file name on first line - load_commands = re.split('Load command (\d+)', output)[1:] - self._rpaths = [] - load_commands = collections.deque(load_commands) - while load_commands: - load_commands.popleft() # command index - command = load_commands.popleft().strip().splitlines() - if command[0].find('LC_RPATH') == -1: - continue - - path = re.findall('path (.+) \(offset \d+\)$', command[2])[0] - image_path = self.image_path_for_recorded_path(path) - image_path.rpath_source = self - self._rpaths.append(image_path) - - def ancestors(self): - ancestors = [] - parent = self.parent - while parent: - ancestors.append(parent) - parent = parent.parent - - return ancestors - - def self_and_ancestors(self): - return [self] + self.ancestors() - - def rpaths(self): - return self._rpaths - - def all_rpaths(self): - rpaths = [] - for image in self.self_and_ancestors(): - rpaths.extend(image.rpaths()) - return rpaths - - def root(self): - if not self.parent: - return self - return self.ancestors()[-1] - - def executable_path(self): - root = self.root() - if root.is_executable(): - return root.image_path - return None - - def filetype(self): - return long(self.header_info.get('filetype', 0)) - - def is_dylib(self): - return self.filetype() == MachOFile.MH_DYLIB - - def is_executable(self): - return self.filetype() == MachOFile.MH_EXECUTE - - def all_dependencies(self): - self.walk_dependencies() - return self.cache()['order'] - - def walk_dependencies(self, known = {}): - if known.get(self.image_path.resolved_path): - return - - known[self.image_path.resolved_path] = self - - for item in self.dependencies(): - item.walk_dependencies(known) - - def dependencies(self): - if not self.image_path.exists(): - return [] - - if self._dependencies: - return self._dependencies - - output = self.shell('otool -arch {0} -L "{1}"', - [self.arch, self.image_path.resolved_path], fatal = True) - output = [line.strip() for line in output.splitlines()] - del(output[0]) - if self.is_dylib(): - # In the case of dylibs, the first line is the id line - del(output[0]) - - self._dependencies = [] - for line in output: - match = re.match('^(.+)\s+(\(.+)\)$', line) - if not match: - continue - recorded_path = match.group(1) - image_path = self.image_path_for_recorded_path(recorded_path) - image = self.lookup_or_make_item(image_path) - self._dependencies.append(image) - - return self._dependencies - - # The root item holds the cache, all lower-level requests bubble up - # the parent chain - def cache(self): - if self.parent: - return self.parent.cache() - return self._cache - - def add_to_cache(self): - cache = self.cache() - cache['paths'][self.image_path.resolved_path] = self - cache['order'].append(self) - - def cached_item_for_path(self, path): - if not path: - return None - return self.cache()['paths'].get(path) - - def lookup_or_make_item(self, image_path): - image = self.cached_item_for_path(image_path.resolved_path) - if not image: # cache miss - image = MachOFile(image_path, self.arch, parent = self) - return image - - def image_path_for_recorded_path(self, recorded_path): - path = ImagePath(None, recorded_path) - - # handle @executable_path - if recorded_path.startswith(ImagePath.EXECUTABLE_PATH_TOKEN): - executable_image_path = self.executable_path() - if executable_image_path: - path.resolved_path = os.path.normpath( - recorded_path.replace( - ImagePath.EXECUTABLE_PATH_TOKEN, - os.path.dirname(executable_image_path.resolved_path))) - - # handle @loader_path - elif recorded_path.startswith(ImagePath.LOADER_PATH_TOKEN): - path.resolved_path = os.path.normpath(recorded_path.replace( - ImagePath.LOADER_PATH_TOKEN, - os.path.dirname(self.image_path.resolved_path))) - - # handle @rpath - elif recorded_path.startswith(ImagePath.RPATH_TOKEN): - for rpath in self.all_rpaths(): - resolved_path = os.path.normpath(recorded_path.replace( - ImagePath.RPATH_TOKEN, rpath.resolved_path)) - if os.path.exists(resolved_path): - path.resolved_path = resolved_path - path.rpath_source = rpath.rpath_source - break - - # handle absolute path - elif recorded_path.startswith('/'): - path.resolved_path = recorded_path - - return path - - def __repr__(self): - return str(self.image_path) - - def dump(self): - print(self.image_path) - for dependency in self.dependencies(): - print('\t{0}'.format(dependency)) - - @staticmethod - def shell(cmd_format, args, fatal = False): - cmd = cmd_format.format(*args) - popen = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE) - output = popen.communicate()[0] - if popen.returncode and fatal: - print("Nonzero exit status for shell command '{}'".format(cmd), - file=sys.stderr) - sys.exit(1) - - return output - - @classmethod - def architectures_for_image_at_path(cls, path): - output = cls.shell('file "{}"', [path]) - file_architectures = re.findall(r' executable (\w+)', output) - ordering = 'x86_64 i386'.split() - file_architectures = sorted(file_architectures, lambda a, b: cmp( - ordering.index(a), ordering.index(b))) - return file_architectures - - MH_EXECUTE = 0x2 - MH_DYLIB = 0x6 - MH_BUNDLE = 0x8 - - -# ANSI terminal coloring sequences -class Color: - HEADER = '\033[95m' - BLUE = '\033[94m' - GREEN = '\033[92m' - RED = '\033[91m' - ENDC = '\033[0m' - - @staticmethod - def red(string): - return Color.wrap(string, Color.RED) - - @staticmethod - def blue(string): - return Color.wrap(string, Color.BLUE) - - @staticmethod - def wrap(string, color): - return Color.HEADER + color + string + Color.ENDC - - -# This class holds path information for a mach-0 image file. -# It holds the path as it was recorded in the loading binary as well as -# the effective, resolved file system path. -# The former can contain @-replacement tokens. -# In the case where the recorded path contains an @rpath token that was -# resolved successfully, we also capture the path of the binary that -# supplied the rpath value that was used. -# That path itself can contain replacement tokens such as @loader_path. -class ImagePath: - - def __init__(self, resolved_path, recorded_path = None): - self.recorded_path = recorded_path - self.resolved_path = resolved_path - self.rpath_source = None - - def __repr__(self): - description = None - - if self.resolved_equals_recorded() or self.recorded_path == None: - description = self.resolved_path - else: - description = '{0} ({1})'.format(self.resolved_path, - self.recorded_path) - - if (not self.is_system_location()) and (not self.uses_dyld_token()): - description = Color.blue(description) - - if self.rpath_source: - description += ' (rpath source: {0})'.format( - self.rpath_source.image_path.resolved_path) - - if not self.exists(): - description += Color.red(' (missing)') - - return description - - def exists(self): - return self.resolved_path and os.path.exists(self.resolved_path) - - def resolved_equals_recorded(self): - return (self.resolved_path and self.recorded_path and - self.resolved_path == self.recorded_path) - - def uses_dyld_token(self): - return self.recorded_path and self.recorded_path.startswith('@') - - def is_system_location(self): - system_prefixes = ['/System/Library', '/usr/lib'] - for prefix in system_prefixes: - if self.resolved_path and self.resolved_path.startswith(prefix): - return True - - EXECUTABLE_PATH_TOKEN = '@executable_path' - LOADER_PATH_TOKEN = '@loader_path' - RPATH_TOKEN = '@rpath' - - -# Command line driver -parser = optparse.OptionParser( - usage = "Usage: %prog [options] path_to_mach_o_file") -parser.add_option( - "--arch", dest = "arch", help = "architecture", metavar = "ARCH") -parser.add_option( - "--all", dest = "include_system_libraries", - help = "Include system frameworks and libraries", action="store_true") -(options, args) = parser.parse_args() - -if len(args) < 1: - parser.print_help() - sys.exit(1) - -archs = MachOFile.architectures_for_image_at_path(args[0]) -if archs and not options.arch: - print('Analyzing architecture {}, override with --arch if needed'.format( - archs[0]), file=sys.stderr) - options.arch = archs[0] - -toplevel_image = MachOFile(ImagePath(args[0]), options.arch) - -for dependency in toplevel_image.all_dependencies(): - if (dependency.image_path.exists() and - (not options.include_system_libraries) and - dependency.image_path.is_system_location()): - continue - - dependency.dump() - print("\n") - |