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
15 using namespace lldb_private;
16
17 enum class MapsKind { Maps, SMaps };
18
ProcMapError(const char * msg,MapsKind kind)19 static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,
20 MapsKind kind) {
21 return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,
22 kind == MapsKind::Maps ? "maps" : "smaps");
23 }
24
25 static llvm::Expected<MemoryRegionInfo>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)26 ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
27 MapsKind maps_kind) {
28 MemoryRegionInfo region;
29 StringExtractor line_extractor(maps_line);
30
31 // Format: {address_start_hex}-{address_end_hex} perms offset dev inode
32 // pathname perms: rwxp (letter is present if set, '-' if not, final
33 // character is p=private, s=shared).
34
35 // Parse out the starting address
36 lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);
37
38 // Parse out hyphen separating start and end address from range.
39 if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))
40 return ProcMapError(
41 "malformed /proc/{pid}/%s entry, missing dash between address range",
42 maps_kind);
43
44 // Parse out the ending address
45 lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);
46
47 // Parse out the space after the address.
48 if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))
49 return ProcMapError(
50 "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);
51
52 // Save the range.
53 region.GetRange().SetRangeBase(start_address);
54 region.GetRange().SetRangeEnd(end_address);
55
56 // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped
57 // into the process.
58 region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
59
60 // Parse out each permission entry.
61 if (line_extractor.GetBytesLeft() < 4)
62 return ProcMapError(
63 "malformed /proc/{pid}/%s entry, missing some portion of "
64 "permissions",
65 maps_kind);
66
67 // Handle read permission.
68 const char read_perm_char = line_extractor.GetChar();
69 if (read_perm_char == 'r')
70 region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
71 else if (read_perm_char == '-')
72 region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
73 else
74 return ProcMapError("unexpected /proc/{pid}/%s read permission char",
75 maps_kind);
76
77 // Handle write permission.
78 const char write_perm_char = line_extractor.GetChar();
79 if (write_perm_char == 'w')
80 region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
81 else if (write_perm_char == '-')
82 region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
83 else
84 return ProcMapError("unexpected /proc/{pid}/%s write permission char",
85 maps_kind);
86
87 // Handle execute permission.
88 const char exec_perm_char = line_extractor.GetChar();
89 if (exec_perm_char == 'x')
90 region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
91 else if (exec_perm_char == '-')
92 region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
93 else
94 return ProcMapError("unexpected /proc/{pid}/%s exec permission char",
95 maps_kind);
96
97 // Handle sharing status (private/shared).
98 const char sharing_char = line_extractor.GetChar();
99 if (sharing_char == 's')
100 region.SetShared(MemoryRegionInfo::OptionalBool::eYes);
101 else if (sharing_char == 'p')
102 region.SetShared(MemoryRegionInfo::OptionalBool::eNo);
103 else
104 region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow);
105
106 line_extractor.SkipSpaces(); // Skip the separator
107 line_extractor.GetHexMaxU64(false, 0); // Read the offset
108 line_extractor.GetHexMaxU64(false, 0); // Read the major device number
109 line_extractor.GetChar(); // Read the device id separator
110 line_extractor.GetHexMaxU64(false, 0); // Read the major device number
111 line_extractor.SkipSpaces(); // Skip the separator
112 line_extractor.GetU64(0, 10); // Read the inode number
113
114 line_extractor.SkipSpaces();
115 const char *name = line_extractor.Peek();
116 if (name)
117 region.SetName(name);
118
119 return region;
120 }
121
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)122 void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,
123 LinuxMapCallback const &callback) {
124 llvm::StringRef lines(linux_map);
125 llvm::StringRef line;
126 while (!lines.empty()) {
127 std::tie(line, lines) = lines.split('\n');
128 if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))
129 break;
130 }
131 }
132
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)133 void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
134 LinuxMapCallback const &callback) {
135 // Entries in /smaps look like:
136 // 00400000-0048a000 r-xp 00000000 fd:03 960637
137 // Size: 552 kB
138 // Rss: 460 kB
139 // <...>
140 // VmFlags: rd ex mr mw me dw
141 // 00500000-0058a000 rwxp 00000000 fd:03 960637
142 // <...>
143 //
144 // Where the first line is identical to the /maps format
145 // and VmFlags is only printed for kernels >= 3.8.
146
147 llvm::StringRef lines(linux_smap);
148 llvm::StringRef line;
149 llvm::Optional<MemoryRegionInfo> region;
150
151 while (lines.size()) {
152 std::tie(line, lines) = lines.split('\n');
153
154 // A property line looks like:
155 // <word>: <value>
156 // (no spaces on the left hand side)
157 // A header will have a ':' but the LHS will contain spaces
158 llvm::StringRef name;
159 llvm::StringRef value;
160 std::tie(name, value) = line.split(':');
161
162 // If this line is a property line
163 if (!name.contains(' ')) {
164 if (region) {
165 if (name == "VmFlags") {
166 if (value.contains("mt"))
167 region->SetMemoryTagged(MemoryRegionInfo::eYes);
168 else
169 region->SetMemoryTagged(MemoryRegionInfo::eNo);
170 }
171 // Ignore anything else
172 } else {
173 // Orphaned settings line
174 callback(ProcMapError(
175 "Found a property line without a corresponding mapping "
176 "in /proc/{pid}/%s",
177 MapsKind::SMaps));
178 return;
179 }
180 } else {
181 // Must be a new region header
182 if (region) {
183 // Save current region
184 callback(*region);
185 region.reset();
186 }
187
188 // Try to start a new region
189 llvm::Expected<MemoryRegionInfo> new_region =
190 ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
191 if (new_region) {
192 region = *new_region;
193 } else {
194 // Stop at first invalid region header
195 callback(new_region.takeError());
196 return;
197 }
198 }
199 }
200
201 // Catch last region
202 if (region)
203 callback(*region);
204 }
205