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   line_extractor.GetChar();              // Read the private bit
98   line_extractor.SkipSpaces();           // Skip the separator
99   line_extractor.GetHexMaxU64(false, 0); // Read the offset
100   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
101   line_extractor.GetChar();              // Read the device id separator
102   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
103   line_extractor.SkipSpaces();           // Skip the separator
104   line_extractor.GetU64(0, 10);          // Read the inode number
105 
106   line_extractor.SkipSpaces();
107   const char *name = line_extractor.Peek();
108   if (name)
109     region.SetName(name);
110 
111   return region;
112 }
113 
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)114 void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,
115                                         LinuxMapCallback const &callback) {
116   llvm::StringRef lines(linux_map);
117   llvm::StringRef line;
118   while (!lines.empty()) {
119     std::tie(line, lines) = lines.split('\n');
120     if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))
121       break;
122   }
123 }
124 
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)125 void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
126                                          LinuxMapCallback const &callback) {
127   // Entries in /smaps look like:
128   // 00400000-0048a000 r-xp 00000000 fd:03 960637
129   // Size:                552 kB
130   // Rss:                 460 kB
131   // <...>
132   // VmFlags: rd ex mr mw me dw
133   // 00500000-0058a000 rwxp 00000000 fd:03 960637
134   // <...>
135   //
136   // Where the first line is identical to the /maps format
137   // and VmFlags is only printed for kernels >= 3.8.
138 
139   llvm::StringRef lines(linux_smap);
140   llvm::StringRef line;
141   llvm::Optional<MemoryRegionInfo> region;
142 
143   while (lines.size()) {
144     std::tie(line, lines) = lines.split('\n');
145 
146     // A property line looks like:
147     // <word>: <value>
148     // (no spaces on the left hand side)
149     // A header will have a ':' but the LHS will contain spaces
150     llvm::StringRef name;
151     llvm::StringRef value;
152     std::tie(name, value) = line.split(':');
153 
154     // If this line is a property line
155     if (!name.contains(' ')) {
156       if (region) {
157         if (name == "VmFlags") {
158           if (value.contains("mt"))
159             region->SetMemoryTagged(MemoryRegionInfo::eYes);
160           else
161             region->SetMemoryTagged(MemoryRegionInfo::eNo);
162         }
163         // Ignore anything else
164       } else {
165         // Orphaned settings line
166         callback(ProcMapError(
167             "Found a property line without a corresponding mapping "
168             "in /proc/{pid}/%s",
169             MapsKind::SMaps));
170         return;
171       }
172     } else {
173       // Must be a new region header
174       if (region) {
175         // Save current region
176         callback(*region);
177         region.reset();
178       }
179 
180       // Try to start a new region
181       llvm::Expected<MemoryRegionInfo> new_region =
182           ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
183       if (new_region) {
184         region = *new_region;
185       } else {
186         // Stop at first invalid region header
187         callback(new_region.takeError());
188         return;
189       }
190     }
191   }
192 
193   // Catch last region
194   if (region)
195     callback(*region);
196 }
197