Skip to content

Handle stepping through language thunks that call other functions than the target #10145

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 3 commits into from
Mar 4, 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
3 changes: 2 additions & 1 deletion lldb/include/lldb/Target/ThreadPlanShouldStopHere.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ class ThreadPlanShouldStopHere {
eNone = 0,
eAvoidInlines = (1 << 0),
eStepInAvoidNoDebug = (1 << 1),
eStepOutAvoidNoDebug = (1 << 2)
eStepOutAvoidNoDebug = (1 << 2),
eStepOutPastThunks = (1 << 3)
};

// Constructors and Destructors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,12 @@ static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
GetThunkKindName(thunk_kind));
AddressRange sym_addr_range(sc.symbol->GetAddress(),
sc.symbol->GetByteSize());
return std::make_shared<ThreadPlanStepInRange>(thread, sym_addr_range, sc,
ThreadPlanSP new_plan_sp = std::make_shared<ThreadPlanStepInRange>(thread, sym_addr_range, sc,
nullptr, eOnlyDuringStepping,
eLazyBoolNo, eLazyBoolNo);
static_cast<ThreadPlanStepInRange *>(new_plan_sp.get())
->GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutPastThunks);
return new_plan_sp;
}
}

Expand Down
20 changes: 15 additions & 5 deletions lldb/source/Target/ThreadPlanShouldStopHere.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "lldb/Target/ThreadPlanShouldStopHere.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/Language.h"
#include "lldb/Target/LanguageRuntime.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
Expand Down Expand Up @@ -86,8 +87,14 @@ bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback(
if (symbol) {
ProcessSP process_sp(current_plan->GetThread().GetProcess());
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
if (runtime->IsSymbolARuntimeThunk(*symbol))
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;
}
}
}
}
Expand Down Expand Up @@ -139,11 +146,14 @@ ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback(
if (sc.symbol) {
ProcessSP process_sp(current_plan->GetThread().GetProcess());
for (auto *runtime : process_sp->GetLanguageRuntimes()) {
if (runtime->IsSymbolARuntimeThunk(*sc.symbol)) {
if (log)
log->Printf("In runtime thunk %s - stepping out.",
sc.symbol->GetName().GetCString());
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()));
just_step_out = true;
break;
}
}
// If the whole function is marked line 0 just step out, that's easier &
Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Target/ThreadPlanStepInRange.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ using namespace lldb;
using namespace lldb_private;

uint32_t ThreadPlanStepInRange::s_default_flag_values =
ThreadPlanShouldStopHere::eStepInAvoidNoDebug;
ThreadPlanShouldStopHere::eStepInAvoidNoDebug |
ThreadPlanShouldStopHere::eStepOutPastThunks;

// ThreadPlanStepInRange: Step through a stack range, either stepping over or
// into based on the value of \a type.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SWIFT_SOURCES := main.swift
include Makefile.rules

cleanup:
rm -f Makefile *.d

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import lldb
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbtest as lldbtest
import lldbsuite.test.lldbutil as lldbutil
import os
import platform

class TestStepThroughAllocatingInit(lldbtest.TestBase):
mydir = lldbtest.TestBase.compute_mydir(__file__)

@swiftTest
def test_swift_stepping_api(self):
"""Test that step in using the Python API steps through thunk."""
self.build()
self.do_test(True)

@swiftTest
def test_swift_stepping_cli(self):
"""Same test with the cli - it goes a slightly different path than
the API."""
self.build()
self.do_test(False)

def setUp(self):
lldbtest.TestBase.setUp(self)
self.main_source = "main.swift"
self.main_source_spec = lldb.SBFileSpec(self.main_source)
# If you are running against a debug swift you are going to
# end up stepping into the stdlib and that will make stepping
# tests impossible to write. So avoid that.

if platform.system() == 'Darwin':
lib_name = "libswiftCore.dylib"
else:
lib_name = "libswiftCore.so"

self.dbg.HandleCommand(
"settings set "
"target.process.thread.step-avoid-libraries {}".format(lib_name))

def do_test(self, use_api):
"""Tests that we can step reliably in swift code."""
exe_name = "a.out"
exe = self.getBuildArtifact(exe_name)

target, process, thread, breakpoint = lldbutil.run_to_source_breakpoint(self,
'Break here to step into init', self.main_source_spec)

# Step into the function.
if use_api:
thread.StepInto()
else:
self.runCmd("thread step-in")
frame_0 = thread.frames[0]
self.assertIn('Foo.init()', frame_0.GetFunctionName())

# Check that our parent frame is indeed allocating_init (otherwise we aren't
# testing what we think we're testing...
frame_1 = thread.frames[1]
self.assertIn("allocating_init", frame_1.GetFunctionName())

# Step one line so some_string is initialized, make sure we can
# get its value:
if use_api:
thread.StepOver()
else:
self.runCmd("thread step-over")

frame_0 = thread.frames[0]
self.assertIn('Foo.init()', frame_0.GetFunctionName())
var = frame_0.FindVariable("some_string")
self.assertTrue(var.GetError().Success())
self.assertEqual(var.GetSummary(), '"foo"')

# Now make sure that stepping out steps past the thunk:
if use_api:
thread.StepOut()
else:
self.runCmd("thread step-out")

frame_0 = thread.frames[0]
self.assertIn("doSomething", frame_0.GetFunctionName())
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Foo {
init() {
let some_string = "foo"
print(some_string)
}
}

func bar(_ _: Foo) {
print("bar")
}

func doSomething()
{
let f = Foo() // Break here to step into init
bar(f)
}

doSomething()