| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 from __future__ import with_statement | 6 from __future__ import with_statement |
| 7 | 7 |
| 8 import errno | 8 import errno |
| 9 import optparse | 9 import optparse |
| 10 import os | 10 import os |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') | 22 NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') |
| 23 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') | 23 FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') |
| 24 | 24 |
| 25 FORMAT_ARCH_MAP = { | 25 FORMAT_ARCH_MAP = { |
| 26 # Names returned by Linux's objdump: | 26 # Names returned by Linux's objdump: |
| 27 'elf64-x86-64': 'x86-64', | 27 'elf64-x86-64': 'x86-64', |
| 28 'elf32-i386': 'x86-32', | 28 'elf32-i386': 'x86-32', |
| 29 # Names returned by x86_64-nacl-objdump: | 29 # Names returned by x86_64-nacl-objdump: |
| 30 'elf64-nacl': 'x86-64', | 30 'elf64-nacl': 'x86-64', |
| 31 'elf32-nacl': 'x86-32', | 31 'elf32-nacl': 'x86-32', |
| 32 # TODO(mball): Add support for 'arm-32' and 'portable' architectures | |
| 33 # 'elf32-little': 'arm-32', | |
| 34 } | 32 } |
| 35 | 33 |
| 34 ARCH_LOCATION = { |
| 35 'x86-32': 'lib32', |
| 36 'x86-64': 'lib64', |
| 37 } |
| 38 |
| 36 # These constants are used within nmf files. | 39 # These constants are used within nmf files. |
| 37 RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader | 40 RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader |
| 38 MAIN_NEXE = 'main.nexe' # Name of entry point for execution | 41 MAIN_NEXE = 'main.nexe' # Name of entry point for execution |
| 39 PROGRAM_KEY = 'program' # Key of the program section in an nmf file | 42 PROGRAM_KEY = 'program' # Key of the program section in an nmf file |
| 40 URL_KEY = 'url' # Key of the url field for a particular file in an nmf file | 43 URL_KEY = 'url' # Key of the url field for a particular file in an nmf file |
| 41 FILES_KEY = 'files' # Key of the files section in an nmf file | 44 FILES_KEY = 'files' # Key of the files section in an nmf file |
| 42 | 45 |
| 43 # The proper name of the dynamic linker, as kept in the IRT. This is | 46 # The proper name of the dynamic linker, as kept in the IRT. This is |
| 44 # excluded from the nmf file by convention. | 47 # excluded from the nmf file by convention. |
| 45 LD_NACL_MAP = { | 48 LD_NACL_MAP = { |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 103 both for staging the libraries and for inclusion into the nmf file. | 106 both for staging the libraries and for inclusion into the nmf file. |
| 104 Examples: ['..'], ['lib_dir'] ''' | 107 Examples: ['..'], ['lib_dir'] ''' |
| 105 self.objdump = objdump | 108 self.objdump = objdump |
| 106 self.main_files = main_files or [] | 109 self.main_files = main_files or [] |
| 107 self.extra_files = extra_files or [] | 110 self.extra_files = extra_files or [] |
| 108 self.lib_path = lib_path or [] | 111 self.lib_path = lib_path or [] |
| 109 self.manifest = None | 112 self.manifest = None |
| 110 self.needed = None | 113 self.needed = None |
| 111 self.lib_prefix = lib_prefix or [] | 114 self.lib_prefix = lib_prefix or [] |
| 112 | 115 |
| 116 |
| 113 def GleanFromObjdump(self, files): | 117 def GleanFromObjdump(self, files): |
| 114 '''Get architecture and dependency information for given files | 118 '''Get architecture and dependency information for given files |
| 115 | 119 |
| 116 Args: | 120 Args: |
| 117 files: A dict with key=filename and value=list or set of archs. E.g.: | 121 files: A dict with key=filename and value=list or set of archs. E.g.: |
| 118 { '/path/to/my.nexe': ['x86-32', 'x86-64'], | 122 { '/path/to/my.nexe': ['x86-32'] |
| 119 '/path/to/libmy.so': ['x86-32'], | 123 '/path/to/lib64/libmy.so': ['x86-64'], |
| 120 '/path/to/my2.nexe': None } # Indicates all architectures | 124 '/path/to/mydata.so': ['x86-32', 'x86-64'], |
| 125 '/path/to/my.data': None } # Indicates all architectures |
| 121 | 126 |
| 122 Returns: A tuple with the following members: | 127 Returns: A tuple with the following members: |
| 123 input_info: A dict with key=filename and value=ArchFile of input files. | 128 input_info: A dict with key=filename and value=ArchFile of input files. |
| 124 Includes the input files as well, with arch filled in if absent. | 129 Includes the input files as well, with arch filled in if absent. |
| 125 Example: { '/path/to/my.nexe': ArchFile(my.nexe), | 130 Example: { '/path/to/my.nexe': ArchFile(my.nexe), |
| 126 '/path/to/libfoo.so': ArchFile(libfoo.so) } | 131 '/path/to/libfoo.so': ArchFile(libfoo.so) } |
| 127 needed: A set of strings formatted as "arch/name". Example: | 132 needed: A set of strings formatted as "arch/name". Example: |
| 128 set(['x86-32/libc.so', 'x86-64/libgcc.so']) | 133 set(['x86-32/libc.so', 'x86-64/libgcc.so']) |
| 129 ''' | 134 ''' |
| 130 DebugPrint("GleanFromObjdump(%s)" % ([self.objdump, '-p'] + files.keys())) | 135 DebugPrint("GleanFromObjdump(%s)" % ([self.objdump, '-p'] + files.keys())) |
| 131 proc = subprocess.Popen([self.objdump, '-p'] + files.keys(), | 136 proc = subprocess.Popen([self.objdump, '-p'] + files.keys(), |
| 132 stdout=subprocess.PIPE, | 137 stdout=subprocess.PIPE, |
| 133 stderr=subprocess.PIPE, bufsize=-1) | 138 stderr=subprocess.PIPE, bufsize=-1) |
| 134 input_info = {} | 139 input_info = {} |
| 135 needed = set() | 140 needed = set() |
| 136 output, err_output = proc.communicate() | 141 output, err_output = proc.communicate() |
| 137 for line in output.splitlines(True): | 142 for line in output.splitlines(True): |
| 138 # Objdump should display the architecture first and then the dependencies | 143 # Objdump should display the architecture first and then the dependencies |
| 139 # second for each file in the list. | 144 # second for each file in the list. |
| 140 matched = FormatMatcher.match(line) | 145 matched = FormatMatcher.match(line) |
| 141 if matched is not None: | 146 if matched is not None: |
| 142 filename = matched.group(1) | 147 filename = matched.group(1) |
| 143 arch = FORMAT_ARCH_MAP[matched.group(2)] | 148 arch = FORMAT_ARCH_MAP[matched.group(2)] |
| 144 if files[filename] is None or arch in files[filename]: | 149 if files[filename] is None or arch in files[filename]: |
| 145 name = os.path.basename(filename) | 150 name = os.path.basename(filename) |
| 146 input_info[filename] = ArchFile( | 151 input_info[filename] = ArchFile( |
| 147 arch=arch, | 152 arch=arch, |
| 148 name=name, | 153 name=name, |
| 149 path=filename, | 154 path=filename, |
| 150 url='/'.join(self.lib_prefix + [arch, name])) | 155 url='/'.join(self.lib_prefix + [ARCH_LOCATION[arch], name])) |
| 151 matched = NeededMatcher.match(line) | 156 matched = NeededMatcher.match(line) |
| 152 if matched is not None: | 157 if matched is not None: |
| 153 if files[filename] is None or arch in files[filename]: | 158 if files[filename] is None or arch in files[filename]: |
| 154 needed.add('/'.join([arch, matched.group(1)])) | 159 needed.add('/'.join([arch, matched.group(1)])) |
| 155 status = proc.poll() | 160 status = proc.poll() |
| 156 if status != 0: | 161 if status != 0: |
| 157 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % | 162 raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % |
| 158 (output, err_output, status)) | 163 (output, err_output, status)) |
| 159 return input_info, needed | 164 return input_info, needed |
| 160 | 165 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 182 A dict with key=filename and value=ArchFile of input files. | 187 A dict with key=filename and value=ArchFile of input files. |
| 183 Includes the input files as well, with arch filled in if absent. | 188 Includes the input files as well, with arch filled in if absent. |
| 184 Example: { '/path/to/my.nexe': ArchFile(my.nexe), | 189 Example: { '/path/to/my.nexe': ArchFile(my.nexe), |
| 185 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' | 190 '/path/to/libfoo.so': ArchFile(libfoo.so) }''' |
| 186 if not self.needed: | 191 if not self.needed: |
| 187 DebugPrint('GetNeeded(%s)' % self.main_files) | 192 DebugPrint('GetNeeded(%s)' % self.main_files) |
| 188 examined = set() | 193 examined = set() |
| 189 all_files, unexamined = self.GleanFromObjdump( | 194 all_files, unexamined = self.GleanFromObjdump( |
| 190 dict([(file, None) for file in self.main_files])) | 195 dict([(file, None) for file in self.main_files])) |
| 191 for name, arch_file in all_files.items(): | 196 for name, arch_file in all_files.items(): |
| 192 arch_file.url = os.path.basename(name) | 197 arch_file.url = name |
| 193 if unexamined: | 198 if unexamined: |
| 194 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) | 199 unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) |
| 195 while unexamined: | 200 while unexamined: |
| 196 files_to_examine = {} | 201 files_to_examine = {} |
| 197 for arch_name in unexamined: | 202 for arch_name in unexamined: |
| 198 arch, name = arch_name.split('/') | 203 arch, name = arch_name.split('/') |
| 199 for path in self.FindLibsInPath(name): | 204 for path in self.FindLibsInPath(name): |
| 200 files_to_examine.setdefault(path, set()).add(arch) | 205 files_to_examine.setdefault(path, set()).add(arch) |
| 201 new_files, needed = self.GleanFromObjdump(files_to_examine) | 206 new_files, needed = self.GleanFromObjdump(files_to_examine) |
| 202 all_files.update(new_files) | 207 all_files.update(new_files) |
| (...skipping 22 matching lines...) Expand all Loading... |
| 225 urllib.url2pathname(arch_file.url)) | 230 urllib.url2pathname(arch_file.url)) |
| 226 try: | 231 try: |
| 227 os.makedirs(os.path.dirname(destination)) | 232 os.makedirs(os.path.dirname(destination)) |
| 228 except OSError as exception_info: | 233 except OSError as exception_info: |
| 229 if exception_info.errno != errno.EEXIST: | 234 if exception_info.errno != errno.EEXIST: |
| 230 raise | 235 raise |
| 231 if (os.path.normcase(os.path.abspath(source)) != | 236 if (os.path.normcase(os.path.abspath(source)) != |
| 232 os.path.normcase(os.path.abspath(destination))): | 237 os.path.normcase(os.path.abspath(destination))): |
| 233 shutil.copy2(source, destination) | 238 shutil.copy2(source, destination) |
| 234 | 239 |
| 235 def _GenerateManifest(self): | 240 def _GenerateManifest(self, runnable=True): |
| 236 programs = {} | 241 '''Create a JSON formatted dict containing the files |
| 237 files = {} | 242 |
| 243 NaCl will map url requests based on architecture. The startup NEXE |
| 244 can always be found under the top key PROGRAM. Additional files are under |
| 245 the FILES key further mapped by file name. In the case of 'runnable' the |
| 246 PROGRAM key is populated with urls pointing the runnable-ld.so which acts |
| 247 as the startup nexe. The application itself, is then placed under the |
| 248 FILES key mapped as 'main.exe' instead of it's original name so that the |
| 249 loader can find it.''' |
| 250 manifest = { FILES_KEY: {}, PROGRAM_KEY: {} } |
| 251 needed = self.GetNeeded() |
| 238 | 252 |
| 239 def add_files(needed): | 253 for need in needed: |
| 240 for filename, arch_file in needed.items(): | 254 archinfo = needed[need] |
| 241 files.setdefault(arch_file.arch, set()).add(arch_file.name) | 255 urlinfo = { URL_KEY: archinfo.url } |
| 256 name = archinfo.name |
| 242 | 257 |
| 243 needed = self.GetNeeded() | 258 # If starting with runnable-ld.so, make that the main executable. |
| 244 add_files(needed) | 259 if runnable: |
| 260 if need.endswith(RUNNABLE_LD): |
| 261 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo |
| 262 continue |
| 245 | 263 |
| 246 for filename in self.main_files: | 264 # For the main nexes: |
| 247 arch_file = needed[filename] | 265 if need.endswith('.nexe') and need in self.main_files: |
| 248 programs[arch_file.arch] = arch_file.name | 266 # Place it under program if we aren't using the runnable-ld.so. |
| 267 if not runnable: |
| 268 manifest[PROGRAM_KEY][archinfo.arch] = urlinfo |
| 269 continue |
| 270 # Otherwise, treat it like another another file named main.nexe. |
| 271 name = MAIN_NEXE |
| 249 | 272 |
| 250 filemap = {} | 273 fileinfo = manifest[FILES_KEY].get(name, {}) |
| 251 for arch in files: | 274 fileinfo[archinfo.arch] = urlinfo |
| 252 for file in files[arch]: | 275 manifest[FILES_KEY][name] = fileinfo |
| 253 if file not in programs.values() and file != RUNNABLE_LD: | |
| 254 filemap.setdefault(file, set()).add(arch) | |
| 255 | 276 |
| 256 def arch_name(arch, file): | |
| 257 # nmf files expect unix-style path separators | |
| 258 return {URL_KEY: '/'.join(self.lib_prefix + [arch, file])} | |
| 259 | |
| 260 # TODO(mcgrathr): perhaps notice a program with no deps | |
| 261 # (i.e. statically linked) and generate program=nexe instead? | |
| 262 manifest = {PROGRAM_KEY: {}, FILES_KEY: {MAIN_NEXE: {}}} | |
| 263 for arch in programs: | |
| 264 manifest[PROGRAM_KEY][arch] = arch_name(arch, RUNNABLE_LD) | |
| 265 manifest[FILES_KEY][MAIN_NEXE][arch] = {URL_KEY: programs[arch]} | |
| 266 | |
| 267 for file in filemap: | |
| 268 manifest[FILES_KEY][file] = dict([(arch, arch_name(arch, file)) | |
| 269 for arch in filemap[file]]) | |
| 270 self.manifest = manifest | 277 self.manifest = manifest |
| 271 | 278 |
| 272 def GetManifest(self): | 279 def GetManifest(self): |
| 273 '''Returns a JSON-formatted dict containing the NaCl dependencies''' | 280 '''Returns a JSON-formatted dict containing the NaCl dependencies''' |
| 274 if not self.manifest: | 281 if not self.manifest: |
| 275 self._GenerateManifest() | 282 self._GenerateManifest() |
| 276 | 283 |
| 277 return self.manifest | 284 return self.manifest |
| 278 | 285 |
| 279 def GetJson(self): | 286 def GetJson(self): |
| (...skipping 14 matching lines...) Expand all Loading... |
| 294 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', | 301 parser.add_option('-D', '--objdump', dest='objdump', default='objdump', |
| 295 help='Use TOOL as the "objdump" tool to run', | 302 help='Use TOOL as the "objdump" tool to run', |
| 296 metavar='TOOL') | 303 metavar='TOOL') |
| 297 parser.add_option('-L', '--library-path', dest='lib_path', | 304 parser.add_option('-L', '--library-path', dest='lib_path', |
| 298 action='append', default=[], | 305 action='append', default=[], |
| 299 help='Add DIRECTORY to library search path', | 306 help='Add DIRECTORY to library search path', |
| 300 metavar='DIRECTORY') | 307 metavar='DIRECTORY') |
| 301 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', | 308 parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', |
| 302 help='Destination directory for staging libraries', | 309 help='Destination directory for staging libraries', |
| 303 metavar='DIRECTORY') | 310 metavar='DIRECTORY') |
| 311 parser.add_option('-r', '--remove', dest='remove', |
| 312 help='Remove the prefix from the files.', |
| 313 metavar='PATH') |
| 304 (options, args) = parser.parse_args(argv) | 314 (options, args) = parser.parse_args(argv) |
| 305 | 315 |
| 306 if len(args) < 1: | 316 if len(args) < 1: |
| 307 parser.print_usage() | 317 parser.print_usage() |
| 308 sys.exit(1) | 318 sys.exit(1) |
| 309 | 319 |
| 310 nmf = NmfUtils(objdump=options.objdump, | 320 nmf = NmfUtils(objdump=options.objdump, |
| 311 main_files=args, | 321 main_files=args, |
| 312 lib_path=options.lib_path) | 322 lib_path=options.lib_path) |
| 313 | 323 |
| 314 manifest = nmf.GetManifest() | 324 manifest = nmf.GetManifest() |
| 315 | 325 |
| 316 if options.output is None: | 326 if options.output is None: |
| 317 sys.stdout.write(nmf.GetJson()) | 327 sys.stdout.write(nmf.GetJson()) |
| 318 else: | 328 else: |
| 319 with open(options.output, 'w') as output: | 329 with open(options.output, 'w') as output: |
| 320 output.write(nmf.GetJson()) | 330 output.write(nmf.GetJson()) |
| 321 | 331 |
| 322 if options.stage_dependencies: | 332 if options.stage_dependencies: |
| 323 nmf.StageDependencies(options.stage_dependencies) | 333 nmf.StageDependencies(options.stage_dependencies) |
| 324 | 334 |
| 325 | 335 |
| 326 # Invoke this file directly for simple testing. | 336 # Invoke this file directly for simple testing. |
| 327 if __name__ == '__main__': | 337 if __name__ == '__main__': |
| 328 sys.exit(Main(sys.argv[1:])) | 338 sys.exit(Main(sys.argv[1:])) |
| OLD | NEW |