aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndi Kleen <ak@suse.de>2005-04-16 18:24:58 -0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:24:58 -0400
commite502cdd63de666832b3b65017bb607c22d2868de (patch)
tree3cc9360617b4b0d2e58f8af37afc97e457f8c048
parentbe61bff789fe44bfb6d9282d8f7eccc860bdcfb6 (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.c88
1 files changed, 88 insertions, 0 deletions
diff --git a/arch/x86_64/kernel/ptrace.c b/arch/x86_64/kernel/ptrace.c
index 002237c3b6a2..c7011675007d 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,
86 return 0; 86 return 0;
87} 87}
88 88
89#define LDT_SEGMENT 4
90
91unsigned long convert_rip_to_linear(struct task_struct *child, struct pt_regs *regs)
92{
93 unsigned long addr, seg;
94
95 addr = regs->rip;
96 seg = regs->cs & 0xffff;
97
98 /*
99 * We'll assume that the code segments in the GDT
100 * are all zero-based. That is largely true: the
101 * TLS segments are used for data, and the PNPBIOS
102 * and APM bios ones we just ignore here.
103 */
104 if (seg & LDT_SEGMENT) {
105 u32 *desc;
106 unsigned long base;
107
108 down(&child->mm->context.sem);
109 desc = child->mm->context.ldt + (seg & ~7);
110 base = (desc[0] >> 16) | ((desc[1] & 0xff) << 16) | (desc[1] & 0xff000000);
111
112 /* 16-bit code segment? */
113 if (!((desc[1] >> 22) & 1))
114 addr &= 0xffff;
115 addr += base;
116 up(&child->mm->context.sem);
117 }
118 return addr;
119}
120
121static int is_at_popf(struct task_struct *child, struct pt_regs *regs)
122{
123 int i, copied;
124 unsigned char opcode[16];
125 unsigned long addr = convert_rip_to_linear(child, regs);
126
127 copied = access_process_vm(child, addr, opcode, sizeof(opcode), 0);
128 for (i = 0; i < copied; i++) {
129 switch (opcode[i]) {
130 /* popf */
131 case 0x9d:
132 return 1;
133
134 /* CHECKME: 64 65 */
135
136 /* opcode and address size prefixes */
137 case 0x66: case 0x67:
138 continue;
139 /* irrelevant prefixes (segment overrides and repeats) */
140 case 0x26: case 0x2e:
141 case 0x36: case 0x3e:
142 case 0x64: case 0x65:
143 case 0xf0: case 0xf2: case 0xf3:
144 continue;
145
146 /* REX prefixes */
147 case 0x40 ... 0x4f:
148 continue;
149
150 /* CHECKME: f0, f2, f3 */
151
152 /*
153 * pushf: NOTE! We should probably not let
154 * the user see the TF bit being set. But
155 * it's more pain than it's worth to avoid
156 * it, and a debugger could emulate this
157 * all in user space if it _really_ cares.
158 */
159 case 0x9c:
160 default:
161 return 0;
162 }
163 }
164 return 0;
165}
166
89static void set_singlestep(struct task_struct *child) 167static void set_singlestep(struct task_struct *child)
90{ 168{
91 struct pt_regs *regs = get_child_regs(child); 169 struct pt_regs *regs = get_child_regs(child);
@@ -106,6 +184,16 @@ static void set_singlestep(struct task_struct *child)
106 /* Set TF on the kernel stack.. */ 184 /* Set TF on the kernel stack.. */
107 regs->eflags |= TRAP_FLAG; 185 regs->eflags |= TRAP_FLAG;
108 186
187 /*
188 * ..but if TF is changed by the instruction we will trace,
189 * don't mark it as being "us" that set it, so that we
190 * won't clear it by hand later.
191 *
192 * AK: this is not enough, LAHF and IRET can change TF in user space too.
193 */
194 if (is_at_popf(child, regs))
195 return;
196
109 child->ptrace |= PT_DTRACE; 197 child->ptrace |= PT_DTRACE;
110} 198}
111 199