// Copyright 2018 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 "src/developer/debug/zxdb/client/process_symbol_data_provider.h"

#include <inttypes.h>
#include <lib/syslog/cpp/macros.h>

#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/memory_dump.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/symbols/dwarf_expr_eval.h"
#include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/symbol_context.h"
#include "src/lib/fxl/strings/string_printf.h"

namespace zxdb {

namespace {

Err ProcessDestroyedErr() { return Err("Process destroyed."); }

debug_ipc::Arch ArchForProcess(Process* process) {
  if (!process)
    return debug_ipc::Arch::kUnknown;
  return process->session()->arch();
}

}  // namespace

ProcessSymbolDataProvider::ProcessSymbolDataProvider(Process* process)
    : process_(process), arch_(ArchForProcess(process)) {}

ProcessSymbolDataProvider::~ProcessSymbolDataProvider() = default;

void ProcessSymbolDataProvider::Disown() { process_ = nullptr; }

debug_ipc::Arch ProcessSymbolDataProvider::GetArch() { return arch_; }

void ProcessSymbolDataProvider::GetMemoryAsync(uint64_t address, uint32_t size,
                                               GetMemoryCallback callback) {
  if (!process_) {
    debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(callback)]() mutable {
      cb(ProcessDestroyedErr(), std::vector<uint8_t>());
    });
    return;
  }

  // Mistakes may make extremely large memory requests which can OOM the system. Prevent those.
  if (size > 1024 * 1024) {
    debug_ipc::MessageLoop::Current()->PostTask(
        FROM_HERE, [address, size, cb = std::move(callback)]() mutable {
          cb(Err(fxl::StringPrintf("Memory request for %u bytes at 0x%" PRIx64 " is too large.",
                                   size, address)),
             std::vector<uint8_t>());
        });
    return;
  }

  process_->ReadMemory(
      address, size,
      [address, size, cb = std::move(callback)](const Err& err, MemoryDump dump) mutable {
        if (err.has_error()) {
          cb(err, std::vector<uint8_t>());
          return;
        }

        FX_DCHECK(size == 0 || dump.address() == address);
        FX_DCHECK(dump.size() == size);
        if (dump.blocks().size() == 1 || (dump.blocks().size() > 1 && !dump.blocks()[1].valid)) {
          // Common case: came back as one block OR it read until an invalid memory boundary and the
          // second block is invalid.
          //
          // In both these cases we can directly return the first data block. We don't have to check
          // the first block's valid flag since if it's not valid it will be empty, which is what
          // our API specifies.
          cb(Err(), std::move(dump.blocks()[0].data));
        } else {
          // The debug agent doesn't guarantee that a memory dump will exist in only one block even
          // if the memory is all valid. Flatten all contiguous valid regions to a single buffer.
          std::vector<uint8_t> flat;
          flat.reserve(dump.size());
          for (const auto block : dump.blocks()) {
            if (!block.valid)
              break;
            flat.insert(flat.end(), block.data.begin(), block.data.end());
          }
          cb(Err(), std::move(flat));
        }
      });
}

void ProcessSymbolDataProvider::WriteMemory(uint64_t address, std::vector<uint8_t> data,
                                            WriteCallback cb) {
  if (!process_) {
    debug_ipc::MessageLoop::Current()->PostTask(
        FROM_HERE, [cb = std::move(cb)]() mutable { cb(ProcessDestroyedErr()); });
    return;
  }
  process_->WriteMemory(address, std::move(data), std::move(cb));
}

std::optional<uint64_t> ProcessSymbolDataProvider::GetDebugAddressForContext(
    const SymbolContext& context) const {
  if (process_) {
    if (auto syms = process_->GetSymbols()) {
      if (auto lms = syms->GetModuleForAddress(context.load_address())) {
        return lms->debug_address();
      }
    }
  }

  return std::nullopt;
}

void ProcessSymbolDataProvider::GetTLSSegment(const SymbolContext& symbol_context,
                                              GetTLSSegmentCallback cb) {
  if (!process_) {
    return cb(Err("Thread-local storage requires a current process."));
  }

  auto syms = process_->GetSymbols();
  if (!syms) {
    return cb(Err("Could not load symbols for process when resolving TLS segment."));
  }

  auto lms = syms->GetModuleForAddress(symbol_context.load_address());
  if (!lms) {
    return cb(Err("Could not find current module when resolving TLS segment."));
  }

  process_->GetTLSHelpers([debug_address = lms->debug_address(), this_ref = RefPtrTo(this),
                           symbol_context,
                           cb = std::move(cb)](ErrOr<const Process::TLSHelpers*> helpers) mutable {
    if (helpers.has_error()) {
      return cb(helpers.err());
    }

    std::vector<uint8_t> program;
    program.reserve(helpers.value()->link_map_tls_modid.size() + helpers.value()->tlsbase.size() +
                    1);

    std::copy(helpers.value()->link_map_tls_modid.begin(),
              helpers.value()->link_map_tls_modid.end(), std::back_inserter(program));
    std::copy(helpers.value()->tlsbase.begin(), helpers.value()->tlsbase.end(),
              std::back_inserter(program));

    // This code manually creates a DwarfExprEval rather that use the AsyncDwarfExprEval (which
    // makes things a little simpler) because we want to get the exact stack value (this is called
    // in the context of another DwarfExprEval).
    auto dwarf_eval = std::make_shared<DwarfExprEval>();
    dwarf_eval->Push(static_cast<DwarfExprEval::StackEntry>(debug_address));
    dwarf_eval->Eval(this_ref, symbol_context, DwarfExpr(std::move(program)),
                     [dwarf_eval, cb = std::move(cb)](DwarfExprEval*, const Err& err) mutable {
                       // Prevent the DwarfExprEval from getting reentrantly deleted from within its
                       // own callback by posting a reference back to the message loop. This creates
                       // an owning reference to the DwarfExprEval to the message loop to force it
                       // to get deleted in the future when that's executed rather than from within
                       // this stack frame.
                       debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, [dwarf_eval]() {});

                       if (err.has_error()) {
                         return cb(err);
                       }

                       if (dwarf_eval->GetResultType() != DwarfExprEval::ResultType::kPointer) {
                         return cb(Err("TLS DWARF expression did not produce a pointer."));
                       }

                       cb(static_cast<uint64_t>(dwarf_eval->GetResult()));
                     });
  });
}

}  // namespace zxdb
