Index: lldb/include/lldb/Symbol/Function.h =================================================================== --- lldb/include/lldb/Symbol/Function.h +++ lldb/include/lldb/Symbol/Function.h @@ -246,10 +246,22 @@ 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; + /// \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 @@ /// 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 @@ /// offset. lldb::addr_t GetUnresolvedReturnPCAddress() const { return return_pc; } + /// Get the call site parameters available at this call edge. + llvm::ArrayRef GetCallSiteParameters() const; + private: void ParseSymbolFileAndResolve(ModuleList &images); @@ -294,6 +310,8 @@ /// 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 @@ 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 m_call_edges; ///< Outgoing call edges. Index: lldb/packages/Python/lldbsuite/test/decorators.py =================================================================== --- lldb/packages/Python/lldbsuite/test/decorators.py +++ lldb/packages/Python/lldbsuite/test/decorators.py @@ -641,6 +641,16 @@ 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 @@ 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" Index: lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/Makefile =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules +CXXFLAGS += -O1 -glldb -Xclang -femit-debug-entry-values Index: lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/TestBasicEntryValuesX86_64.py =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/TestBasicEntryValuesX86_64.py @@ -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'])]) Index: lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/main.cpp =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/param_entry_vals/basic_entry_values_x86_64/main.cpp @@ -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 __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; +} Index: lldb/source/Expression/DWARFExpression.cpp =================================================================== --- lldb/source/Expression/DWARFExpression.cpp +++ lldb/source/Expression/DWARFExpression.cpp @@ -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 @@ 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 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 @@ 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 @@ 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 @@ 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 @@ return false; } +static bool Evaluate_DW_OP_entry_value(std::vector &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 is only used to identify the right call + // site parameter in the parent, and does 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, @@ -2494,6 +2739,7 @@ // address has been dynamically determined by an earlier step during user // expression evaluation. case DW_OP_push_object_address: + // TODO: Reject DW_OP_push_object_address within entry value exprs. if (object_address_ptr) stack.push_back(*object_address_ptr); else { @@ -2751,6 +2997,16 @@ 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 +3121,11 @@ 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 +3226,7 @@ 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: Index: lldb/source/Plugins/SymbolFile/DWARF/DWARFDefines.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/DWARFDefines.cpp +++ lldb/source/Plugins/SymbolFile/DWARF/DWARFDefines.cpp @@ -59,6 +59,8 @@ } 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 @@ 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: Index: lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -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 @@ 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 LocationInCallee = {}; + llvm::Optional 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 { + 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 -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 @@ 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 @@ 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 {}; } Index: lldb/source/Symbol/Function.cpp =================================================================== --- lldb/source/Symbol/Function.cpp +++ lldb/source/Symbol/Function.cpp @@ -127,11 +127,16 @@ } // -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 CallEdge::GetCallSiteParameters() const { + return parameters; +} + void CallEdge::ParseSymbolFileAndResolve(ModuleList &images) { if (resolved) return;