Andrew Gunnerson
Andrew Gunnerson

Reputation: 658

Why does a SIGTRAP PTRACE_EVENT_STOP occur when the tracee receives SIGCONT?

I'm using PTRACE_SEIZE to trace the execution of a child process, but I'm running into an issue where a non-group-stop PTRACE_EVENT_STOP (signal == SIGTRAP) is being emitted when the tracee receives a SIGCONT. I can't seem to find any documentation that might indicate why this is happening. From what I can gather in the ptrace(2) manpage, PTRACE_EVENT_STOP only occurs when one of the following conditions are true:

In the sample program below, I'm getting a PTRACE_EVENT_STOP with signal SIGTRAP, but it doesn't match any of those 3 conditions. When I run the program, I get the following output:

[Child] Raising SIGSTOP
Event for pid 29236: Tracee entered group-stop PTRACE_EVENT_STOP (signal: 19)
Event for pid 29236: Tracee entered non-group-stop PTRACE_EVENT_STOP (signal: 5)
Event for pid 29236: Tracee entered/exited syscall
Event for pid 29236: Tracee entered/exited syscall
Event for pid 29236: Tracee received signal (signal: 18)
Event for pid 29236: Tracee entered/exited syscall
[Child] Resumed from SIGSTOP
Event for pid 29236: Tracee entered/exited syscall
Event for pid 29236: Tracee entered/exited syscall
Event for pid 29236: Process exited

Any ideas as to what this particular PTRACE_EVENT_STOP means?


Sample program:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>

static void handle_events(pid_t pid_spec)
{
    while (1) {
        int status;
        pid_t pid = waitpid(pid_spec, &status, __WALL);

        printf("Event for pid %d: ", pid);

        if (WIFEXITED(status)) {
            printf("Process exited\n");
            break;
        } else if (WIFSIGNALED(status)) {
            printf("Process killed by signal\n");
            break;
        } else if (WIFSTOPPED(status)) {
            int ptrace_event = status >> 16;
            int signal = WSTOPSIG(status);

            switch (ptrace_event) {
                case PTRACE_EVENT_STOP:
                    if (signal == SIGSTOP || signal == SIGTSTP
                            || signal == SIGTTIN || signal == SIGTTOU) {
                        printf("Tracee entered group-stop PTRACE_EVENT_STOP (signal: %d)\n", signal);
                        ptrace(PTRACE_LISTEN, pid, NULL, NULL);
                    } else {
                        printf("Tracee entered non-group-stop PTRACE_EVENT_STOP (signal: %d)\n", signal);
                        ptrace(PTRACE_SYSCALL, pid, NULL, 0);
                    }
                    break;

                default:
                    if (signal == (SIGTRAP | 0x80)) {
                        printf("Tracee entered/exited syscall\n");
                        ptrace(PTRACE_SYSCALL, pid, NULL, 0);
                    } else {
                        printf("Tracee received signal (signal: %d)\n", signal);
                        ptrace(PTRACE_SYSCALL, pid, NULL, signal);
                    }
                    break;
            }
        }
    }
}

int main()
{
    pid_t pid = fork();

    if (pid == 0) {
        printf("[Child] Raising SIGSTOP\n");

        // Allow parent to PTRACE_SEIZE
        if (raise(SIGSTOP) != 0) {
            _exit(255);
        }

        printf("[Child] Resumed from SIGSTOP\n");
        _exit(0);
    }

    // Wait for stop
    int status;
    waitpid(pid, &status, WSTOPPED);

    ptrace(PTRACE_SEIZE, pid, NULL, PTRACE_O_TRACESYSGOOD);

    // Allow child to continue
    kill(pid, SIGCONT);

    handle_events(pid);

    return EXIT_SUCCESS;
}

strace seems to call PTRACE_SYSCALL whenever it sees a non-group-stop PTRACE_EVENT_STOP:

Upvotes: 1

Views: 1625

Answers (1)

Andrew Gunnerson
Andrew Gunnerson

Reputation: 658

I managed to figure out why the event occurs.

When a process is being ptrace'd with PTRACE_SEIZE, ptrace_trap_notify() will be called to set JOBCTL_TRAP_NOTIFY when SIGCONT is received or JOBCTL_TRAP_STOP will be set when a stopping signal is received. get_signal() will check for JOBCTL_TRAP_MASK (ie. JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY) and call do_jobctl_trap(). do_jobctl_trap() will then either send <stopping signal> | (PTRACE_EVENT_STOP << 8) if the process enters group stop or else it will send SIGTRAP | (PTRACE_EVENT_STOP << 8).

Thus, when using PTRACE_SEIZE:

  • A stopping signal will result in a <stopping signal> | (PTRACE_EVENT_STOP << 8) event
  • A SIGCONT signal will result in a SIGTRAP | (PTRACE_EVENT_STOP << 8) event

This behavior appears to have been introduced in commit fb1d910c178ba0c5bc32d3e5a9e82e05b7aad3cd.

Upvotes: 3

Related Questions