When entering kernel via int80, TIF_SINGLESTEP is not set
when TF has been set in eflags by the user. This patch
does that.
To make things symmetrical, something further should be done.
Either (a) add to this patch so it clears TF after setting
TIF_SINGLESTEP, or (b) change the sysenter path so it sets
TF in regs.eflags when it finds TIF_SINGLESTEP was set by
do_debug() during kernel entry.
Signed-off-by: Chuck Ebbert <[email protected]>
--- 2.6.16-rc3.orig/arch/i386/kernel/entry.S
+++ 2.6.16-rc3/arch/i386/kernel/entry.S
@@ -226,6 +226,10 @@ ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
+ testl $TF_MASK,EFLAGS(%esp)
+ jz no_singlestep
+ orl $_TIF_SINGLESTEP,TI_flags(%ebp)
+no_singlestep:
# system call tracing in operation / emulation
/* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
--
Chuck
"Equations are the Devil's sentences." --Stephen Colbert
On Fri, 17 Feb 2006, Chuck Ebbert wrote:
>
> When entering kernel via int80, TIF_SINGLESTEP is not set
> when TF has been set in eflags by the user. This patch
> does that.
This really shouldn't matter.
When we enter the kernel through "int 0x80", we don't need to do anything
about TIF_SINGLESTEP, because unlike the "sysenter" path, the "int"
instruction will automatically do the right thing (save old eflags on the
stack).
So afaik, this won't actually do anything (except make _the_ most
timing-critical path in the kernel slower). Have you actually seen any
effects of it?
Linus
In-Reply-To: <[email protected]>
On Fri, 17 Feb 2006 at 14:14:06 -0800, Linus Torvalds wrote:
> On Fri, 17 Feb 2006, Chuck Ebbert wrote:
> >
> > When entering kernel via int80, TIF_SINGLESTEP is not set
> > when TF has been set in eflags by the user. This patch
> > does that.
> ...
> So afaik, this won't actually do anything (except make _the_ most
> timing-critical path in the kernel slower). Have you actually seen any
> effects of it?
No, because every time I try to write a test program I find new things
that keep me from testing what I started out to test. (Last time it was
syscalls turning off singlestep.)
Now I'm using PTRACE_SINGLESTEP to trace a program that is setting
TF while being traced. This works; TF starts showing as set after
the popf that sets it but then I tried to forward the SIGTRAP on
to the child so its signal handler could run when TF was set:
waitpid(child, &status, 0);
if (WIFSTOPPED(status)) {
int signo = WSTOPSIG(status);
ptrace(PTRACE_GETREGS, child, 0, ®s);
if (signo !=5 || !(regs.eflags & TF_MASK))
signo = 0
ptrace(PTRACE_SINGLESTEP, child, NULL, (void *)signo);
}
Now I can trace the signal handler, but when it returns TF is never
again seen as set in the child program when the tracer gets control.
This kernel patch fixes that (but doesn't handle errors properly):
--- 2.6.16-rc3-nb.orig/arch/i386/kernel/signal.c
+++ 2.6.16-rc3-nb/arch/i386/kernel/signal.c
@@ -145,6 +145,9 @@ restore_sigcontext(struct pt_regs *regs,
{
unsigned int tmpflags;
err |= __get_user(tmpflags, &sc->eflags);
+ /* user setting TF? */
+ if (tmpflags & X86_EFLAGS_TF)
+ current->ptrace &= ~PT_DTRACE;
regs->eflags = (regs->eflags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS);
regs->orig_eax = -1; /* disable syscall checks */
}
But now the program goes into an infinite loop because the same trap
keeps getting delivered over and over. It's almost like after the
child actually handles the signal it gets recycled somehow, the tracer
gets it, sends it to the child again and so on... So I modified the
test program to only start forwarding SIGTRAP after two in a row with
TF set have been delivered, but that shouldn't be necessary, should it?
--
Chuck
"Equations are the Devil's sentences." --Stephen Colbert
In-Reply-To: <[email protected]>
On Fri, 17 Feb 2006 at 14:14:06 -0800, Linus Torvalds wrote:
>
> On Fri, 17 Feb 2006, Chuck Ebbert wrote:
> >
> > When entering kernel via int80, TIF_SINGLESTEP is not set
> > when TF has been set in eflags by the user. This patch
> > does that.
>
> This really shouldn't matter.
>
> When we enter the kernel through "int 0x80", we don't need to do anything
> about TIF_SINGLESTEP, because unlike the "sysenter" path, the "int"
> instruction will automatically do the right thing (save old eflags on the
> stack).
>
> So afaik, this won't actually do anything (except make _the_ most
> timing-critical path in the kernel slower). Have you actually seen any
> effects of it?
OK, I found what I was looking for. If TIF_SINGLESTEP is not set and
someone is ptracing us, do_syscall_trace() never gets called on syscall
exit [see entry.S::syscall_exit_work] and thus send_sigtrap() doesn't get
called [ptrace.c line 699]. The result is some missed SIGTRAPS and in
the case of returning from signal handlers, tracing just stops.
Here is output from the below test program before and after applying the patch.
(I used i386-allow-disabling-x86_feature_sep-at-boot.patch from -mm and booted
with the 'nosep' option for these tests.)
Here is the program's output before patching:
child stopped @ ffffe402, signal 10, eflags 00000246 [syscall ret = 0]
Passing signal 10 to child...
child stopped @ 0804854c, signal 5, eflags 00000246
child stopped @ 0804854d, signal 5, eflags 00000246
child stopped @ 0804854f, signal 5, eflags 00000246
child stopped @ 08048554, signal 5, eflags 00000246
child stopped @ ffffe400, signal 5, eflags 00000246 [syscall #20]
child stopped @ 0804855a, signal 5, eflags 00000246
child stopped @ 0804855b, signal 5, eflags 00000246
child stopped @ ffffe440, signal 5, eflags 00000246 [sigreturn]
child stopped @ ffffe445, signal 5, eflags 00000246
child exited with retcode 0
And here is output afterward. Note the signal delivery upon
syscall exit and much more output. Also you can see the final
syscall made by the child [sys_exit_group]:
child stopped @ ffffe402, signal 10, eflags 00000246 [syscall ret = 0]
Passing signal 10 to child...
child stopped @ 0804854c, signal 5, eflags 00000246
child stopped @ 0804854d, signal 5, eflags 00000246
child stopped @ 0804854f, signal 5, eflags 00000246
child stopped @ 08048554, signal 5, eflags 00000246
child stopped @ ffffe400, signal 5, eflags 00000246 [syscall #20]
child stopped @ ffffe402, signal 5, eflags 00000246 [syscall ret = 1855]
child stopped @ 0804855a, signal 5, eflags 00000246
child stopped @ 0804855b, signal 5, eflags 00000246
child stopped @ ffffe440, signal 5, eflags 00000246 [sigreturn]
child stopped @ ffffe445, signal 5, eflags 00000246
child stopped @ ffffe402, signal 5, eflags 00000246 [syscall ret = 0]
child stopped @ 080485b4, signal 5, eflags 00000217
<..................... 29 lines skipped .......................>
child stopped @ ffffe400, signal 5, eflags 00000246 [syscall #252]
child exited with retcode 0
/* ptrace test program
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <asm/user.h>
#define GETPID 20
//#define ENTER_KERNEL "int $0x80\n\t"
#define ENTER_KERNEL "call *vsyscall_addr\n\t"
static int parent, child, status;
static struct user_regs_struct regs;
static struct sigaction sa;
static void * const vsyscall_addr = (void *)0xffffe400;
static void handler(int nr, siginfo_t *si, void *vuc)
{
asm (ENTER_KERNEL : : "a" (GETPID));
}
void do_child()
{
child = getpid();
sa.sa_sigaction = handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &sa, NULL);
ptrace(PTRACE_TRACEME, 0, 0, 0);
kill(child, SIGUSR1);
}
void do_parent()
{
unsigned long eip;
again:
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
fprintf(stderr, "child exited with retcode %d\n",
WEXITSTATUS(status));
return;
}
if (WIFSIGNALED(status)) {
fprintf(stderr, "child exited on unhandled signal %d\n",
WTERMSIG(status));
return;
}
if (WIFSTOPPED(status)) {
int signo = WSTOPSIG(status);
ptrace(PTRACE_GETREGS, child, 0, ®s);
eip = regs.eip;
if (eip >> 24 != 0x08 && eip >> 8 != 0xffffe4)
goto skip_print;
fprintf(stderr, "child stopped @ %08x, signal %d, eflags %08x",
eip, signo, (unsigned long)regs.eflags);
if (eip == 0xffffe400)
fprintf(stderr, " [syscall #%d]", (int)regs.eax);
/* vsyscall-int80 returns to 0xffffe402 and there is a 'ret' there */
if (eip == 0xffffe410 || (eip == 0xffffe402 && *(unsigned char *)eip == 0xc3))
fprintf(stderr, " [syscall ret = %d]", (int)regs.eax);
if (eip == 0xffffe420 || eip == 0xffffe440)
fprintf(stderr, " [sigreturn]");
fprintf(stderr, "\n");
skip_print:
if (signo == SIGTRAP) signo = 0;
if (signo)
fprintf(stderr, "Passing signal %d to child...\n", signo);
ptrace(PTRACE_SINGLESTEP, child, NULL, (void *)signo);
}
goto again;
}
int main(int argc, char * const argv[])
{
parent = getpid();
child = fork();
if (child) do_parent();
else do_child();
return 0;
}
--
Chuck
"Equations are the Devil's sentences." --Stephen Colbert