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

Anton Blanchard anton at samba.org
Mon Sep 20 19:40:16 EST 2004


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.

Paul: does this change look OK to you?

Since we zero all registers in ELF_PLAT_INIT, I removed the extra memset
in start_thread32.

Signed-off-by: Anton Blanchard <anton at samba.org>

diff -puN arch/ppc64/kernel/process.c~fix_regs arch/ppc64/kernel/process.c
--- foobar2/arch/ppc64/kernel/process.c~fix_regs	2004-09-19 23:51:56.894867391 +1000
+++ foobar2-anton/arch/ppc64/kernel/process.c	2004-09-20 00:17:44.391366279 +1000
@@ -397,11 +397,22 @@ void start_thread(struct pt_regs *regs, 
 	/* 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;
diff -L process.c -puN /dev/null /dev/null
diff -puN arch/ppc64/kernel/sys_ppc32.c~fix_regs arch/ppc64/kernel/sys_ppc32.c
--- foobar2/arch/ppc64/kernel/sys_ppc32.c~fix_regs	2004-09-19 23:52:48.494666233 +1000
+++ foobar2-anton/arch/ppc64/kernel/sys_ppc32.c	2004-09-20 00:17:38.031932803 +1000
@@ -633,8 +633,24 @@ out:
 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;
_



More information about the Linuxppc64-dev mailing list