[lldb] Support stepping through C++ thunks (#127419)
This PR fixes LLDB stepping out, rather than stepping through a C++ thunk. The implementation is based on, and upstreams, the support for runtime thunks in the Swift fork. Fixes #43413
This commit is contained in:
parent
0b8bd472b0
commit
a3dc77c00a
@ -201,6 +201,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool IsSymbolARuntimeThunk(const Symbol &symbol) { return false; }
|
||||
|
||||
// Given the name of a runtime symbol (e.g. in Objective-C, an ivar offset
|
||||
// symbol), try to determine from the runtime what the value of that symbol
|
||||
// would be. Useful when the underlying binary is stripped.
|
||||
|
||||
@ -476,3 +476,14 @@ CPPLanguageRuntime::GetStepThroughTrampolinePlan(Thread &thread,
|
||||
|
||||
return ret_plan_sp;
|
||||
}
|
||||
|
||||
bool CPPLanguageRuntime::IsSymbolARuntimeThunk(const Symbol &symbol) {
|
||||
llvm::StringRef mangled_name =
|
||||
symbol.GetMangled().GetMangledName().GetStringRef();
|
||||
// Virtual function overriding from a non-virtual base use a "Th" prefix.
|
||||
// Virtual function overriding from a virtual base must use a "Tv" prefix.
|
||||
// Virtual function overriding thunks with covariant returns use a "Tc"
|
||||
// prefix.
|
||||
return mangled_name.starts_with("_ZTh") || mangled_name.starts_with("_ZTv") ||
|
||||
mangled_name.starts_with("_ZTc");
|
||||
}
|
||||
|
||||
@ -78,6 +78,9 @@ public:
|
||||
bool stop_others) override;
|
||||
|
||||
bool IsAllowedRuntimeValue(ConstString name) override;
|
||||
|
||||
bool IsSymbolARuntimeThunk(const Symbol &symbol) override;
|
||||
|
||||
protected:
|
||||
// Classes that inherit from CPPLanguageRuntime can see and modify these
|
||||
CPPLanguageRuntime(Process *process);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include "lldb/Target/ThreadPlanShouldStopHere.h"
|
||||
#include "lldb/Symbol/Symbol.h"
|
||||
#include "lldb/Target/LanguageRuntime.h"
|
||||
#include "lldb/Target/RegisterContext.h"
|
||||
#include "lldb/Target/Thread.h"
|
||||
#include "lldb/Utility/LLDBLog.h"
|
||||
@ -76,6 +77,19 @@ bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback(
|
||||
}
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
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
|
||||
@ -109,18 +123,35 @@ ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback(
|
||||
|
||||
if (sc.line_entry.line == 0) {
|
||||
AddressRange range = sc.line_entry.range;
|
||||
|
||||
// If the whole function is marked line 0 just step out, that's easier &
|
||||
// faster than continuing to step through it.
|
||||
bool just_step_out = false;
|
||||
if (sc.symbol && 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 (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)) {
|
||||
LLDB_LOGF(log, "In runtime thunk %s - stepping out.",
|
||||
sc.symbol->GetName().GetCString());
|
||||
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) {
|
||||
|
||||
3
lldb/test/API/lang/cpp/thunk/Makefile
Normal file
3
lldb/test/API/lang/cpp/thunk/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
|
||||
include Makefile.rules
|
||||
46
lldb/test/API/lang/cpp/thunk/TestThunk.py
Normal file
46
lldb/test/API/lang/cpp/thunk/TestThunk.py
Normal file
@ -0,0 +1,46 @@
|
||||
import lldb
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
from lldbsuite.test import lldbutil
|
||||
|
||||
|
||||
class ThunkTest(TestBase):
|
||||
def test_step_through_thunk(self):
|
||||
self.build()
|
||||
lldbutil.run_to_name_breakpoint(self, "testit")
|
||||
|
||||
# Make sure we step through the thunk into Derived1::doit
|
||||
self.expect(
|
||||
"step",
|
||||
STEP_IN_SUCCEEDED,
|
||||
substrs=["stop reason = step in", "Derived1::doit"],
|
||||
)
|
||||
|
||||
self.runCmd("continue")
|
||||
|
||||
self.expect(
|
||||
"step",
|
||||
STEP_IN_SUCCEEDED,
|
||||
substrs=["stop reason = step in", "Derived2::doit"],
|
||||
)
|
||||
|
||||
def test_step_out_thunk(self):
|
||||
self.build()
|
||||
lldbutil.run_to_name_breakpoint(self, "testit_debug")
|
||||
|
||||
# Make sure we step out of the thunk and end up in testit_debug.
|
||||
source = "main.cpp"
|
||||
line = line_number(source, "// Step here")
|
||||
self.expect(
|
||||
"step",
|
||||
STEP_IN_SUCCEEDED,
|
||||
substrs=["stop reason = step in", "{}:{}".format(source, line)],
|
||||
)
|
||||
|
||||
self.runCmd("continue")
|
||||
|
||||
self.expect(
|
||||
"step",
|
||||
STEP_IN_SUCCEEDED,
|
||||
substrs=["stop reason = step in", "Derived2::doit_debug"],
|
||||
)
|
||||
48
lldb/test/API/lang/cpp/thunk/main.cpp
Normal file
48
lldb/test/API/lang/cpp/thunk/main.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include <stdio.h>
|
||||
|
||||
class Base1 {
|
||||
public:
|
||||
virtual ~Base1() {}
|
||||
};
|
||||
|
||||
class Base2 {
|
||||
public:
|
||||
virtual void doit() = 0;
|
||||
virtual void doit_debug() = 0;
|
||||
};
|
||||
|
||||
Base2 *b;
|
||||
|
||||
class Derived1 : public Base1, public Base2 {
|
||||
public:
|
||||
virtual void doit() { printf("Derived1\n"); }
|
||||
virtual void __attribute__((nodebug)) doit_debug() {
|
||||
printf("Derived1 (no debug)\n");
|
||||
}
|
||||
};
|
||||
|
||||
class Derived2 : public Base2 {
|
||||
public:
|
||||
virtual void doit() { printf("Derived2\n"); }
|
||||
virtual void doit_debug() { printf("Derived2 (debug)\n"); }
|
||||
};
|
||||
|
||||
void testit() { b->doit(); }
|
||||
|
||||
void testit_debug() {
|
||||
b->doit_debug();
|
||||
printf("This is where I should step out to with nodebug.\n"); // Step here
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
b = new Derived1();
|
||||
testit();
|
||||
testit_debug();
|
||||
|
||||
b = new Derived2();
|
||||
testit();
|
||||
testit_debug();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user