| //===-- LinuxProcMaps.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 "LinuxProcMaps.h" | |
| #include "lldb/Target/MemoryRegionInfo.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/StringExtractor.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include <optional> | |
| using namespace lldb_private; | |
| enum class MapsKind { Maps, SMaps }; | |
| static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg, | |
| MapsKind kind) { | |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), msg, | |
| kind == MapsKind::Maps ? "maps" : "smaps"); | |
| } | |
| static llvm::Expected<MemoryRegionInfo> | |
| ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line, | |
| 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 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 ProcMapError( | |
| "malformed /proc/{pid}/%s entry, missing space after range", maps_kind); | |
| // Save the range. | |
| 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(eLazyBoolYes); | |
| // Parse out each permission entry. | |
| if (line_extractor.GetBytesLeft() < 4) | |
| 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') | |
| region.SetReadable(eLazyBoolYes); | |
| else if (read_perm_char == '-') | |
| region.SetReadable(eLazyBoolNo); | |
| else | |
| 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') | |
| region.SetWritable(eLazyBoolYes); | |
| else if (write_perm_char == '-') | |
| region.SetWritable(eLazyBoolNo); | |
| else | |
| 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') | |
| region.SetExecutable(eLazyBoolYes); | |
| else if (exec_perm_char == '-') | |
| region.SetExecutable(eLazyBoolNo); | |
| else | |
| return ProcMapError("unexpected /proc/{pid}/%s exec permission char", | |
| maps_kind); | |
| // Handle sharing status (private/shared). | |
| const char sharing_char = line_extractor.GetChar(); | |
| if (sharing_char == 's') | |
| region.SetShared(eLazyBoolYes); | |
| else if (sharing_char == 'p') | |
| region.SetShared(eLazyBoolNo); | |
| else | |
| region.SetShared(eLazyBoolDontKnow); | |
| line_extractor.SkipSpaces(); // Skip the separator | |
| line_extractor.GetHexMaxU64(false, 0); // Read the offset | |
| line_extractor.GetHexMaxU64(false, 0); // Read the major device number | |
| line_extractor.GetChar(); // Read the device id separator | |
| 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) { | |
| region.SetName(name); | |
| // /proc/maps labels only the main thread's stack; thread stacks are | |
| // anonymous and the default MemoryRegionInfo::eDontKnow is kept. | |
| if (llvm::StringRef(name) == "[stack]") | |
| region.SetIsStackMemory(eLazyBoolYes); | |
| else if (llvm::StringRef(name) == "[heap]") | |
| region.SetIsStackMemory(eLazyBoolNo); | |
| else | |
| region.SetIsStackMemory(eLazyBoolDontKnow); | |
| } | |
| return region; | |
| } | |
| void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map, | |
| LinuxMapCallback const &callback) { | |
| llvm::StringRef lines(linux_map); | |
| llvm::StringRef line; | |
| while (!lines.empty()) { | |
| std::tie(line, lines) = lines.split('\n'); | |
| 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; | |
| std::optional<MemoryRegionInfo> region; | |
| while (lines.size()) { | |
| std::tie(line, lines) = lines.split('\n'); | |
| // A property line looks like: | |
| // <word>: <value> | |
| // (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") { | |
| region->SetMemoryTagged(eLazyBoolNo); | |
| region->SetIsShadowStack(eLazyBoolNo); | |
| llvm::SmallVector<llvm::StringRef> flags; | |
| value.split(flags, ' ', /*MaxSplit=*/-1, /*KeepEmpty=*/false); | |
| for (llvm::StringRef flag : flags) | |
| if (flag == "mt") | |
| region->SetMemoryTagged(eLazyBoolYes); | |
| else if (flag == "ss") | |
| region->SetIsShadowStack(eLazyBoolYes); | |
| } else if (name == "ProtectionKey") { | |
| unsigned key = 0; | |
| if (!value.ltrim().getAsInteger(10, key)) | |
| region->SetProtectionKey(key); | |
| } | |
| } 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<MemoryRegionInfo> 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); | |
| } |