Skip to content

Commit ec66b21

Browse files
authored
Improve support for ptrace emulation during group stops. (#3926)
Firefox's minidump writer expects to be able to attach to tasks in group stops and invoke ptrace commands on them (e.g. PTRACE_GETREGS) Fixes #3741.
1 parent abc62ca commit ec66b21

File tree

5 files changed

+116
-23
lines changed

5 files changed

+116
-23
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,7 @@ set(BASIC_TESTS
12611261
x86/ptrace_debug_regs
12621262
ptrace_exec
12631263
x86/ptrace_exec32
1264+
ptrace_group_stop
12641265
ptrace_kill_grandtracee
12651266
x86/ptrace_tls
12661267
ptrace_seize

src/RecordTask.cc

+29-20
Original file line numberDiff line numberDiff line change
@@ -1185,34 +1185,28 @@ void RecordTask::emulate_SIGCONT() {
11851185
}
11861186

11871187
void RecordTask::signal_delivered(int sig) {
1188+
bool needs_SIGCHLD = true;
11881189
Sighandler& h = sighandlers->get(sig);
11891190
if (h.resethand) {
11901191
reset_handler(&h, arch());
11911192
}
11921193

1193-
if (!is_sig_ignored(sig)) {
1194-
switch (sig) {
1195-
case SIGTSTP:
1196-
case SIGTTIN:
1197-
case SIGTTOU:
1198-
if (h.disposition() == SIGNAL_HANDLER) {
1199-
break;
1200-
}
1201-
RR_FALLTHROUGH;
1202-
case SIGSTOP:
1203-
// All threads in the process are stopped.
1204-
for (Task* t : thread_group()->task_set()) {
1205-
auto rt = static_cast<RecordTask*>(t);
1206-
rt->apply_group_stop(sig);
1207-
}
1208-
break;
1209-
case SIGCONT:
1210-
emulate_SIGCONT();
1211-
break;
1194+
if (is_sig_stopping(sig)) {
1195+
// All threads in the process are stopped.
1196+
for (Task* t : thread_group()->task_set()) {
1197+
auto rt = static_cast<RecordTask*>(t);
1198+
rt->apply_group_stop(sig);
12121199
}
1200+
// apply_group_stop calls send_synthetic_SIGCHLD_if_necessary(). Don't
1201+
// do it again.
1202+
needs_SIGCHLD = false;
1203+
} else if (sig == SIGCONT && !is_sig_ignored(sig)) {
1204+
emulate_SIGCONT();
12131205
}
12141206

1215-
send_synthetic_SIGCHLD_if_necessary();
1207+
if (needs_SIGCHLD) {
1208+
send_synthetic_SIGCHLD_if_necessary();
1209+
}
12161210
}
12171211

12181212
bool RecordTask::signal_has_user_handler(int sig) const {
@@ -1259,6 +1253,21 @@ bool RecordTask::is_sig_ignored(int sig) const {
12591253
}
12601254
}
12611255

1256+
bool RecordTask::is_sig_stopping(int sig) const {
1257+
switch (sig) {
1258+
case SIGTSTP:
1259+
case SIGTTIN:
1260+
case SIGTTOU:
1261+
if (sig_disposition(sig) != SIGNAL_DEFAULT) {
1262+
break;
1263+
}
1264+
RR_FALLTHROUGH;
1265+
case SIGSTOP:
1266+
return true;
1267+
}
1268+
return false;
1269+
}
1270+
12621271
SignalDisposition RecordTask::sig_disposition(int sig) const {
12631272
return sighandlers->get(sig).disposition();
12641273
}

src/RecordTask.h

+4
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class RecordTask final : public Task {
246246
* default disposition is "ignore".
247247
*/
248248
bool is_sig_ignored(int sig) const;
249+
/**
250+
* Return true iff |sig| is a stopping signal.
251+
*/
252+
bool is_sig_stopping(int sig) const;
249253
/**
250254
* Return the applications current disposition of |sig|.
251255
*/

src/Scheduler.cc

+18-3
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,22 @@ bool Scheduler::is_task_runnable(RecordTask* t, WaitAggregator& wait_aggregator,
345345
WaitAggregator::try_wait_exit(t);
346346
// N.B.: If we supported ptrace exit notifications for killed tracee's
347347
// that would need handling here, but we don't at the moment.
348-
return t->seen_ptrace_exit_event();
348+
if (t->seen_ptrace_exit_event()) {
349+
LOGM(debug) << " ... but it died";
350+
return true;
351+
}
352+
if (t->is_stopped()) {
353+
return false;
354+
}
355+
// If we're not stopped, we need to get to the stop.
356+
// AFAIK we can only get here with group stops, which are eagerly applied
357+
// to every task in the group. If I'm wrong, die here.
358+
ASSERT(t, t->emulated_stop_type == GROUP_STOP);
359+
LOGM(debug) << " interrupting and waiting";
360+
t->do_ptrace_interrupt();
361+
// Wait on the task to get the kernel to kick it into the group stop.
362+
// If it died, we can deal with it later.
363+
return t->wait();
349364
}
350365
}
351366

@@ -787,8 +802,8 @@ Scheduler::Rescheduled Scheduler::reschedule(Switchable switchable) {
787802
#ifdef MONITOR_UNSWITCHABLE_WAITS
788803
double wait_duration = monotonic_now_sec() - now;
789804
if (wait_duration >= 0.010) {
790-
log_warn("Waiting for unswitchable %s took %g ms",
791-
strevent(current_->event), 1000.0 * wait_duration);
805+
LOGM(warn) << "Waiting for unswitchable " << current_->ev()
806+
<< " took " << 1000.0 * wait_duration << "ms";
792807
}
793808
#endif
794809
}

src/test/ptrace_group_stop.c

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
2+
3+
#include "util.h"
4+
#include "ptrace_util.h"
5+
6+
static void* do_thread(void* arg) {
7+
int pipe_fd = *(int*)arg;
8+
uint32_t tid = gettid();
9+
10+
write(pipe_fd, &tid, 4);
11+
/* Sleep long enough that it will be noticed if it's not interrupted. */
12+
sleep(1000);
13+
14+
return NULL;
15+
}
16+
17+
int main(void) {
18+
pid_t child, child2;
19+
uint32_t msg;
20+
int status;
21+
int pipe_fds[2];
22+
struct user_regs_struct regs;
23+
24+
test_assert(0 == pipe(pipe_fds));
25+
26+
if (0 == (child = fork())) {
27+
pthread_t t;
28+
29+
pthread_create(&t, NULL, do_thread, &pipe_fds[1]);
30+
pthread_join(t, NULL);
31+
32+
return 77;
33+
}
34+
35+
test_assert(4 == read(pipe_fds[0], &msg, 4));
36+
child2 = (pid_t)msg;
37+
close(pipe_fds[0]);
38+
sched_yield();
39+
40+
/* Hit the entire process group with a SIGSTOP. */
41+
tgkill(child, child, SIGSTOP);
42+
43+
/* Force the rr scheduler to run. */
44+
sched_yield();
45+
46+
/* Now seize the stopped task. */
47+
test_assert(0 == ptrace(PTRACE_SEIZE, child2, 0, 0));
48+
test_assert(child2 == waitpid(child2, &status, 0));
49+
test_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
50+
51+
/* Do something that requires the task to be stopped. */
52+
ptrace_getregs(child2, &regs);
53+
54+
/* Verify that we can resume from group stops. */
55+
test_assert(0 == ptrace(PTRACE_CONT, child2, 0, 0));
56+
/* Force the rr scheduler to run. */
57+
sched_yield();
58+
test_assert(0 == ptrace(PTRACE_INTERRUPT, child2, 0, 0));
59+
test_assert(child2 == waitpid(child2, &status, 0));
60+
test_assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
61+
62+
atomic_puts("EXIT-SUCCESS");
63+
return 0;
64+
}

0 commit comments

Comments
 (0)