Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; //% self.filecheck("bt", "main.cpp")
// CHECK-NOT: func{{[23]}}
}

void func2();

void __attribute__((noinline)) func1() {
if (x < 1)
func2();
else
sink();
}

void __attribute__((noinline)) func2() {
if (x < 1)
sink();
else
func1();
}

int main() {
// Tail recursion creates ambiguous execution histories.
x = 0;
func1();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; //% self.filecheck("bt", "main.cpp", "-implicit-check-not=artificial")
// CHECK: frame #0: 0x{{[0-9a-f]+}} a.out`sink() at main.cpp:[[@LINE-1]]:4 [opt]
// CHECK-NEXT: func2{{.*}} [opt] [artificial]
// CHECK-NEXT: main{{.*}} [opt]
}

void __attribute__((noinline)) func2() {
sink(); /* tail */
}

void __attribute__((noinline)) func1() { sink(); /* tail */ }

int __attribute__((disable_tail_calls)) main(int argc, char **) {
// The sequences `main -> f{1,2} -> sink` are both plausible. Test that
// return-pc call site info allows lldb to pick the correct sequence.
func2();
if (argc == 100)
func1();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink2() {
x++; //% self.filecheck("bt", "main.cpp", "-check-prefix=FROM-FUNC1")
// FROM-FUNC1: frame #0: 0x{{[0-9a-f]+}} a.out`sink{{.*}} at main.cpp:[[@LINE-1]]:{{.*}} [opt]
// FROM-FUNC1-NEXT: sink({{.*}} [opt]
// FROM-FUNC1-NEXT: func1{{.*}} [opt] [artificial]
// FROM-FUNC1-NEXT: main{{.*}} [opt]
}

void __attribute__((noinline)) sink(bool called_from_main) {
if (called_from_main) {
x++; //% self.filecheck("bt", "main.cpp", "-check-prefix=FROM-MAIN")
// FROM-MAIN: frame #0: 0x{{[0-9a-f]+}} a.out`sink{{.*}} at main.cpp:[[@LINE-1]]:{{.*}} [opt]
// FROM-MAIN-NEXT: main{{.*}} [opt]
} else {
sink2();
}
}

void __attribute__((noinline)) func1() { sink(false); /* tail */ }

int __attribute__((disable_tail_calls)) main(int argc, char **) {
// When func1 tail-calls sink, make sure that the former appears in the
// backtrace.
sink(true);
func1();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; //% self.filecheck("bt", "main.cpp", "-implicit-check-not=artificial")
// CHECK: frame #0: 0x{{[0-9a-f]+}} a.out`sink() at main.cpp:[[@LINE-1]]:4 [opt]
// CHECK-NEXT: func3{{.*}} [opt] [artificial]
// CHECK-NEXT: func1{{.*}} [opt] [artificial]
// CHECK-NEXT: main{{.*}} [opt]
}

void __attribute__((noinline)) func3() { sink(); /* tail */ }

void __attribute__((noinline)) func2() { sink(); /* tail */ }

void __attribute__((noinline)) func1() { func3(); /* tail */ }

int __attribute__((disable_tail_calls)) main(int argc, char **) {
// The sequences `main -> func1 -> f{2,3} -> sink` are both plausible. Test
// that lldb picks the latter sequence.
func1();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) tail_call_sink() {
x++; //% self.filecheck("bt", "main.cpp", "-check-prefix=TAIL-CALL-SINK")
// TAIL-CALL-SINK: frame #0: 0x{{[0-9a-f]+}} a.out`tail_call_sink() at main.cpp:[[@LINE-1]]:4 [opt]
// TAIL-CALL-SINK-NEXT: func3{{.*}} [opt] [artificial]
// TAIL-CALL-SINK-NEXT: main{{.*}} [opt]

// TODO: The backtrace should include inlinable_function_which_tail_calls.
}

void __attribute__((always_inline)) inlinable_function_which_tail_calls() {
tail_call_sink();
}

void __attribute__((noinline)) func3() {
inlinable_function_which_tail_calls();
}

void __attribute__((always_inline)) inline_sink() {
x++; //% self.filecheck("bt", "main.cpp", "-check-prefix=INLINE-SINK")
// INLINE-SINK: frame #0: 0x{{[0-9a-f]+}} a.out`func2() [inlined] inline_sink() at main.cpp:[[@LINE-1]]:4 [opt]
// INLINE-SINK-NEXT: func2{{.*}} [opt]
// INLINE-SINK-NEXT: func1{{.*}} [opt] [artificial]
// INLINE-SINK-NEXT: main{{.*}} [opt]
}

void __attribute__((noinline)) func2() { inline_sink(); /* inlined */ }

void __attribute__((noinline)) func1() { func2(); /* tail */ }

int __attribute__((disable_tail_calls)) main() {
// First, call a function that tail-calls a function, which itself inlines
// a third function.
func1();

// Next, call a function which contains an inlined tail-call.
func3();

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Test SB API support for identifying artificial (tail call) frames.
"""

import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *

class TestTailCallFrameSBAPI(TestBase):
mydir = TestBase.compute_mydir(__file__)

# If your test case doesn't stress debug info, the
# set this to true. That way it won't be run once for
# each debug info format.
NO_DEBUG_INFO_TESTCASE = True

def test_tail_call_frame_sbapi(self):
self.build()
self.do_test()

def setUp(self):
# Call super's setUp().
TestBase.setUp(self)

def do_test(self):
exe = self.getBuildArtifact("a.out")

# Create a target by the debugger.
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)

breakpoint = target.BreakpointCreateBySourceRegex("break here",
lldb.SBFileSpec("main.cpp"))
self.assertTrue(breakpoint and
breakpoint.GetNumLocations() == 1,
VALID_BREAKPOINT)

error = lldb.SBError()
launch_info = lldb.SBLaunchInfo(None)
process = target.Launch(launch_info, error)
self.assertTrue(process, PROCESS_IS_VALID)

# Did we hit our breakpoint?
threads = lldbutil.get_threads_stopped_at_breakpoint(process,
breakpoint)
self.assertTrue(
len(threads) == 1,
"There should be a thread stopped at our breakpoint")

self.assertTrue(breakpoint.GetHitCount() == 1)

thread = threads[0]

# Here's what we expect to see in the backtrace:
# frame #0: ... a.out`sink() at main.cpp:13:4 [opt]
# frame #1: ... a.out`func3() at main.cpp:14:1 [opt] [artificial]
# frame #2: ... a.out`func2() at main.cpp:18:62 [opt]
# frame #3: ... a.out`func1() at main.cpp:18:85 [opt] [artificial]
# frame #4: ... a.out`main at main.cpp:23:3 [opt]
names = ["sink()", "func3()", "func2()", "func1()", "main"]
artificiality = [False, True, False, True, False]
for idx, (name, is_artificial) in enumerate(zip(names, artificiality)):
frame = thread.GetFrameAtIndex(idx)
self.assertTrue(frame.GetDisplayFunctionName() == name)
self.assertTrue(frame.IsArtificial() == is_artificial)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; /* break here */
}

void __attribute__((noinline)) func3() { sink(); /* tail */ }

void __attribute__((disable_tail_calls, noinline)) func2() { func3(); /* regular */ }

void __attribute__((noinline)) func1() { func2(); /* tail */ }

int __attribute__((disable_tail_calls)) main() {
func1(); /* regular */
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; //% self.filecheck("finish", "main.cpp", "-implicit-check-not=artificial")
// CHECK: stop reason = step out
// CHECK-NEXT: Stepped out past: frame #1: 0x{{[0-9a-f]+}} a.out`func3{{.*}} [opt] [artificial]
// CHECK: frame #0: 0x{{[0-9a-f]+}} a.out`func2{{.*}} [opt]
}

void __attribute__((noinline)) func3() { sink(); /* tail */ }

void __attribute__((disable_tail_calls, noinline)) func2() { func3(); /* regular */ }

void __attribute__((noinline)) func1() { func2(); /* tail */ }

int __attribute__((disable_tail_calls)) main() {
func1(); /* regular */
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Test SB API support for identifying artificial (tail call) frames.
"""

import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *

class TestArtificialFrameThreadStepOut1(TestBase):
mydir = TestBase.compute_mydir(__file__)

# If your test case doesn't stress debug info, the
# set this to true. That way it won't be run once for
# each debug info format.
NO_DEBUG_INFO_TESTCASE = True

def prepare_thread(self):
exe = self.getBuildArtifact("a.out")

# Create a target by the debugger.
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)

breakpoint = target.BreakpointCreateBySourceRegex("break here",
lldb.SBFileSpec("main.cpp"))
self.assertTrue(breakpoint and
breakpoint.GetNumLocations() == 1,
VALID_BREAKPOINT)

error = lldb.SBError()
launch_info = lldb.SBLaunchInfo(None)
process = target.Launch(launch_info, error)
self.assertTrue(process, PROCESS_IS_VALID)

# Did we hit our breakpoint?
threads = lldbutil.get_threads_stopped_at_breakpoint(process,
breakpoint)
self.assertTrue(
len(threads) == 1,
"There should be a thread stopped at our breakpoint")

self.assertTrue(breakpoint.GetHitCount() == 1)

thread = threads[0]

# Here's what we expect to see in the backtrace:
# frame #0: ... a.out`sink() at main.cpp:13:4 [opt]
# frame #1: ... a.out`func3() at main.cpp:14:1 [opt] [artificial]
# frame #2: ... a.out`func2() at main.cpp:18:62 [opt]
# frame #3: ... a.out`func1() at main.cpp:18:85 [opt] [artificial]
# frame #4: ... a.out`main at main.cpp:23:3 [opt]
return thread

def test_stepping_out_past_artificial_frame(self):
self.build()
thread = self.prepare_thread()

# Frame #0's ancestor is artificial. Stepping out should move to
# frame #2, because we behave as-if artificial frames were not present.
thread.StepOut()
frame2 = thread.GetSelectedFrame()
self.assertTrue(frame2.GetDisplayFunctionName() == "func2()")
self.assertFalse(frame2.IsArtificial())

# Ditto: stepping out of frame #2 should move to frame #4.
thread.StepOut()
frame4 = thread.GetSelectedFrame()
self.assertTrue(frame4.GetDisplayFunctionName() == "main")
self.assertFalse(frame2.IsArtificial())

def test_return_past_artificial_frame(self):
self.build()
thread = self.prepare_thread()

value = lldb.SBValue()

# Frame #0's ancestor is artificial. Returning from frame #0 should move
# to frame #2.
thread.ReturnFromFrame(thread.GetSelectedFrame(), value)
frame2 = thread.GetSelectedFrame()
self.assertTrue(frame2.GetDisplayFunctionName() == "func2()")
self.assertFalse(frame2.IsArtificial())

# Ditto: stepping out of frame #2 should move to frame #4.
thread.ReturnFromFrame(thread.GetSelectedFrame(), value)
frame4 = thread.GetSelectedFrame()
self.assertTrue(frame4.GetDisplayFunctionName() == "main")
self.assertFalse(frame2.IsArtificial())

def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; // break here
}

void __attribute__((noinline)) func3() { sink(); /* tail */ }

void __attribute__((disable_tail_calls, noinline)) func2() { func3(); /* regular */ }

void __attribute__((noinline)) func1() { func2(); /* tail */ }

int __attribute__((disable_tail_calls)) main() {
func1(); /* regular */
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

volatile int x;

void __attribute__((noinline)) sink() {
x++; //% self.filecheck("bt", "main.cpp", "-implicit-check-not=artificial")
// CHECK: frame #0: 0x{{[0-9a-f]+}} a.out`sink() at main.cpp:[[@LINE-1]]:4 [opt]
// CHECK-NEXT: frame #1: 0x{{[0-9a-f]+}} a.out`func3{{.*}} [opt] [artificial]
// CHECK-NEXT: frame #2: 0x{{[0-9a-f]+}} a.out`func2{{.*}} [opt]
// CHECK-NEXT: frame #3: 0x{{[0-9a-f]+}} a.out`func1{{.*}} [opt] [artificial]
// CHECK-NEXT: frame #4: 0x{{[0-9a-f]+}} a.out`main{{.*}} [opt]
}

void __attribute__((noinline)) func3() { sink(); /* tail */ }

void __attribute__((disable_tail_calls, noinline)) func2() { func3(); /* regular */ }

void __attribute__((noinline)) func1() { func2(); /* tail */ }

int __attribute__((disable_tail_calls)) main() {
func1(); /* regular */
return 0;
}
11 changes: 11 additions & 0 deletions lldb/scripts/interface/SBFrame.i
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ public:
bool
IsInlined() const;

%feature("docstring", "
/// Return true if this frame is artificial (e.g a frame synthesized to
/// capture a tail call). Local variables may not be available in an artificial
/// frame.
") IsArtificial;
bool
IsArtificial();

bool
IsArtificial() const;

%feature("docstring", "
/// The version that doesn't supply a 'use_dynamic' value will use the
/// target's default.
Expand Down
15 changes: 15 additions & 0 deletions lldb/source/API/SBFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,21 @@ bool SBFrame::IsInlined() const {
return false;
}

bool SBFrame::IsArtificial() {
return static_cast<const SBFrame *>(this)->IsArtificial();
}

bool SBFrame::IsArtificial() const {
std::unique_lock<std::recursive_mutex> lock;
ExecutionContext exe_ctx(m_opaque_sp.get(), lock);

StackFrame *frame = exe_ctx.GetFramePtr();
if (frame)
return frame->IsArtificial();

return false;
}

const char *SBFrame::GetFunctionName() {
return static_cast<const SBFrame *>(this)->GetFunctionName();
}
Expand Down
6 changes: 4 additions & 2 deletions lldb/source/Core/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ static constexpr OptionEnumValueElement g_language_enumerators[] = {
"{ at ${line.file.basename}:${line.number}{:${line.column}}}"
#define IS_OPTIMIZED "{${function.is-optimized} [opt]}"

#define IS_ARTIFICIAL "{${frame.is-artificial} [artificial]}"

#define DEFAULT_THREAD_FORMAT \
"thread #${thread.index}: tid = ${thread.id%tid}" \
"{, ${frame.pc}}" MODULE_WITH_FUNC FILE_AND_LINE \
Expand All @@ -147,11 +149,11 @@ static constexpr OptionEnumValueElement g_language_enumerators[] = {

#define DEFAULT_FRAME_FORMAT \
"frame #${frame.index}: ${frame.pc}" MODULE_WITH_FUNC FILE_AND_LINE \
IS_OPTIMIZED "\\n"
IS_OPTIMIZED IS_ARTIFICIAL "\\n"

#define DEFAULT_FRAME_FORMAT_NO_ARGS \
"frame #${frame.index}: ${frame.pc}" MODULE_WITH_FUNC_NO_ARGS FILE_AND_LINE \
IS_OPTIMIZED "\\n"
IS_OPTIMIZED IS_ARTIFICIAL "\\n"

// Three parts to this disassembly format specification:
// 1. If this is a new function/symbol (no previous symbol/function), print
Expand Down
9 changes: 9 additions & 0 deletions lldb/source/Core/FormatEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ static FormatEntity::Entry::Definition g_frame_child_entries[] = {
ENTRY("flags", FrameRegisterFlags, UInt64),
ENTRY("no-debug", FrameNoDebug, None),
ENTRY_CHILDREN("reg", FrameRegisterByName, UInt64, g_string_entry),
ENTRY("is-artificial", FrameIsArtificial, UInt32),
};

static FormatEntity::Entry::Definition g_function_child_entries[] = {
Expand Down Expand Up @@ -357,6 +358,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
ENUM_TO_CSTR(FrameRegisterFP);
ENUM_TO_CSTR(FrameRegisterFlags);
ENUM_TO_CSTR(FrameRegisterByName);
ENUM_TO_CSTR(FrameIsArtificial);
ENUM_TO_CSTR(ScriptFrame);
ENUM_TO_CSTR(FunctionID);
ENUM_TO_CSTR(FunctionDidChange);
Expand Down Expand Up @@ -1489,6 +1491,13 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
}
return false;

case Entry::Type::FrameIsArtificial: {
if (exe_ctx)
if (StackFrame *frame = exe_ctx->GetFramePtr())
return frame->IsArtificial();
return false;
}

case Entry::Type::ScriptFrame:
if (exe_ctx) {
StackFrame *frame = exe_ctx->GetFramePtr();
Expand Down
54 changes: 54 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3742,6 +3742,60 @@ size_t SymbolFileDWARF::ParseVariables(const SymbolContext &sc,
return vars_added;
}

/// Collect call graph edges present in a function DIE.
static std::vector<lldb_private::CallEdge>
CollectCallEdges(DWARFDIE function_die) {
// Check if the function has a supported call site-related attribute.
// TODO: In the future it may be worthwhile to support call_all_source_calls.
uint64_t has_call_edges =
function_die.GetAttributeValueAsUnsigned(DW_AT_call_all_calls, 0);
if (!has_call_edges)
return {};

Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
LLDB_LOG(log, "CollectCallEdges: Found call site info in {0}",
function_die.GetPubname());

// Scan the DIE for TAG_call_site entries.
// TODO: A recursive scan of all blocks in the subprogram is needed in order
// to be DWARF5-compliant. This may need to be done lazily to be performant.
// For now, assume that all entries are nested directly under the subprogram
// (this is the kind of DWARF LLVM produces) and parse them eagerly.
std::vector<CallEdge> call_edges;
for (DWARFDIE child = function_die.GetFirstChild(); child.IsValid();
child = child.GetSibling()) {
if (child.Tag() != DW_TAG_call_site)
continue;

// Extract DW_AT_call_origin (the call target's DIE).
DWARFDIE call_origin = child.GetReferencedDIE(DW_AT_call_origin);
if (!call_origin.IsValid()) {
LLDB_LOG(log, "CollectCallEdges: Invalid call origin in {0}",
function_die.GetPubname());
continue;
}

// Extract DW_AT_call_return_pc (the PC the call returns to) if it's
// available. It should only ever be unavailable for tail call edges, in
// which case use LLDB_INVALID_ADDRESS.
addr_t return_pc = child.GetAttributeValueAsAddress(DW_AT_call_return_pc,
LLDB_INVALID_ADDRESS);

LLDB_LOG(log, "CollectCallEdges: Found call origin: {0} (retn-PC: {1:x})",
call_origin.GetPubname(), return_pc);
call_edges.emplace_back(call_origin.GetMangledName(), return_pc);
}
return call_edges;
}

std::vector<lldb_private::CallEdge>
SymbolFileDWARF::ParseCallEdgesInFunction(UserID func_id) {
DWARFDIE func_die = GetDIEFromUID(func_id.GetID());
if (func_die.IsValid())
return CollectCallEdges(func_die);
return {};
}

//------------------------------------------------------------------
// PluginInterface protocol
//------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ class SymbolFileDWARF : public lldb_private::SymbolFile,
DIEInDeclContext(const lldb_private::CompilerDeclContext *parent_decl_ctx,
const DWARFDIE &die);

std::vector<lldb_private::CallEdge>
ParseCallEdgesInFunction(UserID func_id) override;

void Dump(lldb_private::Stream &s) override;

protected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,15 @@ size_t SymbolFileDWARFDebugMap::GetTypes(SymbolContextScope *sc_scope,
return type_list.GetSize() - initial_size;
}

std::vector<lldb_private::CallEdge>
SymbolFileDWARFDebugMap::ParseCallEdgesInFunction(UserID func_id) {
uint32_t oso_idx = GetOSOIndexFromUserID(func_id.GetID());
SymbolFileDWARF *oso_dwarf = GetSymbolFileByOSOIndex(oso_idx);
if (oso_dwarf)
return oso_dwarf->ParseCallEdgesInFunction(func_id);
return {};
}

TypeSP SymbolFileDWARFDebugMap::FindDefinitionTypeForDWARFDeclContext(
const DWARFDeclContext &die_decl_ctx) {
TypeSP type_sp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFile {
size_t GetTypes(lldb_private::SymbolContextScope *sc_scope,
uint32_t type_mask,
lldb_private::TypeList &type_list) override;
std::vector<lldb_private::CallEdge>
ParseCallEdgesInFunction(lldb_private::UserID func_id) override;

//------------------------------------------------------------------
// PluginInterface protocol
Expand Down
21 changes: 9 additions & 12 deletions lldb/source/Symbol/Block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,19 +444,16 @@ uint32_t Block::AppendVariables(bool can_create, bool get_parent_variables,
return num_variables_added;
}

CompilerDeclContext Block::GetDeclContext() {
ModuleSP module_sp = CalculateSymbolContextModule();

if (module_sp) {
SymbolVendor *sym_vendor = module_sp->GetSymbolVendor();

if (sym_vendor) {
SymbolFile *sym_file = sym_vendor->GetSymbolFile();
SymbolFile *Block::GetSymbolFile() {
if (ModuleSP module_sp = CalculateSymbolContextModule())
if (SymbolVendor *sym_vendor = module_sp->GetSymbolVendor())
return sym_vendor->GetSymbolFile();
return nullptr;
}

if (sym_file)
return sym_file->GetDeclContextForUID(GetID());
}
}
CompilerDeclContext Block::GetDeclContext() {
if (SymbolFile *sym_file = GetSymbolFile())
return sym_file->GetDeclContextForUID(GetID());
return CompilerDeclContext();
}

Expand Down
93 changes: 93 additions & 0 deletions lldb/source/Symbol/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "lldb/Symbol/Function.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/Section.h"
#include "lldb/Host/Host.h"
#include "lldb/Symbol/CompileUnit.h"
Expand All @@ -18,6 +19,7 @@
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Symbol/SymbolVendor.h"
#include "lldb/Target/Language.h"
#include "lldb/Utility/Log.h"
#include "llvm/Support/Casting.h"

using namespace lldb;
Expand Down Expand Up @@ -128,6 +130,60 @@ size_t InlineFunctionInfo::MemorySize() const {
return FunctionInfo::MemorySize() + m_mangled.MemorySize();
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
CallEdge::CallEdge(const char *symbol_name, lldb::addr_t return_pc)
: return_pc(return_pc), resolved(false) {
lazy_callee.symbol_name = symbol_name;
}

void CallEdge::ParseSymbolFileAndResolve(ModuleList &images) {
if (resolved)
return;

Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
LLDB_LOG(log, "CallEdge: Lazily parsing the call graph for {0}",
lazy_callee.symbol_name);

auto resolve_lazy_callee = [&]() -> Function * {
ConstString callee_name{lazy_callee.symbol_name};
SymbolContextList sc_list;
size_t num_matches =
images.FindFunctionSymbols(callee_name, eFunctionNameTypeAuto, sc_list);
if (num_matches == 0 || !sc_list[0].symbol) {
LLDB_LOG(log, "CallEdge: Found no symbols for {0}, cannot resolve it",
callee_name);
return nullptr;
}
Address callee_addr = sc_list[0].symbol->GetAddress();
if (!callee_addr.IsValid()) {
LLDB_LOG(log, "CallEdge: Invalid symbol address");
return nullptr;
}
Function *f = callee_addr.CalculateSymbolContextFunction();
if (!f) {
LLDB_LOG(log, "CallEdge: Could not find complete function");
return nullptr;
}
return f;
};
lazy_callee.def = resolve_lazy_callee();
resolved = true;
}

Function *CallEdge::GetCallee(ModuleList &images) {
ParseSymbolFileAndResolve(images);
return lazy_callee.def;
}

lldb::addr_t CallEdge::GetReturnPCAddress(Function &caller,
Target &target) const {
const Address &base = caller.GetAddressRange().GetBaseAddress();
Address return_pc_addr{base.GetSection(), return_pc};
return return_pc_addr.GetLoadAddress(&target);
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
Expand Down Expand Up @@ -192,6 +248,43 @@ void Function::GetEndLineSourceInfo(FileSpec &source_file, uint32_t &line_no) {
}
}

llvm::MutableArrayRef<CallEdge> Function::GetCallEdges() {
if (m_call_edges_resolved)
return m_call_edges;

Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
LLDB_LOG(log, "GetCallEdges: Attempting to parse call site info for {0}",
GetDisplayName());

m_call_edges_resolved = true;

// Find the SymbolFile which provided this function's definition.
Block &block = GetBlock(/*can_create*/true);
SymbolFile *sym_file = block.GetSymbolFile();
if (!sym_file)
return llvm::None;

// Lazily read call site information from the SymbolFile.
m_call_edges = sym_file->ParseCallEdgesInFunction(GetID());

// Sort the call edges to speed up return_pc lookups.
std::sort(m_call_edges.begin(), m_call_edges.end(),
[](const CallEdge &LHS, const CallEdge &RHS) {
return LHS.GetUnresolvedReturnPCAddress() <
RHS.GetUnresolvedReturnPCAddress();
});

return m_call_edges;
}

llvm::MutableArrayRef<CallEdge> Function::GetTailCallingEdges() {
// Call edges are sorted by return PC, and tail calling edges have invalid
// return PCs. Find them at the end of the list.
return GetCallEdges().drop_until([](const CallEdge &edge) {
return edge.GetUnresolvedReturnPCAddress() == LLDB_INVALID_ADDRESS;
});
}

Block &Function::GetBlock(bool can_create) {
if (!m_block.BlockInfoHasBeenParsed() && can_create) {
SymbolContext sc;
Expand Down
40 changes: 22 additions & 18 deletions lldb/source/Target/StackFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,18 @@ using namespace lldb_private;

StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
user_id_t unwind_frame_index, addr_t cfa,
bool cfa_is_valid, addr_t pc, uint32_t stop_id,
bool stop_id_is_valid, bool is_history_frame,
bool cfa_is_valid, addr_t pc, StackFrame::Kind kind,
const SymbolContext *sc_ptr)
: m_thread_wp(thread_sp), m_frame_index(frame_idx),
m_concrete_frame_index(unwind_frame_index), m_reg_context_sp(),
m_id(pc, cfa, nullptr), m_frame_code_addr(pc), m_sc(), m_flags(),
m_frame_base(), m_frame_base_error(), m_cfa_is_valid(cfa_is_valid),
m_stop_id(stop_id), m_stop_id_is_valid(stop_id_is_valid),
m_is_history_frame(is_history_frame), m_variable_list_sp(),
m_stack_frame_kind(kind), m_variable_list_sp(),
m_variable_list_value_objects(), m_disassembly(), m_mutex() {
// If we don't have a CFA value, use the frame index for our StackID so that
// recursive functions properly aren't confused with one another on a history
// stack.
if (m_is_history_frame && !m_cfa_is_valid) {
if (IsHistorical() && !m_cfa_is_valid) {
m_id.SetCFA(m_frame_index);
}

Expand All @@ -80,10 +78,9 @@ StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
m_concrete_frame_index(unwind_frame_index),
m_reg_context_sp(reg_context_sp), m_id(pc, cfa, nullptr),
m_frame_code_addr(pc), m_sc(), m_flags(), m_frame_base(),
m_frame_base_error(), m_cfa_is_valid(true), m_stop_id(0),
m_stop_id_is_valid(false), m_is_history_frame(false),
m_variable_list_sp(), m_variable_list_value_objects(), m_disassembly(),
m_mutex() {
m_frame_base_error(), m_cfa_is_valid(true),
m_stack_frame_kind(StackFrame::Kind::Regular), m_variable_list_sp(),
m_variable_list_value_objects(), m_disassembly(), m_mutex() {
if (sc_ptr != nullptr) {
m_sc = *sc_ptr;
m_flags.Set(m_sc.GetResolvedMask());
Expand All @@ -106,10 +103,9 @@ StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
m_id(pc_addr.GetLoadAddress(thread_sp->CalculateTarget().get()), cfa,
nullptr),
m_frame_code_addr(pc_addr), m_sc(), m_flags(), m_frame_base(),
m_frame_base_error(), m_cfa_is_valid(true), m_stop_id(0),
m_stop_id_is_valid(false), m_is_history_frame(false),
m_variable_list_sp(), m_variable_list_value_objects(), m_disassembly(),
m_mutex() {
m_frame_base_error(), m_cfa_is_valid(true),
m_stack_frame_kind(StackFrame::Kind::Regular), m_variable_list_sp(),
m_variable_list_value_objects(), m_disassembly(), m_mutex() {
if (sc_ptr != nullptr) {
m_sc = *sc_ptr;
m_flags.Set(m_sc.GetResolvedMask());
Expand Down Expand Up @@ -210,7 +206,7 @@ const Address &StackFrame::GetFrameCodeAddress() {
bool StackFrame::ChangePC(addr_t pc) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
// We can't change the pc value of a history stack frame - it is immutable.
if (m_is_history_frame)
if (IsHistorical())
return false;
m_frame_code_addr.SetRawAddress(pc);
m_sc.Clear(false);
Expand Down Expand Up @@ -456,7 +452,7 @@ StackFrame::GetInScopeVariableList(bool get_file_globals,
bool must_have_valid_location) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
// We can't fetch variable information for a history stack frame.
if (m_is_history_frame)
if (IsHistorical())
return VariableListSP();

VariableListSP var_list_sp(new VariableList);
Expand Down Expand Up @@ -490,7 +486,7 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
VariableSP &var_sp, Status &error) {
llvm::StringRef original_var_expr = var_expr;
// We can't fetch variable information for a history stack frame.
if (m_is_history_frame)
if (IsHistorical())
return ValueObjectSP();

if (var_expr.empty()) {
Expand Down Expand Up @@ -1135,7 +1131,7 @@ StackFrame::GetValueObjectForFrameVariable(const VariableSP &variable_sp,
DynamicValueType use_dynamic) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
ValueObjectSP valobj_sp;
if (m_is_history_frame) {
if (IsHistorical()) {
return valobj_sp;
}
VariableList *var_list = GetVariableList(true);
Expand Down Expand Up @@ -1164,7 +1160,7 @@ StackFrame::GetValueObjectForFrameVariable(const VariableSP &variable_sp,
ValueObjectSP StackFrame::TrackGlobalVariable(const VariableSP &variable_sp,
DynamicValueType use_dynamic) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
if (m_is_history_frame)
if (IsHistorical())
return ValueObjectSP();

// Check to make sure we aren't already tracking this variable?
Expand Down Expand Up @@ -1194,6 +1190,14 @@ bool StackFrame::IsInlined() {
return false;
}

bool StackFrame::IsHistorical() const {
return m_stack_frame_kind == StackFrame::Kind::History;
}

bool StackFrame::IsArtificial() const {
return m_stack_frame_kind == StackFrame::Kind::Artificial;
}

lldb::LanguageType StackFrame::GetLanguage() {
CompileUnit *cu = GetSymbolContext(eSymbolContextCompUnit).comp_unit;
if (cu)
Expand Down
195 changes: 185 additions & 10 deletions lldb/source/Target/StackFrameList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "lldb/Target/Thread.h"
#include "lldb/Target/Unwind.h"
#include "lldb/Utility/Log.h"
#include "llvm/ADT/SmallPtrSet.h"

//#define DEBUG_STACK_FRAMES 1

Expand Down Expand Up @@ -240,6 +241,178 @@ void StackFrameList::GetOnlyConcreteFramesUpTo(uint32_t end_idx,
m_frames.resize(num_frames);
}

/// Find the unique path through the call graph from \p begin (with return PC
/// \p return_pc) to \p end. On success this path is stored into \p path, and
/// on failure \p path is unchanged.
static void FindInterveningFrames(Function &begin, Function &end,
Target &target, addr_t return_pc,
std::vector<Function *> &path,
ModuleList &images, Log *log) {
LLDB_LOG(log, "Finding frames between {0} and {1}, retn-pc={2:x}",
begin.GetDisplayName(), end.GetDisplayName(), return_pc);

// Find a non-tail calling edge with the correct return PC.
auto first_level_edges = begin.GetCallEdges();
if (log)
for (const CallEdge &edge : first_level_edges)
LLDB_LOG(log, "FindInterveningFrames: found call with retn-PC = {0:x}",
edge.GetReturnPCAddress(begin, target));
auto first_edge_it = std::lower_bound(
first_level_edges.begin(), first_level_edges.end(), return_pc,
[&](const CallEdge &edge, addr_t target_pc) {
return edge.GetReturnPCAddress(begin, target) < target_pc;
});
if (first_edge_it == first_level_edges.end() ||
first_edge_it->GetReturnPCAddress(begin, target) != return_pc) {
LLDB_LOG(log, "No call edge outgoing from {0} with retn-PC == {1:x}",
begin.GetDisplayName(), return_pc);
return;
}
CallEdge &first_edge = const_cast<CallEdge &>(*first_edge_it);

// The first callee may not be resolved, or there may be nothing to fill in.
Function *first_callee = first_edge.GetCallee(images);
if (!first_callee) {
LLDB_LOG(log, "Could not resolve callee");
return;
}
if (first_callee == &end) {
LLDB_LOG(log, "Not searching further, first callee is {0} (retn-PC: {1:x})",
end.GetDisplayName(), return_pc);
return;
}

// Run DFS on the tail-calling edges out of the first callee to find \p end.
// Fully explore the set of functions reachable from the first edge via tail
// calls in order to detect ambiguous executions.
struct DFS {
std::vector<Function *> active_path = {};
std::vector<Function *> solution_path = {};
llvm::SmallPtrSet<Function *, 2> visited_nodes = {};
bool ambiguous = false;
Function *end;
ModuleList &images;

DFS(Function *end, ModuleList &images) : end(end), images(images) {}

void search(Function *first_callee, std::vector<Function *> &path) {
dfs(first_callee);
if (!ambiguous)
path = std::move(solution_path);
}

void dfs(Function *callee) {
// Found a path to the target function.
if (callee == end) {
if (solution_path.empty())
solution_path = active_path;
else
ambiguous = true;
return;
}

// Terminate the search if tail recursion is found, or more generally if
// there's more than one way to reach a target. This errs on the side of
// caution: it conservatively stops searching when some solutions are
// still possible to save time in the average case.
if (!visited_nodes.insert(callee).second) {
ambiguous = true;
return;
}

// Search the calls made from this callee.
active_path.push_back(callee);
for (CallEdge &edge : callee->GetTailCallingEdges()) {
Function *next_callee = edge.GetCallee(images);
if (!next_callee)
continue;

dfs(next_callee);
if (ambiguous)
return;
}
active_path.pop_back();
}
};

DFS(&end, images).search(first_callee, path);
}

/// Given that \p next_frame will be appended to the frame list, synthesize
/// tail call frames between the current end of the list and \p next_frame.
/// If any frames are added, adjust the frame index of \p next_frame.
///
/// --------------
/// | ... | <- Completed frames.
/// --------------
/// | prev_frame |
/// --------------
/// | ... | <- Artificial frames inserted here.
/// --------------
/// | next_frame |
/// --------------
/// | ... | <- Not-yet-visited frames.
/// --------------
void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) {
TargetSP target_sp = next_frame.CalculateTarget();
if (!target_sp)
return;

lldb::RegisterContextSP next_reg_ctx_sp = next_frame.GetRegisterContext();
if (!next_reg_ctx_sp)
return;

Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));

assert(!m_frames.empty() && "Cannot synthesize frames in an empty stack");
StackFrame &prev_frame = *m_frames.back().get();

// Find the functions prev_frame and next_frame are stopped in. The function
// objects are needed to search the lazy call graph for intervening frames.
Function *prev_func =
prev_frame.GetSymbolContext(eSymbolContextFunction).function;
if (!prev_func) {
LLDB_LOG(log, "SynthesizeTailCallFrames: can't find previous function");
return;
}
Function *next_func =
next_frame.GetSymbolContext(eSymbolContextFunction).function;
if (!next_func) {
LLDB_LOG(log, "SynthesizeTailCallFrames: can't find next function");
return;
}

// Try to find the unique sequence of (tail) calls which led from next_frame
// to prev_frame.
std::vector<Function *> path;
addr_t return_pc = next_reg_ctx_sp->GetPC();
Target &target = *target_sp.get();
ModuleList &images = next_frame.CalculateTarget()->GetImages();
FindInterveningFrames(*next_func, *prev_func, target, return_pc, path, images,
log);

// Push synthetic tail call frames.
for (Function *callee : llvm::reverse(path)) {
uint32_t frame_idx = m_frames.size();
uint32_t concrete_frame_idx = next_frame.GetConcreteFrameIndex();
addr_t cfa = LLDB_INVALID_ADDRESS;
bool cfa_is_valid = false;
addr_t pc =
callee->GetAddressRange().GetBaseAddress().GetLoadAddress(&target);
SymbolContext sc;
callee->CalculateSymbolContext(&sc);
auto synth_frame = std::make_shared<StackFrame>(
m_thread.shared_from_this(), frame_idx, concrete_frame_idx, cfa,
cfa_is_valid, pc, StackFrame::Kind::Artificial, &sc);
m_frames.push_back(synth_frame);
LLDB_LOG(log, "Pushed frame {0}", callee->GetDisplayName());
}

// If any frames were created, adjust next_frame's index.
if (!path.empty())
next_frame.SetFrameIndex(m_frames.size());
}

void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
// Do not fetch frames for an invalid thread.
if (!m_thread.IsValid())
Expand Down Expand Up @@ -315,11 +488,15 @@ void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
break;
}
const bool cfa_is_valid = true;
const bool stop_id_is_valid = false;
const bool is_history_frame = false;
unwind_frame_sp.reset(new StackFrame(
m_thread.shared_from_this(), m_frames.size(), idx, cfa, cfa_is_valid,
pc, 0, stop_id_is_valid, is_history_frame, nullptr));
unwind_frame_sp.reset(
new StackFrame(m_thread.shared_from_this(), m_frames.size(), idx, cfa,
cfa_is_valid, pc, StackFrame::Kind::Regular, nullptr));

// Create synthetic tail call frames between the previous frame and the
// newly-found frame. The new frame's index may change after this call,
// although its concrete index will stay the same.
SynthesizeTailCallFrames(*unwind_frame_sp.get());

m_frames.push_back(unwind_frame_sp);
}

Expand Down Expand Up @@ -491,11 +668,9 @@ StackFrameSP StackFrameList::GetFrameAtIndex(uint32_t idx) {
addr_t pc, cfa;
if (unwinder->GetFrameInfoAtIndex(idx, cfa, pc)) {
const bool cfa_is_valid = true;
const bool stop_id_is_valid = false;
const bool is_history_frame = false;
frame_sp.reset(new StackFrame(
m_thread.shared_from_this(), idx, idx, cfa, cfa_is_valid, pc, 0,
stop_id_is_valid, is_history_frame, nullptr));
frame_sp.reset(new StackFrame(m_thread.shared_from_this(), idx, idx,
cfa, cfa_is_valid, pc,
StackFrame::Kind::Regular, nullptr));

Function *function =
frame_sp->GetSymbolContext(eSymbolContextFunction).function;
Expand Down
30 changes: 27 additions & 3 deletions lldb/source/Target/ThreadPlanStepOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,44 @@ ThreadPlanStepOut::ThreadPlanStepOut(
m_return_addr(LLDB_INVALID_ADDRESS), m_stop_others(stop_others),
m_immediate_step_from_function(nullptr),
m_calculate_return_value(gather_return_value) {
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
SetFlagsToDefault();
SetupAvoidNoDebug(step_out_avoids_code_without_debug_info);

m_step_from_insn = m_thread.GetRegisterContext()->GetPC(0);

StackFrameSP return_frame_sp(m_thread.GetStackFrameAtIndex(frame_idx + 1));
uint32_t return_frame_index = frame_idx + 1;
StackFrameSP return_frame_sp(
m_thread.GetStackFrameAtIndex(return_frame_index));
StackFrameSP immediate_return_from_sp(
m_thread.GetStackFrameAtIndex(frame_idx));

if (!return_frame_sp || !immediate_return_from_sp)
return; // we can't do anything here. ValidatePlan() will return false.

// While stepping out, behave as-if artificial frames are not present.
while (return_frame_sp->IsArtificial()) {
m_stepped_past_frames.push_back(return_frame_sp);

++return_frame_index;
return_frame_sp = m_thread.GetStackFrameAtIndex(return_frame_index);

// We never expect to see an artificial frame without a regular ancestor.
// If this happens, log the issue and defensively refuse to step out.
if (!return_frame_sp) {
LLDB_LOG(log, "Can't step out of frame with artificial ancestors");
return;
}
}

m_step_out_to_id = return_frame_sp->GetStackID();
m_immediate_step_from_id = immediate_return_from_sp->GetStackID();

// If the frame directly below the one we are returning to is inlined, we
// have to be a little more careful. It is non-trivial to determine the real
// "return code address" for an inlined frame, so we have to work our way to
// that frame and then step out.
if (immediate_return_from_sp && immediate_return_from_sp->IsInlined()) {
if (immediate_return_from_sp->IsInlined()) {
if (frame_idx > 0) {
// First queue a plan that gets us to this inlined frame, and when we get
// there we'll queue a second plan that walks us out of this frame.
Expand All @@ -82,7 +100,7 @@ ThreadPlanStepOut::ThreadPlanStepOut(
// just do that now.
QueueInlinedStepPlan(false);
}
} else if (return_frame_sp) {
} else {
// Find the return address and set a breakpoint there:
// FIXME - can we do this more securely if we know first_insn?

Expand Down Expand Up @@ -198,6 +216,12 @@ void ThreadPlanStepOut::GetDescription(Stream *s,
s->Printf(" using breakpoint site %d", m_return_bp_id);
}
}

s->Printf("\n");
for (StackFrameSP frame_sp : m_stepped_past_frames) {
s->Printf("Stepped out past: ");
frame_sp->DumpUsingSettingsFormat(s);
}
}

bool ThreadPlanStepOut::ValidatePlan(Stream *error) {
Expand Down