Skip to content

Commit

Permalink
Add support for artificial tail call frames
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vedantk committed Oct 5, 2018
1 parent 9d9c965 commit 4b36f79
Show file tree
Hide file tree
Showing 52 changed files with 1,166 additions and 73 deletions.
4 changes: 4 additions & 0 deletions lldb/include/lldb/API/SBFrame.h
Expand Up @@ -90,6 +90,10 @@ class LLDB_API SBFrame {

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);
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/Core/FormatEntity.h
Expand Up @@ -88,6 +88,7 @@ class FormatEntity {
FrameRegisterFP,
FrameRegisterFlags,
FrameRegisterByName,
FrameIsArtificial,
ScriptFrame,
FunctionID,
FunctionDidChange,
Expand Down
8 changes: 8 additions & 0 deletions lldb/include/lldb/Symbol/Block.h
Expand Up @@ -327,6 +327,14 @@ class Block : public UserID, public SymbolContextScope {
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();

//------------------------------------------------------------------
Expand Down
73 changes: 73 additions & 0 deletions lldb/include/lldb/Symbol/Function.h
Expand Up @@ -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 {

Expand Down Expand Up @@ -290,6 +291,62 @@ class InlineFunctionInfo : public FunctionInfo {
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.
Expand Down Expand Up @@ -396,6 +453,18 @@ class Function : public UserID, public SymbolContextScope {
//------------------------------------------------------------------
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.
///
Expand Down Expand Up @@ -587,6 +656,10 @@ class Function : public UserID, public SymbolContextScope {
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);
};
Expand Down
5 changes: 5 additions & 0 deletions lldb/include/lldb/Symbol/SymbolFile.h
Expand Up @@ -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"

Expand Down Expand Up @@ -194,6 +195,10 @@ class SymbolFile : public PluginInterface {
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.
Expand Down
66 changes: 38 additions & 28 deletions lldb/include/lldb/Target/StackFrame.h
Expand Up @@ -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.
Expand All @@ -54,14 +54,27 @@ class StackFrame : public ExecutionContextScope,
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
Expand Down Expand Up @@ -92,23 +105,7 @@ class StackFrame : public ExecutionContextScope,
/// @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
Expand All @@ -117,8 +114,7 @@ class StackFrame : public ExecutionContextScope,
//------------------------------------------------------------------
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,
Expand Down Expand Up @@ -402,6 +398,18 @@ class StackFrame : public ExecutionContextScope,
//------------------------------------------------------------------
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.
Expand All @@ -412,6 +420,11 @@ class StackFrame : public ExecutionContextScope,
//------------------------------------------------------------------
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.
Expand Down Expand Up @@ -560,10 +573,7 @@ class StackFrame : public ExecutionContextScope,
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
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/StackFrameList.h
Expand Up @@ -99,6 +99,8 @@ class StackFrameList {

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; }
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/Target/ThreadPlanStepOut.h
Expand Up @@ -74,6 +74,7 @@ class ThreadPlanStepOut : public ThreadPlan, public ThreadPlanShouldStopHere {
// 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;

Expand Down
24 changes: 24 additions & 0 deletions lldb/packages/Python/lldbsuite/test/decorators.py
Expand Up @@ -687,6 +687,30 @@ def compiler_doesnt_support_struct_attribute(self):
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."""

Expand Down
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])
@@ -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;
}
@@ -0,0 +1,4 @@
LEVEL = ../../../make
CXX_SOURCES := main.cpp
include $(LEVEL)/Makefile.rules
CXXFLAGS += -g -O1 -glldb
@@ -0,0 +1,5 @@
from lldbsuite.test import lldbinline
from lldbsuite.test import decorators

lldbinline.MakeInlineTest(__file__, globals(),
[decorators.skipUnlessHasCallSiteInfo])

0 comments on commit 4b36f79

Please sign in to comment.