| //===-- 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" |
| |
| 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(MemoryRegionInfo::OptionalBool::eYes); |
| |
| // 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(MemoryRegionInfo::OptionalBool::eYes); |
| else if (read_perm_char == '-') |
| region.SetReadable(MemoryRegionInfo::OptionalBool::eNo); |
| 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(MemoryRegionInfo::OptionalBool::eYes); |
| else if (write_perm_char == '-') |
| region.SetWritable(MemoryRegionInfo::OptionalBool::eNo); |
| 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(MemoryRegionInfo::OptionalBool::eYes); |
| else if (exec_perm_char == '-') |
| region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo); |
| else |
| 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 |
| 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); |
| |
| 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; |
| llvm::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") { |
| 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<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); |
| } |