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.
5 """Helper script to close over all transitive dependencies of a given .nexe
14 where "A -> B" means A depends on B, then GetNeeded(A) will return A, B, C, D
24 SCRIPT_DIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
25 SDK_DIR
= os
.path
.dirname(os
.path
.dirname(SCRIPT_DIR
))
27 NeededMatcher
= re
.compile('^ *NEEDED *([^ ]+)\n$')
28 FormatMatcher
= re
.compile('^(.+):\\s*file format (.+)\n$')
30 RUNNABLE_LD
= 'runnable-ld.so' # Name of the dynamic loader
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',
48 # The proper name of the dynamic linker, as kept in the IRT. This is
49 # excluded from the nmf file by convention.
51 'x86-32': 'ld-nacl-x86-32.so.1',
52 'x86-64': 'ld-nacl-x86-64.so.1',
57 class Error(Exception):
58 '''Local Error class for this file.'''
62 class NoObjdumpError(Error
):
63 '''Error raised when objdump is needed but not found'''
67 def GetNeeded(main_files
, objdump
, lib_path
):
68 '''Collect the list of dependencies for the main_files
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.
76 A dict with key=filename and value=architecture. The architecture will be
77 one of ('x86_32', 'x86_64', 'arm').
80 dynamic
= any(elf
.ParseElfHeader(f
)[1] for f
in main_files
)
83 return _GetNeededDynamic(main_files
, objdump
, lib_path
)
85 return _GetNeededStatic(main_files
)
88 def _GetNeededDynamic(main_files
, objdump
, lib_path
):
90 all_files
, unexamined
= GleanFromObjdump(main_files
, None, objdump
, lib_path
)
91 for arch
in all_files
.itervalues():
93 unexamined
.add((RUNNABLE_LD
, arch
))
98 # Take all the currently unexamined files and group them
100 for name
, arch
in unexamined
:
101 files_to_examine
.setdefault(arch
, []).append(name
)
103 # Call GleanFromObjdump() for each architecture.
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
)
110 examined |
= unexamined
111 unexamined
= needed
- examined
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
)
119 del all_files
[filename
]
124 def GleanFromObjdump(files
, arch
, objdump
, lib_path
):
125 '''Get architecture and dependency information for given files
128 files: A list of files to examine.
129 [ '/path/to/my.nexe',
130 '/path/to/lib64/libmy.so',
131 '/path/to/mydata.so',
133 arch: The architecure we are looking for, or None to accept any
135 objdump: Path to the objdump executable.
136 lib_path: A list of paths to search for shared libraries.
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'])
145 raise NoObjdumpError('No objdump executable found!')
148 for filename
in files
:
149 if os
.path
.exists(filename
):
150 full_paths
.add(filename
)
152 for path
in _FindLibsInPath(filename
, lib_path
):
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,
162 found_basenames
= set()
164 output
, err_output
= proc
.communicate()
166 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' %
167 (output
, err_output
, proc
.returncode
))
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
)
175 filename
= matched
.group(1)
176 file_arch
= OBJDUMP_ARCH_MAP
[matched
.group(2)]
177 if arch
and file_arch
!= arch
:
179 name
= os
.path
.basename(filename
)
180 found_basenames
.add(name
)
181 input_info
[filename
] = file_arch
182 matched
= NeededMatcher
.match(line
)
184 if arch
and file_arch
!= arch
:
186 filename
= matched
.group(1)
187 new_needed
= (filename
, file_arch
)
188 needed
.add(new_needed
)
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
))
194 return input_info
, needed
197 def _FindLibsInPath(name
, lib_path
):
198 '''Finds the set of libraries matching |name| within lib_path
201 name: name of library to find
202 lib_path: A list of paths to search for shared libraries.
205 A list of system paths that match the given name within the lib_path'''
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(bradnelson): 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
:
220 filename
= os
.path
.join(dirname
, name
)
221 if os
.path
.exists(filename
):
222 files
.append(filename
)
224 raise Error('cannot find library %s' % name
)
228 def _GetNeededStatic(main_files
):
230 for filename
in main_files
:
231 arch
= elf
.ParseElfHeader(filename
)[0]
232 needed
[filename
] = arch