from .sections import Section
from .segments import Segment
from ..common.exceptions import ELFError
-from ..common.utils import struct_parse
+from ..common.utils import struct_parse, parse_cstring_from_stream
+
+
+class _DynamicStringTable(object):
+ """ Bare string table based on values found via ELF dynamic tags and
+ loadable segments only. Good enough for get_string() only.
+ """
+ def __init__(self, stream, table_offset):
+ self._stream = stream
+ self._table_offset = table_offset
+
+ def get_string(self, offset):
+ """ Get the string stored at the given offset in this string table.
+ """
+ return parse_cstring_from_stream(self._stream,
+ self._table_offset + offset)
class DynamicTag(object):
self._num_tags = -1
self._offset = position
self._tagsize = self._elfstructs.Elf_Dyn.sizeof()
+
+ # Do not access this directly yourself; use _get_stringtable() instead.
self._stringtable = stringtable
- def iter_tags(self, type=None):
- """ Yield all tags (limit to |type| if specified)
+ def _get_stringtable(self):
+ """ Return a string table for looking up dynamic tag related strings.
+
+ This won't be a "full" string table object, but will at least
+ support the get_string() function.
+ """
+ if self._stringtable:
+ return self._stringtable
+
+ # If the ELF has stripped its section table (which is unusual, but
+ # perfectly valid), we need to use the dynamic tags to locate the
+ # dynamic string table.
+ strtab = None
+ for tag in self._iter_tags(type='DT_STRTAB'):
+ strtab = tag['d_val']
+ break
+ # If we found a dynamic string table, locate the offset in the file
+ # by using the program headers.
+ if strtab:
+ table_offset = next(self._elffile.address_offsets(strtab), None)
+ if table_offset is not None:
+ self._stringtable = _DynamicStringTable(self._stream, table_offset)
+ return self._stringtable
+
+ # That didn't work for some reason. Let's use the section header
+ # even though this ELF is super weird.
+ self._stringtable = self._elffile.get_section_by_name(b'.dynstr')
+ return self._stringtable
+
+ def _iter_tags(self, type=None):
+ """ Yield all raw tags (limit to |type| if specified)
"""
for n in itertools.count():
- tag = self.get_tag(n)
- if type is None or tag.entry.d_tag == type:
+ tag = self._get_tag(n)
+ if type is None or tag['d_tag'] == type:
yield tag
- if tag.entry.d_tag == 'DT_NULL':
+ if tag['d_tag'] == 'DT_NULL':
break
- def get_tag(self, n):
- """ Get the tag at index #n from the file (DynamicTag object)
+ def iter_tags(self, type=None):
+ """ Yield all tags (limit to |type| if specified)
+ """
+ for tag in self._iter_tags(type=type):
+ yield DynamicTag(tag, self._get_stringtable())
+
+ def _get_tag(self, n):
+ """ Get the raw tag at index #n from the file
"""
offset = self._offset + n * self._tagsize
- entry = struct_parse(
+ return struct_parse(
self._elfstructs.Elf_Dyn,
self._stream,
stream_pos=offset)
- return DynamicTag(entry, self._stringtable)
+
+ def get_tag(self, n):
+ """ Get the tag at index #n from the file (DynamicTag object)
+ """
+ return DynamicTag(self._get_tag(n), self._get_stringtable())
def num_tags(self):
""" Number of dynamic tags in the file
import unittest
import os
-from utils import setup_syspath; setup_syspath()
+from utils import setup_syspath
+setup_syspath()
+from elftools.elf.elffile import ELFFile
from elftools.common.exceptions import ELFError
from elftools.elf.dynamic import DynamicTag
class TestDynamicTag(unittest.TestCase):
+ """Tests for the DynamicTag class."""
+
def test_requires_stringtable(self):
with self.assertRaises(ELFError):
dt = DynamicTag('', None)
+class TestDynamic(unittest.TestCase):
+ """Tests for the Dynamic class."""
+
+ def test_missing_sections(self):
+ """Verify we can get dynamic strings w/out section headers"""
+
+ libs = []
+ with open(os.path.join('test', 'testfiles_for_unittests',
+ 'aarch64_super_stripped.elf'), 'rb') as f:
+ elf = ELFFile(f)
+ for segment in elf.iter_segments():
+ if segment.header.p_type != 'PT_DYNAMIC':
+ continue
+
+ for t in segment.iter_tags():
+ if t.entry.d_tag == 'DT_NEEDED':
+ libs.append(t.needed.decode('utf-8'))
+
+ exp = ['libc.so.6']
+ self.assertEqual(libs, exp)
+
+
if __name__ == '__main__':
unittest.main()