15ffd83dbSDimitry Andric //===-- LinuxProcMaps.cpp -------------------------------------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #include "LinuxProcMaps.h"
100b57cec5SDimitry Andric #include "lldb/Target/MemoryRegionInfo.h"
110b57cec5SDimitry Andric #include "lldb/Utility/Status.h"
120b57cec5SDimitry Andric #include "lldb/Utility/StringExtractor.h"
13*af732203SDimitry Andric #include "llvm/ADT/StringRef.h"
140b57cec5SDimitry Andric 
150b57cec5SDimitry Andric using namespace lldb_private;
160b57cec5SDimitry Andric 
17*af732203SDimitry Andric enum class MapsKind { Maps, SMaps };
180b57cec5SDimitry Andric 
ProcMapError(const char * msg,MapsKind kind)19*af732203SDimitry Andric static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,
20*af732203SDimitry Andric                                                      MapsKind kind) {
21*af732203SDimitry Andric   return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,
22*af732203SDimitry Andric                                  kind == MapsKind::Maps ? "maps" : "smaps");
23*af732203SDimitry Andric }
24*af732203SDimitry Andric 
25*af732203SDimitry Andric static llvm::Expected<MemoryRegionInfo>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)26*af732203SDimitry Andric ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
27*af732203SDimitry Andric                                       MapsKind maps_kind) {
28*af732203SDimitry Andric   MemoryRegionInfo region;
290b57cec5SDimitry Andric   StringExtractor line_extractor(maps_line);
300b57cec5SDimitry Andric 
310b57cec5SDimitry Andric   // Format: {address_start_hex}-{address_end_hex} perms offset  dev   inode
320b57cec5SDimitry Andric   // pathname perms: rwxp   (letter is present if set, '-' if not, final
330b57cec5SDimitry Andric   // character is p=private, s=shared).
340b57cec5SDimitry Andric 
350b57cec5SDimitry Andric   // Parse out the starting address
360b57cec5SDimitry Andric   lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);
370b57cec5SDimitry Andric 
380b57cec5SDimitry Andric   // Parse out hyphen separating start and end address from range.
390b57cec5SDimitry Andric   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))
40*af732203SDimitry Andric     return ProcMapError(
41*af732203SDimitry Andric         "malformed /proc/{pid}/%s entry, missing dash between address range",
42*af732203SDimitry Andric         maps_kind);
430b57cec5SDimitry Andric 
440b57cec5SDimitry Andric   // Parse out the ending address
450b57cec5SDimitry Andric   lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);
460b57cec5SDimitry Andric 
470b57cec5SDimitry Andric   // Parse out the space after the address.
480b57cec5SDimitry Andric   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))
49*af732203SDimitry Andric     return ProcMapError(
50*af732203SDimitry Andric         "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);
510b57cec5SDimitry Andric 
520b57cec5SDimitry Andric   // Save the range.
53*af732203SDimitry Andric   region.GetRange().SetRangeBase(start_address);
54*af732203SDimitry Andric   region.GetRange().SetRangeEnd(end_address);
550b57cec5SDimitry Andric 
56*af732203SDimitry Andric   // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped
57*af732203SDimitry Andric   // into the process.
58*af732203SDimitry Andric   region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
590b57cec5SDimitry Andric 
600b57cec5SDimitry Andric   // Parse out each permission entry.
610b57cec5SDimitry Andric   if (line_extractor.GetBytesLeft() < 4)
62*af732203SDimitry Andric     return ProcMapError(
63*af732203SDimitry Andric         "malformed /proc/{pid}/%s entry, missing some portion of "
64*af732203SDimitry Andric         "permissions",
65*af732203SDimitry Andric         maps_kind);
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric   // Handle read permission.
680b57cec5SDimitry Andric   const char read_perm_char = line_extractor.GetChar();
690b57cec5SDimitry Andric   if (read_perm_char == 'r')
70*af732203SDimitry Andric     region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
710b57cec5SDimitry Andric   else if (read_perm_char == '-')
72*af732203SDimitry Andric     region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
730b57cec5SDimitry Andric   else
74*af732203SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s read permission char",
75*af732203SDimitry Andric                         maps_kind);
760b57cec5SDimitry Andric 
770b57cec5SDimitry Andric   // Handle write permission.
780b57cec5SDimitry Andric   const char write_perm_char = line_extractor.GetChar();
790b57cec5SDimitry Andric   if (write_perm_char == 'w')
80*af732203SDimitry Andric     region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
810b57cec5SDimitry Andric   else if (write_perm_char == '-')
82*af732203SDimitry Andric     region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
830b57cec5SDimitry Andric   else
84*af732203SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s write permission char",
85*af732203SDimitry Andric                         maps_kind);
860b57cec5SDimitry Andric 
870b57cec5SDimitry Andric   // Handle execute permission.
880b57cec5SDimitry Andric   const char exec_perm_char = line_extractor.GetChar();
890b57cec5SDimitry Andric   if (exec_perm_char == 'x')
90*af732203SDimitry Andric     region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
910b57cec5SDimitry Andric   else if (exec_perm_char == '-')
92*af732203SDimitry Andric     region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
930b57cec5SDimitry Andric   else
94*af732203SDimitry Andric     return ProcMapError("unexpected /proc/{pid}/%s exec permission char",
95*af732203SDimitry Andric                         maps_kind);
960b57cec5SDimitry Andric 
970b57cec5SDimitry Andric   line_extractor.GetChar();              // Read the private bit
980b57cec5SDimitry Andric   line_extractor.SkipSpaces();           // Skip the separator
990b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the offset
1000b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
1010b57cec5SDimitry Andric   line_extractor.GetChar();              // Read the device id separator
1020b57cec5SDimitry Andric   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
1030b57cec5SDimitry Andric   line_extractor.SkipSpaces();           // Skip the separator
1040b57cec5SDimitry Andric   line_extractor.GetU64(0, 10);          // Read the inode number
1050b57cec5SDimitry Andric 
1060b57cec5SDimitry Andric   line_extractor.SkipSpaces();
1070b57cec5SDimitry Andric   const char *name = line_extractor.Peek();
1080b57cec5SDimitry Andric   if (name)
109*af732203SDimitry Andric     region.SetName(name);
1100b57cec5SDimitry Andric 
111*af732203SDimitry Andric   return region;
1120b57cec5SDimitry Andric }
1130b57cec5SDimitry Andric 
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)1140b57cec5SDimitry Andric void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,
1150b57cec5SDimitry Andric                                         LinuxMapCallback const &callback) {
1160b57cec5SDimitry Andric   llvm::StringRef lines(linux_map);
1170b57cec5SDimitry Andric   llvm::StringRef line;
1180b57cec5SDimitry Andric   while (!lines.empty()) {
1190b57cec5SDimitry Andric     std::tie(line, lines) = lines.split('\n');
120*af732203SDimitry Andric     if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))
1210b57cec5SDimitry Andric       break;
1220b57cec5SDimitry Andric   }
1230b57cec5SDimitry Andric }
124*af732203SDimitry Andric 
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)125*af732203SDimitry Andric void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
126*af732203SDimitry Andric                                          LinuxMapCallback const &callback) {
127*af732203SDimitry Andric   // Entries in /smaps look like:
128*af732203SDimitry Andric   // 00400000-0048a000 r-xp 00000000 fd:03 960637
129*af732203SDimitry Andric   // Size:                552 kB
130*af732203SDimitry Andric   // Rss:                 460 kB
131*af732203SDimitry Andric   // <...>
132*af732203SDimitry Andric   // VmFlags: rd ex mr mw me dw
133*af732203SDimitry Andric   // 00500000-0058a000 rwxp 00000000 fd:03 960637
134*af732203SDimitry Andric   // <...>
135*af732203SDimitry Andric   //
136*af732203SDimitry Andric   // Where the first line is identical to the /maps format
137*af732203SDimitry Andric   // and VmFlags is only printed for kernels >= 3.8.
138*af732203SDimitry Andric 
139*af732203SDimitry Andric   llvm::StringRef lines(linux_smap);
140*af732203SDimitry Andric   llvm::StringRef line;
141*af732203SDimitry Andric   llvm::Optional<MemoryRegionInfo> region;
142*af732203SDimitry Andric 
143*af732203SDimitry Andric   while (lines.size()) {
144*af732203SDimitry Andric     std::tie(line, lines) = lines.split('\n');
145*af732203SDimitry Andric 
146*af732203SDimitry Andric     // A property line looks like:
147*af732203SDimitry Andric     // <word>: <value>
148*af732203SDimitry Andric     // (no spaces on the left hand side)
149*af732203SDimitry Andric     // A header will have a ':' but the LHS will contain spaces
150*af732203SDimitry Andric     llvm::StringRef name;
151*af732203SDimitry Andric     llvm::StringRef value;
152*af732203SDimitry Andric     std::tie(name, value) = line.split(':');
153*af732203SDimitry Andric 
154*af732203SDimitry Andric     // If this line is a property line
155*af732203SDimitry Andric     if (!name.contains(' ')) {
156*af732203SDimitry Andric       if (region) {
157*af732203SDimitry Andric         if (name == "VmFlags") {
158*af732203SDimitry Andric           if (value.contains("mt"))
159*af732203SDimitry Andric             region->SetMemoryTagged(MemoryRegionInfo::eYes);
160*af732203SDimitry Andric           else
161*af732203SDimitry Andric             region->SetMemoryTagged(MemoryRegionInfo::eNo);
162*af732203SDimitry Andric         }
163*af732203SDimitry Andric         // Ignore anything else
164*af732203SDimitry Andric       } else {
165*af732203SDimitry Andric         // Orphaned settings line
166*af732203SDimitry Andric         callback(ProcMapError(
167*af732203SDimitry Andric             "Found a property line without a corresponding mapping "
168*af732203SDimitry Andric             "in /proc/{pid}/%s",
169*af732203SDimitry Andric             MapsKind::SMaps));
170*af732203SDimitry Andric         return;
171*af732203SDimitry Andric       }
172*af732203SDimitry Andric     } else {
173*af732203SDimitry Andric       // Must be a new region header
174*af732203SDimitry Andric       if (region) {
175*af732203SDimitry Andric         // Save current region
176*af732203SDimitry Andric         callback(*region);
177*af732203SDimitry Andric         region.reset();
178*af732203SDimitry Andric       }
179*af732203SDimitry Andric 
180*af732203SDimitry Andric       // Try to start a new region
181*af732203SDimitry Andric       llvm::Expected<MemoryRegionInfo> new_region =
182*af732203SDimitry Andric           ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
183*af732203SDimitry Andric       if (new_region) {
184*af732203SDimitry Andric         region = *new_region;
185*af732203SDimitry Andric       } else {
186*af732203SDimitry Andric         // Stop at first invalid region header
187*af732203SDimitry Andric         callback(new_region.takeError());
188*af732203SDimitry Andric         return;
189*af732203SDimitry Andric       }
190*af732203SDimitry Andric     }
191*af732203SDimitry Andric   }
192*af732203SDimitry Andric 
193*af732203SDimitry Andric   // Catch last region
194*af732203SDimitry Andric   if (region)
195*af732203SDimitry Andric     callback(*region);
196*af732203SDimitry Andric }
197