text: // Copyright 2016 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "garnet/bin/insntrace/control.h"
  
  #include <fcntl.h>
  #include <link.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <cinttypes>
  
  #include <iostream>
  
  #include <fuchsia/hardware/cpu/insntrace/cpp/fidl.h>
  #include <lib/zircon-internal/device/cpu-trace/intel-pt.h>
  #include <zircon/syscalls.h>
  
  #include <lib/fdio/fd.h>
  #include <lib/fdio/fdio.h>
  #include <lib/fdio/directory.h>
  #include <lib/zircon-internal/ktrace.h>
  #include <lib/zx/handle.h>
  #include <lib/zx/vmo.h>
  
  #include "src/lib/files/unique_fd.h"
  #include "src/lib/fxl/logging.h"
  #include "src/lib/fxl/strings/string_printf.h"
  
  #include "garnet/lib/debugger_utils/util.h"
  #include "garnet/lib/debugger_utils/x86_cpuid.h"
  #include "garnet/lib/debugger_utils/x86_pt.h"
  
  #include "garnet/bin/insntrace/config.h"
  #include "garnet/bin/insntrace/ktrace_controller.h"
  #include "garnet/bin/insntrace/utils.h"
  
  namespace insntrace {
  
  using Allocation = ::fuchsia::hardware::cpu::insntrace::Allocation;
  using BufferConfig = ::fuchsia::hardware::cpu::insntrace::BufferConfig;
  using BufferState = ::fuchsia::hardware::cpu::insntrace::BufferState;
  using ControllerSyncPtr = ::fuchsia::hardware::cpu::insntrace::ControllerSyncPtr;
  
  // This isn't emitted by the fidl compiler.
  using BufferDescriptor = uint32_t;
  
  static constexpr char ipt_device_path[] = "/dev/sys/cpu-trace/insntrace";
  
  static constexpr char buffer_output_path_suffix[] = "pt";
  static constexpr char ktrace_output_path_suffix[] = "ktrace";
  static constexpr char cpuid_output_path_suffix[] = "cpuid";
  static constexpr char pt_list_output_path_suffix[] = "ptlist";
  
  static constexpr uint32_t kKtraceGroupMask = KTRACE_GRP_ARCH | KTRACE_GRP_TASKS;
  
  static ControllerSyncPtr OpenDevice() {
    ControllerSyncPtr controller_ptr;
    zx_status_t status =
        fdio_service_connect(ipt_device_path, controller_ptr.NewRequest().TakeChannel().release());
    if (status != ZX_OK) {
      FXL_LOG(ERROR) << "Error connecting to " << ipt_device_path << ": " << status;
      return ControllerSyncPtr();
    }
    return controller_ptr;
  }
  
  bool AllocTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "AllocTrace called";
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return false;
    }
  
    Allocation allocation;
    allocation.mode = config.mode;
    allocation.num_traces = (config.mode == Mode::CPU ? config.num_cpus : config.max_threads);
    FXL_VLOG(2) << fxl::StringPrintf("mode=%u, num_traces=0x%x",
                                     static_cast<unsigned>(allocation.mode), allocation.num_traces);
  
    ::fuchsia::hardware::cpu::insntrace::Controller_Initialize_Result result;
    zx_status_t status = ipt->Initialize(allocation, &result);
    if (status != ZX_OK || result.is_err()) {
      LogFidlFailure("Initialize", status, result.err());
      return false;
    }
  
    return true;
  }
  
  static void InitIptBufferConfig(BufferConfig* ipt_config, const IptConfig& config) {
    memset(ipt_config, 0, sizeof(*ipt_config));
    ipt_config->num_chunks = config.num_chunks;
    ipt_config->chunk_order = config.chunk_order;
    ipt_config->is_circular = config.is_circular;
    ipt_config->ctl = config.CtlMsr();
    ipt_config->address_space_match = config.cr3_match;
    ipt_config->address_range_0.start = config.AddrBegin(0);
    ipt_config->address_range_0.end = config.AddrEnd(0);
    ipt_config->address_range_1.start = config.AddrBegin(1);
    ipt_config->address_range_1.end = config.AddrEnd(1);
  }
  
  bool InitTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "InitTrace called";
    FXL_DCHECK(config.mode == Mode::CPU);
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return false;
    }
  
    for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) {
      BufferConfig ipt_config;
      InitIptBufferConfig(&ipt_config, config);
  
      ::fuchsia::hardware::cpu::insntrace::Controller_AllocateBuffer_Result result;
      zx_status_t status = ipt->AllocateBuffer(ipt_config, &result);
      if (status != ZX_OK || result.is_err()) {
        LogFidlFailure("AllocateBuffer", status, result.err());
        return false;
      }
      // Buffers are automagically assigned to cpus, descriptor == cpu#,
      // so we can just ignore descriptor here.
    }
  
    return true;
  }
  
  // This must be called before a process is started so we emit a ktrace
  // process start record for it.
  
  bool InitProcessTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "InitProcessTrace called";
  
    fuchsia::tracing::kernel::ControllerSyncPtr ktrace;
    if (!OpenKtraceChannel(&ktrace)) {
      return false;
    }
  
    // If tracing cpus we may want all the records for processes that were
    // started during boot, so don't reset ktrace here. If tracing threads it
    // doesn't much matter other than hopefully the necessary records don't get
    // over run, which is handled below by only enabling the collection groups
    // we need. So for now leave existing records alone.
    // A better solution would be to collect the data we need at the time we
    // need it.
  #if 0  // TODO(dje)
    if (config.mode == Mode::THREAD) {
      RequestKtraceStop(ktrace);
      RequestKtraceRewind(ktrace);
    }
  #endif
  
    // We definitely need ktrace turned on in order to get cr3->pid mappings,
    // which we need to map trace cr3 values to ld.so mappings, which we need in
    // order to be able to find the ELFs, which are required by the decoder.
    // So this isn't a nice-to-have, we need it. It's possible ktrace is
    // currently off, so ensure it's turned on.
    // For now just include arch info in the ktrace - we need it, and we don't
    // want to risk the ktrace buffer filling without it.
    // Also include task info to get process exit records - we need to know when
    // a cr3 value becomes invalid. Hopefully this won't cause the buffer to
    // overrun. It it does we could consider having special ktrace records just
    // for this, but that's a last resort kind of thing.
    if (RequestKtraceStart(ktrace, kKtraceGroupMask)) {
      return true;
    }
  
    // TODO(dje): Resume original ktracing? Need ability to get old value.
    RequestKtraceStop(ktrace);
    return false;
  }
  
  bool StartTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "StartTrace called";
    FXL_DCHECK(config.mode == Mode::CPU);
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return false;
    }
  
    zx_status_t status = ipt->Start();
    if (status != ZX_OK) {
      LogFidlFailure("Start", status);
      return false;
    }
  
    return true;
  }
  
  void StopTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "StopTrace called";
    FXL_DCHECK(config.mode == Mode::CPU);
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return;
    }
  
    [[maybe_unused]] zx_status_t status = ipt->Stop();
    FXL_DCHECK(status == ZX_OK);
  }
  
  void StopSidebandDataCollection(const IptConfig& config) {
    FXL_LOG(INFO) << "StopSidebandDataCollection called";
  
    fuchsia::tracing::kernel::ControllerSyncPtr ktrace;
    if (!OpenKtraceChannel(&ktrace)) {
      return;
    }
  
    // Avoid having the records we need overrun by the time we collect them by
    // stopping ktrace here. It will get turned back on by "reset".
    RequestKtraceStop(ktrace);
  }
  
  static std::string GetCpuPtFileName(const std::string& output_path_prefix, uint64_t id) {
    const char* name_prefix = "cpu";
    return fxl::StringPrintf("%s.%s%" PRIu64 ".%s", output_path_prefix.c_str(), name_prefix, id,
                             buffer_output_path_suffix);
  }
  
  static std::string GetThreadPtFileName(const std::string& output_path_prefix, uint64_t id) {
    const char* name_prefix = "thr";
    return fxl::StringPrintf("%s.%s%" PRIu64 ".%s", output_path_prefix.c_str(), name_prefix, id,
                             buffer_output_path_suffix);
  }
  
  // Write the contents of buffer |descriptor| to a file.
  // The file's name is $output_path_prefix.$name_prefix$id.pt.
  
  static zx_status_t WriteBufferData(const IptConfig& config, const ControllerSyncPtr& ipt,
                                     uint32_t descriptor, uint64_t id) {
    std::string output_path;
    if (config.mode == Mode::CPU)
      output_path = GetCpuPtFileName(config.output_path_prefix, id);
    else
      output_path = GetThreadPtFileName(config.output_path_prefix, id);
    const char* c_path = output_path.c_str();
  
    // Refetch the buffer config as we can be invoked in a separate process,
    // after tracing has started, and shouldn't rely on what the user thinks
    // the config is.
    std::unique_ptr<BufferConfig> buffer_config;
    zx_status_t status = ipt->GetBufferConfig(descriptor, &buffer_config);
    if (status != ZX_OK) {
      LogFidlFailure("GetBufferConfig", status);
      return status;
    }
    if (!buffer_config) {
      FXL_LOG(ERROR) << "Failed getting buffer config";
      return ZX_ERR_INTERNAL;
    }
  
    std::unique_ptr<BufferState> buffer_state;
    status = ipt->GetBufferState(descriptor, &buffer_state);
    if (status != ZX_OK) {
      LogFidlFailure("GetBufferState", status);
      return status;
    }
    if (!buffer_state) {
      FXL_LOG(ERROR) << "Failed getting buffer state";
      return ZX_ERR_INTERNAL;
    }
  
    fxl::UniqueFD fd(open(c_path, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR));
    if (!fd.is_valid()) {
      FXL_LOG(ERROR) << fxl::StringPrintf("Failed writing file: %s", c_path) << ", "
                     << debugger_utils::ErrnoString(errno);
      return ZX_ERR_BAD_PATH;
    }
  
    // TODO(dje): Fetch from vmo?
    size_t chunk_size = (1 << buffer_config->chunk_order) * PAGE_SIZE;
    uint32_t num_chunks = buffer_config->num_chunks;
  
    // If using a circular buffer there's (currently) no way to know if
    // tracing wrapped, so for now we just punt and always dump the entire
    // buffer. It's highly likely it wrapped anyway.
    size_t bytes_left;
    if (buffer_config->is_circular)
      bytes_left = num_chunks * chunk_size;
    else
      bytes_left = buffer_state->capture_end;
  
    FXL_LOG(INFO) << fxl::StringPrintf("Writing %zu bytes to %s", bytes_left, c_path);
  
    char buf[4096];
  
    for (uint32_t i = 0; i < num_chunks && bytes_left > 0; ++i) {
      zx::vmo vmo;
      status = ipt->GetChunkHandle(descriptor, i, &vmo);
      if (status != ZX_OK) {
        LogFidlFailure("GetChunkHandle", status);
        FXL_LOG(ERROR) << "Buffer " << descriptor << ", chunk " << i;
        goto Fail;
      }
  
      size_t buffer_remaining = chunk_size;
      size_t offset = 0;
      while (buffer_remaining && bytes_left) {
        size_t to_write = sizeof(buf);
        if (to_write > buffer_remaining)
          to_write = buffer_remaining;
        if (to_write > bytes_left)
          to_write = bytes_left;
        // TODO(dje): Mapping into process and reading directly from that
        // left for another day.
        status = vmo.read(buf, offset, to_write);
        if (status != ZX_OK) {
          FXL_LOG(ERROR) << fxl::StringPrintf("zx_vmo_read: buffer %u, buffer %u, offset %zu: ",
                                              descriptor, i, offset)
                         << debugger_utils::ZxErrorString(status);
          goto Fail;
        }
        if (write(fd.get(), buf, to_write) != (ssize_t)to_write) {
          FXL_LOG(ERROR) << fxl::StringPrintf("Short write, file: %s\n", c_path);
          status = ZX_ERR_IO;
          goto Fail;
        }
        offset += to_write;
        buffer_remaining -= to_write;
        bytes_left -= to_write;
      }
    }
  
    assert(bytes_left == 0);
    status = ZX_OK;
    // fallthrough
  
  Fail:
    // We don't delete the file on failure on purpose, it is kept for
    // debugging purposes.
    return status;
  }
  
  // Write all output files.
  // This assumes tracing has already been stopped.
  
  void DumpTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "DumpTrace called";
    FXL_DCHECK(config.mode == Mode::CPU);
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return;
    }
  
    for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) {
      // Buffer descriptors for cpus is the cpu number.
      auto status = WriteBufferData(config, ipt, cpu, cpu);
      if (status != ZX_OK) {
        FXL_LOG(ERROR) << fxl::StringPrintf("Dump perf of cpu %u: ", cpu)
                       << debugger_utils::ZxErrorString(status);
        // Keep trying to dump other cpu's data.
      }
    }
  }
  
  void DumpSidebandData(const IptConfig& config) {
    FXL_LOG(INFO) << "DumpSidebandData called";
  
    DumpKtraceBuffer(config.output_path_prefix.c_str(), ktrace_output_path_suffix);
  
    // TODO(dje): UniqueFILE?
    {
      std::string cpuid_output_path =
          fxl::StringPrintf("%s.%s", config.output_path_prefix.c_str(), cpuid_output_path_suffix);
      const char* cpuid_c_path = cpuid_output_path.c_str();
  
      FILE* f = fopen(cpuid_c_path, "w");
      if (f != nullptr) {
        debugger_utils::x86_feature_debug(f);
        // Also put the mtc_freq value in the cpuid file, it's as good a place
        // for it as any. See intel-pt.h:pt_config.
        // Alternatively this could be added to the ktrace record.
        // TODO(dje): Put constants in zircon/device/intel-pt.h.
        unsigned mtc_freq = config.mtc_freq;
        fprintf(f, "mtc_freq: %u\n", mtc_freq);
        // TODO(dje): verify writes succeed
        fclose(f);
      } else {
        FXL_LOG(ERROR) << "unable to write PT config to " << cpuid_c_path;
      }
    }
  
    // TODO(dje): UniqueFILE?
    // TODO(dje): Handle Mode::THREAD
    if (config.mode == Mode::CPU) {
      std::string pt_list_output_path =
          fxl::StringPrintf("%s.%s", config.output_path_prefix.c_str(), pt_list_output_path_suffix);
      const char* pt_list_c_path = pt_list_output_path.c_str();
  
      FILE* f = fopen(pt_list_c_path, "w");
      if (f != nullptr) {
        for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) {
          std::string pt_file = GetCpuPtFileName(config.output_path_prefix, cpu);
          fprintf(f, "%u %s\n", cpu, pt_file.c_str());
        }
        // TODO(dje): verify writes succeed
        fclose(f);
      } else {
        FXL_LOG(ERROR) << "unable to write PT list to " << pt_list_c_path;
      }
    }
  }
  
  void ResetTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "ResetTrace called";
    FXL_DCHECK(config.mode == Mode::CPU);
  
    // TODO(dje): Nothing to do currently. There use to be. So keep this
    // function around for a bit.
  }
  
  // Free all resources associated with the true.
  // This means restoring ktrace to its original state.
  // This assumes tracing has already been stopped.
  
  void FreeTrace(const IptConfig& config) {
    FXL_LOG(INFO) << "FreeTrace called";
  
    ControllerSyncPtr ipt{OpenDevice()};
    if (!ipt) {
      return;
    }
  
    ::fuchsia::hardware::cpu::insntrace::Controller_Terminate_Result result;
    zx_status_t status = ipt->Terminate(&result);
    if (status != ZX_OK || result.is_err()) {
      LogFidlFailure("Terminate", status, result.err());
    }
  
    // TODO(dje): Resume original ktracing? Need ability to get old value.
    // For now set the values to what we need: A later run might still need
    // the boot time records.
  
    fuchsia::tracing::kernel::ControllerSyncPtr ktrace;
    if (!OpenKtraceChannel(&ktrace)) {
      return;
    }
  
    RequestKtraceStop(ktrace);
  #if 0  // TODO(dje): See rewind comments in InitProcessTrace.
    RequestKtraceRewind(ktrace);
  #endif
    RequestKtraceStart(ktrace, kKtraceGroupMask);
  }
  
  }  // namespace insntrace
  