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(EC: llvm::inconvertibleErrorCode(), Fmt: msg, |
23 | Vals: 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 (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(little_endian: false, fail_value: 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 | msg: "malformed /proc/{pid}/%s entry, missing dash between address range" , |
43 | kind: maps_kind); |
44 | |
45 | // Parse out the ending address |
46 | lldb::addr_t end_address = line_extractor.GetHexMaxU64(little_endian: false, fail_value: start_address); |
47 | |
48 | // Parse out the space after the address. |
49 | if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' ')) |
50 | return ProcMapError( |
51 | msg: "malformed /proc/{pid}/%s entry, missing space after range" , kind: 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 | msg: "malformed /proc/{pid}/%s entry, missing some portion of " |
65 | "permissions" , |
66 | kind: 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(msg: "unexpected /proc/{pid}/%s read permission char" , |
76 | kind: 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(msg: "unexpected /proc/{pid}/%s write permission char" , |
86 | kind: 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(msg: "unexpected /proc/{pid}/%s exec permission char" , |
96 | kind: 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(little_endian: false, fail_value: 0); // Read the offset |
109 | line_extractor.GetHexMaxU64(little_endian: false, fail_value: 0); // Read the major device number |
110 | line_extractor.GetChar(); // Read the device id separator |
111 | line_extractor.GetHexMaxU64(little_endian: false, fail_value: 0); // Read the major device number |
112 | line_extractor.SkipSpaces(); // Skip the separator |
113 | line_extractor.GetU64(fail_value: 0, base: 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(args&: line, args&: lines) = lines.split(Separator: '\n'); |
129 | if (!callback(ParseMemoryRegionInfoFromProcMapsLine(maps_line: line, maps_kind: 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(args&: line, args&: lines) = lines.split(Separator: '\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(args&: name, args&: value) = line.split(Separator: ':'); |
162 | |
163 | // If this line is a property line |
164 | if (!name.contains(C: ' ')) { |
165 | if (region) { |
166 | if (name == "VmFlags" ) { |
167 | if (value.contains(Other: "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 | msg: "Found a property line without a corresponding mapping " |
177 | "in /proc/{pid}/%s" , |
178 | kind: 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(maps_line: line, maps_kind: 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 | |