Author: Jacob Lalonde Date: 2024-09-09T21:07:12-07:00 New Revision: 492683527eda7ef055660842d9a6b7e2575ae208
URL: https://github.com/llvm/llvm-project/commit/492683527eda7ef055660842d9a6b7e2575ae208 DIFF: https://github.com/llvm/llvm-project/commit/492683527eda7ef055660842d9a6b7e2575ae208.diff LOG: [LLDB][Minidump] Support minidumps where there are multiple exception streams (#97470) Currently, LLDB assumes all minidumps will have unique sections. This is intuitive because almost all of the minidump sections are themselves lists. Exceptions including Signals are unique in that they are all individual sections with their own directory. This means LLDB fails to load minidumps with multiple exceptions due to them not being unique. This behavior is erroneous and this PR introduces support for an arbitrary number of exception streams. Additionally, stop info was calculated only for a single thread before, and now we properly support mapping exceptions to threads. ~~This PR is starting in DRAFT because implementing testing is still required.~~ Added: lldb/test/API/functionalities/postmortem/minidump-new/multiple-sigsev.yaml Modified: lldb/source/Plugins/Process/minidump/MinidumpParser.cpp lldb/source/Plugins/Process/minidump/MinidumpParser.h lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp lldb/source/Plugins/Process/minidump/ProcessMinidump.h lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py lldb/unittests/Process/minidump/MinidumpParserTest.cpp Removed: ################################################################################ diff --git a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp index c099c28a620ecf..afc095ddbb2f91 100644 --- a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp +++ b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp @@ -417,14 +417,9 @@ std::vector<const minidump::Module *> MinidumpParser::GetFilteredModuleList() { return filtered_modules; } -const minidump::ExceptionStream *MinidumpParser::GetExceptionStream() { - auto ExpectedStream = GetMinidumpFile().getExceptionStream(); - if (ExpectedStream) - return &*ExpectedStream; - - LLDB_LOG_ERROR(GetLog(LLDBLog::Process), ExpectedStream.takeError(), - "Failed to read minidump exception stream: {0}"); - return nullptr; +llvm::iterator_range<ExceptionStreamsIterator> +MinidumpParser::GetExceptionStreams() { + return GetMinidumpFile().getExceptionStreams(); } std::optional<minidump::Range> diff --git a/lldb/source/Plugins/Process/minidump/MinidumpParser.h b/lldb/source/Plugins/Process/minidump/MinidumpParser.h index 222c0ef47fb853..f0b6e6027c52f0 100644 --- a/lldb/source/Plugins/Process/minidump/MinidumpParser.h +++ b/lldb/source/Plugins/Process/minidump/MinidumpParser.h @@ -48,6 +48,8 @@ struct Range { }; using FallibleMemory64Iterator = llvm::object::MinidumpFile::FallibleMemory64Iterator; +using ExceptionStreamsIterator = + llvm::object::MinidumpFile::ExceptionStreamsIterator; class MinidumpParser { public: @@ -84,7 +86,7 @@ class MinidumpParser { // have the same name, it keeps the copy with the lowest load address. std::vector<const minidump::Module *> GetFilteredModuleList(); - const llvm::minidump::ExceptionStream *GetExceptionStream(); + llvm::iterator_range<ExceptionStreamsIterator> GetExceptionStreams(); std::optional<Range> FindMemoryRange(lldb::addr_t addr); diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp index ac1ecbfc0e2e70..42cc9f02518b32 100644 --- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp +++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp @@ -157,8 +157,7 @@ ProcessMinidump::ProcessMinidump(lldb::TargetSP target_sp, const FileSpec &core_file, DataBufferSP core_data) : PostMortemProcess(target_sp, listener_sp, core_file), - m_core_data(std::move(core_data)), m_active_exception(nullptr), - m_is_wow64(false) {} + m_core_data(std::move(core_data)), m_is_wow64(false) {} ProcessMinidump::~ProcessMinidump() { Clear(); @@ -209,7 +208,20 @@ Status ProcessMinidump::DoLoadCore() { GetTarget().SetArchitecture(arch, true /*set_platform*/); m_thread_list = m_minidump_parser->GetThreads(); - m_active_exception = m_minidump_parser->GetExceptionStream(); + auto exception_stream_it = m_minidump_parser->GetExceptionStreams(); + for (auto exception_stream : exception_stream_it) { + // If we can't read an exception stream skip it + // We should probably serve a warning + if (!exception_stream) + continue; + + if (!m_exceptions_by_tid + .try_emplace(exception_stream->ThreadId, exception_stream.get()) + .second) { + return Status::FromErrorStringWithFormatv( + "Duplicate exception stream for tid {0}", exception_stream->ThreadId); + } + } SetUnixSignals(UnixSignals::Create(GetArchitecture())); @@ -232,60 +244,57 @@ Status ProcessMinidump::DoDestroy() { return Status(); } void ProcessMinidump::RefreshStateAfterStop() { - if (!m_active_exception) - return; - - constexpr uint32_t BreakpadDumpRequested = 0xFFFFFFFF; - if (m_active_exception->ExceptionRecord.ExceptionCode == - BreakpadDumpRequested) { - // This "ExceptionCode" value is a sentinel that is sometimes used - // when generating a dump for a process that hasn't crashed. - - // TODO: The definition and use of this "dump requested" constant - // in Breakpad are actually Linux-specific, and for similar use - // cases on Mac/Windows it defines diff erent constants, referring - // to them as "simulated" exceptions; consider moving this check - // down to the OS-specific paths and checking each OS for its own - // constant. - return; - } + for (const auto &[_, exception_stream] : m_exceptions_by_tid) { + constexpr uint32_t BreakpadDumpRequested = 0xFFFFFFFF; + if (exception_stream.ExceptionRecord.ExceptionCode == + BreakpadDumpRequested) { + // This "ExceptionCode" value is a sentinel that is sometimes used + // when generating a dump for a process that hasn't crashed. + + // TODO: The definition and use of this "dump requested" constant + // in Breakpad are actually Linux-specific, and for similar use + // cases on Mac/Windows it defines diff erent constants, referring + // to them as "simulated" exceptions; consider moving this check + // down to the OS-specific paths and checking each OS for its own + // constant. + return; + } - lldb::StopInfoSP stop_info; - lldb::ThreadSP stop_thread; + lldb::StopInfoSP stop_info; + lldb::ThreadSP stop_thread; - Process::m_thread_list.SetSelectedThreadByID(m_active_exception->ThreadId); - stop_thread = Process::m_thread_list.GetSelectedThread(); - ArchSpec arch = GetArchitecture(); + Process::m_thread_list.SetSelectedThreadByID(exception_stream.ThreadId); + stop_thread = Process::m_thread_list.GetSelectedThread(); + ArchSpec arch = GetArchitecture(); - if (arch.GetTriple().getOS() == llvm::Triple::Linux) { - uint32_t signo = m_active_exception->ExceptionRecord.ExceptionCode; + if (arch.GetTriple().getOS() == llvm::Triple::Linux) { + uint32_t signo = exception_stream.ExceptionRecord.ExceptionCode; + if (signo == 0) { + // No stop. + return; + } - if (signo == 0) { - // No stop. - return; + stop_info = StopInfo::CreateStopReasonWithSignal(*stop_thread, signo); + } else if (arch.GetTriple().getVendor() == llvm::Triple::Apple) { + stop_info = StopInfoMachException::CreateStopReasonWithMachException( + *stop_thread, exception_stream.ExceptionRecord.ExceptionCode, 2, + exception_stream.ExceptionRecord.ExceptionFlags, + exception_stream.ExceptionRecord.ExceptionAddress, 0); + } else { + std::string desc; + llvm::raw_string_ostream desc_stream(desc); + desc_stream << "Exception " + << llvm::format_hex( + exception_stream.ExceptionRecord.ExceptionCode, 8) + << " encountered at address " + << llvm::format_hex( + exception_stream.ExceptionRecord.ExceptionAddress, 8); + stop_info = StopInfo::CreateStopReasonWithException( + *stop_thread, desc_stream.str().c_str()); } - stop_info = StopInfo::CreateStopReasonWithSignal( - *stop_thread, signo); - } else if (arch.GetTriple().getVendor() == llvm::Triple::Apple) { - stop_info = StopInfoMachException::CreateStopReasonWithMachException( - *stop_thread, m_active_exception->ExceptionRecord.ExceptionCode, 2, - m_active_exception->ExceptionRecord.ExceptionFlags, - m_active_exception->ExceptionRecord.ExceptionAddress, 0); - } else { - std::string desc; - llvm::raw_string_ostream desc_stream(desc); - desc_stream << "Exception " - << llvm::format_hex( - m_active_exception->ExceptionRecord.ExceptionCode, 8) - << " encountered at address " - << llvm::format_hex( - m_active_exception->ExceptionRecord.ExceptionAddress, 8); - stop_info = StopInfo::CreateStopReasonWithException( - *stop_thread, desc_stream.str().c_str()); - } - - stop_thread->SetStopInfo(stop_info); + stop_thread->SetStopInfo(stop_info); + } } bool ProcessMinidump::IsAlive() { return true; } @@ -387,10 +396,9 @@ bool ProcessMinidump::DoUpdateThreadList(ThreadList &old_thread_list, LocationDescriptor context_location = thread.Context; // If the minidump contains an exception context, use it - if (m_active_exception != nullptr && - m_active_exception->ThreadId == thread.ThreadId) { - context_location = m_active_exception->ThreadContext; - } + if (auto it = m_exceptions_by_tid.find(thread.ThreadId); + it != m_exceptions_by_tid.end()) + context_location = it->second.ThreadContext; llvm::ArrayRef<uint8_t> context; if (!m_is_wow64) diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h index e39ae3913e8782..f2ea0a2b61d14e 100644 --- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h +++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h @@ -107,7 +107,8 @@ class ProcessMinidump : public PostMortemProcess { private: lldb::DataBufferSP m_core_data; llvm::ArrayRef<minidump::Thread> m_thread_list; - const minidump::ExceptionStream *m_active_exception; + std::unordered_map<uint32_t, const minidump::ExceptionStream> + m_exceptions_by_tid; lldb::CommandObjectSP m_command_sp; bool m_is_wow64; std::optional<MemoryRegionInfos> m_memory_regions; diff --git a/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py b/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py index 2de3e36b507341..5a0b6e790a424c 100644 --- a/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py +++ b/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py @@ -505,8 +505,22 @@ def test_minidump_memory64list(self): self.assertEqual(region.GetRegionBase(), 0x7FFF12A84030) self.assertTrue(region.GetRegionEnd(), 0x7FFF12A84030 + 0x2FD0) self.assertTrue(region_info_list.GetMemoryRegionAtIndex(1, region)) - self.assertEqual(region.GetRegionBase(), 0x00007fff12a87000) - self.assertTrue(region.GetRegionEnd(), 0x00007fff12a87000 + 0x00000018) + self.assertEqual(region.GetRegionBase(), 0x00007FFF12A87000) + self.assertTrue(region.GetRegionEnd(), 0x00007FFF12A87000 + 0x00000018) self.assertTrue(region_info_list.GetMemoryRegionAtIndex(2, region)) - self.assertEqual(region.GetRegionBase(), 0x00007fff12a87018) - self.assertTrue(region.GetRegionEnd(), 0x00007fff12a87018 + 0x00000400) + self.assertEqual(region.GetRegionBase(), 0x00007FFF12A87018) + self.assertTrue(region.GetRegionEnd(), 0x00007FFF12A87018 + 0x00000400) + + def test_multiple_exceptions_or_signals(self): + """Test that lldb can read the exception information from the Minidump.""" + print("Starting to read multiple-sigsev.yaml") + self.process_from_yaml("multiple-sigsev.yaml") + print("Done reading multiple-sigsev.yaml") + self.check_state() + # This process crashed due to a segmentation fault in both it's threads. + self.assertEqual(self.process.GetNumThreads(), 2) + for i in range(2): + thread = self.process.GetThreadAtIndex(i) + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal) + stop_description = thread.GetStopDescription(256) + self.assertIn("SIGSEGV", stop_description) diff --git a/lldb/test/API/functionalities/postmortem/minidump-new/multiple-sigsev.yaml b/lldb/test/API/functionalities/postmortem/minidump-new/multiple-sigsev.yaml new file mode 100644 index 00000000000000..f6fcfdbf5c0eb0 --- /dev/null +++ b/lldb/test/API/functionalities/postmortem/minidump-new/multiple-sigsev.yaml @@ -0,0 +1,39 @@ +--- !minidump +Streams: + - Type: ThreadList + Threads: + - Thread Id: 0x1B4F23 + Context: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0010000000000033000000000000000000000006020100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000010A234EBFC7F000010A234EBFC7F00000000000000000000F09C34EBFC7F0000C0A91ABCE97F00000000000000000000A0163FBCE97F00004602000000000000921C40000000000030A434EBFC7F000000000000000000000000000000000000C61D4000000000007F0300000000000000000000000000000000000000000000801F0000FFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF25252525252525252525252525252525000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + Stack: + Start of Memory Range: 0x7FFFFFFFD348 + Content: '' + - Thread Id: 0x1B6D22 + Context: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0010000000000033000000000000000000000006020100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000010A234EBFC7F000010A234EBFC7F00000000000000000000F09C34EBFC7F0000C0A91ABCE97F00000000000000000000A0163FBCE97F00004602000000000000921C40000000000030A434EBFC7F000000000000000000000000000000000000C61D4000000000007F0300000000000000000000000000000000000000000000801F0000FFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF25252525252525252525252525252525000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + Stack: + Start of Memory Range: 0x7FFFF75FDE28 + Content: '' + - Type: ModuleList + Modules: + - Base of Image: 0x0000000000400000 + Size of Image: 0x00017000 + Module Name: 'a.out' + CodeView Record: '' + - Type: SystemInfo + Processor Arch: AMD64 + Platform ID: Linux + CSD Version: 'Linux 3.13' + CPU: + Vendor ID: GenuineIntel + Version Info: 0x00000000 + Feature Info: 0x00000000 + - Type: Exception + Thread ID: 0x1B4F23 + Exception Record: + Exception Code: 0xB + Thread Context: 00000000 + - Type: Exception + Thread ID: 0x1B6D22 + Exception Record: + Exception Code: 0xB + Thread Context: 00000000 +... diff --git a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp index 632a7fd4e4f8fa..c7547ba261c7f7 100644 --- a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp +++ b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp @@ -251,10 +251,15 @@ TEST_F(MinidumpParserTest, GetFilteredModuleList) { TEST_F(MinidumpParserTest, GetExceptionStream) { SetUpData("linux-x86_64.dmp"); - const llvm::minidump::ExceptionStream *exception_stream = - parser->GetExceptionStream(); - ASSERT_NE(nullptr, exception_stream); - ASSERT_EQ(11UL, exception_stream->ExceptionRecord.ExceptionCode); + auto exception_streams = parser->GetExceptionStreams(); + size_t count = 0; + for (auto exception_stream : exception_streams) { + ASSERT_THAT_EXPECTED(exception_stream, llvm::Succeeded()); + ASSERT_EQ(16001UL, exception_stream->ThreadId); + count++; + } + + ASSERT_THAT(1UL, count); } void check_mem_range_exists(MinidumpParser &parser, const uint64_t range_start, _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits