-
Notifications
You must be signed in to change notification settings - Fork 0
/
tramp-stack.c
345 lines (280 loc) · 8.44 KB
/
tramp-stack.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
#include <signal.h>
#include "tramp.h"
/* The "log" is a record of the actions we have performed within the
current thread. These actions include allocating trampolines on
behalf of a given stack frame, allocating trampoline page pairs,
and allocating more log space. It is organized this way in order
to minimize memory allocation overhead.
Entries in the log are pairs of uintptr_t. The first entry of the
pair indicates the type of action, and the second is some sort of
data associated with that action:
0 Allocated a new log page. The data entry is the pointer to
the previous log page. This will only ever appear as the
first entry of a log page.
1 Allocated a new tramp page pair. The data entry is the pointer
to the previous pair.
2 Entered the signal stack. The data entry is ignored.
CFA Allocated trampoine entries on behalf of the function instance
identified by its Canonical Frame Address. The data entry is
the number of trampolines allocated. This number will never be
more than the number of trampolines remaining in the current
tramp page pair. If the function requires more trampolines,
we'll use additional log entries.
*/
#define LOG_NEW_LOG 0
#define LOG_NEW_PAGE 1
#define LOG_SIGSTACK 2
#define LOG_SIZE (PAGE_SIZE / sizeof(uintptr_t))
/* All thread-local variables. */
struct tramp_globals
{
/* The current page from which we are allocating trampolines. */
void *cur_page;
/* The current page from which we are logging actions. */
uintptr_t *cur_log;
/* The number of trampolines allocated from the current page. */
unsigned int cur_page_inuse;
/* The number of log entries in use in the current page. */
unsigned int cur_log_inuse;
/* The "current" cfa for subsequent allocations from this function. */
uintptr_t cur_cfa;
/* The active signal stack, assuming SS_ONSTACK is set. */
stack_t cur_sigstack;
/* Previously allocated pages not yet released to the system. */
void *save_page;
uintptr_t *save_log;
};
static __thread struct tramp_globals tramp_G = {
.cur_page_inuse = TRAMP_COUNT,
};
static inline struct tramp_globals *
get_globals (void)
{
struct tramp_globals *G = &tramp_G;
/* ??? When using global-dynamic (or emulated) tls, avoid calling
into the runtime more than once. */
#ifdef __PIC__
asm("" : "+r"(G));
#endif
return G;
}
/* Allocate and free one trampoline page pair, using a thread-local cache. */
static inline void *
alloc_one_tramp_page (struct tramp_globals *G)
{
void *ret = G->save_page;
if (ret == 0)
ret = __tramp_alloc_pair ();
else
G->save_page = 0;
return ret;
}
static inline void
free_one_tramp_page (struct tramp_globals *G, void *page)
{
void *old = G->save_page;
G->save_page = page;
if (old)
__tramp_free_pair (old);
}
/* Allocate and free one log page, using a thread-local cache. */
static inline uintptr_t *
alloc_one_log_page (struct tramp_globals *G)
{
void *ret = G->save_log;
if (ret == 0)
{
ret = mmap (NULL, PAGE_SIZE, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (ret == MAP_FAILED)
abort ();
}
else
G->save_log = 0;
return ret;
}
static inline void
free_one_log_page_raw (uintptr_t *log)
{
if (munmap (log, PAGE_SIZE) < 0)
abort ();
}
static inline void
free_one_log_page (struct tramp_globals *G, uintptr_t *log)
{
uintptr_t *old = G->save_log;
G->save_log = log;
if (old)
free_one_log_page_raw (old);
}
/* Return true if CFA A is older than CFA B on the stack. */
/* ??? Under normal conditions this can be a simple pointer comparison
based on whether or not the stack grows down or up. If split stacks
are in use, this ought to involve some sort of data structure search
to which we have no access given the current API. */
static inline bool
cfa_older_p (uintptr_t a, uintptr_t b)
{
/* ??? Assume stack grows down. */
return a > b;
}
/* Replay the log until we get back to an entry older than CFA.
Note that -1 can be used in order to reply the entire log. */
/* ??? Except that -1 assumes stack grows down; 0 would be the
stack grows up magic value. */
static void
replay_log (struct tramp_globals *G, uintptr_t cfa, bool exit_sigstack)
{
uintptr_t *log = G->cur_log;
unsigned int inuse = G->cur_log_inuse;
while (inuse > 0)
{
uintptr_t action = log[inuse - 2];
uintptr_t data = log[inuse - 1];
switch (action)
{
case LOG_NEW_LOG:
free_one_log_page (G, log);
log = (uintptr_t *) data;
inuse = LOG_SIZE;
break;
case LOG_NEW_PAGE:
assert (G->cur_page_inuse == TRAMP_RESERVE);
free_one_tramp_page (G, G->cur_page);
G->cur_page = (void *) data;
G->cur_page_inuse = TRAMP_COUNT;
break;
case LOG_SIGSTACK:
if (!exit_sigstack)
goto egress;
exit_sigstack = false;
break;
default:
if (!exit_sigstack && cfa_older_p (action, cfa))
goto egress;
assert (G->cur_page_inuse >= data);
G->cur_page_inuse -= data;
break;
}
inuse -= 2;
}
egress:
G->cur_log = log;
G->cur_log_inuse = inuse;
}
/* Add a log entry. */
static void
add_log (struct tramp_globals *G, uintptr_t action, uintptr_t data)
{
uintptr_t *log = G->cur_log;
unsigned int inuse = G->cur_log_inuse;
if (log == NULL || inuse == LOG_SIZE)
{
uintptr_t *new_log = alloc_one_log_page (G);
new_log[0] = LOG_NEW_LOG;
new_log[1] = (uintptr_t) log;
inuse = 2;
G->cur_log = log = new_log;
}
log[inuse++] = action;
log[inuse++] = data;
G->cur_log_inuse = inuse;
}
/* Set CFA only for the first allocation in the function; subsequent
allocations use CFA=0. */
void *
__tramp_stack_alloc (uintptr_t cfa, uintptr_t fnaddr, uintptr_t chain_value)
{
struct tramp_globals *G = get_globals ();
sigset_t old_set, full_set;
sigfillset (&full_set);
pthread_sigmask (SIG_SETMASK, &full_set, &old_set);
if (cfa)
{
stack_t ss;
/* ??? Use glibc tcb entries in order to quickly determine that the cfa
is indeed within the current thread's stack. When this test fails,
there are two possibilities: (1) we're on the signal stack, (2) we're
using split stacks (which needs additional help in order to determine
whether the current frame is inner or outer of the previous), or
(3) the user is doing something odd with the stacks. */
sigaltstack (NULL, &ss);
if (__builtin_expect (ss.ss_flags == SS_ONSTACK, 0))
{
if (G->cur_sigstack.ss_flags == SS_ONSTACK)
{
uintptr_t replay_cfa = cfa;
/* We are still running on the signal stack. Double-check
that it's the same stack, Just In Case. */
if (ss.ss_sp != G->cur_sigstack.ss_sp
|| ss.ss_size != G->cur_sigstack.ss_size)
{
G->cur_sigstack = ss;
replay_cfa = -1;
}
replay_log (G, replay_cfa, false);
}
else
{
G->cur_sigstack = ss;
add_log (G, LOG_SIGSTACK, 0);
}
}
else
{
bool exit_sigstack = false;
if (G->cur_sigstack.ss_flags == SS_ONSTACK)
{
G->cur_sigstack.ss_flags = 0;
exit_sigstack = true;
}
replay_log (G, cfa, exit_sigstack);
}
G->cur_cfa = cfa;
}
/* If needed, allocate a new tramp page pair. */
if (G->cur_page_inuse == TRAMP_COUNT)
{
add_log (G, LOG_NEW_PAGE, (uintptr_t) G->cur_page);
G->cur_page = alloc_one_tramp_page (G);
G->cur_page_inuse = TRAMP_RESERVE;
/* Force a new log entry for the current function frame. */
cfa = G->cur_cfa;
}
/* Add, or update, the log entry for the number of trampolines
allocated by the current function frame. */
if (cfa)
add_log (G, cfa, 1);
else
{
assert (G->cur_log[G->cur_log_inuse - 2] == G->cur_cfa);
G->cur_log[G->cur_log_inuse - 1] += 1;
}
{
void *tramp_code, *page;
uintptr_t *tramp_data;
unsigned int index;
page = G->cur_page;
index = G->cur_page_inuse++;
tramp_code = page + index * TRAMP_SIZE;
tramp_data = tramp_code + PAGE_SIZE;
tramp_data[TRAMP_FUNCADDR_FIRST ? 0 : 1] = fnaddr;
tramp_data[TRAMP_FUNCADDR_FIRST ? 1 : 0] = chain_value;
pthread_sigmask (SIG_SETMASK, &old_set, NULL);
return tramp_code;
}
}
void /* __attribute__((thread_destructor)) */
__tramp_stack_free_thread (void)
{
struct tramp_globals *G = get_globals ();
if (G->cur_log)
replay_log (G, -1, true);
if (G->save_log)
free_one_log_page_raw (G->save_log);
if (G->save_page)
__tramp_free_pair (G->save_page);
}