Files
clang-p2996/lldb/include/lldb/Target/ThreadPlanStepRange.h
jeffreytan81 f838fa820f New ThreadPlanSingleThreadTimeout to resolve potential deadlock in single thread stepping (#90930)
This PR introduces a new `ThreadPlanSingleThreadTimeout` that will be
used to address potential deadlock during single-thread stepping.

While debugging a target with a non-trivial number of threads (around
5000 threads in one example target), we noticed that a simple step over
can take as long as 10 seconds. Enabling single-thread stepping mode
significantly reduces the stepping time to around 3 seconds. However,
this can introduce deadlock if we try to step over a method that depends
on other threads to release a lock.

To address this issue, we introduce a new
`ThreadPlanSingleThreadTimeout` that can be controlled by the
`target.process.thread.single-thread-plan-timeout` setting during
single-thread stepping mode. The concept involves counting the elapsed
time since the last internal stop to detect overall stepping progress.
Once a timeout occurs, we assume the target is not making progress due
to a potential deadlock, as mentioned above. We then send a new async
interrupt, resume all threads, and `ThreadPlanSingleThreadTimeout`
completes its task.

To support this design, the major changes made in this PR are:
1. `ThreadPlanSingleThreadTimeout` is popped during every internal stop
and reset (re-pushed) to the top of the stack (as a leaf node) during
resume. This is achieved by always returning `true` from
`ThreadPlanSingleThreadTimeout::DoPlanExplainsStop()` and
`ThreadPlanSingleThreadTimeout::MischiefManaged()`.
2. A new thread-specific async interrupt stop is introduced, which can
be detected/consumed by `ThreadPlanSingleThreadTimeout`.
3. The clearing of branch breakpoints in the range thread plan has been
moved from `DoPlanExplainsStop()` to `ShouldStop()`, as it is not
guaranteed that it will be called.

The detailed design is discussed in the RFC below:

[https://discourse.llvm.org/t/improve-single-thread-stepping/74599](https://discourse.llvm.org/t/improve-single-thread-stepping/74599)

---------

Co-authored-by: jeffreytan81 <jeffreytan@fb.com>
2024-08-05 17:26:39 -07:00

103 lines
4.0 KiB
C++

//===-- ThreadPlanStepRange.h -----------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_TARGET_THREADPLANSTEPRANGE_H
#define LLDB_TARGET_THREADPLANSTEPRANGE_H
#include "lldb/Core/AddressRange.h"
#include "lldb/Target/StackID.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadPlan.h"
#include "lldb/Target/ThreadPlanShouldStopHere.h"
namespace lldb_private {
class ThreadPlanStepRange : public ThreadPlan {
public:
ThreadPlanStepRange(ThreadPlanKind kind, const char *name, Thread &thread,
const AddressRange &range,
const SymbolContext &addr_context,
lldb::RunMode stop_others,
bool given_ranges_only = false);
~ThreadPlanStepRange() override;
void GetDescription(Stream *s, lldb::DescriptionLevel level) override = 0;
bool ValidatePlan(Stream *error) override;
bool ShouldStop(Event *event_ptr) override = 0;
Vote ShouldReportStop(Event *event_ptr) override;
bool StopOthers() override;
lldb::StateType GetPlanRunState() override;
bool WillStop() override;
bool MischiefManaged() override;
void DidPush() override;
bool IsPlanStale() override;
void AddRange(const AddressRange &new_range);
protected:
bool InRange();
lldb::FrameComparison CompareCurrentFrameToStartFrame();
bool InSymbol();
void DumpRanges(Stream *s);
Disassembler *GetDisassembler();
InstructionList *GetInstructionsForAddress(lldb::addr_t addr,
size_t &range_index,
size_t &insn_offset);
// Pushes a plan to proceed through the next section of instructions in the
// range - usually just a RunToAddress plan to run to the next branch.
// Returns true if it pushed such a plan. If there was no available 'quick
// run' plan, then just single step.
bool SetNextBranchBreakpoint();
// Whether the input stop info is caused by the next branch breakpoint.
// Note: this does not check if branch breakpoint site is shared by other
// breakpoints or not.
bool IsNextBranchBreakpointStop(lldb::StopInfoSP stop_info_sp);
void ClearNextBranchBreakpoint();
void ClearNextBranchBreakpointExplainedStop();
bool NextRangeBreakpointExplainsStop(lldb::StopInfoSP stop_info_sp);
SymbolContext m_addr_context;
std::vector<AddressRange> m_address_ranges;
lldb::RunMode m_stop_others;
StackID m_stack_id; // Use the stack ID so we can tell step out from step in.
StackID m_parent_stack_id; // Use the parent stack ID so we can identify tail
// calls and the like.
bool m_no_more_plans; // Need this one so we can tell if we stepped into a
// call,
// but can't continue, in which case we are done.
bool m_first_run_event; // We want to broadcast only one running event, our
// first.
lldb::BreakpointSP m_next_branch_bp_sp;
bool m_use_fast_step;
bool m_given_ranges_only;
bool m_found_calls = false; // When we set the next branch breakpoint for
// step over, we now extend them past call insns
// that directly return. But if we do that we
// need to run all threads, or we might cause
// deadlocks. This tells us whether we found
// any calls in setting the next branch breakpoint.
private:
std::vector<lldb::DisassemblerSP> m_instruction_ranges;
ThreadPlanStepRange(const ThreadPlanStepRange &) = delete;
const ThreadPlanStepRange &operator=(const ThreadPlanStepRange &) = delete;
};
} // namespace lldb_private
#endif // LLDB_TARGET_THREADPLANSTEPRANGE_H