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
ProcMapError(const char * msg,MapsKind kind)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>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)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
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)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
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)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 if (value.contains("mt"))
168 region->SetMemoryTagged(MemoryRegionInfo::eYes);
169 else
170 region->SetMemoryTagged(MemoryRegionInfo::eNo);
171 }
172 // Ignore anything else
173 } else {
174 // Orphaned settings line
175 callback(ProcMapError(
176 "Found a property line without a corresponding mapping "
177 "in /proc/{pid}/%s",
178 MapsKind::SMaps));
179 return;
180 }
181 } else {
182 // Must be a new region header
183 if (region) {
184 // Save current region
185 callback(*region);
186 region.reset();
187 }
188
189 // Try to start a new region
190 llvm::Expected<MemoryRegionInfo> new_region =
191 ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
192 if (new_region) {
193 region = *new_region;
194 } else {
195 // Stop at first invalid region header
196 callback(new_region.takeError());
197 return;
198 }
199 }
200 }
201
202 // Catch last region
203 if (region)
204 callback(*region);
205 }
206