diff --git a/lldb/bindings/interface/SBMemoryRegionInfo.i b/lldb/bindings/interface/SBMemoryRegionInfo.i --- a/lldb/bindings/interface/SBMemoryRegionInfo.i +++ b/lldb/bindings/interface/SBMemoryRegionInfo.i @@ -47,6 +47,12 @@ GetName (); bool + GetShortFlagNames (lldb::SBStream &flags); + + bool + GetLongFlagNames (lldb::SBStream &flags); + + bool operator == (const lldb::SBMemoryRegionInfo &rhs) const; bool 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 @@ -1086,10 +1086,54 @@ // 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. These names are based on the Linux + // /proc/{pid}/smaps VmFlags names but are not + // necessarily the same and are used across all supported + // platforms. They are short and do not include spaces, + // for example "rd" for readable. (see table below) + // lldb will ignore any unknown flags in this field. + error:; // where is // a hex encoded string value that // contains an error string +The currently supported memory flags are: +| Flag | Meaning | +|------|----------------------------------------| +| rd | readable | +| wr | writeable | +| ex | executable | +| sh | shared | +| mr | may read | +| mw | may write | +| me | may execute | +| ms | may share | +| gd | stack segment grows down | +| pf | pure PFN range | +| dw | disabled write to the mapped file | +| lo | pages are locked in memory | +| io | memory mapped I/O area | +| sr | sequential read advise provided | +| rr | random read advise provided | +| dc | do not copy area on fork | +| de | do not expand area on remapping | +| ac | area is accountable | +| nr | swap space is not reserved for the area| +| ht | area uses huge tlb pages | +| sf | perform synchronous faults | +| nl | non-linear mapping | +| ar | architecture specific flag | +| wf | wipe on fork | +| dd | do not include area into core dump | +| sd | soft-dirty | +| mm | mixed map area | +| hg | huge page advise | +| nh | no-huge page advise | +| mg | mergeable advise | +| um | userfaultfd missing pages tracking | +| uw | userfaultfd wprotect pages tracking | + If the address requested is not in a mapped region (e.g. we've jumped through a NULL pointer and are at 0x0) currently lldb expects to get back the size of the unmapped region -- that is, the distance to the next valid region. diff --git a/lldb/include/lldb/API/SBMemoryRegionInfo.h b/lldb/include/lldb/API/SBMemoryRegionInfo.h --- a/lldb/include/lldb/API/SBMemoryRegionInfo.h +++ b/lldb/include/lldb/API/SBMemoryRegionInfo.h @@ -73,6 +73,27 @@ /// region. If no name can be determined the returns nullptr. const char *GetName(); + /// If the region has flag information, get its flags as a space + /// separated string of short names. Such as "rd", "wr", etc. + /// + /// \param[out] flags + /// Stream to write the flags string to. + /// + /// \return + /// True if flags were written to \p flags, False otherwise. + bool GetShortFlagNames(lldb::SBStream &flags); + + /// If the region has flag information, get its flags as a newline + /// separated string of long names. Such as "readable", + /// "may read", etc. + /// + /// \param[out] flags + /// Stream to write the flags string to. + /// + /// \return + /// True if flags were written to \p flags, False otherwise. + bool GetLongFlagNames(lldb::SBStream &flags); + bool operator==(const lldb::SBMemoryRegionInfo &rhs) const; bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const; 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 @@ -12,27 +12,76 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/RangeMap.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/FormatProviders.h" +#include namespace lldb_private { class MemoryRegionInfo { public: typedef Range RangeType; + enum class Flag { + readable, + writeable, + executable, + shared, + may_read, + may_write, + may_execute, + may_share, + stack_grows_down, + pure_PFN_range, + disabled_write_to_the_mapped_file, + pages_are_locked_in_memory, + memory_mapped_IO_area, + sequential_read_advise_provided, + random_read_advise_provided, + do_not_copy_area_on_fork, + do_not_expand_area_on_remapping, + area_is_accountable, + swap_space_is_not_reserved_for_the_area, + area_uses_huge_tlb_pages, + perform_synchronous_page_faults, + non_linear_mapping, + architecture_specific_flag, + wipe_on_fork, + do_not_include_area_into_core_dump, + soft_dirty, + mixed_map_area, + huge_page_advise, + no_huge_page_advise, + mergeable_advise, + userfaultfd_missing_pages_tracking, + userfaultfd_wprotect_pages_tracking, + }; + struct FlagInfo { + Flag flag; + // E.g. "mr". Used in remote protocol packets, no spaces. + const std::string short_name; + // E.g "may read". Shown to users, may have spaces. + const std::string long_name; + }; + enum OptionalBool { eDontKnow = -1, eNo = 0, eYes = 1 }; 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, + llvm::Optional short_flags) : m_range(range), m_read(read), m_write(write), m_execute(execute), m_mapped(mapped), m_name(name), m_flash(flash), m_blocksize(blocksize) { + if (short_flags) { + SetFlagsFromShortFlags(*short_flags); + } } RangeType &GetRange() { return m_range; } void Clear() { m_range.Clear(); + m_flags.reset(); m_read = m_write = m_execute = eDontKnow; } @@ -48,6 +97,14 @@ ConstString GetName() const { return m_name; } + bool HasFlags() const { return m_flags.hasValue(); } + + // Get space joined string of short flag names + std::string GetShortFlagNames() const; + + // Get a newline joined string of long flag names + std::string GetLongFlagNames() const; + void SetReadable(OptionalBool val) { m_read = val; } void SetWritable(OptionalBool val) { m_write = val; } @@ -66,6 +123,8 @@ void SetBlocksize(lldb::offset_t blocksize) { m_blocksize = blocksize; } + void SetFlagsFromShortFlags(llvm::StringRef flags); + // Get permissions as a uint32_t that is a mask of one or more bits from the // lldb::Permissions uint32_t GetLLDBPermissions() const { @@ -91,12 +150,16 @@ 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_flags == rhs.m_flags; } bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); } protected: + std::string JoinFlags(std::function getter, + llvm::StringRef sep) const; + RangeType m_range; OptionalBool m_read = eDontKnow; OptionalBool m_write = eDontKnow; @@ -105,8 +168,9 @@ ConstString m_name; OptionalBool m_flash = eDontKnow; lldb::offset_t m_blocksize = 0; + llvm::Optional> m_flags; }; - + inline bool operator<(const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) { return lhs.GetRange() < rhs.GetRange(); diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -589,6 +589,10 @@ """Decorate the item to skip tests that should be skipped on Linux.""" return skipIfPlatform(["linux"])(func) +def skipUnlessLinux(func): + """Decorate the item to skip tests that should be skipped on any non-Linux platform.""" + return skipUnlessPlatform(["linux"])(func) + def skipIfWindows(func): """Decorate the item to skip tests that should be skipped on Windows.""" @@ -877,3 +881,21 @@ return unittest2.skipIf( configuration.capture_path or configuration.replay_path, "reproducers unsupported")(func) + +def skipUnlessHasProcSmapsVmFlags(): + # smaps is available on kernels >= 2.6.14 + # VmFlags is present if CONFIG_PROC_PAGE_MONITOR is enabled + # We're assuming that if this Python process has /smaps/ then + # any debug target we launch would have it too. + smaps_path = os.path.join(os.sep, "proc", str(os.getpid()), "smaps") + has_vmflags = False + + try: + with open(smaps_path, 'r') as f: + if "VmFlags" in f.read(): + has_vmflags = True + # Would use FileNotFoundError but that is py3 only + except IOError: + pass + + return unittest2.skipUnless(has_vmflags, "requires /proc/{pid}/smaps with VmFlags") 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 @@ -806,8 +806,9 @@ "start", "size", "permissions", + "flags", "name", - "error"]) + "error"], "Unexpected key \"%s\"" % key) self.assertIsNotNone(val) mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", "")) diff --git a/lldb/source/API/SBMemoryRegionInfo.cpp b/lldb/source/API/SBMemoryRegionInfo.cpp --- a/lldb/source/API/SBMemoryRegionInfo.cpp +++ b/lldb/source/API/SBMemoryRegionInfo.cpp @@ -116,6 +116,32 @@ return m_opaque_up->GetName().AsCString(); } +bool SBMemoryRegionInfo::GetShortFlagNames(SBStream &flags) { + LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetShortFlagNames, + (lldb::SBStream &), flags); + + if (!m_opaque_up->HasFlags()) + return false; + + Stream &strm = flags.ref(); + strm << m_opaque_up->GetShortFlagNames(); + + return true; +} + +bool SBMemoryRegionInfo::GetLongFlagNames(SBStream &flags) { + LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetLongFlagNames, + (lldb::SBStream &), flags); + + if (!m_opaque_up->HasFlags()) + return false; + + Stream &strm = flags.ref(); + strm << m_opaque_up->GetLongFlagNames(); + + return true; +} + bool SBMemoryRegionInfo::GetDescription(SBStream &description) { LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetDescription, (lldb::SBStream &), description); @@ -128,6 +154,8 @@ strm.Printf(m_opaque_up->GetReadable() ? "R" : "-"); strm.Printf(m_opaque_up->GetWritable() ? "W" : "-"); strm.Printf(m_opaque_up->GetExecutable() ? "X" : "-"); + if (m_opaque_up->HasFlags()) + strm.Printf(" flags: %s", m_opaque_up->GetShortFlagNames().c_str()); strm.Printf("]"); return true; @@ -158,6 +186,10 @@ LLDB_REGISTER_METHOD(bool, SBMemoryRegionInfo, IsExecutable, ()); LLDB_REGISTER_METHOD(bool, SBMemoryRegionInfo, IsMapped, ()); LLDB_REGISTER_METHOD(const char *, SBMemoryRegionInfo, GetName, ()); + LLDB_REGISTER_METHOD(bool, SBMemoryRegionInfo, GetShortFlagNames, + (lldb::SBStream &)); + LLDB_REGISTER_METHOD(bool, SBMemoryRegionInfo, GetLongFlagNames, + (lldb::SBStream &)); LLDB_REGISTER_METHOD(bool, SBMemoryRegionInfo, GetDescription, (lldb::SBStream &)); } 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 @@ -1733,12 +1733,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); + if (range_info.HasFlags()) { + result.AppendMessageWithFormatv("flags:\n{0}", + range_info.GetLongFlagNames()); + } + 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,28 +1297,43 @@ 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; + } + + llvm::handleAllErrors(Info.takeError(), + [&Result](const llvm::StringError &e) { + Result.SetErrorToGenericError(); + Result.SetErrorString(e.getMessage()); + }); 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); + if (Result.Fail()) + return Result; + } else { + BufferOrError = getProcFile(GetID(), "maps"); + if (!BufferOrError) { + m_supports_mem_region = LazyBool::eLazyBoolNo; + return BufferOrError.getError(); + } + + ParseLinuxMapRegions(BufferOrError.get()->getBuffer(), callback); + if (Result.Fail()) + return Result; } - 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; if (m_mem_region_cache.empty()) { // No entries after attempting to read them. This shouldn't happen if 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,17 @@ #include "lldb/lldb-forward.h" #include "llvm/ADT/StringRef.h" -#include - +#include "llvm/Support/Error.h" namespace lldb_private { -typedef std::function LinuxMapCallback; +typedef llvm::Expected ExpectedMemoryRegionInfo; +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,92 @@ //===----------------------------------------------------------------------===// #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 ExpectedMemoryRegionInfo ProcMapError(const char *msg, MapsKind kind) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), msg, + kind == MapsKind::Maps ? "maps" : "smaps"); +} + +static ExpectedMemoryRegionInfo 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 +101,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 +116,79 @@ 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.empty()) { + 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") { + // An empty value is ok, although unlikely to happen + region->SetFlagsFromShortFlags(value); + } + // 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 + ExpectedMemoryRegionInfo 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,8 @@ std::string name; name_extractor.GetHexByteString(name); region_info.SetName(name.c_str()); + } else if (name.equals("flags")) { + region_info.SetFlagsFromShortFlags(value); } 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 @@ -2574,6 +2574,13 @@ response.PutChar(';'); } + // Flags + if (region_info.HasFlags()) { + response.PutCString("flags:"); + response.PutCString(region_info.GetShortFlagNames()); + 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 @@ -273,13 +273,14 @@ 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; - }); + ParseLinuxMapRegions( + llvm::toStringRef(data), [&](ExpectedMemoryRegionInfo region) -> bool { + if (region) + regions.push_back(*region); + llvm::handleAllErrors(region.takeError(), + [](const llvm::StringError &e) {}); + 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 @@ -7,18 +7,126 @@ //===----------------------------------------------------------------------===// #include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/Log.h" using namespace lldb_private; +static const std::vector flag_infos = { + {MemoryRegionInfo::Flag::readable, "rd", "readable"}, + {MemoryRegionInfo::Flag::writeable, "wr", "writeable"}, + {MemoryRegionInfo::Flag::executable, "ex", "executable"}, + {MemoryRegionInfo::Flag::shared, "sh", "shared"}, + {MemoryRegionInfo::Flag::may_read, "mr", "may read"}, + {MemoryRegionInfo::Flag::may_write, "mw", "may write"}, + {MemoryRegionInfo::Flag::may_execute, "me", "may execute"}, + {MemoryRegionInfo::Flag::may_share, "ms", "may share"}, + {MemoryRegionInfo::Flag::stack_grows_down, "gd", + "stack segment grows down"}, + {MemoryRegionInfo::Flag::pure_PFN_range, "pf", "pure PFN range"}, + {MemoryRegionInfo::Flag::disabled_write_to_the_mapped_file, "dw", + "disabled write to the mapped file"}, + {MemoryRegionInfo::Flag::pages_are_locked_in_memory, "lo", + "pages are locked in memory"}, + {MemoryRegionInfo::Flag::memory_mapped_IO_area, "io", + "memory mapped I/O area"}, + {MemoryRegionInfo::Flag::sequential_read_advise_provided, "sr", + "sequential read advise provided"}, + {MemoryRegionInfo::Flag::random_read_advise_provided, "rr", + "random read advise provided"}, + {MemoryRegionInfo::Flag::do_not_copy_area_on_fork, "dc", + "do not copy area on fork"}, + {MemoryRegionInfo::Flag::do_not_expand_area_on_remapping, "de", + "do not expand area on remapping"}, + {MemoryRegionInfo::Flag::area_is_accountable, "ac", "area is accountable"}, + {MemoryRegionInfo::Flag::swap_space_is_not_reserved_for_the_area, "nr", + "swap space is not reserved for the area"}, + {MemoryRegionInfo::Flag::area_uses_huge_tlb_pages, "ht", + "area uses huge tlb pages"}, + {MemoryRegionInfo::Flag::perform_synchronous_page_faults, "sf", + "perform synchronous faults"}, + {MemoryRegionInfo::Flag::non_linear_mapping, "nl", "non-linear mapping"}, + {MemoryRegionInfo::Flag::architecture_specific_flag, "ar", + "architecture specific flag"}, + {MemoryRegionInfo::Flag::wipe_on_fork, "wf", "wipe on fork"}, + {MemoryRegionInfo::Flag::do_not_include_area_into_core_dump, "dd", + "do not include area into core dump"}, + {MemoryRegionInfo::Flag::soft_dirty, "sd", "soft-dirty"}, + {MemoryRegionInfo::Flag::mixed_map_area, "mm", "mixed map area"}, + {MemoryRegionInfo::Flag::huge_page_advise, "hg", "huge page advise"}, + {MemoryRegionInfo::Flag::no_huge_page_advise, "nh", "no-huge page advise"}, + {MemoryRegionInfo::Flag::mergeable_advise, "mg", "mergeable advise"}, + {MemoryRegionInfo::Flag::userfaultfd_missing_pages_tracking, "um", + "userfaultfd missing pages tracking"}, + {MemoryRegionInfo::Flag::userfaultfd_wprotect_pages_tracking, "uw", + "userfaultfd wprotect pages tracking"}, +}; + +void MemoryRegionInfo::SetFlagsFromShortFlags(llvm::StringRef flags) { + // Assuming a line of the format: + // ... + // E.g. "rd wr ex" + + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET); + + m_flags = std::set(); + llvm::StringRef flag; + while (flags.size()) { + flags = flags.ltrim(); + std::tie(flag, flags) = flags.split(' '); + // Account for trailing whitespace + if (flag.size()) { + auto it = std::find_if( + flag_infos.begin(), flag_infos.end(), + [&flag](const FlagInfo &info) { return flag == info.short_name; }); + if (it != flag_infos.end()) + m_flags->insert(it->flag); + else + LLDB_LOG(log, "Could not convert memory flag \"{0}\" from string", + flag); + } + } +} + +std::string MemoryRegionInfo::JoinFlags( + std::function getter, + llvm::StringRef sep) const { + if (HasFlags()) { + std::vector flags_strs; + + for (auto it = m_flags->begin(); it != m_flags->end(); ++it) { + auto flag_info = std::find_if( + flag_infos.begin(), flag_infos.end(), + [&it](const FlagInfo &info) { return info.flag == *it; }); + if (flag_info != flag_infos.end()) + flags_strs.push_back(getter(*flag_info)); + else + lldbassert( + false && + "Invalid flag in MemoryRegionInfo, could not convert to string"); + } + return llvm::join(flags_strs, sep); + } + return "?"; +} + +std::string MemoryRegionInfo::GetShortFlagNames() const { + return JoinFlags([](const FlagInfo &info) { return info.short_name; }, " "); +} + +std::string MemoryRegionInfo::GetLongFlagNames() const { + return JoinFlags([](const FlagInfo &info) { return info.long_name; }, "\n"); +} + 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}, \"{10}\")", Info.GetRange().GetRangeBase(), Info.GetRange().GetRangeEnd(), Info.GetReadable(), Info.GetWritable(), Info.GetExecutable(), Info.GetMapped(), Info.GetName(), Info.GetFlash(), - Info.GetBlocksize()); + Info.GetBlocksize(), Info.GetShortFlagNames()); } void llvm::format_provider::format( diff --git a/lldb/test/API/functionalities/memory-region/TestMemoryRegion.py b/lldb/test/API/functionalities/memory-region/TestMemoryRegion.py --- a/lldb/test/API/functionalities/memory-region/TestMemoryRegion.py +++ b/lldb/test/API/functionalities/memory-region/TestMemoryRegion.py @@ -8,6 +8,7 @@ from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil +from textwrap import dedent class MemoryCommandRegion(TestBase): @@ -23,7 +24,6 @@ 'main.cpp', '// Run here before printing memory regions') - def test(self): self.build() # Set breakpoint in main and run @@ -33,6 +33,11 @@ self.runCmd("run", RUN_SUCCEEDED) + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + def test(self): interp = self.dbg.GetCommandInterpreter() result = lldb.SBCommandReturnObject() @@ -62,3 +67,17 @@ interp.HandleCommand("memory region", result) self.assertFalse(result.Succeeded()) self.assertRegexpMatches(result.GetError(), "Usage: memory region ADDR") + + @skipUnlessLinux + @skipUnlessHasProcSmapsVmFlags() + def test_flags(self): + """Test that a kernel with smaps/VmFlags shows memory region flags.""" + self.expect("memory region main", + msg="Expected code address to be readable!", + patterns=[ + "\[0x[0-9A-Fa-f]+-0x[0-9A-Fa-f]+\)" + ], + substrs=[dedent("""\ + flags: + readable""")] + ) diff --git a/lldb/unittests/Process/CMakeLists.txt b/lldb/unittests/Process/CMakeLists.txt --- a/lldb/unittests/Process/CMakeLists.txt +++ b/lldb/unittests/Process/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(POSIX) endif() add_subdirectory(minidump) +add_subdirectory(Utility) add_lldb_unittest(ProcessEventDataTests ProcessEventDataTest.cpp diff --git a/lldb/unittests/Process/Utility/CMakeLists.txt b/lldb/unittests/Process/Utility/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/Utility/CMakeLists.txt @@ -0,0 +1,6 @@ +add_lldb_unittest(LinuxProcMapsTests + LinuxProcMapsTest.cpp + + LINK_LIBS + lldbPluginProcessLinux + ) 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,258 @@ +//===-- 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](ExpectedMemoryRegionInfo Info) { + if (Info) { + err_str.clear(); + regions.push_back(*Info); + return true; + } + + llvm::handleAllErrors( + Info.takeError(), + [this](const llvm::StringError &e) { err_str = e.getMessage(); }); + 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, llvm::None), + }, + "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, llvm::None), + }, + ""), + // 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, llvm::None), + MemoryRegionInfo(make_range(0xffffffffff600000, + 0xffffffffff601000), + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + ConstString("[vsyscall]"), + MemoryRegionInfo::eDontKnow, 0, llvm::None), + }, + "")), ); + +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, llvm::None), + }, + "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, llvm::None), + }, + ""), + // 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: rd wr ab cd", + MemoryRegionInfos{ + MemoryRegionInfo( + make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, + llvm::Optional("rd wr ab cd")), + }, + ""), + // Whitespace in flags line ignored, any number of chars per flag + std::make_tuple("0-0 rw-p 00000000 00:00 0\n" + "VmFlags: rd abc x yz ", + MemoryRegionInfos{ + MemoryRegionInfo( + make_range(0, 0), MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, + llvm::Optional("rd abc x yz")), + }, + ""), + // 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, + llvm::Optional("")), + }, + ""), + // 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: rd wr ab cd", + MemoryRegionInfos{ + MemoryRegionInfo(make_range(0x1111, 0x2222), + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + ConstString("[foo]"), + MemoryRegionInfo::eDontKnow, 0, llvm::None), + MemoryRegionInfo( + make_range(0x3333, 0x4444), MemoryRegionInfo::eYes, + MemoryRegionInfo::eNo, MemoryRegionInfo::eYes, + MemoryRegionInfo::eYes, ConstString("[bar]"), + MemoryRegionInfo::eDontKnow, 0, + llvm::Optional("rd wr ab cd")), + }, + ""), + // 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, llvm::None), + MemoryRegionInfo(make_range(0x3333, 0x4444), + MemoryRegionInfo::eYes, MemoryRegionInfo::eNo, + MemoryRegionInfo::eYes, MemoryRegionInfo::eYes, + ConstString(nullptr), + MemoryRegionInfo::eDontKnow, 0, llvm::None), + }, + "")), ); + +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,28 @@ EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetWritable()); EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetExecutable()); EXPECT_EQ("/foo/bar.so", region_info.GetName().GetStringRef()); + EXPECT_FALSE(region_info.HasFlags()); + EXPECT_EQ("?", region_info.GetShortFlagNames()); + + 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_TRUE(region_info.HasFlags()); + EXPECT_EQ("", region_info.GetShortFlagNames()); + + result = std::async(std::launch::async, [&] { + return client.GetMemoryRegionInfo(addr, region_info); + }); + + HandlePacket(server, "qMemoryRegionInfo:a000", + "start:a000;size:2000;flags: mr mw me ;"); + EXPECT_TRUE(result.get().Success()); + EXPECT_TRUE(region_info.HasFlags()); + EXPECT_EQ("mr mw me", region_info.GetShortFlagNames()); } 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 @@ -374,20 +374,20 @@ )"), llvm::Succeeded()); - EXPECT_THAT( - parser->BuildMemoryRegions(), - testing::Pair(testing::ElementsAre( - MemoryRegionInfo({0x0, 0x10000}, no, no, no, no, - ConstString(), unknown, 0), - MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, yes, - ConstString(), unknown, 0), - MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, yes, - ConstString(), unknown, 0), - MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, yes, - ConstString(), unknown, 0), - MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, yes, - ConstString(), unknown, 0)), - true)); + EXPECT_THAT(parser->BuildMemoryRegions(), + testing::Pair( + testing::ElementsAre( + MemoryRegionInfo({0x0, 0x10000}, no, no, no, no, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, yes, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, yes, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, yes, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, yes, + ConstString(), unknown, 0, llvm::None)), + true)); } TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemoryList) { @@ -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, llvm::None), + MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, llvm::None)), + 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, llvm::None), + MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes, + ConstString(), unknown, 0, llvm::None)), + 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, llvm::None), + MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, yes, + app_process, unknown, 0, llvm::None), + MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, yes, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, yes, + ConstString(), unknown, 0, llvm::None), + MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, yes, + linker, unknown, 0, llvm::None), + MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, yes, + liblog, unknown, 0, llvm::None)), + 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, llvm::None)), + true)); } // Windows Minidump tests diff --git a/lldb/unittests/Target/MemoryRegionInfoTest.cpp b/lldb/unittests/Target/MemoryRegionInfoTest.cpp --- a/lldb/unittests/Target/MemoryRegionInfoTest.cpp +++ b/lldb/unittests/Target/MemoryRegionInfoTest.cpp @@ -17,3 +17,42 @@ EXPECT_EQ("no", llvm::formatv("{0}", MemoryRegionInfo::eNo).str()); EXPECT_EQ("don't know", llvm::formatv("{0}", MemoryRegionInfo::eDontKnow).str()); } + +typedef std::pair MemoryRegionInfoFlagTestParams; + +class MemoryRegionInfoFlagTestFixture + : public ::testing::TestWithParam { +protected: + void do_test(MemoryRegionInfoFlagTestParams params) { + MemoryRegionInfo info; + const char *in; + std::string expected; + std::tie(in, expected) = params; + info.SetFlagsFromShortFlags(in); + ASSERT_TRUE(info.HasFlags()); + ASSERT_EQ(expected, info.GetShortFlagNames()); + } +}; + +static MemoryRegionInfoFlagTestParams make_roundtrip(const char *flag) { + return std::make_pair(flag, flag); +} + +TEST_P(MemoryRegionInfoFlagTestFixture, TestShortFlags) { do_test(GetParam()); } + +INSTANTIATE_TEST_CASE_P( + MemoryRegionInfoFlagsTests, MemoryRegionInfoFlagTestFixture, + ::testing::Values( + std::make_pair("", ""), std::make_pair(" ", ""), + std::make_pair("rd", "rd"), std::make_pair("rd wr ex", "rd wr ex"), + // Output order is the same as the flags enum + std::make_pair("ex wr rd", "rd wr ex"), + // Duplicates ignored + std::make_pair("ex rd ex", "rd ex"), + // Unknown flags ignored + std::make_pair("ex rd zz", "rd ex"), std::make_pair("foo bar", ""), + // Spaces ignored + std::make_pair(" ex rd wr ", "rd wr ex"), + // Roundtrip all flags we currently support + make_roundtrip("rd wr ex sh mr mw me ms gd pf dw lo io sr rr dc de " + "ac nr ht sf nl ar wf dd sd mm hg nh mg um uw")), );