1dda28197Spatrick //===-- LinuxProcMaps.cpp -------------------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick 
9061da546Spatrick #include "LinuxProcMaps.h"
10061da546Spatrick #include "lldb/Target/MemoryRegionInfo.h"
11061da546Spatrick #include "lldb/Utility/Status.h"
12061da546Spatrick #include "lldb/Utility/StringExtractor.h"
13be691f3bSpatrick #include "llvm/ADT/StringRef.h"
14*f6aab3d8Srobert #include <optional>
15061da546Spatrick 
16061da546Spatrick using namespace lldb_private;
17061da546Spatrick 
18be691f3bSpatrick enum class MapsKind { Maps, SMaps };
19061da546Spatrick 
ProcMapError(const char * msg,MapsKind kind)20be691f3bSpatrick static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,
21be691f3bSpatrick                                                      MapsKind kind) {
22be691f3bSpatrick   return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,
23be691f3bSpatrick                                  kind == MapsKind::Maps ? "maps" : "smaps");
24be691f3bSpatrick }
25be691f3bSpatrick 
26be691f3bSpatrick static llvm::Expected<MemoryRegionInfo>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)27be691f3bSpatrick ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
28be691f3bSpatrick                                       MapsKind maps_kind) {
29be691f3bSpatrick   MemoryRegionInfo region;
30061da546Spatrick   StringExtractor line_extractor(maps_line);
31061da546Spatrick 
32061da546Spatrick   // Format: {address_start_hex}-{address_end_hex} perms offset  dev   inode
33061da546Spatrick   // pathname perms: rwxp   (letter is present if set, '-' if not, final
34061da546Spatrick   // character is p=private, s=shared).
35061da546Spatrick 
36061da546Spatrick   // Parse out the starting address
37061da546Spatrick   lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);
38061da546Spatrick 
39061da546Spatrick   // Parse out hyphen separating start and end address from range.
40061da546Spatrick   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))
41be691f3bSpatrick     return ProcMapError(
42be691f3bSpatrick         "malformed /proc/{pid}/%s entry, missing dash between address range",
43be691f3bSpatrick         maps_kind);
44061da546Spatrick 
45061da546Spatrick   // Parse out the ending address
46061da546Spatrick   lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);
47061da546Spatrick 
48061da546Spatrick   // Parse out the space after the address.
49061da546Spatrick   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))
50be691f3bSpatrick     return ProcMapError(
51be691f3bSpatrick         "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);
52061da546Spatrick 
53061da546Spatrick   // Save the range.
54be691f3bSpatrick   region.GetRange().SetRangeBase(start_address);
55be691f3bSpatrick   region.GetRange().SetRangeEnd(end_address);
56061da546Spatrick 
57be691f3bSpatrick   // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped
58be691f3bSpatrick   // into the process.
59be691f3bSpatrick   region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
60061da546Spatrick 
61061da546Spatrick   // Parse out each permission entry.
62061da546Spatrick   if (line_extractor.GetBytesLeft() < 4)
63be691f3bSpatrick     return ProcMapError(
64be691f3bSpatrick         "malformed /proc/{pid}/%s entry, missing some portion of "
65be691f3bSpatrick         "permissions",
66be691f3bSpatrick         maps_kind);
67061da546Spatrick 
68061da546Spatrick   // Handle read permission.
69061da546Spatrick   const char read_perm_char = line_extractor.GetChar();
70061da546Spatrick   if (read_perm_char == 'r')
71be691f3bSpatrick     region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
72061da546Spatrick   else if (read_perm_char == '-')
73be691f3bSpatrick     region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
74061da546Spatrick   else
75be691f3bSpatrick     return ProcMapError("unexpected /proc/{pid}/%s read permission char",
76be691f3bSpatrick                         maps_kind);
77061da546Spatrick 
78061da546Spatrick   // Handle write permission.
79061da546Spatrick   const char write_perm_char = line_extractor.GetChar();
80061da546Spatrick   if (write_perm_char == 'w')
81be691f3bSpatrick     region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
82061da546Spatrick   else if (write_perm_char == '-')
83be691f3bSpatrick     region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
84061da546Spatrick   else
85be691f3bSpatrick     return ProcMapError("unexpected /proc/{pid}/%s write permission char",
86be691f3bSpatrick                         maps_kind);
87061da546Spatrick 
88061da546Spatrick   // Handle execute permission.
89061da546Spatrick   const char exec_perm_char = line_extractor.GetChar();
90061da546Spatrick   if (exec_perm_char == 'x')
91be691f3bSpatrick     region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
92061da546Spatrick   else if (exec_perm_char == '-')
93be691f3bSpatrick     region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
94061da546Spatrick   else
95be691f3bSpatrick     return ProcMapError("unexpected /proc/{pid}/%s exec permission char",
96be691f3bSpatrick                         maps_kind);
97061da546Spatrick 
98*f6aab3d8Srobert   // Handle sharing status (private/shared).
99*f6aab3d8Srobert   const char sharing_char = line_extractor.GetChar();
100*f6aab3d8Srobert   if (sharing_char == 's')
101*f6aab3d8Srobert     region.SetShared(MemoryRegionInfo::OptionalBool::eYes);
102*f6aab3d8Srobert   else if (sharing_char == 'p')
103*f6aab3d8Srobert     region.SetShared(MemoryRegionInfo::OptionalBool::eNo);
104*f6aab3d8Srobert   else
105*f6aab3d8Srobert     region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow);
106*f6aab3d8Srobert 
107061da546Spatrick   line_extractor.SkipSpaces();           // Skip the separator
108061da546Spatrick   line_extractor.GetHexMaxU64(false, 0); // Read the offset
109061da546Spatrick   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
110061da546Spatrick   line_extractor.GetChar();              // Read the device id separator
111061da546Spatrick   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
112061da546Spatrick   line_extractor.SkipSpaces();           // Skip the separator
113061da546Spatrick   line_extractor.GetU64(0, 10);          // Read the inode number
114061da546Spatrick 
115061da546Spatrick   line_extractor.SkipSpaces();
116061da546Spatrick   const char *name = line_extractor.Peek();
117061da546Spatrick   if (name)
118be691f3bSpatrick     region.SetName(name);
119061da546Spatrick 
120be691f3bSpatrick   return region;
121061da546Spatrick }
122061da546Spatrick 
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)123061da546Spatrick void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,
124061da546Spatrick                                         LinuxMapCallback const &callback) {
125061da546Spatrick   llvm::StringRef lines(linux_map);
126061da546Spatrick   llvm::StringRef line;
127061da546Spatrick   while (!lines.empty()) {
128061da546Spatrick     std::tie(line, lines) = lines.split('\n');
129be691f3bSpatrick     if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))
130061da546Spatrick       break;
131061da546Spatrick   }
132061da546Spatrick }
133be691f3bSpatrick 
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)134be691f3bSpatrick void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
135be691f3bSpatrick                                          LinuxMapCallback const &callback) {
136be691f3bSpatrick   // Entries in /smaps look like:
137be691f3bSpatrick   // 00400000-0048a000 r-xp 00000000 fd:03 960637
138be691f3bSpatrick   // Size:                552 kB
139be691f3bSpatrick   // Rss:                 460 kB
140be691f3bSpatrick   // <...>
141be691f3bSpatrick   // VmFlags: rd ex mr mw me dw
142be691f3bSpatrick   // 00500000-0058a000 rwxp 00000000 fd:03 960637
143be691f3bSpatrick   // <...>
144be691f3bSpatrick   //
145be691f3bSpatrick   // Where the first line is identical to the /maps format
146be691f3bSpatrick   // and VmFlags is only printed for kernels >= 3.8.
147be691f3bSpatrick 
148be691f3bSpatrick   llvm::StringRef lines(linux_smap);
149be691f3bSpatrick   llvm::StringRef line;
150*f6aab3d8Srobert   std::optional<MemoryRegionInfo> region;
151be691f3bSpatrick 
152be691f3bSpatrick   while (lines.size()) {
153be691f3bSpatrick     std::tie(line, lines) = lines.split('\n');
154be691f3bSpatrick 
155be691f3bSpatrick     // A property line looks like:
156be691f3bSpatrick     // <word>: <value>
157be691f3bSpatrick     // (no spaces on the left hand side)
158be691f3bSpatrick     // A header will have a ':' but the LHS will contain spaces
159be691f3bSpatrick     llvm::StringRef name;
160be691f3bSpatrick     llvm::StringRef value;
161be691f3bSpatrick     std::tie(name, value) = line.split(':');
162be691f3bSpatrick 
163be691f3bSpatrick     // If this line is a property line
164be691f3bSpatrick     if (!name.contains(' ')) {
165be691f3bSpatrick       if (region) {
166be691f3bSpatrick         if (name == "VmFlags") {
167be691f3bSpatrick           if (value.contains("mt"))
168be691f3bSpatrick             region->SetMemoryTagged(MemoryRegionInfo::eYes);
169be691f3bSpatrick           else
170be691f3bSpatrick             region->SetMemoryTagged(MemoryRegionInfo::eNo);
171be691f3bSpatrick         }
172be691f3bSpatrick         // Ignore anything else
173be691f3bSpatrick       } else {
174be691f3bSpatrick         // Orphaned settings line
175be691f3bSpatrick         callback(ProcMapError(
176be691f3bSpatrick             "Found a property line without a corresponding mapping "
177be691f3bSpatrick             "in /proc/{pid}/%s",
178be691f3bSpatrick             MapsKind::SMaps));
179be691f3bSpatrick         return;
180be691f3bSpatrick       }
181be691f3bSpatrick     } else {
182be691f3bSpatrick       // Must be a new region header
183be691f3bSpatrick       if (region) {
184be691f3bSpatrick         // Save current region
185be691f3bSpatrick         callback(*region);
186be691f3bSpatrick         region.reset();
187be691f3bSpatrick       }
188be691f3bSpatrick 
189be691f3bSpatrick       // Try to start a new region
190be691f3bSpatrick       llvm::Expected<MemoryRegionInfo> new_region =
191be691f3bSpatrick           ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
192be691f3bSpatrick       if (new_region) {
193be691f3bSpatrick         region = *new_region;
194be691f3bSpatrick       } else {
195be691f3bSpatrick         // Stop at first invalid region header
196be691f3bSpatrick         callback(new_region.takeError());
197be691f3bSpatrick         return;
198be691f3bSpatrick       }
199be691f3bSpatrick     }
200be691f3bSpatrick   }
201be691f3bSpatrick 
202be691f3bSpatrick   // Catch last region
203be691f3bSpatrick   if (region)
204be691f3bSpatrick     callback(*region);
205be691f3bSpatrick }
206