180814287SRaphael Isemann //===-- LinuxProcMaps.cpp -------------------------------------------------===//
2026e1bf5SGreg Clayton //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6026e1bf5SGreg Clayton //
7026e1bf5SGreg Clayton //===----------------------------------------------------------------------===//
8026e1bf5SGreg Clayton 
9026e1bf5SGreg Clayton #include "LinuxProcMaps.h"
10026e1bf5SGreg Clayton #include "lldb/Target/MemoryRegionInfo.h"
11026e1bf5SGreg Clayton #include "lldb/Utility/Status.h"
12026e1bf5SGreg Clayton #include "lldb/Utility/StringExtractor.h"
1332541685SDavid Spickett #include "llvm/ADT/StringRef.h"
14026e1bf5SGreg Clayton 
15026e1bf5SGreg Clayton using namespace lldb_private;
16026e1bf5SGreg Clayton 
1732541685SDavid Spickett enum class MapsKind { Maps, SMaps };
18026e1bf5SGreg Clayton 
ProcMapError(const char * msg,MapsKind kind)1932541685SDavid Spickett static llvm::Expected<MemoryRegionInfo> ProcMapError(const char *msg,
2032541685SDavid Spickett                                                      MapsKind kind) {
2132541685SDavid Spickett   return llvm::createStringError(llvm::inconvertibleErrorCode(), msg,
2232541685SDavid Spickett                                  kind == MapsKind::Maps ? "maps" : "smaps");
2332541685SDavid Spickett }
2432541685SDavid Spickett 
2532541685SDavid Spickett static llvm::Expected<MemoryRegionInfo>
ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,MapsKind maps_kind)2632541685SDavid Spickett ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
2732541685SDavid Spickett                                       MapsKind maps_kind) {
2832541685SDavid Spickett   MemoryRegionInfo region;
29026e1bf5SGreg Clayton   StringExtractor line_extractor(maps_line);
30026e1bf5SGreg Clayton 
31026e1bf5SGreg Clayton   // Format: {address_start_hex}-{address_end_hex} perms offset  dev   inode
32026e1bf5SGreg Clayton   // pathname perms: rwxp   (letter is present if set, '-' if not, final
33026e1bf5SGreg Clayton   // character is p=private, s=shared).
34026e1bf5SGreg Clayton 
35026e1bf5SGreg Clayton   // Parse out the starting address
36026e1bf5SGreg Clayton   lldb::addr_t start_address = line_extractor.GetHexMaxU64(false, 0);
37026e1bf5SGreg Clayton 
38026e1bf5SGreg Clayton   // Parse out hyphen separating start and end address from range.
39026e1bf5SGreg Clayton   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != '-'))
4032541685SDavid Spickett     return ProcMapError(
4132541685SDavid Spickett         "malformed /proc/{pid}/%s entry, missing dash between address range",
4232541685SDavid Spickett         maps_kind);
43026e1bf5SGreg Clayton 
44026e1bf5SGreg Clayton   // Parse out the ending address
45026e1bf5SGreg Clayton   lldb::addr_t end_address = line_extractor.GetHexMaxU64(false, start_address);
46026e1bf5SGreg Clayton 
47026e1bf5SGreg Clayton   // Parse out the space after the address.
48026e1bf5SGreg Clayton   if (!line_extractor.GetBytesLeft() || (line_extractor.GetChar() != ' '))
4932541685SDavid Spickett     return ProcMapError(
5032541685SDavid Spickett         "malformed /proc/{pid}/%s entry, missing space after range", maps_kind);
51026e1bf5SGreg Clayton 
52026e1bf5SGreg Clayton   // Save the range.
5332541685SDavid Spickett   region.GetRange().SetRangeBase(start_address);
5432541685SDavid Spickett   region.GetRange().SetRangeEnd(end_address);
55026e1bf5SGreg Clayton 
5632541685SDavid Spickett   // Any memory region in /proc/{pid}/(maps|smaps) is by definition mapped
5732541685SDavid Spickett   // into the process.
5832541685SDavid Spickett   region.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
59026e1bf5SGreg Clayton 
60026e1bf5SGreg Clayton   // Parse out each permission entry.
61026e1bf5SGreg Clayton   if (line_extractor.GetBytesLeft() < 4)
6232541685SDavid Spickett     return ProcMapError(
6332541685SDavid Spickett         "malformed /proc/{pid}/%s entry, missing some portion of "
6432541685SDavid Spickett         "permissions",
6532541685SDavid Spickett         maps_kind);
66026e1bf5SGreg Clayton 
67026e1bf5SGreg Clayton   // Handle read permission.
68026e1bf5SGreg Clayton   const char read_perm_char = line_extractor.GetChar();
69026e1bf5SGreg Clayton   if (read_perm_char == 'r')
7032541685SDavid Spickett     region.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
71026e1bf5SGreg Clayton   else if (read_perm_char == '-')
7232541685SDavid Spickett     region.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
73026e1bf5SGreg Clayton   else
7432541685SDavid Spickett     return ProcMapError("unexpected /proc/{pid}/%s read permission char",
7532541685SDavid Spickett                         maps_kind);
76026e1bf5SGreg Clayton 
77026e1bf5SGreg Clayton   // Handle write permission.
78026e1bf5SGreg Clayton   const char write_perm_char = line_extractor.GetChar();
79026e1bf5SGreg Clayton   if (write_perm_char == 'w')
8032541685SDavid Spickett     region.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
81026e1bf5SGreg Clayton   else if (write_perm_char == '-')
8232541685SDavid Spickett     region.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
83026e1bf5SGreg Clayton   else
8432541685SDavid Spickett     return ProcMapError("unexpected /proc/{pid}/%s write permission char",
8532541685SDavid Spickett                         maps_kind);
86026e1bf5SGreg Clayton 
87026e1bf5SGreg Clayton   // Handle execute permission.
88026e1bf5SGreg Clayton   const char exec_perm_char = line_extractor.GetChar();
89026e1bf5SGreg Clayton   if (exec_perm_char == 'x')
9032541685SDavid Spickett     region.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
91026e1bf5SGreg Clayton   else if (exec_perm_char == '-')
9232541685SDavid Spickett     region.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
93026e1bf5SGreg Clayton   else
9432541685SDavid Spickett     return ProcMapError("unexpected /proc/{pid}/%s exec permission char",
9532541685SDavid Spickett                         maps_kind);
96026e1bf5SGreg Clayton 
97*c0702ac0SEmre Kultursay   // Handle sharing status (private/shared).
98*c0702ac0SEmre Kultursay   const char sharing_char = line_extractor.GetChar();
99*c0702ac0SEmre Kultursay   if (sharing_char == 's')
100*c0702ac0SEmre Kultursay     region.SetShared(MemoryRegionInfo::OptionalBool::eYes);
101*c0702ac0SEmre Kultursay   else if (sharing_char == 'p')
102*c0702ac0SEmre Kultursay     region.SetShared(MemoryRegionInfo::OptionalBool::eNo);
103*c0702ac0SEmre Kultursay   else
104*c0702ac0SEmre Kultursay     region.SetShared(MemoryRegionInfo::OptionalBool::eDontKnow);
105*c0702ac0SEmre Kultursay 
106026e1bf5SGreg Clayton   line_extractor.SkipSpaces();           // Skip the separator
107026e1bf5SGreg Clayton   line_extractor.GetHexMaxU64(false, 0); // Read the offset
108026e1bf5SGreg Clayton   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
109026e1bf5SGreg Clayton   line_extractor.GetChar();              // Read the device id separator
110026e1bf5SGreg Clayton   line_extractor.GetHexMaxU64(false, 0); // Read the major device number
111026e1bf5SGreg Clayton   line_extractor.SkipSpaces();           // Skip the separator
112026e1bf5SGreg Clayton   line_extractor.GetU64(0, 10);          // Read the inode number
113026e1bf5SGreg Clayton 
114026e1bf5SGreg Clayton   line_extractor.SkipSpaces();
115026e1bf5SGreg Clayton   const char *name = line_extractor.Peek();
116026e1bf5SGreg Clayton   if (name)
11732541685SDavid Spickett     region.SetName(name);
118026e1bf5SGreg Clayton 
11932541685SDavid Spickett   return region;
120026e1bf5SGreg Clayton }
121026e1bf5SGreg Clayton 
ParseLinuxMapRegions(llvm::StringRef linux_map,LinuxMapCallback const & callback)122026e1bf5SGreg Clayton void lldb_private::ParseLinuxMapRegions(llvm::StringRef linux_map,
123026e1bf5SGreg Clayton                                         LinuxMapCallback const &callback) {
124026e1bf5SGreg Clayton   llvm::StringRef lines(linux_map);
125026e1bf5SGreg Clayton   llvm::StringRef line;
126026e1bf5SGreg Clayton   while (!lines.empty()) {
127026e1bf5SGreg Clayton     std::tie(line, lines) = lines.split('\n');
12832541685SDavid Spickett     if (!callback(ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::Maps)))
129026e1bf5SGreg Clayton       break;
130026e1bf5SGreg Clayton   }
131026e1bf5SGreg Clayton }
13232541685SDavid Spickett 
ParseLinuxSMapRegions(llvm::StringRef linux_smap,LinuxMapCallback const & callback)13332541685SDavid Spickett void lldb_private::ParseLinuxSMapRegions(llvm::StringRef linux_smap,
13432541685SDavid Spickett                                          LinuxMapCallback const &callback) {
13532541685SDavid Spickett   // Entries in /smaps look like:
13632541685SDavid Spickett   // 00400000-0048a000 r-xp 00000000 fd:03 960637
13732541685SDavid Spickett   // Size:                552 kB
13832541685SDavid Spickett   // Rss:                 460 kB
13932541685SDavid Spickett   // <...>
14032541685SDavid Spickett   // VmFlags: rd ex mr mw me dw
14132541685SDavid Spickett   // 00500000-0058a000 rwxp 00000000 fd:03 960637
14232541685SDavid Spickett   // <...>
14332541685SDavid Spickett   //
14432541685SDavid Spickett   // Where the first line is identical to the /maps format
14532541685SDavid Spickett   // and VmFlags is only printed for kernels >= 3.8.
14632541685SDavid Spickett 
14732541685SDavid Spickett   llvm::StringRef lines(linux_smap);
14832541685SDavid Spickett   llvm::StringRef line;
14932541685SDavid Spickett   llvm::Optional<MemoryRegionInfo> region;
15032541685SDavid Spickett 
15132541685SDavid Spickett   while (lines.size()) {
15232541685SDavid Spickett     std::tie(line, lines) = lines.split('\n');
15332541685SDavid Spickett 
15432541685SDavid Spickett     // A property line looks like:
15532541685SDavid Spickett     // <word>: <value>
15632541685SDavid Spickett     // (no spaces on the left hand side)
15732541685SDavid Spickett     // A header will have a ':' but the LHS will contain spaces
15832541685SDavid Spickett     llvm::StringRef name;
15932541685SDavid Spickett     llvm::StringRef value;
16032541685SDavid Spickett     std::tie(name, value) = line.split(':');
16132541685SDavid Spickett 
16232541685SDavid Spickett     // If this line is a property line
16332541685SDavid Spickett     if (!name.contains(' ')) {
16432541685SDavid Spickett       if (region) {
16532541685SDavid Spickett         if (name == "VmFlags") {
16632541685SDavid Spickett           if (value.contains("mt"))
16732541685SDavid Spickett             region->SetMemoryTagged(MemoryRegionInfo::eYes);
16832541685SDavid Spickett           else
16932541685SDavid Spickett             region->SetMemoryTagged(MemoryRegionInfo::eNo);
17032541685SDavid Spickett         }
17132541685SDavid Spickett         // Ignore anything else
17232541685SDavid Spickett       } else {
17332541685SDavid Spickett         // Orphaned settings line
17432541685SDavid Spickett         callback(ProcMapError(
17532541685SDavid Spickett             "Found a property line without a corresponding mapping "
17632541685SDavid Spickett             "in /proc/{pid}/%s",
17732541685SDavid Spickett             MapsKind::SMaps));
17832541685SDavid Spickett         return;
17932541685SDavid Spickett       }
18032541685SDavid Spickett     } else {
18132541685SDavid Spickett       // Must be a new region header
18232541685SDavid Spickett       if (region) {
18332541685SDavid Spickett         // Save current region
18432541685SDavid Spickett         callback(*region);
18532541685SDavid Spickett         region.reset();
18632541685SDavid Spickett       }
18732541685SDavid Spickett 
18832541685SDavid Spickett       // Try to start a new region
18932541685SDavid Spickett       llvm::Expected<MemoryRegionInfo> new_region =
19032541685SDavid Spickett           ParseMemoryRegionInfoFromProcMapsLine(line, MapsKind::SMaps);
19132541685SDavid Spickett       if (new_region) {
19232541685SDavid Spickett         region = *new_region;
19332541685SDavid Spickett       } else {
19432541685SDavid Spickett         // Stop at first invalid region header
19532541685SDavid Spickett         callback(new_region.takeError());
19632541685SDavid Spickett         return;
19732541685SDavid Spickett       }
19832541685SDavid Spickett     }
19932541685SDavid Spickett   }
20032541685SDavid Spickett 
20132541685SDavid Spickett   // Catch last region
20232541685SDavid Spickett   if (region)
20332541685SDavid Spickett     callback(*region);
20432541685SDavid Spickett }
205