blob: 2bce44a8d558b361bbcb2472ffa015e7b1fa21a7 [file] [edit]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "RecordTask.h"
#include <dirent.h>
#include <elf.h>
#include <limits.h>
#include <linux/perf_event.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include "AutoRemoteSyscalls.h"
#include "PreserveFileMonitor.h"
#include "RecordSession.h"
#include "WaitManager.h"
#include "core.h"
#include "kernel_abi.h"
#include "kernel_metadata.h"
#include "log.h"
#include "record_signal.h"
#include "rr/rr.h"
#include "util.h"
using namespace std;
namespace rr {
/**
* Stores the table of signal dispositions and metadata for an
* arbitrary set of tasks. Each of those tasks must own one of
* the |refcount|s while they still refer to this.
*/
struct Sighandler {
Sighandler() : resethand(false), takes_siginfo(false) {}
template <typename Arch>
void init_arch(const typename Arch::kernel_sigaction& ksa) {
k_sa_handler = ksa.k_sa_handler;
sa.resize(sizeof(ksa));
memcpy(sa.data(), &ksa, sizeof(ksa));
resethand = (ksa.sa_flags & SA_RESETHAND) != 0;
takes_siginfo = (ksa.sa_flags & SA_SIGINFO) != 0;
}
template <typename Arch> void reset_arch() {
typename Arch::kernel_sigaction ksa;
memset(&ksa, 0, sizeof(ksa));
DEBUG_ASSERT(uintptr_t(SIG_DFL) == 0);
init_arch<Arch>(ksa);
}
SignalDisposition disposition() const {
DEBUG_ASSERT(uintptr_t(SIG_DFL) == 0);
DEBUG_ASSERT(uintptr_t(SIG_IGN) == 1);
switch (k_sa_handler.as_int()) {
case 0:
return SIGNAL_DEFAULT;
case 1:
return SIGNAL_IGNORE;
default:
return SIGNAL_HANDLER;
}
}
remote_code_ptr get_user_handler() const {
return disposition() == SIGNAL_HANDLER
? remote_code_ptr(k_sa_handler.as_int())
: remote_code_ptr();
}
remote_ptr<void> k_sa_handler;
// Saved kernel_sigaction; used to restore handler
vector<uint8_t> sa;
bool resethand;
bool takes_siginfo;
};
static void reset_handler(Sighandler* handler, SupportedArch arch) {
RR_ARCH_FUNCTION(handler->reset_arch, arch);
}
struct Sighandlers {
typedef shared_ptr<Sighandlers> shr_ptr;
shr_ptr clone() const {
shr_ptr s(new Sighandlers());
// NB: depends on the fact that Sighandler is for all
// intents and purposes a POD type, though not
// technically.
for (size_t i = 0; i < array_length(handlers); ++i) {
s->handlers[i] = handlers[i];
}
return s;
}
Sighandler& get(int sig) {
assert_valid(sig);
return handlers[sig];
}
const Sighandler& get(int sig) const {
assert_valid(sig);
return handlers[sig];
}
void init_from_current_process() {
for (size_t i = 1; i < array_length(handlers); ++i) {
Sighandler& h = handlers[i];
NativeArch::kernel_sigaction sa;
if (::syscall(SYS_rt_sigaction, i, nullptr, &sa, sizeof(uint64_t))) {
/* EINVAL means we're querying an
* unused signal number. */
DEBUG_ASSERT(EINVAL == errno);
continue;
}
msan_unpoison(&sa, sizeof(NativeArch::kernel_sigaction));
h.init_arch<NativeArch>(sa);
}
}
/**
* For each signal in |table| such that is_user_handler() is
* true, reset the disposition of that signal to SIG_DFL, and
* clear the resethand flag if it's set. SIG_IGN signals are
* not modified.
*
* (After an exec() call copies the original sighandler table,
* this is the operation required by POSIX to initialize that
* table copy.)
*/
void reset_user_handlers(SupportedArch arch) {
for (int i = 0; i < ssize_t(array_length(handlers)); ++i) {
Sighandler& h = handlers[i];
// If the handler was a user handler, reset to
// default. If it was SIG_IGN or SIG_DFL,
// leave it alone.
if (h.disposition() == SIGNAL_HANDLER) {
reset_handler(&h, arch);
}
}
}
void assert_valid(int sig) const {
DEBUG_ASSERT(0 < sig && sig < ssize_t(array_length(handlers)));
}
static shr_ptr create() { return shr_ptr(new Sighandlers()); }
Sighandler handlers[_NSIG];
private:
Sighandlers() {}
Sighandlers(const Sighandlers&);
Sighandlers operator=(const Sighandlers&);
};
RecordTask::RecordTask(RecordSession& session, pid_t _tid, uint32_t serial,
SupportedArch a)
: Task(session, _tid, _tid, serial, a),
scheduler_token(0),
ticks_at_last_recorded_syscall_exit(0),
ip_at_last_recorded_syscall_exit(nullptr),
time_at_start_of_last_timeslice(0),
priority(0),
in_round_robin_queue(false),
stable_exit(false),
detached_proxy(false),
emulated_ptracer(nullptr),
emulated_ptrace_event_msg(0),
emulated_ptrace_options(0),
emulated_ptrace_cont_command(0),
emulated_stop_pending(false),
emulated_ptrace_SIGCHLD_pending(false),
emulated_SIGCHLD_pending(false),
emulated_ptrace_seized(false),
in_wait_type(WAIT_TYPE_NONE),
in_wait_pid(0),
emulated_stop_type(NOT_STOPPED),
blocked_sigs_dirty(true),
syscallbuf_blocked_sigs_generation(0),
flushed_num_rec_bytes(0),
flushed_syscallbuf(false),
delay_syscallbuf_reset_for_desched(false),
delay_syscallbuf_reset_for_seccomp_trap(false),
prctl_seccomp_status(0),
robust_futex_list_len(0),
termination_signal(0),
tsc_mode(PR_TSC_ENABLE),
cpuid_mode(1),
stashed_signals_blocking_more_signals(false),
stashed_group_stop(false),
break_at_syscallbuf_traced_syscalls(false),
break_at_syscallbuf_untraced_syscalls(false),
break_at_syscallbuf_final_instruction(false),
syscallstub_exit_breakpoint(),
next_pmc_interrupt_is_for_user(false),
did_record_robust_futex_changes(false),
waiting_for_reap(false),
waiting_for_ptrace_exit(false),
retry_syscall_patching(false),
sent_shutdown_kill(false),
did_execveat(false),
tick_request_override((TicksRequest)0),
schedule_frozen(false) {
push_event(Event::sentinel());
if (session.tasks().empty()) {
// Initial tracee. It inherited its state from this process, so set it up.
// The very first task we fork inherits the signal
// dispositions of the current OS process (which should all be
// default at this point, but ...). From there on, new tasks
// will transitively inherit from this first task.
auto sh = Sighandlers::create();
sh->init_from_current_process();
sighandlers.swap(sh);
own_namespace_rec_tid = _tid;
}
}
RecordTask::~RecordTask() {
if (emulated_ptracer) {
emulated_ptracer->emulated_ptrace_tracees.erase(this);
if (emulated_ptrace_options & PTRACE_O_TRACEEXIT) {
ASSERT(this, stable_exit)
<< "PTRACE_O_TRACEEXIT only supported for stable exits for now";
}
}
for (RecordTask* t : emulated_ptrace_tracees) {
// XXX emulate PTRACE_O_EXITKILL
ASSERT(this, t->emulated_ptracer == this);
t->emulated_ptracer = nullptr;
t->emulated_ptrace_options = 0;
t->emulated_stop_pending = false;
t->emulated_stop_type = NOT_STOPPED;
}
// We expect tasks to usually exit by a call to exit() or
// exit_group(), so it's not helpful to warn about that.
if (EV_SENTINEL != ev().type() &&
(pending_events.size() > 2 ||
!(ev().type() == EV_SYSCALL &&
(is_exit_syscall(ev().Syscall().number, ev().Syscall().regs.arch()) ||
is_exit_group_syscall(ev().Syscall().number,
ev().Syscall().regs.arch()))))) {
LOG(info) << tid << " still has pending events. From top down:";
log_pending_events();
}
if (detached_proxy) {
// Kill it so that proceed_to_exit will reach PTRACE_EVENT_EXIT.
::kill(tid, SIGKILL);
// We kept the zombie of the original task around to prevent its pid from
// being re-used. Reap that now.
proceed_to_exit();
if (!was_reaped() && may_reap()) {
reap();
}
did_kill();
}
// If this was stopped, notify the scheduler.
set_stopped(false);
}
void RecordTask::record_exit_event(WriteChildTid write_child_tid) {
// The kernel explicitly only clears the futex if the address space is shared.
// If the address space has no other users then the futex will not be cleared
// even if it lives in shared memory which other tasks can read.
if (!tid_futex.is_null() && as->task_set().size() > 1 &&
as->has_mapping(tid_futex)) {
int val = 0;
record_local(tid_futex, &val);
if (write_child_tid == WRITE_CHILD_TID) {
// Write the memory now, otherwise the kernel will write it later and that can
// race with the execution of other threads if we don't wait for this
// thread to fully exit.
// This could fail since the address space might have gone away/been switched
// by execve.
bool ok = true;
write_mem(tid_futex, 0, &ok);
// The kernel will do an unconditional futex wake on that location so we don't
// need to do it.
}
}
// Write the exit event here so that the value recorded above is captured.
// Don't flush syscallbuf. Whatever triggered the exit (syscall, signal)
// should already have flushed it, if it was running. If it was blocked,
// then the syscallbuf would already have been flushed too. Trying to flush
// syscallbuf for an exiting task could be bad,
// e.g. it could be in the middle of syscallbuf code that's supposed to be
// atomic. For the same reasons don't allow syscallbuf to be reset here.
record_event(Event::exit(), DONT_FLUSH_SYSCALLBUF, DONT_RESET_SYSCALLBUF);
}
RecordSession& RecordTask::session() const {
return *Task::session().as_record();
}
TraceWriter& RecordTask::trace_writer() const {
return session().trace_writer();
}
Task* RecordTask::clone(CloneReason reason, int flags, remote_ptr<void> stack,
remote_ptr<void> tls, remote_ptr<int> cleartid_addr,
pid_t new_tid, pid_t new_rec_tid, uint32_t new_serial,
Session* other_session, FdTable::shr_ptr new_fds,
ThreadGroup::shr_ptr new_tg) {
ASSERT(this, reason == Task::TRACEE_CLONE);
ASSERT(this, !new_fds);
ASSERT(this, !new_tg);
Task* t = Task::clone(reason, flags, stack, tls, cleartid_addr, new_tid,
new_rec_tid, new_serial, other_session, new_fds,
new_tg);
if (t->session().is_recording()) {
RecordTask* rt = static_cast<RecordTask*>(t);
if (CLONE_CLEARTID & flags) {
LOG(debug) << "cleartid futex is " << cleartid_addr;
ASSERT(this, !cleartid_addr.is_null());
rt->tid_futex = cleartid_addr;
} else {
LOG(debug) << "(clone child not enabling CLEARTID)";
}
}
return t;
}
void RecordTask::post_wait_clone(Task* cloned_from, int flags) {
ASSERT(cloned_from, cloned_from->session().is_recording());
Task::post_wait_clone(cloned_from, flags);
RecordTask* rt = static_cast<RecordTask*>(cloned_from);
priority = rt->priority;
syscallbuf_code_layout = rt->syscallbuf_code_layout;
prctl_seccomp_status = rt->prctl_seccomp_status;
robust_futex_list = rt->robust_futex_list;
robust_futex_list_len = rt->robust_futex_list_len;
tsc_mode = rt->tsc_mode;
cpuid_mode = rt->cpuid_mode;
if (CLONE_SHARE_SIGHANDLERS & flags) {
sighandlers = rt->sighandlers;
} else {
auto sh = rt->sighandlers->clone();
sighandlers.swap(sh);
}
update_own_namespace_tid();
}
void RecordTask::post_exec() {
// Change syscall number to execve/execveat *for the new arch*. If we don't do this,
// and the arch changes, then the syscall number for execve in the old arch/
// is treated as the syscall we're executing in the new arch, with hilarious
// results.
int new_syscallno = did_execveat ? syscall_number_for_execveat(arch())
: syscall_number_for_execve(arch());
registers.set_original_syscallno(new_syscallno);
// Fix event architecture and syscall number
ev().Syscall().number = new_syscallno;
ev().Syscall().set_arch(arch());
// The signal mask is inherited across execve so we don't need to invalidate.
Task::post_exec(exe_path());
if (emulated_ptracer) {
ASSERT(this, !(emulated_ptracer->arch() == x86 && arch() == x86_64))
<< "We don't support a 32-bit process tracing a 64-bit process";
}
// Clear robust_list state to match kernel state. If this task is cloned
// soon after exec, we must not do a bogus set_robust_list syscall for
// the clone.
set_robust_list(nullptr, 0);
sighandlers = sighandlers->clone();
sighandlers->reset_user_handlers(arch());
// Newly execed tasks always have non-faulting mode (from their point of
// view, even if rr is secretly causing faults).
cpuid_mode = 1;
}
template <typename Arch> static void do_preload_init_arch(RecordTask* t) {
auto params = t->read_mem(
remote_ptr<rrcall_init_preload_params<Arch>>(t->regs().orig_arg1()));
t->syscallbuf_code_layout.syscallbuf_syscall_hook =
params.syscallbuf_syscall_hook.rptr().as_int();
t->syscallbuf_code_layout.syscallbuf_final_exit_instruction =
params.syscallbuf_final_exit_instruction.rptr().as_int();
t->syscallbuf_code_layout.syscallbuf_code_start =
params.syscallbuf_code_start.rptr().as_int();
t->syscallbuf_code_layout.syscallbuf_code_end =
params.syscallbuf_code_end.rptr().as_int();
t->syscallbuf_code_layout.get_pc_thunks_start =
params.get_pc_thunks_start.rptr().as_int();
t->syscallbuf_code_layout.get_pc_thunks_end =
params.get_pc_thunks_end.rptr().as_int();
unsigned char in_chaos = t->session().enable_chaos();
auto in_chaos_ptr REMOTE_PTR_FIELD(params.globals.rptr(), in_chaos);
t->write_mem(in_chaos_ptr, in_chaos);
t->record_local(in_chaos_ptr, &in_chaos);
auto desched_sig = t->session().syscallbuf_desched_sig();
auto desched_sig_ptr = REMOTE_PTR_FIELD(params.globals.rptr(), desched_sig);
t->write_mem(desched_sig_ptr, desched_sig);
t->record_local(desched_sig_ptr, &desched_sig);
uint64_t random_seed;
do {
random_seed = rand() | (uint64_t(rand()) << 32);
} while (!random_seed);
auto random_seed_ptr REMOTE_PTR_FIELD(params.globals.rptr(), random_seed);
t->write_mem(random_seed_ptr, random_seed);
t->record_local(random_seed_ptr, &random_seed);
auto cpu_binding = t->session().cpu_binding();
auto cpu_binding_ptr = REMOTE_PTR_FIELD(params.globals.rptr(), cpu_binding);
t->write_mem(cpu_binding_ptr, cpu_binding);
t->record_local(cpu_binding_ptr, &cpu_binding);
}
void RecordTask::push_syscall_event(int syscallno) {
push_event(SyscallEvent(syscallno, detect_syscall_arch()));
}
static void do_preload_init(RecordTask* t) {
RR_ARCH_FUNCTION(do_preload_init_arch, t->arch(), t);
}
void RecordTask::at_preload_init() {
Task::at_preload_init();
do_preload_init(this);
}
/**
* Avoid using low-numbered file descriptors since that can confuse
* developers.
*/
static int find_free_file_descriptor(RecordTask* t) {
int fd;
int fd_limit;
struct rlimit limit;
// Try to determine what the maximum fd is. If we can't, just
// start the search from 0 and bail out if we hit an absurd
// number of fds;
if (prlimit(t->tgid(), RLIMIT_NOFILE, nullptr, &limit) < 0) {
// We might get EPERM if the tracee has changed UID. If that
// happens fall back to being slow. (We can probably go faster
// using readdir().)
ASSERT(t, errno == EPERM) << "Unknown error in prlimit";
fd_limit = 128*1024;
fd = 0;
} else {
fd_limit = limit.rlim_cur;
fd = max(300, t->fd_table()->last_free_fd());
}
bool searched_from_start = fd == 0;
while (true) {
if (fd >= fd_limit) {
ASSERT(t, !searched_from_start) << "No free fds available";
fd = 0;
searched_from_start = true;
}
char buf[PATH_MAX];
sprintf(buf, "/proc/%d/fd/%d", t->tid, fd);
if (access(buf, F_OK) == -1 && errno == ENOENT) {
t->fd_table()->set_last_free_fd(fd);
return fd;
}
++fd;
}
}
template <typename Arch> void RecordTask::init_buffers_arch() {
ASSERT(this, as->syscallbuf_enabled())
<< "Someone called rrcall_init_buffers with syscallbuf disabled?";
// NB: the tracee can't be interrupted with a signal while
// we're processing the rrcall, because it's masked off all
// signals.
AutoRemoteSyscalls remote(this);
// Arguments to the rrcall.
remote_ptr<rrcall_init_buffers_params<Arch>> child_args = regs().orig_arg1();
auto args = read_mem(child_args);
args.cloned_file_data_fd = -1;
args.syscallbuf_size = syscallbuf_size = session().syscall_buffer_size();
KernelMapping syscallbuf_km = init_syscall_buffer(remote, nullptr);
if (!syscallbuf_km.size()) {
// Syscallbuf allocation failed. This should mean the child is dead,
// but just in case, return an error.
remote.regs().set_syscall_result(-ENOMEM);
return;
}
args.syscallbuf_ptr = syscallbuf_child;
if (syscallbuf_child != nullptr) {
// This needs to be skipped if we couldn't allocate the buffer
// since replaying only reads (and advances) the mmap record
// if `args.syscallbuf_ptr != nullptr`.
auto record_in_trace = trace_writer().write_mapped_region(
this, syscallbuf_km, syscallbuf_km.fake_stat(), syscallbuf_km.fsname(),
vector<TraceRemoteFd>(),
TraceWriter::RR_BUFFER_MAPPING);
ASSERT(this, record_in_trace == TraceWriter::DONT_RECORD_IN_TRACE);
} else {
// This can fail, e.g. if the tracee died unexpectedly.
LOG(debug) << "Syscallbuf initialization failed";
args.syscallbuf_size = 0;
}
if (args.syscallbuf_ptr) {
desched_fd_child = args.desched_counter_fd;
// Prevent the child from closing this fd
fds->add_monitor(this, desched_fd_child, new PreserveFileMonitor());
desched_fd = remote.retrieve_fd(desched_fd_child);
if (trace_writer().supports_file_data_cloning() &&
session().use_read_cloning()) {
cloned_file_data_fname = trace_writer().file_data_clone_file_name(tuid());
ScopedFd clone_file(cloned_file_data_fname.c_str(), O_RDWR | O_CREAT, 0600);
int cloned_file_data = remote.infallible_send_fd_if_alive(clone_file);
if (cloned_file_data >= 0) {
int free_fd = find_free_file_descriptor(this);
cloned_file_data_fd_child =
remote.syscall(syscall_number_for_dup3(arch()), cloned_file_data,
free_fd, O_CLOEXEC);
if (cloned_file_data_fd_child != free_fd) {
ASSERT(this, cloned_file_data_fd_child < 0);
LOG(warn) << "Couldn't dup clone-data file to free fd";
cloned_file_data_fd_child = cloned_file_data;
} else {
// Prevent the child from closing this fd. We're going to close it
// ourselves and we don't want the child closing it and then reopening
// its own file with this fd.
fds->add_monitor(this, cloned_file_data_fd_child,
new PreserveFileMonitor());
remote.infallible_close_syscall_if_alive(cloned_file_data);
}
args.cloned_file_data_fd = cloned_file_data_fd_child;
}
}
}
args.scratch_buf = scratch_ptr;
args.usable_scratch_size = usable_scratch_size();
// Return the mapped buffers to the child.
write_mem(child_args, args);
// The tracee doesn't need this addr returned, because it's
// already written to the inout |args| param, but we stash it
// away in the return value slot so that we can easily check
// that we map the segment at the same addr during replay.
remote.regs().set_syscall_result(syscallbuf_child);
}
void RecordTask::init_buffers() { RR_ARCH_FUNCTION(init_buffers_arch, arch()); }
template <typename Arch>
void RecordTask::on_syscall_exit_arch(int syscallno, const Registers& regs) {
switch (syscallno) {
// These syscalls affect the sigmask even if they fail.
case Arch::epoll_pwait:
case Arch::epoll_pwait2:
case Arch::pselect6:
case Arch::pselect6_time64:
case Arch::ppoll:
case Arch::ppoll_time64:
invalidate_sigmask();
break;
}
if (regs.original_syscallno() == SECCOMP_MAGIC_SKIP_ORIGINAL_SYSCALLNO ||
regs.syscall_failed()) {
return;
}
switch (syscallno) {
case Arch::set_robust_list:
set_robust_list(regs.orig_arg1(), (size_t)regs.arg2());
return;
case Arch::sigaction:
case Arch::rt_sigaction:
// TODO: SYS_signal
update_sigaction(regs);
return;
case Arch::set_tid_address:
set_tid_addr(regs.orig_arg1());
return;
case Arch::sigsuspend:
case Arch::rt_sigsuspend:
case Arch::sigprocmask:
case Arch::rt_sigprocmask:
case Arch::pselect6:
case Arch::pselect6_time64:
case Arch::ppoll:
case Arch::ppoll_time64:
invalidate_sigmask();
return;
}
}
void RecordTask::on_syscall_exit(int syscallno, SupportedArch arch,
const Registers& regs) {
with_converted_registers<void>(regs, arch, [&](const Registers& regs) {
Task::on_syscall_exit(syscallno, arch, regs);
RR_ARCH_FUNCTION(on_syscall_exit_arch, arch, syscallno, regs)
});
}
bool RecordTask::is_at_syscallbuf_syscall_entry_breakpoint() {
auto i = ip().undo_executed_bkpt(arch());
for (auto p : syscallbuf_syscall_entry_breakpoints()) {
if (i == p) {
return true;
}
}
return false;
}
bool RecordTask::is_at_syscallbuf_final_instruction_breakpoint() {
if (!break_at_syscallbuf_final_instruction) {
return false;
}
auto i = ip().undo_executed_bkpt(arch());
return i == syscallbuf_code_layout.syscallbuf_final_exit_instruction;
}
bool RecordTask::is_at_syscallstub_exit_breakpoint() {
if (!break_at_syscallbuf_final_instruction || !syscallstub_exit_breakpoint) {
return false;
}
auto i = ip().undo_executed_bkpt(arch());
return i == syscallstub_exit_breakpoint;
}
bool RecordTask::will_resume_execution(ResumeRequest, WaitRequest,
TicksRequest ticks_request, int sig) {
// We may execute user code, which could lead to an RDTSC or grow-map
// operation which unblocks SIGSEGV, and we'll need to know whether to
// re-block it. So we need our cached sigmask to be up to date.
// We don't need to this if we're not going to execute user code
// (i.e. ticks_request == RESUME_NO_TICKS) except that did_wait can't
// easily check for that and may restore blocked_sigs so it had better be
// accurate.
get_sigmask();
if (stashed_signals_blocking_more_signals) {
// A stashed signal we have already accepted for this task may
// have a sigaction::sa_mask that would block the next signal to be
// delivered and cause it to be delivered to a different task. If we allow
// such a signal to be delivered to this task then we run the risk of never
// being able to process the signal (if it stays blocked indefinitely).
// To prevent this, block any further signal delivery as long as there are
// stashed signals.
// We assume the kernel can't report a new signal of the same number
// in response to us injecting a signal. XXX is this true??? We don't
// have much choice, signal injection won't work if we block the signal.
// We leave rr signals unblocked. TIME_SLICE_SIGNAL has to be unblocked
// because blocking it seems to cause problems for some hardware/kernel
// configurations (see https://github.com/rr-debugger/rr/issues/1979),
// causing them to stop counting events.
sig_set_t sigset = ~session().rr_signal_mask();
if (sig) {
// We're injecting a signal, so make sure that signal is unblocked.
sigset &= ~signal_bit(sig);
}
if (!set_sigmask(sigset)) {
return false;
}
}
// RESUME_NO_TICKS means that tracee code is not going to run so there's no
// need to set breakpoints and in fact they might interfere with rr
// processing.
if (ticks_request != RESUME_NO_TICKS) {
if (!at_may_restart_syscall()) {
// If the tracee has SIGTRAP blocked or ignored and we hit one of these
// breakpoints, the kernel will automatically unblock the signal and set
// its disposition to DFL, effects which we ought to undo to keep these
// SIGTRAPs invisible to tracees. Fixing the sigmask happens
// automatically in did_wait(). Restoring the signal-ignored status is
// handled in `handle_syscallbuf_breakpoint`.
// Set breakpoints at untraced syscalls to catch us entering an untraced
// syscall. We don't need to do this (and shouldn't do this) if the
// execution requestor wants to stop inside untraced syscalls.
// If we have an interrupted syscall that we may restart, don't
// set the breakpoints because we should restart the syscall instead
// of breaking and delivering signals. The syscallbuf code doesn't
// (and must not) perform more than one blocking syscall for any given
// buffered syscall.
for (auto p : syscallbuf_syscall_entry_breakpoints()) {
vm()->add_breakpoint(p, BKPT_INTERNAL);
}
}
if (break_at_syscallbuf_final_instruction) {
vm()->add_breakpoint(
syscallbuf_code_layout.syscallbuf_final_exit_instruction,
BKPT_INTERNAL);
auto stub_bp = as->monkeypatcher().get_jump_stub_exit_breakpoint(ip(), this);
if (stub_bp) {
syscallstub_exit_breakpoint = stub_bp;
vm()->add_breakpoint(stub_bp, BKPT_INTERNAL);
}
}
}
return true;
}
vector<remote_code_ptr> RecordTask::syscallbuf_syscall_entry_breakpoints() {
vector<remote_code_ptr> result;
if (break_at_syscallbuf_untraced_syscalls) {
result.push_back(AddressSpace::rr_page_syscall_entry_point(
AddressSpace::UNTRACED, AddressSpace::UNPRIVILEGED,
AddressSpace::RECORDING_ONLY, arch()));
result.push_back(AddressSpace::rr_page_syscall_entry_point(
AddressSpace::UNTRACED, AddressSpace::UNPRIVILEGED,
AddressSpace::RECORDING_AND_REPLAY, arch()));
}
if (break_at_syscallbuf_traced_syscalls) {
result.push_back(AddressSpace::rr_page_syscall_entry_point(
AddressSpace::TRACED, AddressSpace::UNPRIVILEGED,
AddressSpace::RECORDING_AND_REPLAY, arch()));
}
return result;
}
void RecordTask::did_wait() {
for (auto p : syscallbuf_syscall_entry_breakpoints()) {
vm()->remove_breakpoint(p, BKPT_INTERNAL);
}
if (break_at_syscallbuf_final_instruction) {
vm()->remove_breakpoint(
syscallbuf_code_layout.syscallbuf_final_exit_instruction,
BKPT_INTERNAL);
}
if (syscallstub_exit_breakpoint) {
vm()->remove_breakpoint(syscallstub_exit_breakpoint, BKPT_INTERNAL);
}
if (stashed_signals_blocking_more_signals) {
// Saved 'blocked_sigs' must still be correct regardless of syscallbuf
// state, because we do not allow stashed_signals_blocking_more_signals
// to hold across syscalls (traced or untraced) that change the signal mask.
ASSERT(this, !blocked_sigs_dirty);
// If this fails the tracee is on the exit path and its sigmask is irrelevant.
ptrace_if_stopped(PTRACE_SETSIGMASK, remote_ptr<void>(8), &blocked_sigs);
} else if (syscallbuf_child) {
// The syscallbuf struct is only 32 bytes currently so read the whole thing
// at once to avoid multiple calls to read_mem. Even though this shouldn't
// need a syscall because we use a local-mapping, apparently that lookup
// is still noticeably expensive.
auto syscallbuf = read_mem(syscallbuf_child);
if (syscallbuf.in_sigprocmask_critical_section) {
// |blocked_sigs| may have been updated but the syscall not yet issued.
// Use the kernel's value.
invalidate_sigmask();
} else {
uint32_t syscallbuf_generation = syscallbuf.blocked_sigs_generation;
if (syscallbuf_generation > syscallbuf_blocked_sigs_generation) {
syscallbuf_blocked_sigs_generation = syscallbuf_generation;
blocked_sigs = syscallbuf.blocked_sigs;
}
}
}
}
void RecordTask::set_emulated_ptracer(RecordTask* tracer) {
if (tracer) {
ASSERT(this, !emulated_ptracer);
emulated_ptracer = tracer;
emulated_ptracer->emulated_ptrace_tracees.insert(this);
} else {
ASSERT(this, emulated_ptracer);
ASSERT(this,
emulated_stop_type == NOT_STOPPED ||
emulated_stop_type == GROUP_STOP);
emulated_ptracer->emulated_ptrace_tracees.erase(this);
emulated_ptracer = nullptr;
}
}
bool RecordTask::emulate_ptrace_stop(WaitStatus status, EmulatedStopType stop_type,
const siginfo_t* siginfo, int si_code) {
ASSERT(this, emulated_stop_type == NOT_STOPPED);
if (!emulated_ptracer) {
return false;
}
if (siginfo) {
ASSERT(this, status.ptrace_signal() == siginfo->si_signo);
save_ptrace_signal_siginfo(*siginfo);
} else {
siginfo_t si;
memset(&si, 0, sizeof(si));
si.si_signo = status.ptrace_signal();
if (status.ptrace_event() || status.is_syscall()) {
si.si_code = status.get() >> 8;
} else {
si.si_code = si_code;
}
save_ptrace_signal_siginfo(si);
}
force_emulate_ptrace_stop(status, stop_type);
return true;
}
void RecordTask::force_emulate_ptrace_stop(WaitStatus status, EmulatedStopType stop_type) {
emulated_stop_type = stop_type;
emulated_stop_code = status;
emulated_stop_pending = true;
emulated_ptrace_SIGCHLD_pending = true;
emulated_ptracer->send_synthetic_SIGCHLD_if_necessary();
// The SIGCHLD will eventually be reported to rr via a ptrace stop,
// interrupting wake_task's syscall (probably a waitpid) if necessary. At
// that point, we'll fix up the siginfo data with values that match what
// the kernel would have delivered for a real ptracer's SIGCHLD. When the
// signal handler (if any) returns, if wake_task was in a blocking wait that
// wait will be resumed, at which point rec_prepare_syscall_arch will
// discover the pending ptrace result and emulate the wait syscall to
// return that result immediately.
}
void RecordTask::do_ptrace_exit_stop(WaitStatus exit_status) {
// Notify ptracer of the exit if it's not going to receive it from the
// kernel because it's not the parent. (The kernel has similar logic to
// deliver two stops in this case.)
if (emulated_ptracer &&
(is_clone_child() ||
get_parent_pid() != emulated_ptracer->real_tgid())) {
// The task is dead so treat it as not stopped so we can deliver a new stop
emulated_stop_type = NOT_STOPPED;
// This is a bit wrong; this is an exit stop, not a signal/ptrace stop.
emulate_ptrace_stop(exit_status);
}
}
void RecordTask::did_reach_zombie() {
// Remove from address-space and fds list since we really aren't associated
// with them anymore (and we can't be used to operate on them)
as->erase_task(this);
fds->erase_task(this);
if (!was_reaped()) {
if (may_reap()) {
reap();
} else {
waiting_for_reap = true;
}
}
if ((was_reaped() || !waiting_for_reap) && !emulated_stop_pending) {
delete this;
}
}
void RecordTask::set_stopped(bool stopped) {
if (is_stopped_ == stopped) {
return;
}
is_stopped_ = stopped;
if (stopped) {
session().scheduler().stopped_task(this);
} else {
session().scheduler().started_task(this);
}
}
void RecordTask::send_synthetic_SIGCHLD_if_necessary() {
RecordTask* wake_task = nullptr;
bool need_signal = false;
for (RecordTask* tracee : emulated_ptrace_tracees) {
if (tracee->emulated_ptrace_SIGCHLD_pending) {
need_signal = true;
// check to see if any thread in the ptracer process is in a waitpid that
// could read the status of 'tracee'. If it is, we should wake up that
// thread. Otherwise we send SIGCHLD to the ptracer thread.
for (Task* t : thread_group()->task_set()) {
auto rt = static_cast<RecordTask*>(t);
if (rt->is_waiting_for_ptrace(tracee)) {
wake_task = rt;
break;
}
}
if (wake_task) {
break;
}
}
}
if (!need_signal) {
for (ThreadGroup* child_tg : thread_group()->children()) {
for (Task* child : child_tg->task_set()) {
RecordTask* rchild = static_cast<RecordTask*>(child);
if (rchild->emulated_SIGCHLD_pending) {
need_signal = true;
// check to see if any thread in the ptracer process is in a waitpid
// that
// could read the status of 'tracee'. If it is, we should wake up that
// thread. Otherwise we send SIGCHLD to the ptracer thread.
for (Task* t : thread_group()->task_set()) {
auto rt = static_cast<RecordTask*>(t);
if (rt->is_waiting_for(rchild)) {
wake_task = rt;
break;
}
}
if (wake_task) {
break;
}
}
}
}
if (!need_signal) {
return;
}
}
// ptrace events trigger SIGCHLD in the ptracer's wake_task.
// We can't set all the siginfo values to their correct values here, so
// we'll patch this up when the signal is received.
// If there's already a pending SIGCHLD, this signal will be ignored,
// but at some point the pending SIGCHLD will be delivered and then
// send_synthetic_SIGCHLD_if_necessary will be called again to deliver a new
// SIGCHLD if necessary.
siginfo_t si;
memset(&si, 0, sizeof(si));
si.si_code = SI_QUEUE;
si.si_value.sival_int = SIGCHLD_SYNTHETIC;
int ret;
if (wake_task) {
LOG(debug) << "Sending synthetic SIGCHLD to tid " << wake_task->tid;
// We must use the raw SYS_rt_tgsigqueueinfo syscall here to ensure the
// signal is sent to the correct thread by tid.
ret = syscall(SYS_rt_tgsigqueueinfo, wake_task->tgid(), wake_task->tid,
SIGCHLD, &si);
ASSERT(this, ret == 0);
if (wake_task->is_sig_blocked(SIGCHLD)) {
LOG(debug) << "SIGCHLD is blocked, kicking it out of the syscall";
// Just sending SIGCHLD won't wake it up. Send it a TIME_SLICE_SIGNAL
// as well to make sure it exits a blocking syscall. We ensure those
// can never be blocked.
// We have to send a negative code here because only the kernel can set
// positive codes. We set a magic number so we can recognize it
// when received.
si.si_code = SYNTHETIC_TIME_SLICE_SI_CODE;
ret = syscall(SYS_rt_tgsigqueueinfo, wake_task->tgid(), wake_task->tid,
PerfCounters::TIME_SLICE_SIGNAL, &si);
ASSERT(this, ret == 0);
}
} else {
// Send the signal to the process as a whole and let the kernel
// decide which thread gets it.
ret = syscall(SYS_rt_sigqueueinfo, tgid(), SIGCHLD, &si);
ASSERT(this, ret == 0);
LOG(debug) << "Sending synthetic SIGCHLD to pid " << tgid();
}
}
static bool is_synthetic_SIGCHLD(const siginfo_t& si) {
return si.si_signo == SIGCHLD && si.si_value.sival_int == SIGCHLD_SYNTHETIC;
}
bool RecordTask::set_siginfo_for_synthetic_SIGCHLD(siginfo_t* si) {
if (!is_synthetic_SIGCHLD(*si)) {
return true;
}
if (is_syscall_restart() && EV_SYSCALL_INTERRUPTION == ev().type()) {
int syscallno = regs().original_syscallno();
SupportedArch syscall_arch = ev().Syscall().arch();
if (is_waitpid_syscall(syscallno, syscall_arch) ||
is_waitid_syscall(syscallno, syscall_arch) ||
is_wait4_syscall(syscallno, syscall_arch)) {
// Wait-like syscalls always check for notifications from waited-for processes
// before they check for pending signals. So, if the tracee has a pending
// notification that also generated a signal, the wait syscall will return
// normally rather than returning with ERESTARTSYS etc. (The signal will
// be dequeued and any handler run on the return to userspace, however.)
// We need to emulate this by deferring our synthetic ptrace signal
// until after the wait syscall has returned.
LOG(debug) << "Deferring signal because we're in a wait";
// Return false to tell the caller to defer the signal and resume
// the syscall.
return false;
}
}
for (RecordTask* tracee : emulated_ptrace_tracees) {
if (tracee->emulated_ptrace_SIGCHLD_pending) {
tracee->emulated_ptrace_SIGCHLD_pending = false;
tracee->set_siginfo_for_waited_task<NativeArch>(
reinterpret_cast<NativeArch::siginfo_t*>(si));
si->si_value.sival_int = 0;
return true;
}
}
for (ThreadGroup* child_tg : thread_group()->children()) {
for (Task* child : child_tg->task_set()) {
auto rchild = static_cast<RecordTask*>(child);
if (rchild->emulated_SIGCHLD_pending) {
rchild->emulated_SIGCHLD_pending = false;
rchild->set_siginfo_for_waited_task<NativeArch>(
reinterpret_cast<NativeArch::siginfo_t*>(si));
si->si_value.sival_int = 0;
return true;
}
}
}
return true;
}
bool RecordTask::is_waiting_for_ptrace(RecordTask* t) {
// This task's process must be a ptracer of t.
if (!t->emulated_ptracer ||
t->emulated_ptracer->thread_group() != thread_group()) {
return false;
}
// XXX need to check |options| to make sure this task is eligible!!
switch (in_wait_type) {
case WAIT_TYPE_NONE:
return false;
case WAIT_TYPE_ANY:
return true;
case WAIT_TYPE_SAME_PGID:
return getpgid(t->tgid()) == getpgid(tgid());
case WAIT_TYPE_PGID:
return getpgid(t->tgid()) == in_wait_pid;
case WAIT_TYPE_PID:
// When waiting for a ptracee, a specific pid is interpreted as the
// exact tid.
return t->tid == in_wait_pid;
default:
ASSERT(this, false);
return false;
}
}
bool RecordTask::is_waiting_for(RecordTask* t) {
// t must be a child of this task.
if (t->thread_group()->parent() != thread_group().get()) {
return false;
}
switch (in_wait_type) {
case WAIT_TYPE_NONE:
return false;
case WAIT_TYPE_ANY:
return true;
case WAIT_TYPE_SAME_PGID:
return getpgid(t->tgid()) == getpgid(tgid());
case WAIT_TYPE_PGID:
return getpgid(t->tgid()) == in_wait_pid;
case WAIT_TYPE_PID:
return t->tgid() == in_wait_pid;
default:
ASSERT(this, false);
return false;
}
}
void RecordTask::save_ptrace_signal_siginfo(const siginfo_t& si) {
for (auto it = saved_ptrace_siginfos.begin();
it != saved_ptrace_siginfos.end(); ++it) {
if (it->si_signo == si.si_signo) {
saved_ptrace_siginfos.erase(it);
break;
}
}
saved_ptrace_siginfos.push_back(si);
}
siginfo_t& RecordTask::get_saved_ptrace_siginfo() {
int sig = emulated_stop_code.ptrace_signal();
ASSERT(this, sig > 0);
for (auto it = saved_ptrace_siginfos.begin();
it != saved_ptrace_siginfos.end(); ++it) {
if (it->si_signo == sig) {
return *it;
}
}
ASSERT(this, false) << "No saved siginfo found for stop-signal???";
while (true) {
// Avoid having to return anything along this (unreachable) path
}
}
siginfo_t RecordTask::take_ptrace_signal_siginfo(int sig) {
for (auto it = saved_ptrace_siginfos.begin();
it != saved_ptrace_siginfos.end(); ++it) {
if (it->si_signo == sig) {
siginfo_t si = *it;
saved_ptrace_siginfos.erase(it);
return si;
}
}
siginfo_t si;
memset(&si, 0, sizeof(si));
si.si_signo = sig;
return si;
}
static pid_t get_ppid(pid_t pid) {
auto ppid_str = read_proc_status_fields(pid, "PPid");
if (ppid_str.empty()) {
return -1;
}
char* end;
int actual_ppid = strtol(ppid_str[0].c_str(), &end, 10);
return *end ? -1 : actual_ppid;
}
void RecordTask::apply_group_stop(int sig) {
if (emulated_stop_type == NOT_STOPPED) {
LOG(debug) << "setting " << tid << " to GROUP_STOP due to signal " << sig;
WaitStatus status = WaitStatus::for_group_sig(sig, this);
if (!emulate_ptrace_stop(status)) {
emulated_stop_type = GROUP_STOP;
emulated_stop_code = status;
emulated_stop_pending = true;
emulated_SIGCHLD_pending = true;
RecordTask* t = session().find_task(get_ppid(tid));
if (t) {
t->send_synthetic_SIGCHLD_if_necessary();
}
}
}
}
bool RecordTask::is_signal_pending(int sig) {
auto pending_strs = read_proc_status_fields(tid, "SigPnd", "ShdPnd");
if (pending_strs.size() < 2) {
return false;
}
char* end1;
sig_set_t mask1 = strtoull(pending_strs[0].c_str(), &end1, 16);
char* end2;
sig_set_t mask2 = strtoull(pending_strs[1].c_str(), &end2, 16);
return !*end1 && !*end2 && ((mask1 | mask2) & signal_bit(sig));
}
bool RecordTask::has_any_actionable_signal() {
auto sig_strs = read_proc_status_fields(tid, "SigPnd", "ShdPnd", "SigBlk");
if (sig_strs.size() < 3) {
return false;
}
char* end1;
uint64_t mask1 = strtoull(sig_strs[0].c_str(), &end1, 16);
char* end2;
uint64_t mask2 = strtoull(sig_strs[1].c_str(), &end2, 16);
char* end3;
uint64_t mask_blk = strtoull(sig_strs[2].c_str(), &end3, 16);
return !*end1 && !*end2 && !*end3 && ((mask1 | mask2) & ~mask_blk);
}
void RecordTask::emulate_SIGCONT() {
// All threads in the process are resumed.
for (Task* t : thread_group()->task_set()) {
auto rt = static_cast<RecordTask*>(t);
LOG(debug) << "setting " << tid << " to NOT_STOPPED due to SIGCONT";
rt->clear_stashed_group_stop();
rt->emulated_stop_pending = false;
rt->emulated_stop_type = NOT_STOPPED;
}
}
void RecordTask::signal_delivered(int sig) {
Sighandler& h = sighandlers->get(sig);
if (h.resethand) {
reset_handler(&h, arch());
}
if (!is_sig_ignored(sig)) {
switch (sig) {
case SIGTSTP:
case SIGTTIN:
case SIGTTOU:
if (h.disposition() == SIGNAL_HANDLER) {
break;
}
RR_FALLTHROUGH;
case SIGSTOP:
// All threads in the process are stopped.
for (Task* t : thread_group()->task_set()) {
auto rt = static_cast<RecordTask*>(t);
rt->apply_group_stop(sig);
}
break;
case SIGCONT:
emulate_SIGCONT();
break;
}
}
send_synthetic_SIGCHLD_if_necessary();
}
bool RecordTask::signal_has_user_handler(int sig) const {
return sighandlers->get(sig).disposition() == SIGNAL_HANDLER;
}
remote_code_ptr RecordTask::get_signal_user_handler(int sig) const {
return sighandlers->get(sig).get_user_handler();
}
const vector<uint8_t>& RecordTask::signal_action(int sig) const {
return sighandlers->get(sig).sa;
}
bool RecordTask::signal_handler_takes_siginfo(int sig) const {
return sighandlers->get(sig).takes_siginfo;
}
static bool is_unstoppable_signal(int sig) {
return sig == SIGSTOP || sig == SIGKILL;
}
bool RecordTask::is_sig_blocked(int sig) {
if (is_unstoppable_signal(sig)) {
// These can never be blocked
return false;
}
int sig_bit = sig - 1;
return (get_sigmask() >> sig_bit) & 1;
}
bool RecordTask::is_sig_ignored(int sig) const {
if (is_unstoppable_signal(sig)) {
// These can never be ignored
return false;
}
switch (sighandlers->get(sig).disposition()) {
case SIGNAL_IGNORE:
return true;
case SIGNAL_DEFAULT:
return IGNORE == default_action(sig);
default:
return false;
}
}
SignalDisposition RecordTask::sig_disposition(int sig) const {
return sighandlers->get(sig).disposition();
}
SignalResolvedDisposition RecordTask::sig_resolved_disposition(
int sig, SignalDeterministic deterministic) {
if (is_fatal_signal(sig, deterministic)) {
return DISPOSITION_FATAL;
}
if (signal_has_user_handler(sig) && !is_sig_blocked(sig)) {
return DISPOSITION_USER_HANDLER;
}
return DISPOSITION_IGNORED;
}
void RecordTask::set_siginfo(const siginfo_t& si) {
pending_siginfo = si;
// If this fails, the tracee is on the exit path and its siginfo
// is irrelevant.
ptrace_if_stopped(PTRACE_SETSIGINFO, nullptr, (void*)&si);
}
template <typename Arch>
void RecordTask::update_sigaction_arch(const Registers& regs) {
int sig = regs.orig_arg1_signed();
remote_ptr<typename Arch::kernel_sigaction> new_sigaction = regs.arg2();
if (0 == regs.syscall_result() && !new_sigaction.is_null()) {
// A new sighandler was installed. Update our
// sighandler table.
// TODO: discard attempts to handle or ignore signals
// that can't be by POSIX
typename Arch::kernel_sigaction sa;
memset(&sa, 0, sizeof(sa));
read_bytes_helper(new_sigaction, sizeof(sa), &sa);
sighandlers->get(sig).init_arch<Arch>(sa);
}
}
void RecordTask::update_sigaction(const Registers& regs) {
RR_ARCH_FUNCTION(update_sigaction_arch, regs.arch(), regs);
}
sig_set_t RecordTask::read_sigmask_from_process() {
// During syscall interruptions, PTRACE_GETSIGMASK may return the sigmask that is going
// to be restored, not the kernel's current (internal) sigmask, which is what
// /proc/.../status reports. Always go with what /proc/.../status reports. See
// https://github.com/torvalds/linux/commit/fcfc2aa0185f4a731d05a21e9f359968fdfd02e7
// XXXkhuey and yet that's not what we actually do here ...
if (at_interrupted_non_restartable_signal_modifying_syscall()) {
// Mark the sigmask as already invalid. The moment we exit the kernel and run more
// of the tracee the sigmask will change, so we need to keep refetching the
// sigmask until that happens.
invalidate_sigmask();
} else if (!at_may_restart_syscall()) {
sig_set_t mask;
long ret = fallible_ptrace(PTRACE_GETSIGMASK,
remote_ptr<void>(sizeof(sig_set_t)), &mask);
if (ret >= 0) {
return mask;
}
}
auto results = read_proc_status_fields(tid, "SigBlk");
if (results.empty()) {
// Read failed, process probably died
return 0;
}
ASSERT(this, results.size() == 1);
return strtoull(results[0].c_str(), NULL, 16);
}
sig_set_t RecordTask::get_sigmask() {
if (blocked_sigs_dirty) {
// Clear this first, read_sigmask_from_process might set it again.
blocked_sigs_dirty = false;
blocked_sigs = read_sigmask_from_process();
LOG(debug) << "Refreshed sigmask, now " << HEX(blocked_sigs);
}
return blocked_sigs;
}
bool RecordTask::unblock_signal(int sig) {
sig_set_t mask = get_sigmask();
mask &= ~signal_bit(sig);
if (!set_sigmask(mask)) {
return false;
}
invalidate_sigmask();
return true;
}
bool RecordTask::set_sigmask(sig_set_t mask) {
int ret = fallible_ptrace(PTRACE_SETSIGMASK, remote_ptr<void>(8), &mask);
if (ret < 0) {
if (errno == EIO) {
FATAL() << "PTRACE_SETSIGMASK not supported; rr requires Linux kernel >= 3.11";
}
if (errno == ESRCH) {
return false;
}
ASSERT(this, errno == EINVAL);
} else {
LOG(debug) << "Set signal mask to block all signals (bar "
<< "SYSCALLBUF_DESCHED_SIGNAL/TIME_SLICE_SIGNAL) while we "
<< " have a stashed signal";
}
return true;
}
void RecordTask::set_sig_handler_default(int sig) {
did_set_sig_handler_default(sig);
// This could happen during a syscallbuf untraced syscall. In that case
// our remote syscall here could trigger a desched signal if that event
// is armed, making progress impossible. Disarm the event now.
disarm_desched_event(this);
AutoRemoteSyscalls remote(this);
Sighandler& h = sighandlers->get(sig);
AutoRestoreMem mem(remote, h.sa.data(), h.sa.size());
remote.infallible_syscall(syscall_number_for_rt_sigaction(arch()),
sig, mem.get().as_int(), nullptr,
sigaction_sigset_size(arch()));
}
void RecordTask::did_set_sig_handler_default(int sig) {
Sighandler& h = sighandlers->get(sig);
reset_handler(&h, arch());
}
void RecordTask::verify_signal_states() {
#ifndef DEBUG
return;
#endif
if (ev().is_syscall_event()) {
// If the syscall event is on the event stack with PROCESSING or EXITING
// states, we won't have applied the signal-state updates yet while the
// kernel may have.
return;
}
if (detached_proxy) {
// This task isn't real
return;
}
auto results = read_proc_status_fields(tid, "SigBlk", "SigIgn", "SigCgt");
if (results.empty()) {
// Read failed, process probably died
return;
}
ASSERT(this, results.size() == 3);
sig_set_t blocked = strtoull(results[0].c_str(), NULL, 16);
sig_set_t ignored = strtoull(results[1].c_str(), NULL, 16);
sig_set_t caught = strtoull(results[2].c_str(), NULL, 16);
for (int sig = 1; sig < _NSIG; ++sig) {
sig_set_t mask = signal_bit(sig);
if (is_unstoppable_signal(sig)) {
ASSERT(this, !(blocked & mask))
<< "Expected " << signal_name(sig) << " to not be blocked, but it is";
ASSERT(this, !(ignored & mask))
<< "Expected " << signal_name(sig) << " to not be ignored, but it is";
ASSERT(this, !(caught & mask))
<< "Expected " << signal_name(sig) << " to not be caught, but it is";
} else {
ASSERT(this, !!(blocked & mask) == is_sig_blocked(sig))
<< signal_name(sig)
<< ((blocked & mask) ? " is blocked" : " is not blocked");
if (sig == SIGCHLD && is_container_init() && (ignored & mask)) {
// pid-1-in-its-own-pid-namespace tasks can have their SIGCHLD set
// to "ignore" when they die (in zap_pid_ns_processes). We may
// not have observed anything relating to this death yet. We could
// probe to ensure it's already marked as a zombie but why bother.
// XXX arguably we should actually change our disposition here but
// it would only matter in certain very weird cases: a vfork() where
// the child process is pid-1 in its namespace.
continue;
}
auto disposition = sighandlers->get(sig).disposition();
ASSERT(this, !!(ignored & mask) == (disposition == SIGNAL_IGNORE))
<< signal_name(sig)
<< ((ignored & mask) ? " is ignored" : " is not ignored");
ASSERT(this, !!(caught & mask) == (disposition == SIGNAL_HANDLER))
<< signal_name(sig)
<< ((caught & mask) ? " is caught" : " is not caught");
}
}
}
void RecordTask::stash_sig() {
int sig = stop_sig();
ASSERT(this, sig);
// Callers should avoid passing SYSCALLBUF_DESCHED_SIGNAL in here.
ASSERT(this, sig != session().syscallbuf_desched_sig());
// multiple non-RT signals coalesce
if (sig < SIGRTMIN) {
for (auto it = stashed_signals.begin(); it != stashed_signals.end(); ++it) {
if (it->siginfo.si_signo == sig) {
LOG(debug) << "discarding stashed signal " << sig
<< " since we already have one pending";
return;
}
}
}
const siginfo_t& si = get_siginfo();
stashed_signals.push_back(StashedSignal(si, is_deterministic_signal(this), ip()));
// Once we've stashed a signal, stop at the next traced/untraced syscall to
// check whether we need to process the signal before it runs.
stashed_signals_blocking_more_signals =
break_at_syscallbuf_final_instruction =
break_at_syscallbuf_traced_syscalls =
break_at_syscallbuf_untraced_syscalls = true;
syscallstub_exit_breakpoint = nullptr;
}
void RecordTask::stash_synthetic_sig(const siginfo_t& si,
SignalDeterministic deterministic) {
int sig = si.si_signo;
DEBUG_ASSERT(sig);
// Callers should avoid passing SYSCALLBUF_DESCHED_SIGNAL in here.
DEBUG_ASSERT(sig != session().syscallbuf_desched_sig());
// multiple non-RT signals coalesce
if (sig < SIGRTMIN) {
for (auto it = stashed_signals.begin(); it != stashed_signals.end(); ++it) {
if (it->siginfo.si_signo == sig) {
if (deterministic == DETERMINISTIC_SIG &&
it->deterministic == NONDETERMINISTIC_SIG) {
stashed_signals.erase(it);
break;
} else {
LOG(debug) << "discarding stashed signal " << sig
<< " since we already have one pending";
return;
}
}
}
}
stashed_signals.insert(stashed_signals.begin(),
StashedSignal(si, deterministic, ip()));
stashed_signals_blocking_more_signals =
break_at_syscallbuf_final_instruction =
break_at_syscallbuf_traced_syscalls =
break_at_syscallbuf_untraced_syscalls = true;
syscallstub_exit_breakpoint = nullptr;
}
bool RecordTask::has_stashed_sig(int sig) const {
for (auto it = stashed_signals.begin(); it != stashed_signals.end(); ++it) {
if (it->siginfo.si_signo == sig) {
return true;
}
}
return false;
}
const RecordTask::StashedSignal* RecordTask::stashed_sig_not_synthetic_SIGCHLD() const {
for (auto it = stashed_signals.begin(); it != stashed_signals.end(); ++it) {
if (!is_synthetic_SIGCHLD(it->siginfo)) {
return &*it;
}
}
return nullptr;
}
void RecordTask::pop_stash_sig(const StashedSignal* stashed) {
for (auto it = stashed_signals.begin(); it != stashed_signals.end(); ++it) {
if (&*it == stashed) {
stashed_signals.erase(it);
return;
}
}
ASSERT(this, false) << "signal not found";
}
void RecordTask::stashed_signal_processed() {
break_at_syscallbuf_final_instruction = break_at_syscallbuf_traced_syscalls =
break_at_syscallbuf_untraced_syscalls =
stashed_signals_blocking_more_signals = has_stashed_sig();
syscallstub_exit_breakpoint = nullptr;
}
const RecordTask::StashedSignal* RecordTask::peek_stashed_sig_to_deliver()
const {
if (stashed_signals.empty()) {
return nullptr;
}
// Choose the first non-synthetic-SIGCHLD signal so that if a syscall should
// be interrupted, we'll interrupt it.
for (auto& sig : stashed_signals) {
if (!is_synthetic_SIGCHLD(sig.siginfo)) {
return &sig;
}
}
return &stashed_signals[0];
}
bool RecordTask::is_syscall_restart() {
if (EV_SYSCALL_INTERRUPTION != ev().type()) {
return false;
}
int syscallno = regs().original_syscallno();
SupportedArch syscall_arch = ev().Syscall().arch();
string call_name = syscall_name(syscallno, syscall_arch);
bool is_restart = false;
LOG(debug) << " is syscall interruption of recorded " << ev() << "? (now "
<< call_name << ")";
/* It's possible for the tracee to resume after a sighandler
* with a fresh syscall that happens to be the same as the one
* that was interrupted. So we check here if the args are the
* same.
*
* Of course, it's possible (but less likely) for the tracee
* to incidentally resume with a fresh syscall that just
* happens to have the same *arguments* too. But in that
* case, we would usually set up scratch buffers etc the same
* was as for the original interrupted syscall, so we just
* save a step here.
*
* TODO: it's possible for arg structures to be mutated
* between the original call and restarted call in such a way
* that it might change the scratch allocation decisions. */
if (is_restart_syscall_syscall(syscallno, syscall_arch)) {
is_restart = true;
syscallno = ev().Syscall().number;
LOG(debug) << " (SYS_restart_syscall)";
}
if (ev().Syscall().number != syscallno) {
LOG(debug) << " interrupted " << ev() << " != " << call_name;
goto done;
}
{
const Registers& old_regs = ev().Syscall().regs;
if (!(old_regs.orig_arg1() == regs().arg1() &&
old_regs.arg2() == regs().arg2() &&
old_regs.arg3() == regs().arg3() &&
old_regs.arg4() == regs().arg4() &&
old_regs.arg5() == regs().arg5() &&
old_regs.arg6() == regs().arg6())) {
LOG(debug) << " regs different at interrupted " << call_name << ": "
<< old_regs << " vs " << regs();
goto done;
}
}
is_restart = true;
done:
if (is_restart) {
LOG(debug) << " restart of " << call_name;
}
return is_restart;
}
template <typename Arch>
static uint64_t read_ptr_arch(Task* t, remote_ptr<void> p, bool* ok) {
return t->read_mem(p.cast<typename Arch::unsigned_word>(), ok);
}
static uint64_t read_ptr(Task* t, remote_ptr<void> p, bool* ok) {
RR_ARCH_FUNCTION(read_ptr_arch, t->arch(), t, p, ok);
}
bool RecordTask::is_in_syscallbuf() {
if (!as->syscallbuf_enabled()) {
// Even if we're in the rr page, if syscallbuf isn't enabled then the
// rr page is not being used by syscallbuf.
return false;
}
remote_code_ptr p = ip();
if (is_in_rr_page() || (syscallbuf_code_layout.get_pc_thunks_start <= p &&
p < syscallbuf_code_layout.get_pc_thunks_end)) {
// Look at the caller to see if we're in the syscallbuf or not.
bool ok = true;
uint64_t addr;
if (arch() == aarch64) {
addr = regs().xlr();
}
else {
ASSERT(this, is_x86ish(arch())) << "Unknown architecture";
addr = read_ptr(this, regs().sp(), &ok);
}
if (ok) {
p = addr;
}
}
return as->monkeypatcher().is_jump_stub_instruction(p, false) ||
(syscallbuf_code_layout.syscallbuf_code_start <= p &&
p < syscallbuf_code_layout.syscallbuf_code_end);
}
bool RecordTask::at_may_restart_syscall() const {
ssize_t depth = pending_events.size();
const Event* prev_ev = depth > 2 ? &pending_events[depth - 2] : nullptr;
return EV_SYSCALL_INTERRUPTION == ev().type() ||
(EV_SIGNAL_DELIVERY == ev().type() && prev_ev &&
EV_SYSCALL_INTERRUPTION == prev_ev->type());
}
bool RecordTask::at_interrupted_non_restartable_signal_modifying_syscall() const {
auto r = regs();
// XXXkhuey io_uring_enter (not yet supported) can do this too.
return r.syscall_result_signed() == -EINTR &&
(is_epoll_pwait_syscall(r.original_syscallno(), arch()) ||
is_epoll_pwait2_syscall(r.original_syscallno(), arch()));
}
bool RecordTask::is_arm_desched_event_syscall() {
return is_desched_event_syscall() && PERF_EVENT_IOC_ENABLE == regs().arg2();
}
bool RecordTask::is_disarm_desched_event_syscall() {
return (is_desched_event_syscall() &&
PERF_EVENT_IOC_DISABLE == regs().arg2());
}
bool RecordTask::may_be_blocked() const {
return (EV_SYSCALL == ev().type() &&
PROCESSING_SYSCALL == ev().Syscall().state) ||
emulated_stop_type != NOT_STOPPED ||
(EV_SIGNAL_DELIVERY == ev().type() &&
DISPOSITION_FATAL == ev().Signal().disposition) ||
waiting_for_ptrace_exit;
}
bool RecordTask::maybe_in_spinlock() {
return time_at_start_of_last_timeslice == session().trace_writer().time() &&
regs().matches(registers_at_start_of_last_timeslice);
}
remote_ptr<const struct syscallbuf_record> RecordTask::desched_rec() const {
return (ev().is_syscall_event()
? ev().Syscall().desched_rec
: (EV_DESCHED == ev().type()) ? ev().Desched().rec : nullptr);
}
bool RecordTask::running_inside_desched() const {
for (auto& e : pending_events) {
if (e.type() == EV_DESCHED) {
return e.Desched().rec != desched_rec();
}
}
return false;
}
int RecordTask::get_ptrace_eventmsg_seccomp_data() {
unsigned long data = 0;
// in theory we could hit an assertion failure if the tracee suffers
// a SIGKILL before we get here. But the SIGKILL would have to be
// precisely timed between the generation of a PTRACE_EVENT_FORK/CLONE/
// SYS_clone event, and us fetching the event message here.
if (fallible_ptrace(PTRACE_GETEVENTMSG, nullptr, &data) < 0) {
ASSERT(this, errno == ESRCH);
return -1;
}
return data;
}
void RecordTask::record_local(remote_ptr<void> addr, ssize_t num_bytes,
const void* data) {
ASSERT(this, num_bytes >= 0);
if (!addr) {
return;
}
trace_writer().write_raw(rec_tid, data, num_bytes, addr);
}
bool RecordTask::record_remote_by_local_map(remote_ptr<void> addr,
size_t num_bytes) {
if (uint8_t* local_addr = as->local_mapping(addr, num_bytes)) {
record_local(addr, num_bytes, local_addr);
return true;
}
return false;
}
void RecordTask::record_remote(remote_ptr<void> addr, ssize_t num_bytes) {
ASSERT(this, num_bytes >= 0);
if (!addr) {
return;
}
if (record_remote_by_local_map(addr, num_bytes) != 0) {
return;
}
bool ok = true;
auto buf = read_mem(addr.cast<uint8_t>(), num_bytes, &ok);
if (!ok) {
// Tracee probably died unexpectely. This should only happen
// due to SIGKILL racing with our PTRACE_CONT.
if (!vm()->find_other_thread_group(this) &&
vm()->range_is_private_mapping(MemoryRange(addr, num_bytes))) {
// The recording range is mapped private and no other threadgroup shares the
// address space, so the new memory contents should be unobservable, and we can
// just not record the data.
return;
}
ASSERT(this, false) << "Should have recorded " << num_bytes << " bytes from "
<< addr << ", but failed";
}
trace_writer().write_raw(rec_tid, buf.data(), num_bytes, addr);
}
void RecordTask::record_remote_writable(remote_ptr<void> addr,
ssize_t num_bytes) {
ASSERT(this, num_bytes >= 0);
remote_ptr<void> p = addr;
bool seen_rr_mapping = false;
int mapping_count = 0;
while (p < addr + num_bytes) {
if (!as->has_mapping(p)) {
break;
}
++mapping_count;
auto m = as->mapping_of(p);
if (m.flags) {
seen_rr_mapping = true;
}
if (!(m.map.prot() & PROT_WRITE) ||
(seen_rr_mapping && mapping_count > 1)) {
break;
}
p = m.map.end();
}
num_bytes = min(num_bytes, p - addr);
record_remote(addr, num_bytes);
}
ssize_t RecordTask::record_remote_fallible(remote_ptr<void> addr,
uintptr_t num_bytes,
const std::vector<WriteHole>& holes) {
auto hole_iter = holes.begin();
uintptr_t offset = 0;
vector<uint8_t> buf;
while (offset < num_bytes) {
if (hole_iter != holes.end() && hole_iter->offset == offset) {
offset += hole_iter->size;
++hole_iter;
continue;
}
uintptr_t bytes = min(uintptr_t(4*1024*1024), num_bytes - offset);
if (hole_iter != holes.end()) {
ASSERT(this, hole_iter->offset > offset);
bytes = min(bytes, uintptr_t(hole_iter->offset) - offset);
}
if (record_remote_by_local_map(addr + offset, bytes)) {
offset += bytes;
continue;
}
if (addr) {
buf.resize(bytes);
ssize_t nread = read_bytes_fallible(addr + offset, bytes, buf.data());
if (nread <= 0) {
break;
}
trace_writer().write_raw_data(buf.data(), nread);
offset += nread;
} else {
offset += bytes;
}
}
trace_writer().write_raw_header(rec_tid, offset, addr, holes);
return offset;
}
void RecordTask::record_remote_even_if_null(remote_ptr<void> addr,
ssize_t num_bytes) {
DEBUG_ASSERT(num_bytes >= 0);
if (!addr) {
trace_writer().write_raw(rec_tid, nullptr, 0, addr);
return;
}
if (record_remote_by_local_map(addr, num_bytes) != 0) {
return;
}
auto buf = read_mem(addr.cast<uint8_t>(), num_bytes);
trace_writer().write_raw(rec_tid, buf.data(), num_bytes, addr);
}
void RecordTask::pop_event(EventType expected_type) {
ASSERT(this, pending_events.back().type() == expected_type);
pending_events.pop_back();
}
void RecordTask::log_pending_events() const {
ssize_t depth = pending_events.size();
DEBUG_ASSERT(depth > 0);
if (1 == depth) {
LOG(info) << "(no pending events)";
return;
}
/* The event at depth 0 is the placeholder event, which isn't
* useful to log. Skip it. */
for (auto it = pending_events.rbegin(); it != pending_events.rend(); ++it) {
LOG(info) << *it;
}
}
template <typename Arch>
static void maybe_handle_set_robust_list_arch(RecordTask* t) {
auto remote_locals = AddressSpace::preload_thread_locals_start()
.cast<preload_thread_locals<Arch>>();
if (!remote_locals) {
return;
}
auto robust_list_ptr = REMOTE_PTR_FIELD(remote_locals, robust_list);
auto robust_list = t->read_mem(robust_list_ptr);
if (robust_list.len) {
t->set_robust_list(robust_list.head.rptr(), robust_list.len);
auto robust_list_len_ptr = REMOTE_PTR_FIELD(robust_list_ptr, len);
t->write_mem(robust_list_len_ptr, static_cast<uint32_t>(0));
robust_list.len = 0;
t->record_local(robust_list_len_ptr, &robust_list.len);
}
}
static void maybe_handle_set_robust_list(RecordTask* t) {
RR_ARCH_FUNCTION(maybe_handle_set_robust_list_arch, t->arch(), t);
}
template <typename Arch>
static void maybe_handle_rseq_arch(RecordTask* t) {
auto remote_locals = AddressSpace::preload_thread_locals_start()
.cast<preload_thread_locals<Arch>>();
if (!remote_locals) {
return;
}
auto rseq_ptr = REMOTE_PTR_FIELD(remote_locals, rseq);
auto rseq = t->read_mem(rseq_ptr);
if (rseq.len) {
t->rseq_state = make_unique<RseqState>(rseq.rseq.rptr(), rseq.sig);
auto rseq_len_ptr = REMOTE_PTR_FIELD(rseq_ptr, len);
t->write_mem(rseq_len_ptr, static_cast<uint32_t>(0));
rseq.len = 0;
t->record_local(rseq_len_ptr, &rseq.len);
}
}
static void maybe_handle_rseq(RecordTask* t) {
RR_ARCH_FUNCTION(maybe_handle_rseq_arch, t->arch(), t);
}
void RecordTask::maybe_flush_syscallbuf() {
if (EV_SYSCALLBUF_FLUSH == ev().type()) {
// Already flushing.
return;
}
if (!syscallbuf_child) {
return;
}
// This can be called while the task is not stopped, when we prematurely
// terminate the trace. In that case, the tracee could be concurrently
// modifying the header. We'll take a snapshot of the header now.
// The syscallbuf code ensures that writes to syscallbuf records
// complete before num_rec_bytes is incremented.
struct syscallbuf_hdr hdr = read_mem(syscallbuf_child);
ASSERT(this,
!flushed_syscallbuf || flushed_num_rec_bytes == hdr.num_rec_bytes);
if (!hdr.num_rec_bytes || flushed_syscallbuf) {
// no records, or we've already flushed.
return;
}
push_event(Event(SyscallbufFlushEvent()));
// Apply buffered mprotect operations and flush the buffer in the tracee.
if (hdr.mprotect_record_count) {
auto& records = ev().SyscallbufFlush().mprotect_records;
records = read_mem(REMOTE_PTR_FIELD(preload_globals, mprotect_records[0]),
hdr.mprotect_record_count);
for (auto& r : records) {
as->protect(this, r.start, r.size, r.prot);
}
}
// Write the entire buffer in one shot without parsing it,
// because replay will take care of that.
if (is_stopped()) {
record_remote(syscallbuf_child, syscallbuf_data_size());
} else {
vector<uint8_t> buf;
buf.resize(sizeof(hdr) + hdr.num_rec_bytes);
memcpy(buf.data(), &hdr, sizeof(hdr));
read_bytes_helper(syscallbuf_child + 1, hdr.num_rec_bytes,
buf.data() + sizeof(hdr));
record_local(syscallbuf_child, buf.size(), buf.data());
}
maybe_handle_rseq(this);
maybe_handle_set_robust_list(this);
record_current_event();
pop_event(EV_SYSCALLBUF_FLUSH);
flushed_syscallbuf = true;
flushed_num_rec_bytes = hdr.num_rec_bytes;
LOG(debug) << "Syscallbuf flushed with num_rec_bytes="
<< (uint32_t)hdr.num_rec_bytes;
}
/**
* If the syscallbuf has just been flushed, and resetting hasn't been
* overridden with a delay request, then record the reset event for
* replay.
*/
void RecordTask::maybe_reset_syscallbuf() {
if (flushed_syscallbuf && !delay_syscallbuf_reset_for_desched &&
!delay_syscallbuf_reset_for_seccomp_trap) {
flushed_syscallbuf = false;
LOG(debug) << "Syscallbuf reset";
reset_syscallbuf();
syscallbuf_blocked_sigs_generation = 0;
record_event(Event::syscallbuf_reset());
}
}
void RecordTask::record_event(Event ev, FlushSyscallbuf flush,
AllowSyscallbufReset reset,
const Registers* registers) {
if (flush == FLUSH_SYSCALLBUF) {
maybe_flush_syscallbuf();
}
FrameTime current_time = trace_writer().time();
if (should_dump_memory(ev, current_time)) {
dump_process_memory(this, current_time, "rec");
}
if (should_checksum(ev, current_time)) {
checksum_process_memory(this, current_time);
}
if (trace_writer().clear_fip_fdp()) {
const ExtraRegisters* maybe_extra = extra_regs_fallible();
if (maybe_extra) {
ExtraRegisters extra_registers = *maybe_extra;
extra_registers.clear_fip_fdp();
set_extra_regs(extra_registers);
}
}
const ExtraRegisters* extra_registers = nullptr;
if (ev.record_regs()) {
if (!registers) {
registers = &regs();
}
if (ev.record_extra_regs()) {
extra_registers = &extra_regs();
}
}
if (ev.is_syscall_event()) {
auto state = ev.Syscall().state;
if (state == EXITING_SYSCALL) {
ticks_at_last_recorded_syscall_exit = tick_count();
ip_at_last_recorded_syscall_exit = registers->ip();
if (ticks_at_last_recorded_syscall_exit == ticks_at_last_syscall_entry &&
ip_at_last_recorded_syscall_exit == ip_at_last_syscall_entry) {
// We've done processing this syscall so we can forget about the entry now
// This makes sure that any restarted syscalls would not be treated
// as the same entry.
ticks_at_last_syscall_entry = 0;
ip_at_last_syscall_entry = nullptr;
last_syscall_entry_recorded = false;
}
} else if (state == ENTERING_SYSCALL || state == ENTERING_SYSCALL_PTRACE) {
if (tick_count() == ticks_at_last_syscall_entry &&
registers->ip() == ip_at_last_syscall_entry) {
// Let the process handler know that we've recorded the entry already
last_syscall_entry_recorded = true;
}
}
}
remote_code_ptr rseq_new_ip = ip();
bool invalid_rseq_cs = false;
if (should_apply_rseq_abort(ev.type(), &rseq_new_ip, &invalid_rseq_cs)) {
auto addr = REMOTE_PTR_FIELD(rseq_state->ptr.cast<typename NativeArch::rseq_t>(), rseq_cs);
uint64_t value = 0;
write_mem(addr, value);
record_local(addr, &value);
} else {
ASSERT(this, !invalid_rseq_cs) << "Invalid rseq_cs found, not currently emulated properly by rr (should segfault)";
}
trace_writer().write_frame(this, ev, registers, extra_registers);
LOG(debug) << "Wrote event " << ev << " for time " << current_time;
if (rseq_new_ip != ip()) {
Registers r = regs();
r.set_ip(rseq_new_ip);
set_regs(r);
}
if (!ev.has_ticks_slop() && reset == ALLOW_RESET_SYSCALLBUF) {
// After we've output an event, it's safe to reset the syscallbuf (if not
// explicitly delayed) since we will have exited the syscallbuf code that
// consumed the syscallbuf data.
// This only works if the event has a reliable tick count so when we
// reach it, we're done.
maybe_reset_syscallbuf();
}
}
bool RecordTask::is_fatal_signal(int sig,
SignalDeterministic deterministic) const {
if (thread_group()->received_sigframe_SIGSEGV) {
// Can't be blocked, caught or ignored
return true;
}
auto action = default_action(sig);
if (action != DUMP_CORE && action != TERMINATE) {
// If the default action doesn't kill the process, it won't die.
return false;
}
if (is_sig_ignored(sig)) {
// Deterministic fatal signals can't be ignored.
return deterministic == DETERMINISTIC_SIG;
}
// If there's a signal handler, the signal won't be fatal.
return !signal_has_user_handler(sig);
}
void RecordTask::record_current_event() { record_event(ev()); }
pid_t RecordTask::find_newborn_thread() {
ASSERT(this, session().is_recording());
ASSERT(this, ptrace_event() == PTRACE_EVENT_CLONE);
pid_t hint = get_ptrace_eventmsg_pid();
char path[PATH_MAX];
if (hint >= 0) {
sprintf(path, "/proc/%d/task/%d", tid, hint);
struct stat stat_buf;
// This should always succeed, but may fail in old kernels due to
// a kernel bug. See RecordSession::handle_ptrace_event.
if (!session().find_task(hint) && 0 == stat(path, &stat_buf)) {
return hint;
}
}
sprintf(path, "/proc/%d/task", tid);
DIR* dir = opendir(path);
ASSERT(this, dir);
while (true) {
struct dirent* result = readdir(dir);
ASSERT(this, result);
char* end;
pid_t thread_tid = strtol(result->d_name, &end, 10);
if (*end == '\0' && !session().find_task(thread_tid)) {
closedir(dir);
return thread_tid;
}
}
}
pid_t RecordTask::find_newborn_process(pid_t child_parent) {
ASSERT(this, session().is_recording());
ASSERT(this,
ptrace_event() == PTRACE_EVENT_CLONE ||
ptrace_event() == PTRACE_EVENT_VFORK ||
ptrace_event() == PTRACE_EVENT_FORK);
pid_t hint = get_ptrace_eventmsg_pid();
// This should always succeed, but may fail in old kernels due to
// a kernel bug. See RecordSession::handle_ptrace_event.
if (hint >= 0 && !session().find_task(hint) && get_ppid(hint) == child_parent) {
return hint;
}
DIR* dir = opendir("/proc");
ASSERT(this, dir);
while (true) {
struct dirent* result = readdir(dir);
ASSERT(this, result);
char* end;
pid_t proc_tid = strtol(result->d_name, &end, 10);
if (*end == '\0' && !session().find_task(proc_tid) &&
get_ppid(proc_tid) == child_parent) {
closedir(dir);
return proc_tid;
}
}
}
void RecordTask::set_tid_addr(remote_ptr<int> tid_addr) {
LOG(debug) << "updating cleartid futex to " << tid_addr;
tid_futex = tid_addr;
}
void RecordTask::update_own_namespace_tid() {
AutoRemoteSyscalls remote(this);
own_namespace_rec_tid =
remote.infallible_syscall_if_alive(syscall_number_for_gettid(arch()));
if (own_namespace_rec_tid == -ESRCH) {
own_namespace_rec_tid = -1;
}
}
void RecordTask::kill_if_alive() {
if (!seen_ptrace_exit_event()) {
tgkill(SIGKILL);
}
}
pid_t RecordTask::get_parent_pid() const { return get_ppid(tid); }
void RecordTask::set_tid_and_update_serial(pid_t tid,
pid_t own_namespace_tid) {
hpc.set_tid(tid);
this->tid = rec_tid = tid;
serial = session().next_task_serial();
own_namespace_rec_tid = own_namespace_tid;
}
bool RecordTask::may_reap() {
if (emulated_stop_pending) {
LOG(debug) << "Declining to reap " << tid << "; emulated stop pending";
// Don't reap until the emulated ptrace stop has been processed.
return false;
}
// Non thread-group-leaders may always be reaped
if (tid != real_tgid()) {
return true;
}
for (auto it : thread_group()->task_set()) {
if (&*it != this) {
LOG(debug) << "Declining to reap " << tid << "; leader of non-empty thread-group with active thread " << it->tid;
return false;
}
}
return true;
}
void RecordTask::reap() {
ASSERT(this, !was_reaped_);
LOG(debug) << "Reaping " << tid;
WaitOptions options(tid);
options.block_seconds = 0;
WaitManager::wait_exit(options);
was_reaped_ = true;
}
static uint64_t read_pid_ns(const RecordTask* t) {
char buf[PATH_MAX];
sprintf(buf, "/proc/%d/ns/pid", t->tid);
char link[PATH_MAX];
int ret = readlink(buf, link, sizeof(link));
ASSERT(t, ret >= 0);
ASSERT(t, ret < (int)sizeof(link));
link[ret] = 0;
ASSERT(t, strncmp(link, "pid:[", 5) == 0);
char* end;
uint64_t result = strtoul(link + 5, &end, 10);
ASSERT(t, strcmp(end, "]") == 0);
return result;
}
bool RecordTask::waiting_for_pid_namespace_tasks_to_exit() const {
// read_pid_ns() will fail if we're reaped
if (tg->tgid_own_namespace != 1 || was_reaped()) {
return false;
}
// This might be the last live thread for pid-1 in the pid namespace.
// Checking that it *is* the last live thread is tricky because other
// threads could unexpectedly die asynchronously :-(.
// See if there are any other tasks in the pid namespace.
// Note that due to setns there can be tasks in the pid namespace
// with parents outside the pid namespace other than our thread-group.
// If there are multiple threads in our threadgroup, they're in our
// pid namespace.
if (thread_group()->task_set().size() > 1) {
return true;
}
// If we have any child processes then those belong to our pid namespace
// (or a descendant).
for (auto p : session().thread_group_map()) {
if (p.second->parent() == tg.get()) {
return true;
}
}
// If there are any other tasks in the pid namespace at least one must be
// directly in the namespace.
uint64_t pid_ns = read_pid_ns(this);
for (auto it : session().tasks()) {
auto rt = static_cast<RecordTask*>(it.second);
if (rt == this) {
continue;
}
if (read_pid_ns(rt) == pid_ns) {
return true;
}
}
return false;
}
// Disable chaos mode memory randomization for 32-bit ASAN builds. There isn't
// much address space to play with and it gets tricky.
bool RecordTask::enable_chaos_memory_allocations() const {
return session().enable_chaos() &&
(session().excluded_ranges().empty() || word_size(arch()) >= 8);
}
int RecordTask::process_depth() const {
int depth = 0;
ThreadGroup* tg = this->tg.get();
while (tg) {
++depth;
tg = tg->parent();
}
return depth;
}
template <typename Arch>
static void maybe_restore_original_syscall_registers_arch(RecordTask* t,
void* local_addr) {
if (!local_addr) {
return;
}
auto locals = reinterpret_cast<preload_thread_locals<Arch>*>(local_addr);
static_assert(sizeof(*locals) <= PRELOAD_THREAD_LOCALS_SIZE,
"bad PRELOAD_THREAD_LOCALS_SIZE");
if (!locals->original_syscall_parameters) {
return;
}
auto args = t->read_mem(locals->original_syscall_parameters.rptr());
Registers r = t->regs();
if (args.no != r.syscallno()) {
// Maybe a preparatory syscall before the real syscall (e.g. sys_read)
return;
}
r.set_arg1(args.args[0]);
r.set_arg2(args.args[1]);
r.set_arg3(args.args[2]);
r.set_arg4(args.args[3]);
r.set_arg5(args.args[4]);
r.set_arg6(args.args[5]);
t->set_regs(r);
}
void RecordTask::maybe_restore_original_syscall_registers() {
RR_ARCH_FUNCTION(maybe_restore_original_syscall_registers_arch, arch(), this,
preload_thread_locals());
}
bool RecordTask::post_vm_clone(CloneReason reason, int flags, Task* origin) {
if (Task::post_vm_clone(reason, flags, origin)) {
KernelMapping preload_thread_locals_mapping =
vm()->mapping_of(AddressSpace::preload_thread_locals_start()).map;
auto mode = trace_writer().write_mapped_region(
this, preload_thread_locals_mapping,
preload_thread_locals_mapping.fake_stat(),
preload_thread_locals_mapping.fsname(),
vector<TraceRemoteFd>(),
TraceWriter::RR_BUFFER_MAPPING);
ASSERT(this, mode == TraceWriter::DONT_RECORD_IN_TRACE);
return true;
}
return false;
};
} // namespace rr