'''
A reader of the dynamic section of an ELF shared object: the names it
needs (``DT_NEEDED``), its own name (``DT_SONAME``) and its search paths
(``DT_RPATH``, ``DT_RUNPATH``). It reads what ``readelf -d`` prints for
those tags, without the binutils.
The loader of the C++ backend reads the needed names of an object before it
opens the object: a compiled module names the objects of its imports by
their SONAMEs (see ``toolchain.soname``), and the modules must be loaded
before the dynamic linker looks for the names. The tests read the SONAME
and the needed names of a compiled module.
The reader finds the dynamic section through the program headers
(``PT_DYNAMIC``), as the dynamic linker does, and the strings through the
loadable segment that maps the string table. So it reads an object whose
section headers were stripped (``strip --strip-section-headers``), which
the dynamic linker still loads. It reads the header, the program headers
and the two sections with seeks, not the whole file.
The reader handles the 64-bit format of either byte order, which is what
the C++ compiler writes on the platforms Sprite supports.
'''
import struct
__all__ = ['DynamicSection', 'read_dynamic']
# The tags of the dynamic section that name a string, and the two that
# locate the string table.
DT_NULL = 0
DT_NEEDED = 1
DT_STRTAB = 5
DT_STRSZ = 10
DT_SONAME = 14
DT_RPATH = 15
DT_RUNPATH = 29
# The types of the program headers.
PT_LOAD = 1
PT_DYNAMIC = 2
# The size of an entry of the dynamic section: a tag and a value.
DYN_ENTRY_SIZE = 16
[docs]
class DynamicSection(object):
'''The string entries of the dynamic section of a shared object.'''
def __init__(self, needed, soname, rpath, runpath):
self.needed = needed # the DT_NEEDED names, in order
self.soname = soname # the DT_SONAME, or None
self.rpath = rpath # the DT_RPATH, or None
self.runpath = runpath # the DT_RUNPATH, or None
def __repr__(self):
return 'DynamicSection(needed=%r, soname=%r, rpath=%r, runpath=%r)' % (
self.needed, self.soname, self.rpath, self.runpath
)
def _read(stream, offset, size, what, filename):
'''Reads ``size`` bytes at ``offset``; ValueError when the file is short.'''
stream.seek(offset)
data = stream.read(size)
if len(data) != size:
raise ValueError('%r is truncated: %s ends past the file' % (filename, what))
return data
[docs]
def read_dynamic(filename):
'''
Reads the dynamic section of the shared object ``filename``. Returns a
DynamicSection.
Raises:
ValueError: the file is not a 64-bit ELF file, or has no dynamic
section (a relocatable or a static object), or is truncated.
OSError: the file cannot be read.
'''
with open(filename, 'rb') as stream:
header = stream.read(64)
if header[:4] != b'\x7fELF':
raise ValueError('%r is not an ELF file' % filename)
if len(header) < 64 or header[4] != 2:
raise ValueError('%r is not a 64-bit ELF file' % filename)
if header[5] not in (1, 2):
raise ValueError('%r has an unknown byte order' % filename)
order = '<' if header[5] == 1 else '>'
# The program header table: its offset, the size of an entry, and the
# number of entries, from the ELF header.
phoff, = struct.unpack_from(order + 'Q', header, 0x20)
phentsize, phnum = struct.unpack_from(order + 'HH', header, 0x36)
if phoff == 0 or phnum == 0:
raise ValueError('%r has no program headers' % filename)
table = _read(stream, phoff, phentsize * phnum, 'the program headers', filename)
loads, dynamic = [], None
for i in range(phnum):
# p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz,
# p_align.
p_type, _, p_offset, p_vaddr, _, p_filesz, _, _ = struct.unpack_from(
order + 'IIQQQQQQ', table, i * phentsize
)
if p_type == PT_LOAD:
loads.append((p_vaddr, p_offset, p_filesz))
elif p_type == PT_DYNAMIC and dynamic is None:
dynamic = (p_offset, p_filesz)
if dynamic is None:
raise ValueError('%r has no dynamic section' % filename)
entries = []
data = _read(stream, dynamic[0], dynamic[1], 'the dynamic section', filename)
for at in range(0, len(data) - DYN_ENTRY_SIZE + 1, DYN_ENTRY_SIZE):
tag, value = struct.unpack_from(order + 'qQ', data, at)
if tag == DT_NULL:
break
entries.append((tag, value))
tags = dict(entries)
if DT_STRTAB not in tags or DT_STRSZ not in tags:
raise ValueError('%r has no dynamic string table' % filename)
# DT_STRTAB is a virtual address; the segment that maps it gives the
# offset in the file.
strtab_vaddr, strtab_size = tags[DT_STRTAB], tags[DT_STRSZ]
for p_vaddr, p_offset, p_filesz in loads:
if p_vaddr <= strtab_vaddr < p_vaddr + p_filesz:
strtab_offset = p_offset + strtab_vaddr - p_vaddr
break
else:
raise ValueError('%r maps no dynamic string table' % filename)
strtab = _read(stream, strtab_offset, strtab_size, 'the string table', filename)
def string(at):
end = strtab.find(b'\0', at)
return strtab[at:end if end >= 0 else None].decode('utf-8', 'replace')
needed, soname, rpath, runpath = [], None, None, None
for tag, value in entries:
if tag == DT_NEEDED:
needed.append(string(value))
elif tag == DT_SONAME:
soname = string(value)
elif tag == DT_RPATH:
rpath = string(value)
elif tag == DT_RUNPATH:
runpath = string(value)
return DynamicSection(needed, soname, rpath, runpath)