> It now fails on non-utrace 2.6.25-rc5-git4 with IMO a kernel ptrace bug: > Trap flag found clear after it was set by the inferior.
I have a fix for this one. This case is that TF was just set by using debugger stepping, which sets a kernel flag saying this is a "forced" TF rather than a "natural" one (so ptrace masks off TF when you peek). Then we step again into a "popf" instruction, which will explicitly set or clear it to a new "natural" state. In this case, the kernel failed to reset its flag, so after the popf it still thinks TF is "forced" and hides it. (I think this bug ought to manifest in some case or other with either single-step or block-step.) What the kernel did is just skip other bookkeeping if TF was already set before the step. My fix is to clear the "forced" flag when about to step into a "popf" or similar instruction, even if TF was already set. Btw, I'd find it clearer to read the asm block if you just used "jnz instr3" and the like instead of 1b with multiple 1: labels at the same places you already have named labels. Many of the asserts would be nicer as messages telling you what the offending value of ip or stopsig or whatever was. Whenever I actually debug the kernel, I have to tweak the test programs by hand to display enough info to figure out what I'm doing. > Also it fails later on: > PTRACE_SINGLEBLOCK should stop us at instr8 (not instr5). There was a related bug due to the same control flow in the kernel bookkeeping, i.e. when TF was already set it skipped it all and suppressed block-step. I fixed it so that if TF was already set, it only suppresses block-step only if TF was "natural". (When user-mode has set TF with popf, we "suppress" enabling the block-step hardware for a PTRACE_SINGLEBLOCK, so it degenerates to a plain instruction step as the user wanted to see.) With the kernel fix I'm using, the test case now barfs because of hitting "instr12: hlt" (and so SIGSEGV instead of SIGTRAP). That's because of the known limitation that stepping "pushf" puts a false TF in the flags the inferior sees, so the instr10 branch is not taken. I think this adds up to all expected behavior. The limitations explained in the comments are probably never going to change. (At least, it's not worth addressing them for a synthetic test case before any real-world need arises.) It's especially mind-bending because this is testing everything at once. I think there are permutations it's not testing that were in fact wrong as well and should be fixed by my patch. I would be more confident of the fixes if we were to test some of the cases with few variables as well as the ones we test now. e.g. I think that even if no pushf/popf were involved recently, if we just single-stepped before single-step into popf, we'll fail to clear the "forced"; also, if we just single-stepped we will always fail to suppress block-step. I've been thinking about it too long now and can't quite keep it all straight. I'd like to make sure this all seems coherent and there aren't holes in my logic you can think of before I send this upstream. Thanks, Roland diff --git a/arch/x86/kernel/step.c b/arch/x86/kernel/step.c index 071ff47..0000000 100644 --- a/arch/x86/kernel/step.c +++ b/arch/x86/kernel/step.c @@ -105,6 +105,7 @@ static int is_setting_trap_flag(struct t static int enable_single_step(struct task_struct *child) { struct pt_regs *regs = task_pt_regs(child); + unsigned long oflags; /* * Always set TIF_SINGLESTEP - this guarantees that @@ -113,11 +114,7 @@ static int enable_single_step(struct tas */ set_tsk_thread_flag(child, TIF_SINGLESTEP); - /* - * If TF was already set, don't do anything else - */ - if (regs->flags & X86_EFLAGS_TF) - return 0; + oflags = regs->flags; /* Set TF on the kernel stack.. */ regs->flags |= X86_EFLAGS_TF; @@ -126,9 +123,22 @@ static int enable_single_step(struct tas * ..but if TF is changed by the instruction we will trace, * don't mark it as being "us" that set it, so that we * 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; + } + + /* + * 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);