This needs more comment, I'll try to add them in a separate patch. With the recent changes in utrace API utrace_control(UTRACE_SINGLESTEP) postpones enable_step() to utrace_resume() stage. The tracee can pass tracehook_report_syscall_exit() without TIF_SINGLESTEP, this breaks the send_sigtrap() logic.
Change ptrace_resume_action() to set UTRACE_EVENT(SYSCALL_EXIT) in PTRACE_SINGLESTEP/PTRACE_SINGLEBLOCK case, change report_syscall_exit() to check ctx->resume to detect this case. To synthesize the trap we set ctx->signr = SIGTRAP and provoke UTRACE_SIGNAL_REPORT, like ptrace_resume() does. Roland, we already discussed this a bit and I agree, this is not optimal. We need some changes in utrace, but currently it has a lot of problems and it is not clear yet what utrace should do. Let's fix the code now, once we find a better solution we can revert this change. With this patch "make check" passes all tests again (to clarify, except some tests which upstream doesn't pass too), including this one: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <errno.h> #include <sys/wait.h> #include <sys/ptrace.h> #include <assert.h> #include <sys/user.h> #include <sys/debugreg.h> #include <sys/syscall.h> #define WEVENT(s) ((s & 0xFF0000) >> 16) static int verbose; #define d_printf(fmt, ...) do { if (verbose) printf(fmt, ##__VA_ARGS__); } while (0) static struct user_regs_struct regs; static void resume(int pid, int req, int ck_stat) { int stat; assert(0 == ptrace(req, pid, 0, 0)); assert(waitpid(pid, &stat, __WALL) == pid); //d_printf("===> %06X\n %06X\n", ck_stat, stat); assert(stat == ck_stat); assert(0 == ptrace(PTRACE_GETREGS, pid, NULL, ®s)); } int main(int argc, const char *argv[]) { int pid, child, stat; long rip, nxt_rip; if (getpid() == __NR_getppid) { printf("sorry, restart\n"); return 0; } verbose = argc > 1; pid = fork(); if (!pid) { assert(0 == ptrace(PTRACE_TRACEME, 0,0,0)); kill(getpid(), SIGSTOP); // 1: SYSCALL + SYSCALL + STEP getppid(); // 2: SYSCALL + STEP getppid(); // 3: STEP getppid(); // 4: SYSCALL + STEP if (!fork()) exit(73); // STEPs only if (!fork()) exit(73); assert(0); } assert(wait(&stat) == pid); assert(WIFSTOPPED(stat) && WSTOPSIG(stat) == SIGSTOP); assert(0 == ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK)); //----------------------------------------------------------------------------- d_printf("1: syscall enter\n"); resume(pid, PTRACE_SYSCALL, 0x857F); assert(regs.orig_rax == __NR_getppid); assert(regs.rax == -ENOSYS); rip = regs.rip; d_printf("1: syscall leave\n"); resume(pid, PTRACE_SYSCALL, 0x857F); assert(regs.orig_rax == __NR_getppid); assert(regs.rax == getpid()); assert(regs.rip == rip); d_printf("1: singlestep\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert((long)regs.orig_rax < 0); assert(regs.rax == getpid()); assert(regs.rip != rip); d_printf("1: singlestep\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert(regs.rip != rip); //-------------------------------------------------------------------------------- d_printf("2: stop before syscall insn\n"); do { resume(pid, PTRACE_SINGLESTEP, 0x57F); } while (regs.rip != rip - 2); assert(regs.rax == __NR_getppid); d_printf("2: syscall enter\n"); resume(pid, PTRACE_SYSCALL, 0x857F); assert(regs.orig_rax == __NR_getppid); assert(regs.rax == -ENOSYS); assert(regs.rip == rip); d_printf("2: singlestep\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert(regs.orig_rax == __NR_getppid); assert(regs.rax == getpid()); assert(regs.rip == rip); d_printf("2: singlestep\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert(regs.rip != rip); //-------------------------------------------------------------------------------- d_printf("3: stop before syscall insn\n"); do { resume(pid, PTRACE_SINGLESTEP, 0x57F); } while (regs.rip != rip - 2); assert(regs.rax == __NR_getppid); d_printf("3: step over syscall\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert(regs.orig_rax == __NR_getppid); assert(regs.rax == getpid()); assert(regs.rip == rip); d_printf("3: step over syscall\n"); resume(pid, PTRACE_SINGLESTEP, 0x57F); assert(regs.rip != rip); //---------------------------------------------------------------------------------- d_printf("4: syscall enter\n"); resume(pid, PTRACE_SYSCALL, 0x857F); assert(regs.orig_rax == __NR_clone); assert(regs.rax == -ENOSYS); rip = regs.rip; child = waitpid (-1, &stat, WNOHANG); assert(child == 0); d_printf("4: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F | (PTRACE_EVENT_FORK << 16)); assert(regs.orig_rax == __NR_clone); //assert(regs.rax == -ENOSYS); assert(regs.rip == rip); child = waitpid (-1, &stat, __WALL); assert(child > 0 && child != pid); d_printf("4: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F); assert(regs.orig_rax == __NR_clone); assert(regs.rax == child); assert(regs.rip == rip); d_printf("4: provoke SIGCHLD\n"); kill(child, SIGKILL); assert(child == waitpid (-1, &stat, __WALL)); assert(stat = SIGKILL); d_printf("4: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x7F | (SIGCHLD << 8)); assert(regs.rip == rip); d_printf("4: SIGTERM + STEP\n"); kill(pid, SIGTERM); resume(pid, PTRACE_SINGLESTEP, 0x7F | (SIGTERM << 8)); assert(regs.rip == rip); d_printf("4: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F); assert(regs.rip != rip); nxt_rip = regs.rip; //--------------------------------------------------------------------------- d_printf("5: stop before syscall insn\n"); do { resume(pid, PTRACE_SINGLESTEP, 0x57F); } while (regs.rip != rip - 2); assert(regs.rax == __NR_clone); d_printf("5: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F | (PTRACE_EVENT_FORK << 16)); assert(regs.orig_rax == __NR_clone); //assert(regs.rax == -ENOSYS); assert(regs.rip == rip); child = waitpid (-1, &stat, __WALL); assert(child > 0 && child != pid); d_printf("5: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F); assert(regs.orig_rax == __NR_clone); assert(regs.rax == child); assert(regs.rip == rip); d_printf("5: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F); assert(regs.rip == nxt_rip); kill(child, SIGKILL); assert(child == waitpid (-1, &stat, __WALL)); assert(stat = SIGKILL); d_printf("5: step\n"); resume(pid, PTRACE_CONT, 0x7F | (SIGCHLD << 8)); assert(regs.rip == nxt_rip); d_printf("5: step\n"); resume(pid, PTRACE_SINGLESTEP, 0x057F); assert(regs.rip != nxt_rip); //---------------------------------------------------------------------------- kill(pid, SIGKILL); return 0; } --- kernel/ptrace.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) --- UTRACE-PTRACE/kernel/ptrace.c~133_SYSCALL_EXIT_TRAP 2009-11-16 00:04:22.000000000 +0100 +++ UTRACE-PTRACE/kernel/ptrace.c 2009-11-16 16:59:48.000000000 +0100 @@ -436,8 +436,16 @@ static u32 ptrace_report_syscall_exit(en if (ptrace_event_pending(ctx)) return UTRACE_STOP; - set_syscall_code(ctx, PTRACE_EVENT_SYSCALL_EXIT); + if (ctx->resume != UTRACE_RESUME) { + WARN_ON(ctx->resume != UTRACE_BLOCKSTEP && + ctx->resume != UTRACE_SINGLESTEP); + ctx->resume = UTRACE_RESUME; + + ctx->signr = SIGTRAP; + return UTRACE_INTERRUPT; + } + set_syscall_code(ctx, PTRACE_EVENT_SYSCALL_EXIT); return UTRACE_STOP; } @@ -879,6 +887,7 @@ static int ptrace_resume_action(struct t if (unlikely(!arch_has_block_step())) return -EIO; action = UTRACE_BLOCKSTEP; + events |= UTRACE_EVENT(SYSCALL_EXIT); break; #endif @@ -887,6 +896,7 @@ static int ptrace_resume_action(struct t if (unlikely(!arch_has_single_step())) return -EIO; action = UTRACE_SINGLESTEP; + events |= UTRACE_EVENT(SYSCALL_EXIT); break; #endif