diff options
author | David Spickett <david.spickett@linaro.org> | 2022-07-11 13:26:55 +0100 |
---|---|---|
committer | David Spickett <david.spickett@linaro.org> | 2022-07-26 08:46:36 +0100 |
commit | 2f9fa9ef5387de3d87b0c866c678d93695c1c1f3 (patch) | |
tree | 4890ade4d90ace163f613d285cb67b4d3c51c243 /lldb/test | |
parent | 4075a811ad99b7e263b8b99954cef8c96b042e22 (diff) | |
download | llvm-2f9fa9ef5387de3d87b0c866c678d93695c1c1f3.zip llvm-2f9fa9ef5387de3d87b0c866c678d93695c1c1f3.tar.gz llvm-2f9fa9ef5387de3d87b0c866c678d93695c1c1f3.tar.bz2 |
[lldb][AArch64] Add support for memory tags in core files
This teaches ProcessElfCore to recognise the MTE tag segments.
https://www.kernel.org/doc/html/latest/arm64/memory-tagging-extension.html#core-dump-support
These segments contain all the tags for a matching memory segment
which will have the same size in virtual address terms. In real terms
it's 2 tags per byte so the data in the segment is much smaller.
Since MTE is the only tag type supported I have hardcoded some
things to those values. We could and should support more formats
as they appear but doing so now would leave code untested until that
happens.
A few things to note:
* /proc/pid/smaps is not in the core file, only the details you have
in "maps". Meaning we mark a region tagged only if it has a tag segment.
* A core file supports memory tagging if it has at least 1 memory
tag segment, there is no other flag we can check to tell if memory
tagging was enabled. (unlike a live process that can support memory
tagging even if there are currently no tagged memory regions)
Tests have been added at the commands level for a core file with
mte and without.
There is a lot of overlap between the "memory tag read" tests here and the unit tests for
MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment, but I think it's
worth keeping to check ProcessElfCore doesn't cause an assert.
Depends on D129487
Reviewed By: omjavaid
Differential Revision: https://reviews.llvm.org/D129489
Diffstat (limited to 'lldb/test')
-rw-r--r-- | lldb/test/API/linux/aarch64/mte_core_file/TestAArch64LinuxMTEMemoryTagCoreFile.py | 170 | ||||
-rw-r--r-- | lldb/test/API/linux/aarch64/mte_core_file/core.mte | bin | 0 -> 20608 bytes | |||
-rw-r--r-- | lldb/test/API/linux/aarch64/mte_core_file/core.nomte | bin | 0 -> 20480 bytes | |||
-rw-r--r-- | lldb/test/API/linux/aarch64/mte_core_file/main.c | 78 |
4 files changed, 248 insertions, 0 deletions
diff --git a/lldb/test/API/linux/aarch64/mte_core_file/TestAArch64LinuxMTEMemoryTagCoreFile.py b/lldb/test/API/linux/aarch64/mte_core_file/TestAArch64LinuxMTEMemoryTagCoreFile.py new file mode 100644 index 0000000..d16916c --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_core_file/TestAArch64LinuxMTEMemoryTagCoreFile.py @@ -0,0 +1,170 @@ +""" +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): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + MTE_BUF_ADDR = hex(0xffff82c74000) + BUF_ADDR = hex(0xffff82c73000) + + @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=["error: End address \(0x[A-Fa-f0-9]+\) " + "must be greater than the start address " + "\(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" + "\[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" + "\[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 intial 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" + "\[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) diff --git a/lldb/test/API/linux/aarch64/mte_core_file/core.mte b/lldb/test/API/linux/aarch64/mte_core_file/core.mte Binary files differnew file mode 100644 index 0000000..84a3266 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_core_file/core.mte diff --git a/lldb/test/API/linux/aarch64/mte_core_file/core.nomte b/lldb/test/API/linux/aarch64/mte_core_file/core.nomte Binary files differnew file mode 100644 index 0000000..201f288 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_core_file/core.nomte diff --git a/lldb/test/API/linux/aarch64/mte_core_file/main.c b/lldb/test/API/linux/aarch64/mte_core_file/main.c new file mode 100644 index 0000000..89027e0 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_core_file/main.c @@ -0,0 +1,78 @@ +// Program to generate core files to test MTE tag features. +// +// This file uses ACLE intrinsics as detailed in: +// https://developer.arm.com/documentation/101028/0012/10--Memory-tagging-intrinsics?lang=en +// +// Compile with: +// <gcc or clang> -march=armv8.5-a+memtag -g main.c -o a.out.mte +// <gcc or clang> -march=armv8.5-a+memtag -g main.c -DNO_MTE -o a.out.nomte +// +// /proc/self/coredump_filter was set to 2 when the core files were made. + +#include <arm_acle.h> +#include <asm/mman.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <unistd.h> + +int main(int argc, char const *argv[]) { +#ifdef NO_MTE + *(char *)(0) = 0; +#endif + + if (prctl(PR_SET_TAGGED_ADDR_CTRL, + PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | + // Allow all tags to be generated by the addg + // instruction __arm_mte_increment_tag produces. + (0xffff << PR_MTE_TAG_SHIFT), + 0, 0, 0)) { + return 1; + } + + size_t page_size = sysconf(_SC_PAGESIZE); + char *mte_buf = mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!mte_buf) + return 1; + + printf("mte_buf: %p\n", mte_buf); + + // Allocate some untagged memory before the tagged memory. + char *buf = mmap(0, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!buf) + return 1; + + printf("buf: %p\n", buf); + + // This write means that the memory for buf is included in the corefile. + // So we can read from the end of it into mte_buf during the test. + *buf = 1; + + // These must be next to each other for the tests to work. + // <high address> + // mte_buf + // buf + // <low address> + if ((mte_buf - buf) != page_size) { + return 1; + } + + // Set incrementing tags until end of the page. + char *tagged_ptr = mte_buf; + // This ignores tag bits when subtracting the addresses. + while (__arm_mte_ptrdiff(tagged_ptr, mte_buf) < page_size) { + // Set the allocation tag for this location. + __arm_mte_set_tag(tagged_ptr); + // + 16 for 16 byte granules. + // Earlier we allowed all tag values, so this will give us an + // incrementing pattern 0-0xF wrapping back to 0. + tagged_ptr = __arm_mte_increment_tag(tagged_ptr + 16, 1); + } + + // Will fault because logical tag 0 != allocation tag 1. + *(mte_buf + 16) = 1; + + return 0; +} |