dotfiles

personal dotfiles
git clone anongit@rnpnr.xyz:dotfiles.git
Log | Files | Refs | Feed | Submodules

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 = {&regs, sizeof(regs)};
    157 -  long ptraceRet = ptrace(PTRACE_GETREGSET, childPid, (void *)NT_PRSTATUS, &regs_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 -                         &sectionOffset, &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(&section_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 = {&regs, sizeof(regs)};
    451 -  ptraceRet = ptrace(PTRACE_GETREGSET, childPid, (void *)NT_PRSTATUS, &regs_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, &regs_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