211 lines · cpp
1//===-- LinuxProcMaps.cpp -------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "LinuxProcMaps.h"10#include "lldb/Target/MemoryRegionInfo.h"11#include "lldb/Utility/Status.h"12#include "lldb/Utility/StringExtractor.h"13#include "llvm/ADT/StringRef.h"14#include <optional>15 16using namespace lldb_private;17 18enum class MapsKind { Maps, SMaps };19 20static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,21 MapsKind kind) {22 return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,23 kind == MapsKind::Maps ? "maps" : "smaps");24}25 26static llvm::Expected<MemoryRegionInfo>27ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,28 MapsKind maps_kind) {29 MemoryRegionInfo region;30 StringExtractor line_extractor(maps_line);31 32 // Format: {address_start_hex}-{address_end_hex} perms offset dev inode33 // pathname perms: rwxp (letter is present if set, '-' if not, final34 // character is p=private, s=shared).35 36 // Parse out the starting address37 lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);38 39 // Parse out hyphen separating start and end address from range.40 if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))41 return ProcMapError(42 "malformed /proc/{pid}/%s entry, missing dash between address range",43 maps_kind);44 45 // Parse out the ending address46 lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);47 48 // Parse out the space after the address.49 if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))50 return ProcMapError(51 "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);52 53 // Save the range.54 region.GetRange().SetRangeBase(start_address);55 region.GetRange().SetRangeEnd(end_address);56 57 // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped58 // into the process.59 region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);60 61 // Parse out each permission entry.62 if (line_extractor.GetBytesLeft() < 4)63 return ProcMapError(64 "malformed /proc/{pid}/%s entry, missing some portion of "65 "permissions",66 maps_kind);67 68 // Handle read permission.69 const char read_perm_char = line_extractor.GetChar();70 if (read_perm_char == 'r')71 region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);72 else if (read_perm_char == '-')73 region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);74 else75 return ProcMapError("unexpected /proc/{pid}/%s read permission char",76 maps_kind);77 78 // Handle write permission.79 const char write_perm_char = line_extractor.GetChar();80 if (write_perm_char == 'w')81 region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);82 else if (write_perm_char == '-')83 region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);84 else85 return ProcMapError("unexpected /proc/{pid}/%s write permission char",86 maps_kind);87 88 // Handle execute permission.89 const char exec_perm_char = line_extractor.GetChar();90 if (exec_perm_char == 'x')91 region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);92 else if (exec_perm_char == '-')93 region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);94 else95 return ProcMapError("unexpected /proc/{pid}/%s exec permission char",96 maps_kind);97 98 // Handle sharing status (private/shared).99 const char sharing_char = line_extractor.GetChar();100 if (sharing_char == 's')101 region.SetShared(MemoryRegionInfo::OptionalBool::eYes);102 else if (sharing_char == 'p')103 region.SetShared(MemoryRegionInfo::OptionalBool::eNo);104 else105 region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow);106 107 line_extractor.SkipSpaces(); // Skip the separator108 line_extractor.GetHexMaxU64(false, 0); // Read the offset109 line_extractor.GetHexMaxU64(false, 0); // Read the major device number110 line_extractor.GetChar(); // Read the device id separator111 line_extractor.GetHexMaxU64(false, 0); // Read the major device number112 line_extractor.SkipSpaces(); // Skip the separator113 line_extractor.GetU64(0, 10); // Read the inode number114 115 line_extractor.SkipSpaces();116 const char *name = line_extractor.Peek();117 if (name)118 region.SetName(name);119 120 return region;121}122 123void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,124 LinuxMapCallback const &callback) {125 llvm::StringRef lines(linux_map);126 llvm::StringRef line;127 while (!lines.empty()) {128 std::tie(line, lines) = lines.split('\n');129 if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))130 break;131 }132}133 134void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,135 LinuxMapCallback const &callback) {136 // Entries in /smaps look like:137 // 00400000-0048a000 r-xp 00000000 fd:03 960637138 // Size: 552 kB139 // Rss: 460 kB140 // <...>141 // VmFlags: rd ex mr mw me dw142 // 00500000-0058a000 rwxp 00000000 fd:03 960637143 // <...>144 //145 // Where the first line is identical to the /maps format146 // and VmFlags is only printed for kernels >= 3.8.147 148 llvm::StringRef lines(linux_smap);149 llvm::StringRef line;150 std::optional<MemoryRegionInfo> region;151 152 while (lines.size()) {153 std::tie(line, lines) = lines.split('\n');154 155 // A property line looks like:156 // <word>: <value>157 // (no spaces on the left hand side)158 // A header will have a ':' but the LHS will contain spaces159 llvm::StringRef name;160 llvm::StringRef value;161 std::tie(name, value) = line.split(':');162 163 // If this line is a property line164 if (!name.contains(' ')) {165 if (region) {166 if (name == "VmFlags") {167 region->SetMemoryTagged(MemoryRegionInfo::eNo);168 region->SetIsShadowStack(MemoryRegionInfo::eNo);169 170 llvm::SmallVector<llvm::StringRef> flags;171 value.split(flags, ' ', /*MaxSplit=*/-1, /*KeepEmpty=*/false);172 for (llvm::StringRef flag : flags)173 if (flag == "mt")174 region->SetMemoryTagged(MemoryRegionInfo::eYes);175 else if (flag == "ss")176 region->SetIsShadowStack(MemoryRegionInfo::eYes);177 }178 } else {179 // Orphaned settings line180 callback(ProcMapError(181 "Found a property line without a corresponding mapping "182 "in /proc/{pid}/%s",183 MapsKind::SMaps));184 return;185 }186 } else {187 // Must be a new region header188 if (region) {189 // Save current region190 callback(*region);191 region.reset();192 }193 194 // Try to start a new region195 llvm::Expected<MemoryRegionInfo> new_region =196 ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);197 if (new_region) {198 region = *new_region;199 } else {200 // Stop at first invalid region header201 callback(new_region.takeError());202 return;203 }204 }205 }206 207 // Catch last region208 if (region)209 callback(*region);210}211