"""
Test lldb's support for the AArch64 Permission Overlay extension (POE), which
is used to implement Linux's memory protection keys feature.
"""

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


class AArch64LinuxPOE(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    EXPECTED_POR = "por = 0x0000000001234567"
    EXPECTED_POR_FIELDS = (
        "         = {\n"
        "             Perm15 = No Access\n"
        "             Perm14 = No Access\n"
        "             Perm13 = No Access\n"
        "             Perm12 = No Access\n"
        "             Perm11 = No Access\n"
        "             Perm10 = No Access\n"
        "             Perm9 = No Access\n"
        "             Perm8 = No Access\n"
        "             Perm7 = No Access\n"
        "             Perm6 = Read\n"
        "             Perm5 = Execute\n"
        "             Perm4 = Read, Execute\n"
        "             Perm3 = Write\n"
        "             Perm2 = Write, Read\n"
        "             Perm1 = Write, Execute\n"
        "             Perm0 = Read, Write, Execute\n"
        "           }"
    )

    @skipUnlessArch("aarch64")
    @skipUnlessPlatform(["linux"])
    def test_poe_live(self):
        if not self.isAArch64POE():
            self.skipTest("POE must be present.")

        self.build()
        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

        lldbutil.run_break_set_by_file_and_line(
            self,
            "main.c",
            line_number("main.c", "// Set break point at this line."),
            num_expected_locations=1,
        )

        self.runCmd("run", RUN_SUCCEEDED)

        if self.process().GetState() == lldb.eStateExited:
            self.fail("Test program failed to run.")

        self.expect(
            "thread list",
            STOPPED_DUE_TO_BREAKPOINT,
            substrs=["stopped", "stop reason = breakpoint"],
        )

        self.expect(
            "register read --all",
            substrs=[
                "Permission Overlay Registers",
                f"{self.EXPECTED_POR}",
            ],
        )

        if self.hasXMLSupport():
            self.expect(
                "register read por",
                substrs=[f"     {self.EXPECTED_POR}\n" + self.EXPECTED_POR_FIELDS],
            )

        # POR should be restored after expression evaluation.
        self.expect("expression expr_function()", substrs=["$0 = 1"])
        self.expect("register read por", substrs=[self.EXPECTED_POR])

        # Not passing this to the application allows us to fix the permissions
        # using lldb, then continue to a normal exit.
        self.runCmd("process handle SIGSEGV --pass false")

        self.expect(
            "continue",
            substrs=[
                "stop reason = signal SIGSEGV: failed protection key checks (fault address="
            ],
        )

        # This fault should have happened due to the write, not the read.
        self.assertEqual(
            self.dbg.GetSelectedTarget()
            .GetProcess()
            .GetSelectedThread()
            .GetSelectedFrame()
            .GetFunctionName(),
            "cause_write_fault",
        )

        # Allow writes so we can continue. This value has permission 6 changed
        # from read only (1) to write (4).
        self.runCmd("register write por 0x4234567")

        self.expect("continue", substrs=["exited with status = 0"])

    @skipIfLLVMTargetMissing("AArch64")
    def test_poe_core(self):
        # Core was generated by running the test program on a system with POE
        # and coredump_filter set to 0.
        self.runCmd("target create --core corefile")

        self.expect("process status", substrs=["SIGSEGV: failed protection key checks"])

        self.expect(
            "register read --all",
            substrs=[
                "Permission Overlay Registers",
                f"{self.EXPECTED_POR}",
            ],
        )

        if self.hasXMLSupport():
            self.expect(
                "register read por",
                substrs=[f"     {self.EXPECTED_POR}\n" + self.EXPECTED_POR_FIELDS],
            )
