1*0b57cec5SDimitry Andric //===-- LinuxProcMaps.cpp -------------------------------------------------===//
2*0b57cec5SDimitry Andric //
3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0b57cec5SDimitry Andric //
7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
8*0b57cec5SDimitry Andric 
9*0b57cec5SDimitry Andric #include "LinuxProcMaps.h"
10*0b57cec5SDimitry Andric #include "lldb/Target/MemoryRegionInfo.h"
11*0b57cec5SDimitry Andric #include "lldb/Utility/Status.h"
12*0b57cec5SDimitry Andric #include "lldb/Utility/StringExtractor.h"
13*0b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
14*0b57cec5SDimitry Andric #include <optional>
15*0b57cec5SDimitry Andric 
16*0b57cec5SDimitry Andric using namespace lldb_private;
17*0b57cec5SDimitry Andric 
18*0b57cec5SDimitry Andric enum class MapsKind { Maps, SMaps };
19*0b57cec5SDimitry Andric 
ProcMapError(const char * msg,MapsKind kind)20*0b57cec5SDimitry Andric static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,
21*0b57cec5SDimitry Andric                                                      MapsKind kind) {
22*0b57cec5SDimitry Andric   return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,
23*0b57cec5SDimitry Andric                                  kind == MapsKind::Maps ? "maps" : "smaps");
24*0b57cec5SDimitry Andric }
25*0b57cec5SDimitry Andric 
26*0b57cec5SDimitry Andric static llvm::Expected<MemoryRegionInfo>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)27*0b57cec5SDimitry Andric ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
28*0b57cec5SDimitry Andric                                       MapsKind maps_kind) {
29*0b57cec5SDimitry Andric   MemoryRegionInfo region;
30*0b57cec5SDimitry Andric   StringExtractor line_extractor(maps_line);
31*0b57cec5SDimitry Andric 
32*0b57cec5SDimitry Andric   // Format: {address_start_hex}-{address_end_hex} perms offset  dev   inode
33*0b57cec5SDimitry Andric   // pathname perms: rwxp   (letter is present if set, '-' if not, final
34*0b57cec5SDimitry Andric   // character is p=private, s=shared).
35*0b57cec5SDimitry Andric 
36*0b57cec5SDimitry Andric   // Parse out the starting address
37*0b57cec5SDimitry Andric   lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);
38*0b57cec5SDimitry Andric 
39*0b57cec5SDimitry Andric   // Parse out hyphen separating start and end address from range.
40*0b57cec5SDimitry Andric   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))
41*0b57cec5SDimitry Andric     return ProcMapError(
42*0b57cec5SDimitry Andric         "malformed /proc/{pid}/%s entry, missing dash between address range",
43*0b57cec5SDimitry Andric         maps_kind);
44*0b57cec5SDimitry Andric 
45*0b57cec5SDimitry Andric   // Parse out the ending address
46*0b57cec5SDimitry Andric   lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);
47*0b57cec5SDimitry Andric 
48*0b57cec5SDimitry Andric   // Parse out the space after the address.
49*0b57cec5SDimitry Andric   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))
50*0b57cec5SDimitry Andric     return ProcMapError(
51*0b57cec5SDimitry Andric         "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);
52*0b57cec5SDimitry Andric 
53*0b57cec5SDimitry Andric   // Save the range.
54*0b57cec5SDimitry Andric   region.GetRange().SetRangeBase(start_address);
55*0b57cec5SDimitry Andric   region.GetRange().SetRangeEnd(end_address);
56*0b57cec5SDimitry Andric 
57*0b57cec5SDimitry Andric   // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped
58*0b57cec5SDimitry Andric   // into the process.
59*0b57cec5SDimitry Andric   region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
60*0b57cec5SDimitry Andric 
61*0b57cec5SDimitry Andric   // Parse out each permission entry.
62*0b57cec5SDimitry Andric   if (line_extractor.GetBytesLeft() < 4)
63*0b57cec5SDimitry Andric     return ProcMapError(
64*0b57cec5SDimitry Andric         "malformed /proc/{pid}/%s entry, missing some portion of "
65*0b57cec5SDimitry Andric         "permissions",
66*0b57cec5SDimitry Andric         maps_kind);
67*0b57cec5SDimitry Andric 
68*0b57cec5SDimitry Andric   // Handle read permission.
69*0b57cec5SDimitry Andric   const char read_perm_char = line_extractor.GetChar();
70*0b57cec5SDimitry Andric   if (read_perm_char == 'r')
71*0b57cec5SDimitry Andric     region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
72*0b57cec5SDimitry Andric   else if (read_perm_char == '-')
73*0b57cec5SDimitry Andric     region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
74*0b57cec5SDimitry Andric   else
75*0b57cec5SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s read permission char",
76*0b57cec5SDimitry Andric                         maps_kind);
77*0b57cec5SDimitry Andric 
78*0b57cec5SDimitry Andric   // Handle write permission.
79*0b57cec5SDimitry Andric   const char write_perm_char = line_extractor.GetChar();
80*0b57cec5SDimitry Andric   if (write_perm_char == 'w')
81*0b57cec5SDimitry Andric     region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
82*0b57cec5SDimitry Andric   else if (write_perm_char == '-')
83*0b57cec5SDimitry Andric     region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
84*0b57cec5SDimitry Andric   else
85*0b57cec5SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s write permission char",
86*0b57cec5SDimitry Andric                         maps_kind);
87*0b57cec5SDimitry Andric 
88*0b57cec5SDimitry Andric   // Handle execute permission.
89*0b57cec5SDimitry Andric   const char exec_perm_char = line_extractor.GetChar();
90*0b57cec5SDimitry Andric   if (exec_perm_char == 'x')
91*0b57cec5SDimitry Andric     region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
92*0b57cec5SDimitry Andric   else if (exec_perm_char == '-')
93*0b57cec5SDimitry Andric     region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
94*0b57cec5SDimitry Andric   else
95*0b57cec5SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s exec permission char",
96*0b57cec5SDimitry Andric                         maps_kind);
97*0b57cec5SDimitry Andric 
98*0b57cec5SDimitry Andric   // Handle sharing status (private/shared).
99*0b57cec5SDimitry Andric   const char sharing_char = line_extractor.GetChar();
100*0b57cec5SDimitry Andric   if (sharing_char == 's')
101*0b57cec5SDimitry Andric     region.SetShared(MemoryRegionInfo::OptionalBool::eYes);
102*0b57cec5SDimitry Andric   else if (sharing_char == 'p')
103*0b57cec5SDimitry Andric     region.SetShared(MemoryRegionInfo::OptionalBool::eNo);
104*0b57cec5SDimitry Andric   else
105*0b57cec5SDimitry Andric     region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow);
106*0b57cec5SDimitry Andric 
107*0b57cec5SDimitry Andric   line_extractor.SkipSpaces();           // Skip the separator
108*0b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the offset
109*0b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
110*0b57cec5SDimitry Andric   line_extractor.GetChar();              // Read the device id separator
111*0b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
112*0b57cec5SDimitry Andric   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