1 | //===-- MinidumpParser.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 "MinidumpParser.h" |
10 | #include "NtStructures.h" |
11 | #include "RegisterContextMinidump_x86_32.h" |
12 | |
13 | #include "Plugins/Process/Utility/LinuxProcMaps.h" |
14 | #include "lldb/Utility/LLDBAssert.h" |
15 | #include "lldb/Utility/LLDBLog.h" |
16 | #include "lldb/Utility/Log.h" |
17 | |
18 | // C includes |
19 | // C++ includes |
20 | #include <algorithm> |
21 | #include <map> |
22 | #include <optional> |
23 | #include <vector> |
24 | #include <utility> |
25 | |
26 | using namespace lldb_private; |
27 | using namespace minidump; |
28 | |
29 | llvm::Expected<MinidumpParser> |
30 | MinidumpParser::Create(const lldb::DataBufferSP &data_sp) { |
31 | auto ExpectedFile = llvm::object::MinidumpFile::create( |
32 | Source: llvm::MemoryBufferRef(toStringRef(Input: data_sp->GetData()), "minidump" )); |
33 | if (!ExpectedFile) |
34 | return ExpectedFile.takeError(); |
35 | |
36 | return MinidumpParser(data_sp, std::move(*ExpectedFile)); |
37 | } |
38 | |
39 | MinidumpParser::MinidumpParser(lldb::DataBufferSP data_sp, |
40 | std::unique_ptr<llvm::object::MinidumpFile> file) |
41 | : m_data_sp(std::move(data_sp)), m_file(std::move(file)) {} |
42 | |
43 | llvm::ArrayRef<uint8_t> MinidumpParser::GetData() { |
44 | return llvm::ArrayRef<uint8_t>(m_data_sp->GetBytes(), |
45 | m_data_sp->GetByteSize()); |
46 | } |
47 | |
48 | llvm::ArrayRef<uint8_t> MinidumpParser::GetStream(StreamType stream_type) { |
49 | return m_file->getRawStream(Type: stream_type).value_or(u: llvm::ArrayRef<uint8_t>()); |
50 | } |
51 | |
52 | UUID MinidumpParser::GetModuleUUID(const minidump::Module *module) { |
53 | auto cv_record = |
54 | GetData().slice(N: module->CvRecord.RVA, M: module->CvRecord.DataSize); |
55 | |
56 | // Read the CV record signature |
57 | const llvm::support::ulittle32_t *signature = nullptr; |
58 | Status error = consumeObject(Buffer&: cv_record, Object&: signature); |
59 | if (error.Fail()) |
60 | return UUID(); |
61 | |
62 | const CvSignature cv_signature = |
63 | static_cast<CvSignature>(static_cast<uint32_t>(*signature)); |
64 | |
65 | if (cv_signature == CvSignature::Pdb70) { |
66 | const UUID::CvRecordPdb70 *pdb70_uuid = nullptr; |
67 | Status error = consumeObject(Buffer&: cv_record, Object&: pdb70_uuid); |
68 | if (error.Fail()) |
69 | return UUID(); |
70 | if (GetArchitecture().GetTriple().isOSBinFormatELF()) { |
71 | if (pdb70_uuid->Age != 0) |
72 | return UUID(pdb70_uuid, sizeof(*pdb70_uuid)); |
73 | return UUID(&pdb70_uuid->Uuid, |
74 | sizeof(pdb70_uuid->Uuid)); |
75 | } |
76 | return UUID(*pdb70_uuid); |
77 | } else if (cv_signature == CvSignature::ElfBuildId) |
78 | return UUID(cv_record); |
79 | |
80 | return UUID(); |
81 | } |
82 | |
83 | llvm::ArrayRef<minidump::Thread> MinidumpParser::GetThreads() { |
84 | auto ExpectedThreads = GetMinidumpFile().getThreadList(); |
85 | if (ExpectedThreads) |
86 | return *ExpectedThreads; |
87 | |
88 | LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), ExpectedThreads.takeError(), |
89 | "Failed to read thread list: {0}" ); |
90 | return {}; |
91 | } |
92 | |
93 | llvm::ArrayRef<uint8_t> |
94 | MinidumpParser::GetThreadContext(const LocationDescriptor &location) { |
95 | if (location.RVA + location.DataSize > GetData().size()) |
96 | return {}; |
97 | return GetData().slice(N: location.RVA, M: location.DataSize); |
98 | } |
99 | |
100 | llvm::ArrayRef<uint8_t> |
101 | MinidumpParser::GetThreadContext(const minidump::Thread &td) { |
102 | return GetThreadContext(location: td.Context); |
103 | } |
104 | |
105 | llvm::ArrayRef<uint8_t> |
106 | MinidumpParser::GetThreadContextWow64(const minidump::Thread &td) { |
107 | // On Windows, a 32-bit process can run on a 64-bit machine under WOW64. If |
108 | // the minidump was captured with a 64-bit debugger, then the CONTEXT we just |
109 | // grabbed from the mini_dump_thread is the one for the 64-bit "native" |
110 | // process rather than the 32-bit "guest" process we care about. In this |
111 | // case, we can get the 32-bit CONTEXT from the TEB (Thread Environment |
112 | // Block) of the 64-bit process. |
113 | auto teb_mem = GetMemory(addr: td.EnvironmentBlock, size: sizeof(TEB64)); |
114 | if (teb_mem.empty()) |
115 | return {}; |
116 | |
117 | const TEB64 *wow64teb; |
118 | Status error = consumeObject(Buffer&: teb_mem, Object&: wow64teb); |
119 | if (error.Fail()) |
120 | return {}; |
121 | |
122 | // Slot 1 of the thread-local storage in the 64-bit TEB points to a structure |
123 | // that includes the 32-bit CONTEXT (after a ULONG). See: |
124 | // https://msdn.microsoft.com/en-us/library/ms681670.aspx |
125 | auto context = |
126 | GetMemory(addr: wow64teb->tls_slots[1] + 4, size: sizeof(MinidumpContext_x86_32)); |
127 | if (context.size() < sizeof(MinidumpContext_x86_32)) |
128 | return {}; |
129 | |
130 | return context; |
131 | // NOTE: We don't currently use the TEB for anything else. If we |
132 | // need it in the future, the 32-bit TEB is located according to the address |
133 | // stored in the first slot of the 64-bit TEB (wow64teb.Reserved1[0]). |
134 | } |
135 | |
136 | ArchSpec MinidumpParser::GetArchitecture() { |
137 | if (m_arch.IsValid()) |
138 | return m_arch; |
139 | |
140 | // Set the architecture in m_arch |
141 | llvm::Expected<const SystemInfo &> system_info = m_file->getSystemInfo(); |
142 | |
143 | if (!system_info) { |
144 | LLDB_LOG_ERROR(GetLog(LLDBLog::Process), system_info.takeError(), |
145 | "Failed to read SystemInfo stream: {0}" ); |
146 | return m_arch; |
147 | } |
148 | |
149 | // TODO what to do about big endiand flavors of arm ? |
150 | // TODO set the arm subarch stuff if the minidump has info about it |
151 | |
152 | llvm::Triple triple; |
153 | triple.setVendor(llvm::Triple::VendorType::UnknownVendor); |
154 | |
155 | switch (system_info->ProcessorArch) { |
156 | case ProcessorArchitecture::X86: |
157 | triple.setArch(Kind: llvm::Triple::ArchType::x86); |
158 | break; |
159 | case ProcessorArchitecture::AMD64: |
160 | triple.setArch(Kind: llvm::Triple::ArchType::x86_64); |
161 | break; |
162 | case ProcessorArchitecture::ARM: |
163 | triple.setArch(Kind: llvm::Triple::ArchType::arm); |
164 | break; |
165 | case ProcessorArchitecture::ARM64: |
166 | case ProcessorArchitecture::BP_ARM64: |
167 | triple.setArch(Kind: llvm::Triple::ArchType::aarch64); |
168 | break; |
169 | default: |
170 | triple.setArch(Kind: llvm::Triple::ArchType::UnknownArch); |
171 | break; |
172 | } |
173 | |
174 | // TODO add all of the OSes that Minidump/breakpad distinguishes? |
175 | switch (system_info->PlatformId) { |
176 | case OSPlatform::Win32S: |
177 | case OSPlatform::Win32Windows: |
178 | case OSPlatform::Win32NT: |
179 | case OSPlatform::Win32CE: |
180 | triple.setOS(llvm::Triple::OSType::Win32); |
181 | triple.setVendor(llvm::Triple::VendorType::PC); |
182 | break; |
183 | case OSPlatform::Linux: |
184 | triple.setOS(llvm::Triple::OSType::Linux); |
185 | break; |
186 | case OSPlatform::MacOSX: |
187 | triple.setOS(llvm::Triple::OSType::MacOSX); |
188 | triple.setVendor(llvm::Triple::Apple); |
189 | break; |
190 | case OSPlatform::IOS: |
191 | triple.setOS(llvm::Triple::OSType::IOS); |
192 | triple.setVendor(llvm::Triple::Apple); |
193 | break; |
194 | case OSPlatform::Android: |
195 | triple.setOS(llvm::Triple::OSType::Linux); |
196 | triple.setEnvironment(llvm::Triple::EnvironmentType::Android); |
197 | break; |
198 | default: { |
199 | triple.setOS(llvm::Triple::OSType::UnknownOS); |
200 | auto ExpectedCSD = m_file->getString(Offset: system_info->CSDVersionRVA); |
201 | if (!ExpectedCSD) { |
202 | LLDB_LOG_ERROR(GetLog(LLDBLog::Process), ExpectedCSD.takeError(), |
203 | "Failed to CSD Version string: {0}" ); |
204 | } else { |
205 | if (ExpectedCSD->find(s: "Linux" ) != std::string::npos) |
206 | triple.setOS(llvm::Triple::OSType::Linux); |
207 | } |
208 | break; |
209 | } |
210 | } |
211 | m_arch.SetTriple(triple); |
212 | return m_arch; |
213 | } |
214 | |
215 | const MinidumpMiscInfo *MinidumpParser::GetMiscInfo() { |
216 | llvm::ArrayRef<uint8_t> data = GetStream(stream_type: StreamType::MiscInfo); |
217 | |
218 | if (data.size() == 0) |
219 | return nullptr; |
220 | |
221 | return MinidumpMiscInfo::Parse(data); |
222 | } |
223 | |
224 | std::optional<LinuxProcStatus> MinidumpParser::GetLinuxProcStatus() { |
225 | llvm::ArrayRef<uint8_t> data = GetStream(stream_type: StreamType::LinuxProcStatus); |
226 | |
227 | if (data.size() == 0) |
228 | return std::nullopt; |
229 | |
230 | return LinuxProcStatus::Parse(data); |
231 | } |
232 | |
233 | std::optional<lldb::pid_t> MinidumpParser::GetPid() { |
234 | const MinidumpMiscInfo *misc_info = GetMiscInfo(); |
235 | if (misc_info != nullptr) { |
236 | return misc_info->GetPid(); |
237 | } |
238 | |
239 | std::optional<LinuxProcStatus> proc_status = GetLinuxProcStatus(); |
240 | if (proc_status) { |
241 | return proc_status->GetPid(); |
242 | } |
243 | |
244 | return std::nullopt; |
245 | } |
246 | |
247 | llvm::ArrayRef<minidump::Module> MinidumpParser::GetModuleList() { |
248 | auto ExpectedModules = GetMinidumpFile().getModuleList(); |
249 | if (ExpectedModules) |
250 | return *ExpectedModules; |
251 | |
252 | LLDB_LOG_ERROR(GetLog(LLDBLog::Modules), ExpectedModules.takeError(), |
253 | "Failed to read module list: {0}" ); |
254 | return {}; |
255 | } |
256 | |
257 | static bool |
258 | CreateRegionsCacheFromLinuxMaps(MinidumpParser &parser, |
259 | std::vector<MemoryRegionInfo> ®ions) { |
260 | auto data = parser.GetStream(stream_type: StreamType::LinuxMaps); |
261 | if (data.empty()) |
262 | return false; |
263 | |
264 | Log *log = GetLog(mask: LLDBLog::Expressions); |
265 | ParseLinuxMapRegions( |
266 | linux_map: llvm::toStringRef(Input: data), |
267 | callback: [®ions, &log](llvm::Expected<MemoryRegionInfo> region) -> bool { |
268 | if (region) |
269 | regions.push_back(x: *region); |
270 | else |
271 | LLDB_LOG_ERROR(log, region.takeError(), |
272 | "Reading memory region from minidump failed: {0}" ); |
273 | return true; |
274 | }); |
275 | return !regions.empty(); |
276 | } |
277 | |
278 | /// Check for the memory regions starting at \a load_addr for a contiguous |
279 | /// section that has execute permissions that matches the module path. |
280 | /// |
281 | /// When we load a breakpad generated minidump file, we might have the |
282 | /// /proc/<pid>/maps text for a process that details the memory map of the |
283 | /// process that the minidump is describing. This checks the sorted memory |
284 | /// regions for a section that has execute permissions. A sample maps files |
285 | /// might look like: |
286 | /// |
287 | /// 00400000-00401000 r--p 00000000 fd:01 2838574 /tmp/a.out |
288 | /// 00401000-00402000 r-xp 00001000 fd:01 2838574 /tmp/a.out |
289 | /// 00402000-00403000 r--p 00002000 fd:01 2838574 /tmp/a.out |
290 | /// 00403000-00404000 r--p 00002000 fd:01 2838574 /tmp/a.out |
291 | /// 00404000-00405000 rw-p 00003000 fd:01 2838574 /tmp/a.out |
292 | /// ... |
293 | /// |
294 | /// This function should return true when given 0x00400000 and "/tmp/a.out" |
295 | /// is passed in as the path since it has a consecutive memory region for |
296 | /// "/tmp/a.out" that has execute permissions at 0x00401000. This will help us |
297 | /// differentiate if a file has been memory mapped into a process for reading |
298 | /// and breakpad ends up saving a minidump file that has two module entries for |
299 | /// a given file: one that is read only for the entire file, and then one that |
300 | /// is the real executable that is loaded into memory for execution. For memory |
301 | /// mapped files they will typically show up and r--p permissions and a range |
302 | /// matcning the entire range of the file on disk: |
303 | /// |
304 | /// 00800000-00805000 r--p 00000000 fd:01 2838574 /tmp/a.out |
305 | /// 00805000-00806000 r-xp 00001000 fd:01 1234567 /usr/lib/libc.so |
306 | /// |
307 | /// This function should return false when asked about 0x00800000 with |
308 | /// "/tmp/a.out" as the path. |
309 | /// |
310 | /// \param[in] path |
311 | /// The path to the module to check for in the memory regions. Only sequential |
312 | /// memory regions whose paths match this path will be considered when looking |
313 | /// for execute permissions. |
314 | /// |
315 | /// \param[in] regions |
316 | /// A sorted list of memory regions obtained from a call to |
317 | /// CreateRegionsCacheFromLinuxMaps. |
318 | /// |
319 | /// \param[in] base_of_image |
320 | /// The load address of this module from BaseOfImage in the modules list. |
321 | /// |
322 | /// \return |
323 | /// True if a contiguous region of memory belonging to the module with a |
324 | /// matching path exists that has executable permissions. Returns false if |
325 | /// \a regions is empty or if there are no regions with execute permissions |
326 | /// that match \a path. |
327 | |
328 | static bool CheckForLinuxExecutable(ConstString path, |
329 | const MemoryRegionInfos ®ions, |
330 | lldb::addr_t base_of_image) { |
331 | if (regions.empty()) |
332 | return false; |
333 | lldb::addr_t addr = base_of_image; |
334 | MemoryRegionInfo region = MinidumpParser::GetMemoryRegionInfo(regions, load_addr: addr); |
335 | while (region.GetName() == path) { |
336 | if (region.GetExecutable() == MemoryRegionInfo::eYes) |
337 | return true; |
338 | addr += region.GetRange().GetByteSize(); |
339 | region = MinidumpParser::GetMemoryRegionInfo(regions, load_addr: addr); |
340 | } |
341 | return false; |
342 | } |
343 | |
344 | std::vector<const minidump::Module *> MinidumpParser::GetFilteredModuleList() { |
345 | Log *log = GetLog(mask: LLDBLog::Modules); |
346 | auto ExpectedModules = GetMinidumpFile().getModuleList(); |
347 | if (!ExpectedModules) { |
348 | LLDB_LOG_ERROR(log, ExpectedModules.takeError(), |
349 | "Failed to read module list: {0}" ); |
350 | return {}; |
351 | } |
352 | |
353 | // Create memory regions from the linux maps only. We do this to avoid issues |
354 | // with breakpad generated minidumps where if someone has mmap'ed a shared |
355 | // library into memory to access its data in the object file, we can get a |
356 | // minidump with two mappings for a binary: one whose base image points to a |
357 | // memory region that is read + execute and one that is read only. |
358 | MemoryRegionInfos linux_regions; |
359 | if (CreateRegionsCacheFromLinuxMaps(parser&: *this, regions&: linux_regions)) |
360 | llvm::sort(C&: linux_regions); |
361 | |
362 | // map module_name -> filtered_modules index |
363 | typedef llvm::StringMap<size_t> MapType; |
364 | MapType module_name_to_filtered_index; |
365 | |
366 | std::vector<const minidump::Module *> filtered_modules; |
367 | |
368 | for (const auto &module : *ExpectedModules) { |
369 | auto ExpectedName = m_file->getString(Offset: module.ModuleNameRVA); |
370 | if (!ExpectedName) { |
371 | LLDB_LOG_ERROR(log, ExpectedName.takeError(), |
372 | "Failed to get module name: {0}" ); |
373 | continue; |
374 | } |
375 | |
376 | MapType::iterator iter; |
377 | bool inserted; |
378 | // See if we have inserted this module aready into filtered_modules. If we |
379 | // haven't insert an entry into module_name_to_filtered_index with the |
380 | // index where we will insert it if it isn't in the vector already. |
381 | std::tie(args&: iter, args&: inserted) = module_name_to_filtered_index.try_emplace( |
382 | Key: *ExpectedName, Args: filtered_modules.size()); |
383 | |
384 | if (inserted) { |
385 | // This module has not been seen yet, insert it into filtered_modules at |
386 | // the index that was inserted into module_name_to_filtered_index using |
387 | // "filtered_modules.size()" above. |
388 | filtered_modules.push_back(x: &module); |
389 | } else { |
390 | // We have a duplicate module entry. Check the linux regions to see if |
391 | // either module is not really a mapped executable. If one but not the |
392 | // other is a real mapped executable, prefer the executable one. This |
393 | // can happen when a process mmap's in the file for an executable in |
394 | // order to read bytes from the executable file. A memory region mapping |
395 | // will exist for the mmap'ed version and for the loaded executable, but |
396 | // only one will have a consecutive region that is executable in the |
397 | // memory regions. |
398 | auto dup_module = filtered_modules[iter->second]; |
399 | ConstString name(*ExpectedName); |
400 | bool is_executable = |
401 | CheckForLinuxExecutable(path: name, regions: linux_regions, base_of_image: module.BaseOfImage); |
402 | bool dup_is_executable = |
403 | CheckForLinuxExecutable(path: name, regions: linux_regions, base_of_image: dup_module->BaseOfImage); |
404 | |
405 | if (is_executable != dup_is_executable) { |
406 | if (is_executable) |
407 | filtered_modules[iter->second] = &module; |
408 | continue; |
409 | } |
410 | // This module has been seen. Modules are sometimes mentioned multiple |
411 | // times when they are mapped discontiguously, so find the module with |
412 | // the lowest "base_of_image" and use that as the filtered module. |
413 | if (module.BaseOfImage < dup_module->BaseOfImage) |
414 | filtered_modules[iter->second] = &module; |
415 | } |
416 | } |
417 | return filtered_modules; |
418 | } |
419 | |
420 | const minidump::ExceptionStream *MinidumpParser::GetExceptionStream() { |
421 | auto ExpectedStream = GetMinidumpFile().getExceptionStream(); |
422 | if (ExpectedStream) |
423 | return &*ExpectedStream; |
424 | |
425 | LLDB_LOG_ERROR(GetLog(LLDBLog::Process), ExpectedStream.takeError(), |
426 | "Failed to read minidump exception stream: {0}" ); |
427 | return nullptr; |
428 | } |
429 | |
430 | std::optional<minidump::Range> |
431 | MinidumpParser::FindMemoryRange(lldb::addr_t addr) { |
432 | llvm::ArrayRef<uint8_t> data64 = GetStream(stream_type: StreamType::Memory64List); |
433 | Log *log = GetLog(mask: LLDBLog::Modules); |
434 | |
435 | auto ExpectedMemory = GetMinidumpFile().getMemoryList(); |
436 | if (!ExpectedMemory) { |
437 | LLDB_LOG_ERROR(log, ExpectedMemory.takeError(), |
438 | "Failed to read memory list: {0}" ); |
439 | } else { |
440 | for (const auto &memory_desc : *ExpectedMemory) { |
441 | const LocationDescriptor &loc_desc = memory_desc.Memory; |
442 | const lldb::addr_t range_start = memory_desc.StartOfMemoryRange; |
443 | const size_t range_size = loc_desc.DataSize; |
444 | |
445 | if (loc_desc.RVA + loc_desc.DataSize > GetData().size()) |
446 | return std::nullopt; |
447 | |
448 | if (range_start <= addr && addr < range_start + range_size) { |
449 | auto ExpectedSlice = GetMinidumpFile().getRawData(Desc: loc_desc); |
450 | if (!ExpectedSlice) { |
451 | LLDB_LOG_ERROR(log, ExpectedSlice.takeError(), |
452 | "Failed to get memory slice: {0}" ); |
453 | return std::nullopt; |
454 | } |
455 | return minidump::Range(range_start, *ExpectedSlice); |
456 | } |
457 | } |
458 | } |
459 | |
460 | // Some Minidumps have a Memory64ListStream that captures all the heap memory |
461 | // (full-memory Minidumps). We can't exactly use the same loop as above, |
462 | // because the Minidump uses slightly different data structures to describe |
463 | // those |
464 | |
465 | if (!data64.empty()) { |
466 | llvm::ArrayRef<MinidumpMemoryDescriptor64> memory64_list; |
467 | uint64_t base_rva; |
468 | std::tie(args&: memory64_list, args&: base_rva) = |
469 | MinidumpMemoryDescriptor64::ParseMemory64List(data&: data64); |
470 | |
471 | if (memory64_list.empty()) |
472 | return std::nullopt; |
473 | |
474 | for (const auto &memory_desc64 : memory64_list) { |
475 | const lldb::addr_t range_start = memory_desc64.start_of_memory_range; |
476 | const size_t range_size = memory_desc64.data_size; |
477 | |
478 | if (base_rva + range_size > GetData().size()) |
479 | return std::nullopt; |
480 | |
481 | if (range_start <= addr && addr < range_start + range_size) { |
482 | return minidump::Range(range_start, |
483 | GetData().slice(N: base_rva, M: range_size)); |
484 | } |
485 | base_rva += range_size; |
486 | } |
487 | } |
488 | |
489 | return std::nullopt; |
490 | } |
491 | |
492 | llvm::ArrayRef<uint8_t> MinidumpParser::GetMemory(lldb::addr_t addr, |
493 | size_t size) { |
494 | // I don't have a sense of how frequently this is called or how many memory |
495 | // ranges a Minidump typically has, so I'm not sure if searching for the |
496 | // appropriate range linearly each time is stupid. Perhaps we should build |
497 | // an index for faster lookups. |
498 | std::optional<minidump::Range> range = FindMemoryRange(addr); |
499 | if (!range) |
500 | return {}; |
501 | |
502 | // There's at least some overlap between the beginning of the desired range |
503 | // (addr) and the current range. Figure out where the overlap begins and how |
504 | // much overlap there is. |
505 | |
506 | const size_t offset = addr - range->start; |
507 | |
508 | if (addr < range->start || offset >= range->range_ref.size()) |
509 | return {}; |
510 | |
511 | const size_t overlap = std::min(a: size, b: range->range_ref.size() - offset); |
512 | return range->range_ref.slice(N: offset, M: overlap); |
513 | } |
514 | |
515 | static bool |
516 | CreateRegionsCacheFromMemoryInfoList(MinidumpParser &parser, |
517 | std::vector<MemoryRegionInfo> ®ions) { |
518 | Log *log = GetLog(mask: LLDBLog::Modules); |
519 | auto ExpectedInfo = parser.GetMinidumpFile().getMemoryInfoList(); |
520 | if (!ExpectedInfo) { |
521 | LLDB_LOG_ERROR(log, ExpectedInfo.takeError(), |
522 | "Failed to read memory info list: {0}" ); |
523 | return false; |
524 | } |
525 | constexpr auto yes = MemoryRegionInfo::eYes; |
526 | constexpr auto no = MemoryRegionInfo::eNo; |
527 | for (const MemoryInfo &entry : *ExpectedInfo) { |
528 | MemoryRegionInfo region; |
529 | region.GetRange().SetRangeBase(entry.BaseAddress); |
530 | region.GetRange().SetByteSize(entry.RegionSize); |
531 | |
532 | MemoryProtection prot = entry.Protect; |
533 | region.SetReadable(bool(prot & MemoryProtection::NoAccess) ? no : yes); |
534 | region.SetWritable( |
535 | bool(prot & (MemoryProtection::ReadWrite | MemoryProtection::WriteCopy | |
536 | MemoryProtection::ExecuteReadWrite | |
537 | MemoryProtection::ExeciteWriteCopy)) |
538 | ? yes |
539 | : no); |
540 | region.SetExecutable( |
541 | bool(prot & (MemoryProtection::Execute | MemoryProtection::ExecuteRead | |
542 | MemoryProtection::ExecuteReadWrite | |
543 | MemoryProtection::ExeciteWriteCopy)) |
544 | ? yes |
545 | : no); |
546 | region.SetMapped(entry.State != MemoryState::Free ? yes : no); |
547 | regions.push_back(x: region); |
548 | } |
549 | return !regions.empty(); |
550 | } |
551 | |
552 | static bool |
553 | CreateRegionsCacheFromMemoryList(MinidumpParser &parser, |
554 | std::vector<MemoryRegionInfo> ®ions) { |
555 | Log *log = GetLog(mask: LLDBLog::Modules); |
556 | auto ExpectedMemory = parser.GetMinidumpFile().getMemoryList(); |
557 | if (!ExpectedMemory) { |
558 | LLDB_LOG_ERROR(log, ExpectedMemory.takeError(), |
559 | "Failed to read memory list: {0}" ); |
560 | return false; |
561 | } |
562 | regions.reserve(n: ExpectedMemory->size()); |
563 | for (const MemoryDescriptor &memory_desc : *ExpectedMemory) { |
564 | if (memory_desc.Memory.DataSize == 0) |
565 | continue; |
566 | MemoryRegionInfo region; |
567 | region.GetRange().SetRangeBase(memory_desc.StartOfMemoryRange); |
568 | region.GetRange().SetByteSize(memory_desc.Memory.DataSize); |
569 | region.SetReadable(MemoryRegionInfo::eYes); |
570 | region.SetMapped(MemoryRegionInfo::eYes); |
571 | regions.push_back(x: region); |
572 | } |
573 | regions.shrink_to_fit(); |
574 | return !regions.empty(); |
575 | } |
576 | |
577 | static bool |
578 | CreateRegionsCacheFromMemory64List(MinidumpParser &parser, |
579 | std::vector<MemoryRegionInfo> ®ions) { |
580 | llvm::ArrayRef<uint8_t> data = |
581 | parser.GetStream(stream_type: StreamType::Memory64List); |
582 | if (data.empty()) |
583 | return false; |
584 | llvm::ArrayRef<MinidumpMemoryDescriptor64> memory64_list; |
585 | uint64_t base_rva; |
586 | std::tie(args&: memory64_list, args&: base_rva) = |
587 | MinidumpMemoryDescriptor64::ParseMemory64List(data); |
588 | |
589 | if (memory64_list.empty()) |
590 | return false; |
591 | |
592 | regions.reserve(n: memory64_list.size()); |
593 | for (const auto &memory_desc : memory64_list) { |
594 | if (memory_desc.data_size == 0) |
595 | continue; |
596 | MemoryRegionInfo region; |
597 | region.GetRange().SetRangeBase(memory_desc.start_of_memory_range); |
598 | region.GetRange().SetByteSize(memory_desc.data_size); |
599 | region.SetReadable(MemoryRegionInfo::eYes); |
600 | region.SetMapped(MemoryRegionInfo::eYes); |
601 | regions.push_back(x: region); |
602 | } |
603 | regions.shrink_to_fit(); |
604 | return !regions.empty(); |
605 | } |
606 | |
607 | std::pair<MemoryRegionInfos, bool> MinidumpParser::BuildMemoryRegions() { |
608 | // We create the region cache using the best source. We start with |
609 | // the linux maps since they are the most complete and have names for the |
610 | // regions. Next we try the MemoryInfoList since it has |
611 | // read/write/execute/map data, and then fall back to the MemoryList and |
612 | // Memory64List to just get a list of the memory that is mapped in this |
613 | // core file |
614 | MemoryRegionInfos result; |
615 | const auto &return_sorted = [&](bool is_complete) { |
616 | llvm::sort(C&: result); |
617 | return std::make_pair(x: std::move(result), y&: is_complete); |
618 | }; |
619 | if (CreateRegionsCacheFromLinuxMaps(parser&: *this, regions&: result)) |
620 | return return_sorted(true); |
621 | if (CreateRegionsCacheFromMemoryInfoList(parser&: *this, regions&: result)) |
622 | return return_sorted(true); |
623 | if (CreateRegionsCacheFromMemoryList(parser&: *this, regions&: result)) |
624 | return return_sorted(false); |
625 | CreateRegionsCacheFromMemory64List(parser&: *this, regions&: result); |
626 | return return_sorted(false); |
627 | } |
628 | |
629 | #define ENUM_TO_CSTR(ST) \ |
630 | case StreamType::ST: \ |
631 | return #ST |
632 | |
633 | llvm::StringRef |
634 | MinidumpParser::GetStreamTypeAsString(StreamType stream_type) { |
635 | switch (stream_type) { |
636 | ENUM_TO_CSTR(Unused); |
637 | ENUM_TO_CSTR(ThreadList); |
638 | ENUM_TO_CSTR(ModuleList); |
639 | ENUM_TO_CSTR(MemoryList); |
640 | ENUM_TO_CSTR(Exception); |
641 | ENUM_TO_CSTR(SystemInfo); |
642 | ENUM_TO_CSTR(ThreadExList); |
643 | ENUM_TO_CSTR(Memory64List); |
644 | ENUM_TO_CSTR(CommentA); |
645 | ENUM_TO_CSTR(CommentW); |
646 | ENUM_TO_CSTR(HandleData); |
647 | ENUM_TO_CSTR(FunctionTable); |
648 | ENUM_TO_CSTR(UnloadedModuleList); |
649 | ENUM_TO_CSTR(MiscInfo); |
650 | ENUM_TO_CSTR(MemoryInfoList); |
651 | ENUM_TO_CSTR(ThreadInfoList); |
652 | ENUM_TO_CSTR(HandleOperationList); |
653 | ENUM_TO_CSTR(Token); |
654 | ENUM_TO_CSTR(JavascriptData); |
655 | ENUM_TO_CSTR(SystemMemoryInfo); |
656 | ENUM_TO_CSTR(ProcessVMCounters); |
657 | ENUM_TO_CSTR(LastReserved); |
658 | ENUM_TO_CSTR(BreakpadInfo); |
659 | ENUM_TO_CSTR(AssertionInfo); |
660 | ENUM_TO_CSTR(LinuxCPUInfo); |
661 | ENUM_TO_CSTR(LinuxProcStatus); |
662 | ENUM_TO_CSTR(LinuxLSBRelease); |
663 | ENUM_TO_CSTR(LinuxCMDLine); |
664 | ENUM_TO_CSTR(LinuxEnviron); |
665 | ENUM_TO_CSTR(LinuxAuxv); |
666 | ENUM_TO_CSTR(LinuxMaps); |
667 | ENUM_TO_CSTR(LinuxDSODebug); |
668 | ENUM_TO_CSTR(LinuxProcStat); |
669 | ENUM_TO_CSTR(LinuxProcUptime); |
670 | ENUM_TO_CSTR(LinuxProcFD); |
671 | ENUM_TO_CSTR(FacebookAppCustomData); |
672 | ENUM_TO_CSTR(FacebookBuildID); |
673 | ENUM_TO_CSTR(FacebookAppVersionName); |
674 | ENUM_TO_CSTR(FacebookJavaStack); |
675 | ENUM_TO_CSTR(FacebookDalvikInfo); |
676 | ENUM_TO_CSTR(FacebookUnwindSymbols); |
677 | ENUM_TO_CSTR(FacebookDumpErrorLog); |
678 | ENUM_TO_CSTR(FacebookAppStateLog); |
679 | ENUM_TO_CSTR(FacebookAbortReason); |
680 | ENUM_TO_CSTR(FacebookThreadName); |
681 | ENUM_TO_CSTR(FacebookLogcat); |
682 | } |
683 | return "unknown stream type" ; |
684 | } |
685 | |
686 | MemoryRegionInfo |
687 | MinidumpParser::GetMemoryRegionInfo(const MemoryRegionInfos ®ions, |
688 | lldb::addr_t load_addr) { |
689 | MemoryRegionInfo region; |
690 | auto pos = llvm::upper_bound(Range: regions, Value&: load_addr); |
691 | if (pos != regions.begin() && |
692 | std::prev(x: pos)->GetRange().Contains(r: load_addr)) { |
693 | return *std::prev(x: pos); |
694 | } |
695 | |
696 | if (pos == regions.begin()) |
697 | region.GetRange().SetRangeBase(0); |
698 | else |
699 | region.GetRange().SetRangeBase(std::prev(x: pos)->GetRange().GetRangeEnd()); |
700 | |
701 | if (pos == regions.end()) |
702 | region.GetRange().SetRangeEnd(UINT64_MAX); |
703 | else |
704 | region.GetRange().SetRangeEnd(pos->GetRange().GetRangeBase()); |
705 | |
706 | region.SetReadable(MemoryRegionInfo::eNo); |
707 | region.SetWritable(MemoryRegionInfo::eNo); |
708 | region.SetExecutable(MemoryRegionInfo::eNo); |
709 | region.SetMapped(MemoryRegionInfo::eNo); |
710 | return region; |
711 | } |
712 | |