Skip to content

Commit a3dc77c

Browse files
authored
[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
1 parent 0b8bd47 commit a3dc77c

File tree

7 files changed

+155
-11
lines changed

7 files changed

+155
-11
lines changed

lldb/include/lldb/Target/LanguageRuntime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ class LanguageRuntime : public Runtime, public PluginInterface {
201201
return false;
202202
}
203203

204+
virtual bool IsSymbolARuntimeThunk(const Symbol &symbol) { return false; }
205+
204206
// Given the name of a runtime symbol (e.g. in Objective-C, an ivar offset
205207
// symbol), try to determine from the runtime what the value of that symbol
206208
// would be. Useful when the underlying binary is stripped.

lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,14 @@ CPPLanguageRuntime::GetStepThroughTrampolinePlan(Thread &thread,
476476

477477
return ret_plan_sp;
478478
}
479+
480+
bool CPPLanguageRuntime::IsSymbolARuntimeThunk(const Symbol &symbol) {
481+
llvm::StringRef mangled_name =
482+
symbol.GetMangled().GetMangledName().GetStringRef();
483+
// Virtual function overriding from a non-virtual base use a "Th" prefix.
484+
// Virtual function overriding from a virtual base must use a "Tv" prefix.
485+
// Virtual function overriding thunks with covariant returns use a "Tc"
486+
// prefix.
487+
return mangled_name.starts_with("_ZTh") || mangled_name.starts_with("_ZTv") ||
488+
mangled_name.starts_with("_ZTc");
489+
}

lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ class CPPLanguageRuntime : public LanguageRuntime {
7878
bool stop_others) override;
7979

8080
bool IsAllowedRuntimeValue(ConstString name) override;
81+
82+
bool IsSymbolARuntimeThunk(const Symbol &symbol) override;
83+
8184
protected:
8285
// Classes that inherit from CPPLanguageRuntime can see and modify these
8386
CPPLanguageRuntime(Process *process);

lldb/source/Target/ThreadPlanShouldStopHere.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "lldb/Target/ThreadPlanShouldStopHere.h"
1010
#include "lldb/Symbol/Symbol.h"
11+
#include "lldb/Target/LanguageRuntime.h"
1112
#include "lldb/Target/RegisterContext.h"
1213
#include "lldb/Target/Thread.h"
1314
#include "lldb/Utility/LLDBLog.h"
@@ -76,6 +77,19 @@ bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback(
7677
}
7778
}
7879

80+
// Check whether the frame we are in is a language runtime thunk, only for
81+
// step out:
82+
if (operation == eFrameCompareOlder) {
83+
if (Symbol *symbol = frame->GetSymbolContext(eSymbolContextSymbol).symbol) {
84+
ProcessSP process_sp(current_plan->GetThread().GetProcess());
85+
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
86+
if (runtime->IsSymbolARuntimeThunk(*symbol)) {
87+
should_stop_here = false;
88+
break;
89+
}
90+
}
91+
}
92+
}
7993
// Always avoid code with line number 0.
8094
// FIXME: At present the ShouldStop and the StepFromHere calculate this
8195
// independently. If this ever
@@ -109,18 +123,35 @@ ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback(
109123

110124
if (sc.line_entry.line == 0) {
111125
AddressRange range = sc.line_entry.range;
112-
113-
// If the whole function is marked line 0 just step out, that's easier &
114-
// faster than continuing to step through it.
115126
bool just_step_out = false;
116-
if (sc.symbol && sc.symbol->ValueIsAddress()) {
117-
Address symbol_end = sc.symbol->GetAddress();
118-
symbol_end.Slide(sc.symbol->GetByteSize() - 1);
119-
if (range.ContainsFileAddress(sc.symbol->GetAddress()) &&
120-
range.ContainsFileAddress(symbol_end)) {
121-
LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just "
122-
"stepping out.");
123-
just_step_out = true;
127+
if (sc.symbol) {
128+
ProcessSP process_sp(current_plan->GetThread().GetProcess());
129+
130+
// If this is a runtime thunk, step through it, rather than stepping out
131+
// because it's marked line 0.
132+
bool is_thunk = false;
133+
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
134+
if (runtime->IsSymbolARuntimeThunk(*sc.symbol)) {
135+
LLDB_LOGF(log, "In runtime thunk %s - stepping out.",
136+
sc.symbol->GetName().GetCString());
137+
is_thunk = true;
138+
break;
139+
}
140+
}
141+
142+
// If the whole function is marked line 0 just step out, that's easier &
143+
// faster than continuing to step through it.
144+
// FIXME: This assumes that the function is a single line range. It could
145+
// be a series of contiguous line 0 ranges. Check for that too.
146+
if (!is_thunk && sc.symbol->ValueIsAddress()) {
147+
Address symbol_end = sc.symbol->GetAddress();
148+
symbol_end.Slide(sc.symbol->GetByteSize() - 1);
149+
if (range.ContainsFileAddress(sc.symbol->GetAddress()) &&
150+
range.ContainsFileAddress(symbol_end)) {
151+
LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just "
152+
"stepping out.");
153+
just_step_out = true;
154+
}
124155
}
125156
}
126157
if (!just_step_out) {

lldb/test/API/lang/cpp/thunk/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
6+
7+
class ThunkTest(TestBase):
8+
def test_step_through_thunk(self):
9+
self.build()
10+
lldbutil.run_to_name_breakpoint(self, "testit")
11+
12+
# Make sure we step through the thunk into Derived1::doit
13+
self.expect(
14+
"step",
15+
STEP_IN_SUCCEEDED,
16+
substrs=["stop reason = step in", "Derived1::doit"],
17+
)
18+
19+
self.runCmd("continue")
20+
21+
self.expect(
22+
"step",
23+
STEP_IN_SUCCEEDED,
24+
substrs=["stop reason = step in", "Derived2::doit"],
25+
)
26+
27+
def test_step_out_thunk(self):
28+
self.build()
29+
lldbutil.run_to_name_breakpoint(self, "testit_debug")
30+
31+
# Make sure we step out of the thunk and end up in testit_debug.
32+
source = "main.cpp"
33+
line = line_number(source, "// Step here")
34+
self.expect(
35+
"step",
36+
STEP_IN_SUCCEEDED,
37+
substrs=["stop reason = step in", "{}:{}".format(source, line)],
38+
)
39+
40+
self.runCmd("continue")
41+
42+
self.expect(
43+
"step",
44+
STEP_IN_SUCCEEDED,
45+
substrs=["stop reason = step in", "Derived2::doit_debug"],
46+
)

lldb/test/API/lang/cpp/thunk/main.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <stdio.h>
2+
3+
class Base1 {
4+
public:
5+
virtual ~Base1() {}
6+
};
7+
8+
class Base2 {
9+
public:
10+
virtual void doit() = 0;
11+
virtual void doit_debug() = 0;
12+
};
13+
14+
Base2 *b;
15+
16+
class Derived1 : public Base1, public Base2 {
17+
public:
18+
virtual void doit() { printf("Derived1\n"); }
19+
virtual void __attribute__((nodebug)) doit_debug() {
20+
printf("Derived1 (no debug)\n");
21+
}
22+
};
23+
24+
class Derived2 : public Base2 {
25+
public:
26+
virtual void doit() { printf("Derived2\n"); }
27+
virtual void doit_debug() { printf("Derived2 (debug)\n"); }
28+
};
29+
30+
void testit() { b->doit(); }
31+
32+
void testit_debug() {
33+
b->doit_debug();
34+
printf("This is where I should step out to with nodebug.\n"); // Step here
35+
}
36+
37+
int main() {
38+
39+
b = new Derived1();
40+
testit();
41+
testit_debug();
42+
43+
b = new Derived2();
44+
testit();
45+
testit_debug();
46+
47+
return 0;
48+
}

0 commit comments

Comments
 (0)