# This is a BitKeeper generated diff -Nru style patch. # # ChangeSet # 2004/12/31 13:33:39-08:00 torvalds@evo.osdl.org # x86 TF handling: don't clear after user "popf" # # The "popf" will have set TF to something new, # we should not clear it. # # NOTE! We should not allow the user to see TF on # the stack after a "pushf", but that is better done # by the controlling process in user space. This just # makes it possible to do non-intrusive single-stepping, # but the final part is up to the debugger. # # arch/i386/kernel/ptrace.c # 2004/12/31 13:33:29-08:00 torvalds@evo.osdl.org +83 -1 # x86 TF handling: don't clear after user "popf" # # The "popf" will have set TF to something new, # we should not clear it. # # NOTE! We should not allow the user to see TF on # the stack after a "pushf", but that is better done # by the controlling process in user space. This just # makes it possible to do non-intrusive single-stepping, # but the final part is up to the debugger. # diff -Nru a/arch/i386/kernel/ptrace.c b/arch/i386/kernel/ptrace.c --- a/arch/i386/kernel/ptrace.c 2004-12-31 13:34:48 -08:00 +++ b/arch/i386/kernel/ptrace.c 2004-12-31 13:34:48 -08:00 @@ -144,6 +144,79 @@ return retval; } +#define LDT_SEGMENT 4 + +static unsigned long convert_eip_to_linear(struct task_struct *child, struct pt_regs *regs) +{ + unsigned long addr, seg; + + addr = regs->eip; + seg = regs->xcs & 0xffff; + if (regs->eflags & VM_MASK) { + addr = (addr & 0xffff) + (seg << 4); + return addr; + } + + /* + * We'll assume that the code segments in the GDT + * are all zero-based. That is largely true: the + * TLS segments are used for data, and the PNPBIOS + * and APM bios ones we just ignore here. + */ + if (seg & LDT_SEGMENT) { + u32 *desc; + unsigned long base; + + down(&child->mm->context.sem); + desc = child->mm->context.ldt + (seg & ~7); + base = (desc[0] >> 16) | ((desc[1] & 0xff) << 16) | (desc[1] & 0xff000000); + + /* 16-bit code segment? */ + if (!((desc[1] >> 22) & 1)) + addr &= 0xffff; + addr += base; + up(&child->mm->context.sem); + } + return addr; +} + +static inline int is_at_popf(struct task_struct *child, struct pt_regs *regs) +{ + int i, copied; + unsigned char opcode[16]; + unsigned long addr = convert_eip_to_linear(child, regs); + + copied = access_process_vm(child, addr, opcode, sizeof(opcode), 0); + for (i = 0; i < copied; i++) { + switch (opcode[i]) { + /* popf */ + case 0x9d: + return 1; + /* opcode and address size prefixes */ + case 0x66: case 0x67: + continue; + /* irrelevant prefixes (segment overrides and repeats) */ + case 0x26: case 0x2e: + case 0x36: case 0x3e: + case 0x64: case 0x65: + case 0xf0: case 0xf2: case 0xf3: + continue; + + /* + * pushf: NOTE! We should probably not let + * the user see the TF bit being set. But + * it's more pain than it's worth to avoid + * it, and a debugger could emulate this + * all in user space if it _really_ cares. + */ + case 0x9c: + default: + return 0; + } + } + return 0; +} + static void set_singlestep(struct task_struct *child) { struct pt_regs *regs = get_child_regs(child); @@ -161,8 +234,17 @@ if (regs->eflags & TRAP_FLAG) return; - /* Set TF on the kernel stack, and set the flag to say so */ + /* Set TF on the kernel stack.. */ regs->eflags |= TRAP_FLAG; + + /* + * ..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. + */ + if (is_at_popf(child, regs)) + return; + child->ptrace |= PT_DTRACE; }