]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/sigbus.c
meson: check for sys/auxv.h
[thirdparty/systemd.git] / src / basic / sigbus.c
CommitLineData
fa6ac760
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2014 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
11c3a366 20#include <errno.h>
fa6ac760 21#include <signal.h>
11c3a366 22#include <stddef.h>
fa6ac760
LP
23#include <sys/mman.h>
24
25#include "macro.h"
fa6ac760 26#include "sigbus.h"
cf0fbc49 27#include "util.h"
fa6ac760
LP
28
29#define SIGBUS_QUEUE_MAX 64
30
31static struct sigaction old_sigaction;
32static unsigned n_installed = 0;
33
34/* We maintain a fixed size list of page addresses that triggered a
35 SIGBUS. We access with list with atomic operations, so that we
36 don't have to deal with locks between signal handler and main
37 programs in possibly multiple threads. */
38
39static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX];
40static volatile sig_atomic_t n_sigbus_queue = 0;
41
42static void sigbus_push(void *addr) {
43 unsigned u;
44
45 assert(addr);
46
47 /* Find a free place, increase the number of entries and leave, if we can */
48 for (u = 0; u < SIGBUS_QUEUE_MAX; u++)
49 if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) {
50 __sync_fetch_and_add(&n_sigbus_queue, 1);
51 return;
52 }
53
54 /* If we can't, make sure the queue size is out of bounds, to
55 * mark it as overflow */
56 for (;;) {
57 unsigned c;
58
59 __sync_synchronize();
60 c = n_sigbus_queue;
61
62 if (c > SIGBUS_QUEUE_MAX) /* already overflow */
63 return;
64
65 if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX))
66 return;
67 }
68}
69
70int sigbus_pop(void **ret) {
71 assert(ret);
72
73 for (;;) {
74 unsigned u, c;
75
76 __sync_synchronize();
77 c = n_sigbus_queue;
78
79 if (_likely_(c == 0))
80 return 0;
81
82 if (_unlikely_(c >= SIGBUS_QUEUE_MAX))
83 return -EOVERFLOW;
84
85 for (u = 0; u < SIGBUS_QUEUE_MAX; u++) {
86 void *addr;
87
88 addr = sigbus_queue[u];
89 if (!addr)
90 continue;
91
92 if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) {
93 __sync_fetch_and_sub(&n_sigbus_queue, 1);
94 *ret = addr;
95 return 1;
96 }
97 }
98 }
99}
100
101static void sigbus_handler(int sn, siginfo_t *si, void *data) {
102 unsigned long ul;
103 void *aligned;
104
105 assert(sn == SIGBUS);
106 assert(si);
107
108 if (si->si_code != BUS_ADRERR || !si->si_addr) {
109 assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
110 raise(SIGBUS);
111 return;
112 }
113
114 ul = (unsigned long) si->si_addr;
115 ul = ul / page_size();
116 ul = ul * page_size();
117 aligned = (void*) ul;
118
119 /* Let's remember which address failed */
120 sigbus_push(aligned);
121
122 /* Replace mapping with an anonymous page, so that the
123 * execution can continue, however with a zeroed out page */
124 assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned);
125}
126
127void sigbus_install(void) {
128 struct sigaction sa = {
129 .sa_sigaction = sigbus_handler,
130 .sa_flags = SA_SIGINFO,
131 };
132
133 n_installed++;
134
135 if (n_installed == 1)
136 assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0);
137
138 return;
139}
140
141void sigbus_reset(void) {
142
143 if (n_installed <= 0)
144 return;
145
146 n_installed--;
147
148 if (n_installed == 0)
149 assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
150
151 return;
152}