this repo has no description
at fixPythonPipStalling 587 lines 17 kB view raw
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};