def run(self): """ self.exit_status = os.waitpid(self.pid, os.WNOHANG | os.WUNTRACED) while self.exit_status == (0, 0): self.exit_status = os.waitpid(self.pid, os.WNOHANG | os.WUNTRACED) """ self.spawn_target() self.finished_starting.set() if self.proc_name: gone, _ = psutil.wait_procs([self._psutil_proc]) self.exit_status = gone[0].returncode else: exit_info = os.waitpid(self.pid, 0) self.exit_status = exit_info[1] # [0] is the pid default_reason = 'Process died for unknown reason' if self.exit_status is not None: if os.WCOREDUMP(self.exit_status): reason = 'Segmentation fault' elif os.WIFSTOPPED(self.exit_status): reason = 'Stopped with signal ' + str( os.WTERMSIG(self.exit_status)) elif os.WIFSIGNALED(self.exit_status): reason = 'Terminated with signal ' + str( os.WTERMSIG(self.exit_status)) elif os.WIFEXITED(self.exit_status): reason = 'Exit with code - ' + str( os.WEXITSTATUS(self.exit_status)) else: reason = default_reason else: reason = default_reason self.process_monitor.last_synopsis = '[{0}] Crash. Exit code: {1}. Reason - {2}\n'.format( time.strftime("%I:%M.%S"), self.exit_status if self.exit_status is not None else '<unknown>', reason)
def _wait_for_child(self, childpid): self.__child_exited.acquire() if self.__child_exited.wait(60) == False: # Py2, <Py3.2: always None raise getmailOperationError('waiting child pid %d timed out' % childpid) self.__child_exited.release() if self.__child_pid != childpid: #self.log.error('got child pid %d, not %d' % (pid, childpid)) raise getmailOperationError('got child pid %d, not %d' % (self.__child_pid, childpid)) if os.WIFSTOPPED(self.__child_status): raise getmailOperationError( 'child pid %d stopped by signal %d' % (self.__child_pid, os.WSTOPSIG(self.__child_status))) if os.WIFSIGNALED(self.__child_status): raise getmailOperationError( 'child pid %d killed by signal %d' % (self.__child_pid, os.WTERMSIG(self.__child_status))) if not os.WIFEXITED(self.__child_status): raise getmailOperationError('child pid %d failed to exit' % self.__child_pid) exitcode = os.WEXITSTATUS(self.__child_status) return exitcode
def wait_pid(pid=0, mode=os.WNOHANG): while True: try: exit_pid, exit_status = os.waitpid(pid, mode) except OSError as exc: if exc.errno == errno.EINTR: continue if exc.errno != errno.ECHILD: raise if IS_PY2: sys.exc_clear() break else: if not exit_pid: break elif os.WIFEXITED(exit_status): return ProcessExit(exit_pid, os.WEXITSTATUS(exit_status)) elif os.WIFSIGNALED(exit_status): return ProcessExit(exit_pid, -os.WTERMSIG(exit_status)) elif os.WIFSTOPPED(exit_status): return ProcessExit(exit_pid, os.WSTOPSIG(exit_status)) return None
def verify(self, filename, sigfilename=None): args = [] for keyring in self._keyrings: args.append('--keyring') args.append(keyring) if sigfilename: args.append(sigfilename) args = [self._gpgv] + args + [filename] msg = "" (status, output) = commands.getstatusoutput(string.join(args)) if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): if os.WIFEXITED(status): msg = "gpgv exited with error code %d" % ( os.WEXITSTATUS(status), ) elif os.WIFSTOPPED(status): msg = "gpgv stopped unexpectedly with signal %d" % ( os.WSTOPSIG(status), ) elif os.WIFSIGNALED(status): msg = "gpgv died with signal %d" % (os.WTERMSIG(status), ) raise GPGSigVerificationFailure(msg, output) return output.splitlines()
def post_send(self): """ This routine is called after the fuzzer transmits a test case and returns the status of the target. @rtype: bool @return: Return True if the target is still active, False otherwise. """ if not self.dbg.is_alive(): exit_status = self.dbg.get_exit_status() rec_file = open(self.crash_bin, 'a') if os.WCOREDUMP(exit_status): reason = 'Segmentation fault' elif os.WIFSTOPPED(exit_status): reason = 'Stopped with signal ' + str(os.WTERMSIG(exit_status)) elif os.WIFSIGNALED(exit_status): reason = 'Terminated with signal ' + str( os.WTERMSIG(exit_status)) elif os.WIFEXITED(exit_status): reason = 'Exit with code - ' + str(os.WEXITSTATUS(exit_status)) else: reason = 'Process died for unknown reason' self.last_synopsis = '[%s] Crash : Test - %d Reason - %s\n' % ( time.strftime("%I:%M.%S"), self.test_number, reason) rec_file.write(self.last_synopsis) rec_file.close() if self.coredump_dir is not None: dest = os.path.join(self.coredump_dir, str(self.test_number)) src = self._get_coredump_path() if src is not None: self.log("moving core dump %s -> %s" % (src, dest)) os.rename(src, dest) return self.dbg.is_alive()
def _get_file_md5sum(self, filename): if os.access('/usr/bin/md5sum', os.X_OK): cmd = '/usr/bin/md5sum %s' % (filename, ) self._logger.debug("Running: %s" % (cmd, )) child = popen2.Popen3(cmd, 1) child.tochild.close() erroutput = child.childerr.read() child.childerr.close() if erroutput != '': child.fromchild.close() raise ChangeFileException( "md5sum returned error output \"%s\"" % (erroutput, )) (md5sum, filename) = string.split(child.fromchild.read(), None, 1) child.fromchild.close() status = child.wait() if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): if os.WIFEXITED(status): msg = "md5sum exited with error code %d" % ( os.WEXITSTATUS(status), ) elif os.WIFSTOPPED(status): msg = "md5sum stopped unexpectedly with signal %d" % ( os.WSTOPSIG(status), ) elif os.WIFSIGNALED(status): msg = "md5sum died with signal %d" % ( os.WTERMSIG(status), ) raise ChangeFileException(msg) return md5sum.strip() import md5 fhdl = open(filename) md5sum = md5.new() buf = fhdl.read(8192) while buf != '': md5sum.update(buf) buf = fhdl.read(8192) fhdl.close() return md5sum.hexdigest()
def __handle_event(self, pid, status): if os.WCOREDUMP(status): logger.warning("Core dump created for %s" % (self.pid)) if os.WIFEXITED(status): self.service.instances.pop(self.pid, None) self.log_event(0) self.__spawn() elif os.WIFSIGNALED(status): self.service.instances.pop(self.pid, None) sig = os.WTERMSIG(status) self.log_event(sig) self.__spawn() elif os.WIFSTOPPED(status): self.coverage.capture(self.pid) sig = os.WSTOPSIG(status) #Don't log breakpoints if sig == signal.SIGTRAP: self.exitbp.desinstall(set_ip = True) ptrace_cont(self.pid, 0) return self.log_event(sig) if sig == signal.SIGPIPE: ptrace_detach(self.pid) os.waitpid(self.pid, 0) #Clean up Zombie self.running = False elif sig == signal.SIGSEGV: self.__capturecore() self.__spawn() else: ptrace_cont(self.pid, sig)
def child_handler(): while True: #make sure to clean up all children try: child = os.waitpid(-1, os.WNOHANG | os.WCONTINUED | os.WUNTRACED) # child will be a tuple of (pid, exit_status) if child == (0, 0): break pid = child[0] exit_status = child[1] # get the job from the global joblist that contains # subprocess with pid pid j = joblist.get_job_with_process(pid) # get the subprocess from the job that matches pid pid p = j.get_subprocess(pid) # updates the status of the child process if (os.WIFEXITED(exit_status)): p.set_status(STATUS.TERMINATED) elif (os.WIFSTOPPED(exit_status)): p.set_status(STATUS.STPPED) elif (os.WIFCONTINUED(exit_status)): p.set_status(STATUS.RUNNING) # if the child was terminated by signal, print the relevant information elif (os.WIFSIGNALED(exit_status)): print("Job pid: " + str(pid) + " terminated by signal: " + str(os.WTERMSIG(exit_status))) p.set_status(STATUS.TERMINATED) # updates job list by looking at subprocesses joblist.synchronize(j) except OSError: break
def platformProcessEvent(self, status): if os.WIFEXITED(status): tid = self.getMeta("ThreadId", -1) if tid != self.getPid(): # Set the selected thread ID to the pid cause # the old one's invalid if tid in self.pthreads: self.pthreads.remove(tid) self.setMeta("ThreadId", self.getPid()) self._fireExitThread(tid, os.WEXITSTATUS(status)) else: self._fireExit(os.WEXITSTATUS(status)) elif os.WIFSIGNALED(status): self.setMeta("ExitCode", os.WTERMSIG(status)) self.fireNotifiers(vtrace.NOTIFY_EXIT) elif os.WIFSTOPPED(status): sig = os.WSTOPSIG(status) self.handlePosixSignal(sig) else: print "OMG WTF JUST HAPPENED??!?11/!?1?>!"
def wait_status_to_string(status): def num_to_sig(num): sigs = ["SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGSYS"] if num-1 < len(sigs): return sigs[num-1] else: return str(num) ff = [os.WCOREDUMP, os.WIFSTOPPED, os.WIFSIGNALED, os.WIFEXITED, os.WIFCONTINUED] status_list = [] status_list.append(str(status)) for f in ff: if f(status): status_list.append(f.__name__) status_list.append(num_to_sig(os.WEXITSTATUS(status))) status_list.append(num_to_sig(os.WSTOPSIG(status))) status_list.append(num_to_sig(os.WTERMSIG(status))) if(os.WIFSTOPPED(status)): ss = (status & 0xfff00) >> 8 ptrace_sigs = ["PTRACE_EVENT_FORK", "PTRACE_EVENT_VFORK", "PTRACE_EVENT_CLONE", "PTRACE_EVENT_EXEC", "PTRACE_EVENT_VFORK_DONE", "PTRACE_EVENT_EXIT"] if ss > 0 and ss-1 < len(ptrace_sigs): status_list.append(ptrace_sigs[ss-1]) return "|".join(status_list)
def _wait_for_child(self, childpid): while not self.__child_exited: # Could implement a maximum wait time here self.log.trace('waiting for child %d' % childpid) time.sleep(1.0) #raise getmailDeliveryError('failed waiting for commands %s %d (%s)' # % (self.conf['command'], childpid, o)) if self.__child_pid != childpid: #self.log.error('got child pid %d, not %d' % (pid, childpid)) raise getmailOperationError('got child pid %d, not %d' % (self.__child_pid, childpid)) if os.WIFSTOPPED(self.__child_status): raise getmailOperationError( 'child pid %d stopped by signal %d' % (self.__child_pid, os.WSTOPSIG(self.__child_status))) if os.WIFSIGNALED(self.__child_status): raise getmailOperationError( 'child pid %d killed by signal %d' % (self.__child_pid, os.WTERMSIG(self.__child_status))) if not os.WIFEXITED(self.__child_status): raise getmailOperationError('child pid %d failed to exit' % self.__child_pid) exitcode = os.WEXITSTATUS(self.__child_status) return exitcode
def _debugger_thread_inner(main_pid, dbgproc_started, dbgthread_stop, stack_request_pipe, stack_queue, syscall_queue, syscall_filter): ptrace_options = ptrace.PTRACE_O_TRACECLONE # Attach to the tracee and wait for it to stop. ptrace.attach_and_wait(main_pid, ptrace_options) if syscall_filter is not None: filter_ = lambda sc: any(m.match(sc) for m in syscall_filter) else: filter_ = None syscall_trap = signal.SIGTRAP | 0x80 enabled = False signum = 0 syscall_state = {} sigstop_received = set() processes = {main_pid} mem_fds = {} mem_fds[main_pid] = _open_procmem(main_pid) # Notify the parent that we are ready to start tracing. dbgproc_started.set() try: # Restart the tracee and enter the tracing loop. ptrace.syscall(main_pid) while True: if dbgthread_stop.is_set(): break pid, status = ptrace.wait(-1) if os.WIFEXITED(status) or os.WIFSIGNALED(status): # Traced thread has died. processes.discard(pid) mem_fd = mem_fds.get(pid) if mem_fd is not None: try: os.close(mem_fd) except IOError: pass if not processes: break else: continue elif os.WIFSTOPPED(status): ptrace_event = ptrace.WPTRACEEVENT(status) if ptrace_event == ptrace.PTRACE_EVENT_CLONE: # A new thread has been created. new_pid = ptrace.geteventmsg(pid) # See the comment below for the explanation of this check. if new_pid not in sigstop_received: ptrace.wait_for_trace_stop(new_pid) try: ptrace.syscall(new_pid) except OSError as e: if e.errno != errno.ESRCH: # The new thread might have already died. raise else: sigstop_received.discard(new_pid) mem_fds[new_pid] = _open_procmem(new_pid) processes.add(new_pid) ptrace.syscall(pid) continue stopsig = os.WSTOPSIG(status) if stopsig != syscall_trap: # Signal-delivery-stop. # The special condition below is for cases when we # receive a SIGSTOP for a newly created thread _before_ # receiving the PTRACE_EVENT_CLONE event for its parent. # In this case we must not forward the signal, but # must record its receipt so that once we _do_ receive # PTRACE_EVENT_CLONE for the parent, we don't wait for # SIGSTOP in the child again. if (stopsig != signal.SIGSTOP or pid in processes or all(syscall.name != 'clone' for syscall in syscall_state.values() if syscall is not None)): # forward the signal signum = stopsig else: sigstop_received.add(pid) else: # Syscall-stop. syscall = syscall_state.get(pid) regs = ptrace.getregs(pid) mem_fd = mem_fds.get(pid) if syscall is None: # Syscall-enter-stop. syscall_state[pid] = ptrace.syscall_enter( pid, regs, mem_fd) else: # Syscall-exit-stop. ptrace.syscall_exit(syscall, regs, mem_fd) if enabled: # Stop tracing once the tracee executes # the magic open() in ptracer.disable(). stop_tracing = ( syscall.name == 'open' and syscall.args[0].value == b'\x03\x02\x01' ) or (syscall.name == 'openat' and syscall.args[1].value == b'\x03\x02\x01') if stop_tracing: break elif filter_ is None or filter_(syscall): # Wait for the traceback to arrive. os.write(stack_request_pipe, struct.pack('!Q', pid)) stack = stack_queue.get() if stack is None: ptrace.cont(pid) break syscall.traceback = stack syscall_queue.put_nowait(syscall) elif not enabled: # Start tracing once the tracee executes # the magic open() in ptracer.enable(). start_tracing = ( syscall.name == 'open' and syscall.args[0].value == b'\x01\x02\x03' ) or (syscall.name == 'openat' and syscall.args[1].value == b'\x01\x02\x03') if start_tracing: enabled = True syscall_state[pid] = None else: logger.error('unexpected status of traced process %s: %s', pid, status) # Continue until next syscall. ptrace.syscall(pid, signum) signum = 0 finally: for process in processes: try: ptrace.detach(process) except OSError as e: if e.errno == errno.ESRCH: pass else: raise for fd in mem_fds.values(): try: os.close(fd) except (OSError, IOError): pass
def isalive(self): '''This tests if the child process is running or not. This is non-blocking. If the child was terminated then this will read the exitstatus or signalstatus of the child. This returns True if the child process appears to be running or False if not. It can take literally SECONDS for Solaris to return the right status. ''' if self.terminated: return False if self.flag_eof: # This is for Linux, which requires the blocking form # of waitpid to get the status of a defunct process. # This is super-lame. The flag_eof would have been set # in read_nonblocking(), so this should be safe. waitpid_options = 0 else: waitpid_options = os.WNOHANG try: pid, status = os.waitpid(self.pid, waitpid_options) except OSError as e: # No child processes if e.errno == errno.ECHILD: raise PtyProcessError( 'isalive() encountered condition ' + 'where "terminated" is 0, but there was no child ' + 'process. Did someone else call waitpid() ' + 'on our process?') else: raise # I have to do this twice for Solaris. # I can't even believe that I figured this out... # If waitpid() returns 0 it means that no child process # wishes to report, and the value of status is undefined. if pid == 0: try: ### os.WNOHANG) # Solaris! pid, status = os.waitpid(self.pid, waitpid_options) except OSError as e: # pragma: no cover # This should never happen... if e.errno == errno.ECHILD: raise PtyProcessError( 'isalive() encountered condition ' + 'that should never happen. There was no child ' + 'process. Did someone else call waitpid() ' + 'on our process?') else: raise # If pid is still 0 after two calls to waitpid() then the process # really is alive. This seems to work on all platforms, except for # Irix which seems to require a blocking call on waitpid or select, # so I let read_nonblocking take care of this situation # (unfortunately, this requires waiting through the timeout). if pid == 0: return True if pid == 0: return True if os.WIFEXITED(status): self.status = status self.exitstatus = os.WEXITSTATUS(status) self.signalstatus = None self.terminated = True elif os.WIFSIGNALED(status): self.status = status self.exitstatus = None self.signalstatus = os.WTERMSIG(status) self.terminated = True elif os.WIFSTOPPED(status): raise PtyProcessError( 'isalive() encountered condition ' + 'where child process is stopped. This is not ' + 'supported. Is some other process attempting ' + 'job control with our child pid?') return False
class ParTestCase(unittest.TestCase): """A class whose instances are single test cases. The ParTestCase starts a new process for each test this enables the isolation of tests one from the other. """ def __init__(self, methodName='runTest'): """Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name. """ try: self.__testMethodName = methodName testMethod = getattr(self, methodName) self.__testMethodDoc = testMethod.__doc__ except AttributeError: raise ValueError, "no such test method in %s: %s" % \ (self.__class__, methodName) def shortDescription(self): """Returns a one-line description of the test, or None if no description has been provided. The default implementation of this method returns the first line of the specified test method's docstring. """ doc = self.__testMethodDoc return doc and string.strip(string.split(doc, "\n")[0]) or None def id(self): return "%s.%s" % (self.__class__, self.__testMethodName) def __str__(self): return "%s (%s)" % (self.__testMethodName, self.__class__) def __repr__(self): return "<%s testMethod=%s>" % \ (self.__class__, self.__testMethodName) def __call__(self, result=None): if result is None: result = self.defaultTestResult() result.startTest(self) testMethod = getattr(self, self.__testMethodName) try: result.createPipe() tpid = os.fork() if tpid == 0: # # The child processes should close the read side of the pipe. # result.closePipeRd() try: self.setUp() except KeyboardInterrupt: os._exit(-1) except: result.addError(self, self.__exc_info()) os._exit(0) ok = 0 try: testMethod() ok = 1 except self.failureException, e: result.addFailure(self, self.__exc_info()) except KeyboardInterrupt: os._exit(-1) except: result.addError(self, self.__exc_info()) try: self.tearDown() except KeyboardInterrupt: os._exit(-1) except: result.addError(self, self.__exc_info()) ok = 0 if ok: result.addSuccess(self) # # IMPORTANT NOTE: # child processses of the test processes (tpid), can throw # exceptions either explicitly or implicitly through assert_ # and other unittest functions. This means that they reach # the os._exit command below. This exit command avoids that # the exceptions propogate further 'up' in the code. # os._exit(0) # # The parent process should close the write side of the pipe. # result.closePipeWr() # # Set the watchdog # test_timeout = getattr(self, '_TEST_TIMEOUT', TEST_TIMEOUT) wd = WatchDog(timeout=test_timeout) try: try: cpid, status = os.waitpid(tpid, 0) except KeyboardInterrupt: raise except: result.addError(self, self.__exc_info()) finally: # # Turn of the watchdog # wd.close() # # Check the exit status. This part is wraped in a try caluse # so that I can use the addError method. # try: if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0: if os.WEXITSTATUS(status) == 255: raise KeyboardInterrupt else: raise TestError, 'The test process exited unexpectedly with code %d' % ( os.WEXITSTATUS(status) - 256) if os.WIFSTOPPED(status): sig = os.WSTOPSIG(status) if sig in SIGNALS_DICT.keys(): sig_str = '%s(%d)' % (SIGNALS_DICT[sig], sig) else: sig_str = 'None(%d)' % sig raise TestError, 'The test process stopped unexpectedly by signal %s' % sig_str if os.WIFSIGNALED(status): sig = os.WTERMSIG(status) if sig in SIGNALS_DICT.keys(): sig_str = '%s(%d)' % (SIGNALS_DICT[sig], sig) else: sig_str = 'None(%d)' % sig raise TestError, 'The test process terminated unexpectedly by signal %s' % sig_str except KeyboardInterrupt: raise except: result.addError(self, self.__exc_info())
try: os.execvp("./" + path, args) except Exception, e: # print traceback if exception occurs import traceback traceback.print_exc(file=os.sys.stderr) # always exit os._exit(1) else: # parent t = timeout while t >= 0: # wait for completion child_pid, status = os.waitpid(pid, os.WNOHANG) if child_pid == pid: if os.WIFSTOPPED(status) or \ os.WIFSIGNALED(status): return None elif os.WIFEXITED(status): return os.WEXITSTATUS(status) # wait for a second time.sleep(1) t -= 1 # not completed within timeout seconds TimedOut = 1 os.kill(pid, 9) os.kill(pid + 1, 9) from os import walk
def RunProgram(self, program, arguments, context, result): """Run the 'program'. 'program' -- The path to the program to run. 'arguments' -- A list of the arguments to the program. This list must contain a first argument corresponding to 'argv[0]'. 'context' -- A 'Context' giving run-time parameters to the test. 'result' -- A 'Result' object. The outcome will be 'Result.PASS' when this method is called. The 'result' may be modified by this method to indicate outcomes other than 'Result.PASS' or to add annotations.""" # Construct the environment. environment = self.MakeEnvironment(context) # Create the executable. if self.timeout >= 0: timeout = self.timeout else: # If no timeout was specified, we sill run this process in a # separate process group and kill the entire process group # when the child is done executing. That means that # orphaned child processes created by the test will be # cleaned up. timeout = -2 e = qm.executable.Filter(self.stdin, timeout) # Run it. exit_status = e.Run(arguments, environment, path=program) # If the process terminated normally, check the outputs. if sys.platform == "win32" or os.WIFEXITED(exit_status): # There are no causes of failure yet. causes = [] # The target program terminated normally. Extract the # exit code, if this test checks it. if self.exit_code is None: exit_code = None elif sys.platform == "win32": exit_code = exit_status else: exit_code = os.WEXITSTATUS(exit_status) # Get the output generated by the program. stdout = e.stdout stderr = e.stderr # Record the results. result["RoseTest.exit_code"] = str(exit_code) result["RoseTest.stdout"] = result.Quote(stdout) result["RoseTest.stderr"] = result.Quote(stderr) # Check to see if the exit code matches. if exit_code != self.exit_code: causes.append("exit_code") result["RoseTest.expected_exit_code"] = str(self.exit_code) # Validate the output. causes += self.ValidateOutput(stdout, stderr, result, arguments[len(arguments) - 1]) # If anything went wrong, the test failed. if causes: result.Fail("Unexpected %s." % string.join(causes, ", ")) elif os.WIFSIGNALED(exit_status): # The target program terminated with a signal. Construe # that as a test failure. signal_number = str(os.WTERMSIG(exit_status)) result.Fail("Program terminated by signal.") result["RoseTest.signal_number"] = signal_number # START MODIFICATION GMY 7/26/2006 # Get the output generated by the program. stdout = e.stdout stderr = e.stderr result["RoseTest.stdout"] = stdout result["RoseTest.stderr"] = stderr # END MODIFICATION GMY 7/26/2006 elif os.WIFSTOPPED(exit_status): # The target program was stopped. Construe that as a # test failure. signal_number = str(os.WSTOPSIG(exit_status)) result.Fail("Program stopped by signal.") result["RoseTest.signal_number"] = signal_number else: # The target program terminated abnormally in some other # manner. (This shouldn't normally happen...) result.Fail("Program did not terminate normally.")
raise DistutilsExecError, \ "command %r terminated by signal %d" % \ (cmd, os.WTERMSIG(status)) elif os.WIFEXITED(status): exit_status = os.WEXITSTATUS(status) if exit_status == 0: return # hey, it succeeded! else: if not DEBUG: cmd = executable raise DistutilsExecError, \ "command %r failed with exit status %d" % \ (cmd, exit_status) elif os.WIFSTOPPED(status): continue else: if not DEBUG: cmd = executable raise DistutilsExecError, \ "unknown error executing %r: termination status %d" % \ (cmd, status) def find_executable(executable, path=None): """Tries to find 'executable' in the directories listed in 'path'. A string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']. Returns the complete filename or None if not found.
def runCommandWithOutput(command): """ _runCommand_ Run the command without deadlocking stdout and stderr, echo all output to sys.stdout and sys.stderr Returns the exitCode and the a string containing std out & error """ # capture stdout and stderr from command child = Popen(command, shell=True, bufsize=1, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) child.stdin.close() # don't need to talk to child outfile = child.stdout outfd = outfile.fileno() errfile = child.stderr errfd = errfile.fileno() makeNonBlocking(outfd) # don't deadlock! makeNonBlocking(errfd) outdata = errdata = '' outeof = erreof = 0 output = '' while True: ready = select.select([outfd, errfd], [], []) # wait for input if outfd in ready[0]: try: outchunk = outfile.read() except Exception as ex: msg = "Unable to read stdout chunk... skipping" print(msg) outchunk = '' if outchunk == '': outeof = 1 sys.stdout.write(outchunk) output += outchunk if errfd in ready[0]: try: errchunk = errfile.read() except Exception as ex: msg = "Unable to read stderr chunk... skipping" print(msg, str(ex)) errchunk = "" if errchunk == '': erreof = 1 output += errchunk sys.stderr.write(errchunk) if outeof and erreof: break select.select([], [], [], .1) # give a little time for buffers to fill err = child.wait() if os.WIFEXITED(err): err = os.WEXITSTATUS(err) elif os.WIFSIGNALED(err): err = os.WTERMSIG(err) elif os.WIFSTOPPED(err): err = os.WSTOPSIG(err) return err, output
def main_run_server(args): import io import signal import socket benchmark_dir, socket_name, = args update_sys_path(benchmark_dir) # Socket I/O s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.bind(socket_name) s.listen(1) # Read and act on commands from socket while True: stdout_file = None try: conn, addr = s.accept() except KeyboardInterrupt: break try: fd, stdout_file = tempfile.mkstemp() os.close(fd) # Read command read_size, = struct.unpack('<Q', recvall(conn, 8)) command_text = recvall(conn, read_size) if sys.version_info[0] >= 3: command_text = command_text.decode('utf-8') # Parse command command = json.loads(command_text) action = command.pop('action') if action == 'quit': break elif action == 'preimport': # Import benchmark suite before forking. # Capture I/O to a file during import. with posix_redirect_output(stdout_file, permanent=False): for benchmark in disc_benchmarks(benchmark_dir, ignore_import_errors=True): pass # Report result with io.open(stdout_file, 'r', errors='replace') as f: out = f.read() out = json.dumps(out) if sys.version_info[0] >= 3: out = out.encode('utf-8') conn.sendall(struct.pack('<Q', len(out))) conn.sendall(out) continue benchmark_id = command.pop('benchmark_id') params_str = command.pop('params_str') profile_path = command.pop('profile_path') result_file = command.pop('result_file') timeout = command.pop('timeout') cwd = command.pop('cwd') if command: raise RuntimeError('Command contained unknown data: {!r}'.format(command_text)) # Spawn benchmark run_args = (benchmark_dir, benchmark_id, params_str, profile_path, result_file) pid = os.fork() if pid == 0: conn.close() sys.stdin.close() exitcode = 1 try: with posix_redirect_output(stdout_file, permanent=True): try: os.chdir(cwd) main_run(run_args) exitcode = 0 except BaseException as ec: import traceback traceback.print_exc() finally: os._exit(exitcode) # Wait for results # (Poll in a loop is simplest --- also used by subprocess.py) start_time = wall_timer() is_timeout = False while True: res, status = os.waitpid(pid, os.WNOHANG) if res != 0: break if timeout is not None and wall_timer() > start_time + timeout: # Timeout if is_timeout: os.kill(pid, signal.SIGKILL) else: os.kill(pid, signal.SIGTERM) is_timeout = True time.sleep(0.05) # Report result with io.open(stdout_file, 'r', errors='replace') as f: out = f.read() # Emulate subprocess if os.WIFSIGNALED(status): retcode = -os.WTERMSIG(status) elif os.WIFEXITED(status): retcode = os.WEXITSTATUS(status) elif os.WIFSTOPPED(status): retcode = -os.WSTOPSIG(status) else: # shouldn't happen, but fail silently retcode = -128 info = {'out': out, 'errcode': -256 if is_timeout else retcode} result_text = json.dumps(info) if sys.version_info[0] >= 3: result_text = result_text.encode('utf-8') conn.sendall(struct.pack('<Q', len(result_text))) conn.sendall(result_text) except KeyboardInterrupt: break finally: conn.close() if stdout_file is not None: os.unlink(stdout_file)
def platformProcessEvent(self, event): # Skim some linux specific events before passing to posix tid, status = event if os.WIFSTOPPED(status): sig = status >> 8 # Cant use os.WSTOPSIG() here... #print('STOPPED: %d %d %.8x %d' % (self.pid, tid, status, sig)) # Ok... this is a crazy little state engine that tries # to account for the discrepancies in how linux posts # signals to the debugger... # Thread Creation: # In each case below, the kernel may deliver # any of the 3 signals in any order... ALSO # (and more importantly) *if* the kernel sends # SIGSTOP to the thread first, the debugger # will get a SIGSTOP *instead* of SIG_LINUX_CLONE # ( this will go back and forth on *ONE BOX* with # the same kernel version... Finally squished it # because it presents more frequently ( 1 in 10 ) # on my new ARM linux dev board. WTF?!1?!one?!? ) # # Case 1 (SIG_LINUX_CLONE): # debugger gets SIG_LINUX CLONE as expected # and can then use ptrace(PT_GETEVENTMSG) # to get new TID and attach as normal # Case 2 (SIGSTOP delivered to thread) # Thread is already stoped and attached but # parent debugger doesn't know yet. We add # the tid to the stopped_cache so when the # kernel gets around to telling the debugger # we don't wait on him again. # Case 3 (SIGSTOP delivered to debugger) # In both case 2 and case 3, this will cause # the SIG_LINUX_CLONE to be skipped. Either # way, we should head down into thread attach. # ( The thread may be already stopped ) if sig == SIG_LINUX_SYSCALL: self.fireNotifiers(vtrace.NOTIFY_SYSCALL) elif sig == SIG_LINUX_EXIT: ecode = self.getPtraceEvent() >> 8 if tid == self.getPid(): self._fireExit(ecode) self.platformDetach() else: self.detachThread(tid, ecode) elif sig == SIG_LINUX_CLONE: # Handle a new thread here! newtid = self.getPtraceEvent() #print('CLONE (new tid: %d)' % newtid) self.attachThread(newtid, attached=True) elif sig == signal.SIGSTOP and tid != self.pid: #print('OMG IM THE NEW THREAD! %d' % tid) # We're not even a real event right now... self.runAgain() self._stopped_cache[tid] = True elif sig == signal.SIGSTOP: # If we are still 'exec()'ing, we havent hit the SIGTRAP # yet ( so our process info is still python, lets skip it ) if self.execing: self._stopped_hack = True self.setupPtraceOptions(tid) self.runAgain() elif self._stopped_hack: newtid = self.getPtraceEvent(tid) #print("WHY DID WE GET *ANOTHER* STOP?: %d" % tid) #print('PTRACE EVENT: %d' % newtid) self.attachThread(newtid, attached=True) else: # on first attach... self._stopped_hack = True self.setupPtraceOptions(tid) self.handlePosixSignal(sig) #FIXME eventually implement child catching! else: self.handlePosixSignal(sig) return v_posix.PosixMixin.platformProcessEvent(self, event)
def alive(self, recover=False): """ try to determine if the child process is still active. If not, mark the child as dead and close all IO descriptors etc ("func:`finalize`). If `recover` is `True` and the child is indeed dead, we attempt to re-initialize it (:func:`initialize`). We only do that for so many times (`self.recover_max`) before giving up -- at that point it seems likely that the child exits due to a re-occurring operations condition. Note that upstream consumers of the :class:`PTYProcess` should be careful to only use `recover=True` when they can indeed handle a disconnected/reconnected client at that point, i.e. if there are no assumptions on persistent state beyond those in control of the upstream consumers themselves. """ with self.rlock: # do we have a child which we can check? if self.child: wstat = None while True: # print 'waitpid %s' % self.child # hey, kiddo, whats up? try: wpid, wstat = os.waitpid(self.child, os.WNOHANG) # print 'waitpid %s : %s - %s' % (self.child, wpid, wstat) except OSError as e: if e.errno == errno.ECHILD: # child disappeared, go to zombie cleanup routine break raise ("waitpid failed on wait (%s)" % e) # did we get a note about child termination? if 0 == wpid: # print 'waitpid %s : %s - %s -- none' % (self.child, wpid, wstat) # nope, all is well - carry on return True # Yes, we got a note. # Well, maybe the child fooled us and is just playing dead? if os.WIFSTOPPED (wstat) or \ os.WIFCONTINUED (wstat) : # print 'waitpid %s : %s - %s -- stop/cont' % (self.child, wpid, wstat) # we don't care if someone stopped/resumed the child -- that is up # to higher powers. For our purposes, the child is alive. Ha! continue break # so its dead -- make sure it stays dead, to avoid zombie # apocalypse... # print "he's dead, honeybunny, jim is dead..." self.child = None self.finalize(wstat=wstat) # check if we can attempt a post-mortem revival though if not recover: # print 'not alive, not recover' # nope, we are on holy ground - revival not allowed. return False # we are allowed to revive! So can we try one more time... pleeeease?? # (for cats, allow up to 9 attempts; for Buddhists, always allow to # reincarnate, etc.) if self.recover_attempts >= self.recover_max: # nope, its gone for good - just report the sad news # print 'not alive, no recover anymore' return False # MEDIIIIC!!!! self.recover_attempts += 1 self.initialize() # well, now we don't trust the child anymore, of course! So we check # again. Yes, this is recursive -- but note that recover_attempts get # incremented on every iteration, and this will eventually lead to # call termination (tm). # print 'alive, or not alive? Check again!' return self.alive(recover=True)
errchunk = errfile.read() except Exception, ex: msg = "Unable to read stderr chunk... skipping" print msg, str(ex) errchunk = "" if errchunk == '': erreof = 1 sys.stderr.write(errchunk) if outeof and erreof: break select.select([], [], [], .1) # give a little time for buffers to fill err = child.wait() if os.WIFEXITED(err): return os.WEXITSTATUS(err) elif os.WIFSIGNALED(err): return os.WTERMSIG(err) elif os.WIFSTOPPED(err): return os.WSTOPSIG(err) return err def runCommandWithOutput(command): """ _runCommand_ Run the command without deadlocking stdout and stderr, echo all output to sys.stdout and sys.stderr Returns the exitCode and the a string containing std out & error """ child = popen2.Popen3(command, 1) # capture stdout and stderr from command
def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): global _cfg_target global _cfg_target_split log.info(' '.join(cmd)) if dry_run: return else: executable = cmd[0] exec_fn = search_path and os.execvp or os.execv env = None if sys.platform == 'darwin': if _cfg_target is None: _cfg_target = sysconfig.get_config_var( 'MACOSX_DEPLOYMENT_TARGET') or '' if _cfg_target: _cfg_target_split = [ int(x) for x in _cfg_target.split('.') ] if _cfg_target: cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target) if _cfg_target_split > [int(x) for x in cur_target.split('.')]: my_msg = '$MACOSX_DEPLOYMENT_TARGET mismatch: now "%s" but "%s" during configure' % ( cur_target, _cfg_target) raise DistutilsPlatformError(my_msg) env = dict(os.environ, MACOSX_DEPLOYMENT_TARGET=cur_target) exec_fn = search_path and os.execvpe or os.execve pid = os.fork() if pid == 0: try: if env is None: exec_fn(executable, cmd) else: exec_fn(executable, cmd, env) except OSError as e: if not DEBUG: cmd = executable sys.stderr.write('unable to execute %r: %s\n' % (cmd, e.strerror)) os._exit(1) if not DEBUG: cmd = executable sys.stderr.write('unable to execute %r for unknown reasons' % cmd) os._exit(1) else: while 1: try: pid, status = os.waitpid(pid, 0) except OSError as exc: import errno if exc.errno == errno.EINTR: continue if not DEBUG: cmd = executable raise DistutilsExecError, 'command %r failed: %s' % ( cmd, exc[-1]) if os.WIFSIGNALED(status): if not DEBUG: cmd = executable raise DistutilsExecError, 'command %r terminated by signal %d' % ( cmd, os.WTERMSIG(status)) elif os.WIFEXITED(status): exit_status = os.WEXITSTATUS(status) if exit_status == 0: return if not DEBUG: cmd = executable raise DistutilsExecError, 'command %r failed with exit status %d' % ( cmd, exit_status) elif os.WIFSTOPPED(status): continue else: if not DEBUG: cmd = executable raise DistutilsExecError, 'unknown error executing %r: termination status %d' % ( cmd, status) return
os.chdir('/') os.setsid() os.umask(0) try: pid = os.fork() if pid > 0: time.sleep(0.2) id, status = os.waitpid(pid, os.WNOHANG) if id == 0: sys.exit(0) print '-->', id, status print 'WCOREDUMP', os.WCOREDUMP(status) print 'WIFCONTINUED', os.WIFCONTINUED(status) print 'WIFSTOPPED', os.WIFSTOPPED(status) print 'WIFSIGNALED', os.WIFSIGNALED(status) print 'WIFEXITED', os.WIFEXITED(status) print 'WEXITSTATUS', os.WEXITSTATUS(status) print 'WSTOPSIG', os.WSTOPSIG(status) print 'WTERMSIG', os.WTERMSIG(status) sys.exit(0) except OSError, e: print >> sys.stderr, 'fork() ERROR:', e.errno, e.strerror sys.exit(1) pid = os.getpid() print 'River daemon pid', pid os.close(0)
def wait(self): """ blocks forever until the child finishes on its own, or is getting killed. Actully, we might just as well try to figure out what is going on on the remote end of things -- so we read the pipe until the child dies... """ output = "" # yes, for ever and ever... while True: try: output += self.read() except: break # yes, for ever and ever... while True: if not self.child: # this was quick ;-) return output # we need to lock, as the SIGCHLD will only arrive once with self.rlock: # hey, kiddo, whats up? try: wpid, wstat = os.waitpid(self.child, 0) except OSError as e: if e.errno == errno.ECHILD: # child disappeared self.exit_code = None self.exit_signal = None self.finalize() return output # no idea what happened -- it is likely bad raise se.NoSuccess("waitpid failed on wait") # did we get a note about child termination? if 0 == wpid: # nope, all is well - carry on continue # Yes, we got a note. # Well, maybe the child fooled us and is just playing dead? if os.WIFSTOPPED (wstat) or \ os.WIFCONTINUED (wstat) : # we don't care if someone stopped/resumed the child -- that is up # to higher powers. For our purposes, the child is alive. Ha! continue # not stopped, poor thing... - soooo, what happened?? But hey, # either way, its dead -- make sure it stays dead, to avoid # zombie apocalypse... self.child = None self.finalize(wstat=wstat) return output
def fork_worker(self, job): """Invoked by ``work`` method. ``fork_worker`` does the actual forking to create the child process that will process the job. It's also responsible for monitoring the child process and handling hangs and crashes. Finally, the ``process`` method actually processes the job by eventually calling the Job instance's ``perform`` method. """ logger.debug('picked up job') logger.debug('job details: %s' % job) self.before_fork(job) self.child = os.fork() if self.child: self._setproctitle("Forked %s at %s" % (self.child, datetime.datetime.now())) logger.info('Forked %s at %s' % (self.child, datetime.datetime.now())) try: start = datetime.datetime.now() # waits for the result or times out while True: pid, status = os.waitpid(self.child, os.WNOHANG) if pid != 0: if os.WIFEXITED(status) and os.WEXITSTATUS( status) == 0: break if os.WIFSTOPPED(status): logger.warning("Process stopped by signal %d" % os.WSTOPSIG(status)) else: if os.WIFSIGNALED(status): raise CrashError( "Unexpected exit by signal %d" % os.WTERMSIG(status)) raise CrashError("Unexpected exit status %d" % os.WEXITSTATUS(status)) time.sleep(0.5) now = datetime.datetime.now() if self.timeout and ((now - start).seconds > self.timeout): os.kill(self.child, signal.SIGKILL) os.waitpid(-1, os.WNOHANG) raise TimeoutError("Timed out after %d seconds" % self.timeout) except OSError as ose: import errno if ose.errno != errno.EINTR: raise ose except JobError: self._handle_job_exception(job) finally: # If the child process' job called os._exit manually we need to # finish the clean up here. if self.job(): self.done_working(job) logger.debug('done waiting') else: self._setproctitle("Processing %s since %s" % (job, datetime.datetime.now())) logger.info('Processing %s since %s' % (job, datetime.datetime.now())) self.after_fork(job) # re-seed the Python PRNG after forking, otherwise # all job process will share the same sequence of # random numbers random.seed() self.process(job) os._exit(0) self.child = None
def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): log.info(' '.join(cmd)) if dry_run: return executable = cmd[0] exec_fn = search_path and os.execvp or os.execv env = None if sys.platform == 'darwin': global _cfg_target, _cfg_target_split if _cfg_target is None: _cfg_target = sysconfig.get_config_var( 'MACOSX_DEPLOYMENT_TARGET') or '' if _cfg_target: _cfg_target_split = [int(x) for x in _cfg_target.split('.')] if _cfg_target: # ensure that the deployment target of build process is not less # than that used when the interpreter was built. This ensures # extension modules are built with correct compatibility values cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target) if _cfg_target_split > [int(x) for x in cur_target.split('.')]: my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: ' 'now "%s" but "%s" during configure' % (cur_target, _cfg_target)) raise DistutilsPlatformError(my_msg) env = dict(os.environ, MACOSX_DEPLOYMENT_TARGET=cur_target) exec_fn = search_path and os.execvpe or os.execve pid = os.fork() if pid == 0: # in the child try: if env is None: exec_fn(executable, cmd) else: exec_fn(executable, cmd, env) except OSError as e: if not DEBUG: cmd = executable sys.stderr.write("unable to execute %r: %s\n" % (cmd, e.strerror)) os._exit(1) if not DEBUG: cmd = executable sys.stderr.write("unable to execute %r for unknown reasons" % cmd) os._exit(1) else: # in the parent # Loop until the child either exits or is terminated by a signal # (ie. keep waiting if it's merely stopped) while True: try: pid, status = os.waitpid(pid, 0) except OSError as exc: if not DEBUG: cmd = executable raise DistutilsExecError( "command %r failed: %s" % (cmd, exc.args[-1])) if os.WIFSIGNALED(status): if not DEBUG: cmd = executable raise DistutilsExecError( "command %r terminated by signal %d" % (cmd, os.WTERMSIG(status))) elif os.WIFEXITED(status): exit_status = os.WEXITSTATUS(status) if exit_status == 0: return # hey, it succeeded! else: if not DEBUG: cmd = executable raise DistutilsExecError( "command %r failed with exit status %d" % (cmd, exit_status)) elif os.WIFSTOPPED(status): continue else: if not DEBUG: cmd = executable raise DistutilsExecError( "unknown error executing %r: termination status %d" % (cmd, status))
def _run(self, pid): # This is the entry point after running either `start` or `attach`. In # both cases we are already the tracer of `pid`, and the process is # running. # The initial tracee is the only tracee that may stop for other reasons # before `PTRACE_EVENT_STOP`, so we handle it specially here. If the # tracee was created with `Engine.start` then it will stop itself with # `SIGSTOP`, in which case we will observe group-stop. But # `WPTRACEEVENT(status) will also be `PTRACE_EVENT_STOP` in that case, # so there's no reason to distinguish between them. while True: pid_, status = self._wait() if pid != pid_: _debug('child <PID:%d> is not initial tracee' % pid_) continue e = WPTRACEEVENT(status) s = os.WSTOPSIG(status) if os.WIFSTOPPED(status) and e == PTRACE_EVENT_STOP: _debug('seized initial tracee <PID:%d>' % pid) self._new_tracee(pid) break _debug('still waiting for <PID:%d>' % pid) # If this is not group-stop (`e` != 0 and `s` != `SIGTRAP`), # event-stop (`e` != 0 and `s` == `SIGTRAP`) or syscall-stop (`s` & # 0x80), then it must be signal-stop. if not e and not s & 0x80: cont_signal = s else: cont_signal = 0 ptrace_cont(pid, cont_signal) # For a child to become a tracee two things must happen: 1) # `PTRACE_EVENT_STOP` is observed in the child and 2) # `PTRACE_EVENT_{FORK,VFORK,CLONE}`is observed in the parent. The first # condition ensures that the tracee is running and the second lets us # know the parent. # # In the case of the `clone` syscall we must also save the flags used, # as they decide the thread group and parent of the child. # # Caveat: I have only seen `PTRACE_EVENT_STOP` before # `PTRACE_EVENT_{FORK,VFORK,CLONE}` in the case of `vfork`, and not # consistently, but the ptrace man page doesn't say anything about the # order. Besides, there's no reason to rely on it anyway. # `stop_seen` records the children in whom we have observed # `PTRACE_EVENT_STOP`. stop_seen = set() # `parent_seen` maps children to their "parents". Here parent refers to # the process that spawned the child, not the child's parent as reported # by `getppid`. parent_seen = {} # `clone_flags` maps parents to the flags used in the `clone` syscall. # When `clone` is called the PID of the child is not yet known, so the # mapping cannot be from children. clone_flags = {} # This function creates a tracee if the conditions discussed above are # met. def maybe_tracee(pid): if pid not in stop_seen: _debug('PTRACE_EVENT_STOP has not yet been observed in ' '<PID:%d>' % pid) return if pid not in parent_seen: _debug('parent of <PID:%d> has not yet observed ' 'PTRACE_EVENT_{FORK,VFORK,CLONE}' % pid) return parent = parent_seen.pop(pid) cflags = clone_flags.pop(parent.pid, 0) self._new_tracee(pid, parent, cflags) while self.tracees: try: pid, status = self._wait() except OSError as e: if e.errno == errno.ECHILD: # This may happen if all the tracees are killed by SIGKILL, # so we didn't get a change to observe their death. _debug('no children, exiting') break raise _debug('wait() -> %d, %02x|%02x|%02x' % \ (pid, status >> 16, (status >> 8) & 0xff, status & 0xff)) if pid not in self.tracees: # The child is not a tracee. That can happen because 1) it was # created with follow mode disabled, and is not meant to be a # tracee, or 2) this is the first time we see the tracee in # which case we expect to observe `PTRACE_EVENT_STOP` # According to the `wait(2)` man page `WIFSTOPPED(status)` can # only be true if `wait` was called with `UNTRACED` or if the # child is a ptrace tracee. In the second case we must observe # `PTRACE_EVENT_STOP`. if os.WIFSTOPPED(status): assert WPTRACEEVENT(status) == PTRACE_EVENT_STOP, \ 'non-tracee child stopped without PTRACE_EVENT_STOP' assert pid not in stop_seen, \ 'already saw PTRACE_EVENT_STOP for child <PID:%d>' % pid stop_seen.add(pid) # Create and start tracee if we already know the parent maybe_tracee(pid) continue # OK, this is a proper tracee, continue to the real logic. First we # figure out what happened to it. tracee = self.tracees.get(pid) s = os.WSTOPSIG(status) e = WPTRACEEVENT(status) # When we continue the tracee, this is the signal we should send it. cont_signal = 0 # Why did `wait` return this tracee? stopped = False signalled = False continued = False exited = False # If the tracee was stopped, which kind of stop was it? signal_stop = False group_stop = False event_stop = False syscall_stop = False # This will be set if we have event-stop. event = None # Tracers can return a value on syscall-enter in which case the # syscall is "emulated" # XXX: For some reason I couldn't get ptrace_sysemu to work, so I'm # XXX: using User-Mode-Linux's trick and replacing it by a syscall # XXX: to `getpid` instead. See code further down. # XXX: Link to UML's SYSEMU patches: http://sysemu.sourceforge.net/ sysemu = False # Here we just set the variables. The real logic follows below. if os.WIFSTOPPED(status): stopped = True if s == SIGTRAP | 0x80: assert e == 0, \ 'WPTRACEEVENT(status) should be 0 in syscall-stop' syscall_stop = True syscall = tracee.syscall elif e: if s == SIGTRAP: event_stop = True event = e else: assert e == PTRACE_EVENT_STOP, \ 'WPTRACEEVENT(status) should be ' \ 'PTRACE_EVENT_STOP in group-stop' group_stop = True else: signal_stop = True tracee.siginfo._init() siginfo = tracee.siginfo cont_signal = s if os.WIFSIGNALED(status): signalled = True signal = os.WTERMSIG(status) if os.WIFCONTINUED(status): continued = True if os.WIFEXITED(status): exited = True status = os.WEXITSTATUS(status) # Tracee exited or was killed by a signal, so remove it. No need to # report this exit as we have already done so when we got # `PTRACE_EVENT_EXIT`. # # Caveat: At the moment (kernel 4.5.0) tracees stop in event-stop # with `PTRACE_EVENT_EXIT` even if they are killed by `SIGKILL`. # According to the man page that may change in the future. We # handle that hypothetical situation below, if ptrace fails with # `ESRCH` when we continue the tracee. if exited or signalled: _debug('<PID:%d> terminated' % pid) self._del_tracee(tracee) continue # Log events. if event: _debug('event %d:%s' % (event, event_names[event])) # Handle `execve`'s: when a thread which is not the thread group # leader executes `execve`, all other threads in the thread group # die and the `execve`'ing thread becomes leader. This code must be # executed early as `tracee` is in fact the wrong tracee at this # point, and we need to correct that. if event == PTRACE_EVENT_EXEC: oldpid = ptrace_geteventmsg(pid) if pid != oldpid: _debug('repid (%d -> %d)' % (oldpid, pid)) # Neither this tracee nor the thread group leader will # report death, so we must do the clean-up here self._del_tracee(tracee) # This is the correct tracee, it just changed its pid tracee = self.tracees.pop(oldpid) tracee.pid = pid tracee.thread_group = set([tracee]) tracee.tgid = tracee.pid self.tracees[pid] = tracee self._run_callbacks('repid', tracee, oldpid) # We're entering or exiting a syscall so update `in_syscall`. # Invariant: a callback for `syscall` will always see `in_syscall` # as being true, and a callback for `syscall_return` will always see # it as being false. # # This check must be placed here, before the personality is # detected, because a syscall on Linux x86_64 (XXX: and others?) be # run in 32 bit mode depending on how it was called (specifically # through `int 0x80` or 32 bit `syscall` [which apparently only # exists on AMD CPU's and is all but undocumented; see comment in # `/linux/arch/x86/entry/entry_32.S`]). if syscall_stop: # Go from syscall to not in syscall or vice versa tracee.in_syscall ^= True # See if the tracee changed personality. This check may depend on # `in_syscall` (see comment above). self._detect_personality(tracee) # OK, now all the tracee's state variables has been set and we're # ready to fire callbacks, etc. # Trigger single step callbacks. We do not reset # `_was_singlestepped` here because we need it to be set in order to # suppress callbacks for `SIGTRAP`. if tracee._was_singlestepped: _debug('step') self._run_callbacks('step', tracee) # This tracee was stopped, but now it's running again! if not tracee.is_running: tracee.is_running = True self._run_callbacks('cont', tracee) # Handle syscalls. if syscall_stop: # This is syscall-enter. if tracee.in_syscall: # The `nr` and `name` attributes are what the tracer sees # and not the real values in case of a restarted or emulated # syscall. realnr = syscall._get_nr() realname = tracee.syscalls.syscall_names[realnr] _debug('syscal-enter %d:%s' % (realnr, realname)) if self.trace_restart or realname != 'restart_syscall': # Initialize syscall object. syscall._init() args = syscall.args retval = self._run_syscall_callbacks(tracee) # We were supposed to enter a syscall, but a tracer # returned a value, so we'll "emulate" the syscall # instead. if retval != None: _debug('emulating syscall %d:%s -> 0x%x' % \ (syscall.nr, syscall.name, retval)) # XXX: For some reason `ptrace_sysemu` doesn't seem # XXX: to work for me, so I replace the syscall with # XXX: a "nop" syscall in the form of `getpid` syscall.emulated = True syscall.emu_nr = syscall.nr syscall.emu_retval = retval syscall.name = 'getpid' # We are about to enter an un-emulated (if it was # emulated `name` would be "getpid") `clone` syscall and # we want to follow the child. We must save the flags # used in the syscall so we can correctly set the parent # and thread group of the newly created process/thread # when it arrives. Also, if the `CLONE_UNTRACED` flag # is set, we unset it so we become a tracer of the # child. This check needs to be placed here, after the # callbacks have run, as the syscall may be changed or # simulated by a tracer. `CLONE_UNTRACED` is defined in # /usr/include/linux/sched.h CLONE_UNTRACED = 0x00800000 if self.follow and syscall.name == 'clone': if syscall.args[0] & CLONE_UNTRACED: _debug('removed CLONE_UNTRACED in clone ' \ 'syscall') clone_flags[pid] = syscall.args[0] syscall.args[0] &= ~CLONE_UNTRACED else: _debug('ignoring syscall-enter due to restart') # This is syscall-exit. else: _debug('syscall-exit %d:%s -> %#x' % \ (syscall.nr, syscall.name, syscall.retval)) if self.trace_restart or \ syscall.retval not in RETVAL_RESTART: # Finalize syscall object, i.e. stop the timer. syscall._fini() # This is the initial tracee returning from `execve` if self._wait_initial: self._wait_initial = False self._run_callbacks('birth', tracee) else: # Set syscall number and return value if the syscall # was "emulated", i.e. `getpid`. if syscall.emulated: syscall.nr = syscall.emu_nr syscall.retval = syscall.emu_retval retval = self._run_syscall_callbacks(tracee) if retval != None: _debug('overriding syscall %d:%s -> 0x%x' % \ (syscall.nr, syscall.name, retval)) syscall.retval = retval # The `emulated` flag is reset here, after the # callbacks have run, so they can see whether the # syscall was emulated or not. syscall.emulated = False else: _debug('ignoring syscall-exit due to restart') # Run callbacks for signals and single stepping. elif signal_stop: # A single stepped tracee will signal-stop with `SIGTRAP` when # executing the next instruction, so we need to suppress that # signal. Otherwise run callbacks and deliver signals as usual. if siginfo.signo == SIGTRAP and tracee._was_singlestepped: cont_signal = 0 else: _debug('signal %d:%s' % (siginfo.signo, siginfo.signame)) retval = self._run_signal_callbacks(tracee) if retval != None: if retval == 0: _debug('supressing signal %d:%s' % \ (siginfo.signo, siginfo.signame)) else: _debug('overriding signal %d:%s -> %d:%s' % \ (siginfo.signo, siginfo.signame, retval, signal_names.get(retval, 'SIG???'))) cont_signal = retval # Ditto for group-stops. elif group_stop: _debug('group-stop') tracee.is_running = False self._run_callbacks('stop', tracee) # Handle births. elif event in PTRACE_EVENTS_FOLLOW: newpid = ptrace_geteventmsg(pid) # Even if the child is not the result of a `clone` syscall, we # may have saved clone flags if a previous `clone` failed. In # that case we must remove the stale flags. if event != PTRACE_EVENT_CLONE: clone_flags.pop(pid, None) # Record this tracee as the parent. parent_seen[newpid] = tracee # And finally create and start the new tracee if # `PTRACE_EVENT_STOP` was already seen (as mentioned above, I've # only seen this behavior from `vfork`). maybe_tracee(newpid) # Handle deaths. elif event == PTRACE_EVENT_EXIT: status = ptrace_geteventmsg(pid) tracee.is_running = False tracee.is_alive = False self._run_callbacks('death', tracee, status) # Should the tracee be single stepped? do_singlestep = self.singlestep or tracee.singlestep or \ tracee.singlesteps > 0 # If so, we need a to know whether we're about to make a syscall or # not, and figuring that out probably requires reading the tracee's # registers and/or memory, so we should do it here, before we flush # the register, memory and siginfo caches. if do_singlestep: at_syscall = tracee.at_syscall # And now we can flush them. tracee._cacheflush() # Continue the tracee. try: if group_stop: ptrace_listen(pid) elif sysemu: # XXX: See comments about `ptrace_sysemu` above. ptrace_sysemu(pid, cont_signal) elif tracee._do_detach: _debug('detached <PID:%d>' % pid) ptrace_detach(pid, cont_signal) # Continue the tracee. _tgkill(tracee.tgid, tracee.tid, SIGCONT) elif do_singlestep: # For each tracee we record whether it was single stepped # since any of the variables above may change before we # observe SIGTRAP and thus cannot be relied on tracee._was_singlestepped = True # We decrement `singlesteps` here so changes made by later # callbacks will not be affected. if tracee.singlesteps > 0: tracee.singlesteps -= 1 # If we're entering or exiting a syscall we must continue # the tracee with `PTRACE_SYSCALL` in order to observe that. if at_syscall or tracee.in_syscall: ptrace_syscall(pid, cont_signal) else: ptrace_singlestep(pid, cont_signal) else: tracee._was_singlestepped = False ptrace_syscall(pid, cont_signal) except OSError as e: if e.errno == errno.ESRCH: # This doesn't happen at the moment (kernel 4.5.0), but it # may in the future. See the BUGS section in the ptrace man # page. del self.tracees[pid] self._run_callbacks('kill', tracee) else: raise self._run_callbacks('finish')
def ikos_analyzer(db_path, pp_path, opt): if settings.BUILD_MODE == 'Debug': log.warning('ikos was built in debug mode, the analysis might be slow') # Fix huge slow down when ikos-analyzer uses DROP TABLE on an existing db if os.path.isfile(db_path): os.remove(db_path) cmd = [settings.ikos_analyzer()] # analysis options cmd += [ '-a=%s' % ','.join(opt.analyses), '-d=%s' % opt.domain, '-entry-points=%s' % ','.join(opt.entry_points), '-globals-init=%s' % opt.globals_init, '-proc=%s' % opt.procedural, '-j=%d' % opt.jobs, '-widening-strategy=%s' % opt.widening_strategy, '-widening-delay=%d' % opt.widening_delay, '-widening-period=%d' % opt.widening_period ] if opt.narrowing_strategy == 'auto': if opt.domain in domains_without_narrowing: cmd.append('-narrowing-strategy=meet') else: cmd.append('-narrowing-strategy=narrow') else: cmd.append('-narrowing-strategy=%s' % opt.narrowing_strategy) if opt.narrowing_iterations is not None: cmd.append('-narrowing-iterations=%d' % opt.narrowing_iterations) elif (opt.narrowing_strategy == 'auto' and opt.domain in domains_without_narrowing): cmd.append('-narrowing-iterations=%d' % args.meet_iterations_if_no_narrowing) if opt.widening_delay_functions: cmd.append('-widening-delay-functions=%s' % ','.join(opt.widening_delay_functions)) if opt.no_init_globals: cmd.append('-no-init-globals=%s' % ','.join(opt.no_init_globals)) if opt.no_liveness: cmd.append('-no-liveness') if opt.no_pointer: cmd.append('-no-pointer') if opt.no_widening_hints: cmd.append('-no-widening-hints') if opt.partitioning != 'no': cmd.append('-enable-partitioning-domain') if opt.no_fixpoint_cache: cmd.append('-no-fixpoint-cache') if opt.no_checks: cmd.append('-no-checks') if opt.hardware_addresses: cmd.append('-hardware-addresses=%s' % ','.join(opt.hardware_addresses)) if opt.hardware_addresses_file: cmd.append('-hardware-addresses-file=%s' % opt.hardware_addresses_file) if opt.argc is not None: cmd.append('-argc=%d' % opt.argc) # import options cmd.append('-allow-dbg-mismatch') if opt.no_bc_verify: cmd.append('-no-verify') if opt.no_libc: cmd.append('-no-libc') if opt.no_libcpp: cmd.append('-no-libcpp') if opt.no_libikos: cmd.append('-no-libikos') # AR passes options if opt.no_type_check: cmd.append('-no-type-check') if opt.no_simplify_cfg: cmd.append('-no-simplify-cfg') if opt.no_simplify_upcast_comparison: cmd.append('-no-simplify-upcast-comparison') if 'gauge' in opt.domain: cmd.append('-add-loop-counters') if opt.partitioning == 'return': cmd.append('-add-partitioning-variables') # debug options cmd += [ '-display-checks=%s' % opt.display_checks, '-display-inv=%s' % opt.display_inv ] if opt.display_ar: cmd.append('-display-ar') if opt.display_liveness: cmd.append('-display-liveness') if opt.display_function_pointer: cmd.append('-display-function-pointer') if opt.display_pointer: cmd.append('-display-pointer') if opt.display_fixpoint_parameters: cmd.append('-display-fixpoint-parameters') if opt.generate_dot: cmd += ['-generate-dot', '-generate-dot-dir', opt.generate_dot_dir] # add -name-values if necessary if (opt.display_checks in ('all', 'fail') or opt.display_inv in ('all', 'fail') or opt.display_liveness or opt.display_fixpoint_parameters or opt.display_function_pointer or opt.display_pointer or opt.display_raw_checks): cmd.append('-name-values') # misc. options if opt.color == 'yes': cmd.append('-color=1') elif opt.color == 'no': cmd.append('-color=0') cmd.append('-log=%s' % opt.log_level) cmd.append('-progress=%s' % opt.progress) # input/output cmd += [pp_path, '-o', db_path] # set resource limit, if requested if opt.mem: import resource # fails on Windows def set_limits(): mem_bytes = opt.mem * 1024 * 1024 resource.setrlimit(resource.RLIMIT_AS, [mem_bytes, mem_bytes]) else: set_limits = None # called after timeout def kill(p): try: log.error('Timeout') p.send_signal(signal.SIGALRM) except OSError: pass log.info('Running ikos analyzer') log.debug('Running %s' % command_string(cmd)) p = subprocess.Popen(cmd, preexec_fn=set_limits) timer = threading.Timer(opt.cpu, kill, [p]) if opt.cpu: timer.start() try: if sys.platform.startswith('win'): return_status = p.wait() else: _, return_status = os.waitpid(p.pid, 0) finally: # kill the timer if the process has terminated already if timer.isAlive(): timer.cancel() # special case for Windows, since it does not define WIFEXITED & co. if sys.platform.startswith('win'): if return_status != 0: raise AnalyzerError('a run-time error occurred', cmd, return_status) else: return # if it did not terminate properly, propagate this error code if os.WIFEXITED(return_status) and os.WEXITSTATUS(return_status) != 0: exit_status = os.WEXITSTATUS(return_status) raise AnalyzerError('a run-time error occurred', cmd, exit_status) if os.WIFSIGNALED(return_status): signum = os.WTERMSIG(return_status) raise AnalyzerError('exited with signal %s' % signal_name(signum), cmd, signum) if os.WIFSTOPPED(return_status): signum = os.WSTOPSIG(return_status) raise AnalyzerError('exited with signal %d' % signal_name(signum), cmd, signum)
if pid == 0: os.execv(sys.argv[1], sys.argv[1:]) print('execv didnt work', file=sys.stderr) else: ''' At the end of the loop we get an exception when the connection with the other side terminates. This is kinda ugly since strictly speaking this is not an error, but oh well. ''' bufsize = 1024 buf = os.read(fd, bufsize).decode() over = False while len(buf) > 0 and not over: # print('got buf [{0}]'.format(buf)) lines = buf.split('\n') buf = lines[-1] del lines[-1] for line in lines: line += '\n' print('got line [{0}]'.format(line)) try: buf += os.read(fd, bufsize).decode() except OSError as e: over = True (pid, ret) = os.wait() if os.WIFEXITED(ret): print('proc exited and status was [{}]'.format(os.WEXITSTATUS(ret))) if os.WIFSTOPPED(ret): print('proc stopped and stopsig was [{}]'.format(os.WSTOPSIG(ret))) if os.WIFSIGNALED(ret): print('proc signaled and signal was [{}]'.format(os.WTERMSIG(ret)))