jingham created this revision. jingham added a reviewer: JDevlieghere. Herald added subscribers: lldb-commits, dang. Herald added a project: LLDB. jingham requested review of this revision.
Previously, you could only have a list of command-line commands as the reaction to a stop hook. This patch adds the ability to define the stop hooks directly in Python, which allows you to do logging and add logic to the stop hook more easily. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D88123 Files: lldb/bindings/python/python-swigsafecast.swig lldb/bindings/python/python-wrapper.swig lldb/include/lldb/Interpreter/ScriptInterpreter.h lldb/include/lldb/Symbol/SymbolContext.h lldb/include/lldb/Target/Target.h lldb/source/Commands/CommandObjectTarget.cpp lldb/source/Commands/Options.td lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h lldb/source/Symbol/SymbolContext.cpp lldb/source/Target/Target.cpp lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py lldb/test/API/commands/target/stop-hooks/TestStopHooks.py lldb/test/API/commands/target/stop-hooks/main.c lldb/test/API/commands/target/stop-hooks/stop_hook.py lldb/test/Shell/Commands/Inputs/stop_hook.py lldb/test/Shell/Commands/command-stop-hook-output.test lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
Index: lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp =================================================================== --- lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -254,3 +254,17 @@ const lldb::TargetSP &target_sp) { return nullptr; } + +extern "C" void *LLDBSwigPythonCreateScriptedStopHook( + lldb::TargetSP target_sp, const char *python_class_name, + const char *session_dictionary_name, + lldb_private::StructuredDataImpl *args_impl, Status &error) { + return nullptr; +} + +extern "C" bool +LLDBSwigPythonStopHookCallHandleStop(void *implementor, + lldb::ExecutionContextRefSP exc_ctx_sp, + lldb::StreamSP stream) { + return false; +} Index: lldb/test/Shell/Commands/command-stop-hook-output.test =================================================================== --- /dev/null +++ lldb/test/Shell/Commands/command-stop-hook-output.test @@ -0,0 +1,18 @@ +# RUN: %clang_host -g %S/Inputs/main.c -o %t +# RUN: %lldb %t -O 'command script import %S/Inputs/stop_hook.py' -s %s -o exit | FileCheck %s + +b main +# CHECK-LABEL: b main +# CHECK: Breakpoint 1: where = {{.*}}`main + +target stop-hook add -P stop_hook.stop_handler +# CHECK-LABEL: target stop-hook add -P stop_hook.stop_handler +# CHECK: Stop hook #1 added. + +run +# CHECK-LABEL: run +# CHECK: I did indeed run +# CHECK: Process {{.*}} stopped +# CHECK: stop reason = breakpoint 1 +# CHECK: frame #0: {{.*}}`main at main.c + Index: lldb/test/Shell/Commands/Inputs/stop_hook.py =================================================================== --- /dev/null +++ lldb/test/Shell/Commands/Inputs/stop_hook.py @@ -0,0 +1,10 @@ +import lldb + +class stop_handler: + def __init__(self, target, extra_args, dict): + self.extra_args = extra_args + self.target = target + + def handle_stop(self, exe_ctx, stream): + stream.Print("I did indeed run\n") + return True Index: lldb/test/API/commands/target/stop-hooks/stop_hook.py =================================================================== --- /dev/null +++ lldb/test/API/commands/target/stop-hooks/stop_hook.py @@ -0,0 +1,34 @@ +import lldb + +class stop_handler: + def __init__(self, target, extra_args, dict): + self.extra_args = extra_args + self.target = target + self.counter = 0 + ret_val = self.extra_args.GetValueForKey("return_false") + if ret_val: + self.ret_val = False + else: + self.ret_val = True + + def handle_stop(self, exe_ctx, stream): + self.counter += 1 + stream.Print("I have stopped %d times.\n"%(self.counter)) + increment = 1 + value = self.extra_args.GetValueForKey("increment") + if value: + incr_as_str = value.GetStringValue(100) + increment = int(incr_as_str) + else: + stream.Print("Could not find increment in extra_args\n") + frame = exe_ctx.GetFrame() + expression = "g_var += %d"%(increment) + expr_result = frame.EvaluateExpression(expression) + if not expr_result.GetError().Success(): + stream.Print("Error running expression: %s"%(expr_result.GetError().GetCString())) + value = exe_ctx.target.FindFirstGlobalVariable("g_var") + if not value.IsValid(): + stream.Print("Didn't get a valid value for g_var.") + else: + int_val = value.GetValueAsUnsigned() + return self.ret_val Index: lldb/test/API/commands/target/stop-hooks/main.c =================================================================== --- lldb/test/API/commands/target/stop-hooks/main.c +++ lldb/test/API/commands/target/stop-hooks/main.c @@ -10,5 +10,6 @@ int main() { - return step_out_of_me(); + int result = step_out_of_me(); // Stop here first + return result; } Index: lldb/test/API/commands/target/stop-hooks/TestStopHooks.py =================================================================== --- lldb/test/API/commands/target/stop-hooks/TestStopHooks.py +++ lldb/test/API/commands/target/stop-hooks/TestStopHooks.py @@ -1,5 +1,5 @@ """ -Test that stop hooks trigger on "step-out" +Test stop hook functionality """ @@ -18,10 +18,15 @@ # each debug info format. NO_DEBUG_INFO_TESTCASE = True - def test_stop_hooks_step_out(self): - """Test that stop hooks fire on step-out.""" + def setUp(self): + TestBase.setUp(self) self.build() self.main_source_file = lldb.SBFileSpec("main.c") + full_path = os.path.join(self.getSourceDir(), "main.c") + self.main_start_line = line_number(full_path, "main()") + + def test_stop_hooks_step_out(self): + """Test that stop hooks fire on step-out.""" self.step_out_test() def step_out_test(self): @@ -37,4 +42,3 @@ self.assertTrue(var.IsValid()) self.assertEqual(var.GetValueAsUnsigned(), 1, "Updated g_var") - Index: lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py =================================================================== --- /dev/null +++ lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py @@ -0,0 +1,131 @@ +""" +Test stop hook functionality +""" + + + +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.lldbtest import * + + +class TestStopHooks(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 setUp(self): + TestBase.setUp(self) + self.build() + self.main_source_file = lldb.SBFileSpec("main.c") + full_path = os.path.join(self.getSourceDir(), "main.c") + self.main_start_line = line_number(full_path, "main()") + + def not_test_stop_hooks_scripted(self): + """Test that a scripted stop hook works with no specifiers""" + self.stop_hooks_scripted(5) + + def not_test_stop_hooks_scripted_right_func(self): + """Test that a scripted stop hook fires when there is a function match""" + self.stop_hooks_scripted(5, "-n step_out_of_me") + + def not_test_stop_hooks_scripted_wrong_func(self): + """Test that a scripted stop hook doesn't fire when the function does not match""" + self.stop_hooks_scripted(0, "-n main") + + def not_test_stop_hooks_scripted_right_lines(self): + """Test that a scripted stop hook fires when there is a function match""" + self.stop_hooks_scripted(5, "-f main.c -l 1 -e %d"%(self.main_start_line)) + + def not_test_stop_hooks_scripted_wrong_lines(self): + """Test that a scripted stop hook doesn't fire when the function does not match""" + self.stop_hooks_scripted(0, "-f main.c -l %d -e 100"%(self.main_start_line)) + + def not_test_stop_hooks_scripted_auto_continue(self): + """Test that the --auto-continue flag works""" + self.do_test_auto_continue(False) + + def test_stop_hooks_scripted_return_false(self): + """Test that the returning False from a stop hook works""" + self.do_test_auto_continue(True) + + def do_test_auto_continue(self, return_true): + """Test that auto-continue works.""" + # We set auto-continue to 1 but the stop hook only applies to step_out_of_me, + # so we should end up stopped in main, having run the expression only once. + self.script_setup() + + result = lldb.SBCommandReturnObject() + + if return_true: + command = "target stop-hook add -P stop_hook.stop_handler -k increment -v 5 -k return_false -v 1 -n step_out_of_me" + else: + command = "target stop-hook add -G 1 -P stop_hook.stop_handler -k increment -v 5 -n step_out_of_me" + + print("Running command: %s"%(command)) + + self.interp.HandleCommand(command, result) + self.assertTrue(result.Succeeded, "Set the target stop hook") + + # First run to main. If we go straight to the first stop hook hit, + # run_to_source_breakpoint will fail because we aren't at original breakpoint + + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + "Stop here first", self.main_source_file) + + # Now set the breakpoint on step_out_of_me, and make sure we run the + # expression, then continue back to main. + bkpt = target.BreakpointCreateBySourceRegex("Set a breakpoint here and step out", self.main_source_file) + self.assertTrue(bkpt.GetNumLocations() > 0, "Got breakpoints in step_out_of_me") + process.Continue() + + var = target.FindFirstGlobalVariable("g_var") + self.assertTrue(var.IsValid()) + self.assertEqual(var.GetValueAsUnsigned(), 5, "Updated g_var") + + func_name = process.GetSelectedThread().frames[0].GetFunctionName() + self.assertEqual("main", func_name, "Didn't stop at the expected function.") + + def script_setup(self): + self.interp = self.dbg.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + + # Bring in our script file: + script_name = os.path.join(self.getSourceDir(), "stop_hook.py") + command = "command script import " + script_name + self.interp.HandleCommand(command, result) + self.assertTrue(result.Succeeded(), "com scr imp failed: %s"%(result.GetError())) + + # set a breakpoint at the end of main to catch our auto-continue tests. + # Do it in the dummy target so it will get copied to our target even when + # we don't have a chance to stop. + dummy_target = self.dbg.GetDummyTarget() + dummy_target.BreakpointCreateBySourceRegex("return result", self.main_source_file) + + + def stop_hooks_scripted(self, g_var_value, specifier = None): + self.script_setup() + + result = lldb.SBCommandReturnObject() + + command = "target stop-hook add -P stop_hook.stop_handler -k increment -v 5 " + if specifier: + command += specifier + print("Running command: %s"%(command)) + + self.interp.HandleCommand(command, result) + self.assertTrue(result.Succeeded, "Set the target stop hook") + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + "Set a breakpoint here", self.main_source_file) + # At this point we've hit our stop hook so we should have run our expression, + # which increments g_var by the amount specified by the increment key's value. + while process.GetState() == lldb.eStateRunning: + continue + + var = target.FindFirstGlobalVariable("g_var") + self.assertTrue(var.IsValid()) + self.assertEqual(var.GetValueAsUnsigned(), g_var_value, "Updated g_var") Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -2484,13 +2484,24 @@ return m_clang_modules_decl_vendor_up.get(); } -Target::StopHookSP Target::CreateStopHook() { +Target::StopHookSP Target::CreateStopHook(bool use_commands) { lldb::user_id_t new_uid = ++m_stop_hook_next_id; - Target::StopHookSP stop_hook_sp(new StopHook(shared_from_this(), new_uid)); + Target::StopHookSP stop_hook_sp; + if (use_commands) + stop_hook_sp.reset(new StopHookCommandLine(shared_from_this(), new_uid)); + else + stop_hook_sp.reset(new StopHookScripted(shared_from_this(), new_uid)); m_stop_hooks[new_uid] = stop_hook_sp; return stop_hook_sp; } +void Target::UndoCreateStopHook(lldb::user_id_t user_id) { + if (!RemoveStopHookByID(user_id)) + return; + if (user_id == m_stop_hook_next_id) + m_stop_hook_next_id--; +} + bool Target::RemoveStopHookByID(lldb::user_id_t user_id) { size_t num_removed = m_stop_hooks.erase(user_id); return (num_removed != 0); @@ -2546,25 +2557,18 @@ if (m_stop_hooks.empty()) return; - StopHookCollection::iterator pos, end = m_stop_hooks.end(); - // If there aren't any active stop hooks, don't bother either. - // Also see if any of the active hooks want to auto-continue. bool any_active_hooks = false; - bool auto_continue = false; for (auto hook : m_stop_hooks) { if (hook.second->IsActive()) { any_active_hooks = true; - auto_continue |= hook.second->GetAutoContinue(); + break; } } if (!any_active_hooks) return; - CommandReturnObject result(m_debugger.GetUseColor()); - std::vector<ExecutionContext> exc_ctx_with_reasons; - std::vector<SymbolContext> sym_ctx_with_reasons; ThreadList &cur_threadlist = m_process_sp->GetThreadList(); size_t num_threads = cur_threadlist.GetSize(); @@ -2572,10 +2576,8 @@ lldb::ThreadSP cur_thread_sp = cur_threadlist.GetThreadAtIndex(i); if (cur_thread_sp->ThreadStoppedForAReason()) { lldb::StackFrameSP cur_frame_sp = cur_thread_sp->GetStackFrameAtIndex(0); - exc_ctx_with_reasons.push_back(ExecutionContext( - m_process_sp.get(), cur_thread_sp.get(), cur_frame_sp.get())); - sym_ctx_with_reasons.push_back( - cur_frame_sp->GetSymbolContext(eSymbolContextEverything)); + exc_ctx_with_reasons.emplace_back(m_process_sp.get(), cur_thread_sp.get(), + cur_frame_sp.get()); } } @@ -2584,91 +2586,86 @@ if (num_exe_ctx == 0) return; - result.SetImmediateOutputStream(m_debugger.GetAsyncOutputStream()); - result.SetImmediateErrorStream(m_debugger.GetAsyncErrorStream()); + StreamSP output_sp = m_debugger.GetAsyncOutputStream(); - bool keep_going = true; + bool auto_continue = false; bool hooks_ran = false; bool print_hook_header = (m_stop_hooks.size() != 1); bool print_thread_header = (num_exe_ctx != 1); - bool did_restart = false; + bool should_stop = false; + bool somebody_restarted = false; - for (pos = m_stop_hooks.begin(); keep_going && pos != end; pos++) { - // result.Clear(); - StopHookSP cur_hook_sp = (*pos).second; + for (auto stop_entry : m_stop_hooks) { + StopHookSP cur_hook_sp = stop_entry.second; if (!cur_hook_sp->IsActive()) continue; bool any_thread_matched = false; - for (size_t i = 0; keep_going && i < num_exe_ctx; i++) { - if ((cur_hook_sp->GetSpecifier() == nullptr || - cur_hook_sp->GetSpecifier()->SymbolContextMatches( - sym_ctx_with_reasons[i])) && - (cur_hook_sp->GetThreadSpecifier() == nullptr || - cur_hook_sp->GetThreadSpecifier()->ThreadPassesBasicTests( - exc_ctx_with_reasons[i].GetThreadRef()))) { - if (!hooks_ran) { - hooks_ran = true; - } - if (print_hook_header && !any_thread_matched) { - const char *cmd = - (cur_hook_sp->GetCommands().GetSize() == 1 - ? cur_hook_sp->GetCommands().GetStringAtIndex(0) - : nullptr); - if (cmd) - result.AppendMessageWithFormat("\n- Hook %" PRIu64 " (%s)\n", - cur_hook_sp->GetID(), cmd); - else - result.AppendMessageWithFormat("\n- Hook %" PRIu64 "\n", - cur_hook_sp->GetID()); - any_thread_matched = true; - } + for (auto exc_ctx : exc_ctx_with_reasons) { + // We detect somebody restarted in the stop-hook loop, and broke out of + // that loop back to here. So break out of here too. + if (somebody_restarted) + break; - if (print_thread_header) - result.AppendMessageWithFormat( - "-- Thread %d\n", - exc_ctx_with_reasons[i].GetThreadPtr()->GetIndexID()); - - CommandInterpreterRunOptions options; - options.SetStopOnContinue(true); - options.SetStopOnError(true); - options.SetEchoCommands(false); - options.SetPrintResults(true); - options.SetPrintErrors(true); - options.SetAddToHistory(false); - - // Force Async: - bool old_async = GetDebugger().GetAsyncExecution(); - GetDebugger().SetAsyncExecution(true); - GetDebugger().GetCommandInterpreter().HandleCommands( - cur_hook_sp->GetCommands(), &exc_ctx_with_reasons[i], options, - result); - GetDebugger().SetAsyncExecution(old_async); - // If the command started the target going again, we should bag out of - // running the stop hooks. - if ((result.GetStatus() == eReturnStatusSuccessContinuingNoResult) || - (result.GetStatus() == eReturnStatusSuccessContinuingResult)) { - // But only complain if there were more stop hooks to do: - StopHookCollection::iterator tmp = pos; - if (++tmp != end) - result.AppendMessageWithFormat( - "\nAborting stop hooks, hook %" PRIu64 - " set the program running.\n" - " Consider using '-G true' to make " - "stop hooks auto-continue.\n", - cur_hook_sp->GetID()); - keep_going = false; - did_restart = true; - } + if (!cur_hook_sp->ExecutionContextPasses(exc_ctx)) + continue; + + // We only consult the auto-continue for a stop hook if it matched the + // specifier. + auto_continue |= cur_hook_sp->GetAutoContinue(); + + if (!hooks_ran) + hooks_ran = true; + + if (print_hook_header && !any_thread_matched) { + StreamString s; + cur_hook_sp->GetDescription(&s, eDescriptionLevelBrief); + if (s.GetSize() != 0) + output_sp->Printf("\n- Hook %" PRIu64 " (%s)\n", cur_hook_sp->GetID(), + s.GetData()); + else + output_sp->Printf("\n- Hook %" PRIu64 "\n", cur_hook_sp->GetID()); + any_thread_matched = true; + } + + if (print_thread_header) + output_sp->Printf("-- Thread %d\n", + exc_ctx.GetThreadPtr()->GetIndexID()); + + bool this_should_stop = cur_hook_sp->HandleStop(exc_ctx, output_sp); + // If this hook is set to auto-continue that should override the + // HandleStop result... + if (cur_hook_sp->GetAutoContinue()) + this_should_stop = false; + + // If anybody wanted to stop, we should all stop. + if (!should_stop) + should_stop = this_should_stop; + + // We don't have a good way to prohibit people from restarting the target + // willy nilly in a stop hook. So see if the private state is running + // here and bag out if it is. + // FIXME: when we are doing non-stop mode for realz we'll have to instead + // track each thread, and only bag out if a thread is set running. + if (m_process_sp->GetPrivateState() != eStateStopped) { + output_sp->Printf("\nAborting stop hooks, hook %" PRIu64 + " set the program running.\n" + " Consider using '-G true' to make " + "stop hooks auto-continue.\n", + cur_hook_sp->GetID()); + somebody_restarted = true; + break; } } } + + output_sp->Flush(); + // Finally, if auto-continue was requested, do it now: - if (!did_restart && auto_continue) + // We only compute should_stop against the hook results if a hook got to run + // which is why we have to do this conjoint test. + if (!somebody_restarted && ((hooks_ran && !should_stop) || auto_continue)) m_process_sp->PrivateResume(); - - result.GetImmediateOutputStream()->Flush(); - result.GetImmediateErrorStream()->Flush(); } const TargetPropertiesSP &Target::GetGlobalProperties() { @@ -3128,20 +3125,17 @@ // Target::StopHook Target::StopHook::StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid) - : UserID(uid), m_target_sp(target_sp), m_commands(), m_specifier_sp(), + : UserID(uid), m_target_sp(target_sp), m_specifier_sp(), m_thread_spec_up() {} Target::StopHook::StopHook(const StopHook &rhs) : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), - m_commands(rhs.m_commands), m_specifier_sp(rhs.m_specifier_sp), - m_thread_spec_up(), m_active(rhs.m_active), - m_auto_continue(rhs.m_auto_continue) { + m_specifier_sp(rhs.m_specifier_sp), m_thread_spec_up(), + m_active(rhs.m_active), m_auto_continue(rhs.m_auto_continue) { if (rhs.m_thread_spec_up) m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up); } -Target::StopHook::~StopHook() = default; - void Target::StopHook::SetSpecifier(SymbolContextSpecifier *specifier) { m_specifier_sp.reset(specifier); } @@ -3150,8 +3144,31 @@ m_thread_spec_up.reset(specifier); } +bool Target::StopHook::ExecutionContextPasses(const ExecutionContext &exc_ctx) { + SymbolContextSpecifier *specifier = GetSpecifier(); + if (!specifier) + return true; + + bool will_run = true; + if (exc_ctx.GetFramePtr()) + will_run = GetSpecifier()->SymbolContextMatches( + exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything)); + if (will_run && GetThreadSpecifier() != nullptr) + will_run = + GetThreadSpecifier()->ThreadPassesBasicTests(exc_ctx.GetThreadRef()); + + return will_run; +} + void Target::StopHook::GetDescription(Stream *s, lldb::DescriptionLevel level) const { + + // For brief descriptions, only print the subclass description: + if (level == eDescriptionLevelBrief) { + GetSubclassDescription(s, level); + return; + } + unsigned indent_level = s->GetIndentLevel(); s->SetIndentLevel(indent_level + 2); @@ -3182,15 +3199,147 @@ s->PutCString("\n"); s->SetIndentLevel(indent_level + 2); } + GetSubclassDescription(s, level); +} +void Target::StopHookCommandLine::GetSubclassDescription( + Stream *s, lldb::DescriptionLevel level) const { + // The brief description just prints the first command. + if (level == eDescriptionLevelBrief) { + if (m_commands.GetSize() == 1) + s->PutCString(m_commands.GetStringAtIndex(0)); + return; + } s->Indent("Commands: \n"); - s->SetIndentLevel(indent_level + 4); + s->SetIndentLevel(s->GetIndentLevel() + 4); uint32_t num_commands = m_commands.GetSize(); for (uint32_t i = 0; i < num_commands; i++) { s->Indent(m_commands.GetStringAtIndex(i)); s->PutCString("\n"); } - s->SetIndentLevel(indent_level); + s->SetIndentLevel(s->GetIndentLevel() - 4); +} + +// Target::StopHookCommandLine +void Target::StopHookCommandLine::SetActionFromString(std::string &string) { + GetCommandPointer()->SplitIntoLines(string); +} + +void Target::StopHookCommandLine::SetActionFromStrings( + std::vector<std::string> &strings) { + for (auto string : strings) + GetCommandPointer()->AppendString(string.c_str()); +} + +bool Target::StopHookCommandLine::HandleStop(ExecutionContext &exc_ctx, + StreamSP output_sp) { + assert(exc_ctx.GetTargetPtr() && "Can't call PerformAction on a context " + "with no target"); + + if (!m_commands.GetSize()) + return true; + + CommandReturnObject result(false); + result.SetImmediateOutputStream(output_sp); + Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger(); + CommandInterpreterRunOptions options; + options.SetStopOnContinue(true); + options.SetStopOnError(true); + options.SetEchoCommands(false); + options.SetPrintResults(true); + options.SetPrintErrors(true); + options.SetAddToHistory(false); + + // Force Async: + bool old_async = debugger.GetAsyncExecution(); + debugger.SetAsyncExecution(true); + debugger.GetCommandInterpreter().HandleCommands(GetCommands(), &exc_ctx, + options, result); + debugger.SetAsyncExecution(old_async); + + return true; +} + +// Target::StopHookScripted +Status Target::StopHookScripted::SetScriptCallback( + std::string class_name, StructuredData::ObjectSP extra_args_sp) { + Status error; + m_class_name = class_name; + + m_extra_args = new StructuredDataImpl(); + + if (extra_args_sp) + m_extra_args->SetObjectSP(extra_args_sp); + + ScriptInterpreter *script_interp = + GetTarget()->GetDebugger().GetScriptInterpreter(); + if (!script_interp) { + error.SetErrorString("No script interpreter installed."); + return error; + } + + m_implementation_sp = script_interp->CreateScriptedStopHook( + GetTarget(), m_class_name.c_str(), m_extra_args, error); + + return error; +} + +bool Target::StopHookScripted::HandleStop(ExecutionContext &exc_ctx, + StreamSP output_sp) { + assert(exc_ctx.GetTargetPtr() && "Can't call HandleStop on a context " + "with no target"); + + ScriptInterpreter *script_interp = + GetTarget()->GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return true; + + bool should_stop = script_interp->ScriptedStopHookHandleStop( + m_implementation_sp, exc_ctx, output_sp); + + return should_stop; +} + +void Target::StopHookScripted::GetSubclassDescription( + Stream *s, lldb::DescriptionLevel level) const { + if (level == eDescriptionLevelBrief) { + s->PutCString(m_class_name); + return; + } + s->Indent("Class:"); + s->Printf("%s\n", m_class_name.c_str()); + + // Now print the extra args: + // FIXME: We should use StructuredData.GetDescription on the m_extra_args + // but that seems to rely on some printing plugin that doesn't exist. + if (!m_extra_args->IsValid()) + return; + StructuredData::ObjectSP object_sp = m_extra_args->GetObjectSP(); + if (!object_sp || !object_sp->IsValid()) + return; + + StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary(); + if (!as_dict || !as_dict->IsValid()) + return; + + uint32_t num_keys = as_dict->GetSize(); + if (num_keys == 0) + return; + + s->Indent("Args:\n"); + s->SetIndentLevel(s->GetIndentLevel() + 4); + + auto print_one_element = [&s](ConstString key, + StructuredData::Object *object) { + s->Indent(); + s->Printf("%s : %s\n", key.GetCString(), + object->GetStringValue().str().c_str()); + return true; + }; + + as_dict->ForEach(print_one_element); + + s->SetIndentLevel(s->GetIndentLevel() - 4); } static constexpr OptionEnumValueElement g_dynamic_value_types[] = { Index: lldb/source/Symbol/SymbolContext.cpp =================================================================== --- lldb/source/Symbol/SymbolContext.cpp +++ lldb/source/Symbol/SymbolContext.cpp @@ -1010,11 +1010,15 @@ m_type = eNothingSpecified; } -bool SymbolContextSpecifier::SymbolContextMatches(SymbolContext &sc) { +bool SymbolContextSpecifier::SymbolContextMatches(const SymbolContext &sc) { if (m_type == eNothingSpecified) return true; - if (m_target_sp.get() != sc.target_sp.get()) + // Only compare targets if this specifier has one and it's not the Dummy + // target. Otherwise if a specifier gets made in the dummy target and + // copied over we'll artificially fail the comparision. + if (m_target_sp && !m_target_sp->IsDummyTarget() && + m_target_sp != sc.target_sp) return false; if (m_type & eModuleSpecified) { Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -105,6 +105,14 @@ lldb::SearchDepth ScriptedBreakpointResolverSearchDepth( StructuredData::GenericSP implementor_sp) override; + StructuredData::GenericSP + CreateScriptedStopHook(lldb::TargetSP target_sp, const char *class_name, + StructuredDataImpl *args_data, Status &error) override; + + bool ScriptedStopHookHandleStop(StructuredData::GenericSP implementor_sp, + ExecutionContext &exc_ctx, + lldb::StreamSP stream_sp) override; + StructuredData::GenericSP CreateFrameRecognizer(const char *class_name) override; Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -127,6 +127,16 @@ LLDBSwigPythonCallBreakpointResolver(void *implementor, const char *method_name, lldb_private::SymbolContext *sym_ctx); +extern "C" void *LLDBSwigPythonCreateScriptedStopHook( + TargetSP target_sp, const char *python_class_name, + const char *session_dictionary_name, lldb_private::StructuredDataImpl *args, + lldb_private::Status &error); + +extern "C" unsigned int +LLDBSwigPythonStopHookCallHandleStop(void *implementor, + lldb::ExecutionContextRefSP exc_ctx, + lldb::StreamSP stream); + extern "C" size_t LLDBSwigPython_CalculateNumChildren(void *implementor, uint32_t max); @@ -1979,6 +1989,60 @@ return lldb::eSearchDepthModule; } +StructuredData::GenericSP ScriptInterpreterPythonImpl::CreateScriptedStopHook( + TargetSP target_sp, const char *class_name, StructuredDataImpl *args_data, + Status &error) { + + if (!target_sp) { + error.SetErrorString("No target for scripted stop-hook."); + return StructuredData::GenericSP(); + } + + if (class_name == nullptr || class_name[0] == '\0') { + error.SetErrorString("No class name for scripted stop-hook."); + return StructuredData::GenericSP(); + } + + ScriptInterpreter *script_interpreter = m_debugger.GetScriptInterpreter(); + ScriptInterpreterPythonImpl *python_interpreter = + static_cast<ScriptInterpreterPythonImpl *>(script_interpreter); + + if (!script_interpreter) { + error.SetErrorString("No script interpreter for scripted stop-hook."); + return StructuredData::GenericSP(); + } + + void *ret_val; + + { + Locker py_lock(this, + Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN); + + ret_val = LLDBSwigPythonCreateScriptedStopHook( + target_sp, class_name, python_interpreter->m_dictionary_name.c_str(), + args_data, error); + } + + return StructuredData::GenericSP(new StructuredPythonObject(ret_val)); +} + +bool ScriptInterpreterPythonImpl::ScriptedStopHookHandleStop( + StructuredData::GenericSP implementor_sp, ExecutionContext &exc_ctx, + lldb::StreamSP stream_sp) { + assert(implementor_sp && + "can't call a stop hook with an invalid implementor"); + assert(stream_sp && "can't call a stop hook with an invalid stream"); + + Locker py_lock(this, + Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN); + + lldb::ExecutionContextRefSP exc_ctx_ref_sp(new ExecutionContextRef(exc_ctx)); + + bool ret_val = LLDBSwigPythonStopHookCallHandleStop( + implementor_sp->GetValue(), exc_ctx_ref_sp, stream_sp); + return ret_val; +} + StructuredData::ObjectSP ScriptInterpreterPythonImpl::LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) { Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -876,7 +876,7 @@ } let Command = "target stop hook add" in { - def target_stop_hook_add_one_liner : Option<"one-liner", "o">, + def target_stop_hook_add_one_liner : Option<"one-liner", "o">, GroupRange<1,3>, Arg<"OneLiner">, Desc<"Add a command for the stop hook. Can be specified " "more than once, and commands will be run in the order they appear.">; def target_stop_hook_add_shlib : Option<"shlib", "s">, Arg<"ShlibName">, @@ -894,19 +894,19 @@ def target_stop_hook_add_queue_name : Option<"queue-name", "q">, Arg<"QueueName">, Desc<"The stop hook is run only for threads in the queue " "whose name is given by this argument.">; - def target_stop_hook_add_file : Option<"file", "f">, Group<1>, + def target_stop_hook_add_file : Option<"file", "f">, Groups<[1,4]>, Arg<"Filename">, Desc<"Specify the source file within which the stop-hook " "is to be run.">, Completion<"SourceFile">; - def target_stop_hook_add_start_line : Option<"start-line", "l">, Group<1>, + def target_stop_hook_add_start_line : Option<"start-line", "l">, Groups<[1,4]>, Arg<"LineNum">, Desc<"Set the start of the line range for which the " "stop-hook is to be run.">; - def target_stop_hook_add_end_line : Option<"end-line", "e">, Group<1>, + def target_stop_hook_add_end_line : Option<"end-line", "e">, Groups<[1,4]>, Arg<"LineNum">, Desc<"Set the end of the line range for which the stop-hook" " is to be run.">; - def target_stop_hook_add_classname : Option<"classname", "c">, Group<2>, + def target_stop_hook_add_classname : Option<"classname", "c">, Groups<[2,5]>, Arg<"ClassName">, Desc<"Specify the class within which the stop-hook is to be run.">; - def target_stop_hook_add_name : Option<"name", "n">, Group<3>, + def target_stop_hook_add_name : Option<"name", "n">, Groups<[3,6]>, Arg<"FunctionName">, Desc<"Set the function name within which the stop hook" " will be run.">, Completion<"Symbol">; def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">, Index: lldb/source/Commands/CommandObjectTarget.cpp =================================================================== --- lldb/source/Commands/CommandObjectTarget.cpp +++ lldb/source/Commands/CommandObjectTarget.cpp @@ -24,6 +24,7 @@ #include "lldb/Interpreter/OptionGroupFile.h" #include "lldb/Interpreter/OptionGroupFormat.h" #include "lldb/Interpreter/OptionGroupPlatform.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" #include "lldb/Interpreter/OptionGroupString.h" #include "lldb/Interpreter/OptionGroupUInt64.h" #include "lldb/Interpreter/OptionGroupUUID.h" @@ -4442,10 +4443,10 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed, public IOHandlerDelegateMultiline { public: - class CommandOptions : public Options { + class CommandOptions : public OptionGroup { public: CommandOptions() - : Options(), m_line_start(0), m_line_end(UINT_MAX), + : OptionGroup(), m_line_start(0), m_line_end(UINT_MAX), m_func_name_type_mask(eFunctionNameTypeAuto), m_sym_ctx_specified(false), m_thread_specified(false), m_use_one_liner(false), m_one_liner() {} @@ -4459,7 +4460,8 @@ Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override { Status error; - const int short_option = m_getopt_table[option_idx].val; + const int short_option = + g_target_stop_hook_add_options[option_idx].short_option; switch (short_option) { case 'c': @@ -4589,20 +4591,75 @@ // Instance variables to hold the values for one_liner options. bool m_use_one_liner; std::vector<std::string> m_one_liner; + bool m_auto_continue; }; CommandObjectTargetStopHookAdd(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "target stop-hook add", - "Add a hook to be executed when the target stops.", + "Add a hook to be executed when the target stops." + "The hook can either be a list of commands or an " + "appropriately defined Python class. You can also " + "add filters so the hook only runs a certain stop " + "points.", "target stop-hook add"), IOHandlerDelegateMultiline("DONE", IOHandlerDelegate::Completion::LLDBCommand), - m_options() {} + m_options(), m_python_class_options("scripted stop-hook", true, 'P') { + SetHelpLong( + R"( +Command Based stop-hooks: +------------------------- + Stop hooks can run a list of lldb commands by providing one or more + --one-line-command options. The commands will get run in the order they are + added. Or you can provide no commands, in which case you will enter a + command editor where you can enter the commands to be run. + +Python Based Stop Hooks: +------------------------ + Stop hooks can be implemented with a suitably defined Python class, whose name + is passed in the --python-class option. + + When the stop hook is added, the class is initialized by calling: + + def__init__(self, target, extra_args, dict): + + target: The target that the stop hook is being added to. + extra_args: An SBStructuredData Dictionary filled with the -key -value + option pairs passed to the command. + dict: An implementation detail provided by lldb. + + Then when the stop-hook triggers, lldb will run the 'handle_stop' method. + The method has the signature: + + def handle_stop(self, exe_ctx, stream): + + exe_ctx: An SBExecutionContext for the thread that has stopped. + stream: An SBStream, anything written to this stream will be printed in the + the stop message when the process stops. + + Return Value: The method returns "should_stop". If should_stop is false + from all the stop hook executions on threads that stopped + with a reason, then the process will continue. Note that this + will happen only after all the stop hooks are run. + +Filter Options: +--------------- + Stop hooks can be set to always run, or to only run when the stopped thread + matches the filter options passed on the command line. The available filter + options include a shared library or a thread or queue specification, + a line range in a source file, a function name or a class name. + )"); + m_all_options.Append(&m_python_class_options, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2, + LLDB_OPT_SET_FROM_TO(4, 6)); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } ~CommandObjectTargetStopHookAdd() override = default; - Options *GetOptions() override { return &m_options; } + Options *GetOptions() override { return &m_all_options; } protected: void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { @@ -4626,10 +4683,16 @@ error_sp->Flush(); } Target *target = GetDebugger().GetSelectedTarget().get(); - if (target) - target->RemoveStopHookByID(m_stop_hook_sp->GetID()); + if (target) { + target->UndoCreateStopHook(m_stop_hook_sp->GetID()); + } } else { - m_stop_hook_sp->GetCommandPointer()->SplitIntoLines(line); + // The IOHandler editor is only for command lines stop hooks: + Status error; + Target::StopHookCommandLine *hook_ptr = + static_cast<Target::StopHookCommandLine *>(m_stop_hook_sp.get()); + + hook_ptr->SetActionFromString(line); StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); if (output_sp) { output_sp->Printf("Stop hook #%" PRIu64 " added.\n", @@ -4646,7 +4709,8 @@ m_stop_hook_sp.reset(); Target &target = GetSelectedOrDummyTarget(); - Target::StopHookSP new_hook_sp = target.CreateStopHook(); + Target::StopHookSP new_hook_sp = + target.CreateStopHook(m_python_class_options.GetName().empty()); // First step, make the specifier. std::unique_ptr<SymbolContextSpecifier> specifier_up; @@ -4715,11 +4779,30 @@ new_hook_sp->SetAutoContinue(m_options.m_auto_continue); if (m_options.m_use_one_liner) { - // Use one-liners. - for (auto cmd : m_options.m_one_liner) - new_hook_sp->GetCommandPointer()->AppendString(cmd.c_str()); + // This is a command line stop hook: + Target::StopHookCommandLine *hook_ptr = + static_cast<Target::StopHookCommandLine *>(new_hook_sp.get()); + hook_ptr->SetActionFromStrings(m_options.m_one_liner); result.AppendMessageWithFormat("Stop hook #%" PRIu64 " added.\n", new_hook_sp->GetID()); + } else if (!m_python_class_options.GetName().empty()) { + // This is a scripted stop hook: + Target::StopHookScripted *hook_ptr = + static_cast<Target::StopHookScripted *>(new_hook_sp.get()); + Status error = hook_ptr->SetScriptCallback( + m_python_class_options.GetName(), + m_python_class_options.GetStructuredData()); + if (error.Success()) + result.AppendMessageWithFormat("Stop hook #%" PRIu64 " added.\n", + new_hook_sp->GetID()); + else { + // FIXME: Set the stop hook ID counter back. + result.AppendErrorWithFormat("Couldn't add stop hook: %s", + error.AsCString()); + result.SetStatus(eReturnStatusFailed); + target.UndoCreateStopHook(new_hook_sp->GetID()); + return false; + } } else { m_stop_hook_sp = new_hook_sp; m_interpreter.GetLLDBCommandsFromIOHandler("> ", // Prompt @@ -4732,6 +4815,9 @@ private: CommandOptions m_options; + OptionGroupPythonClassWithDict m_python_class_options; + OptionGroupOptions m_all_options; + Target::StopHookSP m_stop_hook_sp; }; Index: lldb/include/lldb/Target/Target.h =================================================================== --- lldb/include/lldb/Target/Target.h +++ lldb/include/lldb/Target/Target.h @@ -28,6 +28,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" +#include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/LLDBAssert.h" @@ -508,6 +509,8 @@ static void SetDefaultArchitecture(const ArchSpec &arch); + bool IsDummyTarget() const { return m_is_dummy_target; } + /// Find a binary on the system and return its Module, /// or return an existing Module that is already in the Target. /// @@ -1139,23 +1142,25 @@ class StopHook : public UserID { public: StopHook(const StopHook &rhs); - - ~StopHook(); - - StringList *GetCommandPointer() { return &m_commands; } - - const StringList &GetCommands() { return m_commands; } + virtual ~StopHook() = default; lldb::TargetSP &GetTarget() { return m_target_sp; } - void SetCommands(StringList &in_commands) { m_commands = in_commands; } - // Set the specifier. The stop hook will own the specifier, and is // responsible for deleting it when we're done. void SetSpecifier(SymbolContextSpecifier *specifier); SymbolContextSpecifier *GetSpecifier() { return m_specifier_sp.get(); } + bool ExecutionContextPasses(const ExecutionContext &exe_ctx); + + // Called on stop, this gets passed the ExecutionContext for each "stop + // with a reason" thread. It should add to the stream whatever text it + // wants to show the user, and return False to indicate it wants the target + // not to stop. + virtual bool HandleStop(ExecutionContext &exe_ctx, + lldb::StreamSP output) = 0; + // Set the Thread Specifier. The stop hook will own the thread specifier, // and is responsible for deleting it when we're done. void SetThreadSpecifier(ThreadSpec *specifier); @@ -1173,26 +1178,78 @@ bool GetAutoContinue() const { return m_auto_continue; } void GetDescription(Stream *s, lldb::DescriptionLevel level) const; + virtual void GetSubclassDescription(Stream *s, + lldb::DescriptionLevel level) const = 0; - private: + protected: lldb::TargetSP m_target_sp; - StringList m_commands; lldb::SymbolContextSpecifierSP m_specifier_sp; std::unique_ptr<ThreadSpec> m_thread_spec_up; bool m_active = true; bool m_auto_continue = false; + StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid); + }; + + class StopHookCommandLine : public StopHook { + public: + virtual ~StopHookCommandLine() = default; + + StringList *GetCommandPointer() { return &m_commands; } + const StringList &GetCommands() { return m_commands; } + void SetActionFromString(std::string &strings); + void SetActionFromStrings(std::vector<std::string> &strings); + + bool HandleStop(ExecutionContext &exc_ctx, + lldb::StreamSP output_sp) override; + void GetSubclassDescription(Stream *s, + lldb::DescriptionLevel level) const override; + + private: + StringList m_commands; // Use CreateStopHook to make a new empty stop hook. The GetCommandPointer // and fill it with commands, and SetSpecifier to set the specifier shared // pointer (can be null, that will match anything.) - StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid); + StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid) + : StopHook(target_sp, uid) {} + friend class Target; + }; + + class StopHookScripted : public StopHook { + public: + virtual ~StopHookScripted() = default; + bool HandleStop(ExecutionContext &exc_ctx, lldb::StreamSP output) override; + + Status SetScriptCallback(std::string class_name, + StructuredData::ObjectSP extra_args_sp); + + void GetSubclassDescription(Stream *s, + lldb::DescriptionLevel level) const override; + + private: + std::string m_class_name; + StructuredDataImpl *m_extra_args; // We own this structured data, + // but the SD itself manages the UP. + StructuredData::GenericSP m_implementation_sp; // This holds the python + // callback object. + + // Use CreateStopHook to make a new empty stop hook. The GetCommandPointer + // and fill it with commands, and SetSpecifier to set the specifier shared + // pointer (can be null, that will match anything.) + StopHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid) + : StopHook(target_sp, uid) {} friend class Target; }; + typedef std::shared_ptr<StopHook> StopHookSP; // Add an empty stop hook to the Target's stop hook list, and returns a // shared pointer to it in new_hook. Returns the id of the new hook. - StopHookSP CreateStopHook(); + StopHookSP CreateStopHook(bool use_commands); + + // If you tried to create a stop hook, and that failed, call this to + // remove the stop hook, as it will also reset the stop hook counter. + void UndoCreateStopHook(lldb::user_id_t uid); void RunStopHooks(); Index: lldb/include/lldb/Symbol/SymbolContext.h =================================================================== --- lldb/include/lldb/Symbol/SymbolContext.h +++ lldb/include/lldb/Symbol/SymbolContext.h @@ -340,7 +340,7 @@ void Clear(); - bool SymbolContextMatches(SymbolContext &sc); + bool SymbolContextMatches(const SymbolContext &sc); bool AddressMatches(lldb::addr_t addr); Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -298,6 +298,23 @@ return lldb::eSearchDepthModule; } + virtual StructuredData::GenericSP + CreateScriptedStopHook(lldb::TargetSP target_sp, const char *class_name, + StructuredDataImpl *args_data, Status &error) { + error.SetErrorString("Creating scripted stop-hooks with the current " + "script interpreter is not supported."); + return StructuredData::GenericSP(); + } + + // This dispatches to the handle_stop method of the stop-hook class. It + // returns a "should_stop" bool. + virtual bool + ScriptedStopHookHandleStop(StructuredData::GenericSP implementor_sp, + ExecutionContext &exc_ctx, + lldb::StreamSP stream_sp) { + return true; + } + virtual StructuredData::ObjectSP LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) { return StructuredData::ObjectSP(); Index: lldb/bindings/python/python-wrapper.swig =================================================================== --- lldb/bindings/python/python-wrapper.swig +++ lldb/bindings/python/python-wrapper.swig @@ -468,6 +468,123 @@ return ret_val; } +SWIGEXPORT void * +LLDBSwigPythonCreateScriptedStopHook +( + lldb::TargetSP target_sp, + const char *python_class_name, + const char *session_dictionary_name, + lldb_private::StructuredDataImpl *args_impl, + Status &error +) +{ + if (python_class_name == NULL || python_class_name[0] == '\0') { + error.SetErrorString("Empty class name."); + Py_RETURN_NONE; + } + if (!session_dictionary_name) { + error.SetErrorString("No session dictionary"); + Py_RETURN_NONE; + } + + PyErr_Cleaner py_err_cleaner(true); + + auto dict = + PythonModule::MainModule().ResolveName<PythonDictionary>( + session_dictionary_name); + auto pfunc = + PythonObject::ResolveNameWithDictionary<PythonCallable>( + python_class_name, dict); + + if (!pfunc.IsAllocated()) { + error.SetErrorStringWithFormat("Could not find class: %s.", + python_class_name); + return nullptr; + } + + lldb::SBTarget *target_val + = new lldb::SBTarget(target_sp); + + PythonObject target_arg(PyRefType::Owned, SBTypeToSWIGWrapper(target_val)); + + lldb::SBStructuredData *args_value = new lldb::SBStructuredData(args_impl); + PythonObject args_arg(PyRefType::Owned, SBTypeToSWIGWrapper(args_value)); + + PythonObject result = pfunc(target_arg, args_arg, dict); + + if (result.IsAllocated()) + { + // Check that the handle_stop callback is defined: + auto callback_func = result.ResolveName<PythonCallable>("handle_stop"); + if (callback_func.IsAllocated()) { + if (auto args_info = callback_func.GetArgInfo()) { + if ((*args_info).max_positional_args < 2) { + error.SetErrorStringWithFormat("Wrong number of args for " + "handle_stop callback, should be 2 (excluding self), got: %d", + (*args_info).max_positional_args); + } else + return result.release(); + } else { + error.SetErrorString("Couldn't get num arguments for handle_stop " + "callback."); + } + return result.release(); + } + else { + error.SetErrorStringWithFormat("Class \"%s\" is missing the required " + "handle_stop callback."); + result.release(); + } + } + Py_RETURN_NONE; +} + +SWIGEXPORT bool +LLDBSwigPythonStopHookCallHandleStop +( + void *implementor, + lldb::ExecutionContextRefSP exc_ctx_sp, + lldb::StreamSP stream +) +{ + // handle_stop will return a bool with the meaning "should_stop"... + // If you return nothing we'll assume we are going to stop. + // Also any errors should return true, since we should stop on error. + + PyErr_Cleaner py_err_cleaner(false); + PythonObject self(PyRefType::Borrowed, static_cast<PyObject*>(implementor)); + auto pfunc = self.ResolveName<PythonCallable>("handle_stop"); + + if (!pfunc.IsAllocated()) + return true; + + PythonObject result; + lldb::SBExecutionContext sb_exc_ctx(exc_ctx_sp); + PythonObject exc_ctx_arg(PyRefType::Owned, SBTypeToSWIGWrapper(sb_exc_ctx)); + lldb::SBStream sb_stream; + PythonObject sb_stream_arg(PyRefType::Owned, + SBTypeToSWIGWrapper(sb_stream)); + result = pfunc(exc_ctx_arg, sb_stream_arg); + + if (PyErr_Occurred()) + { + stream->PutCString("Python error occurred handling stop-hook."); + PyErr_Print(); + PyErr_Clear(); + return true; + } + + // Now add the result to the output stream. SBStream only + // makes an internally help StreamString which I can't interpose, so I + // have to copy it over here. + stream->PutCString(sb_stream.GetData()); + + if (result.get() == Py_False) + return false; + else + return true; +} + // wrapper that calls an optional instance member of an object taking no arguments static PyObject* LLDBSwigPython_CallOptionalMember Index: lldb/bindings/python/python-swigsafecast.swig =================================================================== --- lldb/bindings/python/python-swigsafecast.swig +++ lldb/bindings/python/python-swigsafecast.swig @@ -152,3 +152,10 @@ { return SWIG_NewPointerObj((void *) sym_ctx_sb, SWIGTYPE_p_lldb__SBSymbolContext, 0); } + +template <> +PyObject* +SBTypeToSWIGWrapper (lldb::SBStream* stream_sb) +{ + return SWIG_NewPointerObj((void *) stream_sb, SWIGTYPE_p_lldb__SBStream, 0); +}
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits