diff options
author | Andi Kleen <ak@suse.de> | 2005-04-16 18:24:58 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:24:58 -0400 |
commit | e502cdd63de666832b3b65017bb607c22d2868de (patch) | |
tree | 3cc9360617b4b0d2e58f8af37afc97e457f8c048 /arch | |
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>
Diffstat (limited to 'arch')
-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 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 | |||
91 | unsigned 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 | |||
121 | static 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 | |||
89 | static void set_singlestep(struct task_struct *child) | 167 | static 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 | ||