blob: 6e72d32cb4b68192fe6e92c5a9a56af3baaf569c [file] [log] [blame]
Jerome Forissier690b7e62025-04-18 16:09:32 +02001// SPDX-License-Identifier: LGPL-2.1-or-later
2/*
3 * An implementation of initjmp() in C, that plays well with the system's
4 * setjmp() and longjmp() functions.
5 * Taken verbatim from arch/sandbox/os/setjmp.c in the barebox project.
6 * Modified so that initjmp() accepts a stack_size argument.
7 *
8 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
9 * Copyright (C) 2011 Kevin Wolf <kwolf@redhat.com>
10 * Copyright (C) 2012 Alex Barcelo <abarcelo@ac.upc.edu>
11 * Copyright (C) 2021 Ahmad Fatoum, Pengutronix
12 * Copyright (C) 2025 Linaro Ltd.
13 * This file is partly based on pth_mctx.c, from the GNU Portable Threads
14 * Copyright (c) 1999-2006 Ralf S. Engelschall <rse@engelschall.com>
15 */
16
17/* XXX Is there a nicer way to disable glibc's stack check for longjmp? */
18#ifdef _FORTIFY_SOURCE
19#undef _FORTIFY_SOURCE
20#endif
21
22#include <pthread.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <setjmp.h>
26#include <signal.h>
27
28typedef sigjmp_buf _jmp_buf __attribute__((aligned((16))));
29_Static_assert(sizeof(_jmp_buf) <= 512, "sigjmp_buf size exceeds expectation");
30
31/*
32 * Information for the signal handler (trampoline)
33 */
34static struct {
35 _jmp_buf *reenter;
36 void (*entry)(void);
37 volatile sig_atomic_t called;
38} tr_state;
39
40/*
41 * "boot" function
42 * This is what starts the coroutine, is called from the trampoline
43 * (from the signal handler when it is not signal handling, read ahead
44 * for more information).
45 */
46static void __attribute__((noinline, noreturn))
47coroutine_bootstrap(void (*entry)(void))
48{
49 for (;;)
50 entry();
51}
52
53/*
54 * This is used as the signal handler. This is called with the brand new stack
55 * (thanks to sigaltstack). We have to return, given that this is a signal
56 * handler and the sigmask and some other things are changed.
57 */
58static void coroutine_trampoline(int signal)
59{
60 /* Get the thread specific information */
61 tr_state.called = 1;
62
63 /*
64 * Here we have to do a bit of a ping pong between the caller, given that
65 * this is a signal handler and we have to do a return "soon". Then the
66 * caller can reestablish everything and do a siglongjmp here again.
67 */
68 if (!sigsetjmp(*tr_state.reenter, 0)) {
69 return;
70 }
71
72 /*
73 * Ok, the caller has siglongjmp'ed back to us, so now prepare
74 * us for the real machine state switching. We have to jump
75 * into another function here to get a new stack context for
76 * the auto variables (which have to be auto-variables
77 * because the start of the thread happens later). Else with
78 * PIC (i.e. Position Independent Code which is used when PTH
79 * is built as a shared library) most platforms would
80 * horrible core dump as experience showed.
81 */
82 coroutine_bootstrap(tr_state.entry);
83}
84
85int __attribute__((weak)) initjmp(_jmp_buf jmp, void (*func)(void),
86 void *stack_base, size_t stack_size)
87{
88 struct sigaction sa;
89 struct sigaction osa;
90 stack_t ss;
91 stack_t oss;
92 sigset_t sigs;
93 sigset_t osigs;
94
95 /* The way to manipulate stack is with the sigaltstack function. We
96 * prepare a stack, with it delivering a signal to ourselves and then
97 * put sigsetjmp/siglongjmp where needed.
98 * This has been done keeping coroutine-ucontext (from the QEMU project)
99 * as a model and with the pth ideas (GNU Portable Threads).
100 * See coroutine-ucontext for the basics of the coroutines and see
101 * pth_mctx.c (from the pth project) for the
102 * sigaltstack way of manipulating stacks.
103 */
104
105 tr_state.entry = func;
106 tr_state.reenter = (void *)jmp;
107
108 /*
109 * Preserve the SIGUSR2 signal state, block SIGUSR2,
110 * and establish our signal handler. The signal will
111 * later transfer control onto the signal stack.
112 */
113 sigemptyset(&sigs);
114 sigaddset(&sigs, SIGUSR2);
115 pthread_sigmask(SIG_BLOCK, &sigs, &osigs);
116 sa.sa_handler = coroutine_trampoline;
117 sigfillset(&sa.sa_mask);
118 sa.sa_flags = SA_ONSTACK;
119 if (sigaction(SIGUSR2, &sa, &osa) != 0) {
120 return -1;
121 }
122
123 /*
124 * Set the new stack.
125 */
126 ss.ss_sp = stack_base;
127 ss.ss_size = stack_size;
128 ss.ss_flags = 0;
129 if (sigaltstack(&ss, &oss) < 0) {
130 return -1;
131 }
132
133 /*
134 * Now transfer control onto the signal stack and set it up.
135 * It will return immediately via "return" after the sigsetjmp()
136 * was performed. Be careful here with race conditions. The
137 * signal can be delivered the first time sigsuspend() is
138 * called.
139 */
140 tr_state.called = 0;
141 pthread_kill(pthread_self(), SIGUSR2);
142 sigfillset(&sigs);
143 sigdelset(&sigs, SIGUSR2);
144 while (!tr_state.called) {
145 sigsuspend(&sigs);
146 }
147
148 /*
149 * Inform the system that we are back off the signal stack by
150 * removing the alternative signal stack. Be careful here: It
151 * first has to be disabled, before it can be removed.
152 */
153 sigaltstack(NULL, &ss);
154 ss.ss_flags = SS_DISABLE;
155 if (sigaltstack(&ss, NULL) < 0) {
156 return -1;
157 }
158 sigaltstack(NULL, &ss);
159 if (!(oss.ss_flags & SS_DISABLE)) {
160 sigaltstack(&oss, NULL);
161 }
162
163 /*
164 * Restore the old SIGUSR2 signal handler and mask
165 */
166 sigaction(SIGUSR2, &osa, NULL);
167 pthread_sigmask(SIG_SETMASK, &osigs, NULL);
168
169 /*
170 * jmp can now be used to enter the trampoline again, but not as a
171 * signal handler. Instead it's longjmp'd to directly.
172 */
173 return 0;
174}
175