git: 8efa35fea384 - main - libc/powerpc64: Fix swapcontext(3)

From: Justin Hibbits <jhibbits_at_FreeBSD.org>
Date: Sun, 13 Jul 2025 18:01:40 UTC
The branch main has been updated by jhibbits:

URL: https://cgit.FreeBSD.org/src/commit/?id=8efa35fea384d209c683dfbae8f49f2737a41941

commit 8efa35fea384d209c683dfbae8f49f2737a41941
Author:     Timothy Pearson <tpearson@raptorengineering.com>
AuthorDate: 2025-07-08 13:41:15 +0000
Commit:     Justin Hibbits <jhibbits@FreeBSD.org>
CommitDate: 2025-07-13 18:00:56 +0000

    libc/powerpc64: Fix swapcontext(3)
    
    On PowerPC platforms a valid link to the Table of Contents (TOC) is
    required for PLT lookups to function.  This TOC pointer is stored in
    a dedicated register, and is used along with the stack pointer by both
    C prologue and PLT lookup code.
    
    When calling swapcontext() with uc_link != NULL, a PLT lookup to
    setcontext(3) is attempted from within the _ctx_done context.  The
    exiting process has usually trashed both r1 and r2 at this point,
    leading to a crash within the PLT lookup before setcontext(2) is
    reached to restore the linked context.
    
    Save and restore r2 as in a regular function.  This ensures the
    subsequent PLT lookup to setcontext(3) succeeds.
    
    Signed-off-by: Timothy Pearson <tpearson@raptorengineering.com>
    
    MFC after:      1 week
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1759
---
 lib/libc/powerpc64/gen/_ctx_start.S   | 14 ++++++++
 lib/libc/powerpc64/gen/makecontext.c  |  3 +-
 lib/libc/tests/sys/Makefile           |  4 +--
 lib/libc/tests/sys/swapcontext_test.c | 63 +++++++++++++++++++++++++++++++++++
 4 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/lib/libc/powerpc64/gen/_ctx_start.S b/lib/libc/powerpc64/gen/_ctx_start.S
index c2f8abfd6486..98225f9c1138 100644
--- a/lib/libc/powerpc64/gen/_ctx_start.S
+++ b/lib/libc/powerpc64/gen/_ctx_start.S
@@ -34,6 +34,16 @@
 	ld	%r2,8(%r14)
 	ld	%r14,0(%r14)
 #else
+	/*
+	 * The stack frame was already set up in makecontext(),
+	 * so we can safely use the guaranteed fields here.
+	 *
+	 * Note we do step on the allocated stack frame's TOC,
+	 * but since we never return from this function (i.e.
+	 * never restore the stack frame) this should be safe.
+	 */
+	std	%r2,24(%r1)	/* save TOC */
+
 	/* Load global entry point */
 	mr	%r12,%r14
 #endif
@@ -41,6 +51,10 @@
 	blrl			/* branch to start function */
 	mr	%r3,%r15	/* pass pointer to ucontext as argument */
 	nop
+#if defined(_CALL_ELF) && _CALL_ELF != 1
+	/* Restore TOC */
+	ld	%r2,24(%r1)
+#endif
 	bl	CNAME(_ctx_done) /* branch to ctxt completion func */
 	/*
 	 * we should never return from the
diff --git a/lib/libc/powerpc64/gen/makecontext.c b/lib/libc/powerpc64/gen/makecontext.c
index 75c2d40bdd60..9e3a976fa1bd 100644
--- a/lib/libc/powerpc64/gen/makecontext.c
+++ b/lib/libc/powerpc64/gen/makecontext.c
@@ -78,7 +78,7 @@ __makecontext(ucontext_t *ucp, void (*start)(void), int argc, ...)
 	 */
 	stackargs = (argc > 8) ? argc - 8 : 0;
 	sp = (char *) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size
-		- sizeof(uintptr_t)*(stackargs + 2);
+		- sizeof(uintptr_t)*(stackargs + 6);
 	sp = (char *)((uintptr_t)sp & ~0x1f);
 
 	mc = &ucp->uc_mcontext;
@@ -119,6 +119,7 @@ __makecontext(ucontext_t *ucp, void (*start)(void), int argc, ...)
 	mc->mc_srr0 = *(uintptr_t *)_ctx_start;
 #else
 	mc->mc_srr0 = (uintptr_t) _ctx_start;
+	mc->mc_gpr[12] = (uintptr_t) _ctx_start;/* required for prologue */
 #endif
 	mc->mc_gpr[1] = (uintptr_t) sp;		/* new stack pointer */
 	mc->mc_gpr[14] = (uintptr_t) start;	/* r14 <- start */
diff --git a/lib/libc/tests/sys/Makefile b/lib/libc/tests/sys/Makefile
index 89d341ff400a..88f8191a16eb 100644
--- a/lib/libc/tests/sys/Makefile
+++ b/lib/libc/tests/sys/Makefile
@@ -7,11 +7,11 @@ ATF_TESTS_C+=			brk_test
 .endif
 ATF_TESTS_C+=			cpuset_test
 ATF_TESTS_C+=			errno_test
+ATF_TESTS_C+=			swapcontext_test
 ATF_TESTS_C+=			queue_test
 ATF_TESTS_C+=			sendfile_test
 
-# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg,
-# swapcontext
+# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg
 NETBSD_ATF_TESTS_C+=		access_test
 NETBSD_ATF_TESTS_C+=		bind_test
 NETBSD_ATF_TESTS_C+=		chroot_test
diff --git a/lib/libc/tests/sys/swapcontext_test.c b/lib/libc/tests/sys/swapcontext_test.c
new file mode 100644
index 000000000000..f341a746e515
--- /dev/null
+++ b/lib/libc/tests/sys/swapcontext_test.c
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2025 Raptor Computing Systems, LLC
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <errno.h>
+
+#include <atf-c.h>
+
+#define STACK_SIZE	(64ull << 10)
+
+static volatile int callback_reached = 0;
+
+static ucontext_t uctx_save, uctx_switch;
+
+static void swapcontext_callback()
+{
+	// Increment callback reached variable
+	// If this is called multiple times, we will fail the test
+	// If this is not called at all, we will fail the test
+	callback_reached++;
+}
+
+ATF_TC(swapcontext_basic);
+ATF_TC_HEAD(swapcontext_basic, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+		"Verify basic functionality of swapcontext");
+}
+
+ATF_TC_BODY(swapcontext_basic, tc)
+{
+	char *stack;
+        int res;
+
+	stack = malloc(STACK_SIZE);
+	ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno));
+	res = getcontext(&uctx_switch);
+	ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno));
+
+	uctx_switch.uc_stack.ss_sp = stack;
+	uctx_switch.uc_stack.ss_size = STACK_SIZE;
+	uctx_switch.uc_link = &uctx_save;
+	makecontext(&uctx_switch, swapcontext_callback, 0);
+
+	res = swapcontext(&uctx_save, &uctx_switch);
+
+        ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno));
+        ATF_REQUIRE_MSG(callback_reached == 1,
+		"callback failed, reached %d times", callback_reached);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, swapcontext_basic);
+
+	return (atf_no_error());
+}
+