forked from OSchip/llvm-project
[DWARF] Evaluate DW_OP_entry_value
Add support for evaluating DW_OP_entry_value. This involves parsing DW_TAG_call_site_parameter and wiring the information through to the expression evaluator. rdar://54496008 Differential Revision: https://reviews.llvm.org/D67376 llvm-svn: 371668
This commit is contained in:
parent
bb58118525
commit
21d417dc18
|
@ -246,10 +246,22 @@ private:
|
|||
|
||||
class Function;
|
||||
|
||||
/// \class CallSiteParameter Function.h "lldb/Symbol/Function.h"
|
||||
///
|
||||
/// Represent the locations of a parameter at a call site, both in the caller
|
||||
/// and in the callee.
|
||||
struct CallSiteParameter {
|
||||
DWARFExpression LocationInCallee;
|
||||
DWARFExpression LocationInCaller;
|
||||
};
|
||||
|
||||
/// A vector of \c CallSiteParameter.
|
||||
using CallSiteParameterArray = llvm::SmallVector<CallSiteParameter, 0>;
|
||||
|
||||
/// \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.
|
||||
/// in the call graph between two functions, or to evaluate DW_OP_entry_value.
|
||||
class CallEdge {
|
||||
public:
|
||||
/// Construct a call edge using a symbol name to identify the calling
|
||||
|
@ -259,7 +271,8 @@ public:
|
|||
/// 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(const char *symbol_name, lldb::addr_t return_pc,
|
||||
CallSiteParameterArray parameters);
|
||||
|
||||
CallEdge(CallEdge &&) = default;
|
||||
CallEdge &operator=(CallEdge &&) = default;
|
||||
|
@ -279,6 +292,9 @@ public:
|
|||
/// offset.
|
||||
lldb::addr_t GetUnresolvedReturnPCAddress() const { return return_pc; }
|
||||
|
||||
/// Get the call site parameters available at this call edge.
|
||||
llvm::ArrayRef<CallSiteParameter> GetCallSiteParameters() const;
|
||||
|
||||
private:
|
||||
void ParseSymbolFileAndResolve(ModuleList &images);
|
||||
|
||||
|
@ -294,6 +310,8 @@ private:
|
|||
/// gives the return PC for the call.
|
||||
lldb::addr_t return_pc;
|
||||
|
||||
CallSiteParameterArray parameters;
|
||||
|
||||
/// Whether or not an attempt was made to find the callee's definition.
|
||||
bool resolved;
|
||||
|
||||
|
@ -569,6 +587,8 @@ protected:
|
|||
uint32_t
|
||||
m_prologue_byte_size; ///< Compute the prologue size once and cache it
|
||||
|
||||
// TODO: Use a layer of indirection to point to call edges, to save space
|
||||
// when call info hasn't been parsed.
|
||||
bool m_call_edges_resolved = false; ///< Whether call site info has been
|
||||
/// parsed.
|
||||
std::vector<CallEdge> m_call_edges; ///< Outgoing call edges.
|
||||
|
|
|
@ -641,6 +641,16 @@ def skipUnlessPlatform(oslist):
|
|||
return unittest2.skipUnless(lldbplatformutil.getPlatform() in oslist,
|
||||
"requires one of %s" % (", ".join(oslist)))
|
||||
|
||||
def skipUnlessArch(arch):
|
||||
"""Decorate the item to skip tests unless running on the specified architecture."""
|
||||
|
||||
def arch_doesnt_match(self):
|
||||
target_arch = self.getArchitecture()
|
||||
if arch != target_arch:
|
||||
return "Test only runs on " + arch + ", but target arch is " + target_arch
|
||||
return None
|
||||
|
||||
return skipTestIfFn(arch_doesnt_match)
|
||||
|
||||
def skipIfTargetAndroid(bugnumber=None, api_levels=None, archs=None):
|
||||
"""Decorator to skip tests when the target is Android.
|
||||
|
@ -682,7 +692,7 @@ def skipUnlessHasCallSiteInfo(func):
|
|||
|
||||
f = tempfile.NamedTemporaryFile()
|
||||
cmd = "echo 'int main() {}' | " \
|
||||
"%s -g -glldb -O1 -S -emit-llvm -x c -o %s -" % (compiler_path, f.name)
|
||||
"%s -g -glldb -O1 -Xclang -femit-debug-entry-values -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"
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
LEVEL = ../../../make
|
||||
CXX_SOURCES := main.cpp
|
||||
include $(LEVEL)/Makefile.rules
|
||||
CXXFLAGS += -O1 -glldb -Xclang -femit-debug-entry-values
|
|
@ -0,0 +1,8 @@
|
|||
from lldbsuite.test import lldbinline
|
||||
from lldbsuite.test import decorators
|
||||
|
||||
lldbinline.MakeInlineTest(__file__, globals(),
|
||||
[decorators.skipUnlessDarwin,
|
||||
decorators.skipUnlessArch('x86_64'),
|
||||
decorators.skipUnlessHasCallSiteInfo,
|
||||
decorators.skipIf(dwarf_version=['<', '4'])])
|
|
@ -0,0 +1,169 @@
|
|||
// Note: This test requires the SysV AMD64 ABI to be in use, and requires
|
||||
// compiler support for DWARF entry values.
|
||||
|
||||
// Inhibit dead-arg-elim by using 'x'.
|
||||
template<typename T> __attribute__((noinline)) void use(T x) {
|
||||
asm volatile (""
|
||||
/* Outputs */ :
|
||||
/* Inputs */ : "g"(x)
|
||||
/* Clobbers */ :
|
||||
);
|
||||
}
|
||||
|
||||
// Destroy %rsi in the current frame.
|
||||
#define DESTROY_RSI \
|
||||
asm volatile ("xorq %%rsi, %%rsi" \
|
||||
/* Outputs */ : \
|
||||
/* Inputs */ : \
|
||||
/* Clobbers */ : "rsi" \
|
||||
);
|
||||
|
||||
struct S1 {
|
||||
int field1 = 123;
|
||||
int *field2 = &field1;
|
||||
};
|
||||
|
||||
__attribute__((noinline))
|
||||
void func1(int &sink, int x) {
|
||||
use(x);
|
||||
|
||||
// Destroy 'x' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("image lookup -va $pc", "main.cpp", "-check-prefix=FUNC1-DESC")
|
||||
// FUNC1-DESC: name = "x", type = "int", location = DW_OP_entry_value( rsi)
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func2(int &sink, int x) {
|
||||
use(x);
|
||||
|
||||
// Destroy 'x' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("expr x", "main.cpp", "-check-prefix=FUNC2-EXPR")
|
||||
// FUNC2-EXPR: (int) ${{.*}} = 123
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func3(int &sink, int *p) {
|
||||
use(p);
|
||||
|
||||
// Destroy 'p' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("expr *p", "main.cpp", "-check-prefix=FUNC3-EXPR")
|
||||
// FUNC3-EXPR: (int) ${{.*}} = 123
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func4_amb(int &sink, int x) {
|
||||
use(x);
|
||||
|
||||
// Destroy 'x' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("expr x", "main.cpp", "-check-prefix=FUNC4-EXPR", expect_cmd_failure=True)
|
||||
// FUNC4-EXPR: couldn't get the value of variable x: Could not evaluate DW_OP_entry_value.
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func5_amb() {}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func6(int &sink, int x) {
|
||||
if (sink > 0)
|
||||
func4_amb(sink, x); /* tail (taken) */
|
||||
else
|
||||
func5_amb(); /* tail */
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func7(int &sink, int x) {
|
||||
//% self.filecheck("bt", "main.cpp", "-check-prefix=FUNC7-BT")
|
||||
// FUNC7-BT: func7
|
||||
// FUNC7-BT-NEXT: [inlined] func8_inlined
|
||||
// FUNC7-BT-NEXT: [inlined] func9_inlined
|
||||
// FUNC7-BT-NEXT: func10
|
||||
use(x);
|
||||
|
||||
// Destroy 'x' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("expr x", "main.cpp", "-check-prefix=FUNC7-EXPR")
|
||||
// FUNC7-EXPR: (int) ${{.*}} = 123
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void func8_inlined(int &sink, int x) {
|
||||
func7(sink, x);
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void func9_inlined(int &sink, int x) {
|
||||
func8_inlined(sink, x);
|
||||
}
|
||||
|
||||
__attribute__((noinline, disable_tail_calls))
|
||||
void func10(int &sink, int x) {
|
||||
func9_inlined(sink, x);
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func11_tailcalled(int &sink, int x) {
|
||||
//% self.filecheck("bt", "main.cpp", "-check-prefix=FUNC11-BT")
|
||||
// FUNC11-BT: func11_tailcalled{{.*}}
|
||||
// FUNC11-BT-NEXT: func12{{.*}} [artificial]
|
||||
use(x);
|
||||
|
||||
// Destroy 'x' in the current frame.
|
||||
DESTROY_RSI;
|
||||
|
||||
//% self.filecheck("expr x", "main.cpp", "-check-prefix=FUNC11-EXPR")
|
||||
// FUNC11-EXPR: (int) ${{.*}} = 123
|
||||
|
||||
++sink;
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void func12(int &sink, int x) {
|
||||
func11_tailcalled(sink, x);
|
||||
}
|
||||
|
||||
__attribute__((disable_tail_calls))
|
||||
int main() {
|
||||
int sink = 0;
|
||||
S1 s1;
|
||||
|
||||
// Test location dumping for DW_OP_entry_value.
|
||||
func1(sink, 123);
|
||||
|
||||
// Test evaluation of "DW_OP_constu" in the parent frame.
|
||||
func2(sink, 123);
|
||||
|
||||
// Test evaluation of "DW_OP_fbreg -24, DW_OP_deref" in the parent frame.
|
||||
func3(sink, s1.field2);
|
||||
|
||||
// The sequences `main -> func4 -> func{5,6}_amb -> sink` are both plausible.
|
||||
// Test that lldb doesn't attempt to guess which one occurred: entry value
|
||||
// evaluation should fail.
|
||||
func6(sink, 123);
|
||||
|
||||
// Test that evaluation can "see through" inlining.
|
||||
func10(sink, 123);
|
||||
|
||||
// Test that evaluation can "see through" tail calls.
|
||||
func12(sink, 123);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -33,6 +33,7 @@
|
|||
#include "lldb/Target/RegisterContext.h"
|
||||
#include "lldb/Target/StackFrame.h"
|
||||
#include "lldb/Target/StackID.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
#include "lldb/Target/Thread.h"
|
||||
|
||||
#include "Plugins/SymbolFile/DWARF/DWARFUnit.h"
|
||||
|
@ -91,9 +92,27 @@ void DWARFExpression::DumpLocation(Stream *s, lldb::offset_t offset,
|
|||
return;
|
||||
const lldb::offset_t start_offset = offset;
|
||||
const lldb::offset_t end_offset = offset + length;
|
||||
|
||||
// An operation within a DWARF expression may contain a sub-expression. The
|
||||
// motivating example for this is DW_OP_entry_value. Keep track of where each
|
||||
// each sub-expression ends.
|
||||
std::vector<lldb::offset_t> ends_of_subexprs;
|
||||
|
||||
// "Finish" (i.e. print the closing right-parens) for sub-expressions up to
|
||||
// the specified \p op_offset.
|
||||
auto finish_subexpressions_to = [&](const lldb::offset_t op_offset) {
|
||||
while (!ends_of_subexprs.empty() && op_offset >= ends_of_subexprs.back()) {
|
||||
ends_of_subexprs.pop_back();
|
||||
s->Printf(")");
|
||||
if (!ends_of_subexprs.empty())
|
||||
s->Printf(" ");
|
||||
}
|
||||
};
|
||||
|
||||
while (m_data.ValidOffset(offset) && offset < end_offset) {
|
||||
const lldb::offset_t op_offset = offset;
|
||||
const uint8_t op = m_data.GetU8(&offset);
|
||||
finish_subexpressions_to(op_offset);
|
||||
|
||||
switch (level) {
|
||||
default:
|
||||
|
@ -466,8 +485,16 @@ void DWARFExpression::DumpLocation(Stream *s, lldb::offset_t offset,
|
|||
case DW_OP_APPLE_uninit:
|
||||
s->PutCString("DW_OP_APPLE_uninit"); // 0xF0
|
||||
break;
|
||||
case DW_OP_entry_value: {
|
||||
uint32_t subexpr_len = m_data.GetULEB128(&offset);
|
||||
s->PutCString("DW_OP_entry_value(");
|
||||
ends_of_subexprs.push_back(offset + subexpr_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finish_subexpressions_to(end_offset);
|
||||
}
|
||||
|
||||
void DWARFExpression::SetLocationListSlide(addr_t slide) {
|
||||
|
@ -580,6 +607,8 @@ static bool ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
|
|||
return false;
|
||||
}
|
||||
|
||||
/// Return the length in bytes of the set of operands for \p op. No guarantees
|
||||
/// are made on the state of \p data after this call.
|
||||
static offset_t GetOpcodeDataSize(const DataExtractor &data,
|
||||
const lldb::offset_t data_offset,
|
||||
const uint8_t op) {
|
||||
|
@ -776,6 +805,12 @@ static offset_t GetOpcodeDataSize(const DataExtractor &data,
|
|||
return offset - data_offset;
|
||||
}
|
||||
|
||||
case DW_OP_entry_value: // 0xa3 ULEB128 size + variable-length block
|
||||
{
|
||||
uint64_t subexpr_len = data.GetULEB128(&offset);
|
||||
return (offset - data_offset) + subexpr_len;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1071,6 +1106,216 @@ bool DWARFExpression::DumpLocationForAddress(Stream *s,
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool Evaluate_DW_OP_entry_value(std::vector<Value> &stack,
|
||||
ExecutionContext *exe_ctx,
|
||||
RegisterContext *reg_ctx,
|
||||
const DataExtractor &opcodes,
|
||||
lldb::offset_t &opcode_offset,
|
||||
Status *error_ptr, Log *log) {
|
||||
// DW_OP_entry_value(sub-expr) describes the location a variable had upon
|
||||
// function entry: this variable location is presumed to be optimized out at
|
||||
// the current PC value. The caller of the function may have call site
|
||||
// information that describes an alternate location for the variable (e.g. a
|
||||
// constant literal, or a spilled stack value) in the parent frame.
|
||||
//
|
||||
// Example (this is pseudo-code & pseudo-DWARF, but hopefully illustrative):
|
||||
//
|
||||
// void child(int &sink, int x) {
|
||||
// ...
|
||||
// /* "x" gets optimized out. */
|
||||
//
|
||||
// /* The location of "x" here is: DW_OP_entry_value($reg2). */
|
||||
// ++sink;
|
||||
// }
|
||||
//
|
||||
// void parent() {
|
||||
// int sink;
|
||||
//
|
||||
// /*
|
||||
// * The callsite information emitted here is:
|
||||
// *
|
||||
// * DW_TAG_call_site
|
||||
// * DW_AT_return_pc ... (for "child(sink, 123);")
|
||||
// * DW_TAG_call_site_parameter (for "sink")
|
||||
// * DW_AT_location ($reg1)
|
||||
// * DW_AT_call_value ($SP - 8)
|
||||
// * DW_TAG_call_site_parameter (for "x")
|
||||
// * DW_AT_location ($reg2)
|
||||
// * DW_AT_call_value ($literal 123)
|
||||
// *
|
||||
// * DW_TAG_call_site
|
||||
// * DW_AT_return_pc ... (for "child(sink, 456);")
|
||||
// * ...
|
||||
// */
|
||||
// child(sink, 123);
|
||||
// child(sink, 456);
|
||||
// }
|
||||
//
|
||||
// When the program stops at "++sink" within `child`, the debugger determines
|
||||
// the call site by analyzing the return address. Once the call site is found,
|
||||
// the debugger determines which parameter is referenced by DW_OP_entry_value
|
||||
// and evaluates the corresponding location for that parameter in `parent`.
|
||||
|
||||
// 1. Find the function which pushed the current frame onto the stack.
|
||||
if ((!exe_ctx || !exe_ctx->HasTargetScope()) || !reg_ctx) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no exe/reg context");
|
||||
return false;
|
||||
}
|
||||
|
||||
StackFrame *current_frame = exe_ctx->GetFramePtr();
|
||||
Thread *thread = exe_ctx->GetThreadPtr();
|
||||
if (!current_frame || !thread) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no current frame/thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
Target &target = exe_ctx->GetTargetRef();
|
||||
StackFrameSP parent_frame = nullptr;
|
||||
addr_t return_pc = LLDB_INVALID_ADDRESS;
|
||||
uint32_t current_frame_idx = current_frame->GetFrameIndex();
|
||||
uint32_t num_frames = thread->GetStackFrameCount();
|
||||
for (uint32_t parent_frame_idx = current_frame_idx + 1;
|
||||
parent_frame_idx < num_frames; ++parent_frame_idx) {
|
||||
parent_frame = thread->GetStackFrameAtIndex(parent_frame_idx);
|
||||
// Require a valid sequence of frames.
|
||||
if (!parent_frame)
|
||||
break;
|
||||
|
||||
// Record the first valid return address, even if this is an inlined frame,
|
||||
// in order to look up the associated call edge in the first non-inlined
|
||||
// parent frame.
|
||||
if (return_pc == LLDB_INVALID_ADDRESS) {
|
||||
return_pc = parent_frame->GetFrameCodeAddress().GetLoadAddress(&target);
|
||||
LLDB_LOG(log,
|
||||
"Evaluate_DW_OP_entry_value: immediate ancestor with pc = {0:x}",
|
||||
return_pc);
|
||||
}
|
||||
|
||||
// If we've found an inlined frame, skip it (these have no call site
|
||||
// parameters).
|
||||
if (parent_frame->IsInlined())
|
||||
continue;
|
||||
|
||||
// We've found the first non-inlined parent frame.
|
||||
break;
|
||||
}
|
||||
if (!parent_frame || !parent_frame->GetRegisterContext()) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no parent frame with reg ctx");
|
||||
return false;
|
||||
}
|
||||
|
||||
Function *parent_func =
|
||||
parent_frame->GetSymbolContext(eSymbolContextFunction).function;
|
||||
if (!parent_func) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no parent function");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Find the call edge in the parent function responsible for creating the
|
||||
// current activation.
|
||||
Function *current_func =
|
||||
current_frame->GetSymbolContext(eSymbolContextFunction).function;
|
||||
if (!current_func) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no current function");
|
||||
return false;
|
||||
}
|
||||
|
||||
CallEdge *call_edge = nullptr;
|
||||
ModuleList &modlist = target.GetImages();
|
||||
if (!parent_frame->IsArtificial()) {
|
||||
// If the parent frame is not artificial, the current activation may be
|
||||
// produced by an ambiguous tail call. In this case, refuse to proceed.
|
||||
call_edge = parent_func->GetCallEdgeForReturnAddress(return_pc, target);
|
||||
if (!call_edge) {
|
||||
LLDB_LOG(log,
|
||||
"Evaluate_DW_OP_entry_value: no call edge for retn-pc = {0:x} "
|
||||
"in parent frame {1}",
|
||||
return_pc, parent_func->GetName());
|
||||
return false;
|
||||
}
|
||||
Function *callee_func = call_edge->GetCallee(modlist);
|
||||
if (callee_func != current_func) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: ambiguous call sequence, "
|
||||
"can't find real parent frame");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// The StackFrameList solver machinery has deduced that an unambiguous tail
|
||||
// call sequence that produced the current activation. The first edge in
|
||||
// the parent that points to the current function must be valid.
|
||||
for (CallEdge &edge : parent_func->GetTailCallingEdges()) {
|
||||
if (edge.GetCallee(modlist) == current_func) {
|
||||
call_edge = &edge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!call_edge) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: no unambiguous edge from parent "
|
||||
"to current function");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Attempt to locate the DW_OP_entry_value expression in the set of
|
||||
// available call site parameters. If found, evaluate the corresponding
|
||||
// parameter in the context of the parent frame.
|
||||
const uint32_t subexpr_len = opcodes.GetULEB128(&opcode_offset);
|
||||
const void *subexpr_data = opcodes.GetData(&opcode_offset, subexpr_len);
|
||||
if (!subexpr_data) {
|
||||
LLDB_LOG(log, "Evaluate_DW_OP_entry_value: subexpr could not be read");
|
||||
return false;
|
||||
}
|
||||
|
||||
const CallSiteParameter *matched_param = nullptr;
|
||||
for (const CallSiteParameter ¶m : call_edge->GetCallSiteParameters()) {
|
||||
DataExtractor param_subexpr_extractor;
|
||||
if (!param.LocationInCallee.GetExpressionData(param_subexpr_extractor))
|
||||
continue;
|
||||
lldb::offset_t param_subexpr_offset = 0;
|
||||
const void *param_subexpr_data =
|
||||
param_subexpr_extractor.GetData(¶m_subexpr_offset, subexpr_len);
|
||||
if (!param_subexpr_data ||
|
||||
param_subexpr_extractor.BytesLeft(param_subexpr_offset) != 0)
|
||||
continue;
|
||||
|
||||
// At this point, the DW_OP_entry_value sub-expression and the callee-side
|
||||
// expression in the call site parameter are known to have the same length.
|
||||
// Check whether they are equal.
|
||||
//
|
||||
// Note that an equality check is sufficient: the contents of the
|
||||
// DW_OP_entry_value subexpression are only used to identify the right call
|
||||
// site parameter in the parent, and do not require any special handling.
|
||||
if (memcmp(subexpr_data, param_subexpr_data, subexpr_len) == 0) {
|
||||
matched_param = ¶m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched_param) {
|
||||
LLDB_LOG(log,
|
||||
"Evaluate_DW_OP_entry_value: no matching call site param found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Add support for DW_OP_push_object_address within a DW_OP_entry_value
|
||||
// subexpresion whenever llvm does.
|
||||
Value result;
|
||||
ExecutionContext parent_exe_ctx = *exe_ctx;
|
||||
parent_exe_ctx.SetFrameSP(parent_frame);
|
||||
const DWARFExpression ¶m_expr = matched_param->LocationInCaller;
|
||||
if (!param_expr.Evaluate(&parent_exe_ctx,
|
||||
parent_frame->GetRegisterContext().get(),
|
||||
/*loclist_base_addr=*/LLDB_INVALID_ADDRESS,
|
||||
/*initial_value_ptr=*/nullptr,
|
||||
/*object_address_ptr=*/nullptr, result, error_ptr)) {
|
||||
LLDB_LOG(log,
|
||||
"Evaluate_DW_OP_entry_value: call site param evaluation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
stack.push_back(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DWARFExpression::Evaluate(ExecutionContextScope *exe_scope,
|
||||
lldb::addr_t loclist_base_load_addr,
|
||||
const Value *initial_value_ptr,
|
||||
|
@ -2751,6 +2996,16 @@ bool DWARFExpression::Evaluate(
|
|||
stack.push_back(Scalar(value));
|
||||
} break;
|
||||
|
||||
case DW_OP_entry_value: {
|
||||
if (!Evaluate_DW_OP_entry_value(stack, exe_ctx, reg_ctx, opcodes, offset,
|
||||
error_ptr, log)) {
|
||||
LLDB_ERRORF(error_ptr, "Could not evaluate %s.",
|
||||
DW_OP_value_to_name(op));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LLDB_LOGF(log, "Unhandled opcode %s in DWARFExpression.",
|
||||
DW_OP_value_to_name(op));
|
||||
|
@ -2865,6 +3120,11 @@ static bool print_dwarf_exp_op(Stream &s, const DataExtractor &data,
|
|||
s.Printf("%" PRIu64 " %" PRIi64, uint, sint);
|
||||
return true;
|
||||
}
|
||||
if (opcode_class == DRC_TWOOPERANDS && opcode == DW_OP_entry_value) {
|
||||
uint = data.GetULEB128(offset_ptr);
|
||||
s.Printf("%" PRIu64 " ", uint);
|
||||
return true;
|
||||
}
|
||||
if (opcode_class != DRC_ONEOPERAND) {
|
||||
s.Printf("UNKNOWN OP %u", opcode);
|
||||
return false;
|
||||
|
@ -2965,6 +3225,7 @@ static bool print_dwarf_exp_op(Stream &s, const DataExtractor &data,
|
|||
case DW_OP_regx:
|
||||
case DW_OP_GNU_addr_index:
|
||||
case DW_OP_GNU_const_index:
|
||||
case DW_OP_entry_value:
|
||||
size = 128;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -59,6 +59,8 @@ const char *DW_OP_value_to_name(uint32_t val) {
|
|||
}
|
||||
|
||||
DRC_class DW_OP_value_to_class(uint32_t val) {
|
||||
// FIXME: If we just used llvm's DWARFExpression printer, we could delete
|
||||
// all this code (and more in lldb's DWARFExpression.cpp).
|
||||
switch (val) {
|
||||
case 0x03:
|
||||
return DRC_ONEOPERAND;
|
||||
|
@ -358,6 +360,8 @@ DRC_class DW_OP_value_to_class(uint32_t val) {
|
|||
return DRC_DWARFv3 | DRC_ONEOPERAND;
|
||||
case 0x9a:
|
||||
return DRC_DWARFv3 | DRC_ONEOPERAND;
|
||||
case 0xa3: /* DW_OP_entry_value */
|
||||
return DRC_TWOOPERANDS;
|
||||
case 0xf0:
|
||||
return DRC_ZEROOPERANDS; /* DW_OP_APPLE_uninit */
|
||||
case 0xe0:
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "SymbolFileDWARF.h"
|
||||
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/Threading.h"
|
||||
|
||||
|
@ -3708,9 +3709,57 @@ size_t SymbolFileDWARF::ParseVariables(const SymbolContext &sc,
|
|||
return vars_added;
|
||||
}
|
||||
|
||||
/// Collect call site parameters in a DW_TAG_call_site DIE.
|
||||
static CallSiteParameterArray
|
||||
CollectCallSiteParameters(ModuleSP module, DWARFDIE call_site_die) {
|
||||
CallSiteParameterArray parameters;
|
||||
for (DWARFDIE child = call_site_die.GetFirstChild(); child.IsValid();
|
||||
child = child.GetSibling()) {
|
||||
if (child.Tag() != DW_TAG_call_site_parameter)
|
||||
continue;
|
||||
|
||||
llvm::Optional<DWARFExpression> LocationInCallee = {};
|
||||
llvm::Optional<DWARFExpression> LocationInCaller = {};
|
||||
|
||||
DWARFAttributes attributes;
|
||||
const size_t num_attributes = child.GetAttributes(attributes);
|
||||
|
||||
// Parse the location at index \p attr_index within this call site parameter
|
||||
// DIE, or return None on failure.
|
||||
auto parse_simple_location =
|
||||
[&](int attr_index) -> llvm::Optional<DWARFExpression> {
|
||||
DWARFFormValue form_value;
|
||||
if (!attributes.ExtractFormValueAtIndex(attr_index, form_value))
|
||||
return {};
|
||||
if (!DWARFFormValue::IsBlockForm(form_value.Form()))
|
||||
return {};
|
||||
auto data = child.GetData();
|
||||
uint32_t block_offset = form_value.BlockData() - data.GetDataStart();
|
||||
uint32_t block_length = form_value.Unsigned();
|
||||
return DWARFExpression(module,
|
||||
DataExtractor(data, block_offset, block_length),
|
||||
child.GetCU());
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < num_attributes; ++i) {
|
||||
dw_attr_t attr = attributes.AttributeAtIndex(i);
|
||||
if (attr == DW_AT_location)
|
||||
LocationInCallee = parse_simple_location(i);
|
||||
if (attr == DW_AT_call_value)
|
||||
LocationInCaller = parse_simple_location(i);
|
||||
}
|
||||
|
||||
if (LocationInCallee && LocationInCaller) {
|
||||
CallSiteParameter param = {*LocationInCallee, *LocationInCaller};
|
||||
parameters.push_back(param);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/// Collect call graph edges present in a function DIE.
|
||||
static std::vector<lldb_private::CallEdge>
|
||||
CollectCallEdges(DWARFDIE function_die) {
|
||||
CollectCallEdges(ModuleSP module, 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 =
|
||||
|
@ -3747,9 +3796,28 @@ CollectCallEdges(DWARFDIE function_die) {
|
|||
addr_t return_pc = child.GetAttributeValueAsAddress(DW_AT_call_return_pc,
|
||||
LLDB_INVALID_ADDRESS);
|
||||
|
||||
// Extract call site parameters.
|
||||
CallSiteParameterArray parameters =
|
||||
CollectCallSiteParameters(module, child);
|
||||
|
||||
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);
|
||||
if (log && parameters.size()) {
|
||||
for (const CallSiteParameter ¶m : parameters) {
|
||||
StreamString callee_loc_desc, caller_loc_desc;
|
||||
param.LocationInCallee.GetDescription(&callee_loc_desc,
|
||||
eDescriptionLevelBrief,
|
||||
LLDB_INVALID_ADDRESS, nullptr);
|
||||
param.LocationInCaller.GetDescription(&caller_loc_desc,
|
||||
eDescriptionLevelBrief,
|
||||
LLDB_INVALID_ADDRESS, nullptr);
|
||||
LLDB_LOG(log, "CollectCallEdges: \tparam: {0} => {1}",
|
||||
callee_loc_desc.GetString(), caller_loc_desc.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
call_edges.emplace_back(call_origin.GetMangledName(), return_pc,
|
||||
std::move(parameters));
|
||||
}
|
||||
return call_edges;
|
||||
}
|
||||
|
@ -3758,7 +3826,7 @@ std::vector<lldb_private::CallEdge>
|
|||
SymbolFileDWARF::ParseCallEdgesInFunction(UserID func_id) {
|
||||
DWARFDIE func_die = GetDIE(func_id.GetID());
|
||||
if (func_die.IsValid())
|
||||
return CollectCallEdges(func_die);
|
||||
return CollectCallEdges(GetObjectFile()->GetModule(), func_die);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -127,11 +127,16 @@ size_t InlineFunctionInfo::MemorySize() const {
|
|||
}
|
||||
|
||||
//
|
||||
CallEdge::CallEdge(const char *symbol_name, lldb::addr_t return_pc)
|
||||
: return_pc(return_pc), resolved(false) {
|
||||
CallEdge::CallEdge(const char *symbol_name, lldb::addr_t return_pc,
|
||||
CallSiteParameterArray parameters)
|
||||
: return_pc(return_pc), parameters(std::move(parameters)), resolved(false) {
|
||||
lazy_callee.symbol_name = symbol_name;
|
||||
}
|
||||
|
||||
llvm::ArrayRef<CallSiteParameter> CallEdge::GetCallSiteParameters() const {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
void CallEdge::ParseSymbolFileAndResolve(ModuleList &images) {
|
||||
if (resolved)
|
||||
return;
|
||||
|
|
Loading…
Reference in New Issue