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-exception 6 // 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 16 using namespace lldb_private; 17 18 enum class MapsKind { Maps, SMaps }; 19 20 static 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 26 static llvm::Expected<MemoryRegionInfo> 27 ParseMemoryRegionInfoFromProcMapsLine(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 inode 33 // pathname perms: rwxp (letter is present if set, '-' if not, final 34 // character is p=private, s=shared). 35 36 // Parse out the starting address 37 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 address 46 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 mapped 58 // 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 else 75 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 else 85 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 else 95 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 else 105 region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow); 106 107 line_extractor.SkipSpaces(); // Skip the separator 108 line_extractor.GetHexMaxU64(false, 0); // Read the offset 109 line_extractor.GetHexMaxU64(false, 0); // Read the major device number 110 line_extractor.GetChar(); // Read the device id separator 111 line_extractor.GetHexMaxU64(false, 0); // Read the major device number 112 line_extractor.SkipSpaces(); // Skip the separator 113 line_extractor.GetU64(0, 10); // Read the inode number 114 115 line_extractor.SkipSpaces(); 116 const char *name = line_extractor.Peek(); 117 if (name) 118 region.SetName(name); 119 120 return region; 121 } 122 123 void 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 134 void 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 960637 138 // Size: 552 kB 139 // Rss: 460 kB 140 // <...> 141 // VmFlags: rd ex mr mw me dw 142 // 00500000-0058a000 rwxp 00000000 fd:03 960637 143 // <...> 144 // 145 // Where the first line is identical to the /maps format 146 // 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 spaces 159 llvm::StringRef name; 160 llvm::StringRef value; 161 std::tie(name, value) = line.split(':'); 162 163 // If this line is a property line 164 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 line 180 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 header 188 if (region) { 189 // Save current region 190 callback(*region); 191 region.reset(); 192 } 193 194 // Try to start a new region 195 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 header 201 callback(new_region.takeError()); 202 return; 203 } 204 } 205 } 206 207 // Catch last region 208 if (region) 209 callback(*region); 210 } 211