this repo has no description
1#include <unistd.h>
2#include <sys/mman.h>
3#include <stdint.h>
4#include <stdlib.h>
5#include <pthread.h>
6#include <string.h>
7#include <darling/emulation/common/simple.h>
8#include "xtracelib.h"
9#include "mig_trace.h"
10#include "tls.h"
11#include "lock.h"
12#include "memory.h"
13#include <limits.h>
14
15#include <darling/emulation/linux_premigration/ext/for-xtrace.h>
16#include <fcntl.h>
17#include <signal.h>
18
19// Defined in assembly
20extern "C" void darling_mach_syscall_entry_trampoline(void);
21extern "C" void darling_mach_syscall_exit_trampoline(void);
22extern "C" void darling_bsd_syscall_entry_trampoline(void);
23extern "C" void darling_bsd_syscall_exit_trampoline(void);
24extern "C" int sys_thread_selfid(void);
25
26static void xtrace_thread_exit_hook(void);
27static void xtrace_execve_inject_hook(const char*** envp_ptr);
28static void xtrace_postfork_child_hook(void);
29
30#ifdef __x86_64__
31struct hook {
32 uint8_t movabs[2];
33 uint64_t addr;
34 uint8_t call[3];
35} __attribute__((packed));
36#elif defined(__i386__)
37struct hook {
38 uint8_t mov;
39 uint32_t addr;
40 uint8_t call[2];
41} __attribute__((packed));
42#else
43#error "Missing hook struct for arch"
44#endif
45
46// Defined in libsystem_kernel
47extern struct hook* _darling_mach_syscall_entry;
48extern struct hook* _darling_mach_syscall_exit;
49extern struct hook* _darling_bsd_syscall_entry;
50extern struct hook* _darling_bsd_syscall_exit;
51
52extern "C" void _xtrace_thread_exit(void);
53extern "C" void _xtrace_execve_inject(const char*** envp_ptr);
54extern "C" void _xtrace_postfork_child(void);
55
56static void xtrace_setup_mach(void);
57static void xtrace_setup_bsd(void);
58static void setup_hook(struct hook* hook, void* fnptr, bool jump);
59static void xtrace_setup_options(void);
60static void xtrace_setup_misc_hooks(void);
61
62static int xtrace_ignore = 1;
63
64// whether to use a sigaltstack guard page below the stack
65// (this should probably be left on)
66#define SIGALTSTACK_GUARD 1
67
68__attribute__((constructor))
69extern "C"
70void xtrace_setup()
71{
72 xtrace_setup_options();
73 xtrace_setup_mig_tracing();
74 xtrace_setup_mach();
75 xtrace_setup_bsd();
76 xtrace_setup_misc_hooks();
77
78 // override the default sigaltstack used by libsystem_kernel for the main thread
79 // (we need more than the default 8KiB; testing has shown that 16KiB seems to be enough)
80 struct bsd_stack custom_altstack = {
81 .ss_size = 16 * 1024,
82 .ss_flags = 0,
83 };
84
85#if SIGALTSTACK_GUARD
86 custom_altstack.ss_sp = (void*)mmap(NULL, custom_altstack.ss_size + 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
87 if (custom_altstack.ss_sp == MAP_FAILED) {
88 xtrace_abort("xtrace: failed to allocate larger sigstack for main thread");
89 }
90
91 mprotect(custom_altstack.ss_sp, 4096, PROT_NONE);
92 custom_altstack.ss_sp = (char*)custom_altstack.ss_sp + 4096;
93#else
94 custom_altstack.ss_sp = (void*)mmap(NULL, custom_altstack.ss_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
95 if (custom_altstack.ss_sp == MAP_FAILED) {
96 xtrace_abort("xtrace: failed to allocate larger sigstack for main thread");
97 }
98#endif
99
100 if (_sigaltstack_for_xtrace(&custom_altstack, NULL) < 0) {
101 xtrace_abort("failed to override sigaltstack");
102 }
103
104 // and set the size to allocate for future threads
105 _sigaltstack_set_default_size_for_xtrace(custom_altstack.ss_size);
106
107 xtrace_ignore = 0;
108}
109
110static int xtrace_split_entry_and_exit = 0;
111int xtrace_no_color = 0;
112int xtrace_kprintf = 0;
113
114static int xtrace_use_logfile = 0;
115static int xtrace_use_per_thread_logfile = 0;
116
117static char xtrace_logfile_base[PATH_MAX] = {0};
118
119static xtrace_once_t xtrace_common_logfile_once = XTRACE_ONCE_INITIALIZER;
120int xtrace_common_logfile = -1;
121
122static void xtrace_per_thread_logfile_destroy(int* ptr) {
123 if (xtrace_use_per_thread_logfile && ptr && *ptr >= 0) {
124 _close_for_xtrace(*ptr);
125 }
126};
127
128DEFINE_XTRACE_TLS_VAR(int, xtrace_per_thread_logfile, -1, xtrace_per_thread_logfile_destroy);
129
130static bool string_is_truthy(const char* string) {
131 return string && (string[0] == '1' || string[0] == 'T' || string[0] == 't' || string[0] == 'Y' || string[0] == 'y');
132};
133
134static void xtrace_setup_options(void)
135{
136 const char* xtrace_log_file = getenv("XTRACE_LOG_FILE");
137
138 xtrace_split_entry_and_exit = string_is_truthy(getenv("XTRACE_SPLIT_ENTRY_AND_EXIT"));
139 xtrace_no_color = string_is_truthy(getenv("XTRACE_NO_COLOR"));
140 xtrace_kprintf = string_is_truthy(getenv("XTRACE_KPRINTF"));
141 xtrace_use_per_thread_logfile = string_is_truthy(getenv("XTRACE_LOG_FILE_PER_THREAD"));
142
143 if (xtrace_log_file != NULL && xtrace_log_file[0] != '\0') {
144 xtrace_use_logfile = 1;
145 strlcpy(xtrace_logfile_base, xtrace_log_file, sizeof(xtrace_logfile_base));
146 }
147}
148
149
150static void setup_hook(struct hook* hook, void* fnptr, bool jump)
151{
152#if defined(__x86_64__)
153 // this hook is (in GAS syntax):
154 // movq $<fnptr value>, %r10
155 // call *%r10
156 // the call turns to a jump if `jump` is true
157 hook->movabs[0] = 0x49;
158 hook->movabs[1] = 0xba;
159 hook->call[0] = 0x41;
160 hook->call[1] = 0xff;
161 hook->call[2] = jump ? 0xe2 : 0xd2;
162 hook->addr = (uintptr_t)fnptr;
163#elif defined(__i386__)
164 // this hook is (in GAS syntax):
165 // mov $<fnptr value>, %ecx
166 // call *%ecx
167 // the call turns into a jump if `jump` is true
168 hook->mov = 0xb9;
169 hook->addr = (uintptr_t)fnptr;
170 hook->call[0] = 0xff;
171 hook->call[1] = jump ? 0xe1 : 0xd1;
172#else
173#error "Missing hook implementation for arch
174#endif
175}
176
177static void xtrace_setup_mach(void)
178{
179 uintptr_t area = (uintptr_t)_darling_mach_syscall_entry;
180 uintptr_t areaEnd = ((uintptr_t)_darling_mach_syscall_exit) + sizeof(struct hook);
181
182 // __asm__("int3");
183 area &= ~(4096-1);
184 areaEnd &= ~(4096-1);
185
186 uintptr_t bytes = 4096 + (areaEnd-area);
187
188 mprotect((void*) area, bytes, PROT_READ | PROT_WRITE | PROT_EXEC);
189
190 setup_hook(_darling_mach_syscall_entry, (void*)darling_mach_syscall_entry_trampoline, false);
191 setup_hook(_darling_mach_syscall_exit, (void*)darling_mach_syscall_exit_trampoline, false);
192
193 mprotect((void*) area, bytes, PROT_READ | PROT_EXEC);
194}
195
196static void xtrace_setup_bsd(void)
197{
198 uintptr_t area = (uintptr_t)_darling_bsd_syscall_entry;
199 uintptr_t areaEnd = ((uintptr_t)_darling_bsd_syscall_exit) + sizeof(struct hook);
200
201 // __asm__("int3");
202 area &= ~(4096-1);
203 areaEnd &= ~(4096-1);
204
205 uintptr_t bytes = 4096 + (areaEnd-area);
206
207 mprotect((void*) area, bytes, PROT_READ | PROT_WRITE | PROT_EXEC);
208
209 setup_hook(_darling_bsd_syscall_entry, (void*)darling_bsd_syscall_entry_trampoline, false);
210 setup_hook(_darling_bsd_syscall_exit, (void*)darling_bsd_syscall_exit_trampoline, false);
211
212 mprotect((void*) area, bytes, PROT_READ | PROT_EXEC);
213}
214
215// like setup_hook, but also takes care of making memory writable for the hook setup and restoring it afterwards
216static void setup_hook_with_perms(struct hook* hook, void* fnptr, bool jump) {
217 uintptr_t area = (uintptr_t)hook;
218 uintptr_t areaEnd = ((uintptr_t)hook) + sizeof(struct hook);
219
220 // __asm__("int3");
221 area &= ~(4096-1);
222 areaEnd &= ~(4096-1);
223
224 uintptr_t bytes = 4096 + (areaEnd-area);
225
226 mprotect((void*) area, bytes, PROT_READ | PROT_WRITE | PROT_EXEC);
227
228 setup_hook(hook, fnptr, jump);
229
230 mprotect((void*) area, bytes, PROT_READ | PROT_EXEC);
231};
232
233static void xtrace_setup_misc_hooks(void) {
234 setup_hook_with_perms((hook*)&_xtrace_thread_exit, (void*)xtrace_thread_exit_hook, true);
235 setup_hook_with_perms((hook*)&_xtrace_execve_inject, (void*)xtrace_execve_inject_hook, true);
236 setup_hook_with_perms((hook*)&_xtrace_postfork_child, (void*)xtrace_postfork_child_hook, true);
237};
238
239void xtrace_set_gray_color(xtrace::String* log)
240{
241 if (xtrace_no_color)
242 return;
243
244 log->append("\033[37m");
245}
246
247void xtrace_reset_color(xtrace::String* log)
248{
249 if (xtrace_no_color)
250 return;
251
252 log->append("\033[0m");
253}
254
255void xtrace_start_line(xtrace::String* log, int indent)
256{
257 xtrace_set_gray_color(log);
258
259 log->append_format("[%d]", sys_thread_selfid());
260 for (int i = 0; i < indent + 1; i++)
261 log->append(" ");
262
263 xtrace_reset_color(log);
264}
265
266static void print_call(xtrace::String* log, const struct calldef* defs, const char* type, int nr, int indent, int gray_name)
267{
268 xtrace_start_line(log, indent);
269
270 if (gray_name)
271 xtrace_set_gray_color(log);
272
273 if (defs[nr].name != NULL)
274 log->append_format("%s", defs[nr].name);
275 else
276 log->append_format("%s %d", type, nr);
277
278 // Leaves gray color on!
279}
280
281
282struct nested_call_struct {
283 // We're inside this many calls. In other words, we have printed this many
284 // call entries without matching exits.
285 int current_level;
286 // What that value was the last time. if we've just handled an entry or an
287 // exit, this will be greater/less than current_level.
288 int previous_level;
289 // Call numbers, indexed by current level.
290 int nrs[64];
291};
292
293DEFINE_XTRACE_TLS_VAR(struct nested_call_struct, nested_call, (struct nested_call_struct) {0}, NULL);
294
295void handle_generic_entry(xtrace::String* log, const struct calldef* defs, const char* type, int nr, void* args[])
296{
297 if (xtrace_ignore)
298 return;
299
300 if (get_ptr_nested_call()->previous_level < get_ptr_nested_call()->current_level && !xtrace_split_entry_and_exit)
301 {
302 // We are after an earlier entry without an exit.
303 xtrace_log("%s\n", log->c_str());
304 log->clear();
305 }
306
307 int indent = 4 * get_ptr_nested_call()->current_level;
308 get_ptr_nested_call()->nrs[get_ptr_nested_call()->current_level] = nr;
309
310 print_call(log, defs, type, nr, indent, 0);
311
312 if (defs[nr].name != NULL && defs[nr].print_args != NULL)
313 {
314 log->append("(");
315 defs[nr].print_args(log, nr, args);
316 log->append(")");
317 }
318 else
319 log->append("(...)");
320
321 if (xtrace_split_entry_and_exit) {
322 xtrace_log("%s\n", log->c_str());
323 log->clear();
324 }
325
326 get_ptr_nested_call()->previous_level = get_ptr_nested_call()->current_level++;
327}
328
329void handle_generic_exit(xtrace::String* log, const struct calldef* defs, const char* type, uintptr_t retval, int force_split)
330{
331 if (xtrace_ignore)
332 return;
333
334 if (get_ptr_nested_call()->previous_level > get_ptr_nested_call()->current_level)
335 {
336 // We are after an exit, so our call has been split up.
337 force_split = 1;
338 }
339 get_ptr_nested_call()->previous_level = get_ptr_nested_call()->current_level--;
340 int nr = get_ptr_nested_call()->nrs[get_ptr_nested_call()->current_level];
341
342 if (xtrace_split_entry_and_exit || force_split)
343 {
344 int indent = 4 * get_ptr_nested_call()->current_level;
345 print_call(log, defs, type, nr, indent, 1);
346 log->append("()");
347 }
348
349 xtrace_set_gray_color(log);
350 log->append(" -> ");
351 xtrace_reset_color(log);
352
353 if (defs[nr].name != NULL && defs[nr].print_retval != NULL)
354 defs[nr].print_retval(log, nr, retval);
355 else
356 log->append_format("0x%lx", retval);
357
358 xtrace_log("%s\n", log->c_str());
359 log->clear();
360}
361
362extern "C"
363void xtrace_log(const char* format, ...) {
364 va_list args;
365 va_start(args, format);
366 xtrace_log_v(format, args);
367 va_end(args);
368};
369
370// TODO: we should add guarded FD support so that we can make FDs like these logfile descriptors guarded.
371// it would also be very useful to guard the special LKM descriptor.
372
373static void xtrace_common_logfile_init(void) {
374 xtrace_common_logfile = _open_for_xtrace(xtrace_logfile_base, O_WRONLY | O_APPEND | O_CLOEXEC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
375};
376
377static void ensure_logfile(void) {
378 bool created = false;
379 int fd = -1;
380
381 if (!xtrace_use_logfile) {
382 xtrace_abort("xtrace: tried to use logfile when not enabled");
383 }
384
385 if (get_xtrace_per_thread_logfile() != -1) {
386 return;
387 }
388
389 if (xtrace_use_per_thread_logfile) {
390 char filename[sizeof(xtrace_logfile_base)];
391 char append[32] = {0};
392
393 strlcpy(filename, xtrace_logfile_base, PATH_MAX);
394
395 __simple_snprintf(append, sizeof(append), ".%d", sys_thread_selfid());
396 strlcat(filename, append, PATH_MAX);
397
398 fd = _open_for_xtrace(filename, O_WRONLY | O_APPEND | O_CLOEXEC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
399 } else {
400 xtrace_once(&xtrace_common_logfile_once, xtrace_common_logfile_init);
401 fd = xtrace_common_logfile;
402 }
403
404 if (fd < 0) {
405 xtrace_abort("xtrace: failed to open logfile");
406 }
407
408 set_xtrace_per_thread_logfile(fd);
409};
410
411extern "C"
412void xtrace_log_v(const char* format, va_list args) {
413 if (xtrace_kprintf) {
414 char real_format[512] = "xtrace: ";
415 strlcpy(&real_format[0] + (sizeof("xtrace: ") - 1), format, sizeof(real_format) - (sizeof("xtrace: ") - 1));
416 __simple_vkprintf(real_format, args);
417 } else if (xtrace_use_logfile) {
418 char output[512];
419 ensure_logfile();
420 __simple_vsnprintf(output, sizeof(output), format, args);
421 __write_for_xtrace(get_xtrace_per_thread_logfile(), output, __simple_strlen(output));
422 } else {
423 __simple_vprintf(format, args);
424 }
425};
426
427extern "C"
428void xtrace_error(const char* format, ...) {
429 va_list args;
430 va_start(args, format);
431 xtrace_error_v(format, args);
432 va_end(args);
433};
434
435extern "C"
436void xtrace_error_v(const char* format, va_list args) {
437 if (xtrace_kprintf) {
438 char real_format[512] = "xtrace: ";
439 strlcpy(real_format + (sizeof("xtrace: ") - 1), format, sizeof(real_format) - (sizeof("xtrace: ") - 1));
440 __simple_vkprintf(real_format, args);
441 } else if (xtrace_use_logfile) {
442 char output[512];
443 ensure_logfile();
444 __simple_vsnprintf(output, sizeof(output), format, args);
445 __write_for_xtrace(get_xtrace_per_thread_logfile(), output, __simple_strlen(output));
446 } else {
447 __simple_vfprintf(STDERR_FILENO, format, args);
448 }
449};
450
451extern "C"
452void xtrace_abort(const char* message) {
453 _abort_with_payload_for_xtrace(0, 0, NULL, 0, message, 0);
454 __builtin_unreachable();
455};
456
457static void xtrace_thread_exit_hook(void) {
458 xtrace_tls_thread_cleanup();
459};
460
461static size_t envp_count(const char** envp) {
462 size_t count = 0;
463 for (const char** ptr = envp; *ptr != NULL; ++ptr) {
464 ++count;
465 }
466 return count;
467};
468
469static const char** envp_find(const char** envp, const char* key) {
470 size_t key_length = strlen(key);
471
472 for (const char** ptr = envp; *ptr != NULL; ++ptr) {
473 const char* entry_key = *ptr;
474 const char* entry_key_end = strchr(entry_key, '=');
475 size_t entry_key_length = entry_key_end - entry_key;
476
477 if (entry_key_length != key_length) {
478 continue;
479 }
480
481 if (strncmp(key, entry_key, key_length) != 0) {
482 continue;
483 }
484
485 return ptr;
486 }
487
488 return NULL;
489};
490
491static const char* envp_make_entry(const char* key, const char* value) {
492 size_t key_length = strlen(key);
493 size_t value_length = strlen(value);
494 char* entry = (char*)xtrace_malloc(key_length + value_length + 2);
495 memcpy(entry, key, key_length);
496 entry[key_length] = '=';
497 memcpy(&entry[key_length + 1], value, value_length);
498 entry[key_length + value_length + 2] = '\0';
499 return entry;
500};
501
502static void envp_set(const char*** envp_ptr, const char* key, const char* value, bool* allocated) {
503 const char** envp = *envp_ptr;
504
505 if (!envp) {
506 *envp_ptr = envp = (const char**)xtrace_malloc(sizeof(const char*) * 2);
507 envp[0] = envp_make_entry(key, value);
508 envp[1] = NULL;
509 *allocated = true;
510 } else {
511 const char** entry_ptr = envp_find(envp, key);
512
513 if (entry_ptr) {
514 *entry_ptr = envp_make_entry(key, value);
515 } else {
516 size_t count = envp_count(envp);
517 const char** new_envp = (const char**)xtrace_malloc(sizeof(const char*) * (count + 2));
518
519 memcpy(new_envp, envp, sizeof(const char*) * count);
520
521 if (*allocated) {
522 xtrace_free(envp);
523 }
524
525 new_envp[count] = envp_make_entry(key, value);
526 new_envp[count + 1] = NULL;
527 *allocated = true;
528 *envp_ptr = new_envp;
529 }
530 }
531};
532
533static const char* envp_get(const char** envp, const char* key) {
534 const char** entry = envp_find(envp, key);
535
536 if (!entry) {
537 return NULL;
538 }
539
540 return strchr(*entry, '=') + 1;
541};
542
543#define LIBRARY_PATH "/usr/lib/darling/libxtrace.dylib"
544#define LIBRARY_PATH_LENGTH (sizeof(LIBRARY_PATH) - 1)
545
546static void xtrace_execve_inject_hook(const char*** envp_ptr) {
547 bool allocated = false;
548
549 envp_set(envp_ptr, "XTRACE_SPLIT_ENTRY_AND_EXIT", xtrace_split_entry_and_exit ? "1" : "0", &allocated);
550 envp_set(envp_ptr, "XTRACE_NO_COLOR", xtrace_no_color ? "1" : "0", &allocated);
551 envp_set(envp_ptr, "XTRACE_KPRINTF", xtrace_kprintf ? "1" : "0", &allocated);
552 envp_set(envp_ptr, "XTRACE_LOG_FILE_PER_THREAD", xtrace_use_per_thread_logfile ? "1" : "0", &allocated);
553 envp_set(envp_ptr, "XTRACE_LOG_FILE", xtrace_use_logfile ? xtrace_logfile_base : "", &allocated);
554
555 const char* insert_libraries = envp_get(*envp_ptr, "DYLD_INSERT_LIBRARIES");
556 size_t insert_libraries_length = insert_libraries ? strlen(insert_libraries) : 0;
557 char* new_value = (char*)xtrace_malloc(insert_libraries_length + (insert_libraries_length == 0 ? 0 : 1) + LIBRARY_PATH_LENGTH + 1);
558 size_t offset = 0;
559
560 if (insert_libraries && insert_libraries_length > 0) {
561 memcpy(&new_value[offset], insert_libraries, insert_libraries_length);
562 offset += insert_libraries_length;
563
564 new_value[offset] = ':';
565 ++offset;
566 }
567
568 memcpy(&new_value[offset], LIBRARY_PATH, LIBRARY_PATH_LENGTH);
569 offset += LIBRARY_PATH_LENGTH;
570
571 new_value[offset] = '\0';
572
573 envp_set(envp_ptr, "DYLD_INSERT_LIBRARIES", new_value, &allocated);
574};
575
576static void xtrace_postfork_child_hook(void) {
577 // TODO: cleanup TLS
578
579 // reset the per-thread logfile (if necessary)
580 if (xtrace_use_per_thread_logfile) {
581 int fd = get_xtrace_per_thread_logfile();
582 if (fd >= 0) {
583 _close_for_xtrace(fd);
584 }
585 set_xtrace_per_thread_logfile(-1);
586 }
587};