Skip to content

Commit abc62ca

Browse files
authored
Fix PTRACE_INTERRUPT to actually work if the tracee is not already stopped. (#3923)
The current code is completely untested because it will always call syscall_state.emulate_result twice and fatally assert. The ptrace_seize test does not yield between PTRACE_CONT and PTRACE_INTERRUPT, so rr will not switch to the emulated ptracee and resume execution, and the PTRACE_INTERRUPT will always find the emulated ptracee already stopped. Additionally, the test does not wait on the emulated ptracee again after interrupting it, so the following assertion about the value of status is bogus. Fix all of that by adding the appropriate waitpid, tweaking the status assertion, duplicating the PTRACE_CONT/PTRACE_INTERRUPT sequence but with an intervening sched_yield() to force the emulated ptracee to resume, and fixing rr's PTRACE_INTERRUPT emulation to emulate_result only once and to place an emulated stop on the emulated ptracee.
1 parent f32d474 commit abc62ca

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

src/Scheduler.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ class Scheduler {
211211
* the next runnable task after 't' in round-robin order.
212212
* Sets 'by_waitpid' to true if we determined the task was runnable by
213213
* calling waitpid on it and observing a state change. This task *must*
214-
* be returned by get_next_thread, and is_runnable_task must not be called
214+
* be returned by get_next_thread, and is_task_runnable must not be called
215215
* on it again until it has run.
216216
* Considers only tasks with priority <= priority_threshold.
217217
*/

src/record_syscall.cc

+9-2
Original file line numberDiff line numberDiff line change
@@ -3061,12 +3061,19 @@ static Switchable prepare_ptrace(RecordTask* t,
30613061
case PTRACE_INTERRUPT: {
30623062
RecordTask* tracee = verify_ptrace_target(t, syscall_state, pid, false);
30633063
if (tracee) {
3064+
uint64_t result = 0;
30643065
if (!tracee->is_stopped()) {
30653066
// Running in a blocked syscall. Forward the PTRACE_INTERRUPT.
30663067
// Regular syscall exit handling will take over from here.
3068+
LOG(debug) << "Interrupting " << tracee->tid;
30673069
errno = 0;
30683070
tracee->fallible_ptrace(PTRACE_INTERRUPT, nullptr, nullptr);
3069-
syscall_state.emulate_result(-errno);
3071+
result = -errno;
3072+
// Technically PTRACE_INTERRUPT stops are distinct from group stops,
3073+
// but not in any way we currently care about.
3074+
// NB: Despite the ptrace man page claiming the kernel sends SIGTRAP
3075+
// in practice it actually sends SIGSTOP.
3076+
tracee->apply_group_stop(SIGSTOP);
30703077
} else if (tracee->status().is_syscall()) {
30713078
tracee->emulate_ptrace_stop(tracee->status(), SYSCALL_EXIT_STOP);
30723079
} else if (tracee->emulated_stop_pending == NOT_STOPPED) {
@@ -3075,7 +3082,7 @@ static Switchable prepare_ptrace(RecordTask* t,
30753082
tracee->apply_group_stop(SIGSTOP);
30763083
}
30773084
// Otherwise, there's nothing to do.
3078-
syscall_state.emulate_result(0);
3085+
syscall_state.emulate_result(result);
30793086
}
30803087
break;
30813088
}

src/test/ptrace_seize.c

+18-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,25 @@ int main(void) {
4040
test_assert(status == ((PTRACE_EVENT_STOP << 16) | (SIGSTOP << 8) | 0x7f));
4141

4242
test_assert(0 == ptrace(PTRACE_CONT, child, NULL, 0));
43+
/* Most likely there is no context switch between these two syscalls, rr does
44+
* not actually restart the tracee, and this PTRACE_INTERRUPT sees the task
45+
* is already stopped internally, and is emulated as a noop.
46+
*/
4347
test_assert(0 == ptrace(PTRACE_INTERRUPT, child, NULL, 0));
44-
test_assert(status == ((PTRACE_EVENT_STOP << 16) | (SIGSTOP << 8) | 0x7f) ||
45-
status == (((SIGTRAP | 0x80) << 8) | 0x7f));
48+
test_assert(child == waitpid(child, &status, 0));
49+
test_assert(status == ((PTRACE_EVENT_STOP << 16) | (SIGSTOP << 8) | 0x7f));
50+
51+
test_assert(WIFSTOPPED(status));
52+
53+
test_assert(0 == ptrace(PTRACE_CONT, child, NULL, 0));
54+
/* Use a sched_yield() to force rr to context switch and restart the tracee,
55+
* so when we PTRACE_INTERRUPT below rr is forced to PTRACE_INTERRUPT the
56+
* tracee.
57+
*/
58+
sched_yield();
59+
test_assert(0 == ptrace(PTRACE_INTERRUPT, child, NULL, 0));
60+
test_assert(child == waitpid(child, &status, 0));
61+
test_assert(status == ((PTRACE_EVENT_STOP << 16) | (SIGSTOP << 8) | 0x7f));
4662

4763
test_assert(WIFSTOPPED(status));
4864

0 commit comments

Comments
 (0)