Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -835,9 +835,10 @@ "send packet: $OK#00", ], True) - def add_qSupported_packets(self): + def add_qSupported_packets(self, client_features=[]): + features = ''.join(';' + x for x in client_features) self.test_sequence.add_log_lines( - ["read packet: $qSupported#00", + ["read packet: $qSupported{}#00".format(features), {"direction": "send", "regex": r"^\$(.*)#[0-9a-fA-F]{2}", "capture": {1: "qSupported_response"}}, ], True) Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -49,6 +49,8 @@ llvm::Expected> Attach(lldb::pid_t pid, NativeDelegate &native_delegate, MainLoop &mainloop) const override; + + Extension GetSupportedExtensions() const override; }; // NativeProcessProtocol Interface @@ -136,6 +138,7 @@ private: MainLoop::SignalHandleUP m_sigchld_handle; ArchSpec m_arch; + MainLoop& m_main_loop; LazyBool m_supports_mem_region = eLazyBoolCalculate; std::vector> m_mem_region_cache; Index: lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -281,6 +281,11 @@ pid, -1, native_delegate, Info.GetArchitecture(), mainloop, *tids_or)); } +NativeProcessLinux::Extension +NativeProcessLinux::Factory::GetSupportedExtensions() const { + return Extension::fork | Extension::vfork; +} + // Public Instance Methods NativeProcessLinux::NativeProcessLinux(::pid_t pid, int terminal_fd, @@ -288,7 +293,7 @@ const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch), - m_intel_pt_manager(pid) { + m_main_loop(mainloop), m_intel_pt_manager(pid) { if (m_terminal_fd != -1) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); @@ -647,7 +652,12 @@ } case (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)): { - ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); + if ((m_enabled_extensions & Extension::vfork) == Extension::vfork) { + thread.SetStoppedByVForkDone(); + StopRunningThreads(thread.GetID()); + } + else + ResumeThread(thread, thread.GetState(), LLDB_INVALID_SIGNAL_NUMBER); break; } @@ -912,16 +922,28 @@ LLVM_FALLTHROUGH; case PTRACE_EVENT_FORK: case PTRACE_EVENT_VFORK: { - MainLoop unused_loop; - NativeProcessLinux child_process{static_cast<::pid_t>(child_pid), - m_terminal_fd, - m_delegate, - m_arch, - unused_loop, - {static_cast<::pid_t>(child_pid)}}; - child_process.Detach(); - ResumeThread(*parent_thread, parent_thread->GetState(), - LLDB_INVALID_SIGNAL_NUMBER); + bool is_vfork = clone_info->event == PTRACE_EVENT_VFORK; + NativeProcessLinux *child_process = new NativeProcessLinux( + static_cast<::pid_t>(child_pid), m_terminal_fd, m_delegate, m_arch, + m_main_loop, {static_cast<::pid_t>(child_pid)}); + if (!is_vfork) + child_process->m_software_breakpoints = m_software_breakpoints; + + std::unique_ptr child_process_up{child_process}; + Extension expected_ext = is_vfork ? Extension::vfork : Extension::fork; + if ((m_enabled_extensions & expected_ext) == expected_ext) { + m_delegate.NewSubprocess(this, std::move(child_process_up)); + // NB: non-vfork clone() is reported as fork + if (is_vfork) + parent_thread->SetStoppedByVFork(child_pid); + else + parent_thread->SetStoppedByFork(child_pid); + StopRunningThreads(parent_thread->GetID()); + } else { + child_process_up->Detach(); + ResumeThread(*parent_thread, parent_thread->GetState(), + LLDB_INVALID_SIGNAL_NUMBER); + } break; } default: Index: lldb/source/Plugins/Process/Linux/NativeThreadLinux.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeThreadLinux.h +++ lldb/source/Plugins/Process/Linux/NativeThreadLinux.h @@ -85,6 +85,12 @@ void SetStoppedByTrace(); + void SetStoppedByFork(lldb::pid_t child_pid); + + void SetStoppedByVFork(lldb::pid_t child_pid); + + void SetStoppedByVForkDone(); + void SetStoppedWithNoReason(); void SetStoppedByProcessorTrace(llvm::StringRef description); Index: lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -394,6 +394,28 @@ m_stop_info.details.signal.signo = SIGTRAP; } +void NativeThreadLinux::SetStoppedByFork(lldb::pid_t child_pid) { + SetStopped(); + + m_stop_info.reason = StopReason::eStopReasonFork; + m_stop_info.details.fork.child_pid = child_pid; + m_stop_info.details.fork.child_tid = child_pid; +} + +void NativeThreadLinux::SetStoppedByVFork(lldb::pid_t child_pid) { + SetStopped(); + + m_stop_info.reason = StopReason::eStopReasonVFork; + m_stop_info.details.fork.child_pid = child_pid; + m_stop_info.details.fork.child_tid = child_pid; +} + +void NativeThreadLinux::SetStoppedByVForkDone() { + SetStopped(); + + m_stop_info.reason = StopReason::eStopReasonVForkDone; +} + void NativeThreadLinux::SetStoppedWithNoReason() { SetStopped(); Index: lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py @@ -0,0 +1,58 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class TestGdbRemoteFork(gdbremote_testcase.GdbRemoteTestCaseBase): + mydir = TestBase.compute_mydir(__file__) + + def fork_and_detach_test(self, variant): + self.build() + self.prep_debug_monitor_and_inferior(inferior_args=[variant]) + self.add_qSupported_packets(["{}-events+".format(variant)]) + ret = self.expect_gdbremote_sequence() + self.assertIn("{}-events+".format(variant), ret["qSupported_response"]) + self.reset_test_sequence() + + # continue and expect fork + fork_regex = "[$]T.*;{}:p([0-9a-f]*)[.]([0-9a-f]*).*".format(variant) + self.test_sequence.add_log_lines([ + "read packet: $c#00", + {"direction": "send", "regex": fork_regex, + "capture": {1: "pid", 2: "tid"}}, + ], True) + ret = self.expect_gdbremote_sequence() + pid = int(ret["pid"], 16) + self.reset_test_sequence() + + # detach the forked child + self.test_sequence.add_log_lines([ + "read packet: $D;{:x}#00".format(pid), + {"direction": "send", "regex": r"[$]OK#.*"}, + ], True) + ret = self.expect_gdbremote_sequence() + self.reset_test_sequence() + + @skipIf(oslist=no_match(["linux"])) + def test_fork(self): + self.fork_and_detach_test("fork") + + # resume the parent + self.test_sequence.add_log_lines([ + "read packet: $c#00", + {"direction": "send", "regex": r"[$]W00#.*"}, + ], True) + self.expect_gdbremote_sequence() + + @skipIf(oslist=no_match(["linux"])) + def test_vfork(self): + self.fork_and_detach_test("vfork") + + # resume the parent + self.test_sequence.add_log_lines([ + "read packet: $c#00", + {"direction": "send", "regex": r"[$]T.*vforkdone.*"}, + "read packet: $c#00", + {"direction": "send", "regex": r"[$]W00#.*"}, + ], True) + self.expect_gdbremote_sequence() Index: lldb/test/API/tools/lldb-server/main.cpp =================================================================== --- lldb/test/API/tools/lldb-server/main.cpp +++ lldb/test/API/tools/lldb-server/main.cpp @@ -28,6 +28,8 @@ static const char *const GET_DATA_ADDRESS_PREFIX = "get-data-address-hex:"; static const char *const GET_STACK_ADDRESS_COMMAND = "get-stack-address-hex:"; static const char *const GET_HEAP_ADDRESS_COMMAND = "get-heap-address-hex:"; +static const char *const FORK_COMMAND = "fork"; +static const char *const VFORK_COMMAND = "vfork"; static const char *const GET_CODE_ADDRESS_PREFIX = "get-code-address-hex:"; static const char *const CALL_FUNCTION_PREFIX = "call-function:"; @@ -225,7 +227,13 @@ sig_result = signal(SIGSEGV, signal_handler); if (sig_result == SIG_ERR) { - fprintf(stderr, "failed to set SIGUSR1 handler: errno=%d\n", errno); + fprintf(stderr, "failed to set SIGSEGV handler: errno=%d\n", errno); + exit(1); + } + + sig_result = signal(SIGCHLD, SIG_IGN); + if (sig_result == SIG_ERR) { + fprintf(stderr, "failed to set SIGCHLD handler: errno=%d\n", errno); exit(1); } #endif @@ -311,6 +319,12 @@ } if (func_p) func_p(); + } else if (!std::strcmp(argv[i], FORK_COMMAND)) { + if (fork() == 0) + _exit(0); + } else if (!std::strcmp(argv[i], VFORK_COMMAND)) { + if (vfork() == 0) + _exit(0); } else if (std::strstr(argv[i], THREAD_PREFIX)) { // Check if we're creating a new thread. if (std::strstr(argv[i] + strlen(THREAD_PREFIX), THREAD_COMMAND_NEW)) {