blob: 3c3772f44017d6c20fe0e183b534dd85e8a6926d [file] [log] [blame]
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Tool for reading archive (.a) files
# For information about the archive file format, see:
import driver_log
import elftools
import pathtools
# See above link to wiki entry on archive format.
AR_MAGIC = b'!<arch>\n'
# Thin archives are like normal archives except that there are only
# indirect references to each member (the data is not embedded).
# See manpage for a description of this.
THIN_MAGIC = b'!<thin>\n'
# filetype.IsArchive calls this IsArchive. Top-level tools should prefer
# filetype.IsArchive, both for consistency (i.e., all checks for file type come
# from that library), and because its results are cached.
def IsArchive(filename):
fp = driver_log.DriverOpen(filename, "rb")
magic =
return magic in [AR_MAGIC, THIN_MAGIC]
def GetMemberFilename(member, strtab_data):
""" Get the real filename of the archive member. """
if not member.is_long_name:
# GNU style long filenames are /[index]
# where index is a position within the strtab_data.
# Filter out non-digits
name = ''.join([c for c in if c.isdigit()])
name_index = int(name)
name_data = strtab_data[name_index:]
name_data = name_data.split('\n', 2)[0]
assert (name_data.endswith('/'))
return name_data[:-1]
def GetThinArchiveData(archive_filename, member, strtab_data):
# Get member's filename (relative to the archive) and open the member
# ourselves to check the data.
member_filename = GetMemberFilename(member, strtab_data)
member_filename = pathtools.join(
member_fp = driver_log.DriverOpen(member_filename, 'rb')
data =
return data
def GetArchiveType(filename):
fp = driver_log.DriverOpen(filename, "rb")
# Read the archive magic header
magic =
assert(magic in [AR_MAGIC, THIN_MAGIC])
# Find a regular file or symbol table
empty_file = True
found_type = ''
strtab_data = b''
while not found_type:
member = MemberHeader(fp)
if member.error == 'EOF':
elif member.error:
driver_log.Log.Fatal("%s: %s", filename, member.error)
empty_file = False
if member.is_regular_file:
if not magic == THIN_MAGIC:
data =
# For thin archives, do not read the data section.
# We instead must get at the member indirectly.
data = GetThinArchiveData(filename, member, strtab_data)
if data.startswith(b'BC'):
found_type = 'archive-bc'
elf_header = elftools.DecodeELFHeader(data, filename)
if elf_header:
found_type = 'archive-%s' % elf_header.arch
elif member.is_strtab:
# We need the strtab data to get long filenames.
data =
strtab_data = data
# Other symbol tables we can just skip ahead.
data =
if empty_file:
# Empty archives are treated as bitcode ones.
found_type = 'archive-bc'
elif not found_type:
driver_log.Log.Fatal("%s: Unable to determine archive type", filename)
return found_type
class MemberHeader(object):
def __init__(self, fp):
self.error = ''
header =
if len(header) == 0:
self.error = "EOF"
if len(header) != 60:
self.error = 'Short count reading archive member header'
return = header[0:16]
self.size = header[48:48 + 10]
self.fmag = header[58:60]
if self.fmag != b'`\n':
self.error = 'Invalid archive member header magic string %s' % header
self.size = int(self.size)
self.is_svr4_symtab = ( == b'/ ')
self.is_llvm_symtab = ( == b'#_LLVM_SYM_TAB_#')
self.is_bsd4_symtab = ( == b'__.SYMDEF SORTED')
self.is_strtab = ( == b'// ')
self.is_regular_file = not (self.is_svr4_symtab or
self.is_llvm_symtab or
self.is_bsd4_symtab or
# BSD style long names (not supported)
self.error = "BSD-style long file names not supported"
# If it's a GNU long filename, note this. We use this for thin archives.
self.is_long_name = (self.is_regular_file and'/'))
if self.is_regular_file and not self.is_long_name:
# Filenames end with '/' and are padded with spaces up to 16 bytes =[:-1]