Commit 8259f7f6 authored by Anton Blanchard's avatar Anton Blanchard Committed by Linus Torvalds

[PATCH] ppc64: User tasks must have a valid thread.regs

There have been reports of problems running UP ppc64 kernels where the
kernel would die in the floating point save/restore code.

It turns out kernel threads that call exec (and so become user tasks) do
not have a valid thread.regs.  This means init (pid 1) does not, it also
means anything called out of exec_usermodehelper does not.  Once that task
has forked (eg init), then the thread.regs in the new task is correctly
set.

On UP do lazy save/restore of floating point regs.  The SLES9 init is doing
floating point (the debian version of init appears not to).  The lack of
thread.regs in init combined with the fact that it does floating point
leads to our lazy FP save/restore code blowing up.

There were other places where this problem exhibited itself in weird and
interesting ways.  If a task being exec'ed out of a kernel thread used more
than 1MB of stack, it would be terminated due to the checks in
arch/ppc64/mm/fault.c (looking for a valid thread.regs when extending the
stack).  We had a test case using the tux webserver that was failing due to
this.

Since we zero all registers in ELF_PLAT_INIT, I removed the extra memset
in start_thread32.
Signed-off-by: default avatarAnton Blanchard <anton@samba.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent b5e82911
......@@ -397,11 +397,22 @@ void start_thread(struct pt_regs *regs, unsigned long fdptr, unsigned long sp)
/* Check whether the e_entry function descriptor entries
* need to be relocated before we can use them.
*/
if ( load_addr != 0 ) {
if (load_addr != 0) {
entry += load_addr;
toc += load_addr;
}
/*
* If we exec out of a kernel thread then thread.regs will not be
* set. Do it now.
*/
if (!current->thread.regs) {
unsigned long childregs = (unsigned long)current->thread_info +
THREAD_SIZE;
childregs -= sizeof(struct pt_regs);
current->thread.regs = childregs;
}
regs->nip = entry;
regs->gpr[1] = sp;
regs->gpr[2] = toc;
......
......@@ -633,8 +633,24 @@ long sys32_execve(unsigned long a0, unsigned long a1, unsigned long a2,
void start_thread32(struct pt_regs* regs, unsigned long nip, unsigned long sp)
{
set_fs(USER_DS);
memset(regs->gpr, 0, sizeof(regs->gpr));
memset(&regs->ctr, 0, 4 * sizeof(regs->ctr));
/*
* If we exec out of a kernel thread then thread.regs will not be
* set. Do it now.
*/
if (!current->thread.regs) {
unsigned long childregs = (unsigned long)current->thread_info +
THREAD_SIZE;
childregs -= sizeof(struct pt_regs);
current->thread.regs = childregs;
}
/*
* ELF_PLAT_INIT already clears all registers but it also sets r2.
* So just clear r2 here.
*/
regs->gpr[2] = 0;
regs->nip = nip;
regs->gpr[1] = sp;
regs->msr = MSR_USER32;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment