#!/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()
