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