| # Copyright 2014 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Helper script to close over all transitive dependencies of a given .nexe |
| executable. |
| |
| e.g. Given |
| A -> B |
| B -> C |
| B -> D |
| C -> E |
| |
| where "A -> B" means A depends on B, then GetNeeded(A) will return A, B, C, D |
| and E. |
| """ |
| |
| import os |
| import re |
| import subprocess |
| |
| import elf |
| |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| SDK_DIR = os.path.dirname(os.path.dirname(SCRIPT_DIR)) |
| |
| NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') |
| FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') |
| |
| LOADER_X86 = 'runnable-ld.so' # Name of the dynamic loader |
| LOADER_ARM = 'elf_loader_arm.nexe' # Name of the ARM dynamic loader |
| |
| OBJDUMP_ARCH_MAP = { |
| # Names returned by Linux's objdump: |
| 'elf64-x86-64': 'x86-64', |
| 'elf32-i386': 'x86-32', |
| 'elf32-little': 'arm', |
| 'elf32-littlearm': 'arm', |
| # Names returned by old x86_64-nacl-objdump: |
| 'elf64-nacl': 'x86-64', |
| 'elf32-nacl': 'x86-32', |
| # Names returned by new x86_64-nacl-objdump: |
| 'elf64-x86-64-nacl': 'x86-64', |
| 'elf32-x86-64-nacl': 'x86-64', |
| 'elf32-i386-nacl': 'x86-32', |
| 'elf32-littlearm-nacl': 'arm', |
| } |
| |
| # The proper name of the dynamic linker, as kept in the IRT. This is |
| # excluded from the nmf file by convention. |
| LD_NACL_MAP = { |
| 'x86-32': 'ld-nacl-x86-32.so.1', |
| 'x86-64': 'ld-nacl-x86-64.so.1', |
| 'arm': None, |
| } |
| |
| |
| class Error(Exception): |
| '''Local Error class for this file.''' |
| pass |
| |
| |
| class NoObjdumpError(Error): |
| '''Error raised when objdump is needed but not found''' |
| pass |
| |
| |
| def GetNeeded(main_files, objdump, lib_path): |
| '''Collect the list of dependencies for the main_files |
| |
| Args: |
| main_files: A list of files to find dependencies of. |
| objdump: Path to the objdump executable. |
| lib_path: A list of paths to search for shared libraries. |
| |
| Returns: |
| A dict with key=filename and value=architecture. The architecture will be |
| one of ('x86_32', 'x86_64', 'arm'). |
| ''' |
| |
| dynamic = any(elf.ParseElfHeader(f)[1] for f in main_files) |
| |
| if dynamic: |
| return _GetNeededDynamic(main_files, objdump, lib_path) |
| else: |
| return _GetNeededStatic(main_files) |
| |
| |
| def _GetNeededDynamic(main_files, objdump, lib_path): |
| examined = set() |
| all_files, unexamined = GleanFromObjdump(main_files, None, objdump, lib_path) |
| for arch in all_files.values(): |
| if unexamined: |
| if arch == 'arm': |
| unexamined.add((LOADER_ARM, arch)) |
| else: |
| unexamined.add((LOADER_X86, arch)) |
| |
| while unexamined: |
| files_to_examine = {} |
| |
| # Take all the currently unexamined files and group them |
| # by architecture. |
| for name, arch in unexamined: |
| files_to_examine.setdefault(arch, []).append(name) |
| |
| # Call GleanFromObjdump() for each architecture. |
| needed = set() |
| for arch, files in files_to_examine.items(): |
| new_files, new_needed = GleanFromObjdump(files, arch, objdump, lib_path) |
| all_files.update(new_files) |
| needed |= new_needed |
| |
| examined |= unexamined |
| unexamined = needed - examined |
| |
| # With the runnable-ld.so scheme we have today, the proper name of |
| # the dynamic linker should be excluded from the list of files. |
| ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())] |
| for filename, arch in list(all_files.items()): |
| name = os.path.basename(filename) |
| if name in ldso: |
| del all_files[filename] |
| |
| return all_files |
| |
| |
| def GleanFromObjdump(files, arch, objdump, lib_path): |
| '''Get architecture and dependency information for given files |
| |
| Args: |
| files: A list of files to examine. |
| [ '/path/to/my.nexe', |
| '/path/to/lib64/libmy.so', |
| '/path/to/mydata.so', |
| '/path/to/my.data' ] |
| arch: The architecure we are looking for, or None to accept any |
| architecture. |
| objdump: Path to the objdump executable. |
| lib_path: A list of paths to search for shared libraries. |
| |
| Returns: A tuple with the following members: |
| input_info: A dict with key=filename and value=architecture. The |
| architecture will be one of ('x86_32', 'x86_64', 'arm'). |
| needed: A set of strings formatted as "arch/name". Example: |
| set(['x86-32/libc.so', 'x86-64/libgcc.so']) |
| ''' |
| if not objdump: |
| raise NoObjdumpError('No objdump executable found!') |
| |
| full_paths = set() |
| for filename in files: |
| if os.path.exists(filename): |
| full_paths.add(filename) |
| else: |
| for path in _FindLibsInPath(filename, lib_path): |
| full_paths.add(path) |
| |
| cmd = [objdump, '-p'] + list(sorted(full_paths)) |
| env = {'LANG': 'en_US.UTF-8'} |
| proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, bufsize=-1, |
| universal_newlines=True, |
| env=env) |
| |
| input_info = {} |
| found_basenames = set() |
| needed = set() |
| output, err_output = proc.communicate() |
| if proc.returncode: |
| raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % |
| (output, err_output, proc.returncode)) |
| |
| file_arch = None |
| for line in output.splitlines(True): |
| # Objdump should display the architecture first and then the dependencies |
| # second for each file in the list. |
| matched = FormatMatcher.match(line) |
| if matched: |
| filename = matched.group(1) |
| file_arch = OBJDUMP_ARCH_MAP[matched.group(2)] |
| if arch and file_arch != arch: |
| continue |
| name = os.path.basename(filename) |
| found_basenames.add(name) |
| input_info[filename] = file_arch |
| matched = NeededMatcher.match(line) |
| if matched: |
| if arch and file_arch != arch: |
| continue |
| filename = matched.group(1) |
| new_needed = (filename, file_arch) |
| needed.add(new_needed) |
| |
| for filename in files: |
| if os.path.basename(filename) not in found_basenames: |
| raise Error('Library not found [%s]: %s' % (arch, filename)) |
| |
| return input_info, needed |
| |
| |
| def _FindLibsInPath(name, lib_path): |
| '''Finds the set of libraries matching |name| within lib_path |
| |
| Args: |
| name: name of library to find |
| lib_path: A list of paths to search for shared libraries. |
| |
| Returns: |
| A list of system paths that match the given name within the lib_path''' |
| files = [] |
| for dirname in lib_path: |
| # The libc.so files in the the glibc toolchain is actually a linker |
| # script which references libc.so.<SHA1>. This means the libc.so itself |
| # does not end up in the NEEDED section for glibc. |
| if name == 'libc.so': |
| continue |
| filename = os.path.join(dirname, name) |
| if os.path.exists(filename): |
| files.append(filename) |
| if not files: |
| raise Error('cannot find library %s' % name) |
| return files |
| |
| |
| def _GetNeededStatic(main_files): |
| needed = {} |
| for filename in main_files: |
| arch = elf.ParseElfHeader(filename)[0] |
| needed[filename] = arch |
| return needed |