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