import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test.gdbclientutils import *
from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase


class MyResponder(MockGDBServerResponder):
    """
    A responder which simulates a process with a single shared library loaded.
    Its parameters allow configuration of various properties of the library.
    """

    def __init__(self, testcase, triple, library_name, auxv_entry, region_info):
        MockGDBServerResponder.__init__(self)
        self.testcase = testcase
        self._triple = triple
        self._library_name = library_name
        self._auxv_entry = auxv_entry
        self._region_info = region_info

    def qSupported(self, client_supported):
        return (
            super().qSupported(client_supported)
            + ";qXfer:auxv:read+;qXfer:libraries-svr4:read+"
        )

    def qXferRead(self, obj, annex, offset, length):
        if obj == "features" and annex == "target.xml":
            return (
                """<?xml version="1.0"?>
                <target version="1.0">
                  <architecture>i386:x86-64</architecture>
                  <feature name="org.gnu.gdb.i386.core">
                    <reg name="rip" bitsize="64" regnum="0" type="code_ptr" group="general"/>
                  </feature>
                </target>""",
                False,
            )
        elif obj == "auxv":
            # 0x09 = AT_ENTRY, which lldb uses to compute the load bias of the
            # main binary.
            return (
                hex_decode_bytes(
                    self._auxv_entry
                    + "09000000000000000000ee000000000000000000000000000000000000000000"
                ),
                False,
            )
        elif obj == "libraries-svr4":
            return (
                """<?xml version="1.0"?>
                <library-list-svr4 version="1.0">
                  <library name="%s" lm="0xdeadbeef" l_addr="0xef0000" l_ld="0xdeadbeef"/>
                </library-list-svr4>"""
                % self._library_name,
                False,
            )
        else:
            return None, False

    def qfThreadInfo(self):
        return "m47"

    def qsThreadInfo(self):
        return "l"

    def qProcessInfo(self):
        return "pid:47;ptrsize:8;endian:little;triple:%s;" % hex_encode_bytes(
            self._triple
        )

    def setBreakpoint(self, packet):
        return "OK"

    def readMemory(self, addr, length):
        if addr == 0xEE1000:
            return "00" * 0x30 + "0020ee0000000000"
        elif addr == 0xEE2000:
            return "01000000000000000030ee0000000000dead00000000000000000000000000000000000000000000"
        elif addr == 0xEF0000:
            with open(self.testcase.getBuildArtifact("libmodule_load.so"), "rb") as f:
                contents = f.read(-1)
            return hex_encode_bytes(seven.bitcast_to_string(contents))
        return ("baadf00d00" * 1000)[0 : length * 2]

    def qMemoryRegionInfo(self, addr):
        if addr < 0xEE0000:
            return "start:0;size:ee0000;"
        elif addr < 0xEF0000:
            return "start:ee0000;size:10000;"
        elif addr < 0xF00000:
            return "start:ef0000;size:1000;permissions:rx;" + self._region_info
        else:
            return "start:ef1000;size:ffffffffff10f000"


class TestGdbClientModuleLoad(GDBRemoteTestBase):
    @skipIfXmlSupportMissing
    def test_android_app_process(self):
        """
        This test simulates the scenario where the (android) dynamic linker
        reports incorrect file name of the main executable. Lldb uses
        qMemoryRegionInfo to get the correct value.
        """
        region_info = "name:%s;" % (
            hex_encode_bytes(self.getBuildArtifact("libmodule_load.so"))
        )
        self.server.responder = MyResponder(
            self, "x86_64-pc-linux-android", "bogus-name", "", region_info
        )
        self.yaml2obj("module_load.yaml", self.getBuildArtifact("libmodule_load.so"))
        target = self.createTarget("module_load.yaml")

        process = self.connect(target)
        self.assertTrue(process.IsValid(), "Process is valid")

        lldbutil.expect_state_changes(
            self, self.dbg.GetListener(), process, [lldb.eStateStopped]
        )

        self.filecheck("image list", __file__, "-check-prefix=ANDROID")

    # ANDROID: [  0] {{.*}} 0x0000000000ee0000 {{.*}}module_load
    # ANDROID: [  1] {{.*}} 0x0000000000ef0000 {{.*}}libmodule_load.so

    @skipIfXmlSupportMissing
    def test_vdso(self):
        """
        This test checks vdso loading in the situation where the process does
        not have memory region information about the vdso address. This can
        happen in core files, as they don't store this data.
        We want to check that the vdso is loaded exactly once.
        """
        # vdso address
        AT_SYSINFO_EHDR = "21000000000000000000ef0000000000"
        self.server.responder = MyResponder(
            self, "x86_64-pc-linux", "linux-vdso.so.1", AT_SYSINFO_EHDR, ""
        )
        self.yaml2obj("module_load.yaml", self.getBuildArtifact("libmodule_load.so"))
        target = self.createTarget("module_load.yaml")

        process = self.connect(target)
        self.assertTrue(process.IsValid(), "Process is valid")

        lldbutil.expect_state_changes(
            self, self.dbg.GetListener(), process, [lldb.eStateStopped]
        )

        self.filecheck("image list", __file__, "-check-prefix=VDSO")
        # VDSO: [  0] {{.*}} 0x0000000000ee0000 {{.*}}module_load
        # VDSO: [  1] {{.*}} 0x0000000000ef0000 {{.*}}[vdso]
        self.assertEqual(self.target().GetNumModules(), 2)
