jingham created this revision. jingham added reviewers: JDevlieghere, labath, chaoren, clayborg, kastiglione. Herald added a project: All. jingham requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
One of the weaknesses of the signal handling model in lldb is that we access the information about signals from the process. That's good, since different OS'es and even different OS versions might have more or fewer signals, or they may be renumbered, etc. However, it means you can't set signal handling behaviors in your .lldbinit, and if you set them in one run they don't persist on re-run. That's been somewhat annoying over the years. This adds a "DummySignals" map to the target that holds signals as std::strings, and the user intentions. I called them DummySignals because if you set them in an .lldbinit, they will go into the Dummy Target, and then will prime new targets. They work like all the other Dummy Target elements, if you add a handling with no Targets, they go to the dummy target then to your target. If you add them when there's a real target, it will only go into that target. Just like before, if you run `process handle` when you have a process, the UnixSignals values will be changed, but the change will also be recorded in the DummySignals map so we can apply it on re-run. I also added a `-c` option to "process handle" which allows you to clear the setting from your target (and with `-d` from the dummy target). I also added a `-t` option to "process handle" to show you just the changes you made to the default setting values. Since I wanted you to also be able to "reset to the default" I had to change the UnixSignals class so it recorded the default value as well as the current value. There are a couple of pieces of this that aren't done yet, but the patch was getting pretty big. 1. No SB API. I'll add an SBTarget::HandleSignal to set the handling in the dummy target. 2. When you add a signal with no process, we can't spell-check the signal name for you. The current patch emits a warning on Launch or Attach if there was a signal whose handling is configured but the signal name wasn't recognized. I don't plan to do #2 right now. You would have to have the AddSignal add the signal name to a global pool, and then construct all the Platforms and get them to make their UnixSignals so the registry would happen. Then you could compare the signal against this list. I'm not even sure that being able to spell check the signals is worth having to construct all the platforms to build this registry. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D126259 Files: lldb/include/lldb/Target/Target.h lldb/include/lldb/Target/UnixSignals.h lldb/packages/Python/lldbsuite/test/lldbutil.py lldb/source/Commands/CommandObjectProcess.cpp lldb/source/Commands/Options.td lldb/source/Target/Process.cpp lldb/source/Target/Target.cpp lldb/source/Target/UnixSignals.cpp lldb/test/API/commands/process/handle/Makefile lldb/test/API/commands/process/handle/TestProcessHandle.py lldb/test/API/commands/process/handle/main.cpp lldb/test/API/functionalities/signal/raise/TestRaise.py lldb/unittests/Signals/UnixSignalsTest.cpp
Index: lldb/unittests/Signals/UnixSignalsTest.cpp =================================================================== --- lldb/unittests/Signals/UnixSignalsTest.cpp +++ lldb/unittests/Signals/UnixSignalsTest.cpp @@ -53,6 +53,29 @@ EXPECT_EQ(LLDB_INVALID_SIGNAL_NUMBER, signals.GetNextSignalNumber(16)); } +TEST(UnixSignalsTest, Reset) { + TestSignals signals; + bool stop_val = signals.GetShouldStop(2); + bool notify_val = signals.GetShouldNotify(2); + bool suppress_val = signals.GetShouldSuppress(2); + + // Change two, then reset one and make sure only that one was reset: + EXPECT_EQ(true, signals.SetShouldNotify(2, !notify_val)); + EXPECT_EQ(true, signals.SetShouldSuppress(2, !suppress_val)); + EXPECT_EQ(true, signals.ResetSignal(2, false, true, false)); + EXPECT_EQ(stop_val, signals.GetShouldStop(2)); + EXPECT_EQ(notify_val, signals.GetShouldStop(2)); + EXPECT_EQ(!suppress_val, signals.GetShouldNotify(2)); + + // Make sure reset with no arguments resets them all: + EXPECT_EQ(true, signals.SetShouldSuppress(2, !suppress_val)); + EXPECT_EQ(true, signals.SetShouldNotify(2, !notify_val)); + EXPECT_EQ(true, signals.ResetSignal(2)); + EXPECT_EQ(stop_val, signals.GetShouldStop(2)); + EXPECT_EQ(notify_val, signals.GetShouldNotify(2)); + EXPECT_EQ(suppress_val, signals.GetShouldSuppress(2)); +} + TEST(UnixSignalsTest, GetInfo) { TestSignals signals; Index: lldb/test/API/functionalities/signal/raise/TestRaise.py =================================================================== --- lldb/test/API/functionalities/signal/raise/TestRaise.py +++ lldb/test/API/functionalities/signal/raise/TestRaise.py @@ -36,6 +36,10 @@ def launch(self, target, signal): # launch the process, do not stop at entry point. + # If we have gotten the default for this signal, reset that as well. + if len(self.default_pass) != 0: + lldbutil.set_actions_for_signal(self, signal, self.default_pass, self.default_stop, self.default_notify) + process = target.LaunchSimple( [signal], None, self.get_process_working_directory()) self.assertTrue(process, PROCESS_IS_VALID) @@ -64,27 +68,19 @@ target = self.dbg.CreateTarget(exe) self.assertTrue(target, VALID_TARGET) lldbutil.run_break_set_by_symbol(self, "main") + self.default_pass = "" + self.default_stop = "" + self.default_notify = "" # launch process = self.launch(target, signal) signo = process.GetUnixSignals().GetSignalNumberFromName(signal) # retrieve default signal disposition - return_obj = lldb.SBCommandReturnObject() - self.dbg.GetCommandInterpreter().HandleCommand( - "process handle %s " % signal, return_obj) - match = re.match( - 'NAME *PASS *STOP *NOTIFY.*(false|true) *(false|true) *(false|true)', - return_obj.GetOutput(), - re.IGNORECASE | re.DOTALL) - if not match: - self.fail('Unable to retrieve default signal disposition.') - default_pass = match.group(1) - default_stop = match.group(2) - default_notify = match.group(3) + (self.default_pass, self.default_stop, self.default_notify) = lldbutil.get_actions_for_signal(self, signal) # Make sure we stop at the signal - self.set_handle(signal, "false", "true", "true") + lldbutil.set_actions_for_signal(self, signal, "false", "true", "true") process.Continue() self.assertEqual(process.GetState(), lldb.eStateStopped) thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal) @@ -102,12 +98,11 @@ self.assertEqual(process.GetState(), lldb.eStateExited) self.assertEqual(process.GetExitStatus(), 0) - # launch again process = self.launch(target, signal) # Make sure we do not stop at the signal. We should still get the # notification. - self.set_handle(signal, "false", "false", "true") + lldbutil.set_actions_for_signal(self, signal, "false", "false", "true") self.expect( "process continue", substrs=[ @@ -121,7 +116,7 @@ # Make sure we do not stop at the signal, and we do not get the # notification. - self.set_handle(signal, "false", "false", "false") + lldbutil.set_actions_for_signal(self, signal, "false", "false", "false") self.expect( "process continue", substrs=["stopped and restarted"], @@ -131,14 +126,14 @@ if not test_passing: # reset signal handling to default - self.set_handle(signal, default_pass, default_stop, default_notify) + lldbutil.set_actions_for_signal(self, signal, self.default_pass, self.default_stop, self.default_notify) return # launch again process = self.launch(target, signal) # Make sure we stop at the signal - self.set_handle(signal, "true", "true", "true") + lldbutil.set_actions_for_signal(self, signal, "true", "true", "true") process.Continue() self.assertEqual(process.GetState(), lldb.eStateStopped) thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal) @@ -164,7 +159,7 @@ # Make sure we do not stop at the signal. We should still get the notification. Process # should receive the signal. - self.set_handle(signal, "true", "false", "true") + lldbutil.set_actions_for_signal(self, signal, "true", "false", "true") self.expect( "process continue", substrs=[ @@ -178,7 +173,7 @@ # Make sure we do not stop at the signal, and we do not get the notification. Process # should receive the signal. - self.set_handle(signal, "true", "false", "false") + lldbutil.set_actions_for_signal(self, signal, "true", "false", "false") self.expect( "process continue", substrs=["stopped and restarted"], @@ -187,4 +182,4 @@ self.assertEqual(process.GetExitStatus(), signo) # reset signal handling to default - self.set_handle(signal, default_pass, default_stop, default_notify) + lldbutil.set_actions_for_signal(self, signal, self.default_pass, self.default_stop, self.default_notify) Index: lldb/test/API/commands/process/handle/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/process/handle/main.cpp @@ -0,0 +1,3 @@ +int main() { + return 0; // break here +} Index: lldb/test/API/commands/process/handle/TestProcessHandle.py =================================================================== --- /dev/null +++ lldb/test/API/commands/process/handle/TestProcessHandle.py @@ -0,0 +1,130 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestProcessHandle(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @no_debug_info_test + def test_process_handle(self): + """Test that calling process handle before we have a target, and before we + have a process will affect the process. Also that the signal settings + are preserved on rerun.""" + self.build() + + # First, I need a reference value so I can see whether changes actually took: + (target, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self, '// break here', lldb.SBFileSpec("main.cpp")) + (default_pass, default_stop, default_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV") + + # Let's change the value here, then exit and make sure the changed value sticks: + new_value = "false" + if default_pass == "true": + new_value = "false" + + # First make sure we get an error for bogus values when running: + lldbutil.set_actions_for_signal(self, "NOTSIGSEGV", new_value, None, None, expect_success=False) + # Then set the one we intend to change. + lldbutil.set_actions_for_signal(self, "SIGSEGV", new_value, None, None) + + process.Continue() + + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) + + # Check that we preserved the setting: + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV",from_target=True) + self.assertEqual(curr_pass, new_value, "Pass was set correctly") + self.assertEqual(curr_stop, "not set", "Stop was not set by us") + self.assertEqual(curr_notify, "not set", "Notify was not set by us") + + # Run again and make sure that we prime the new process with these settings: + process = lldbutil.run_to_breakpoint_do_run(self, target, bkpt) + + # We check the process settings now, to see what got copied into the process: + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV") + self.assertEqual(curr_pass, new_value, "Pass was set correctly") + self.assertEqual(curr_stop, default_stop, "Stop was its default value") + self.assertEqual(curr_notify, default_notify, "Notify was its default value") + + # Now kill this target, set the handling and make sure the values get copied from the dummy into the new target. + success = self.dbg.DeleteTarget(target) + self.assertTrue(success, "Deleted the target") + self.assertEqual(self.dbg.GetNumTargets(), 0, "We did delete all the targets.") + + # The signal settings should be back at their default - we were only setting this on the target: + lldbutil.get_actions_for_signal(self, "SIGSEGV", from_target=True, expected_absent=True) + # Set a valid one: + lldbutil.set_actions_for_signal(self, "SIGSEGV", new_value, None, None) + # Set a bogus one - we don't have a way to check pre-run so this is allowed + # but we should get an error message when launching: + lldbutil.set_actions_for_signal(self, "SIGNOTSIG", new_value, None, None) + + out_filename = self.getBuildArtifact('output') + success = True + try: + f = open(out_filename, 'w') + except: + success = False + + if not success: + self.fail("Couldn't open error output file for writing.") + + self.dbg.SetErrorFileHandle(f, False) + # Now make a new process and make sure the right values got copied into the new target + (target, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self, '// break here', lldb.SBFileSpec("main.cpp")) + f.write("TESTPATTERN\n") + f.flush() + f.close() + + try: + f = open(out_filename, 'r') + except: + success = False + + if not success: + self.fail("Couldn't open error output file for reading") + errors = f.read() + f.close() + + self.assertIn("SIGNOTSIG", errors, "We warned about the unset signal") + # Also make sure we didn't accidentally add this bogus setting to the process. + lldbutil.set_actions_for_signal(self, "SIGNOTSIG", "true", "true", "true", expect_success=False) + + # Check that they went into the target: + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV",from_target=True) + self.assertEqual(curr_pass, new_value, "Pass was set correctly") + self.assertEqual(curr_stop, "not set", "Stop was not set by us") + self.assertEqual(curr_notify, "not set", "Notify was not set by us") + + # And the process: + # Check that they went into the target: + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV") + self.assertEqual(curr_pass, new_value, "Pass was set correctly") + self.assertEqual(curr_stop, default_stop, "Stop was its default value") + self.assertEqual(curr_notify, default_notify, "Notify was its default value") + + # Now clear the handling, and make sure that we get the right signal values again: + self.runCmd("process handle -c SIGSEGV") + # Check that there is no longer configuration for SIGSEGV in the target: + lldbutil.get_actions_for_signal(self, "SIGSEGV",from_target=True, expected_absent=True) + # Make a new process, to make sure we did indeed reset the values: + (target, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self, '// break here', lldb.SBFileSpec("main.cpp")) + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV") + self.assertEqual(curr_pass, new_value, "Pass was set correctly") + self.assertEqual(curr_stop, default_stop, "Stop was its default value") + self.assertEqual(curr_notify, default_notify, "Notify was its default value") + + # Finally remove this from the dummy target as well, and make sure it was cleared from there: + self.runCmd("process handle -c -d SIGSEGV") + error = process.Kill() + self.assertSuccess(error, "Killed the process") + success = self.dbg.DeleteTarget(target) + self.assertTrue(success, "Destroyed the target.") + + (target, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self, '// break here', lldb.SBFileSpec("main.cpp")) + (curr_pass, curr_stop, curr_notify) = lldbutil.get_actions_for_signal(self, "SIGSEGV") + self.assertEqual(curr_pass, default_pass, "Pass was set correctly") + self.assertEqual(curr_stop, default_stop, "Stop was its default value") + self.assertEqual(curr_notify, default_notify, "Notify was its default value") Index: lldb/test/API/commands/process/handle/Makefile =================================================================== --- /dev/null +++ lldb/test/API/commands/process/handle/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules Index: lldb/source/Target/UnixSignals.cpp =================================================================== --- lldb/source/Target/UnixSignals.cpp +++ lldb/source/Target/UnixSignals.cpp @@ -22,7 +22,9 @@ const char *description, const char *alias) : m_name(name), m_alias(alias), m_description(), m_suppress(default_suppress), m_stop(default_stop), - m_notify(default_notify) { + m_notify(default_notify), + m_default_suppress(default_suppress), m_default_stop(default_stop), + m_default_notify(default_notify) { if (description) m_description.assign(description); } @@ -330,3 +332,23 @@ } return std::move(json_signals); } + +void UnixSignals::Signal::Reset(bool reset_stop, bool reset_notify, + bool reset_suppress) { + if (reset_stop) + m_stop = m_default_stop; + if (reset_notify) + m_notify = m_default_notify; + if (reset_suppress) + m_suppress = m_default_suppress; +} + +bool UnixSignals::ResetSignal(int32_t signo, bool reset_stop, + bool reset_notify, bool reset_suppress) { + auto elem = m_signals.find(signo); + if (elem == m_signals.end()) + return false; + (*elem).second.Reset(reset_stop, reset_notify, reset_suppress); + return true; +} + Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -51,6 +51,7 @@ #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/UnixSignals.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBAssert.h" @@ -106,7 +107,7 @@ SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded"); SetEventName(eBroadcastBitWatchpointChanged, "watchpoint-changed"); SetEventName(eBroadcastBitSymbolsLoaded, "symbols-loaded"); - + CheckInWithManager(); LLDB_LOG(GetLog(LLDBLog::Object), "{0} Target::Target()", @@ -147,6 +148,8 @@ m_frame_recognizer_manager_up = std::make_unique<StackFrameRecognizerManager>( *target.m_frame_recognizer_manager_up); + + m_dummy_signals = target.m_dummy_signals; } void Target::Dump(Stream *s, lldb::DescriptionLevel description_level) { @@ -287,6 +290,8 @@ m_stop_hooks.clear(); m_stop_hook_next_id = 0; m_suppress_stop_hooks = false; + Args signal_args; + ClearDummySignals(signal_args); } llvm::StringRef Target::GetABIName() const { @@ -3349,6 +3354,136 @@ } } +void Target::AddDummySignal(const char *name, LazyBool pass, LazyBool notify, + LazyBool stop) { + if (!name) + return; + // Don't add a signal if all the actions are trivial: + if (pass == eLazyBoolCalculate && notify == eLazyBoolCalculate + && stop == eLazyBoolCalculate) + return; + + auto elem = m_dummy_signals.find(name); + if (elem != m_dummy_signals.end()) { + (*elem).second.pass = pass; + (*elem).second.notify = notify; + (*elem).second.stop = stop; + return; + } + DummySignalValues value(pass, notify, stop); + m_dummy_signals.insert(std::make_pair(name, value)); +} + +bool Target::UpdateSignalFromDummy(UnixSignalsSP signals_sp, + const DummySignalElement &elem) { + if (!signals_sp) + return false; + + int32_t signo = signals_sp->GetSignalNumberFromName(elem.first.c_str()); + if (signo == LLDB_INVALID_SIGNAL_NUMBER) + return false; + + if (elem.second.pass == eLazyBoolYes) + signals_sp->SetShouldSuppress(signo, false); + else if (elem.second.pass == eLazyBoolNo) + signals_sp->SetShouldSuppress(signo, true); + + if (elem.second.notify == eLazyBoolYes) + signals_sp->SetShouldNotify(signo, true); + else if (elem.second.notify == eLazyBoolNo) + signals_sp->SetShouldNotify(signo, false); + + if (elem.second.stop == eLazyBoolYes) + signals_sp->SetShouldStop(signo, true); + else if (elem.second.stop == eLazyBoolNo) + signals_sp->SetShouldStop(signo, false); + return true; +} + +bool Target::ResetSignalFromDummy(UnixSignalsSP signals_sp, + const DummySignalElement &elem) { + if (!signals_sp) + return false; + int32_t signo = signals_sp->GetSignalNumberFromName(elem.first.c_str()); + if (signo == LLDB_INVALID_SIGNAL_NUMBER) + return false; + bool do_pass = elem.second.pass != eLazyBoolCalculate; + bool do_stop = elem.second.stop != eLazyBoolCalculate; + bool do_notify = elem.second.notify != eLazyBoolCalculate; + signals_sp->ResetSignal(signo, do_stop, do_notify, do_pass); + return true; +} + +void Target::UpdateSignalsFromDummy(UnixSignalsSP signals_sp, + StreamSP warning_stream_sp) { + if (!signals_sp) + return; + + for (auto elem : m_dummy_signals) { + if (!UpdateSignalFromDummy(signals_sp, elem)) + warning_stream_sp->Printf("Target signal '%s' not found in process\n", + elem.first.c_str()); + } +} + +void Target::ClearDummySignals(Args &signal_names) { + ProcessSP process_sp = GetProcessSP(); + // The simplest case, delete them all with no process to update. + if (signal_names.GetArgumentCount() == 0 && !process_sp) { + m_dummy_signals.clear(); + return; + } + UnixSignalsSP signals_sp; + if (process_sp) + process_sp->GetUnixSignals(); + + for (const Args::ArgEntry &entry : signal_names) { + const char *signal_name = entry.c_str(); + auto elem = m_dummy_signals.find(signal_name); + // If we didn't find it go on. + // FIXME: Should I pipe error handling through here? + if (elem == m_dummy_signals.end()) { + continue; + } + if (signals_sp) + ResetSignalFromDummy(signals_sp, *elem); + m_dummy_signals.erase(elem); + } +} + +void Target::PrintDummySignals(Stream &strm, Args signal_args, size_t num_signals) { + strm.Printf("NAME PASS STOP NOTIFY\n"); + strm.Printf("=========== ======= ======= =======\n"); + + auto str_for_lazy = [] (LazyBool lazy) -> const char * { + switch (lazy) { + case eLazyBoolCalculate: return "not set"; + case eLazyBoolYes: return "true "; + case eLazyBoolNo: return "false "; + } + }; + size_t num_args = signal_args.GetArgumentCount(); + for (auto elem : m_dummy_signals) { + bool print_it = false; + if (num_signals == 0) { + print_it = true; + } else { + for (size_t idx = 0; idx < num_args; idx++) { + if (elem.first == signal_args.GetArgumentAtIndex(idx)) { + print_it = true; + break; + } + } + } + if (print_it) { + strm.Printf("%-11s ", elem.first.c_str()); + strm.Printf("%s %s %s\n", str_for_lazy(elem.second.pass), + str_for_lazy(elem.second.stop), + str_for_lazy(elem.second.notify)); + } + } +} + // Target::StopHook Target::StopHook::StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid) : UserID(uid), m_target_sp(target_sp), m_specifier_sp(), Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -2557,6 +2557,13 @@ if (state == eStateStopped || state == eStateCrashed) { DidLaunch(); + + // Now that we know the process type, update its signal responses from the + // ones stored in the Target: + if (m_unix_signals_sp) { + StreamSP warning_strm = GetTarget().GetDebugger().GetAsyncErrorStream(); + GetTarget().UpdateSignalsFromDummy(m_unix_signals_sp, warning_strm); + } DynamicLoader *dyld = GetDynamicLoader(); if (dyld) @@ -2919,6 +2926,12 @@ } } } + // Now that we know the process type, update its signal responses from the + // ones stored in the Target: + if (m_unix_signals_sp) { + StreamSP warning_strm = GetTarget().GetDebugger().GetAsyncErrorStream(); + GetTarget().UpdateSignalsFromDummy(m_unix_signals_sp, warning_strm); + } // We have completed the attach, now it is time to find the dynamic loader // plug-in Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -738,6 +738,8 @@ } let Command = "process handle" in { + def process_handle_clear : Option<"clear", "c">, Group<2>, + Desc<"Removes the signals listed from the Target signal handlers">; def process_handle_stop : Option<"stop", "s">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the process should be stopped if the signal is " "received.">; @@ -746,6 +748,10 @@ "received.">; def process_handle_pass : Option<"pass", "p">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the signal should be passed to the process.">; + def process_handle_only_target : Option<"target", "t">, Group<1>, + Desc<"Show only the signals with behaviors modified in this target">; + def process_handle_dummy : Option<"dummy", "d">, Group<2>, + Desc<"Also clear the values in the dummy target so they won't be inherited by new targets.">; } let Command = "process status" in { Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -1432,6 +1432,12 @@ const int short_option = m_getopt_table[option_idx].val; switch (short_option) { + case 'c': + do_clear = true; + break; + case 'd': + dummy = true; + break; case 's': stop = std::string(option_arg); break; @@ -1441,6 +1447,9 @@ case 'p': pass = std::string(option_arg); break; + case 't': + only_target_values = true; + break; default: llvm_unreachable("Unimplemented option"); } @@ -1451,6 +1460,9 @@ stop.clear(); notify.clear(); pass.clear(); + only_target_values = false; + do_clear = false; + dummy = false; } llvm::ArrayRef<OptionDefinition> GetDefinitions() override { @@ -1462,6 +1474,9 @@ std::string stop; std::string notify; std::string pass; + bool only_target_values; + bool do_clear; + bool dummy; }; CommandObjectProcessHandle(CommandInterpreter &interpreter) @@ -1469,9 +1484,19 @@ "Manage LLDB handling of OS signals for the " "current target process. Defaults to showing " "current policy.", - nullptr, eCommandRequiresTarget) { - SetHelpLong("\nIf no signals are specified, update them all. If no update " - "option is specified, list the current values."); + nullptr) { + SetHelpLong("\nIf no signals are specified but one or more actions are, " + "and there is a live process, update them all. If no action " + "is specified, list the current values.\n" + "If you specify actions with no target (e.g. in an init file) " + "or in a target with no process " + "the values will get copied into subsequent targets, but " + "lldb won't be able to spell-check the options since it can't " + "know which signal set will later be in force." + "\nYou can see the signal modifications held by the target" + "by passing the -t option." + "\nYou can also clear the target modification for a signal" + "by passing the -c option"); CommandArgumentEntry arg; CommandArgumentData signal_arg; @@ -1554,16 +1579,14 @@ protected: bool DoExecute(Args &signal_args, CommandReturnObject &result) override { - Target *target_sp = &GetSelectedTarget(); + Target *target_sp = &GetSelectedOrDummyTarget(); + // Any signals that are being set should be added to the Target's + // DummySignals so they will get applied on rerun, etc. + // If we have a process, however, we can do a more accurate job of vetting + // the user's options. ProcessSP process_sp = target_sp->GetProcessSP(); - if (!process_sp) { - result.AppendError("No current process; cannot handle signals until you " - "have a valid process.\n"); - return false; - } - int stop_action = -1; // -1 means leave the current setting alone int pass_action = -1; // -1 means leave the current setting alone int notify_action = -1; // -1 means leave the current setting alone @@ -1588,35 +1611,91 @@ "true or false.\n"); return false; } + + bool no_actions = (stop_action == -1 && pass_action == -1 + && notify_action == -1); + if (m_options.only_target_values && !no_actions) { + result.AppendError("-t is for reporting, not setting, target values."); + return false; + } size_t num_args = signal_args.GetArgumentCount(); - UnixSignalsSP signals_sp = process_sp->GetUnixSignals(); + UnixSignalsSP signals_sp; + if (process_sp) + signals_sp = process_sp->GetUnixSignals(); + int num_signals_set = 0; + // If we were just asked to print the target values, do that here and + // return: + if (m_options.only_target_values) { + target_sp->PrintDummySignals(result.GetOutputStream(), + signal_args, num_args); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + // This handles clearing values: + if (m_options.do_clear) { + target_sp->ClearDummySignals(signal_args); + if (m_options.dummy) + GetDummyTarget().ClearDummySignals(signal_args); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + // This rest handles setting values: if (num_args > 0) { for (const auto &arg : signal_args) { - int32_t signo = signals_sp->GetSignalNumberFromName(arg.c_str()); - if (signo != LLDB_INVALID_SIGNAL_NUMBER) { - // Casting the actions as bools here should be okay, because - // VerifyCommandOptionValue guarantees the value is either 0 or 1. - if (stop_action != -1) - signals_sp->SetShouldStop(signo, stop_action); - if (pass_action != -1) { - bool suppress = !pass_action; - signals_sp->SetShouldSuppress(signo, suppress); + // Do the process first. If we have a process we can catch + // invalid signal names, which we do here. + if (signals_sp) { + int32_t signo = signals_sp->GetSignalNumberFromName(arg.c_str()); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) { + // Casting the actions as bools here should be okay, because + // VerifyCommandOptionValue guarantees the value is either 0 or 1. + if (stop_action != -1) + signals_sp->SetShouldStop(signo, stop_action); + if (pass_action != -1) { + bool suppress = !pass_action; + signals_sp->SetShouldSuppress(signo, suppress); + } + if (notify_action != -1) + signals_sp->SetShouldNotify(signo, notify_action); + ++num_signals_set; + } else { + result.AppendErrorWithFormat("Invalid signal name '%s'\n", + arg.c_str()); + continue; } - if (notify_action != -1) - signals_sp->SetShouldNotify(signo, notify_action); - ++num_signals_set; } else { - result.AppendErrorWithFormat("Invalid signal name '%s'\n", - arg.c_str()); + // If there's no process we can't check, so we just set them all: + num_signals_set = num_args; } + auto set_lazy_bool = [] (int action) -> LazyBool { + LazyBool lazy; + if (action == -1) + lazy = eLazyBoolCalculate; + else if (action) + lazy = eLazyBoolYes; + else + lazy = eLazyBoolNo; + return lazy; + }; + + // If there were no actions, we're just listing, don't add the dummy: + if (!no_actions) + target_sp->AddDummySignal(arg.c_str(), + set_lazy_bool(pass_action), + set_lazy_bool(notify_action), + set_lazy_bool(stop_action)); } } else { // No signal specified, if any command options were specified, update ALL - // signals. - if ((notify_action != -1) || (stop_action != -1) || (pass_action != -1)) { + // signals. But we can't do this without a process since we don't know + // all the possible signals that might be valid for this target. + if (((notify_action != -1) || (stop_action != -1) || (pass_action != -1)) + && process_sp) { if (m_interpreter.Confirm( "Do you really want to update all the signals?", false)) { int32_t signo = signals_sp->GetFirstSignalNumber(); @@ -1635,11 +1714,15 @@ } } - PrintSignalInformation(result.GetOutputStream(), signal_args, - num_signals_set, signals_sp); + if (signals_sp) + PrintSignalInformation(result.GetOutputStream(), signal_args, + num_signals_set, signals_sp); + else + target_sp->PrintDummySignals(result.GetOutputStream(), + signal_args, num_signals_set); if (num_signals_set > 0) - result.SetStatus(eReturnStatusSuccessFinishNoResult); + result.SetStatus(eReturnStatusSuccessFinishResult); else result.SetStatus(eReturnStatusFailed); Index: lldb/packages/Python/lldbsuite/test/lldbutil.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbutil.py +++ lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -1527,6 +1527,42 @@ # No remote platform; fall back to using local python signals. return getattr(signal, signal_name) +def get_actions_for_signal(testcase, signal_name, from_target=False, expected_absent=False): + """Returns a triple of (pass, stop, notify)""" + return_obj = lldb.SBCommandReturnObject() + command = "process handle {0}".format(signal_name) + if from_target: + command += " -t" + testcase.dbg.GetCommandInterpreter().HandleCommand( + command, return_obj) + match = re.match( + 'NAME *PASS *STOP *NOTIFY.*(false|true|not set) *(false|true|not set) *(false|true|not set)', + return_obj.GetOutput(), + re.IGNORECASE | re.DOTALL) + if match and expected_absent: + testcase.fail('Signal "{0}" was supposed to be absent'.format(signal_name)) + if not match: + if expected_absent: + return (None, None, None) + testcase.fail('Unable to retrieve default signal disposition.') + return (match.group(1), match.group(2), match.group(3)) + + + +def set_actions_for_signal(testcase, signal_name, pass_action, stop_action, notify_action, expect_success=True): + return_obj = lldb.SBCommandReturnObject() + command = "process handle {0}".format(signal_name) + if pass_action != None: + command += " -p {0}".format(pass_action) + if stop_action != None: + command += " -s {0}".format(stop_action) + if notify_action != None: + command +=" -n {0}".format(notify_action) + + testcase.dbg.GetCommandInterpreter().HandleCommand(command, return_obj) + testcase.assertEqual(expect_success, + return_obj.Succeeded(), + "Setting signal handling for {0} worked as expected".format(signal_name)) class PrintableRegex(object): Index: lldb/include/lldb/Target/UnixSignals.h =================================================================== --- lldb/include/lldb/Target/UnixSignals.h +++ lldb/include/lldb/Target/UnixSignals.h @@ -55,6 +55,9 @@ bool SetShouldNotify(int32_t signo, bool value); bool SetShouldNotify(const char *signal_name, bool value); + + bool ResetSignal(int32_t signo, bool reset_stop = true, + bool reset_notify = true, bool reset_suppress = true); // These provide an iterator through the signals available on this system. // Call GetFirstSignalNumber to get the first entry, then iterate on @@ -114,11 +117,13 @@ std::string m_description; uint32_t m_hit_count = 0; bool m_suppress : 1, m_stop : 1, m_notify : 1; + bool m_default_suppress : 1, m_default_stop : 1, m_default_notify : 1; Signal(const char *name, bool default_suppress, bool default_stop, bool default_notify, const char *description, const char *alias); ~Signal() = default; + void Reset(bool reset_stop, bool reset_notify, bool reset_suppress); }; virtual void Reset(); Index: lldb/include/lldb/Target/Target.h =================================================================== --- lldb/include/lldb/Target/Target.h +++ lldb/include/lldb/Target/Target.h @@ -1414,6 +1414,42 @@ return *m_frame_recognizer_manager_up; } + /// Add a signal for the target. This will get copied over to the process + /// if the signal exists on that target. Only the values with Yes and No are + /// set, Calculate values will be ignored. +protected: + struct DummySignalValues { + LazyBool pass = eLazyBoolCalculate; + LazyBool notify = eLazyBoolCalculate; + LazyBool stop = eLazyBoolCalculate; + DummySignalValues(LazyBool pass, LazyBool notify, LazyBool stop) : + pass(pass), notify(notify), stop(stop) {} + DummySignalValues() = default; + }; + using DummySignalElement = std::pair<std::string, DummySignalValues>; + static bool UpdateSignalFromDummy(lldb::UnixSignalsSP signals_sp, + const DummySignalElement &element); + static bool ResetSignalFromDummy(lldb::UnixSignalsSP signals_sp, + const DummySignalElement &element); + +public: + /// Add a signal to the Target's list of stored signals/actions. These + /// values will get copied into any processes launched from + /// this target. + void AddDummySignal(const char *name, LazyBool pass, LazyBool print, + LazyBool stop); + /// Updates the signals in signals_sp using the stored dummy signals. + /// If warning_stream_sp is not null, if any stored signals are not found in + /// the current process, a warning will be emitted here. + void UpdateSignalsFromDummy(lldb::UnixSignalsSP signals_sp, + lldb::StreamSP warning_stream_sp); + /// Clear the dummy signals in signal_names from the target, or all signals + /// if signal_names is empty. Also remove the behaviors they set from the + /// process's signals if it exists. + void ClearDummySignals(Args &signal_names); + /// Print all the signals set in this target. + void PrintDummySignals(Stream &strm, Args signals, size_t num_signals); + protected: /// Implementing of ModuleList::Notifier. @@ -1443,6 +1479,7 @@ ArchSpec m_spec; std::unique_ptr<Architecture> m_plugin_up; }; + // Member variables. Debugger &m_debugger; lldb::PlatformSP m_platform_sp; ///< The platform for this target. @@ -1493,6 +1530,11 @@ lldb::TraceSP m_trace_sp; /// Stores the frame recognizers of this target. lldb::StackFrameRecognizerManagerUP m_frame_recognizer_manager_up; + std::map<std::string, DummySignalValues> m_dummy_signals;/// These are used to + /// set the signal state when you don't have a process and more usefully + /// in the Dummy target where you can't know exactly what signals you + /// will have. + static void ImageSearchPathsChanged(const PathMappingList &path_list, void *baton);
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits