The branch main has been updated by jhibbits:

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

commit 8efa35fea384d209c683dfbae8f49f2737a41941
Author:     Timothy Pearson <tpear...@raptorengineering.com>
AuthorDate: 2025-07-08 13:41:15 +0000
Commit:     Justin Hibbits <jhibb...@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 <tpear...@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());
+}
+

Reply via email to