x86 ptrace: block-step fix

The enable_single_step() logic bails out early if TF is already set.
That skips some of the bookkeeping that keeps things straight.
This makes PTRACE_SINGLEBLOCK break the behavior of a user task
that was already setting TF itself in user mode.

Fix the bookkeeping to notice the old TF setting as it should.

Test case at: http://sources.redhat.com/cgi-bin/cvsweb.cgi/~checkout~/tests/ptrace-tests/tests/step-jump-cont-strict.c?cvsroot=systemtap

Signed-off-by: Roland McGrath <roland@redhat.com>
This commit is contained in:
Roland McGrath 2008-07-09 01:07:02 -07:00
parent a3cf859321
commit 6718d0d6da

View File

@ -105,6 +105,7 @@ static int is_setting_trap_flag(struct task_struct *child, struct pt_regs *regs)
static int enable_single_step(struct task_struct *child) static int enable_single_step(struct task_struct *child)
{ {
struct pt_regs *regs = task_pt_regs(child); struct pt_regs *regs = task_pt_regs(child);
unsigned long oflags;
/* /*
* Always set TIF_SINGLESTEP - this guarantees that * Always set TIF_SINGLESTEP - this guarantees that
@ -113,11 +114,7 @@ static int enable_single_step(struct task_struct *child)
*/ */
set_tsk_thread_flag(child, TIF_SINGLESTEP); set_tsk_thread_flag(child, TIF_SINGLESTEP);
/* oflags = regs->flags;
* If TF was already set, don't do anything else
*/
if (regs->flags & X86_EFLAGS_TF)
return 0;
/* Set TF on the kernel stack.. */ /* Set TF on the kernel stack.. */
regs->flags |= X86_EFLAGS_TF; regs->flags |= X86_EFLAGS_TF;
@ -126,9 +123,22 @@ static int enable_single_step(struct task_struct *child)
* ..but if TF is changed by the instruction we will trace, * ..but if TF is changed by the instruction we will trace,
* don't mark it as being "us" that set it, so that we * don't mark it as being "us" that set it, so that we
* won't clear it by hand later. * won't clear it by hand later.
*
* Note that if we don't actually execute the popf because
* of a signal arriving right now or suchlike, we will lose
* track of the fact that it really was "us" that set it.
*/ */
if (is_setting_trap_flag(child, regs)) if (is_setting_trap_flag(child, regs)) {
clear_tsk_thread_flag(child, TIF_FORCED_TF);
return 0; return 0;
}
/*
* If TF was already set, check whether it was us who set it.
* If not, we should never attempt a block step.
*/
if (oflags & X86_EFLAGS_TF)
return test_tsk_thread_flag(child, TIF_FORCED_TF);
set_tsk_thread_flag(child, TIF_FORCED_TF); set_tsk_thread_flag(child, TIF_FORCED_TF);