forked from plasma-umass/coz
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlibcoz.cpp
531 lines (433 loc) · 17.2 KB
/
libcoz.cpp
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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
/*
* Copyright (c) 2015, Charlie Curtsinger and Emery Berger,
* University of Massachusetts Amherst
* This file is part of the Coz project. See LICENSE.md file at the top-level
* directory of this distribution and at http://github.com/plasma-umass/coz.
*/
#include <dlfcn.h>
#include <linux/limits.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sstream>
#include <string>
#include <unordered_set>
#include "inspect.h"
#include "profiler.h"
#include "progress_point.h"
#include "real.h"
#include "util.h"
#include "ccutil/log.h"
// Include the client header file
#include "coz.h"
using namespace std;
/// The type of a main function
typedef int (*main_fn_t)(int, char**, char**);
/// The program's real main function
main_fn_t real_main;
static bool end_to_end = false;
bool initialized = false;
static bool init_in_progress = false;
/**
* Called by the application to get/create a progress point
*/
extern "C" coz_counter_t* _coz_get_counter(progress_point_type t, const char* name) {
if(t == progress_point_type::throughput) {
throughput_point* p = profiler::get_instance().get_throughput_point(name);
if(p) return p->get_counter_struct();
else return nullptr;
} else if(t == progress_point_type::begin) {
latency_point* p = profiler::get_instance().get_latency_point(name);
if(p) return p->get_begin_counter_struct();
else return nullptr;
} else if(t == progress_point_type::end) {
latency_point* p = profiler::get_instance().get_latency_point(name);
if(p) return p->get_end_counter_struct();
else return nullptr;
} else {
WARNING << "Unknown progress point type " << ((int)t) << " named " << name;
return nullptr;
}
}
/**
* Read a link's contents and return it as a string
*/
static string readlink_str(const char* path) {
size_t exe_size = 1024;
ssize_t exe_used;
while(true) {
char exe_path[exe_size];
exe_used = readlink(path, exe_path, exe_size - 1);
REQUIRE(exe_used > 0) << "Unable to read link " << path;
if(exe_used < exe_size - 1) {
exe_path[exe_used] = '\0';
return string(exe_path);
}
exe_size += 1024;
}
}
/*
* Initialize coz. This will either happen as main() is called using
* __libc_start_main, or on the first call to pthread_create() (if
* that happen earlier, which might happen if some shared library
* dependency create a thread in its initializer).
*/
void init_coz(void) {
if (init_in_progress) {
INFO << "init_coz in progress, do not recurse";
return;
}
init_in_progress = true;
INFO << "bootstrapping coz";
initialized = false;
// Remove Coz from LD_PRELOAD. Just clearing LD_PRELOAD for now FIXME!
unsetenv("LD_PRELOAD");
// Read settings out of environment variables
string output_file = getenv_safe("COZ_OUTPUT", "profile.coz");
vector<string> binary_scope_v = split(getenv_safe("COZ_BINARY_SCOPE"), '\t');
unordered_set<string> binary_scope(binary_scope_v.begin(), binary_scope_v.end());
vector<string> source_scope_v = split(getenv_safe("COZ_SOURCE_SCOPE"), '\t');
unordered_set<string> source_scope(source_scope_v.begin(), source_scope_v.end());
vector<string> progress_points_v = split(getenv_safe("COZ_PROGRESS_POINTS"), '\t');
unordered_set<string> progress_points(progress_points_v.begin(), progress_points_v.end());
end_to_end = getenv("COZ_END_TO_END");
string fixed_line_name = getenv_safe("COZ_FIXED_LINE", "");
int fixed_speedup;
stringstream(getenv_safe("COZ_FIXED_SPEEDUP", "-1")) >> fixed_speedup;
// Replace 'MAIN' in the binary_scope with the real path of the main executable
if(binary_scope.find("MAIN") != binary_scope.end()) {
binary_scope.erase("MAIN");
string main_name = readlink_str("/proc/self/exe");
binary_scope.insert(main_name);
INFO << "Including MAIN, which is " << main_name;
}
// Build the memory map for all in-scope binaries
memory_map::get_instance().build(binary_scope, source_scope);
// Register any sampling progress points
for(const string& line_name : progress_points) {
/*shared_ptr<line> l = memory_map::get_instance().find_line(line_name);
if(l) {
progress_point* p = new sampling_progress_point(line_name, l);
profiler::get_instance().sampling_progress_point(p);
} else {
WARNING << "Progress line \"" << line_name << "\" was not found.";
}*/
FATAL << "Sampling-based progress points are temporarily unsupported";
}
shared_ptr<line> fixed_line;
if(fixed_line_name != "") {
fixed_line = memory_map::get_instance().find_line(fixed_line_name);
PREFER(fixed_line) << "Fixed line \"" << fixed_line_name << "\" was not found.";
}
// Create an end-to-end progress point and register it if running in
// end-to-end mode
if(end_to_end) {
(void)profiler::get_instance().get_throughput_point("end-to-end");
}
// Start the profiler
profiler::get_instance().startup(output_file,
fixed_line.get(),
fixed_speedup,
end_to_end);
// Synchronizations can be intercepted once the profiler has been initialized
initialized = true;
init_in_progress = false;
}
/**
* Pass the real __libc_start_main this main function, then run the real main
* function. This allows Coz to shut down when the real main function returns.
*/
static int wrapped_main(int argc, char** argv, char** env) {
if (!initialized)
init_coz();
// Run the real main function
int result = real_main(argc, argv, env);
// Increment the end-to-end progress point just before shutdown
if(end_to_end) {
throughput_point* end_point =
profiler::get_instance().get_throughput_point("end-to-end");
end_point->visit();
}
// Shut down the profiler
profiler::get_instance().shutdown();
return result;
}
/**
* Interpose on the call to __libc_start_main to run before libc constructors.
*/
extern "C" int __libc_start_main(main_fn_t, int, char**, void (*)(), void (*)(), void (*)(), void*) __attribute__((weak, alias("coz_libc_start_main")));
extern "C" int coz_libc_start_main(main_fn_t main_fn, int argc, char** argv,
void (*init)(), void (*fini)(), void (*rtld_fini)(), void* stack_end) {
// Find the real __libc_start_main
auto real_libc_start_main = (decltype(__libc_start_main)*)dlsym(RTLD_NEXT, "__libc_start_main");
// Save the program's real main function
real_main = main_fn;
// Run the real __libc_start_main, but pass in the wrapped main function
int result = real_libc_start_main(wrapped_main, argc, argv, init, fini, rtld_fini, stack_end);
return result;
}
/// Remove coz's required signals from a signal mask
static void remove_coz_signals(sigset_t* set) {
if(sigismember(set, SampleSignal)) {
sigdelset(set, SampleSignal);
}
if(sigismember(set, SIGSEGV)) {
sigdelset(set, SIGSEGV);
}
if(sigismember(set, SIGABRT)) {
sigdelset(set, SIGABRT);
}
}
/// Check if a signal is required by coz
static bool is_coz_signal(int signum) {
return signum == SampleSignal || signum == SIGSEGV || signum == SIGABRT;
}
extern "C" {
/// Pass pthread_create calls to coz so child threads can inherit the parent's delay count
int pthread_create(pthread_t* thread,
const pthread_attr_t* attr,
thread_fn_t fn,
void* arg) {
return profiler::get_instance().handle_pthread_create(thread, attr, fn, arg);
}
/// Catch up on delays before exiting, possibly unblocking a thread joining this one
void __attribute__((noreturn)) pthread_exit(void* result) {
profiler::get_instance().handle_pthread_exit(result);
}
/// Skip any delays added while waiting to join a thread
int pthread_join(pthread_t t, void** retval) {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_join(t, retval);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
int pthread_tryjoin_np(pthread_t t, void** retval) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_tryjoin_np(t, retval);
if(initialized) profiler::get_instance().post_block(result == 0);
return result;
}
int pthread_timedjoin_np(pthread_t t, void** ret, const struct timespec* abstime) {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_timedjoin_np(t, ret, abstime);
if(initialized) profiler::get_instance().post_block(result == 0);
return result;
}
/// Skip any global delays added while blocked on a mutex
int pthread_mutex_lock(pthread_mutex_t* mutex) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_mutex_lock(mutex);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
/// Catch up on delays before unblocking any threads waiting on a mutex
int pthread_mutex_unlock(pthread_mutex_t* mutex) throw() {
if(initialized) profiler::get_instance().catch_up();
return real::pthread_mutex_unlock(mutex);
}
/// Skip any delays added while waiting on a condition variable
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_cond_wait(cond, mutex);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
/**
* Wait on a condvar for a fixed timeout. If the wait does *not* time out, skip any global
* delays added during the waiting period.
*/
int pthread_cond_timedwait(pthread_cond_t* cond,
pthread_mutex_t* mutex,
const struct timespec* time) {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_cond_timedwait(cond, mutex, time);
// Skip delays only if the wait didn't time out
if(initialized) profiler::get_instance().post_block(result == 0);
return result;
}
/// Catchup on delays before waking a thread waiting on a condition variable
int pthread_cond_signal(pthread_cond_t* cond) throw() {
if(initialized) profiler::get_instance().catch_up();
return real::pthread_cond_signal(cond);
}
/// Catch up on delays before waking any threads waiting on a condition variable
int pthread_cond_broadcast(pthread_cond_t* cond) throw() {
if(initialized) profiler::get_instance().catch_up();
return real::pthread_cond_broadcast(cond);
}
/// Catch up before, and skip ahead after waking from a barrier
int pthread_barrier_wait(pthread_barrier_t* barrier) throw() {
if(initialized) profiler::get_instance().catch_up();
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_barrier_wait(barrier);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_rwlock_rdlock(rwlock);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
int pthread_rwlock_timedrdlock(pthread_rwlock_t* rwlock, const struct timespec* abstime) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_rwlock_timedrdlock(rwlock, abstime);
if(initialized) profiler::get_instance().post_block(result == 0);
return result;
}
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_rwlock_wrlock(rwlock);
if(initialized) profiler::get_instance().post_block(true);
return result;
}
int pthread_rwlock_timedwrlock(pthread_rwlock_t* rwlock, const struct timespec* abstime) throw() {
if(initialized) profiler::get_instance().pre_block();
int result = real::pthread_rwlock_timedwrlock(rwlock, abstime);
if(initialized) profiler::get_instance().post_block(result == 0);
return result;
}
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock) throw() {
if(initialized) profiler::get_instance().catch_up();
return real::pthread_rwlock_unlock(rwlock);
}
/// Run shutdown before exiting
void __attribute__((noreturn)) exit(int status) throw() {
profiler::get_instance().shutdown();
real::exit(status);
}
/// Run shutdown before exiting
void __attribute__((noreturn)) _exit(int status) {
profiler::get_instance().shutdown();
real::_exit(status);
}
/// Run shutdown before exiting
void __attribute__((noreturn)) _Exit(int status) throw() {
profiler::get_instance().shutdown();
real::_Exit(status);
}
/// Don't allow programs to set signal handlers for coz's required signals
sighandler_t signal(int signum, sighandler_t handler) throw() {
if(is_coz_signal(signum)) {
return NULL;
} else {
return real::signal(signum, handler);
}
}
/// Don't allow programs to set handlers or mask signals required for coz
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact) throw() {
if(is_coz_signal(signum)) {
return 0;
} else if(act != NULL) {
struct sigaction my_act = *act;
remove_coz_signals(&my_act.sa_mask);
return real::sigaction(signum, &my_act, oldact);
} else {
return real::sigaction(signum, act, oldact);
}
}
/// Ensure coz's signals remain unmasked
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset) throw() {
if(how == SIG_BLOCK || how == SIG_SETMASK) {
if(set != NULL) {
sigset_t myset = *set;
remove_coz_signals(&myset);
return real::sigprocmask(how, &myset, oldset);
}
}
return real::sigprocmask(how, set, oldset);
}
/// Ensure coz's signals remain unmasked
int pthread_sigmask(int how, const sigset_t* set, sigset_t* oldset) throw() {
if(how == SIG_BLOCK || how == SIG_SETMASK) {
if(set != NULL) {
sigset_t myset = *set;
remove_coz_signals(&myset);
return real::pthread_sigmask(how, &myset, oldset);
}
}
return real::pthread_sigmask(how, set, oldset);
}
/// Catch up on delays before sending a signal to the current process
int kill(pid_t pid, int sig) throw() {
if(pid == getpid())
profiler::get_instance().catch_up();
return real::kill(pid, sig);
}
/// Catch up on delays before sending a signal to another thread
int pthread_kill(pthread_t thread, int sig) throw() {
// TODO: Don't allow threads to send coz's signals
if(initialized) profiler::get_instance().catch_up();
return real::pthread_kill(thread, sig);
}
int pthread_sigqueue(pthread_t thread, int sig, const union sigval val) throw() {
if(initialized) profiler::get_instance().catch_up();
return real::pthread_sigqueue(thread, sig, val);
}
/**
* Ensure a thread cannot wait for coz's signals.
* If the waking signal is delivered from the same process, skip any global delays added
* while blocked.
*/
int sigwait(const sigset_t* set, int* sig) {
sigset_t myset = *set;
remove_coz_signals(&myset);
siginfo_t info;
if(initialized) profiler::get_instance().pre_block();
int result = real::sigwaitinfo(&myset, &info);
// Woken up by another thread if the call did not fail, and the waking process is this one
if(initialized) profiler::get_instance().post_block(result != -1 && info.si_pid == getpid());
if(result == -1) {
// If there was an error, return the error code
return errno;
} else {
// No need to check if sig is null because it's declared as non-null
*sig = result;
return 0;
}
}
/**
* Ensure a thread cannot wait for coz's signals.
* If the waking signal is delivered from the same process, skip any added global delays.
*/
int sigwaitinfo(const sigset_t* set, siginfo_t* info) {
sigset_t myset = *set;
siginfo_t myinfo;
remove_coz_signals(&myset);
if(initialized) profiler::get_instance().pre_block();
int result = real::sigwaitinfo(&myset, &myinfo);
// Woken up by another thread if the call did not fail, and the waking process is this one
if(initialized) profiler::get_instance().post_block(result > 0 && myinfo.si_pid == getpid());
if(result > 0 && info)
*info = myinfo;
return result;
}
/**
* Ensure a thread cannot wait for coz's signals.
* If the waking signal is delivered from the same process, skip any global delays.
*/
int sigtimedwait(const sigset_t* set, siginfo_t* info, const struct timespec* timeout) {
sigset_t myset = *set;
siginfo_t myinfo;
remove_coz_signals(&myset);
if(initialized) profiler::get_instance().pre_block();
int result = real::sigtimedwait(&myset, &myinfo, timeout);
// Woken up by another thread if the call did not fail, and the waking process is this one
if(initialized) profiler::get_instance().post_block(result > 0 && myinfo.si_pid == getpid());
if(result > 0 && info)
*info = myinfo;
return result;
}
/**
* Set the process signal mask, suspend, then wake and restore the signal mask.
* If the waking signal is delivered from within the process, skip any added global delays
*/
int sigsuspend(const sigset_t* set) {
sigset_t oldset;
int sig;
real::sigprocmask(SIG_SETMASK, set, &oldset);
int rc = sigwait(set, &sig);
real::sigprocmask(SIG_SETMASK, &oldset, nullptr);
return rc;
}
}