
Jonas recently added a trampoline handling strategy for simple language thunks that does: "step through language thunks stepping in one level deep and stopping if you hit user code". That was actually pulled over from the swift implementation. However, this strategy and the strategy we have to "step out past language thunks" when stepping out come into conflict if the thunk you are stepping through calls some other function before dispatching to the intended method. When you step out of the called function back into the thunk, should you keep stepping out past the thunk or not? In most cases, you want to step out past the thunk, but in this particular case you don't. This patch adds a way to inform the thread plan (or really it's ShouldStopHere behavior) of which behavior it should have, and passes the don't step through thunks to the step through plan it uses to step through thunks. I didn't add a test for this because I couldn't find a C++ thunk that calls another function before getting to the target function. I asked the clang folks here if they could think of a case where clang would do this, and they couldn't. If anyone can think of such a construct, it will be easy to write the step through test for it... This does happen in swift, however, so when I cherry-pick this to the swift fork I'll test it there.
202 lines
7.4 KiB
C++
202 lines
7.4 KiB
C++
//===-- ThreadPlanShouldStopHere.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/ThreadPlanShouldStopHere.h"
|
|
#include "lldb/Symbol/Symbol.h"
|
|
#include "lldb/Target/Language.h"
|
|
#include "lldb/Target/LanguageRuntime.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/Log.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
// ThreadPlanShouldStopHere constructor
|
|
ThreadPlanShouldStopHere::ThreadPlanShouldStopHere(ThreadPlan *owner)
|
|
: m_callbacks(), m_baton(nullptr), m_owner(owner),
|
|
m_flags(ThreadPlanShouldStopHere::eNone) {
|
|
m_callbacks.should_stop_here_callback =
|
|
ThreadPlanShouldStopHere::DefaultShouldStopHereCallback;
|
|
m_callbacks.step_from_here_callback =
|
|
ThreadPlanShouldStopHere::DefaultStepFromHereCallback;
|
|
}
|
|
|
|
ThreadPlanShouldStopHere::ThreadPlanShouldStopHere(
|
|
ThreadPlan *owner, const ThreadPlanShouldStopHereCallbacks *callbacks,
|
|
void *baton)
|
|
: m_callbacks(), m_baton(), m_owner(owner),
|
|
m_flags(ThreadPlanShouldStopHere::eNone) {
|
|
SetShouldStopHereCallbacks(callbacks, baton);
|
|
}
|
|
|
|
ThreadPlanShouldStopHere::~ThreadPlanShouldStopHere() = default;
|
|
|
|
bool ThreadPlanShouldStopHere::InvokeShouldStopHereCallback(
|
|
FrameComparison operation, Status &status) {
|
|
bool should_stop_here = true;
|
|
if (m_callbacks.should_stop_here_callback) {
|
|
should_stop_here = m_callbacks.should_stop_here_callback(
|
|
m_owner, m_flags, operation, status, m_baton);
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
if (log) {
|
|
lldb::addr_t current_addr =
|
|
m_owner->GetThread().GetRegisterContext()->GetPC(0);
|
|
|
|
LLDB_LOGF(log, "ShouldStopHere callback returned %u from 0x%" PRIx64 ".",
|
|
should_stop_here, current_addr);
|
|
}
|
|
}
|
|
|
|
return should_stop_here;
|
|
}
|
|
|
|
bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback(
|
|
ThreadPlan *current_plan, Flags &flags, FrameComparison operation,
|
|
Status &status, void *baton) {
|
|
bool should_stop_here = true;
|
|
StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get();
|
|
if (!frame)
|
|
return true;
|
|
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
|
|
if ((operation == eFrameCompareOlder && flags.Test(eStepOutAvoidNoDebug)) ||
|
|
(operation == eFrameCompareYounger && flags.Test(eStepInAvoidNoDebug)) ||
|
|
(operation == eFrameCompareSameParent &&
|
|
flags.Test(eStepInAvoidNoDebug))) {
|
|
if (!frame->HasDebugInformation()) {
|
|
LLDB_LOGF(log, "Stepping out of frame with no debug info");
|
|
|
|
should_stop_here = false;
|
|
}
|
|
}
|
|
|
|
// Check whether the frame we are in is a language runtime thunk, only for
|
|
// step out:
|
|
if (operation == eFrameCompareOlder) {
|
|
if (Symbol *symbol = frame->GetSymbolContext(eSymbolContextSymbol).symbol) {
|
|
ProcessSP process_sp(current_plan->GetThread().GetProcess());
|
|
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
|
|
if (runtime->IsSymbolARuntimeThunk(*symbol) &&
|
|
flags.Test(ThreadPlanShouldStopHere::eStepOutPastThunks)) {
|
|
LLDB_LOGF(
|
|
log, "Stepping out past a language thunk %s for: %s",
|
|
frame->GetFunctionName(),
|
|
Language::GetNameForLanguageType(runtime->GetLanguageType()));
|
|
should_stop_here = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Always avoid code with line number 0.
|
|
// FIXME: At present the ShouldStop and the StepFromHere calculate this
|
|
// independently. If this ever
|
|
// becomes expensive (this one isn't) we can try to have this set a state
|
|
// that the StepFromHere can use.
|
|
if (frame) {
|
|
SymbolContext sc;
|
|
sc = frame->GetSymbolContext(eSymbolContextLineEntry);
|
|
if (sc.line_entry.line == 0)
|
|
should_stop_here = false;
|
|
}
|
|
|
|
return should_stop_here;
|
|
}
|
|
|
|
ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback(
|
|
ThreadPlan *current_plan, Flags &flags, FrameComparison operation,
|
|
Status &status, void *baton) {
|
|
const bool stop_others = false;
|
|
const size_t frame_index = 0;
|
|
ThreadPlanSP return_plan_sp;
|
|
// If we are stepping through code at line number 0, then we need to step
|
|
// over this range. Otherwise we will step out.
|
|
Log *log = GetLog(LLDBLog::Step);
|
|
|
|
StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get();
|
|
if (!frame)
|
|
return return_plan_sp;
|
|
SymbolContext sc;
|
|
sc = frame->GetSymbolContext(eSymbolContextLineEntry | eSymbolContextSymbol);
|
|
|
|
if (sc.line_entry.line == 0) {
|
|
AddressRange range = sc.line_entry.range;
|
|
bool just_step_out = false;
|
|
if (sc.symbol) {
|
|
ProcessSP process_sp(current_plan->GetThread().GetProcess());
|
|
|
|
// If this is a runtime thunk, step through it, rather than stepping out
|
|
// because it's marked line 0.
|
|
bool is_thunk = false;
|
|
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
|
|
if (runtime->IsSymbolARuntimeThunk(*sc.symbol) &&
|
|
flags.Test(ThreadPlanShouldStopHere::eStepOutPastThunks)) {
|
|
LLDB_LOGF(
|
|
log, "Stepping out past a language thunk %s for: %s",
|
|
frame->GetFunctionName(),
|
|
Language::GetNameForLanguageType(runtime->GetLanguageType()));
|
|
is_thunk = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the whole function is marked line 0 just step out, that's easier &
|
|
// faster than continuing to step through it.
|
|
// FIXME: This assumes that the function is a single line range. It could
|
|
// be a series of contiguous line 0 ranges. Check for that too.
|
|
if (!is_thunk && sc.symbol->ValueIsAddress()) {
|
|
Address symbol_end = sc.symbol->GetAddress();
|
|
symbol_end.Slide(sc.symbol->GetByteSize() - 1);
|
|
if (range.ContainsFileAddress(sc.symbol->GetAddress()) &&
|
|
range.ContainsFileAddress(symbol_end)) {
|
|
LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just "
|
|
"stepping out.");
|
|
just_step_out = true;
|
|
}
|
|
}
|
|
}
|
|
if (!just_step_out) {
|
|
LLDB_LOGF(log, "ThreadPlanShouldStopHere::DefaultStepFromHereCallback "
|
|
"Queueing StepInRange plan to step through line 0 code.");
|
|
|
|
return_plan_sp = current_plan->GetThread().QueueThreadPlanForStepInRange(
|
|
false, range, sc, nullptr, eOnlyDuringStepping, status,
|
|
eLazyBoolCalculate, eLazyBoolNo);
|
|
}
|
|
}
|
|
|
|
if (!return_plan_sp)
|
|
return_plan_sp =
|
|
current_plan->GetThread().QueueThreadPlanForStepOutNoShouldStop(
|
|
false, nullptr, true, stop_others, eVoteNo, eVoteNoOpinion,
|
|
frame_index, status, true);
|
|
return return_plan_sp;
|
|
}
|
|
|
|
ThreadPlanSP ThreadPlanShouldStopHere::QueueStepOutFromHerePlan(
|
|
lldb_private::Flags &flags, lldb::FrameComparison operation,
|
|
Status &status) {
|
|
ThreadPlanSP return_plan_sp;
|
|
if (m_callbacks.step_from_here_callback) {
|
|
return_plan_sp = m_callbacks.step_from_here_callback(
|
|
m_owner, flags, operation, status, m_baton);
|
|
}
|
|
return return_plan_sp;
|
|
}
|
|
|
|
lldb::ThreadPlanSP ThreadPlanShouldStopHere::CheckShouldStopHereAndQueueStepOut(
|
|
lldb::FrameComparison operation, Status &status) {
|
|
if (!InvokeShouldStopHereCallback(operation, status))
|
|
return QueueStepOutFromHerePlan(m_flags, operation, status);
|
|
else
|
|
return ThreadPlanSP();
|
|
}
|