From 05a1f2ac4c65d83783e084b0942ddbe94347d2d4 Mon Sep 17 00:00:00 2001 From: Pavel Labath Date: Thu, 28 May 2015 08:59:21 +0000 Subject: [PATCH] [NativeProcessLinux] Support inferiors which change their process group Summary: Previously, we wait()ed for events from the inferiors process group. This is resulted in a failure if the inferior changed its process group in the middle of execution. To avoid this, I pass -1 to the wait() call. The flag __WNOTHREAD makes sure we don't actually wait for events from any process, but only the processes(threads) which are our children (or traced by us). Since this happens on the monitor thread, which is dedicated to monitoring a single inferior, we will be getting events only from this inferior. Test Plan: All tests pass on linux. I have added a test to check the new functionality. Reviewers: chaoren, ovyalov Subscribers: lldb-commits Differential Revision: http://reviews.llvm.org/D10061 llvm-svn: 238405 --- .../Process/Linux/NativeProcessLinux.cpp | 14 +-- .../functionalities/process_group/Makefile | 5 + .../process_group/TestChangeProcessGroup.py | 109 ++++++++++++++++++ .../test/functionalities/process_group/main.c | 86 ++++++++++++++ 4 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 lldb/test/functionalities/process_group/Makefile create mode 100644 lldb/test/functionalities/process_group/TestChangeProcessGroup.py create mode 100644 lldb/test/functionalities/process_group/main.c diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index 325fdde029d7..489890aa5132 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -786,7 +786,7 @@ NativeProcessLinux::Monitor::HandleWait() while (true) { int status = -1; - ::pid_t wait_pid = waitpid(m_child_pid, &status, __WALL | WNOHANG); + ::pid_t wait_pid = waitpid(-1, &status, __WALL | __WNOTHREAD | WNOHANG); if (wait_pid == 0) break; // We are done. @@ -797,8 +797,8 @@ NativeProcessLinux::Monitor::HandleWait() continue; if (log) - log->Printf("NativeProcessLinux::Monitor::%s waitpid (pid = %" PRIi32 ", &status, __WALL | WNOHANG) failed: %s", - __FUNCTION__, m_child_pid, strerror(errno)); + log->Printf("NativeProcessLinux::Monitor::%s waitpid (-1, &status, __WALL | __WNOTHREAD | WNOHANG) failed: %s", + __FUNCTION__, strerror(errno)); break; } @@ -821,7 +821,7 @@ NativeProcessLinux::Monitor::HandleWait() { signal = WTERMSIG(status); status_cstr = "SIGNALED"; - if (wait_pid == abs(m_child_pid)) { + if (wait_pid == m_child_pid) { exited = true; exit_status = -1; } @@ -830,9 +830,9 @@ NativeProcessLinux::Monitor::HandleWait() status_cstr = "(\?\?\?)"; if (log) - log->Printf("NativeProcessLinux::Monitor::%s: waitpid (pid = %" PRIi32 ", &status, __WALL | WNOHANG)" + log->Printf("NativeProcessLinux::Monitor::%s: waitpid (-1, &status, __WALL | __WNOTHREAD | WNOHANG)" "=> pid = %" PRIi32 ", status = 0x%8.8x (%s), signal = %i, exit_state = %i", - __FUNCTION__, m_child_pid, wait_pid, status, status_cstr, signal, exit_status); + __FUNCTION__, wait_pid, status, status_cstr, signal, exit_status); m_native_process->MonitorCallback (wait_pid, exited, signal, exit_status); } @@ -893,7 +893,7 @@ NativeProcessLinux::Monitor::MainLoop() { ::pid_t child_pid = (*m_initial_operation_up)(m_operation_error); m_initial_operation_up.reset(); - m_child_pid = -getpgid(child_pid), + m_child_pid = child_pid; sem_post(&m_operation_sem); while (true) diff --git a/lldb/test/functionalities/process_group/Makefile b/lldb/test/functionalities/process_group/Makefile new file mode 100644 index 000000000000..0d70f2595019 --- /dev/null +++ b/lldb/test/functionalities/process_group/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/process_group/TestChangeProcessGroup.py b/lldb/test/functionalities/process_group/TestChangeProcessGroup.py new file mode 100644 index 000000000000..c887312a4d7f --- /dev/null +++ b/lldb/test/functionalities/process_group/TestChangeProcessGroup.py @@ -0,0 +1,109 @@ +"""Test that we handle inferiors which change their process group""" + +import os +import unittest2 +import lldb +from lldbtest import * +import lldbutil + + +class ChangeProcessGroupTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number to break for main.c. + self.line = line_number('main.c', '// Set breakpoint here') + + @skipIfWindows # setpgid call does not exist on Windows + @skipUnlessDarwin + @dsym_test + def test_setpgid_with_dsym(self): + self.buildDsym() + self.setpgid() + + @skipIfWindows # setpgid call does not exist on Windows + @dwarf_test + def test_setpgid_with_dwarf(self): + self.buildDwarf() + self.setpgid() + + def run_platform_command(self, cmd): + platform = self.dbg.GetSelectedPlatform() + shell_command = lldb.SBPlatformShellCommand(cmd) + err = platform.Run(shell_command) + return (err, shell_command.GetStatus(), shell_command.GetOutput()) + + def setpgid(self): + exe = os.path.join(os.getcwd(), 'a.out') + + # Use a file as a synchronization point between test and inferior. + pid_file_path = os.path.join(self.get_process_working_directory(), + "pid_file_%d" % (int(time.time()))) + self.addTearDownHook(lambda: self.run_platform_command("rm %s" % (pid_file_path))) + + popen = self.spawnSubprocess(exe, [pid_file_path]) + self.addTearDownHook(self.cleanupSubprocesses) + + max_attempts = 5 + for i in range(max_attempts): + err, retcode, msg = self.run_platform_command("ls %s" % pid_file_path) + if err.Success() and retcode == 0: + break + else: + print msg + if i < max_attempts: + # Exponential backoff! + time.sleep(pow(2, i) * 0.25) + else: + self.fail("Child PID file %s not found even after %d attempts." % (pid_file_path, max_attempts)) + + err, retcode, pid = self.run_platform_command("cat %s" % (pid_file_path)) + + self.assertTrue(err.Success() and retcode == 0, + "Failed to read file %s: %s, retcode: %d" % (pid_file_path, err.GetCString(), retcode)) + + + # Create a target by the debugger. + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + listener = lldb.SBListener("my.attach.listener") + error = lldb.SBError() + process = target.AttachToProcessWithID(listener, int(pid), error) + self.assertTrue(error.Success() and process, PROCESS_IS_VALID) + + # set a breakpoint just before the setpgid() call + lldbutil.run_break_set_by_file_and_line(self, 'main.c', self.line) + + thread = process.GetSelectedThread() + # this gives a chance for the thread to exit the sleep syscall and sidesteps + # on linux + thread.StepInstruction(False) + + # release the child from its loop + self.expect("expr release_child_flag = 1", substrs = ["= 1"]) + process.Continue() + + # make sure the child's process group id is different from its pid + self.expect("print (int)getpgid(0)", substrs = [pid], matching=False) + + # step over the setpgid() call + thread.StepOver() + self.assertEqual(thread.GetStopReason(), lldb.eStopReasonPlanComplete) + + # verify that the process group has been set correctly + # this also checks that we are still in full control of the child + self.expect("print (pid_t)getpgid(0)", substrs = [pid]) + + # run to completion + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/process_group/main.c b/lldb/test/functionalities/process_group/main.c new file mode 100644 index 000000000000..5de63db50dfc --- /dev/null +++ b/lldb/test/functionalities/process_group/main.c @@ -0,0 +1,86 @@ +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +volatile int release_child_flag = 0; + +int main(int argc, char const *argv[]) +{ +#if defined(__linux__) + // Immediately enable any ptracer so that we can allow the stub attach + // operation to succeed. Some Linux kernels are locked down so that + // only an ancestor process can be a ptracer of a process. This disables that + // restriction. Without it, attach-related stub tests will fail. +#if defined(PR_SET_PTRACER) && defined(PR_SET_PTRACER_ANY) + // For now we execute on best effort basis. If this fails for + // some reason, so be it. + const int prctl_result = prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + (void) prctl_result; +#endif +#endif + + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return 1; + } + + if (child > 0) + { // parent + if (argc < 2) + { + fprintf(stderr, "Need pid filename.\n"); + return 2; + } + + // Let the test suite know the child's pid. + FILE *pid_file = fopen(argv[1], "w"); + if (pid_file == NULL) + { + perror("fopen"); + return 3; + } + + fprintf(pid_file, "%d\n", child); + if (fclose(pid_file) == EOF) + { + perror("fclose"); + return 4; + } + + // And wait for the child to finish it's work. + int status = 0; + pid_t wpid = wait(&status); + if (wpid == -1) + { + perror("wait"); + return 5; + } + if (wpid != child) + { + fprintf(stderr, "wait() waited for wrong child\n"); + return 6; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + fprintf(stderr, "child did not exit correctly\n"); + return 7; + } + } + else + { // child + while (! release_child_flag) // Wait for debugger to attach + sleep(1); + + printf("Child's previous process group is: %d\n", getpgid(0)); + setpgid(0, 0); // Set breakpoint here + printf("Child's process group set to: %d\n", getpgid(0)); + } + + return 0; +}