0002-Rework-injection-and-synchronisaion-on-linux.patch (18443B)
1 From 18cd5579dc447a2b99c509403aed0b004b04db52 Mon Sep 17 00:00:00 2001 2 From: Jules Maselbas <jmaselbas@zdiv.net> 3 Date: Mon, 22 Jan 2024 17:55:21 +0100 4 Subject: [PATCH 2/2] Rework injection and synchronisaion on linux 5 6 --- 7 renderdoc/os/posix/linux/linux_hook.cpp | 3 - 8 renderdoc/os/posix/linux/linux_network.cpp | 6 +- 9 renderdoc/os/posix/linux/linux_process.cpp | 505 +-------------------- 10 renderdoc/os/posix/posix_process.cpp | 5 +- 11 4 files changed, 13 insertions(+), 506 deletions(-) 12 13 diff --git a/renderdoc/os/posix/linux/linux_hook.cpp b/renderdoc/os/posix/linux/linux_hook.cpp 14 index 56ec173c1..99f16932e 100644 15 --- a/renderdoc/os/posix/linux/linux_hook.cpp 16 +++ b/renderdoc/os/posix/linux/linux_hook.cpp 17 @@ -103,7 +103,6 @@ void PreForkConfigureHooks(); 18 void GetUnhookedEnvp(char *const *envp, rdcstr &envpStr, rdcarray<char *> &modifiedEnv); 19 void GetHookedEnvp(char *const *envp, rdcstr &envpStr, rdcarray<char *> &modifiedEnv); 20 void ResetHookingEnvVars(); 21 -void StopAtMainInChild(); 22 bool StopChildAtMain(pid_t childPid, bool *exitWithNoExec); 23 void ResumeProcess(pid_t childPid, uint32_t delay = 0); 24 int direct_setenv(const char *name, const char *value, int overwrite); 25 @@ -317,8 +316,6 @@ __attribute__((visibility("default"))) pid_t fork() 26 { 27 if(Linux_Debug_PtraceLogging()) 28 RDCLOG("hooked fork() in child %d", getpid()); 29 - 30 - StopAtMainInChild(); 31 } 32 else if(ret > 0) 33 { 34 diff --git a/renderdoc/os/posix/linux/linux_network.cpp b/renderdoc/os/posix/linux/linux_network.cpp 35 index 8be1a19dd..382ce6bfb 100644 36 --- a/renderdoc/os/posix/linux/linux_network.cpp 37 +++ b/renderdoc/os/posix/linux/linux_network.cpp 38 @@ -25,6 +25,8 @@ 39 #include "os/os_specific.h" 40 #include "os/posix/posix_network.h" 41 42 +void StopAtMainInChild(); 43 + 44 namespace Network 45 { 46 void SocketPostSend() 47 @@ -39,6 +41,8 @@ uint32_t Socket::GetRemoteIP() const 48 49 Socket *CreateServerSocket(const rdcstr &bindaddr, uint16_t port, int queuesize) 50 { 51 - return CreateTCPServerSocket(bindaddr, port, queuesize); 52 + Socket *s = CreateTCPServerSocket(bindaddr, port, queuesize); 53 + StopAtMainInChild(); 54 + return s; 55 } 56 }; 57 diff --git a/renderdoc/os/posix/linux/linux_process.cpp b/renderdoc/os/posix/linux/linux_process.cpp 58 index aa9c7393c..db0fe9116 100644 59 --- a/renderdoc/os/posix/linux/linux_process.cpp 60 +++ b/renderdoc/os/posix/linux/linux_process.cpp 61 @@ -164,512 +164,21 @@ int GetIdentPort(pid_t childPid) 62 return ret; 63 } 64 65 -static bool ptrace_scope_ok() 66 -{ 67 - if(!Linux_PtraceChildProcesses()) 68 - return false; 69 - 70 - rdcstr contents; 71 - FileIO::ReadAll("/proc/sys/kernel/yama/ptrace_scope", contents); 72 - contents.trim(); 73 - if(!contents.empty()) 74 - { 75 - int ptrace_scope = atoi(contents.c_str()); 76 - if(ptrace_scope > 1) 77 - { 78 - if(RenderDoc::Inst().IsReplayApp()) 79 - { 80 - static bool warned = false; 81 - if(!warned) 82 - { 83 - warned = true; 84 - RDCWARN( 85 - "ptrace_scope value %d means ptrace can't be used to pause child processes while " 86 - "attaching.", 87 - ptrace_scope); 88 - } 89 - } 90 - return false; 91 - } 92 - } 93 - 94 - return true; 95 -} 96 - 97 -static uint64_t get_nanotime() 98 -{ 99 - timespec ts; 100 - clock_gettime(CLOCK_MONOTONIC, &ts); 101 - uint64_t ret = uint64_t(ts.tv_sec) * 1000000000ULL + uint32_t(ts.tv_nsec & 0xffffffff); 102 - return ret; 103 -} 104 - 105 -#if defined(__arm__) 106 - 107 -// for some reason arm doesn't have the same struct name. Sigh :( 108 -#define user_regs_struct user_regs 109 - 110 -#define INST_PTR_REG ARM_pc 111 - 112 -#define BREAK_INST 0xe7f001f0ULL 113 -#define BREAK_INST_BYTES_SIZE 4 114 -// on ARM seemingly the instruction isn't actually considered executed, so we don't have to modify 115 -// the instruction pointer at all. 116 -#define BREAK_INST_INST_PTR_ADJUST 0 117 - 118 -#elif defined(__aarch64__) 119 - 120 -#define INST_PTR_REG pc 121 - 122 -#define BREAK_INST 0xd4200000ULL 123 -#define BREAK_INST_BYTES_SIZE 4 124 -// on ARM seemingly the instruction isn't actually considered executed, so we don't have to modify 125 -// the instruction pointer at all. 126 -#define BREAK_INST_INST_PTR_ADJUST 0 127 - 128 -#elif defined(__riscv) 129 - 130 -#define INST_PTR_REG pc 131 - 132 -// ebreak 133 -#define BREAK_INST 0x00100073ULL 134 -#define BREAK_INST_BYTES_SIZE 4 135 -#define BREAK_INST_INST_PTR_ADJUST 4 136 - 137 -#else 138 - 139 -#define BREAK_INST 0xccULL 140 -#define BREAK_INST_BYTES_SIZE 1 141 -// step back over the instruction 142 -#define BREAK_INST_INST_PTR_ADJUST 1 143 - 144 -#if ENABLED(RDOC_X64) 145 -#define INST_PTR_REG rip 146 -#else 147 -#define INST_PTR_REG eip 148 -#endif 149 - 150 -#endif 151 - 152 -static uint64_t get_child_ip(pid_t childPid) 153 -{ 154 - user_regs_struct regs = {}; 155 - 156 - iovec regs_iovec = {®s, sizeof(regs)}; 157 - long ptraceRet = ptrace(PTRACE_GETREGSET, childPid, (void *)NT_PRSTATUS, ®s_iovec); 158 - if(ptraceRet == 0) 159 - return uint64_t(regs.INST_PTR_REG); 160 - 161 - return 0; 162 -} 163 - 164 -static bool wait_traced_child(pid_t childPid, uint32_t timeoutMS, int &status) 165 -{ 166 - // spin waiting for the traced child, with a 100ms timeout 167 - status = 0; 168 - uint64_t start_nano = get_nanotime(); 169 - uint64_t end_nano = 0; 170 - int ret = 0; 171 - 172 - const uint64_t timeoutNanoseconds = uint64_t(timeoutMS) * 1000 * 1000; 173 - 174 - while((ret = waitpid(childPid, &status, WNOHANG)) == 0) 175 - { 176 - status = 0; 177 - 178 - // if we're in a capturing process then the process itself might have done waitpid(-1) and 179 - // swallowed the wait for our child. So as an alternative we check to see if we can query the 180 - // instruction pointer, which is only possible if the child is stopped. 181 - uint64_t ip = get_child_ip(childPid); 182 - if(ip != 0) 183 - { 184 - // do waitpid again in case we raced and the child stopped in between the call to waitpid and 185 - // get_child_ip. 186 - ret = waitpid(childPid, &status, WNOHANG); 187 - 188 - // if it still didn't succeed, set status to 0 so we know we're earlying out and don't check 189 - // the status codes. 190 - if(ret == 0) 191 - status = 0; 192 - return true; 193 - } 194 - 195 - usleep(10); 196 - 197 - // check the timeout 198 - end_nano = get_nanotime(); 199 - if(end_nano - start_nano > timeoutNanoseconds) 200 - break; 201 - } 202 - 203 - return WIFSTOPPED(status); 204 -} 205 - 206 bool StopChildAtMain(pid_t childPid, bool *exitWithNoExec) 207 { 208 - // don't do this unless the ptrace scope is OK. 209 - if(!ptrace_scope_ok()) 210 - return false; 211 + int stat; 212 + pid_t pid = waitpid(childPid, &stat, WUNTRACED); 213 + return pid == childPid && WIFSTOPPED(stat); 214 +} 215 216 - if(Linux_Debug_PtraceLogging()) 217 - RDCLOG("Stopping child PID %u at main", childPid); 218 - 219 - int childStatus = 0; 220 - 221 - // we have a low timeout for this stop since it should happen almost immediately (right after the 222 - // fork). If it didn't then we want to fail relatively fast. 223 - if(!wait_traced_child(childPid, 100, childStatus)) 224 - { 225 - RDCERR("Didn't get initial stop from child PID %u", childPid); 226 - return false; 227 - } 228 - 229 - if(childStatus > 0 && WSTOPSIG(childStatus) != SIGSTOP) 230 - { 231 - RDCERR("Initial signal from child PID %u was %x, expected %x", childPid, WSTOPSIG(childStatus), 232 - SIGSTOP); 233 - return false; 234 - } 235 - 236 - if(Linux_Debug_PtraceLogging()) 237 - RDCLOG("Child PID %u is stopped in StopAtMainInChild()", childPid); 238 - 239 - int64_t ptraceRet = 0; 240 - 241 - // continue until exec 242 - ptraceRet = ptrace(PTRACE_SETOPTIONS, childPid, NULL, PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT); 243 - RDCASSERTEQUAL(ptraceRet, 0); 244 - 245 - if(Linux_Debug_PtraceLogging()) 246 - RDCLOG("Child PID %u configured to trace exec(). Continuing child", childPid); 247 - 248 - // continue 249 - ptraceRet = ptrace(PTRACE_CONT, childPid, NULL, NULL); 250 - RDCASSERTEQUAL(ptraceRet, 0); 251 - 252 - // we're not under control of when the application calls exec() after fork() in the case of child 253 - // processes, so be a little more generous with the timeout 254 - if(!wait_traced_child(childPid, 250, childStatus)) 255 - { 256 - RDCERR("Didn't get to execve in child PID %u", childPid); 257 - return false; 258 - } 259 - 260 - int statusResult = childStatus >> 8; 261 - 262 - if(childStatus > 0 && 263 - (statusResult == SIGCHLD || statusResult == (SIGTRAP | (PTRACE_EVENT_EXIT << 8)))) 264 - { 265 - if(Linux_Debug_PtraceLogging()) 266 - RDCLOG("Child PID %u exited while waiting for exec() 0x%x", childPid, childStatus); 267 - if(exitWithNoExec) 268 - *exitWithNoExec = true; 269 - 270 - if(statusResult == SIGCHLD) 271 - ptrace(PTRACE_DETACH, childPid, NULL, SIGCHLD); 272 - else 273 - ptrace(PTRACE_DETACH, childPid, NULL, NULL); 274 - return false; 275 - } 276 - 277 - if(childStatus > 0 && statusResult != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) 278 - { 279 - RDCERR("Exec wait event from child PID %u was status 0x%x, expected 0x%x", childPid, 280 - statusResult, (SIGTRAP | (PTRACE_EVENT_EXEC << 8))); 281 - 282 - return false; 283 - } 284 - 285 - if(Linux_Debug_PtraceLogging()) 286 - RDCLOG("Child PID %u is stopped at execve() 0x%x", childPid, childStatus); 287 - 288 - rdcstr exepath; 289 - long baseVirtualPointer = 0; 290 - uint32_t sectionOffset = 0; 291 - 292 - rdcstr mapsName = StringFormat::Fmt("/proc/%u/maps", childPid); 293 - 294 - FILE *maps = FileIO::fopen(mapsName, FileIO::ReadText); 295 - 296 - if(!maps) 297 - { 298 - RDCERR("Couldn't open %s", mapsName.c_str()); 299 - return false; 300 - } 301 - 302 - while(!feof(maps)) 303 - { 304 - char line[512] = {0}; 305 - if(fgets(line, 511, maps)) 306 - { 307 - char *sp = strchr(line, ' '); 308 - if(sp == NULL) 309 - continue; 310 - 311 - sp++; 312 - 313 - if(!strncmp(sp, "r-xp", 4)) 314 - { 315 - RDCCOMPILE_ASSERT(sizeof(long) == sizeof(void *), "Expected long to be pointer sized"); 316 - int pathOffset = 0; 317 - int num = sscanf(line, "%lx-%*x r-xp %x %*x:%*x %*u %n", &baseVirtualPointer, 318 - §ionOffset, &pathOffset); 319 - 320 - if(num != 2 || pathOffset == 0) 321 - { 322 - RDCERR("Couldn't parse first executable mapping '%s'", rdcstr(line).trimmed().c_str()); 323 - return false; 324 - } 325 - 326 - exepath = line + pathOffset; 327 - exepath.trim(); 328 - break; 329 - } 330 - } 331 - } 332 - 333 - if(baseVirtualPointer == 0) 334 - { 335 - RDCERR("Couldn't find executable mapping in maps file"); 336 - return false; 337 - } 338 - 339 - if(Linux_Debug_PtraceLogging()) 340 - RDCLOG("Child PID %u has exepath %s basePointer 0x%llx and sectionOffset 0x%x", childPid, 341 - exepath.c_str(), (uint64_t)baseVirtualPointer, (uint32_t)sectionOffset); 342 - 343 - FileIO::fclose(maps); 344 - 345 - FILE *elf = FileIO::fopen(exepath, FileIO::ReadText); 346 - 347 - if(!elf) 348 - { 349 - RDCERR("Couldn't open %s to parse ELF header", exepath.c_str()); 350 - return false; 351 - } 352 - 353 - Elf64_Ehdr elf_header; 354 - size_t read = FileIO::fread(&elf_header, sizeof(elf_header), 1, elf); 355 - 356 - if(read != 1) 357 - { 358 - FileIO::fclose(elf); 359 - RDCERR("Couldn't read ELF header from %s", exepath.c_str()); 360 - return false; 361 - } 362 - 363 - size_t entryVirtual = (size_t)elf_header.e_entry; 364 - // if the section doesn't shift between file offset and virtual address this will be the same 365 - size_t entryFileOffset = entryVirtual; 366 - 367 - if(elf_header.e_shoff) 368 - { 369 - if(Linux_Debug_PtraceLogging()) 370 - RDCLOG("exepath %s contains sections, rebasing to correct section", exepath.c_str()); 371 - 372 - FileIO::fseek64(elf, elf_header.e_shoff, SEEK_SET); 373 - 374 - RDCASSERTEQUAL(elf_header.e_shentsize, sizeof(Elf64_Shdr)); 375 - 376 - for(Elf64_Half s = 0; s < elf_header.e_shnum; s++) 377 - { 378 - Elf64_Shdr section_header; 379 - read = FileIO::fread(§ion_header, sizeof(section_header), 1, elf); 380 - 381 - if(read != 1) 382 - { 383 - FileIO::fclose(elf); 384 - RDCERR("Couldn't read section header from %s", exepath.c_str()); 385 - return false; 386 - } 387 - 388 - if(section_header.sh_addr <= entryVirtual && 389 - entryVirtual < section_header.sh_addr + section_header.sh_size) 390 - { 391 - if(Linux_Debug_PtraceLogging()) 392 - RDCLOG( 393 - "Found section in %s from 0x%llx - 0x%llx at offset 0x%llx containing entry 0x%llx.", 394 - exepath.c_str(), (uint64_t)section_header.sh_addr, 395 - uint64_t(section_header.sh_addr + section_header.sh_size), 396 - (uint64_t)section_header.sh_offset, (uint64_t)entryVirtual); 397 - 398 - entryFileOffset = 399 - (entryVirtual - (size_t)section_header.sh_addr) + (size_t)section_header.sh_offset; 400 - 401 - break; 402 - } 403 - } 404 - } 405 - 406 - FileIO::fclose(elf); 407 - 408 - void *entry = (void *)(baseVirtualPointer + entryFileOffset - sectionOffset); 409 - 410 - if(Linux_Debug_PtraceLogging()) 411 - RDCLOG("child process %u executable %s has entry %p at 0x%llx + (0x%llx - 0x%x)", childPid, 412 - exepath.c_str(), entry, (uint64_t)baseVirtualPointer, (uint64_t)entryFileOffset, 413 - (uint32_t)sectionOffset); 414 - 415 - // this reads a 'word' and returns as long, upcast (if needed) to uint64_t 416 - uint64_t origEntryWord = (uint64_t)ptrace(PTRACE_PEEKTEXT, childPid, entry, 0); 417 - 418 - if(Linux_Debug_PtraceLogging()) 419 - RDCLOG("Read word %llx from %p in child process %u running executable %s", 420 - (uint64_t)origEntryWord, entry, childPid, exepath.c_str()); 421 - 422 - uint64_t breakpointWord = 423 - (origEntryWord & (0xffffffffffffffffULL << (BREAK_INST_BYTES_SIZE * 8))) | BREAK_INST; 424 - // downcast back to long, if that means truncating 425 - ptraceRet = ptrace(PTRACE_POKETEXT, childPid, entry, (long)breakpointWord); 426 - RDCASSERTEQUAL(ptraceRet, 0); 427 - 428 - if(Linux_Debug_PtraceLogging()) 429 - RDCLOG("Changed word to %llx and re-poked in process %u. Continuing child", 430 - (uint64_t)breakpointWord, childPid); 431 - 432 - // continue 433 - ptraceRet = ptrace(PTRACE_CONT, childPid, NULL, NULL); 434 - RDCASSERTEQUAL(ptraceRet, 0); 435 - 436 - // it could take a long time to hit main so we have a large timeout here 437 - if(!wait_traced_child(childPid, 2000, childStatus)) 438 - { 439 - RDCERR("Didn't hit breakpoint in PID %u (%x)", childPid, childStatus); 440 - return false; 441 - } 442 - 443 - if(Linux_Debug_PtraceLogging()) 444 - RDCLOG("Process %u hit entry point", childPid); 445 - 446 - // we're now at main! now just need to clean up after ourselves 447 - 448 - user_regs_struct regs = {}; 449 - 450 - iovec regs_iovec = {®s, sizeof(regs)}; 451 - ptraceRet = ptrace(PTRACE_GETREGSET, childPid, (void *)NT_PRSTATUS, ®s_iovec); 452 - RDCASSERTEQUAL(ptraceRet, 0); 453 - 454 - if(Linux_Debug_PtraceLogging()) 455 - RDCLOG("Process %u instruction pointer is at %llx, for entry point %p", childPid, 456 - (uint64_t)(regs.INST_PTR_REG), entry); 457 - 458 - // step back past the byte(s) we inserted the breakpoint on 459 - regs.INST_PTR_REG -= BREAK_INST_INST_PTR_ADJUST; 460 - ptraceRet = ptrace(PTRACE_SETREGSET, childPid, (void *)NT_PRSTATUS, ®s_iovec); 461 - RDCASSERTEQUAL(ptraceRet, 0); 462 - 463 - // restore the function 464 - ptraceRet = ptrace(PTRACE_POKETEXT, childPid, entry, origEntryWord); 465 - RDCASSERTEQUAL(ptraceRet, 0); 466 - 467 - if(Linux_Debug_PtraceLogging()) 468 - RDCLOG("Process %u instruction pointer adjusted and breakpoint removed.", childPid); 469 - 470 - // we'll resume after reading the ident port in the calling function 471 - return true; 472 +void ResumeProcess(pid_t childPid, uint32_t delay = 0) 473 +{ 474 + kill(childPid, SIGCONT); 475 } 476 477 void StopAtMainInChild() 478 { 479 - // don't do this unless the ptrace scope is OK. 480 - if(!ptrace_scope_ok()) 481 - return; 482 - 483 - if(Linux_Debug_PtraceLogging()) 484 - RDCLOG("Stopping in main at child for ptracing"); 485 - 486 - // allow parent tracing, and immediately stop so the parent process can attach 487 - ptrace(PTRACE_TRACEME, 0, 0, 0); 488 - 489 - if(Linux_Debug_PtraceLogging()) 490 - RDCLOG("Done PTRACE_TRACEME, raising SIGSTOP"); 491 - 492 raise(SIGSTOP); 493 - 494 - if(Linux_Debug_PtraceLogging()) 495 - RDCLOG("Resumed after SIGSTOP"); 496 -} 497 - 498 -void ResumeProcess(pid_t childPid, uint32_t delaySeconds) 499 -{ 500 - if(!ptrace_scope_ok()) 501 - return; 502 - 503 - if(childPid != 0) 504 - { 505 - // if we have a delay, see if the process is paused. If so then detach it but keep it stopped 506 - // and wait to see if someone attaches 507 - if(delaySeconds > 0) 508 - { 509 - uint64_t ip = get_child_ip(childPid); 510 - 511 - if(ip != 0) 512 - { 513 - if(Linux_Debug_PtraceLogging()) 514 - RDCLOG("Detaching %u with SIGSTOP to allow a debugger to attach, waiting %u seconds", 515 - childPid, delaySeconds); 516 - 517 - // detach but stop, to allow a debugger to attach 518 - ptrace(PTRACE_DETACH, childPid, NULL, SIGSTOP); 519 - 520 - rdcstr filename = StringFormat::Fmt("/proc/%u/status", childPid); 521 - 522 - uint64_t start_nano = get_nanotime(); 523 - uint64_t end_nano = 0; 524 - 525 - const uint64_t timeoutNanoseconds = uint64_t(delaySeconds) * 1000 * 1000 * 1000; 526 - 527 - bool connected = false; 528 - 529 - // watch for a tracer to attach 530 - do 531 - { 532 - usleep(10); 533 - 534 - rdcstr status; 535 - FileIO::ReadAll(filename, status); 536 - 537 - int32_t offs = status.find("TracerPid:"); 538 - 539 - if(offs < 0) 540 - break; 541 - 542 - status.erase(0, offs + sizeof("TracerPid:")); 543 - status.trim(); 544 - 545 - end_nano = get_nanotime(); 546 - 547 - if(status[0] != '0') 548 - { 549 - RDCLOG("Debugger PID %u attached after %f seconds", atoi(status.c_str()), 550 - double(end_nano - start_nano) / 1000000000.0); 551 - connected = true; 552 - break; 553 - } 554 - } while(end_nano - start_nano < timeoutNanoseconds); 555 - 556 - if(!connected) 557 - { 558 - RDCLOG("Timed out waiting for debugger, resuming"); 559 - kill(childPid, SIGCONT); 560 - } 561 - return; 562 - } 563 - else 564 - { 565 - RDCERR("Can't delay for debugger without ptrace, check ptrace_scope value"); 566 - } 567 - } 568 - 569 - if(Linux_Debug_PtraceLogging()) 570 - RDCLOG("Detaching immediately from %u", childPid); 571 - 572 - // try to detach and resume the process, ignoring any errors if we weren't tracing 573 - long ret = ptrace(PTRACE_DETACH, childPid, NULL, NULL); 574 - 575 - if(Linux_Debug_PtraceLogging()) 576 - RDCLOG("Detached pid %u (%ld)", childPid, ret); 577 - } 578 } 579 580 // because OSUtility::DebuggerPresent is called often we want it to be 581 diff --git a/renderdoc/os/posix/posix_process.cpp b/renderdoc/os/posix/posix_process.cpp 582 index 1adefb0d8..488a171c8 100644 583 --- a/renderdoc/os/posix/posix_process.cpp 584 +++ b/renderdoc/os/posix/posix_process.cpp 585 @@ -447,7 +447,7 @@ static void CleanupStringArray(char **arr) 586 587 while(*arr) 588 { 589 - delete[] * arr; 590 + delete[] *arr; 591 arr++; 592 } 593 594 @@ -608,9 +608,6 @@ static pid_t RunProcess(rdcstr appName, rdcstr workDir, const rdcstr &cmdLine, c 595 childPid = fork(); 596 if(childPid == 0) 597 { 598 - if(pauseAtMain) 599 - StopAtMainInChild(); 600 - 601 FileIO::ReleaseFDAfterFork(); 602 if(stdoutPipe) 603 { 604 -- 605 2.45.2 606