[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
This commit is contained in:
Pavel Labath 2015-05-28 08:59:21 +00:00
parent e1326cadb2
commit 05a1f2ac4c
4 changed files with 207 additions and 7 deletions

View File

@ -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)

View File

@ -0,0 +1,5 @@
LEVEL = ../../make
C_SOURCES := main.c
include $(LEVEL)/Makefile.rules

View File

@ -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
# <https://llvm.org/bugs/show_bug.cgi?id=23659> 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()

View File

@ -0,0 +1,86 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#if defined(__linux__)
#include <sys/prctl.h>
#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;
}