From c238acf3051405731dfcb45c9918fdf9cec0431c Mon Sep 17 00:00:00 2001
From: Mihawk <163614687+mihawk-99@users.noreply.github.com>
Date: Thu, 8 Oct 2026 19:29:12 -0400
Subject: [PATCH] include: account for PS5 signal-frame padding before mcontext
---
include/freebsd/sys/_ucontext.h | 2 +
samples/test_signal_context/Makefile | 48 +++++++++++++++++++++
samples/test_signal_context/main.c | 64 ++++++++++++++++++++++++++++
3 files changed, 114 insertions(+)
create mode 100644 samples/test_signal_context/Makefile
create mode 100644 samples/test_signal_context/main.c
diff --git a/include/freebsd/sys/_ucontext.h b/include/freebsd/sys/_ucontext.h
index 7c13c366..bfc9369b 100644
--- a/include/freebsd/sys/_ucontext.h
+++ b/include/freebsd/sys/_ucontext.h
@@ -41,6 +41,8 @@ typedef struct __ucontext {
* note: the union is not defined, though.
*/
__sigset_t uc_sigmask;
+ /* PS5 signal frames place uc_mcontext at offset 64 (observed on 12.09). */
+ int __ps5_reserved[12];
mcontext_t uc_mcontext;
struct __ucontext *uc_link;
diff --git a/samples/test_signal_context/Makefile b/samples/test_signal_context/Makefile
new file mode 100644
index 00000000..699bf923
--- /dev/null
+++ b/samples/test_signal_context/Makefile
@@ -0,0 +1,48 @@
+# Copyright (C) 2023 John Törnblom
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not see
+# .
+
+PS5_HOST ?= ps5
+PS5_PORT ?= 9021
+
+ifdef PS5_PAYLOAD_SDK
+ include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
+else
+ $(error PS5_PAYLOAD_SDK is undefined)
+endif
+
+ELF := test_signal_context.elf
+
+CFLAGS := -Wall -Werror -g
+
+all: $(ELF)
+
+$(ELF): main.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+ rm -f $(ELF)
+
+test: $(ELF)
+ $(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $^
+
+debug: $(ELF)
+ gdb-multiarch \
+ -ex "set architecture i386:x86-64" \
+ -ex "target extended-remote $(PS5_HOST):2159" \
+ -ex "file $(ELF)" \
+ -ex "remote put $(ELF) /data/$(ELF)" \
+ -ex "set remote exec-file /data/$(ELF)" \
+ -ex "start"
diff --git a/samples/test_signal_context/main.c b/samples/test_signal_context/main.c
new file mode 100644
index 00000000..ba33ccc4
--- /dev/null
+++ b/samples/test_signal_context/main.c
@@ -0,0 +1,64 @@
+/* Copyright (C) 2026 Mihawk
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+_Static_assert(offsetof(ucontext_t, uc_mcontext) == 64, "PS5 mcontext offset");
+_Static_assert(offsetof(ucontext_t, uc_mcontext.mc_rip) == 224, "PS5 RIP offset");
+
+static sigjmp_buf escape;
+static volatile uint64_t saved_rax;
+static volatile uintptr_t saved_rip;
+extern void context_trigger(void);
+extern const char context_fault[];
+
+/* UD2 gives a known faulting instruction and a caller-saved register marker. */
+__asm__(".text\n"
+ ".global context_trigger\n"
+ ".type context_trigger,@function\n"
+ "context_trigger:\n"
+ "movabs $0x5a17000000001234, %rax\n"
+ ".global context_fault\n"
+ "context_fault:\n"
+ "ud2\n"
+ "ret\n");
+
+static void
+handle_fault(int signal_number, siginfo_t *info, void *opaque) {
+ const ucontext_t *context = opaque;
+ (void)signal_number;
+ (void)info;
+ saved_rax = context->uc_mcontext.mc_rax;
+ saved_rip = context->uc_mcontext.mc_rip;
+ siglongjmp(escape, 1);
+}
+
+int
+main(void) {
+ struct sigaction action = {0}, previous;
+ action.sa_sigaction = handle_fault;
+ action.sa_flags = SA_SIGINFO;
+ sigemptyset(&action.sa_mask);
+ if(sigaction(SIGILL, &action, &previous) != 0) {
+ perror("sigaction");
+ return 1;
+ }
+ if(sigsetjmp(escape, 1) == 0) {
+ context_trigger();
+ }
+ if(sigaction(SIGILL, &previous, NULL) != 0) {
+ perror("restore sigaction");
+ return 1;
+ }
+ printf("RAX=%016" PRIx64 " RIP=%" PRIxPTR " expected RIP=%" PRIxPTR "\n",
+ saved_rax, saved_rip, (uintptr_t)context_fault);
+ return saved_rax != UINT64_C(0x5a17000000001234) ||
+ saved_rip != (uintptr_t)context_fault;
+}