diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h index 3435d392e2b9..8de40e852f4c 100644 --- a/lldb/include/lldb/Target/StopInfo.h +++ b/lldb/include/lldb/Target/StopInfo.h @@ -157,7 +157,9 @@ public: CreateStopReasonToTrace (Thread &thread); static lldb::StopInfoSP - CreateStopReasonWithPlan (lldb::ThreadPlanSP &plan, lldb::ValueObjectSP return_valobj_sp); + CreateStopReasonWithPlan (lldb::ThreadPlanSP &plan, + lldb::ValueObjectSP return_valobj_sp, + lldb::ClangExpressionVariableSP expression_variable_sp); static lldb::StopInfoSP CreateStopReasonWithException (Thread &thread, const char *description); @@ -168,6 +170,9 @@ public: static lldb::ValueObjectSP GetReturnValueObject (lldb::StopInfoSP &stop_info_sp); + static lldb::ClangExpressionVariableSP + GetExpressionVariable (lldb::StopInfoSP &stop_info_sp); + protected: // Perform any action that is associated with this stop. This is done as the // Event is removed from the event queue. ProcessEventData::DoOnRemoval does the job. diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index e40a0dfdaf89..cba09e164105 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -965,6 +965,17 @@ public: lldb::ValueObjectSP GetReturnValueObject (); + //------------------------------------------------------------------ + /// Gets the outer-most expression variable from the completed plans + /// + /// @return + /// A ClangExpressionVariableSP, either empty if there is no + /// plan completed an expression during the current stop + /// or the expression variable that was made for the completed expression. + //------------------------------------------------------------------ + lldb::ClangExpressionVariableSP + GetExpressionVariable (); + //------------------------------------------------------------------ /// Checks whether the given plan is in the completed plans for this /// stop. diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h index 3c83fd1b9630..85d11a099db8 100644 --- a/lldb/include/lldb/Target/ThreadPlan.h +++ b/lldb/include/lldb/Target/ThreadPlan.h @@ -501,11 +501,26 @@ public: return m_thread.GetStopInfo (); } + // If the completion of the thread plan stepped out of a function, the return value of the function + // might have been captured by the thread plan (currently only ThreadPlanStepOut does this.) + // If so, the ReturnValueObject can be retrieved from here. + virtual lldb::ValueObjectSP GetReturnValueObject () { return lldb::ValueObjectSP(); } + + // If the thread plan managing the evaluation of a user expression lives longer than the command + // that instigated the expression (generally because the expression evaluation hit a breakpoint, and + // the user regained control at that point) a subsequent process control command step/continue/etc. might + // complete the expression evaluations. If so, the result of the expression evaluation will show up here. + + virtual lldb::ClangExpressionVariableSP + GetExpressionVariable () + { + return lldb::ClangExpressionVariableSP(); + } // If a thread plan stores the state before it was run, then you might // want to restore the state when it is done. This will do that job. diff --git a/lldb/include/lldb/Target/ThreadPlanCallUserExpression.h b/lldb/include/lldb/Target/ThreadPlanCallUserExpression.h index 5eb7cc1cd452..67ac642de7bd 100644 --- a/lldb/include/lldb/Target/ThreadPlanCallUserExpression.h +++ b/lldb/include/lldb/Target/ThreadPlanCallUserExpression.h @@ -40,21 +40,35 @@ public: GetDescription (Stream *s, lldb::DescriptionLevel level); virtual void - WillPop () - { - ThreadPlanCallFunction::WillPop(); - if (m_user_expression_sp) - m_user_expression_sp.reset(); - } + WillPop (); virtual lldb::StopInfoSP GetRealStopInfo(); + virtual bool + MischiefManaged (); + + void + TransferExpressionOwnership () + { + m_manage_materialization = true; + } + + virtual lldb::ClangExpressionVariableSP + GetExpressionVariable () + { + return m_result_var_sp; + } + protected: private: ClangUserExpression::ClangUserExpressionSP m_user_expression_sp; // This is currently just used to ensure the // User expression the initiated this ThreadPlan // lives as long as the thread plan does. + bool m_manage_materialization = false; + lldb::ClangExpressionVariableSP m_result_var_sp; // If we are left to manage the materialization, + // then stuff the result expression variable here. + DISALLOW_COPY_AND_ASSIGN (ThreadPlanCallUserExpression); }; diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 0def3680a62b..5ce2eae07350 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -111,6 +111,7 @@ g_language_enumerators[] = "{, ${thread.info.trace_messages} messages}" \ "{, stop reason = ${thread.stop-reason}}"\ "{\\nReturn value: ${thread.return-value}}"\ + "{\\nCompleted expression: ${thread.completed-expression}}"\ "\\n" #define DEFAULT_FRAME_FORMAT "frame #${frame.index}: ${frame.pc}"\ @@ -2075,6 +2076,19 @@ FormatPromptRecurse } } } + else if (IsToken (var_name_begin, "completed-expression}")) + { + StopInfoSP stop_info_sp = thread->GetStopInfo (); + if (stop_info_sp && stop_info_sp->IsValid()) + { + ClangExpressionVariableSP expression_var_sp = StopInfo::GetExpressionVariable (stop_info_sp); + if (expression_var_sp && expression_var_sp->GetValueObject()) + { + expression_var_sp->GetValueObject()->Dump(s); + var_success = true; + } + } + } else if (IsToken (var_name_begin, "script:")) { var_name_begin += ::strlen("script:"); diff --git a/lldb/source/Expression/ClangUserExpression.cpp b/lldb/source/Expression/ClangUserExpression.cpp index 8f580024821b..62f2869dd4b8 100644 --- a/lldb/source/Expression/ClangUserExpression.cpp +++ b/lldb/source/Expression/ClangUserExpression.cpp @@ -884,17 +884,19 @@ ClangUserExpression::Execute (Stream &error_stream, } args.push_back(struct_address); - - lldb::ThreadPlanSP call_plan_sp(new ThreadPlanCallUserExpression (exe_ctx.GetThreadRef(), - wrapper_address, - args, - options, - shared_ptr_to_me)); + + ThreadPlanCallUserExpression *user_expression_plan = + new ThreadPlanCallUserExpression (exe_ctx.GetThreadRef(), + wrapper_address, + args, + options, + shared_ptr_to_me); + lldb::ThreadPlanSP call_plan_sp(user_expression_plan); if (!call_plan_sp || !call_plan_sp->ValidatePlan (&error_stream)) return lldb::eExpressionSetupError; - lldb::addr_t function_stack_pointer = static_cast(call_plan_sp.get())->GetFunctionStackPointer(); + lldb::addr_t function_stack_pointer = user_expression_plan->GetFunctionStackPointer(); function_stack_bottom = function_stack_pointer - Host::GetPageSize(); function_stack_top = function_stack_pointer; @@ -935,8 +937,12 @@ ClangUserExpression::Execute (Stream &error_stream, || (execution_result == lldb::eExpressionHitBreakpoint && options.DoesIgnoreBreakpoints())) error_stream.PutCString ("\nThe process has been returned to the state before expression evaluation."); else + { + if (execution_result == lldb::eExpressionHitBreakpoint) + user_expression_plan->TransferExpressionOwnership(); error_stream.PutCString ("\nThe process has been left at the point where it was interrupted, " "use \"thread return -x\" to return to the state before expression evaluation."); + } return execution_result; } diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index 7241f6b3c757..931cc0ae8f25 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -997,10 +997,11 @@ class StopInfoThreadPlan : public StopInfo { public: - StopInfoThreadPlan (ThreadPlanSP &plan_sp, ValueObjectSP &return_valobj_sp) : + StopInfoThreadPlan (ThreadPlanSP &plan_sp, ValueObjectSP &return_valobj_sp, ClangExpressionVariableSP &expression_variable_sp) : StopInfo (plan_sp->GetThread(), LLDB_INVALID_UID), m_plan_sp (plan_sp), - m_return_valobj_sp (return_valobj_sp) + m_return_valobj_sp (return_valobj_sp), + m_expression_variable_sp (expression_variable_sp) { } @@ -1032,6 +1033,12 @@ public: return m_return_valobj_sp; } + ClangExpressionVariableSP + GetExpressionVariable() + { + return m_expression_variable_sp; + } + protected: virtual bool ShouldStop (Event *event_ptr) @@ -1045,6 +1052,7 @@ protected: private: ThreadPlanSP m_plan_sp; ValueObjectSP m_return_valobj_sp; + ClangExpressionVariableSP m_expression_variable_sp; }; class StopInfoExec : public StopInfo @@ -1123,9 +1131,11 @@ StopInfo::CreateStopReasonToTrace (Thread &thread) } StopInfoSP -StopInfo::CreateStopReasonWithPlan (ThreadPlanSP &plan_sp, ValueObjectSP return_valobj_sp) +StopInfo::CreateStopReasonWithPlan (ThreadPlanSP &plan_sp, + ValueObjectSP return_valobj_sp, + ClangExpressionVariableSP expression_variable_sp) { - return StopInfoSP (new StopInfoThreadPlan (plan_sp, return_valobj_sp)); + return StopInfoSP (new StopInfoThreadPlan (plan_sp, return_valobj_sp, expression_variable_sp)); } StopInfoSP @@ -1151,3 +1161,15 @@ StopInfo::GetReturnValueObject(StopInfoSP &stop_info_sp) else return ValueObjectSP(); } + +ClangExpressionVariableSP +StopInfo::GetExpressionVariable(StopInfoSP &stop_info_sp) +{ + if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonPlanComplete) + { + StopInfoThreadPlan *plan_stop_info = static_cast(stop_info_sp.get()); + return plan_stop_info->GetExpressionVariable(); + } + else + return ClangExpressionVariableSP(); +} diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 89d4359ca6fd..3aa7fbcaad9d 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -420,7 +420,7 @@ Thread::GetStopInfo () const uint32_t stop_id = process_sp ? process_sp->GetStopID() : UINT32_MAX; if (plan_sp && plan_sp->PlanSucceeded()) { - return StopInfo::CreateStopReasonWithPlan (plan_sp, GetReturnValueObject()); + return StopInfo::CreateStopReasonWithPlan (plan_sp, GetReturnValueObject(), GetExpressionVariable()); } else { @@ -1184,6 +1184,22 @@ Thread::GetReturnValueObject () return ValueObjectSP(); } +ClangExpressionVariableSP +Thread::GetExpressionVariable () +{ + if (!m_completed_plan_stack.empty()) + { + for (int i = m_completed_plan_stack.size() - 1; i >= 0; i--) + { + ClangExpressionVariableSP expression_variable_sp; + expression_variable_sp = m_completed_plan_stack[i]->GetExpressionVariable(); + if (expression_variable_sp) + return expression_variable_sp; + } + } + return ClangExpressionVariableSP(); +} + bool Thread::IsThreadPlanDone (ThreadPlan *plan) { diff --git a/lldb/source/Target/ThreadPlanCallUserExpression.cpp b/lldb/source/Target/ThreadPlanCallUserExpression.cpp index 55aa26f4dd92..a7d627f25795 100644 --- a/lldb/source/Target/ThreadPlanCallUserExpression.cpp +++ b/lldb/source/Target/ThreadPlanCallUserExpression.cpp @@ -56,7 +56,54 @@ ThreadPlanCallUserExpression::~ThreadPlanCallUserExpression () void ThreadPlanCallUserExpression::GetDescription (Stream *s, lldb::DescriptionLevel level) { - ThreadPlanCallFunction::GetDescription (s, level); + if (level == eDescriptionLevelBrief) + s->Printf("User Expression thread plan"); + else + ThreadPlanCallFunction::GetDescription (s, level); +} + +void +ThreadPlanCallUserExpression::WillPop () +{ + ThreadPlanCallFunction::WillPop(); + if (m_user_expression_sp) + m_user_expression_sp.reset(); +} + +bool +ThreadPlanCallUserExpression::MischiefManaged () +{ + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); + + if (IsPlanComplete()) + { + if (log) + log->Printf("ThreadPlanCallFunction(%p): Completed call function plan.", + static_cast(this)); + + if (m_manage_materialization && PlanSucceeded() && m_user_expression_sp) + { + lldb::addr_t function_stack_top; + lldb::addr_t function_stack_bottom; + lldb::addr_t function_stack_pointer = GetFunctionStackPointer(); + + function_stack_bottom = function_stack_pointer - Host::GetPageSize(); + function_stack_top = function_stack_pointer; + + StreamString error_stream; + + ExecutionContext exe_ctx(GetThread()); + + m_user_expression_sp->FinalizeJITExecution(error_stream, exe_ctx, m_result_var_sp, function_stack_bottom, function_stack_top); + } + + ThreadPlan::MischiefManaged (); + return true; + } + else + { + return false; + } } StopInfoSP diff --git a/lldb/test/expression_command/call-function/TestCallStopAndContinue.py b/lldb/test/expression_command/call-function/TestCallStopAndContinue.py new file mode 100644 index 000000000000..3398c94e8be8 --- /dev/null +++ b/lldb/test/expression_command/call-function/TestCallStopAndContinue.py @@ -0,0 +1,59 @@ +""" +Test calling a function, stopping in the call, continue and gather the result on stop. +""" + +import unittest2 +import lldb +import lldbutil +from lldbtest import * + +class ExprCommandCallStopContinueTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number to break for main.c. + self.line = line_number('main.cpp', + '// Please test these expressions while stopped at this line:') + self.func_line = line_number ('main.cpp', + '{ 5, "five" }') + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @dsym_test + def test_with_dsym(self): + """Test gathering result from interrupted function call.""" + self.buildDsym() + self.call_function() + + @dwarf_test + def test_with_dwarf(self): + """Test gathering result from interrupted function call.""" + self.buildDwarf() + self.call_function() + + def call_function(self): + """Test gathering result from interrupted function call.""" + self.runCmd("file a.out", CURRENT_EXECUTABLE_SET) + + # Some versions of GCC encode two locations for the 'return' statement in main.cpp + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.line, num_expected_locations=-1, loc_exact=True) + + self.runCmd("run", RUN_SUCCEEDED) + + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.func_line, num_expected_locations=-1, loc_exact=True) + + self.expect("expr -i false -- returnsFive()", error=True, + substrs = ['Execution was interrupted, reason: breakpoint']) + + self.runCmd("continue", "Continue completed") + self.expect ("thread list", + substrs = ['stop reason = User Expression thread plan', + r'Completed expression: (Five) $0 = (number = 5, name = "five")']) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/expression_command/call-function/main.cpp b/lldb/test/expression_command/call-function/main.cpp index 09c8633114b3..ca43c2eabe5a 100644 --- a/lldb/test/expression_command/call-function/main.cpp +++ b/lldb/test/expression_command/call-function/main.cpp @@ -1,11 +1,25 @@ #include #include +struct Five +{ + int number; + const char *name; +}; + +Five +returnsFive() +{ + Five my_five = { 5, "five" }; + return my_five; +} + int main (int argc, char const *argv[]) { std::string str = "Hello world"; std::cout << str << std::endl; std::cout << str.c_str() << std::endl; + Five main_five = returnsFive(); #if 0 print str print str.c_str()