"""
Test Unified Section List merging.
"""

import os
import shutil

import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
from lldbsuite.test.lldbutil import symbol_type_to_str


class ModuleUnifiedSectionList(TestBase):
    SHARED_BUILD_TESTCASE = False

    @skipUnlessPlatform(["linux", "freebsd", "netbsd"])
    def test_unified_section_list(self):
        self.build()
        exe = self.getBuildArtifact("a.out")
        debug_info = self.getBuildArtifact("a.out.debug")
        new_dir = os.path.join(os.path.dirname(debug_info), "new_dir")
        os.mkdir(new_dir)
        renamed_debug_info = os.path.join(new_dir, "renamed.debug")
        os.rename(debug_info, renamed_debug_info)
        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)
        self.assertGreater(target.GetNumModules(), 0)

        main_exe_module = target.GetModuleAtIndex(0)
        eh_frame = main_exe_module.FindSection(".eh_frame")
        self.assertTrue(eh_frame.IsValid())
        self.assertGreater(eh_frame.size, 0)

        # Should be stripped in main executable.
        debug_info_section = main_exe_module.FindSection(".debug_info")
        self.assertFalse(debug_info_section.IsValid())

        ci = self.dbg.GetCommandInterpreter()
        res = lldb.SBCommandReturnObject()
        ci.HandleCommand(f"target symbols add {renamed_debug_info}", res)
        self.assertTrue(res.Succeeded())

        # Should be stripped in .debuginfo but be present in main executable.
        main_exe_module = target.GetModuleAtIndex(0)
        eh_frame = main_exe_module.FindSection(".eh_frame")
        self.assertTrue(eh_frame.IsValid())
        self.assertGreater(eh_frame.size, 0)

        # Should be unified and both sections should have contents.
        debug_info_section = main_exe_module.FindSection(".debug_info")
        self.assertTrue(debug_info_section.IsValid())
        self.assertGreater(debug_info_section.file_size, 0)

    def test_unified_section_list_overwrite_larger_section(self):
        """
        Test the merging of an ELF file with another ELF File where all the new sections are bigger, validating we
        overwrite .comment from SHT_NOBITS to the new SHT_PROGBITS section and the smaller .text with the larger
        .text
        """
        exe = self.getBuildArtifact("a.out")
        self.yaml2obj("main.yaml", exe)

        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)
        main_exe_module = target.GetModuleAtIndex(0)

        # First we verify out .text section is the expected BEC0FFEE
        text_before_merge = main_exe_module.FindSection(".text")
        self.assertTrue(text_before_merge.IsValid())
        error = lldb.SBError()
        section_content = text_before_merge.data.ReadRawData(
            error, 0, text_before_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content, bytes.fromhex("BEC0FFEE"))

        # .comment in main.yaml should be SHT_NOBITS, and size 0
        comment_before_merge = main_exe_module.FindSection(".comment")
        self.assertTrue(comment_before_merge.IsValid())
        self.assertEqual(comment_before_merge.data.size, 0)

        # yamlize the main.largertext.yaml and force symbol loading
        debug_info = self.getBuildArtifact("a.out.debug")
        self.yaml2obj("main.largertext.yaml", debug_info)

        ci = self.dbg.GetCommandInterpreter()
        res = lldb.SBCommandReturnObject()
        ci.HandleCommand(f"target symbols add {debug_info}", res)
        self.assertTrue(res.Succeeded())

        # verify we took the larger .text section
        main_exe_module_after_merge = target.GetModuleAtIndex(0)
        text_after_merge = main_exe_module_after_merge.FindSection(".text")
        self.assertTrue(text_after_merge.IsValid())
        self.assertGreater(text_after_merge.data.size, text_before_merge.data.size)
        section_content_after_merge = text_after_merge.data.ReadRawData(
            error, 0, text_after_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB"))

        # in main.largertext.yaml comment is not SHT_NOBITS, and so we should see
        # the size > 0 and equal to BAADF00D
        comment_after_merge = main_exe_module_after_merge.FindSection(".comment")
        self.assertTrue(comment_after_merge.IsValid())
        comment_content_after_merge = comment_after_merge.data.ReadRawData(
            error, 0, comment_after_merge.data.size
        )

        self.assertTrue(error.Success())
        self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D"))

    def test_unified_section_list_overwrite_smaller_section(self):
        """
        Test the merging of an ELF file with another ELF File where all the existing sections are bigger, validating we don't
        overwrite with the SHT_NOBITS for .comment or the smaller .text section.
        """
        exe = self.getBuildArtifact("a.out")
        self.yaml2obj("main.largertext.yaml", exe)

        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)
        main_exe_module = target.GetModuleAtIndex(0)

        # Same as above test but inverse, verify our larger .text section
        # is the expected BEC0FFEE palindrome
        text_before_merge = main_exe_module.FindSection(".text")
        self.assertTrue(text_before_merge.IsValid())
        error = lldb.SBError()
        section_content = text_before_merge.data.ReadRawData(
            error, 0, text_before_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content, bytes.fromhex("BEC0FFEEEEFF0CEB"))

        # Comment is SHT_PROGBITS on the larger yaml and should remain
        # the same after merge.
        comment_before_merge = main_exe_module.FindSection(".comment")
        self.assertTrue(comment_before_merge.IsValid())
        comment_content = comment_before_merge.data.ReadRawData(
            error, 0, comment_before_merge.data.size
        )

        self.assertTrue(error.Success())
        self.assertEqual(comment_content, bytes.fromhex("BAADF00D"))

        debug_info = self.getBuildArtifact("a.out.debug")
        self.yaml2obj("main.yaml", debug_info)

        ci = self.dbg.GetCommandInterpreter()
        res = lldb.SBCommandReturnObject()
        ci.HandleCommand(f"target symbols add {debug_info}", res)
        self.assertTrue(res.Succeeded())

        # Verify we didn't replace the sections after merge.s
        main_exe_module_after_merge = target.GetModuleAtIndex(0)
        text_after_merge = main_exe_module_after_merge.FindSection(".text")
        self.assertTrue(text_after_merge.IsValid())
        self.assertEqual(text_after_merge.data.size, text_before_merge.data.size)
        section_content_after_merge = text_after_merge.data.ReadRawData(
            error, 0, text_after_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB"))

        comment_after_merge = main_exe_module_after_merge.FindSection(".comment")
        self.assertTrue(comment_after_merge.IsValid())
        comment_content_after_merge = comment_after_merge.data.ReadRawData(
            error, 0, comment_after_merge.data.size
        )

        self.assertTrue(error.Success())
        self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D"))

    def test_unified_section_list_overwrite_mixed_merge(self):
        """
        Test the merging of an ELF file with another ELF File where the lhs has a larger .comment section
        and the RHS has a larger .text section.
        """
        exe = self.getBuildArtifact("a.out")
        self.yaml2obj("main.largercomment.yaml", exe)

        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)
        main_exe_module = target.GetModuleAtIndex(0)

        # Verify we have the expected smaller BEC0FFEE
        text_before_merge = main_exe_module.FindSection(".text")
        self.assertTrue(text_before_merge.IsValid())
        error = lldb.SBError()
        section_content = text_before_merge.data.ReadRawData(
            error, 0, text_before_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content, bytes.fromhex("BEC0FFEE"))

        # Verify we have the larger palindromic comment
        comment_before_merge = main_exe_module.FindSection(".comment")
        self.assertTrue(comment_before_merge.IsValid())
        comment_content = comment_before_merge.data.ReadRawData(
            error, 0, comment_before_merge.data.size
        )

        self.assertTrue(error.Success())
        self.assertEqual(comment_content, bytes.fromhex("BAADF00DF00DBAAD"))

        debug_info = self.getBuildArtifact("a.out.debug")
        self.yaml2obj("main.largertext.yaml", debug_info)

        ci = self.dbg.GetCommandInterpreter()
        res = lldb.SBCommandReturnObject()
        ci.HandleCommand(f"target symbols add {debug_info}", res)
        self.assertTrue(res.Succeeded())

        # Verify we replaced .text
        main_exe_module_after_merge = target.GetModuleAtIndex(0)
        text_after_merge = main_exe_module_after_merge.FindSection(".text")
        self.assertTrue(text_after_merge.IsValid())
        section_content_after_merge = text_after_merge.data.ReadRawData(
            error, 0, text_after_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB"))

        # Verify .comment is still the same.
        comment_after_merge = main_exe_module_after_merge.FindSection(".comment")
        self.assertTrue(comment_after_merge.IsValid())
        comment_content_after_merge = comment_after_merge.data.ReadRawData(
            error, 0, comment_after_merge.data.size
        )

        self.assertTrue(error.Success())
        self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00DF00DBAAD"))

    def test_unified_section_list_overwrite_equal_size(self):
        """
        Test the merging of an ELF file with an ELF file with sections of the same size with different values
        .text
        """
        exe = self.getBuildArtifact("a.out")
        self.yaml2obj("main.yaml", exe)

        target = self.dbg.CreateTarget(exe)
        self.assertTrue(target, VALID_TARGET)
        main_exe_module = target.GetModuleAtIndex(0)

        # First we verify out .text section is the expected BEC0FFEE
        text_before_merge = main_exe_module.FindSection(".text")
        self.assertTrue(text_before_merge.IsValid())
        error = lldb.SBError()
        section_content = text_before_merge.data.ReadRawData(
            error, 0, text_before_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content, bytes.fromhex("BEC0FFEE"))

        # .comment in main.yaml should be SHT_NOBITS, and size 0
        comment_before_merge = main_exe_module.FindSection(".comment")
        self.assertTrue(comment_before_merge.IsValid())
        self.assertEqual(comment_before_merge.data.size, 0)

        # yamlize the main with the .text reversed from BEC0FFEE
        # to EEFF0CEB. We should still keep our .text with BEC0FFEE
        debug_info = self.getBuildArtifact("a.out.debug")
        self.yaml2obj("main.reversedtext.yaml", debug_info)

        ci = self.dbg.GetCommandInterpreter()
        res = lldb.SBCommandReturnObject()
        ci.HandleCommand(f"target symbols add {debug_info}", res)
        self.assertTrue(res.Succeeded())

        # verify .text did not change
        main_exe_module_after_merge = target.GetModuleAtIndex(0)
        text_after_merge = main_exe_module_after_merge.FindSection(".text")
        self.assertTrue(text_after_merge.IsValid())
        section_content_after_merge = text_after_merge.data.ReadRawData(
            error, 0, text_after_merge.data.size
        )
        self.assertTrue(error.Success())
        self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEE"))

        # verify comment did not change
        comment_afer_merge = main_exe_module_after_merge.FindSection(".comment")
        self.assertTrue(comment_afer_merge.IsValid())
        self.assertEqual(comment_afer_merge.data.size, 0)
