forked from OSchip/llvm-project
[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:
parent
e1326cadb2
commit
05a1f2ac4c
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
LEVEL = ../../make
|
||||
|
||||
C_SOURCES := main.c
|
||||
|
||||
include $(LEVEL)/Makefile.rules
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue