// Copyright 2017 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 <fcntl.h>
#include <inttypes.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <unistd.h>

#include <map>

#include "decoder.h"
#include "garnet/lib/debugger_utils/ktrace_reader.h"
#include "garnet/lib/debugger_utils/util.h"
#include "src/lib/files/path.h"
#include "src/lib/files/unique_fd.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "third_party/simple-pt/elf.h"
#include "third_party/simple-pt/symtab.h"

namespace intel_processor_trace {

// For passing data from ReadKtraceFile to ProcessKtraceRecord.
struct KtraceData {
  DecoderState* state;
};

int DecoderState::ProcessKtraceRecord(debugger_utils::KtraceRecord* rec, void* arg) {
  KtraceData* data = reinterpret_cast<KtraceData*>(arg);
  DecoderState* state = data->state;

  // We're interested in TAG_IPT_* records.

  switch (rec->hdr.tag) {
    case TAG_IPT_START: {
      // N.B. There may be many IPT_START/STOP records present.
      // We only want the last one.
      const ktrace_rec_32b* r = &rec->r_32B;
      uint64_t kernel_cr3 = r->c | ((uint64_t)r->d << 32);
      state->set_nom_freq(r->a);
      state->set_kernel_cr3(kernel_cr3);
      FX_LOGS(INFO) << fxl::StringPrintf("Ktrace IPT start, ts %" PRIu64
                                         ", nom_freq %u, kernel cr3 0x%" PRIx64,
                                         rec->hdr.ts, r->a, kernel_cr3);
      break;
    }
    case TAG_IPT_CPU_INFO: {
      FX_LOGS(INFO) << "Ktrace IPT start, ts " << rec->hdr.ts;
      const ktrace_rec_32b* r = &rec->r_32B;
      state->set_family(r->b);
      state->set_model(r->c);
      state->set_stepping(r->d);
      FX_LOGS(INFO) << fxl::StringPrintf("Ktrace IPT CPU INFO, ts %" PRIu64
                                         ", family %u, model %u, stepping %u",
                                         rec->hdr.ts, r->b, r->c, r->d);
      break;
    }
    case TAG_IPT_STOP:
      FX_LOGS(INFO) << "Ktrace IPT stop, ts " << rec->hdr.ts;
      break;
    case TAG_IPT_PROCESS_CREATE: {
      const ktrace_rec_32b* r = &rec->r_32B;
      uint64_t pid = r->a | ((uint64_t)r->b << 32);
      uint64_t cr3 = r->c | ((uint64_t)r->d << 32);
      FX_LOGS(INFO) << fxl::StringPrintf("Ktrace process create, ts %" PRIu64 ", pid %" PRIu64
                                         ", cr3 0x%" PRIx64,
                                         rec->hdr.ts, pid, cr3);
      if (!state->AddProcess(pid, cr3, rec->hdr.ts)) {
        FX_LOGS(ERROR) << "Error adding process: " << pid;
      }
      break;
    }
    case TAG_PROC_EXIT: {
      const ktrace_rec_32b* r = &rec->r_32B;
      uint64_t pid = r->a | ((uint64_t)r->b << 32);
      FX_LOGS(INFO) << fxl::StringPrintf("Ktrace process exit, ts %" PRIu64 ", pid %" PRIu64,
                                         rec->hdr.ts, pid);
      // N.B. We don't remove the process from any table here. This pass is run
      // before we scan the actual PT dump.
      if (!state->MarkProcessExited(pid, rec->hdr.ts)) {
        FX_LOGS(ERROR) << "Error marking process exit: " << pid;
      }
      break;
    }
  }

  return 0;
}

bool DecoderState::ReadKtraceFile(const std::string& file) {
  FX_LOGS(INFO) << "Loading ktrace data from " << file;

  fbl::unique_fd fd(open(file.c_str(), O_RDONLY));
  if (!fd.is_valid()) {
    FX_LOGS(ERROR) << "error opening ktrace file"
                   << ", " << debugger_utils::ErrnoString(errno);
    return false;
  }

  KtraceData data = {this};
  int rc = debugger_utils::KtraceReadFile(fd.get(), ProcessKtraceRecord, &data);
  if (rc != 0) {
    FX_LOGS(ERROR) << fxl::StringPrintf("Error %d reading ktrace file", rc);
    return false;
  }

  return true;
}

bool DecoderState::ReadMapFile(const std::string& file) {
  // TODO(dje): This is temporary until we can properly trace ld.so activity.
  // MG-519
  return load_maps_.ReadLogListenerOutput(file);
}

bool DecoderState::ReadIdsFile(const std::string& file) { return build_ids_.ReadIdsFile(file); }

bool DecoderState::ReadPtListFile(const std::string& file) {
  FX_LOGS(INFO) << "Loading pt file list from " << file;

  FILE* f = fopen(file.c_str(), "r");
  if (!f) {
    FX_LOGS(ERROR) << "error opening pt file list file"
                   << ", " << debugger_utils::ErrnoString(errno);
    return false;
  }

  char* line = nullptr;
  size_t linelen = 0;
  int lineno = 1;

  auto cleanup = fit::defer([line, f]() {
    free(line);
    fclose(f);
  });

  for (; getline(&line, &linelen, f) > 0; ++lineno) {
    size_t n = strlen(line);
    if (n > 0 && line[n - 1] == '\n')
      line[n - 1] = '\0';
    FX_VLOGS(2) << fxl::StringPrintf("read %d: %s", lineno, line);

#define MAX_LINE_LEN 1024
    if (linelen > MAX_LINE_LEN) {
      FX_VLOGS(2) << fxl::StringPrintf("%d: ignoring: %s", lineno, line);
      continue;
    }

    if (!strcmp(line, "\n"))
      continue;
    if (line[0] == '#')
      continue;

    unsigned long long id;
    char path[linelen];
    if (sscanf(line, "%llu %s", &id, path) == 2) {
      AddPtFile(files::GetDirectoryName(file), id, path);
    } else {
      FX_VLOGS(2) << fxl::StringPrintf("%d: ignoring: %s", lineno, line);
    }
  }

  return true;
}

void DecoderState::AddSymtab(std::unique_ptr<SymbolTable> symtab) {
  symtabs_.push_back(std::move(symtab));
}

bool DecoderState::ReadElf(const std::string& file_name, uint64_t base, uint64_t cr3,
                           uint64_t file_off, uint64_t map_len) {
  FX_DCHECK(image_);

  std::unique_ptr<SymbolTable> symtab;
  std::unique_ptr<SymbolTable> dynsym;

  if (!simple_pt::ReadElf(file_name.c_str(), image_, base, cr3, file_off, map_len, &symtab,
                          &dynsym))
    return false;

  if (symtab)
    AddSymtab(std::move(symtab));
  if (dynsym)
    AddSymtab(std::move(dynsym));
  return true;
}

bool DecoderState::ReadKernelElf(const std::string& file_name, uint64_t cr3) {
  FX_DCHECK(image_);

  std::unique_ptr<SymbolTable> symtab;
  std::unique_ptr<SymbolTable> dynsym;

  if (!simple_pt::ReadNonPicElf(file_name.c_str(), image_, cr3, true, &symtab, &dynsym))
    return false;

  if (symtab)
    AddSymtab(std::move(symtab));
  if (dynsym)
    FX_LOGS(WARNING) << "Kernel has SHT_DYNSYM symtab?";
  return true;
}

}  // namespace intel_processor_trace
