• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Helper script to close over all transitive dependencies of a given .nexe
6executable.
7
8e.g. Given
9A -> B
10B -> C
11B -> D
12C -> E
13
14where "A -> B" means A depends on B, then GetNeeded(A) will return A, B, C, D
15and E.
16"""
17
18import os
19import re
20import subprocess
21
22import elf
23
24SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
25SDK_DIR = os.path.dirname(os.path.dirname(SCRIPT_DIR))
26
27NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$')
28FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$')
29
30RUNNABLE_LD = 'runnable-ld.so'  # Name of the dynamic loader
31
32OBJDUMP_ARCH_MAP = {
33    # Names returned by Linux's objdump:
34    'elf64-x86-64': 'x86-64',
35    'elf32-i386': 'x86-32',
36    'elf32-little': 'arm',
37    'elf32-littlearm': 'arm',
38    # Names returned by old x86_64-nacl-objdump:
39    'elf64-nacl': 'x86-64',
40    'elf32-nacl': 'x86-32',
41    # Names returned by new x86_64-nacl-objdump:
42    'elf64-x86-64-nacl': 'x86-64',
43    'elf32-x86-64-nacl': 'x86-64',
44    'elf32-i386-nacl': 'x86-32',
45    'elf32-littlearm-nacl': 'arm',
46}
47
48# The proper name of the dynamic linker, as kept in the IRT.  This is
49# excluded from the nmf file by convention.
50LD_NACL_MAP = {
51    'x86-32': 'ld-nacl-x86-32.so.1',
52    'x86-64': 'ld-nacl-x86-64.so.1',
53    'arm': None,
54}
55
56
57class Error(Exception):
58  '''Local Error class for this file.'''
59  pass
60
61
62class NoObjdumpError(Error):
63  '''Error raised when objdump is needed but not found'''
64  pass
65
66
67def GetNeeded(main_files, objdump, lib_path):
68  '''Collect the list of dependencies for the main_files
69
70  Args:
71    main_files: A list of files to find dependencies of.
72    objdump: Path to the objdump executable.
73    lib_path: A list of paths to search for shared libraries.
74
75  Returns:
76    A dict with key=filename and value=architecture. The architecture will be
77    one of ('x86_32', 'x86_64', 'arm').
78  '''
79
80  dynamic = any(elf.ParseElfHeader(f)[1] for f in main_files)
81
82  if dynamic:
83    return _GetNeededDynamic(main_files, objdump, lib_path)
84  else:
85    return _GetNeededStatic(main_files)
86
87
88def _GetNeededDynamic(main_files, objdump, lib_path):
89  examined = set()
90  all_files, unexamined = GleanFromObjdump(main_files, None, objdump, lib_path)
91  for arch in all_files.itervalues():
92    if unexamined:
93      unexamined.add((RUNNABLE_LD, arch))
94
95  while unexamined:
96    files_to_examine = {}
97
98    # Take all the currently unexamined files and group them
99    # by architecture.
100    for name, arch in unexamined:
101      files_to_examine.setdefault(arch, []).append(name)
102
103    # Call GleanFromObjdump() for each architecture.
104    needed = set()
105    for arch, files in files_to_examine.iteritems():
106      new_files, new_needed = GleanFromObjdump(files, arch, objdump, lib_path)
107      all_files.update(new_files)
108      needed |= new_needed
109
110    examined |= unexamined
111    unexamined = needed - examined
112
113  # With the runnable-ld.so scheme we have today, the proper name of
114  # the dynamic linker should be excluded from the list of files.
115  ldso = [LD_NACL_MAP[arch] for arch in set(OBJDUMP_ARCH_MAP.values())]
116  for filename, arch in all_files.items():
117    name = os.path.basename(filename)
118    if name in ldso:
119      del all_files[filename]
120
121  return all_files
122
123
124def GleanFromObjdump(files, arch, objdump, lib_path):
125  '''Get architecture and dependency information for given files
126
127  Args:
128    files: A list of files to examine.
129        [ '/path/to/my.nexe',
130          '/path/to/lib64/libmy.so',
131          '/path/to/mydata.so',
132          '/path/to/my.data' ]
133    arch: The architecure we are looking for, or None to accept any
134          architecture.
135    objdump: Path to the objdump executable.
136    lib_path: A list of paths to search for shared libraries.
137
138  Returns: A tuple with the following members:
139    input_info: A dict with key=filename and value=architecture. The
140        architecture will be one of ('x86_32', 'x86_64', 'arm').
141    needed: A set of strings formatted as "arch/name".  Example:
142        set(['x86-32/libc.so', 'x86-64/libgcc.so'])
143  '''
144  if not objdump:
145    raise NoObjdumpError('No objdump executable found!')
146
147  full_paths = set()
148  for filename in files:
149    if os.path.exists(filename):
150      full_paths.add(filename)
151    else:
152      for path in _FindLibsInPath(filename, lib_path):
153        full_paths.add(path)
154
155  cmd = [objdump, '-p'] + list(full_paths)
156  env = {'LANG': 'en_US.UTF-8'}
157  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
158                          stderr=subprocess.PIPE, bufsize=-1,
159                          env=env)
160
161  input_info = {}
162  found_basenames = set()
163  needed = set()
164  output, err_output = proc.communicate()
165  if proc.returncode:
166    raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' %
167                (output, err_output, proc.returncode))
168
169  file_arch = None
170  for line in output.splitlines(True):
171    # Objdump should display the architecture first and then the dependencies
172    # second for each file in the list.
173    matched = FormatMatcher.match(line)
174    if matched:
175      filename = matched.group(1)
176      file_arch = OBJDUMP_ARCH_MAP[matched.group(2)]
177      if arch and file_arch != arch:
178        continue
179      name = os.path.basename(filename)
180      found_basenames.add(name)
181      input_info[filename] = file_arch
182    matched = NeededMatcher.match(line)
183    if matched:
184      if arch and file_arch != arch:
185        continue
186      filename = matched.group(1)
187      new_needed = (filename, file_arch)
188      needed.add(new_needed)
189
190  for filename in files:
191    if os.path.basename(filename) not in found_basenames:
192      raise Error('Library not found [%s]: %s' % (arch, filename))
193
194  return input_info, needed
195
196
197def _FindLibsInPath(name, lib_path):
198  '''Finds the set of libraries matching |name| within lib_path
199
200  Args:
201    name: name of library to find
202    lib_path: A list of paths to search for shared libraries.
203
204  Returns:
205    A list of system paths that match the given name within the lib_path'''
206  files = []
207  for dirname in lib_path:
208    # The libc.so files in the the glibc toolchain is actually a linker
209    # script which references libc.so.<SHA1>.  This means the lib.so itself
210    # does not end up in the NEEDED section for glibc.  However with bionic
211    # the SONAME is actually libc.so.  If we pass glibc's libc.so to objdump
212    # if fails to parse it, os this filters out libc.so expept for within
213    # the bionic toolchain.
214    # TODO(noelallen): Remove this once the SONAME in bionic is made to be
215    # unique in the same it is under glibc:
216    # https://code.google.com/p/nativeclient/issues/detail?id=3833
217    rel_dirname = os.path.relpath(dirname, SDK_DIR)
218    if name == 'libc.so' and 'bionic' not in rel_dirname:
219      continue
220    filename = os.path.join(dirname, name)
221    if os.path.exists(filename):
222      files.append(filename)
223  if not files:
224    raise Error('cannot find library %s' % name)
225  return files
226
227
228def _GetNeededStatic(main_files):
229  needed = {}
230  for filename in main_files:
231    arch = elf.ParseElfHeader(filename)[0]
232    needed[filename] = arch
233  return needed
234