"""
Test that memory tagging features work with Linux core files.
"""

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


class AArch64LinuxMTEMemoryTagCoreFileTestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    MTE_BUF_ADDR = hex(0xFFFFA733B000)
    BUF_ADDR = hex(0xFFFFA733A000)

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_tag_core_file_memory_region(self):
        """Test that memory regions are marked as tagged when there is a tag
        segment in the core file."""
        self.runCmd("target create --core core.mte")

        # There should only be one tagged region.
        self.runCmd("memory region --all")
        got = self.res.GetOutput()
        found_tagged_region = False

        for line in got.splitlines():
            if "memory tagging: enabled" in line:
                if found_tagged_region:
                    self.fail("Expected only one tagged region.")
                found_tagged_region = True

        self.assertTrue(found_tagged_region, "Did not find a tagged memory region.")

        # mte_buf is tagged, buf is not.
        tagged = "memory tagging: enabled"
        self.expect("memory region {}".format(self.MTE_BUF_ADDR), patterns=[tagged])
        self.expect(
            "memory region {}".format(self.BUF_ADDR), patterns=[tagged], matching=False
        )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_tag_core_file_tag_write(self):
        """Test that "memory tag write" does not work with core files
        as they are read only."""
        self.runCmd("target create --core core.mte")

        self.expect(
            "memory tag write {} 1".format(self.MTE_BUF_ADDR),
            error=True,
            patterns=["error: elf-core does not support writing memory tags"],
        )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_tag_core_file_tag_read(self):
        """Test that "memory tag read" works with core files."""
        self.runCmd("target create --core core.mte")

        # Tags are packed 2 per byte meaning that in addition to granule alignment
        # there is also 2 x granule alignment going on.

        # All input validation should work as normal.
        not_tagged_pattern = (
            "error: Address range 0x[A-Fa-f0-9]+:0x[A-Fa-f0-9]+ "
            "is not in a memory tagged region"
        )
        self.expect(
            "memory tag read {}".format(self.BUF_ADDR),
            error=True,
            patterns=[not_tagged_pattern],
        )
        # The first part of this range is not tagged.
        self.expect(
            "memory tag read {addr}-16 {addr}+16".format(addr=self.MTE_BUF_ADDR),
            error=True,
            patterns=[not_tagged_pattern],
        )
        # The last part of this range is not tagged.
        self.expect(
            "memory tag read {addr}+4096-16 {addr}+4096+16".format(
                addr=self.MTE_BUF_ADDR
            ),
            error=True,
            patterns=[not_tagged_pattern],
        )

        self.expect(
            "memory tag read {addr}+16 {addr}".format(addr=self.MTE_BUF_ADDR),
            error=True,
            patterns=[
                r"error: End address \(0x[A-Fa-f0-9]+\) "
                "must be greater than the start address "
                r"\(0x[A-Fa-f0-9]+\)"
            ],
        )

        # The simplest scenario. 2 granules means 1 byte of packed tags
        # with no realignment required.
        self.expect(
            "memory tag read {addr} {addr}+32".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\\): 0x0\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)$"
            ],
        )

        # Here we want just one tag so must use half of the first byte.
        # (start is aligned length is not)
        self.expect(
            "memory tag read {addr} {addr}+16".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n" r"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"
            ],
        )
        # Get the other half of the first byte.
        # (end is aligned start is not)
        self.expect(
            "memory tag read {addr}+16 {addr}+32".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                r"\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\): 0x1 \(mismatch\)$"
            ],
        )

        # Same thing but with a starting range > 1 granule.
        self.expect(
            "memory tag read {addr} {addr}+48".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\\): 0x0\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\\): 0x2 \\(mismatch\\)$"
            ],
        )
        self.expect(
            "memory tag read {addr}+16 {addr}+64".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\\): 0x2 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\\): 0x3 \\(mismatch\\)$"
            ],
        )
        # Here both start and end are unaligned.
        self.expect(
            "memory tag read {addr}+16 {addr}+80".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\\): 0x2 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+30, 0x[A-Fa-f0-9]+40\\): 0x3 \\(mismatch\\)\n"
                "\\[0x[A-Fa-f0-9]+40, 0x[A-Fa-f0-9]+50\\): 0x4 \\(mismatch\\)$"
            ],
        )

        # For the initial alignment of start/end to granule boundaries the tag manager
        # is used, so this reads 1 tag as it would normally.
        self.expect(
            "memory tag read {addr} {addr}+1".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n" r"\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\): 0x0$"
            ],
        )

        # This range is aligned to granules as mte_buf to mte_buf+32 so the result
        # should be 2 granules.
        self.expect(
            "memory tag read {addr} {addr}+17".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\\): 0x0\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)$"
            ],
        )

        # Alignment of this range causes it to become unaligned to 2*granule boundaries.
        self.expect(
            "memory tag read {addr} {addr}+33".format(addr=self.MTE_BUF_ADDR),
            patterns=[
                "Allocation tags:\n"
                "\\[0x[A-Fa-f0-9]+00, 0x[A-Fa-f0-9]+10\\): 0x0\n"
                "\\[0x[A-Fa-f0-9]+10, 0x[A-Fa-f0-9]+20\\): 0x1 \\(mismatch\\)\n",
                "\\[0x[A-Fa-f0-9]+20, 0x[A-Fa-f0-9]+30\\): 0x2 \\(mismatch\\)$",
            ],
        )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_commands_no_mte(self):
        """Test that memory tagging commands fail on an AArch64 corefile without
        any tag segments."""

        self.runCmd("target create --core core.nomte")

        self.expect(
            "memory tag read 0 1",
            substrs=["error: Process does not support memory tagging"],
            error=True,
        )
        # Note that this tells you memory tagging is not supported at all, versus
        # the MTE core file which does support it but does not allow writing tags.
        self.expect(
            "memory tag write 0 1",
            substrs=["error: Process does not support memory tagging"],
            error=True,
        )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_tag_fault_reason(self):
        """Test that we correctly report the fault reason."""
        self.runCmd("target create --core core.mte")

        # There is no fault address shown here because core files do not include
        # si_addr.
        self.expect(
            "bt",
            substrs=[
                "* thread #1, name = 'a.out.mte', stop reason = SIGSEGV: sync tag check fault (fault address=0xffffa733b010)"
            ],
        )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_ctrl_register(self):
        """Test that we correctly report the mte_ctrl register"."""
        # The register is present even if MTE is not used in the current process
        # and also on targets without MTE, because it controls parts of the
        # overall tagged address ABI as well.
        self.runCmd("target create --core core.nomte")
        self.expect("register read mte_ctrl", substrs=["mte_ctrl = 0x0000000000000000"])

        self.runCmd("target create --core core.mte")
        # The expected value is:
        # * Allowed tags value of 0xFFFF, shifted up by 3 resulting in 0x7fff8.
        # * Bit 19 set to 0, which means that store only checking is disabled.
        # * Bit 1 set to enable synchronous tag faults.
        # * Bit 0 set to enable the tagged address ABI.
        expected = ["mte_ctrl = 0x000000000007fffb"]

        if self.hasXMLSupport():
            expected.append(
                "(STORE_ONLY = 0, TAGS = 65535, TCF = TCF_SYNC, TAGGED_ADDR_ENABLE = 1)"
            )

        self.expect("register read mte_ctrl", substrs=expected)

        if self.hasXMLSupport():
            # Should get enumerator descriptions for TCF
            self.expect(
                "register info mte_ctrl",
                substrs=[
                    "TCF: 0 = TCF_NONE, 1 = TCF_SYNC, 2 = TCF_ASYNC, 3 = TCF_ASYMM"
                ],
            )

    @skipIfLLVMTargetMissing("AArch64")
    def test_mte_no_tags(self):
        """Test that we handle there being a tag segment but that segment does
        not contain any tag data. This can happen when the core is dumped
        with a restrictive limit or filter."""
        self.runCmd("target create --core core.mte.notags")

        mte_buf_addr = 0xFFFFA4AF3000

        # We can see which memory was tagged.
        self.expect(
            f"memory region {mte_buf_addr}", substrs=["memory tagging: enabled"]
        )

        # We cannot read those tags.
        self.expect(
            f"memory tag read {mte_buf_addr}",
            substrs=[
                "Could not read tags from core file segment. Segment is missing some or all tag data."
            ],
            error=True,
        )
