text: /*
   * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
   *
   * SPDX-License-Identifier: BSD-2-Clause
   */
  
  #include <AK/Assertions.h>
  #include <AK/Types.h>
  #include <LibC/sys/arch/i386/regs.h>
  #include <LibCore/ArgsParser.h>
  #include <LibCore/File.h>
  #include <signal.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/ptrace.h>
  #include <sys/wait.h>
  #include <syscall.h>
  #include <unistd.h>
  
  static int g_pid = -1;
  
  static void handle_sigint(int)
  {
      if (g_pid == -1)
          return;
  
      if (ptrace(PT_DETACH, g_pid, 0, 0) == -1) {
          perror("detach");
      }
  }
  
  int main(int argc, char** argv)
  {
      if (pledge("stdio wpath cpath proc exec ptrace sigaction", nullptr) < 0) {
          perror("pledge");
          return 1;
      }
  
      Vector<const char*> child_argv;
  
      const char* output_filename = nullptr;
      auto trace_file = Core::File::standard_error();
  
      Core::ArgsParser parser;
      parser.set_stop_on_first_non_option(true);
      parser.set_general_help(
          "Trace all syscalls and their result.");
      parser.add_option(g_pid, "Trace the given PID", "pid", 'p', "pid");
      parser.add_option(output_filename, "Filename to write output to", "output", 'o', "output");
      parser.add_positional_argument(child_argv, "Arguments to exec", "argument", Core::ArgsParser::Required::No);
  
      parser.parse(argc, argv);
  
      if (output_filename != nullptr) {
          auto open_result = Core::File::open(output_filename, Core::OpenMode::WriteOnly);
          if (open_result.is_error()) {
              outln(stderr, "Failed to open output file: {}", open_result.error());
              return 1;
          }
          trace_file = open_result.value();
      }
  
      if (pledge("stdio proc exec ptrace sigaction", nullptr) < 0) {
          perror("pledge");
          return 1;
      }
  
      int status;
      if (g_pid == -1) {
          if (child_argv.is_empty()) {
              warnln("strace: Expected either a pid or some arguments");
              return 1;
          }
  
          child_argv.append(nullptr);
          int pid = fork();
          if (pid < 0) {
              perror("fork");
              return 1;
          }
  
          if (!pid) {
              if (ptrace(PT_TRACE_ME, 0, 0, 0) == -1) {
                  perror("traceme");
                  return 1;
              }
              int rc = execvp(child_argv.first(), const_cast<char**>(child_argv.data()));
              if (rc < 0) {
                  perror("execvp");
                  exit(1);
              }
              VERIFY_NOT_REACHED();
          }
  
          g_pid = pid;
          if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) {
              perror("waitpid");
              return 1;
          }
      }
  
      struct sigaction sa;
      memset(&sa, 0, sizeof(struct sigaction));
      sa.sa_handler = handle_sigint;
      sigaction(SIGINT, &sa, nullptr);
  
      if (ptrace(PT_ATTACH, g_pid, 0, 0) == -1) {
          perror("attach");
          return 1;
      }
      if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
          perror("waitpid");
          return 1;
      }
  
      for (;;) {
          if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) {
              perror("syscall");
              return 1;
          }
          if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
              perror("wait_pid");
              return 1;
          }
          PtraceRegisters regs = {};
          if (ptrace(PT_GETREGS, g_pid, &regs, 0) == -1) {
              perror("getregs");
              return 1;
          }
  #if ARCH(I386)
          u32 syscall_index = regs.eax;
          u32 arg1 = regs.edx;
          u32 arg2 = regs.ecx;
          u32 arg3 = regs.ebx;
  #else
          u64 syscall_index = regs.rax;
          u64 arg1 = regs.rdx;
          u64 arg2 = regs.rcx;
          u64 arg3 = regs.rbx;
  #endif
  
          if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) {
              perror("syscall");
              return 1;
          }
          if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
              perror("wait_pid");
              return 1;
          }
  
          if (ptrace(PT_GETREGS, g_pid, &regs, 0) == -1) {
              perror("getregs");
              return 1;
          }
  
  #if ARCH(I386)
          u32 res = regs.eax;
  #else
          u64 res = regs.rax;
  #endif
  
          auto string = String::formatted("{}({:#08x}, {:#08x}, {:#08x})\t={}\n",
              Syscall::to_string((Syscall::Function)syscall_index),
              arg1,
              arg2,
              arg3,
              res);
  
          if (!trace_file->write(string)) {
              warnln("write: {}", trace_file->error_string());
              return 1;
          }
      }
  
      return 0;
  }
  