Zth (libzth)
Loading...
Searching...
No Matches
sigaltstack.h
Go to the documentation of this file.
1#ifndef ZTH_CONTEXT_SIGALTSTACK_H
2#define ZTH_CONTEXT_SIGALTSTACK_H
3/*
4 * SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 */
8
9#ifndef ZTH_CONTEXT_CONTEXT_H
10# error This file must be included by libzth/context/context.h.
11#endif
12
13#ifdef __cplusplus
14
15# ifdef ZTH_ENABLE_ASAN
16# error Invalid configuration combination sigaltstack with asan.
17# endif
18
19# include <csetjmp>
20# include <csignal>
21# include <sys/types.h>
22
23# ifdef ZTH_HAVE_PTHREAD
24# include <pthread.h>
25# ifndef ZTH_OS_MAC
26# define pthread_yield_np() pthread_yield()
27# endif
28# else
29# define pthread_sigmask(...) sigprocmask(__VA_ARGS__)
30# define pthread_kill(...) kill(__VA_ARGS__)
31# define pthread_self() getpid()
32# define pthread_yield_np() sched_yield()
33# endif
34
35namespace zth {
36namespace impl {
37
38static void context_global_init()
39{
40 // By default, block SIGUSR1 for the main/all thread(s).
41 int res = 0;
42
43 sigset_t sigs;
44 sigemptyset(&sigs);
45 sigaddset(&sigs, SIGUSR1);
46 if((res = pthread_sigmask(SIG_BLOCK, &sigs, nullptr)))
47 goto error;
48
49 return;
50error:
51 zth_abort("Cannot initialize signals; %s", err(res).c_str());
52}
53ZTH_INIT_CALL(context_global_init)
54
55} // namespace impl
56
57class Context : public impl::ContextArch<Context> {
59public:
61
62 constexpr explicit Context(ContextAttr const& attr) noexcept
63 : base(attr)
65 , m_mask()
67 {}
68
69private:
70 static void context_trampoline(int sig)
71 {
72 // At this point, we are in the signal handler.
73
74 if(unlikely(sig != SIGUSR1))
75 // Huh?
76 return;
77
78 // Put the context on the stack, as trampoline_context is about to change.
79 Context* context = ZTH_TLS_GET(trampoline_context);
80
81 if(unlikely(!context || context->m_did_trampoline))
82 // Huh?
83 return;
84
85 if(setjmp(context->m_trampoline_env) == 0) {
86 // Return immediately to complete the signal handler.
87 context->m_did_trampoline = 1;
88 return;
89 }
90
91 // If we get here, we are not in the signal handler anymore, but in the actual fiber
92 // context.
93 zth_dbg(context, "Bootstrapping %p", context);
94
96 int res = pthread_sigmask(SIG_SETMASK, &context->m_mask, nullptr);
97 if(unlikely(res))
98 zth_abort("Cannot set signal mask");
99 }
100
101 // However, we still need to setup the context for the first time.
102 if(sigsetjmp(context->m_env, Config::ContextSignals) == 0) {
103 // Now, we are ready to be context switched to this fiber.
104 // Go back to our parent, as it was not our schedule turn to continue with
105 // actual execution.
106 siglongjmp(*context->m_parent, 1);
107 }
108
109 // This is actually the first time we enter the fiber by the normal scheduler.
110 context_entry(context);
111 }
112
113 ZTH_TLS_MEMBER(Context*, trampoline_context)
114
115public:
116 static int init() noexcept
117 {
118 int res = base::init();
119 if(res)
120 return res;
121
122 // Claim SIGUSR1 for context_create().
123 struct sigaction sa;
124 sa.sa_handler = &context_trampoline;
125 sigemptyset(&sa.sa_mask);
126 sa.sa_flags = SA_ONSTACK;
127 if(sigaction(SIGUSR1, &sa, nullptr))
128 return errno;
129
130 // Let SIGUSR1 remain blocked.
131
132 // All set.
133 return 0;
134 }
135
136 static void deinit() noexcept
137 {
138 // Release SIGUSR1.
139 struct sigaction sa;
140 sa.sa_handler = SIG_DFL;
141 sigemptyset(&sa.sa_mask);
142 sa.sa_flags = 0;
143 sigaction(SIGUSR1, &sa, nullptr);
144
145 base::deinit();
146 }
147
148 int create() noexcept
149 {
150 int res = base::create();
151 if(unlikely(res))
152 return res;
153
154 Stack const& stack_ = stackUsable();
155 if(unlikely(!stack_))
156 // Stackless fiber only saves current context; nothing to do.
157 return 0;
158
160 stack_t ss = {};
161 stack_t oss = {};
162
163 // Let the new context inherit our signal mask.
165 if(unlikely((res = pthread_sigmask(0, nullptr, &m_mask))))
166 goto rollback;
167
168 ss.ss_sp = stack_.p;
169 ss.ss_size = stack_.size;
170
171# ifdef ZTH_USE_VALGRIND
172 // Disable checking during bootstrap.
173 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(ss.ss_sp, ss.ss_size);
174# endif
175
176# ifdef SS_AUTODISARM
177 ss.ss_flags |= SS_AUTODISARM;
178# endif
179 if(unlikely(sigaltstack(&ss, &oss))) {
180 res = errno;
181 goto rollback;
182 }
183
184 ZTH_TLS_SET(trampoline_context, this);
185
186 // Ready to do the signal.
187 sigset_t sigs;
188 sigemptyset(&sigs);
189 sigaddset(&sigs, SIGUSR1);
190 if(unlikely((res = pthread_sigmask(SIG_UNBLOCK, &sigs, nullptr))))
191 goto rollback_altstack;
192
193 // As we are not blocking SIGUSR1, we will get the signal.
194 if(unlikely((res = pthread_kill(pthread_self(), SIGUSR1))))
195 goto rollback_altstack;
196
197 // Shouldn't take long...
198 while(!m_did_trampoline)
200
201 // Block again.
202 if(unlikely((res = pthread_sigmask(SIG_BLOCK, &sigs, nullptr))))
203 goto rollback_altstack;
204
205 if(Config::Debug)
206 ZTH_TLS_SET(trampoline_context, nullptr);
207
209 if(unlikely(sigaltstack(nullptr, &ss))) {
210 res = errno;
211 goto rollback_altstack;
212 }
213 zth_assert(!(ss.ss_flags & SS_ONSTACK));
214 }
215
216# ifndef SS_AUTODISARM
217 // Reset sigaltstack.
218 ss.ss_flags = SS_DISABLE;
219 if(unlikely(sigaltstack(&ss, nullptr))) {
220 res = errno;
221 goto rollback_altstack;
222 }
223# endif
224
225 if(unlikely(!(oss.ss_flags & SS_DISABLE))) {
226 if(sigaltstack(&oss, nullptr)) {
227 res = errno;
228 goto rollback;
229 }
230 }
231# ifdef ZTH_HAVE_VALGRIND
232 else if(RUNNING_ON_VALGRIND) {
233 // Valgrind has a hard time tracking when we left the signal handler
234 // and are not using the altstack anymore. Reset the altstack, such
235 // that successive sigaltstack() do not fail with EPERM.
236 stack_t dss;
237 dss.ss_sp = dummyAltStack;
238 dss.ss_size = sizeof(dummyAltStack);
239 dss.ss_flags = 0;
240 if(unlikely(sigaltstack(&dss, nullptr))) {
241 res = errno;
242 goto rollback;
243 }
244
245 // Although the previous stack was disabled, it is still valid.
246 VALGRIND_MAKE_MEM_DEFINED(ss.ss_sp, ss.ss_size);
247 }
248# endif
249
250 // Ok, we have returned from the signal handler.
251 // Now go back again and save a proper env.
252
253 sigjmp_buf me;
254 m_parent = &me;
255 if(sigsetjmp(me, Config::ContextSignals) == 0)
256 longjmp(m_trampoline_env, 1);
257
258# ifdef ZTH_USE_VALGRIND
259 // Disabled checking during bootstrap.
260 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(ss.ss_sp, ss.ss_size);
261# endif
262
264 if(unlikely(sigaltstack(nullptr, &oss))) {
265 res = errno;
266 goto rollback;
267 }
268 zth_assert(!(oss.ss_flags & SS_ONSTACK));
269 }
270
271 // context is setup properly and is ready for normal scheduling.
272 if(Config::Debug)
273 m_parent = nullptr;
274
275 return 0;
276
277rollback_altstack:
278# ifdef ZTH_USE_VALGRIND
279 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(ss.ss_sp, ss.ss_size);
280# endif
281 if(!(oss.ss_flags & SS_DISABLE))
282 sigaltstack(&oss, nullptr);
283rollback:
285 return res ? res : EINVAL;
286 }
287
288 void context_switch(Context& to) noexcept
289 {
290 if(sigsetjmp(m_env, Config::ContextSignals) == 0) {
291 // Go switch away from here.
292 siglongjmp(to.m_env, 1);
293 }
294 }
295
296private:
297 union {
299 sigjmp_buf m_env;
300 };
301 sigset_t m_mask;
302 union {
303 sig_atomic_t volatile m_did_trampoline;
304 sigjmp_buf* volatile m_parent;
305 };
306# ifdef ZTH_HAVE_VALGRIND
307 static char dummyAltStack[MINSIGSTKSZ];
308# endif
309};
310
311ZTH_TLS_DEFINE(Context*, Context::trampoline_context, nullptr)
312
313# ifdef ZTH_HAVE_VALGRIND
314char Context::dummyAltStack[MINSIGSTKSZ];
315# endif
316
317} // namespace zth
318#endif // __cplusplus
319#endif // ZTH_CONTEXT_SIGALTSTACK_H
sigjmp_buf *volatile m_parent
int create() noexcept
impl::ContextArch< Context > base
Definition sigaltstack.h:60
sig_atomic_t volatile m_did_trampoline
static int init() noexcept
void context_switch(Context &to) noexcept
sigjmp_buf m_env
constexpr Context(ContextAttr const &attr) noexcept
Definition sigaltstack.h:62
jmp_buf m_trampoline_env
static void deinit() noexcept
static void deinit() noexcept
Final system cleanup.
Definition context.h:138
int create() noexcept
Create context.
Definition context.h:159
ContextAttr & attr() noexcept
Return the context attributes, requested by the user.
Definition context.h:143
Stack const & stackUsable() const noexcept
Return the start of the actual usable stack.
Definition context.h:257
void destroy() noexcept
Destroy and cleanup context.
Definition context.h:189
static int init() noexcept
One-time system initialization.
Definition context.h:130
void zth_abort(char const *fmt,...)
Aborts the process after printing the given printf() formatted message.
Definition util.cpp:334
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:189
#define ZTH_CLASS_NEW_DELETE(T)
Define new/delete operators for a class, which are allocator-aware.
Definition allocator.h:143
#define ZTH_INIT_CALL(f)
Definition init.h:48
#define ZTH_TLS_DEFINE(type, var, init)
Definition macros.h:100
#define ZTH_TLS_GET(var)
Definition macros.h:104
#define ZTH_TLS_MEMBER(type, var)
Definition macros.h:102
#define ZTH_TLS_SET(var, value)
Definition macros.h:103
void context_entry(Context *context)
string err(int e)
Return a string like strerror() does, but as a zth::string.
Definition util.h:675
#define pthread_self()
Definition sigaltstack.h:31
#define pthread_sigmask(...)
Definition sigaltstack.h:29
#define pthread_kill(...)
Definition sigaltstack.h:30
#define pthread_yield_np()
Definition sigaltstack.h:32
static bool const Debug
This is a debug build when set to true.
Definition config.h:56
static bool const EnableAssert
When true, enable zth_assert().
Definition config.h:64
static bool const ContextSignals
Take POSIX signal into account when doing a context switch.
Definition config.h:145
Stack information.
Definition context.h:107
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:212
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:55