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 
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>
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 
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 
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