Skip to content

[lldb] Support stepping through C++ thunks #127419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/LanguageRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ class LanguageRuntime : public Runtime, public PluginInterface {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class CPPLanguageRuntime : public LanguageRuntime {
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);
Expand Down
53 changes: 42 additions & 11 deletions lldb/source/Target/ThreadPlanShouldStopHere.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/lang/cpp/thunk/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
46 changes: 46 additions & 0 deletions lldb/test/API/lang/cpp/thunk/TestThunk.py
Original file line number Diff line number Diff line change
@@ -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 changes: 48 additions & 0 deletions lldb/test/API/lang/cpp/thunk/main.cpp
Original file line number Diff line number Diff line change
@@ -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;
}