
This PR fixes another race condition in https://github.com/llvm/llvm-project/pull/90930. The failure was found by @labath with this log: https://paste.debian.net/hidden/30235a5c/: ``` dotest_wrapper. < 15> send packet: $z0,224505,1#65 ... b-remote.async> < 22> send packet: $vCont;s:p1dcf.1dcf#4c intern-state GDBRemoteClientBase::Lock::Lock sent packet: \x03 b-remote.async> < 818> read packet: $T13thread:p1dcf.1dcf;name:a.out;threads:1dcf,1dd2;jstopinfo:5b7b226e616d65223a22612e6f7574222c22726561736f6e223a227369676e616c222c227369676e616c223a31392c22746964223a373633317d2c7b226e616d65223a22612e6f7574222c22746964223a373633347d5d;thread-pcs:0000000000224505,00007f4e4302119a;00:0000000000000000;01:0000000000000000;02:0100000000000000;03:0000000000000000;04:9084997dfc7f0000;05:a8742a0000000000;06:b084997dfc7f0000;07:6084997dfc7f0000;08:0000000000000000;09:00d7e5424e7f0000;0a:d0d9e5424e7f0000;0b:0202000000000000;0c:80cc290000000000;0d:d8cc1c434e7f0000;0e:2886997dfc7f0000;0f:0100000000000000;10:0545220000000000;11:0602000000000000;12:3300000000000000;13:0000000000000000;14:0000000000000000;15:2b00000000000000;16:80fbe5424e7f0000;17:0000000000000000;18:0000000000000000;19:0000000000000000;reason:signal;#b9 ``` It shows an async interrupt "\x03" was sent immediately after `vCont;s` single step over breakpoint at address `0x224505` (which was disabled before vCont). And the later stop was still at the original PC (0x224505) not moving forward. The investigation shows the failure happens when timeout is short and async interrupt is sent to lldb-server immediately after vCont so ptrace() resumes and then async interrupts debuggee immediately so debuggee does not get a chance to execute and move PC. So it enters stop mode immediately at original PC. `ThreadPlanStepOverBreakpoint` does not expect PC not moving and reports stop at the original place. To fix this, the PR prevents `ThreadPlanSingleThreadTimeout` from being created during `ThreadPlanStepOverBreakpoint` by introduces a new `SupportsResumeOthers()` method and `ThreadPlanStepOverBreakpoint` returns false for it. This makes sense because we should never resume threads during step over breakpoint anyway otherwise it might cause other threads to miss breakpoint. --------- Co-authored-by: jeffreytan81 <jeffreytan@fb.com>
258 lines
8.8 KiB
C++
258 lines
8.8 KiB
C++
//===-- ThreadPlanStepOverRange.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/ThreadPlanSingleThreadTimeout.h"
|
|
#include "lldb/Symbol/Block.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/Function.h"
|
|
#include "lldb/Symbol/LineTable.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "lldb/Target/ThreadPlanStepOut.h"
|
|
#include "lldb/Target/ThreadPlanStepThrough.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/Log.h"
|
|
#include "lldb/Utility/Stream.h"
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb;
|
|
|
|
ThreadPlanSingleThreadTimeout::ThreadPlanSingleThreadTimeout(
|
|
Thread &thread, TimeoutInfoSP &info)
|
|
: ThreadPlan(ThreadPlan::eKindSingleThreadTimeout, "Single thread timeout",
|
|
thread, eVoteNo, eVoteNoOpinion),
|
|
m_info(info), m_state(State::WaitTimeout) {
|
|
m_info->m_isAlive = true;
|
|
m_state = m_info->m_last_state;
|
|
// TODO: reuse m_timer_thread without recreation.
|
|
m_timer_thread = std::thread(TimeoutThreadFunc, this);
|
|
}
|
|
|
|
ThreadPlanSingleThreadTimeout::~ThreadPlanSingleThreadTimeout() {
|
|
m_info->m_isAlive = false;
|
|
}
|
|
|
|
uint64_t ThreadPlanSingleThreadTimeout::GetRemainingTimeoutMilliSeconds() {
|
|
uint64_t timeout_in_ms = GetThread().GetSingleThreadPlanTimeout();
|
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
|
std::chrono::milliseconds duration_ms =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(now -
|
|
m_timeout_start);
|
|
return timeout_in_ms - duration_ms.count();
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::GetDescription(
|
|
Stream *s, lldb::DescriptionLevel level) {
|
|
s->Printf("Single thread timeout, state(%s), remaining %" PRIu64 " ms",
|
|
StateToString(m_state).c_str(), GetRemainingTimeoutMilliSeconds());
|
|
}
|
|
|
|
std::string ThreadPlanSingleThreadTimeout::StateToString(State state) {
|
|
switch (state) {
|
|
case State::WaitTimeout:
|
|
return "WaitTimeout";
|
|
case State::AsyncInterrupt:
|
|
return "AsyncInterrupt";
|
|
case State::Done:
|
|
return "Done";
|
|
}
|
|
llvm_unreachable("Uncovered state value!");
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::PushNewWithTimeout(Thread &thread,
|
|
TimeoutInfoSP &info) {
|
|
uint64_t timeout_in_ms = thread.GetSingleThreadPlanTimeout();
|
|
if (timeout_in_ms == 0)
|
|
return;
|
|
|
|
// Do not create timeout if we are not stopping other threads.
|
|
if (!thread.GetCurrentPlan()->StopOthers())
|
|
return;
|
|
|
|
if (!thread.GetCurrentPlan()->SupportsResumeOthers())
|
|
return;
|
|
|
|
auto timeout_plan = new ThreadPlanSingleThreadTimeout(thread, info);
|
|
ThreadPlanSP thread_plan_sp(timeout_plan);
|
|
auto status = thread.QueueThreadPlan(thread_plan_sp,
|
|
/*abort_other_plans*/ false);
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(
|
|
log,
|
|
"ThreadPlanSingleThreadTimeout pushing a brand new one with %" PRIu64
|
|
" ms",
|
|
timeout_in_ms);
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::ResumeFromPrevState(Thread &thread,
|
|
TimeoutInfoSP &info) {
|
|
uint64_t timeout_in_ms = thread.GetSingleThreadPlanTimeout();
|
|
if (timeout_in_ms == 0)
|
|
return;
|
|
|
|
// There is already an instance alive.
|
|
if (info->m_isAlive)
|
|
return;
|
|
|
|
// Do not create timeout if we are not stopping other threads.
|
|
if (!thread.GetCurrentPlan()->StopOthers())
|
|
return;
|
|
|
|
if (!thread.GetCurrentPlan()->SupportsResumeOthers())
|
|
return;
|
|
|
|
auto timeout_plan = new ThreadPlanSingleThreadTimeout(thread, info);
|
|
ThreadPlanSP thread_plan_sp(timeout_plan);
|
|
auto status = thread.QueueThreadPlan(thread_plan_sp,
|
|
/*abort_other_plans*/ false);
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(
|
|
log,
|
|
"ThreadPlanSingleThreadTimeout reset from previous state with %" PRIu64
|
|
" ms",
|
|
timeout_in_ms);
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::WillStop() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout::WillStop().");
|
|
|
|
// Reset the state during stop.
|
|
m_info->m_last_state = State::WaitTimeout;
|
|
return true;
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::DidPop() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout::DidPop().");
|
|
// Tell timer thread to exit.
|
|
m_info->m_isAlive = false;
|
|
}
|
|
m_wakeup_cv.notify_one();
|
|
// Wait for timer thread to exit.
|
|
m_timer_thread.join();
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::DoPlanExplainsStop(Event *event_ptr) {
|
|
bool is_timeout_interrupt = IsTimeoutAsyncInterrupt(event_ptr);
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(log,
|
|
"ThreadPlanSingleThreadTimeout::DoPlanExplainsStop() returns %d. "
|
|
"%" PRIu64 " ms remaining.",
|
|
is_timeout_interrupt, GetRemainingTimeoutMilliSeconds());
|
|
return is_timeout_interrupt;
|
|
}
|
|
|
|
lldb::StateType ThreadPlanSingleThreadTimeout::GetPlanRunState() {
|
|
return GetPreviousPlan()->GetPlanRunState();
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::TimeoutThreadFunc(
|
|
ThreadPlanSingleThreadTimeout *self) {
|
|
std::unique_lock<std::mutex> lock(self->m_mutex);
|
|
uint64_t timeout_in_ms = self->GetThread().GetSingleThreadPlanTimeout();
|
|
// The thread should wakeup either when timeout or
|
|
// ThreadPlanSingleThreadTimeout has been popped (not alive).
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
self->m_timeout_start = std::chrono::steady_clock::now();
|
|
LLDB_LOGF(
|
|
log,
|
|
"ThreadPlanSingleThreadTimeout::TimeoutThreadFunc(), wait for %" PRIu64
|
|
" ms",
|
|
timeout_in_ms);
|
|
self->m_wakeup_cv.wait_for(lock, std::chrono::milliseconds(timeout_in_ms),
|
|
[self] { return !self->m_info->m_isAlive; });
|
|
LLDB_LOGF(log,
|
|
"ThreadPlanSingleThreadTimeout::TimeoutThreadFunc() wake up with "
|
|
"m_isAlive(%d).",
|
|
self->m_info->m_isAlive);
|
|
if (!self->m_info->m_isAlive)
|
|
return;
|
|
|
|
self->HandleTimeout();
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::MischiefManaged() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout::MischiefManaged() called.");
|
|
// Need to reset timer on each internal stop/execution progress.
|
|
return true;
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::ShouldStop(Event *event_ptr) {
|
|
return HandleEvent(event_ptr);
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::SetStopOthers(bool new_value) {
|
|
// Note: this assumes that the SingleThreadTimeout plan is always going to be
|
|
// pushed on behalf of the plan directly above it.
|
|
GetPreviousPlan()->SetStopOthers(new_value);
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::StopOthers() {
|
|
if (m_state == State::Done)
|
|
return false;
|
|
else
|
|
return GetPreviousPlan()->StopOthers();
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::IsTimeoutAsyncInterrupt(Event *event_ptr) {
|
|
lldb::StateType stop_state =
|
|
Process::ProcessEventData::GetStateFromEvent(event_ptr);
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(log,
|
|
"ThreadPlanSingleThreadTimeout::IsTimeoutAsyncInterrupt(): got "
|
|
"event: %s.",
|
|
StateAsCString(stop_state));
|
|
|
|
lldb::StopInfoSP stop_info = GetThread().GetStopInfo();
|
|
return (m_state == State::AsyncInterrupt &&
|
|
stop_state == lldb::eStateStopped && stop_info &&
|
|
stop_info->GetStopReason() == lldb::eStopReasonInterrupt);
|
|
}
|
|
|
|
bool ThreadPlanSingleThreadTimeout::HandleEvent(Event *event_ptr) {
|
|
if (IsTimeoutAsyncInterrupt(event_ptr)) {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
if (Process::ProcessEventData::GetRestartedFromEvent(event_ptr)) {
|
|
// If we were restarted, we just need to go back up to fetch
|
|
// another event.
|
|
LLDB_LOGF(log,
|
|
"ThreadPlanSingleThreadTimeout::HandleEvent(): Got a stop and "
|
|
"restart, so we'll continue waiting.");
|
|
|
|
} else {
|
|
LLDB_LOGF(
|
|
log,
|
|
"ThreadPlanSingleThreadTimeout::HandleEvent(): Got async interrupt "
|
|
", so we will resume all threads.");
|
|
GetThread().GetCurrentPlan()->SetStopOthers(false);
|
|
GetPreviousPlan()->SetStopOthers(false);
|
|
m_state = State::Done;
|
|
}
|
|
}
|
|
// Should not report stop.
|
|
return false;
|
|
}
|
|
|
|
void ThreadPlanSingleThreadTimeout::HandleTimeout() {
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
LLDB_LOGF(
|
|
log,
|
|
"ThreadPlanSingleThreadTimeout::HandleTimeout() send async interrupt.");
|
|
m_state = State::AsyncInterrupt;
|
|
|
|
// Private state thread will only send async interrupt
|
|
// in running state so no need to check state here.
|
|
m_process.SendAsyncInterrupt(&GetThread());
|
|
}
|