diff options
author | Andi Kleen <ak@suse.de> | 2005-04-16 15:24:58 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:24:58 -0700 |
commit | e502cdd63de666832b3b65017bb607c22d2868de (patch) | |
tree | 3cc9360617b4b0d2e58f8af37afc97e457f8c048 | |
parent | be61bff789fe44bfb6d9282d8f7eccc860bdcfb6 (diff) |
[PATCH] x86_64: Handle programs that set TF in user space using popf while single stepping
Ported from i386/Linus
Still won't handle other TF changing instructions like IRET or LAHF.
Prefix handling must be double checked...
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | arch/x86_64/kernel/ptrace.c | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/arch/x86_64/kernel/ptrace.c b/arch/x86_64/kernel/ptrace.c index 002237c3b6a..c7011675007 100644 --- a/arch/x86_64/kernel/ptrace.c +++ b/arch/x86_64/kernel/ptrace.c @@ -86,6 +86,84 @@ static inline long put_stack_long(struct task_struct *task, int offset, return 0; } +#define LDT_SEGMENT 4 + +unsigned long convert_rip_to_linear(struct task_struct *child, struct pt_regs *regs) +{ + unsigned long addr, seg; + + addr = regs->rip; + seg = regs->cs & 0xffff; + + /* + * 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 int is_at_popf(struct task_struct *child, struct pt_regs *regs) +{ + int i, copied; + unsigned char opcode[16]; + unsigned long addr = convert_rip_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; + + /* CHECKME: 64 65 */ + + /* 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; + + /* REX prefixes */ + case 0x40 ... 0x4f: + continue; + + /* CHECKME: f0, f2, f3 */ + + /* + * 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); @@ -106,6 +184,16 @@ static void set_singlestep(struct task_struct *child) /* 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. + * + * AK: this is not enough, LAHF and IRET can change TF in user space too. + */ + if (is_at_popf(child, regs)) + return; + child->ptrace |= PT_DTRACE; } |