Commit 1e11a6c0 authored by Davide Libenzi's avatar Davide Libenzi Committed by Linus Torvalds

[PATCH] ptrace single-stepping fix

This patch permits a ptrace process on x86 to "see" the instruction following
the INT #80h op.  This has been tested on 2.6.6 using the appended test
source.  Running over this:

80485a9:       b8 14 00 00 00          mov    $0x14,%eax
80485ae:       cd 80                   int    $0x80
80485b0:       89 45 ec                mov    %eax,0xffffffec(%ebp)
80485b3:       eb f4                   jmp    80485a9 <main+0x85>

it produces:

waiting ...
done: pid=12387  status=1407
sig=5
EIP=0x080485a9
waiting ...
done: pid=12387  status=1407
sig=5
EIP=0x080485ae
waiting ...
done: pid=12387  status=1407
sig=5
EIP=0x080485b0
waiting ...
done: pid=12387  status=1407
sig=5
EIP=0x080485b3

(Andi says: "I think this patch is a bad idea.  The ptrace handling is
traditionally fragile (I remember when merging a rather simple patch from IBM
for DR allocation long ago into the suse it broke several debuggers).  If you
really want to do that wait for 2.7.")


test-program.c:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <linux/user.h>
#include <linux/unistd.h>

int main(int ac, char **av) {
	int i, status, res;
	long start, end;
	pid_t cpid, pid;
	struct user_regs_struct ur;
	struct sigaction sa;

	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = SIG_DFL;
	sigaction(SIGCHLD, &sa, NULL);

	printf("nargs=%d\n", ac);
	if (ac == 1)
		goto tracer;

	printf("arg=%s\n", av[1]);
loop:
	__asm__ volatile ("int $0x80"
			  : "=a" (res)
			  : "0" (__NR_getpid));
	goto loop;
endloop:
	exit(0);

tracer:
	if ((cpid = fork()) != 0)
		goto parent;

	printf("child=%d\n", getpid());
	ptrace(PTRACE_TRACEME, 0, NULL, NULL);

	execl(av[0], av[0], "child", NULL);

	exit(0);

parent:
	start = (long) &&loop;
	end = (long) &&endloop;

	printf("pchild=%d\n", cpid);

	for (;;) {
		pid = wait(&status);
		if (pid != cpid)
			continue;
		res = WSTOPSIG(status);
		if (ptrace(PTRACE_GETREGS, pid, NULL, &ur)) {
			printf("[%d] error: ptrace(PTRACE_GETREGS, %d)\n",
			       pid, pid);
			return 1;
		}

		if (ptrace(PTRACE_SINGLESTEP, pid, NULL, res != SIGTRAP ? res: 0)) {
			perror("ptrace(PTRACE_SINGLESTEP)");
			return 1;
		}

		if (ur.eip >= start && ur.eip <= end)
			break;
	}

	for (i = 0; i < 15; i++) {
		printf("waiting ...\n");
		pid = wait(&status);
		printf("done: pid=%d  status=%d\n", pid, status);
		if (pid != cpid)
			continue;
		res = WSTOPSIG(status);
		printf("sig=%d\n", res);
		if (ptrace(PTRACE_GETREGS, pid, NULL, &ur)) {
			printf("[%d] error: ptrace(PTRACE_GETREGS, %d)\n",
			       pid, pid);
			return 1;
		}

		printf("EIP=0x%08x\n", ur.eip);

		if (ptrace(PTRACE_SINGLESTEP, pid, NULL, res != SIGTRAP ? res: 0)) {
			perror("ptrace(PTRACE_SINGLESTEP)");
			return 1;
		}
	}

	if (ptrace(PTRACE_CONT, cpid, NULL, SIGKILL)) {
		perror("ptrace(PTRACE_SINGLESTEP)");
		return 1;
	}

	return 0;
}
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent c49d427d
...@@ -348,7 +348,7 @@ syscall_trace_entry: ...@@ -348,7 +348,7 @@ syscall_trace_entry:
# perform syscall exit tracing # perform syscall exit tracing
ALIGN ALIGN
syscall_exit_work: syscall_exit_work:
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT), %cl testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
jz work_pending jz work_pending
sti # could let do_syscall_trace() call sti # could let do_syscall_trace() call
# schedule() instead # schedule() instead
......
...@@ -147,6 +147,7 @@ void ptrace_disable(struct task_struct *child) ...@@ -147,6 +147,7 @@ void ptrace_disable(struct task_struct *child)
{ {
long tmp; long tmp;
clear_tsk_thread_flag(child, TIF_SINGLESTEP);
tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG;
put_stack_long(child, EFL_OFFSET, tmp); put_stack_long(child, EFL_OFFSET, tmp);
} }
...@@ -370,6 +371,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data) ...@@ -370,6 +371,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
else { else {
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
} }
clear_tsk_thread_flag(child, TIF_SINGLESTEP);
child->exit_code = data; child->exit_code = data;
/* make sure the single step bit is not set. */ /* make sure the single step bit is not set. */
tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG;
...@@ -391,6 +393,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data) ...@@ -391,6 +393,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
if (child->state == TASK_ZOMBIE) /* already dead */ if (child->state == TASK_ZOMBIE) /* already dead */
break; break;
child->exit_code = SIGKILL; child->exit_code = SIGKILL;
clear_tsk_thread_flag(child, TIF_SINGLESTEP);
/* make sure the single step bit is not set. */ /* make sure the single step bit is not set. */
tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG;
put_stack_long(child, EFL_OFFSET, tmp); put_stack_long(child, EFL_OFFSET, tmp);
...@@ -411,6 +414,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data) ...@@ -411,6 +414,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
} }
tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG; tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG;
put_stack_long(child, EFL_OFFSET, tmp); put_stack_long(child, EFL_OFFSET, tmp);
set_tsk_thread_flag(child, TIF_SINGLESTEP);
child->exit_code = data; child->exit_code = data;
/* give it a chance to run. */ /* give it a chance to run. */
wake_up_process(child); wake_up_process(child);
...@@ -535,7 +539,8 @@ void do_syscall_trace(struct pt_regs *regs, int entryexit) ...@@ -535,7 +539,8 @@ void do_syscall_trace(struct pt_regs *regs, int entryexit)
audit_syscall_exit(current, regs->eax); audit_syscall_exit(current, regs->eax);
} }
if (!test_thread_flag(TIF_SYSCALL_TRACE)) if (!test_thread_flag(TIF_SYSCALL_TRACE) &&
!test_thread_flag(TIF_SINGLESTEP))
return; return;
if (!(current->ptrace & PT_PTRACED)) if (!(current->ptrace & PT_PTRACED))
return; return;
......
...@@ -157,7 +157,7 @@ static inline unsigned long current_stack_pointer(void) ...@@ -157,7 +157,7 @@ static inline unsigned long current_stack_pointer(void)
/* work to do on interrupt/exception return */ /* work to do on interrupt/exception return */
#define _TIF_WORK_MASK \ #define _TIF_WORK_MASK \
(0x0000FFFF & ~(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT)) (0x0000FFFF & ~(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP))
#define _TIF_ALLWORK_MASK 0x0000FFFF /* work to do on any return to u-space */ #define _TIF_ALLWORK_MASK 0x0000FFFF /* work to do on any return to u-space */
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment