summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVedant Kumar <vsk@apple.com>2018-10-05 23:23:15 +0000
committerVedant Kumar <vsk@apple.com>2018-10-05 23:23:15 +0000
commit4b36f7911d8d186b60c1c63b5680e7a7ab300b61 (patch)
tree3c400746ca753e0c54c091d023bd9249c1c360d3
parent9d9c9655443cf8810390f525312d8ce70c4d319b (diff)
downloadbcm5719-llvm-4b36f7911d8d186b60c1c63b5680e7a7ab300b61.tar.gz
bcm5719-llvm-4b36f7911d8d186b60c1c63b5680e7a7ab300b61.zip
Add support for artificial tail call frames
This patch teaches lldb to detect when there are missing frames in a backtrace due to a sequence of tail calls, and to fill in the backtrace with artificial tail call frames when this happens. This is only done when the execution history can be determined from the call graph and from the return PC addresses of calls on the stack. Ambiguous sequences of tail calls (e.g anything involving tail calls and recursion) are detected and ignored. Depends on D49887. Differential Revision: https://reviews.llvm.org/D50478 llvm-svn: 343900
-rw-r--r--lldb/include/lldb/API/SBFrame.h4
-rw-r--r--lldb/include/lldb/Core/FormatEntity.h1
-rw-r--r--lldb/include/lldb/Symbol/Block.h8
-rw-r--r--lldb/include/lldb/Symbol/Function.h73
-rw-r--r--lldb/include/lldb/Symbol/SymbolFile.h5
-rw-r--r--lldb/include/lldb/Target/StackFrame.h66
-rw-r--r--lldb/include/lldb/Target/StackFrameList.h2
-rw-r--r--lldb/include/lldb/Target/ThreadPlanStepOut.h1
-rw-r--r--lldb/packages/Python/lldbsuite/test/decorators.py24
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/TestAmbiguousTailCallSeq1.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/main.cpp33
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/TestAmbiguousTailCallSeq2.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/main.cpp38
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/TestDisambiguateCallSite.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/main.cpp32
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/TestDisambiguatePathsToCommonSink.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/main.cpp38
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/TestDisambiguateTailCallSeq.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/main.cpp31
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/TestInliningAndTailCalls.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/main.cpp50
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/TestTailCallFrameSBAPI.py65
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/main.cpp25
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/TestArtificialFrameStepOutMessage.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/main.cpp28
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/TestSteppingOutWithArtificialFrames.py92
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/main.cpp25
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/Makefile4
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/TestUnambiguousTailCalls.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/main.cpp30
-rw-r--r--lldb/scripts/interface/SBFrame.i11
-rw-r--r--lldb/source/API/SBFrame.cpp15
-rw-r--r--lldb/source/Core/Debugger.cpp6
-rw-r--r--lldb/source/Core/FormatEntity.cpp9
-rw-r--r--lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp54
-rw-r--r--lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h3
-rw-r--r--lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp9
-rw-r--r--lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h2
-rw-r--r--lldb/source/Symbol/Block.cpp21
-rw-r--r--lldb/source/Symbol/Function.cpp93
-rw-r--r--lldb/source/Target/StackFrame.cpp40
-rw-r--r--lldb/source/Target/StackFrameList.cpp195
-rw-r--r--lldb/source/Target/ThreadPlanStepOut.cpp30
52 files changed, 1166 insertions, 73 deletions
diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h
index b8953dd1323..1123dade5de 100644
--- a/lldb/include/lldb/API/SBFrame.h
+++ b/lldb/include/lldb/API/SBFrame.h
@@ -90,6 +90,10 @@ public:
bool IsInlined() const;
+ bool IsArtificial();
+
+ bool IsArtificial() const;
+
/// The version that doesn't supply a 'use_dynamic' value will use the
/// target's default.
lldb::SBValue EvaluateExpression(const char *expr);
diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h
index 1e5277e6fe5..7090ee9c58e 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -88,6 +88,7 @@ public:
FrameRegisterFP,
FrameRegisterFlags,
FrameRegisterByName,
+ FrameIsArtificial,
ScriptFrame,
FunctionID,
FunctionDidChange,
diff --git a/lldb/include/lldb/Symbol/Block.h b/lldb/include/lldb/Symbol/Block.h
index 1664362431f..36904e535b6 100644
--- a/lldb/include/lldb/Symbol/Block.h
+++ b/lldb/include/lldb/Symbol/Block.h
@@ -327,6 +327,14 @@ public:
return m_inlineInfoSP.get();
}
+ //------------------------------------------------------------------
+ /// Get the symbol file which contains debug info for this block's
+ /// symbol context module.
+ ///
+ /// @return A pointer to the symbol file or nullptr.
+ //------------------------------------------------------------------
+ SymbolFile *GetSymbolFile();
+
CompilerDeclContext GetDeclContext();
//------------------------------------------------------------------
diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h
index f93b77236e8..2e0ed5327fe 100644
--- a/lldb/include/lldb/Symbol/Function.h
+++ b/lldb/include/lldb/Symbol/Function.h
@@ -16,6 +16,7 @@
#include "lldb/Symbol/Block.h"
#include "lldb/Symbol/Declaration.h"
#include "lldb/Utility/UserID.h"
+#include "llvm/ADT/ArrayRef.h"
namespace lldb_private {
@@ -290,6 +291,62 @@ private:
Declaration m_call_decl;
};
+class Function;
+
+//----------------------------------------------------------------------
+/// @class CallEdge Function.h "lldb/Symbol/Function.h"
+///
+/// Represent a call made within a Function. This can be used to find a path
+/// in the call graph between two functions.
+//----------------------------------------------------------------------
+class CallEdge {
+public:
+ /// Construct a call edge using a symbol name to identify the calling
+ /// function, and a return PC within the calling function to identify a
+ /// specific call site.
+ ///
+ /// TODO: A symbol name may not be globally unique. To disambiguate ODR
+ /// conflicts, it's necessary to determine the \c Target a call edge is
+ /// associated with before resolving it.
+ CallEdge(const char *symbol_name, lldb::addr_t return_pc);
+
+ CallEdge(CallEdge &&) = default;
+ CallEdge &operator=(CallEdge &&) = default;
+
+ /// Get the callee's definition.
+ ///
+ /// Note that this might lazily invoke the DWARF parser.
+ Function *GetCallee(ModuleList &images);
+
+ /// Get the load PC address of the instruction which executes after the call
+ /// returns. Returns LLDB_INVALID_ADDRESS iff this is a tail call. \p caller
+ /// is the Function containing this call, and \p target is the Target which
+ /// made the call.
+ lldb::addr_t GetReturnPCAddress(Function &caller, Target &target) const;
+
+ /// Like \ref GetReturnPCAddress, but returns an unresolved file address.
+ lldb::addr_t GetUnresolvedReturnPCAddress() const { return return_pc; }
+
+private:
+ void ParseSymbolFileAndResolve(ModuleList &images);
+
+ /// Either the callee's mangled name or its definition, discriminated by
+ /// \ref resolved.
+ union {
+ const char *symbol_name;
+ Function *def;
+ } lazy_callee;
+
+ /// An invalid address if this is a tail call. Otherwise, the return PC for
+ /// the call. Note that this is a file address which must be resolved.
+ lldb::addr_t return_pc;
+
+ /// Whether or not an attempt was made to find the callee's definition.
+ bool resolved;
+
+ DISALLOW_COPY_AND_ASSIGN(CallEdge);
+};
+
//----------------------------------------------------------------------
/// @class Function Function.h "lldb/Symbol/Function.h"
/// A class that describes a function.
@@ -397,6 +454,18 @@ public:
void GetEndLineSourceInfo(FileSpec &source_file, uint32_t &line_no);
//------------------------------------------------------------------
+ /// Get the outgoing call edges from this function, sorted by their return
+ /// PC addresses (in increasing order).
+ //------------------------------------------------------------------
+ llvm::MutableArrayRef<CallEdge> GetCallEdges();
+
+ //------------------------------------------------------------------
+ /// Get the outgoing tail-calling edges from this function. If none exist,
+ /// return None.
+ //------------------------------------------------------------------
+ llvm::MutableArrayRef<CallEdge> GetTailCallingEdges();
+
+ //------------------------------------------------------------------
/// Get accessor for the block list.
///
/// @return
@@ -587,6 +656,10 @@ protected:
Flags m_flags;
uint32_t
m_prologue_byte_size; ///< Compute the prologue size once and cache it
+
+ bool m_call_edges_resolved = false; ///< Whether call site info has been
+ /// parsed.
+ std::vector<CallEdge> m_call_edges; ///< Outgoing call edges.
private:
DISALLOW_COPY_AND_ASSIGN(Function);
};
diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index 7b77c60a3c3..61dffbfd201 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -14,6 +14,7 @@
#include "lldb/Symbol/CompilerDecl.h"
#include "lldb/Symbol/CompilerDeclContext.h"
#include "lldb/Symbol/CompilerType.h"
+#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Type.h"
#include "lldb/lldb-private.h"
@@ -194,6 +195,10 @@ public:
ObjectFile *GetObjectFile() { return m_obj_file; }
const ObjectFile *GetObjectFile() const { return m_obj_file; }
+ virtual std::vector<CallEdge> ParseCallEdgesInFunction(UserID func_id) {
+ return {};
+ }
+
//------------------------------------------------------------------
/// Notify the SymbolFile that the file addresses in the Sections
/// for this module have been changed.
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 556145a0b02..068c8800450 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -35,9 +35,9 @@ namespace lldb_private {
/// This base class provides an interface to stack frames.
///
/// StackFrames may have a Canonical Frame Address (CFA) or not.
-/// A frame may have a plain pc value or it may have a pc value + stop_id
-/// to indicate a specific point in the debug session so the correct section
-/// load list is used for symbolication.
+/// A frame may have a plain pc value or it may indicate a specific point in
+/// the debug session so the correct section load list is used for
+/// symbolication.
///
/// Local variables may be available, or not. A register context may be
/// available, or not.
@@ -54,14 +54,27 @@ public:
eExpressionPathOptionsInspectAnonymousUnions = (1u << 5)
};
+ enum class Kind {
+ /// A regular stack frame with access to registers and local variables.
+ Regular,
+
+ /// A historical stack frame -- possibly without CFA or registers or
+ /// local variables.
+ History,
+
+ /// An artificial stack frame (e.g. a synthesized result of inferring
+ /// missing tail call frames from a backtrace) with limited support for
+ /// local variables.
+ Artificial
+ };
+
//------------------------------------------------------------------
/// Construct a StackFrame object without supplying a RegisterContextSP.
///
/// This is the one constructor that doesn't take a RegisterContext
/// parameter. This ctor may be called when creating a history StackFrame;
/// these are used if we've collected a stack trace of pc addresses at some
- /// point in the past. We may only have pc values. We may have pc values
- /// and the stop_id when the stack trace was recorded. We may have a CFA,
+ /// point in the past. We may only have pc values. We may have a CFA,
/// or more likely, we won't.
///
/// @param [in] thread_sp
@@ -92,23 +105,7 @@ public:
/// @param [in] pc
/// The current pc value of this stack frame.
///
- /// @param [in] stop_id
- /// The stop_id which should be used when looking up symbols for the pc
- /// value,
- /// if appropriate. This argument is ignored if stop_id_is_valid is false.
- ///
- /// @param [in] stop_id_is_valid
- /// If the stop_id argument provided is not needed for this StackFrame, this
- /// should be false. If this is a history stack frame and we know the
- /// stop_id
- /// when the pc value was collected, that stop_id should be provided and
- /// this
- /// will be true.
- ///
- /// @param [in] is_history_frame
- /// If this is a historical stack frame -- possibly without CFA or registers
- /// or
- /// local variables -- then this should be set to true.
+ /// @param [in] frame_kind
///
/// @param [in] sc_ptr
/// Optionally seed the StackFrame with the SymbolContext information that
@@ -117,8 +114,7 @@ public:
//------------------------------------------------------------------
StackFrame(const lldb::ThreadSP &thread_sp, lldb::user_id_t frame_idx,
lldb::user_id_t concrete_frame_idx, lldb::addr_t cfa,
- bool cfa_is_valid, lldb::addr_t pc, uint32_t stop_id,
- bool stop_id_is_valid, bool is_history_frame,
+ bool cfa_is_valid, lldb::addr_t pc, Kind frame_kind,
const SymbolContext *sc_ptr);
StackFrame(const lldb::ThreadSP &thread_sp, lldb::user_id_t frame_idx,
@@ -403,6 +399,18 @@ public:
bool IsInlined();
//------------------------------------------------------------------
+ /// Query whether this frame is part of a historical backtrace.
+ //------------------------------------------------------------------
+ bool IsHistorical() const;
+
+ //------------------------------------------------------------------
+ /// Query whether this frame is artificial (e.g a synthesized result of
+ /// inferring missing tail call frames from a backtrace). Artificial frames
+ /// may have limited support for inspecting variables.
+ //------------------------------------------------------------------
+ bool IsArtificial() const;
+
+ //------------------------------------------------------------------
/// Query this frame to find what frame it is in this Thread's
/// StackFrameList.
///
@@ -413,6 +421,11 @@ public:
uint32_t GetFrameIndex() const;
//------------------------------------------------------------------
+ /// Set this frame's synthetic frame index.
+ //------------------------------------------------------------------
+ void SetFrameIndex(uint32_t index) { m_frame_index = index; }
+
+ //------------------------------------------------------------------
/// Query this frame to find what frame it is in this Thread's
/// StackFrameList, not counting inlined frames.
///
@@ -560,10 +573,7 @@ private:
Status m_frame_base_error;
bool m_cfa_is_valid; // Does this frame have a CFA? Different from CFA ==
// LLDB_INVALID_ADDRESS
- uint32_t m_stop_id;
- bool m_stop_id_is_valid; // Does this frame have a stop_id? Use it when
- // referring to the m_frame_code_addr.
- bool m_is_history_frame;
+ Kind m_stack_frame_kind;
lldb::VariableListSP m_variable_list_sp;
ValueObjectList m_variable_list_value_objects; // Value objects for each
// variable in
diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index 9a745a23bd0..0de90b3ba4a 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -99,6 +99,8 @@ protected:
void GetOnlyConcreteFramesUpTo(uint32_t end_idx, Unwind *unwinder);
+ void SynthesizeTailCallFrames(StackFrame &next_frame);
+
bool GetAllFramesFetched() { return m_concrete_frames_fetched == UINT32_MAX; }
void SetAllFramesFetched() { m_concrete_frames_fetched = UINT32_MAX; }
diff --git a/lldb/include/lldb/Target/ThreadPlanStepOut.h b/lldb/include/lldb/Target/ThreadPlanStepOut.h
index 285f4cab13a..e9761ca33a0 100644
--- a/lldb/include/lldb/Target/ThreadPlanStepOut.h
+++ b/lldb/include/lldb/Target/ThreadPlanStepOut.h
@@ -74,6 +74,7 @@ private:
// if ShouldStopHere told us
// to.
Function *m_immediate_step_from_function;
+ std::vector<lldb::StackFrameSP> m_stepped_past_frames;
lldb::ValueObjectSP m_return_valobj_sp;
bool m_calculate_return_value;
diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index 8b33cad3d1b..59cd3191828 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -687,6 +687,30 @@ def skipUnlessSupportedTypeAttribute(attr):
return None
return skipTestIfFn(compiler_doesnt_support_struct_attribute)
+def skipUnlessHasCallSiteInfo(func):
+ """Decorate the function to skip testing unless call site info from clang is available."""
+
+ def is_compiler_clang_with_call_site_info(self):
+ compiler_path = self.getCompiler()
+ compiler = os.path.basename(compiler_path)
+ if not compiler.startswith("clang"):
+ return "Test requires clang as compiler"
+
+ f = tempfile.NamedTemporaryFile()
+ cmd = "echo 'int main() {}' | " \
+ "%s -g -glldb -O1 -S -emit-llvm -x c -o %s -" % (compiler_path, f.name)
+ if os.popen(cmd).close() is not None:
+ return "Compiler can't compile with call site info enabled"
+
+ with open(f.name, 'r') as ir_output_file:
+ buf = ir_output_file.read()
+
+ if 'DIFlagAllCallsDescribed' not in buf:
+ return "Compiler did not introduce DIFlagAllCallsDescribed IR flag"
+
+ return None
+ return skipTestIfFn(is_compiler_clang_with_call_site_info)(func)
+
def skipUnlessThreadSanitizer(func):
"""Decorate the item to skip test unless Clang -fsanitize=thread is supported."""
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/TestAmbiguousTailCallSeq1.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/TestAmbiguousTailCallSeq1.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/TestAmbiguousTailCallSeq1.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/main.cpp
new file mode 100644
index 00000000000..48190184be1
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq1/main.cpp
@@ -0,0 +1,33 @@
+//===-- 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]}}_amb
+}
+
+void __attribute__((noinline)) func3_amb() { sink(); /* tail */ }
+
+void __attribute__((noinline)) func2_amb() { sink(); /* tail */ }
+
+void __attribute__((noinline)) func1() {
+ if (x > 0)
+ func2_amb(); /* tail */
+ else
+ func3_amb(); /* tail */
+}
+
+int __attribute__((disable_tail_calls)) main(int argc, char **) {
+ // The sequences `main -> func1 -> f{2,3}_amb -> sink` are both plausible. Test
+ // that lldb doesn't attempt to guess which one occurred.
+ func1();
+ return 0;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/TestAmbiguousTailCallSeq2.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/TestAmbiguousTailCallSeq2.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/TestAmbiguousTailCallSeq2.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/main.cpp
new file mode 100644
index 00000000000..1651db2ea4a
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/ambiguous_tail_call_seq2/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/TestDisambiguateCallSite.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/TestDisambiguateCallSite.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/TestDisambiguateCallSite.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/main.cpp
new file mode 100644
index 00000000000..d3aef19f7a4
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_call_site/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/TestDisambiguatePathsToCommonSink.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/TestDisambiguatePathsToCommonSink.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/TestDisambiguatePathsToCommonSink.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/main.cpp
new file mode 100644
index 00000000000..5189218c4ef
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_paths_to_common_sink/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/TestDisambiguateTailCallSeq.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/TestDisambiguateTailCallSeq.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/TestDisambiguateTailCallSeq.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/main.cpp
new file mode 100644
index 00000000000..3c723b8a3ee
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/disambiguate_tail_call_seq/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/TestInliningAndTailCalls.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/TestInliningAndTailCalls.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/TestInliningAndTailCalls.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/main.cpp
new file mode 100644
index 00000000000..e4504ad151f
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/inlining_and_tail_calls/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/TestTailCallFrameSBAPI.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/TestTailCallFrameSBAPI.py
new file mode 100644
index 00000000000..0e475a4c006
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/TestTailCallFrameSBAPI.py
@@ -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)
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/main.cpp
new file mode 100644
index 00000000000..f9e84da5173
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/sbapi_support/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/TestArtificialFrameStepOutMessage.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/TestArtificialFrameStepOutMessage.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/TestArtificialFrameStepOutMessage.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/main.cpp
new file mode 100644
index 00000000000..f2f11365df7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_message/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/TestSteppingOutWithArtificialFrames.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/TestSteppingOutWithArtificialFrames.py
new file mode 100644
index 00000000000..c1806f62ef7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/TestSteppingOutWithArtificialFrames.py
@@ -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)
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/main.cpp
new file mode 100644
index 00000000000..f7a81873906
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/thread_step_out_or_return/main.cpp
@@ -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;
+}
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/Makefile b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/Makefile
new file mode 100644
index 00000000000..15bc2e7f415
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/Makefile
@@ -0,0 +1,4 @@
+LEVEL = ../../../make
+CXX_SOURCES := main.cpp
+include $(LEVEL)/Makefile.rules
+CXXFLAGS += -g -O1 -glldb
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/TestUnambiguousTailCalls.py b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/TestUnambiguousTailCalls.py
new file mode 100644
index 00000000000..aec4d503fd7
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/TestUnambiguousTailCalls.py
@@ -0,0 +1,5 @@
+from lldbsuite.test import lldbinline
+from lldbsuite.test import decorators
+
+lldbinline.MakeInlineTest(__file__, globals(),
+ [decorators.skipUnlessHasCallSiteInfo])
diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/main.cpp
new file mode 100644
index 00000000000..c180d45b9de
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/functionalities/tail_call_frames/unambiguous_sequence/main.cpp
@@ -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;
+}
diff --git a/lldb/scripts/interface/SBFrame.i b/lldb/scripts/interface/SBFrame.i
index b8654cb0eb6..1ba2c2ebcd9 100644
--- a/lldb/scripts/interface/SBFrame.i
+++ b/lldb/scripts/interface/SBFrame.i
@@ -154,6 +154,17 @@ public:
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.
") EvaluateExpression;
diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp
index 05e629cbafa..b1c9cb62fa9 100644
--- a/lldb/source/API/SBFrame.cpp
+++ b/lldb/source/API/SBFrame.cpp
@@ -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();
}
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 1c2d69cb58a..be72ac0b16e 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -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 \
@@ -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
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index 977effcac8f..3cc5b756b1f 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -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[] = {
@@ -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);
@@ -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();
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index ea13399bc42..4f9e726f065 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -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
//------------------------------------------------------------------
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index b3fa8b59d76..abf4e39bc47 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -317,6 +317,9 @@ public:
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:
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index 39c70d14652..4c91686627b 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -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;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
index 550f74a203e..4bb9af0e3f2 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -122,6 +122,8 @@ public:
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
diff --git a/lldb/source/Symbol/Block.cpp b/lldb/source/Symbol/Block.cpp
index 46f875fca77..acc284de75e 100644
--- a/lldb/source/Symbol/Block.cpp
+++ b/lldb/source/Symbol/Block.cpp
@@ -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();
}
diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp
index 4064eb5163a..35f5751875c 100644
--- a/lldb/source/Symbol/Function.cpp
+++ b/lldb/source/Symbol/Function.cpp
@@ -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"
@@ -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;
@@ -131,6 +133,60 @@ size_t InlineFunctionInfo::MemorySize() const {
//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
+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);
+}
+
+//----------------------------------------------------------------------
+//
+//----------------------------------------------------------------------
Function::Function(CompileUnit *comp_unit, lldb::user_id_t func_uid,
lldb::user_id_t type_uid, const Mangled &mangled, Type *type,
const AddressRange &range)
@@ -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;
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 0e9c6df2bf7..5ed15099612 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -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);
}
@@ -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());
@@ -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());
@@ -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);
@@ -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);
@@ -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()) {
@@ -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);
@@ -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?
@@ -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)
diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index f177df0ccdf..a01de7a36d9 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -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
@@ -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())
@@ -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);
}
@@ -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;
diff --git a/lldb/source/Target/ThreadPlanStepOut.cpp b/lldb/source/Target/ThreadPlanStepOut.cpp
index f2db6e4b6f4..9acc17c7622 100644
--- a/lldb/source/Target/ThreadPlanStepOut.cpp
+++ b/lldb/source/Target/ThreadPlanStepOut.cpp
@@ -48,18 +48,36 @@ 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();
@@ -67,7 +85,7 @@ ThreadPlanStepOut::ThreadPlanStepOut(
// 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.
@@ -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?
@@ -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) {
OpenPOWER on IntegriCloud