diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -1123,6 +1123,11 @@ // the file while for anonymous regions it have to be the name // associated to the region if that is available. + flags:; // where is a space separated string + // of flag names. Currently the only supported flag + // is "mt" for AArch64 memory tagging. lldb will + // ignore any other flags in this field. + error:; // where is // a hex encoded string value that // contains an error string diff --git a/lldb/docs/use/qemu-testing.rst b/lldb/docs/use/qemu-testing.rst --- a/lldb/docs/use/qemu-testing.rst +++ b/lldb/docs/use/qemu-testing.rst @@ -93,6 +93,9 @@ * --sve option will enable AArch64 SVE mode. +* --mte option will enable AArch64 MTE (memory tagging) mode. + (can be used on its own or in addition to --sve) + **Example:** Run QEMU Arm or AArch64 system emulation using run-qemu.sh :: diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h b/lldb/include/lldb/Target/MemoryRegionInfo.h --- a/lldb/include/lldb/Target/MemoryRegionInfo.h +++ b/lldb/include/lldb/Target/MemoryRegionInfo.h @@ -24,16 +24,17 @@ MemoryRegionInfo() = default; MemoryRegionInfo(RangeType range, OptionalBool read, OptionalBool write, OptionalBool execute, OptionalBool mapped, ConstString name, - OptionalBool flash, lldb::offset_t blocksize) + OptionalBool flash, lldb::offset_t blocksize, + OptionalBool memory_tagged) : m_range(range), m_read(read), m_write(write), m_execute(execute), - m_mapped(mapped), m_name(name), m_flash(flash), m_blocksize(blocksize) { - } + m_mapped(mapped), m_name(name), m_flash(flash), m_blocksize(blocksize), + m_memory_tagged(memory_tagged) {} RangeType &GetRange() { return m_range; } void Clear() { m_range.Clear(); - m_read = m_write = m_execute = eDontKnow; + m_read = m_write = m_execute = m_memory_tagged = eDontKnow; } const RangeType &GetRange() const { return m_range; } @@ -48,6 +49,8 @@ ConstString GetName() const { return m_name; } + OptionalBool GetMemoryTagged() const { return m_memory_tagged; } + void SetReadable(OptionalBool val) { m_read = val; } void SetWritable(OptionalBool val) { m_write = val; } @@ -66,6 +69,8 @@ void SetBlocksize(lldb::offset_t blocksize) { m_blocksize = blocksize; } + void SetMemoryTagged(OptionalBool val) { m_memory_tagged = val; } + // Get permissions as a uint32_t that is a mask of one or more bits from the // lldb::Permissions uint32_t GetLLDBPermissions() const { @@ -91,7 +96,8 @@ return m_range == rhs.m_range && m_read == rhs.m_read && m_write == rhs.m_write && m_execute == rhs.m_execute && m_mapped == rhs.m_mapped && m_name == rhs.m_name && - m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize; + m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize && + m_memory_tagged == rhs.m_memory_tagged; } bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); } @@ -105,6 +111,7 @@ ConstString m_name; OptionalBool m_flash = eDontKnow; lldb::offset_t m_blocksize = 0; + OptionalBool m_memory_tagged = eDontKnow; }; inline bool operator<(const MemoryRegionInfo &lhs, diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1318,6 +1318,30 @@ return " sve " in cpuinfo + def hasLinuxVmFlags(self): + """ Check that the target machine has "VmFlags" lines in + its /proc/{pid}/smaps files.""" + + triple = self.dbg.GetSelectedPlatform().GetTriple() + if not re.match(".*-.*-linux", triple): + return False + + self.runCmd('platform process list') + pid = None + for line in self.res.GetOutput().splitlines(): + if 'lldb-server' in line: + pid = line.split(' ')[0] + break + + if pid is None: + return False + + smaps_path = self.getBuildArtifact('smaps') + self.runCmd('platform get-file "/proc/{}/smaps" {}'.format(pid, smaps_path)) + + with open(smaps_path, 'r') as f: + return "VmFlags" in f.read() + def getArchitecture(self): """Returns the architecture in effect the test suite is running with.""" module = builder_module() diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -727,13 +727,13 @@ # Validate keys are known. for (key, val) in list(mem_region_dict.items()): - self.assertTrue( - key in [ - "start", - "size", - "permissions", - "name", - "error"]) + self.assertIn(key, + ["start", + "size", + "permissions", + "flags", + "name", + "error"]) self.assertIsNotNone(val) mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", "")) diff --git a/lldb/scripts/lldb-test-qemu/run-qemu.sh b/lldb/scripts/lldb-test-qemu/run-qemu.sh --- a/lldb/scripts/lldb-test-qemu/run-qemu.sh +++ b/lldb/scripts/lldb-test-qemu/run-qemu.sh @@ -5,7 +5,8 @@ echo -e "Starts QEMU system mode emulation for the architecture.\n" echo -e " --help\t\t\tDisplay this information." echo -e " --arch {arm|arm64}\t\tSelects architecture QEMU system emulation." - echo -e " --sve {path}\t\t\tEnables AArch64 SVE mode.\n" + echo -e " --sve\t\t\t\tEnables AArch64 SVE mode." + echo -e " --mte\t\t\t\tEnables AArch64 MTE mode.\n" echo -e " --rootfs {path}\t\tPath of root file system image." echo -e " --qemu {path}\t\t\tPath of pre-installed qemu-system-* executable." echo -e " --kernel {path}\t\tPath of Linux kernel prebuilt image.\n" @@ -48,6 +49,7 @@ --kernel) KERNEL_IMG=$2; shift;; --qemu) QEMU_BIN=$2; shift;; --sve) SVE=1;; + --mte) MTE=1;; --help) print_usage 0 ;; *) invalid_arg "$1" ;; esac @@ -99,6 +101,9 @@ if [[ $SVE ]]; then echo "warning: --sve is supported by AArch64 targets only" fi + if [[ $MTE ]]; then + echo "warning: --mte is supported by AArch64 targets only" + fi elif [[ "$ARCH" == "arm64" ]]; then QEMU_MACHINE=virt QEMU_SVE_MAX_VQ=4 @@ -107,6 +112,9 @@ if [[ $SVE ]]; then QEMU_CPU="max,sve-max-vq=$QEMU_SVE_MAX_VQ" fi + if [[ $MTE ]]; then + QEMU_MACHINE="$QEMU_MACHINE,mte=on" + fi fi run_qemu diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -1709,12 +1709,18 @@ section_name = section_sp->GetName(); } } + result.AppendMessageWithFormatv( - "[{0:x16}-{1:x16}) {2:r}{3:w}{4:x}{5}{6}{7}{8}\n", + "[{0:x16}-{1:x16}) {2:r}{3:w}{4:x}{5}{6}{7}{8}", range_info.GetRange().GetRangeBase(), range_info.GetRange().GetRangeEnd(), range_info.GetReadable(), range_info.GetWritable(), range_info.GetExecutable(), name ? " " : "", name, section_name ? " " : "", section_name); + MemoryRegionInfo::OptionalBool memory_tagged = + range_info.GetMemoryTagged(); + if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes) + result.AppendMessage("memory tagging: enabled"); + m_prev_end_addr = range_info.GetRange().GetRangeEnd(); result.SetStatus(eReturnStatusSuccessFinishResult); return true; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1297,26 +1297,36 @@ return Status(); } - auto BufferOrError = getProcFile(GetID(), "maps"); - if (!BufferOrError) { + Status Result; + LinuxMapCallback callback = [&](llvm::Expected Info) { + if (Info) { + FileSpec file_spec(Info->GetName().GetCString()); + FileSystem::Instance().Resolve(file_spec); + m_mem_region_cache.emplace_back(*Info, file_spec); + return true; + } + + Result = Info.takeError(); m_supports_mem_region = LazyBool::eLazyBoolNo; - return BufferOrError.getError(); + LLDB_LOG(log, "failed to parse proc maps: {0}", Result); + return false; + }; + + // Linux kernel since 2.6.14 has /proc/{pid}/smaps + // if CONFIG_PROC_PAGE_MONITOR is enabled + auto BufferOrError = getProcFile(GetID(), "smaps"); + if (BufferOrError) + ParseLinuxSMapRegions(BufferOrError.get()->getBuffer(), callback); + else { + BufferOrError = getProcFile(GetID(), "maps"); + if (!BufferOrError) { + m_supports_mem_region = LazyBool::eLazyBoolNo; + return BufferOrError.getError(); + } + + ParseLinuxMapRegions(BufferOrError.get()->getBuffer(), callback); } - Status Result; - ParseLinuxMapRegions(BufferOrError.get()->getBuffer(), - [&](const MemoryRegionInfo &Info, const Status &ST) { - if (ST.Success()) { - FileSpec file_spec(Info.GetName().GetCString()); - FileSystem::Instance().Resolve(file_spec); - m_mem_region_cache.emplace_back(Info, file_spec); - return true; - } else { - m_supports_mem_region = LazyBool::eLazyBoolNo; - LLDB_LOG(log, "failed to parse proc maps: {0}", ST); - Result = ST; - return false; - } - }); + if (Result.Fail()) return Result; diff --git a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.h b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.h --- a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.h +++ b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.h @@ -11,16 +11,16 @@ #include "lldb/lldb-forward.h" #include "llvm/ADT/StringRef.h" -#include - +#include "llvm/Support/Error.h" namespace lldb_private { -typedef std::function LinuxMapCallback; +typedef std::function)> LinuxMapCallback; void ParseLinuxMapRegions(llvm::StringRef linux_map, LinuxMapCallback const &callback); +void ParseLinuxSMapRegions(llvm::StringRef linux_smap, + LinuxMapCallback const &callback); } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp --- a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp +++ b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp @@ -7,80 +7,93 @@ //===----------------------------------------------------------------------===// #include "LinuxProcMaps.h" -#include "llvm/ADT/StringRef.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StringExtractor.h" +#include "llvm/ADT/StringRef.h" using namespace lldb_private; -static Status +enum class MapsKind { Maps, SMaps }; + +static llvm::Expected ProcMapError(const char *msg, + MapsKind kind) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), msg, + kind == MapsKind::Maps ? "maps" : "smaps"); +} + +static llvm::Expected ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line, - MemoryRegionInfo &memory_region_info) { - memory_region_info.Clear(); - + MapsKind maps_kind) { + MemoryRegionInfo region; StringExtractor line_extractor(maps_line); - + // Format: {address_start_hex}-{address_end_hex} perms offset dev inode // pathname perms: rwxp (letter is present if set, '-' if not, final // character is p=private, s=shared). - + // Parse out the starting address lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0); - + // Parse out hyphen separating start and end address from range. if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-')) - return Status( - "malformed /proc/{pid}/maps entry, missing dash between address range"); - + return ProcMapError( + "malformed /proc/{pid}/%s entry, missing dash between address range", + maps_kind); + // Parse out the ending address lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address); - + // Parse out the space after the address. if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' ')) - return Status( - "malformed /proc/{pid}/maps entry, missing space after range"); - + return ProcMapError( + "malformed /proc/{pid}/%s entry, missing space after range", maps_kind); + // Save the range. - memory_region_info.GetRange().SetRangeBase(start_address); - memory_region_info.GetRange().SetRangeEnd(end_address); - - // Any memory region in /proc/{pid}/maps is by definition mapped into the - // process. - memory_region_info.SetMapped(MemoryRegionInfo::OptionalBool::eYes); - + region.GetRange().SetRangeBase(start_address); + region.GetRange().SetRangeEnd(end_address); + + // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped + // into the process. + region.SetMapped(MemoryRegionInfo::OptionalBool::eYes); + // Parse out each permission entry. if (line_extractor.GetBytesLeft() < 4) - return Status("malformed /proc/{pid}/maps entry, missing some portion of " - "permissions"); - + return ProcMapError( + "malformed /proc/{pid}/%s entry, missing some portion of " + "permissions", + maps_kind); + // Handle read permission. const char read_perm_char = line_extractor.GetChar(); if (read_perm_char == 'r') - memory_region_info.SetReadable(MemoryRegionInfo::OptionalBool::eYes); + region.SetReadable(MemoryRegionInfo::OptionalBool::eYes); else if (read_perm_char == '-') - memory_region_info.SetReadable(MemoryRegionInfo::OptionalBool::eNo); + region.SetReadable(MemoryRegionInfo::OptionalBool::eNo); else - return Status("unexpected /proc/{pid}/maps read permission char"); - + return ProcMapError("unexpected /proc/{pid}/%s read permission char", + maps_kind); + // Handle write permission. const char write_perm_char = line_extractor.GetChar(); if (write_perm_char == 'w') - memory_region_info.SetWritable(MemoryRegionInfo::OptionalBool::eYes); + region.SetWritable(MemoryRegionInfo::OptionalBool::eYes); else if (write_perm_char == '-') - memory_region_info.SetWritable(MemoryRegionInfo::OptionalBool::eNo); + region.SetWritable(MemoryRegionInfo::OptionalBool::eNo); else - return Status("unexpected /proc/{pid}/maps write permission char"); - + return ProcMapError("unexpected /proc/{pid}/%s write permission char", + maps_kind); + // Handle execute permission. const char exec_perm_char = line_extractor.GetChar(); if (exec_perm_char == 'x') - memory_region_info.SetExecutable(MemoryRegionInfo::OptionalBool::eYes); + region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes); else if (exec_perm_char == '-') - memory_region_info.SetExecutable(MemoryRegionInfo::OptionalBool::eNo); + region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo); else - return Status("unexpected /proc/{pid}/maps exec permission char"); - + return ProcMapError("unexpected /proc/{pid}/%s exec permission char", + maps_kind); + line_extractor.GetChar(); // Read the private bit line_extractor.SkipSpaces(); // Skip the separator line_extractor.GetHexMaxU64(false, 0); // Read the offset @@ -89,13 +102,13 @@ line_extractor.GetHexMaxU64(false, 0); // Read the major device number line_extractor.SkipSpaces(); // Skip the separator line_extractor.GetU64(0, 10); // Read the inode number - + line_extractor.SkipSpaces(); const char *name = line_extractor.Peek(); if (name) - memory_region_info.SetName(name); - - return Status(); + region.SetName(name); + + return region; } void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map, @@ -104,9 +117,80 @@ llvm::StringRef line; while (!lines.empty()) { std::tie(line, lines) = lines.split('\n'); - MemoryRegionInfo region; - Status error = ParseMemoryRegionInfoFromProcMapsLine(line, region); - if (!callback(region, error)) + if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps))) break; } } + +void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap, + LinuxMapCallback const &callback) { + // Entries in /smaps look like: + // 00400000-0048a000 r-xp 00000000 fd:03 960637 + // Size: 552 kB + // Rss: 460 kB + // <...> + // VmFlags: rd ex mr mw me dw + // 00500000-0058a000 rwxp 00000000 fd:03 960637 + // <...> + // + // Where the first line is identical to the /maps format + // and VmFlags is only printed for kernels >= 3.8. + + llvm::StringRef lines(linux_smap); + llvm::StringRef line; + llvm::Optional region; + + while (lines.size()) { + std::tie(line, lines) = lines.split('\n'); + + // A property line looks like: + // : + // (no spaces on the left hand side) + // A header will have a ':' but the LHS will contain spaces + llvm::StringRef name; + llvm::StringRef value; + std::tie(name, value) = line.split(':'); + + // If this line is a property line + if (!name.contains(' ')) { + if (region) { + if (name == "VmFlags") { + if (value.contains("mt")) + region->SetMemoryTagged(MemoryRegionInfo::eYes); + else + region->SetMemoryTagged(MemoryRegionInfo::eNo); + } + // Ignore anything else + } else { + // Orphaned settings line + callback(ProcMapError( + "Found a property line without a corresponding mapping " + "in /proc/{pid}/%s", + MapsKind::SMaps)); + return; + } + } else { + // Must be a new region header + if (region) { + // Save current region + callback(*region); + region.reset(); + } + + // Try to start a new region + llvm::Expected new_region = + ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps); + if (new_region) { + region = *new_region; + } else { + // Stop at first invalid region header + callback(new_region.takeError()); + return; + } + } + } + + // Catch last region + if (region) + callback(*region); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -1529,6 +1529,22 @@ std::string name; name_extractor.GetHexByteString(name); region_info.SetName(name.c_str()); + } else if (name.equals("flags")) { + region_info.SetMemoryTagged(MemoryRegionInfo::eNo); + + llvm::StringRef flags = value; + llvm::StringRef flag; + while (flags.size()) { + flags = flags.ltrim(); + std::tie(flag, flags) = flags.split(' '); + // To account for trailing whitespace + if (flag.size()) { + if (flag == "mt") { + region_info.SetMemoryTagged(MemoryRegionInfo::eYes); + break; + } + } + } } else if (name.equals("error")) { StringExtractorGDBRemote error_extractor(value); std::string error_string; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2605,6 +2605,17 @@ response.PutChar(';'); } + // Flags + MemoryRegionInfo::OptionalBool memory_tagged = + region_info.GetMemoryTagged(); + if (memory_tagged != MemoryRegionInfo::eDontKnow) { + response.PutCString("flags:"); + if (memory_tagged == MemoryRegionInfo::eYes) { + response.PutCString("mt"); + } + response.PutChar(';'); + } + // Name ConstString name = region_info.GetName(); if (name) { diff --git a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp --- a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp +++ b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp @@ -263,13 +263,18 @@ auto data = parser.GetStream(StreamType::LinuxMaps); if (data.empty()) return false; - ParseLinuxMapRegions(llvm::toStringRef(data), - [&](const lldb_private::MemoryRegionInfo ®ion, - const lldb_private::Status &status) -> bool { - if (status.Success()) - regions.push_back(region); - return true; - }); + + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS); + ParseLinuxMapRegions( + llvm::toStringRef(data), + [®ions, &log](llvm::Expected region) -> bool { + if (region) + regions.push_back(*region); + else + LLDB_LOG_ERROR(log, region.takeError(), + "Reading memory region from minidump failed: {0}"); + return true; + }); return !regions.empty(); } diff --git a/lldb/source/Target/MemoryRegionInfo.cpp b/lldb/source/Target/MemoryRegionInfo.cpp --- a/lldb/source/Target/MemoryRegionInfo.cpp +++ b/lldb/source/Target/MemoryRegionInfo.cpp @@ -13,12 +13,12 @@ llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS, const MemoryRegionInfo &Info) { return OS << llvm::formatv("MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, " - "{5}, `{6}`, {7}, {8})", + "{5}, `{6}`, {7}, {8}, {9})", Info.GetRange().GetRangeBase(), Info.GetRange().GetRangeEnd(), Info.GetReadable(), Info.GetWritable(), Info.GetExecutable(), Info.GetMapped(), Info.GetName(), Info.GetFlash(), - Info.GetBlocksize()); + Info.GetBlocksize(), Info.GetMemoryTagged()); } void llvm::format_provider::format( diff --git a/lldb/test/API/linux/aarch64/mte_memory_region/Makefile b/lldb/test/API/linux/aarch64/mte_memory_region/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_memory_region/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/linux/aarch64/mte_memory_region/TestAArch64LinuxMTEMemoryRegion.py b/lldb/test/API/linux/aarch64/mte_memory_region/TestAArch64LinuxMTEMemoryRegion.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_memory_region/TestAArch64LinuxMTEMemoryRegion.py @@ -0,0 +1,55 @@ +""" +Test that "memory region" command can show memory tagged regions +on AArch64 Linux. +""" + + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class AArch64LinuxMTEMemoryRegionTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @skipIf(archs=no_match(["aarch64"])) + @skipUnlessPlatform(["linux"]) + def test_mte_regions(self): + if not self.hasLinuxVmFlags(): + self.skipTest('/proc/{pid}/smaps VmFlags 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: + # 47 = non MTE toolchain + # 48 = non MTE target + exit_status = self.process().GetExitStatus() + if exit_status == 47: + self.skipTest("MTE must be available in toolchain") + elif exit_status == 48: + self.skipTest("target must have MTE enabled") + + # Otherwise we have MTE but another problem occured + self.fail("Test program failed to run.") + + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + substrs = ["memory tagging: enabled"] + # The new page will be tagged + self.expect("memory region the_page", substrs=substrs) + # Code page will not be + self.expect("memory region main", substrs=substrs, matching=False) diff --git a/lldb/test/API/linux/aarch64/mte_memory_region/main.c b/lldb/test/API/linux/aarch64/mte_memory_region/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_memory_region/main.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +#define INCOMPATIBLE_TOOLCHAIN 47 +#define INCOMPATIBLE_TARGET 48 + +// This is in a seperate non static function +// so that we can always breakpoint the return 0 here. +// Even if main never reaches it because HWCAP2_MTE +// is not defined. +// If it were in main then you would effectively have: +// return TEST_INCOMPATIBLE; +// return 0; +// So the two returns would have the same breakpoint location +// and we couldn't tell them apart. +int setup_mte_page(void) { +#ifdef HWCAP2_MTE + if (!(getauxval(AT_HWCAP2) & HWCAP2_MTE)) + return INCOMPATIBLE_TARGET; + + int got = prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0); + if (got) + return 1; + + void *the_page = mmap(0, sysconf(_SC_PAGESIZE), PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (the_page == MAP_FAILED) + return 1; +#endif + + return 0; // Set break point at this line. +} + +int main(int argc, char const *argv[]) { +#ifdef HWCAP2_MTE + return setup_mte_page(); +#else + return INCOMPATIBLE_TOOLCHAIN; +#endif +} diff --git a/lldb/unittests/Process/Utility/CMakeLists.txt b/lldb/unittests/Process/Utility/CMakeLists.txt --- a/lldb/unittests/Process/Utility/CMakeLists.txt +++ b/lldb/unittests/Process/Utility/CMakeLists.txt @@ -1,5 +1,6 @@ add_lldb_unittest(ProcessUtilityTests RegisterContextFreeBSDTest.cpp + LinuxProcMapsTest.cpp LINK_LIBS lldbPluginProcessUtility) diff --git a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp @@ -0,0 +1,262 @@ +//===-- LinuxProcMapsTest.cpp ---------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "Plugins/Process/Utility/LinuxProcMaps.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Utility/Status.h" +#include + +using namespace lldb_private; + +typedef std::tuple + LinuxProcMapsTestParams; + +// Wrapper for convenience because Range is usually begin, size +static MemoryRegionInfo::RangeType make_range(lldb::addr_t begin, + lldb::addr_t end) { + MemoryRegionInfo::RangeType range(begin, 0); + range.SetRangeEnd(end); + return range; +} + +class LinuxProcMapsTestFixture + : public ::testing::TestWithParam { +protected: + Status error; + std::string err_str; + MemoryRegionInfos regions; + LinuxMapCallback callback; + + void SetUp() override { + callback = [this](llvm::Expected Info) { + if (Info) { + err_str.clear(); + regions.push_back(*Info); + return true; + } + + err_str = toString(Info.takeError()); + return false; + }; + } + + void check_regions(LinuxProcMapsTestParams params) { + EXPECT_THAT(std::get<1>(params), testing::ContainerEq(regions)); + ASSERT_EQ(std::get<2>(params), err_str); + } +}; + +TEST_P(LinuxProcMapsTestFixture, ParseMapRegions) { + auto params = GetParam(); + ParseLinuxMapRegions(std::get<0>(params), callback); + check_regions(params); +} + +// Note: ConstString("") != ConstString(nullptr) +// When a region has no name, it will have the latter in the MemoryRegionInfo +INSTANTIATE_TEST_CASE_P( + ProcMapTests, LinuxProcMapsTestFixture, + ::testing::Values( + // Nothing in nothing out + std::make_tuple("", MemoryRegionInfos{}, ""), + // Various formatting error conditions + std::make_tuple("55a4512f7000/55a451b68000 rw-p 00000000 00:00 0", + MemoryRegionInfos{}, + "malformed /proc/{pid}/maps entry, missing dash " + "between address range"), + std::make_tuple("0-0 rw", MemoryRegionInfos{}, + "malformed /proc/{pid}/maps entry, missing some " + "portion of permissions"), + std::make_tuple("0-0 z--p 00000000 00:00 0", MemoryRegionInfos{}, + "unexpected /proc/{pid}/maps read permission char"), + std::make_tuple("0-0 rz-p 00000000 00:00 0", MemoryRegionInfos{}, + "unexpected /proc/{pid}/maps write permission char"), + std::make_tuple("0-0 rwzp 00000000 00:00 0", MemoryRegionInfos{}, + "unexpected /proc/{pid}/maps exec permission char"), + // Stops at first parsing error + std::make_tuple( + "0-1 rw-p 00000000 00:00 0 [abc]\n" + "0-0 rwzp 00000000 00:00 0\n" + "2-3 r-xp 00000000 00:00 0 [def]\n", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0, 1), MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, ConstString("[abc]"), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + "unexpected /proc/{pid}/maps exec permission char"), + // Single entry + std::make_tuple( + "55a4512f7000-55a451b68000 rw-p 00000000 00:00 0 [heap]", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x55a4512f7000, 0x55a451b68000), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[heap]"), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + ""), + // Multiple entries + std::make_tuple( + "7fc090021000-7fc094000000 ---p 00000000 00:00 0\n" + "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 " + "[vsyscall]", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x7fc090021000, 0x7fc094000000), + MemoryRegionInfo::eNo, MemoryRegionInfo::eNo, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + MemoryRegionInfo( + make_range(0xffffffffff600000, 0xffffffffff601000), + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + ConstString("[vsyscall]"), MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + "")), ); + +class LinuxProcSMapsTestFixture : public LinuxProcMapsTestFixture {}; + +INSTANTIATE_TEST_CASE_P( + ProcSMapTests, LinuxProcSMapsTestFixture, + ::testing::Values( + // Nothing in nothing out + std::make_tuple("", MemoryRegionInfos{}, ""), + // Uses the same parsing for first line, so same errors but referring to + // smaps + std::make_tuple("0/0 rw-p 00000000 00:00 0", MemoryRegionInfos{}, + "malformed /proc/{pid}/smaps entry, missing dash " + "between address range"), + // Stop parsing at first error + std::make_tuple( + "1111-2222 rw-p 00000000 00:00 0 [foo]\n" + "0/0 rw-p 00000000 00:00 0", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + "malformed /proc/{pid}/smaps entry, missing dash between address " + "range"), + // Property line without a region is an error + std::make_tuple("Referenced: 2188 kB\n" + "1111-2222 rw-p 00000000 00:00 0 [foo]\n" + "3333-4444 rw-p 00000000 00:00 0 [bar]\n", + MemoryRegionInfos{}, + "Found a property line without a corresponding mapping " + "in /proc/{pid}/smaps"), + // Single region parses, has no flags + std::make_tuple( + "1111-2222 rw-p 00000000 00:00 0 [foo]", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + ""), + // Single region with flags, other lines ignored + std::make_tuple( + "1111-2222 rw-p 00000000 00:00 0 [foo]\n" + "Referenced: 2188 kB\n" + "AnonHugePages: 0 kB\n" + "VmFlags: mt", + MemoryRegionInfos{ + MemoryRegionInfo( + make_range(0x1111, 0x2222), MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, MemoryRegionInfo::eYes), + }, + ""), + // Whitespace ignored + std::make_tuple( + "0-0 rw-p 00000000 00:00 0\n" + "VmFlags: mt ", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0, 0), MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eYes), + }, + ""), + // VmFlags line means it has flag info, but nothing is set + std::make_tuple( + "0-0 rw-p 00000000 00:00 0\n" + "VmFlags: ", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0, 0), MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eNo), + }, + ""), + // Handle some pages not having a flags line + std::make_tuple( + "1111-2222 rw-p 00000000 00:00 0 [foo]\n" + "Referenced: 2188 kB\n" + "AnonHugePages: 0 kB\n" + "3333-4444 r-xp 00000000 00:00 0 [bar]\n" + "VmFlags: mt", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + MemoryRegionInfo( + make_range(0x3333, 0x4444), MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, ConstString("[bar]"), + MemoryRegionInfo::eDontKnow, 0, MemoryRegionInfo::eYes), + }, + ""), + // Handle no pages having a flags line (older kernels) + std::make_tuple( + "1111-2222 rw-p 00000000 00:00 0\n" + "Referenced: 2188 kB\n" + "AnonHugePages: 0 kB\n" + "3333-4444 r-xp 00000000 00:00 0\n" + "KernelPageSize: 4 kB\n" + "MMUPageSize: 4 kB\n", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + MemoryRegionInfo(make_range(0x3333, 0x4444), + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + MemoryRegionInfo::eDontKnow), + }, + "")), ); + +TEST_P(LinuxProcSMapsTestFixture, ParseSMapRegions) { + auto params = GetParam(); + ParseLinuxSMapRegions(std::get<0>(params), callback); + check_regions(params); +} diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -343,6 +343,25 @@ EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetWritable()); EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetExecutable()); EXPECT_EQ("/foo/bar.so", region_info.GetName().GetStringRef()); + EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.GetMemoryTagged()); + + result = std::async(std::launch::async, [&] { + return client.GetMemoryRegionInfo(addr, region_info); + }); + + HandlePacket(server, "qMemoryRegionInfo:a000", + "start:a000;size:2000;flags:;"); + EXPECT_TRUE(result.get().Success()); + EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetMemoryTagged()); + + result = std::async(std::launch::async, [&] { + return client.GetMemoryRegionInfo(addr, region_info); + }); + + HandlePacket(server, "qMemoryRegionInfo:a000", + "start:a000;size:2000;flags: mt zz mt ;"); + EXPECT_TRUE(result.get().Success()); + EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetMemoryTagged()); } TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfoInvalidResponse) { diff --git a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp --- a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp +++ b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp @@ -378,15 +378,15 @@ parser->BuildMemoryRegions(), testing::Pair(testing::ElementsAre( MemoryRegionInfo({0x0, 0x10000}, no, no, no, no, - ConstString(), unknown, 0), + ConstString(), unknown, 0, unknown), MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, yes, - ConstString(), unknown, 0), + ConstString(), unknown, 0, unknown), MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, yes, - ConstString(), unknown, 0), + ConstString(), unknown, 0, unknown), MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, yes, - ConstString(), unknown, 0), + ConstString(), unknown, 0, unknown), MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, yes, - ConstString(), unknown, 0)), + ConstString(), unknown, 0, unknown)), true)); } @@ -409,12 +409,13 @@ EXPECT_THAT( parser->BuildMemoryRegions(), - testing::Pair(testing::ElementsAre( - MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, - yes, ConstString(), unknown, 0), - MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, - yes, ConstString(), unknown, 0)), - false)); + testing::Pair( + testing::ElementsAre( + MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, unknown), + MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, unknown)), + false)); } TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemory64List) { @@ -424,12 +425,13 @@ // we don't have a MemoryInfoListStream. EXPECT_THAT( parser->BuildMemoryRegions(), - testing::Pair(testing::ElementsAre( - MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, - yes, ConstString(), unknown, 0), - MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, - yes, ConstString(), unknown, 0)), - false)); + testing::Pair( + testing::ElementsAre( + MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, unknown), + MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, unknown)), + false)); } TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) { @@ -453,22 +455,42 @@ ConstString app_process("/system/bin/app_process"); ConstString linker("/system/bin/linker"); ConstString liblog("/system/lib/liblog.so"); - EXPECT_THAT( - parser->BuildMemoryRegions(), - testing::Pair(testing::ElementsAre( - MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, - yes, app_process, unknown, 0), - MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, yes, - app_process, unknown, 0), - MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, - yes, ConstString(), unknown, 0), - MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, yes, - ConstString(), unknown, 0), - MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, - yes, linker, unknown, 0), - MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, - yes, liblog, unknown, 0)), - true)); + EXPECT_THAT(parser->BuildMemoryRegions(), + testing::Pair( + testing::ElementsAre( + MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, yes, + app_process, unknown, 0, unknown), + MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, yes, + app_process, unknown, 0, unknown), + MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, yes, + ConstString(), unknown, 0, unknown), + MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, yes, + ConstString(), unknown, 0, unknown), + MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, yes, + linker, unknown, 0, unknown), + MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, yes, + liblog, unknown, 0, unknown)), + true)); +} + +TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMapsError) { + ASSERT_THAT_ERROR(SetUpFromYaml(R"( +--- !minidump +Streams: + - Type: LinuxMaps + Text: | + 400d9000-400db000 r?xp 00000000 b3:04 227 + 400fc000-400fd000 rwxp 00001000 b3:04 1096 +... +)"), + llvm::Succeeded()); + // Test that when a /proc/maps region fails to parse + // we handle the error and continue with the rest. + EXPECT_THAT(parser->BuildMemoryRegions(), + testing::Pair(testing::ElementsAre(MemoryRegionInfo( + {0x400fc000, 0x1000}, yes, yes, yes, yes, + ConstString(nullptr), unknown, 0, unknown)), + true)); } // Windows Minidump tests