chromium/tools/android/elf_compression/test/elf_headers_test.py

#!/usr/bin/env python3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import pathlib
import sys
import unittest

sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
import elf_headers

ELF_HEADER_TEST_LIBRARY = 'testdata/lib.so'


class ElfHeaderTest(unittest.TestCase):

  def setUp(self):
    super(ElfHeaderTest, self).setUp()
    script_dir = os.path.dirname(os.path.abspath(__file__))
    self.library_path = os.path.join(script_dir, ELF_HEADER_TEST_LIBRARY)

  def testElfHeaderParsing(self):
    with open(self.library_path, 'rb') as f:
      data = f.read()
    elf = elf_headers.ElfHeader(data)
    # Validating all of the ELF header fields.
    self.assertEqual(elf.e_type, elf_headers.ElfHeader.EType.ET_DYN)
    self.assertEqual(elf.e_entry, 0x1040)
    self.assertEqual(elf.e_phoff, 64)
    self.assertEqual(elf.e_shoff, 14136)
    self.assertEqual(elf.e_flags, 0)
    self.assertEqual(elf.e_ehsize, 64)
    self.assertEqual(elf.e_phentsize, 56)
    self.assertEqual(elf.e_phnum, 8)
    self.assertEqual(elf.e_shentsize, 64)
    self.assertEqual(elf.e_shnum, 26)
    self.assertEqual(elf.e_shstrndx, 25)
    # Validating types and amount of all segments excluding GNU specific ones.
    phdrs = elf.GetProgramHeaders()
    self.assertEqual(len(phdrs), 8)

    phdr_types = [
        elf_headers.ProgramHeader.Type.PT_LOAD,
        elf_headers.ProgramHeader.Type.PT_LOAD,
        elf_headers.ProgramHeader.Type.PT_LOAD,
        elf_headers.ProgramHeader.Type.PT_LOAD,
        elf_headers.ProgramHeader.Type.PT_DYNAMIC,
        None,  # Non-standard segment: GNU_EH_FRAME
        None,  # Non-standard segment: GNU_STACK
        None,  # Non-standard segment: GNU_RELRO
    ]
    for i in range(0, len(phdrs)):
      if phdr_types[i] is not None:
        self.assertEqual(phdrs[i].p_type, phdr_types[i])

    # Validating all of the fields of the first segment.
    load_phdr = phdrs[0]
    self.assertEqual(load_phdr.p_offset, 0x0)
    self.assertEqual(load_phdr.p_vaddr, 0x0)
    self.assertEqual(load_phdr.p_paddr, 0x0)
    self.assertEqual(load_phdr.p_filesz, 0x468)
    self.assertEqual(load_phdr.p_memsz, 0x468)
    self.assertEqual(load_phdr.p_flags, 0b100)
    self.assertEqual(load_phdr.p_align, 0x1000)
    # Validating offsets of the second segment
    load_phdr = phdrs[1]
    self.assertEqual(load_phdr.p_offset, 0x1000)
    self.assertEqual(load_phdr.p_vaddr, 0x1000)
    self.assertEqual(load_phdr.p_paddr, 0x1000)

    # Validating types and amount of sections excluding GNU ones.
    shdrs = elf.GetSectionHeaders()
    self.assertEqual(len(shdrs), 26)

    shdr_types = [
        elf_headers.SectionHeader.Type.SHT_NULL,
        elf_headers.SectionHeader.Type.SHT_HASH,
        None,  # Non-standard section: GNU_HASH
        elf_headers.SectionHeader.Type.SHT_DYNSYM,
        elf_headers.SectionHeader.Type.SHT_STRTAB,
        None,  # Non-standard section: VERSYM
        None,  # Non-standard section: VERNEED
        elf_headers.SectionHeader.Type.SHT_RELA,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        None,  # Non-standard section: INIT_ARRAY
        None,  # Non-standard section: FINI_ARRAY
        elf_headers.SectionHeader.Type.SHT_DYNAMIC,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_NOBITS,
        elf_headers.SectionHeader.Type.SHT_PROGBITS,
        elf_headers.SectionHeader.Type.SHT_SYMTAB,
        elf_headers.SectionHeader.Type.SHT_STRTAB,
        elf_headers.SectionHeader.Type.SHT_STRTAB,
    ]
    for i in range(0, len(shdrs)):
      if shdr_types[i] is not None:
        self.assertEqual(shdrs[i].sh_type, shdr_types[i])

    # Validate all fields of the first and second section, since the first one
    # is NULL section.
    shdr = shdrs[0]
    self.assertEqual(shdr.sh_flags, 0)
    self.assertEqual(shdr.sh_addr, 0x0)
    self.assertEqual(shdr.sh_offset, 0x0)
    self.assertEqual(shdr.sh_size, 0x0)
    self.assertEqual(shdr.sh_link, 0)
    self.assertEqual(shdr.sh_info, 0)
    self.assertEqual(shdr.sh_addralign, 0)
    self.assertEqual(shdr.sh_entsize, 0)

    shdr = shdrs[1]
    self.assertEqual(shdr.sh_flags, 2)
    self.assertEqual(shdr.sh_addr, 0x200)
    self.assertEqual(shdr.sh_offset, 0x200)
    self.assertEqual(shdr.sh_size, 0x30)
    self.assertEqual(shdr.sh_link, 3)
    self.assertEqual(shdr.sh_info, 0)
    self.assertEqual(shdr.sh_addralign, 8)
    self.assertEqual(shdr.sh_entsize, 4)

  def testElfHeaderSectionNames(self):
    """Test that the section names are correctly resolved"""
    with open(self.library_path, 'rb') as f:
      data = f.read()
    elf = elf_headers.ElfHeader(data)

    section_names = [
        '',
        '.hash',
        '.gnu.hash',
        '.dynsym',
        '.dynstr',
        '.gnu.version',
        '.gnu.version_r',
        '.rela.dyn',
        '.init',
        '.plt',
        '.plt.got',
        '.text',
        '.fini',
        '.eh_frame_hdr',
        '.eh_frame',
        '.init_array',
        '.fini_array',
        '.dynamic',
        '.got',
        '.got.plt',
        '.data',
        '.bss',
        '.comment',
        '.symtab',
        '.strtab',
        '.shstrtab',
    ]
    shdrs = elf.GetSectionHeaders()
    for i in range(0, len(shdrs)):
      self.assertEqual(shdrs[i].GetStrName(), section_names[i])

  def testElfHeaderNoopPatching(self):
    """Patching the ELF without any changes."""
    with open(self.library_path, 'rb') as f:
      data = bytearray(f.read())
    data_copy = data[:]
    elf = elf_headers.ElfHeader(data)
    elf.PatchData(data)
    self.assertEqual(data, data_copy)

  def testElfHeaderPatchingAndParsing(self):
    """Patching the ELF and validating that it worked."""
    with open(self.library_path, 'rb') as f:
      data = bytearray(f.read())
    elf = elf_headers.ElfHeader(data)
    # Changing some values.
    elf.e_ehsize = 42
    elf.GetProgramHeaders()[0].p_align = 1
    elf.GetProgramHeaders()[0].p_filesz = 10
    elf.PatchData(data)

    updated_elf = elf_headers.ElfHeader(data)
    # Validating all of the ELF header fields.
    self.assertEqual(updated_elf.e_type, elf.e_type)
    self.assertEqual(updated_elf.e_entry, elf.e_entry)
    self.assertEqual(updated_elf.e_phoff, elf.e_phoff)
    self.assertEqual(updated_elf.e_shoff, elf.e_shoff)
    self.assertEqual(updated_elf.e_flags, elf.e_flags)
    self.assertEqual(updated_elf.e_ehsize, 42)
    self.assertEqual(updated_elf.e_phentsize, elf.e_phentsize)
    self.assertEqual(updated_elf.e_phnum, elf.e_phnum)
    self.assertEqual(updated_elf.e_shentsize, elf.e_shentsize)
    self.assertEqual(updated_elf.e_shnum, elf.e_shnum)
    self.assertEqual(updated_elf.e_shstrndx, elf.e_shstrndx)

    # Validating all of the fields of the first segment.
    load_phdr = elf.GetProgramHeaders()[0]
    updated_load_phdr = updated_elf.GetProgramHeaders()[0]

    self.assertEqual(updated_load_phdr.p_offset, load_phdr.p_offset)
    self.assertEqual(updated_load_phdr.p_vaddr, load_phdr.p_vaddr)
    self.assertEqual(updated_load_phdr.p_paddr, load_phdr.p_paddr)
    self.assertEqual(updated_load_phdr.p_filesz, 10)
    self.assertEqual(updated_load_phdr.p_memsz, load_phdr.p_memsz)
    self.assertEqual(updated_load_phdr.p_flags, load_phdr.p_flags)
    self.assertEqual(updated_load_phdr.p_align, 1)


if __name__ == '__main__':
  unittest.main()