void Dump(Stream *s) const override;
+ virtual bool ShouldStop(Event *event_ptr, bool &found_valid_stopinfo);
+
void DoOnRemoval(Event *event_ptr) override;
static const Process::ProcessEventData *
return ProcessEventData::GetFlavorString();
}
+bool Process::ProcessEventData::ShouldStop(Event *event_ptr,
+ bool &found_valid_stopinfo) {
+ found_valid_stopinfo = false;
+
+ ProcessSP process_sp(m_process_wp.lock());
+ if (!process_sp)
+ return false;
+
+ ThreadList &curr_thread_list = process_sp->GetThreadList();
+ uint32_t num_threads = curr_thread_list.GetSize();
+ uint32_t idx;
+
+ // The actions might change one of the thread's stop_info's opinions about
+ // whether we should stop the process, so we need to query that as we go.
+
+ // One other complication here, is that we try to catch any case where the
+ // target has run (except for expressions) and immediately exit, but if we
+ // get that wrong (which is possible) then the thread list might have
+ // changed, and that would cause our iteration here to crash. We could
+ // make a copy of the thread list, but we'd really like to also know if it
+ // has changed at all, so we make up a vector of the thread ID's and check
+ // what we get back against this list & bag out if anything differs.
+ ThreadList not_suspended_thread_list(process_sp.get());
+ std::vector<uint32_t> thread_index_array(num_threads);
+ uint32_t not_suspended_idx = 0;
+ for (idx = 0; idx < num_threads; ++idx) {
+ lldb::ThreadSP thread_sp = curr_thread_list.GetThreadAtIndex(idx);
+
+ /*
+ Filter out all suspended threads, they could not be the reason
+ of stop and no need to perform any actions on them.
+ */
+ if (thread_sp->GetResumeState() != eStateSuspended) {
+ not_suspended_thread_list.AddThread(thread_sp);
+ thread_index_array[not_suspended_idx] = thread_sp->GetIndexID();
+ not_suspended_idx++;
+ }
+ }
+
+ // Use this to track whether we should continue from here. We will only
+ // continue the target running if no thread says we should stop. Of course
+ // if some thread's PerformAction actually sets the target running, then it
+ // doesn't matter what the other threads say...
+
+ bool still_should_stop = false;
+
+ // Sometimes - for instance if we have a bug in the stub we are talking to,
+ // we stop but no thread has a valid stop reason. In that case we should
+ // just stop, because we have no way of telling what the right thing to do
+ // is, and it's better to let the user decide than continue behind their
+ // backs.
+
+ for (idx = 0; idx < not_suspended_thread_list.GetSize(); ++idx) {
+ curr_thread_list = process_sp->GetThreadList();
+ if (curr_thread_list.GetSize() != num_threads) {
+ Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP |
+ LIBLLDB_LOG_PROCESS));
+ LLDB_LOGF(
+ log,
+ "Number of threads changed from %u to %u while processing event.",
+ num_threads, curr_thread_list.GetSize());
+ break;
+ }
+
+ lldb::ThreadSP thread_sp = not_suspended_thread_list.GetThreadAtIndex(idx);
+
+ if (thread_sp->GetIndexID() != thread_index_array[idx]) {
+ Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP |
+ LIBLLDB_LOG_PROCESS));
+ LLDB_LOGF(log,
+ "The thread at position %u changed from %u to %u while "
+ "processing event.",
+ idx, thread_index_array[idx], thread_sp->GetIndexID());
+ break;
+ }
+
+ StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
+ if (stop_info_sp && stop_info_sp->IsValid()) {
+ found_valid_stopinfo = true;
+ bool this_thread_wants_to_stop;
+ if (stop_info_sp->GetOverrideShouldStop()) {
+ this_thread_wants_to_stop =
+ stop_info_sp->GetOverriddenShouldStopValue();
+ } else {
+ stop_info_sp->PerformAction(event_ptr);
+ // The stop action might restart the target. If it does, then we
+ // want to mark that in the event so that whoever is receiving it
+ // will know to wait for the running event and reflect that state
+ // appropriately. We also need to stop processing actions, since they
+ // aren't expecting the target to be running.
+
+ // FIXME: we might have run.
+ if (stop_info_sp->HasTargetRunSinceMe()) {
+ SetRestarted(true);
+ break;
+ }
+
+ this_thread_wants_to_stop = stop_info_sp->ShouldStop(event_ptr);
+ }
+
+ if (!still_should_stop)
+ still_should_stop = this_thread_wants_to_stop;
+ }
+ }
+
+ return still_should_stop;
+}
+
void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) {
ProcessSP process_sp(m_process_wp.lock());
if (m_interrupted)
return;
- // If we're stopped and haven't restarted, then do the StopInfo actions here:
- if (m_state == eStateStopped && !m_restarted) {
- ThreadList &curr_thread_list = process_sp->GetThreadList();
- uint32_t num_threads = curr_thread_list.GetSize();
- uint32_t idx;
-
- // The actions might change one of the thread's stop_info's opinions about
- // whether we should stop the process, so we need to query that as we go.
-
- // One other complication here, is that we try to catch any case where the
- // target has run (except for expressions) and immediately exit, but if we
- // get that wrong (which is possible) then the thread list might have
- // changed, and that would cause our iteration here to crash. We could
- // make a copy of the thread list, but we'd really like to also know if it
- // has changed at all, so we make up a vector of the thread ID's and check
- // what we get back against this list & bag out if anything differs.
- std::vector<uint32_t> thread_index_array(num_threads);
- for (idx = 0; idx < num_threads; ++idx)
- thread_index_array[idx] =
- curr_thread_list.GetThreadAtIndex(idx)->GetIndexID();
-
- // Use this to track whether we should continue from here. We will only
- // continue the target running if no thread says we should stop. Of course
- // if some thread's PerformAction actually sets the target running, then it
- // doesn't matter what the other threads say...
-
- bool still_should_stop = false;
-
- // Sometimes - for instance if we have a bug in the stub we are talking to,
- // we stop but no thread has a valid stop reason. In that case we should
- // just stop, because we have no way of telling what the right thing to do
- // is, and it's better to let the user decide than continue behind their
- // backs.
-
- bool does_anybody_have_an_opinion = false;
-
- for (idx = 0; idx < num_threads; ++idx) {
- curr_thread_list = process_sp->GetThreadList();
- if (curr_thread_list.GetSize() != num_threads) {
- Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP |
- LIBLLDB_LOG_PROCESS));
- LLDB_LOGF(
- log,
- "Number of threads changed from %u to %u while processing event.",
- num_threads, curr_thread_list.GetSize());
- break;
- }
-
- lldb::ThreadSP thread_sp = curr_thread_list.GetThreadAtIndex(idx);
-
- if (thread_sp->GetIndexID() != thread_index_array[idx]) {
- Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP |
- LIBLLDB_LOG_PROCESS));
- LLDB_LOGF(log,
- "The thread at position %u changed from %u to %u while "
- "processing event.",
- idx, thread_index_array[idx], thread_sp->GetIndexID());
- break;
- }
-
- StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
- if (stop_info_sp && stop_info_sp->IsValid()) {
- does_anybody_have_an_opinion = true;
- bool this_thread_wants_to_stop;
- if (stop_info_sp->GetOverrideShouldStop()) {
- this_thread_wants_to_stop =
- stop_info_sp->GetOverriddenShouldStopValue();
- } else {
- stop_info_sp->PerformAction(event_ptr);
- // The stop action might restart the target. If it does, then we
- // want to mark that in the event so that whoever is receiving it
- // will know to wait for the running event and reflect that state
- // appropriately. We also need to stop processing actions, since they
- // aren't expecting the target to be running.
-
- // FIXME: we might have run.
- if (stop_info_sp->HasTargetRunSinceMe()) {
- SetRestarted(true);
- break;
- }
+ // If we're not stopped or have restarted, then skip the StopInfo actions:
+ if (m_state != eStateStopped || m_restarted) {
+ return;
+ }
- this_thread_wants_to_stop = stop_info_sp->ShouldStop(event_ptr);
- }
+ bool does_anybody_have_an_opinion = false;
+ bool still_should_stop = ShouldStop(event_ptr, does_anybody_have_an_opinion);
- if (!still_should_stop)
- still_should_stop = this_thread_wants_to_stop;
- }
- }
+ if (GetRestarted()) {
+ return;
+ }
- if (!GetRestarted()) {
- if (!still_should_stop && does_anybody_have_an_opinion) {
- // We've been asked to continue, so do that here.
+ if (!still_should_stop && does_anybody_have_an_opinion) {
+ // We've been asked to continue, so do that here.
+ SetRestarted(true);
+ // Use the public resume method here, since this is just extending a
+ // public resume.
+ process_sp->PrivateResume();
+ } else {
+ bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) &&
+ !process_sp->StateChangedIsHijackedForSynchronousResume();
+
+ if (!hijacked) {
+ // If we didn't restart, run the Stop Hooks here.
+ // Don't do that if state changed events aren't hooked up to the
+ // public (or SyncResume) broadcasters. StopHooks are just for
+ // real public stops. They might also restart the target,
+ // so watch for that.
+ process_sp->GetTarget().RunStopHooks();
+ if (process_sp->GetPrivateState() == eStateRunning)
SetRestarted(true);
- // Use the public resume method here, since this is just extending a
- // public resume.
- process_sp->PrivateResume();
- } else {
- bool hijacked =
- process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) &&
- !process_sp->StateChangedIsHijackedForSynchronousResume();
-
- if (!hijacked) {
- // If we didn't restart, run the Stop Hooks here.
- // Don't do that if state changed events aren't hooked up to the
- // public (or SyncResume) broadcasters. StopHooks are just for
- // real public stops. They might also restart the target,
- // so watch for that.
- process_sp->GetTarget().RunStopHooks();
- if (process_sp->GetPrivateState() == eStateRunning)
- SetRestarted(true);
- }
}
}
}
-}
void Process::ProcessEventData::Dump(Stream *s) const {
ProcessSP process_sp(m_process_wp.lock());
--- /dev/null
+ENABLE_THREADS := YES
+CXX_SOURCES := main.cpp
+include Makefile.rules
--- /dev/null
+"""
+Test that suspended threads do not affect should-stop decisions.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbsuite.test.lldbutil as lldbutil
+
+
+class IgnoreSuspendedThreadTestCase(TestBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def setUp(self):
+ #Call super's setUp().
+ TestBase.setUp(self)
+ #Find the line numbers for our breakpoints.
+ self.break_1 = line_number('main.cpp', '// Set first breakpoint here')
+ self.break_2 = line_number('main.cpp', '// Set second breakpoint here')
+ self.break_3 = line_number('main.cpp', '// Set third breakpoint here')
+
+ def printThreadsStoppedByBreakpoint(self, process):
+ stopped_threads = \
+ lldbutil.get_stopped_threads(process, lldb.eStopReasonBreakpoint)
+ for thread in stopped_threads:
+ break_id = thread.GetStopReasonDataAtIndex(0)
+ print('Thread ' + str(thread.GetThreadID()) + \
+ ' stopped at breakpoint ' + str(break_id))
+
+ def test(self):
+ self.build()
+ target = lldbutil.run_to_breakpoint_make_target(self)
+
+ #This should create a breakpoint with 1 location.
+ bp1_id = \
+ lldbutil.run_break_set_by_file_and_line(self,
+ "main.cpp",
+ self.break_1,
+ num_expected_locations=1)
+
+ bp2_id = \
+ lldbutil.run_break_set_by_file_and_line(self,
+ "main.cpp",
+ self.break_2,
+ num_expected_locations=1)
+
+ bp3_id = \
+ lldbutil.run_break_set_by_file_and_line(self,
+ "main.cpp",
+ self.break_3,
+ num_expected_locations=1)
+
+ #Run the program.
+ self.runCmd("run", RUN_SUCCEEDED)
+ #Get the target process
+ process = target.GetProcess()
+
+ if self.TraceOn():
+ print('First stop:')
+ self.printThreadsStoppedByBreakpoint(process)
+
+ thread_to_suspend = \
+ lldbutil.get_one_thread_stopped_at_breakpoint_id(process,
+ bp1_id)
+ self.assertIsNotNone(thread_to_suspend, "Should hit breakpoint 1")
+ thread_to_suspend.Suspend()
+
+ #Do not stop at bp2 and autocontinue to bp3
+ target.FindBreakpointByID(bp2_id).SetAutoContinue(True)
+
+ #Run to the third breakpoint
+ self.runCmd("continue")
+
+ if self.TraceOn():
+ print('Second stop:')
+ self.printThreadsStoppedByBreakpoint(process)
+
+ stopped_thread = \
+ lldbutil.get_one_thread_stopped_at_breakpoint_id(process,
+ bp3_id)
+ self.assertIsNotNone(stopped_thread,
+ "Should hit breakpoint 3")
+
+ thread_to_suspend.Resume()
+
+ #Run to completion
+ self.runCmd("continue")
+
+ #At this point, the inferior process should have exited.
+ self.assertEqual(process.GetState(),
+ lldb.eStateExited,
+ PROCESS_EXITED)
--- /dev/null
+// Test simulates situation when suspended thread could stop process
+// where thread that is a real reason of stop says process
+// should not stop in it's action handler.
+
+#include <chrono>
+#include <thread>
+
+void thread1() {
+ // Will be suspended at breakpoint stop
+ // Set first breakpoint here
+}
+
+void thread2() {
+ /*
+ Prevent threads from stopping simultaneously
+ */
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ // Set second breakpoint here
+}
+
+int main() {
+ // Create a thread
+ std::thread thread_1(thread1);
+
+ // Create another thread.
+ std::thread thread_2(thread2);
+
+ // Wait for the thread that was not suspeneded
+ thread_2.join();
+
+ // Wait for thread that was suspended
+ thread_1.join(); // Set third breakpoint here
+
+ return 0;
+}
add_subdirectory(tools)
add_subdirectory(UnwindAssembly)
add_subdirectory(Utility)
+add_subdirectory(Thread)
if(LLDB_CAN_USE_DEBUGSERVER AND LLDB_TOOL_DEBUGSERVER_BUILD AND NOT LLDB_USE_SYSTEM_DEBUGSERVER)
add_subdirectory(debugserver)
add_subdirectory(POSIX)
endif()
add_subdirectory(minidump)
+
+add_lldb_unittest(ProcessEventDataTest
+ ProcessEventDataTest.cpp
+
+ LINK_LIBS
+ lldbCore
+ lldbHost
+ lldbTarget
+ lldbSymbol
+ lldbUtility
+ lldbUtilityHelpers
+ lldbInterpreter
+ lldbPluginPlatformMacOSX
+ )
--- /dev/null
+//===-- ProcessEventDataTest.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Plugins/Platform/MacOSX/PlatformMacOSX.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StopInfo.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/Event.h"
+#include "lldb/Utility/Reproducer.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+using namespace lldb_private::repro;
+using namespace lldb;
+
+namespace {
+class ProcessEventDataTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None));
+ FileSystem::Initialize();
+ HostInfo::Initialize();
+ PlatformMacOSX::Initialize();
+ }
+ void TearDown() override {
+ PlatformMacOSX::Terminate();
+ HostInfo::Terminate();
+ FileSystem::Terminate();
+ Reproducer::Terminate();
+ }
+};
+
+class DummyProcess : public Process {
+public:
+ using Process::Process;
+
+ virtual bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) {
+ return true;
+ }
+ virtual Status DoDestroy() { return {}; }
+ virtual void RefreshStateAfterStop() {}
+ virtual size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ Status &error) {
+ return 0;
+ }
+ virtual bool UpdateThreadList(ThreadList &old_thread_list,
+ ThreadList &new_thread_list) {
+ return false;
+ }
+ virtual ConstString GetPluginName() { return ConstString("Dummy"); }
+ virtual uint32_t GetPluginVersion() { return 0; }
+
+ ProcessModID &GetModIDNonConstRef() { return m_mod_id; }
+};
+
+class DummyThread : public Thread {
+public:
+ using Thread::Thread;
+
+ ~DummyThread() override { DestroyThread(); }
+
+ void RefreshStateAfterStop() override {}
+
+ lldb::RegisterContextSP GetRegisterContext() override { return nullptr; }
+
+ lldb::RegisterContextSP
+ CreateRegisterContextForFrame(StackFrame *frame) override {
+ return nullptr;
+ }
+
+ bool CalculateStopInfo() override { return false; }
+};
+
+class DummyStopInfo : public StopInfo {
+public:
+ DummyStopInfo(Thread &thread, uint64_t value)
+ : StopInfo(thread, value), m_should_stop(true),
+ m_stop_reason(eStopReasonBreakpoint) {}
+
+ bool ShouldStop(Event *event_ptr) override { return m_should_stop; }
+
+ StopReason GetStopReason() const override { return m_stop_reason; }
+
+ bool m_should_stop;
+ StopReason m_stop_reason;
+};
+
+class DummyProcessEventData : public Process::ProcessEventData {
+public:
+ DummyProcessEventData(ProcessSP &process_sp, StateType state)
+ : ProcessEventData(process_sp, state), m_should_stop_hit_count(0) {}
+ bool ShouldStop(Event *event_ptr, bool &found_valid_stopinfo) override {
+ m_should_stop_hit_count++;
+ return false;
+ }
+
+ int m_should_stop_hit_count;
+};
+} // namespace
+
+typedef std::shared_ptr<Process::ProcessEventData> ProcessEventDataSP;
+typedef std::shared_ptr<Event> EventSP;
+
+TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) {
+ Status error;
+ PlatformSP platform_sp;
+ TargetSP target_sp;
+ error = debugger_sp->GetTargetList().CreateTarget(
+ *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
+
+ if (target_sp) {
+ debugger_sp->GetTargetList().SetSelectedTarget(target_sp.get());
+ }
+
+ return target_sp;
+}
+
+ThreadSP CreateThread(ProcessSP &process_sp, bool should_stop,
+ bool has_valid_stopinfo) {
+ ThreadSP thread_sp = std::make_shared<DummyThread>(*process_sp.get(), 0);
+ if (thread_sp == nullptr) {
+ return nullptr;
+ }
+
+ if (has_valid_stopinfo) {
+ StopInfoSP stopinfo_sp =
+ std::make_shared<DummyStopInfo>(*thread_sp.get(), 0);
+ static_cast<DummyStopInfo *>(stopinfo_sp.get())->m_should_stop =
+ should_stop;
+ if (stopinfo_sp == nullptr) {
+ return nullptr;
+ }
+
+ thread_sp->SetStopInfo(stopinfo_sp);
+ }
+
+ process_sp->GetThreadList().AddThread(thread_sp);
+
+ return thread_sp;
+}
+
+TEST_F(ProcessEventDataTest, DoOnRemoval) {
+ ArchSpec arch("x86_64-apple-macosx-");
+
+ Platform::SetHostPlatform(PlatformMacOSX::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ ASSERT_TRUE(target_sp);
+
+ ListenerSP listener_sp(Listener::MakeListener("dummy"));
+ ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp);
+ ASSERT_TRUE(process_sp);
+
+ /*
+ Should hit ShouldStop if state is eStateStopped
+ */
+ ProcessEventDataSP event_data_sp =
+ std::make_shared<DummyProcessEventData>(process_sp, eStateStopped);
+ EventSP event_sp = std::make_shared<Event>(0, event_data_sp);
+ event_data_sp->SetUpdateStateOnRemoval(event_sp.get());
+ event_data_sp->DoOnRemoval(event_sp.get());
+ bool result = static_cast<DummyProcessEventData *>(event_data_sp.get())
+ ->m_should_stop_hit_count == 1;
+ ASSERT_TRUE(result);
+
+ /*
+ Should not hit ShouldStop if state is not eStateStopped
+ */
+ event_data_sp =
+ std::make_shared<DummyProcessEventData>(process_sp, eStateStepping);
+ event_sp = std::make_shared<Event>(0, event_data_sp);
+ event_data_sp->SetUpdateStateOnRemoval(event_sp.get());
+ event_data_sp->DoOnRemoval(event_sp.get());
+ result = static_cast<DummyProcessEventData *>(event_data_sp.get())
+ ->m_should_stop_hit_count == 0;
+ ASSERT_TRUE(result);
+}
+
+TEST_F(ProcessEventDataTest, ShouldStop) {
+ ArchSpec arch("x86_64-apple-macosx-");
+
+ Platform::SetHostPlatform(PlatformMacOSX::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ ASSERT_TRUE(target_sp);
+
+ ListenerSP listener_sp(Listener::MakeListener("dummy"));
+ ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp);
+ ASSERT_TRUE(process_sp);
+
+ // wants to stop and has valid StopInfo
+ ThreadSP thread_sp = CreateThread(process_sp, true, true);
+
+ ProcessEventDataSP event_data_sp =
+ std::make_shared<Process::ProcessEventData>(process_sp, eStateStopped);
+ EventSP event_sp = std::make_shared<Event>(0, event_data_sp);
+ /*
+ Should stop if thread has valid StopInfo and not suspended
+ */
+ bool found_valid_stopinfo = false;
+ bool should_stop =
+ event_data_sp->ShouldStop(event_sp.get(), found_valid_stopinfo);
+ ASSERT_TRUE(should_stop == true && found_valid_stopinfo == true);
+
+ /*
+ Should not stop if thread has valid StopInfo but was suspended
+ */
+ found_valid_stopinfo = false;
+ thread_sp->SetResumeState(eStateSuspended);
+ should_stop = event_data_sp->ShouldStop(event_sp.get(), found_valid_stopinfo);
+ ASSERT_TRUE(should_stop == false && found_valid_stopinfo == false);
+
+ /*
+ Should not stop, thread-reason of stop does not want to stop in its
+ callback and suspended thread who wants to (from previous stop)
+ must be ignored.
+ */
+ event_data_sp =
+ std::make_shared<Process::ProcessEventData>(process_sp, eStateStopped);
+ event_sp = std::make_shared<Event>(0, event_data_sp);
+ process_sp->GetThreadList().Clear();
+
+ for (int i = 0; i < 6; i++) {
+ if (i == 2) {
+ // Does not want to stop but has valid StopInfo
+ thread_sp = CreateThread(process_sp, false, true);
+ } else if (i == 5) {
+ // Wants to stop and has valid StopInfo
+ thread_sp = CreateThread(process_sp, true, true);
+ thread_sp->SetResumeState(eStateSuspended);
+ } else {
+ // Thread has no StopInfo i.e is not the reason of stop
+ thread_sp = CreateThread(process_sp, false, false);
+ }
+ }
+ ASSERT_TRUE(process_sp->GetThreadList().GetSize() == 6);
+
+ found_valid_stopinfo = false;
+ should_stop = event_data_sp->ShouldStop(event_sp.get(), found_valid_stopinfo);
+ ASSERT_TRUE(should_stop == false && found_valid_stopinfo == true);
+}
--- /dev/null
+add_lldb_unittest(ThreadTest
+ ThreadTest.cpp
+
+ LINK_LIBS
+ lldbCore
+ lldbHost
+ lldbTarget
+ lldbSymbol
+ lldbUtility
+ lldbUtilityHelpers
+ lldbInterpreter
+ lldbBreakpoint
+ lldbPluginPlatformLinux
+ )
+
--- /dev/null
+//===-- ThreadTest.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/Thread.h"
+#include "Plugins/Platform/Linux/PlatformLinux.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StopInfo.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/Reproducer.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+using namespace lldb_private::repro;
+using namespace lldb;
+
+namespace {
+class ThreadTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None));
+ FileSystem::Initialize();
+ HostInfo::Initialize();
+ platform_linux::PlatformLinux::Initialize();
+ }
+ void TearDown() override {
+ platform_linux::PlatformLinux::Terminate();
+ HostInfo::Terminate();
+ FileSystem::Terminate();
+ Reproducer::Terminate();
+ }
+};
+
+class DummyProcess : public Process {
+public:
+ using Process::Process;
+
+ virtual bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) {
+ return true;
+ }
+ virtual Status DoDestroy() { return {}; }
+ virtual void RefreshStateAfterStop() {}
+ virtual size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ Status &error) {
+ return 0;
+ }
+ virtual bool UpdateThreadList(ThreadList &old_thread_list,
+ ThreadList &new_thread_list) {
+ return false;
+ }
+ virtual ConstString GetPluginName() { return ConstString("Dummy"); }
+ virtual uint32_t GetPluginVersion() { return 0; }
+
+ ProcessModID &GetModIDNonConstRef() { return m_mod_id; }
+};
+
+class DummyThread : public Thread {
+public:
+ using Thread::Thread;
+
+ ~DummyThread() override { DestroyThread(); }
+
+ void RefreshStateAfterStop() override {}
+
+ lldb::RegisterContextSP GetRegisterContext() override { return nullptr; }
+
+ lldb::RegisterContextSP
+ CreateRegisterContextForFrame(StackFrame *frame) override {
+ return nullptr;
+ }
+
+ bool CalculateStopInfo() override { return false; }
+
+ bool IsStillAtLastBreakpointHit() override { return true; }
+};
+} // namespace
+
+TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) {
+ Status error;
+ PlatformSP platform_sp;
+ TargetSP target_sp;
+ error = debugger_sp->GetTargetList().CreateTarget(
+ *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
+
+ if (target_sp) {
+ debugger_sp->GetTargetList().SetSelectedTarget(target_sp.get());
+ }
+
+ return target_sp;
+}
+
+TEST_F(ThreadTest, SetStopInfo) {
+ ArchSpec arch("powerpc64-pc-linux");
+
+ Platform::SetHostPlatform(
+ platform_linux::PlatformLinux::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ ASSERT_TRUE(target_sp);
+
+ ListenerSP listener_sp(Listener::MakeListener("dummy"));
+ ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp);
+ ASSERT_TRUE(process_sp);
+
+ DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+
+ ThreadSP thread_sp = std::make_shared<DummyThread>(*process_sp.get(), 0);
+ ASSERT_TRUE(thread_sp);
+
+ StopInfoSP stopinfo_sp =
+ StopInfo::CreateStopReasonWithBreakpointSiteID(*thread_sp.get(), 0);
+ ASSERT_TRUE(stopinfo_sp->IsValid() == true);
+
+ /*
+ Should make stopinfo valid.
+ */
+ process->GetModIDNonConstRef().BumpStopID();
+ ASSERT_TRUE(stopinfo_sp->IsValid() == false);
+
+ thread_sp->SetStopInfo(stopinfo_sp);
+ ASSERT_TRUE(stopinfo_sp->IsValid() == true);
+}
+
+TEST_F(ThreadTest, GetPrivateStopInfo) {
+ ArchSpec arch("powerpc64-pc-linux");
+
+ Platform::SetHostPlatform(
+ platform_linux::PlatformLinux::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ ASSERT_TRUE(target_sp);
+
+ ListenerSP listener_sp(Listener::MakeListener("dummy"));
+ ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp);
+ ASSERT_TRUE(process_sp);
+
+ DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+
+ ThreadSP thread_sp = std::make_shared<DummyThread>(*process_sp.get(), 0);
+ ASSERT_TRUE(thread_sp);
+
+ StopInfoSP stopinfo_sp =
+ StopInfo::CreateStopReasonWithBreakpointSiteID(*thread_sp.get(), 0);
+ ASSERT_TRUE(stopinfo_sp);
+
+ thread_sp->SetStopInfo(stopinfo_sp);
+
+ /*
+ Should make stopinfo valid if thread is at last breakpoint hit.
+ */
+ process->GetModIDNonConstRef().BumpStopID();
+ ASSERT_TRUE(stopinfo_sp->IsValid() == false);
+ StopInfoSP new_stopinfo_sp = thread_sp->GetPrivateStopInfo();
+ ASSERT_TRUE(new_stopinfo_sp && stopinfo_sp->IsValid() == true);
+}