//===-- ProcessKDP.cpp ------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
#include <errno.h>
#include <stdlib.h>

// C++ Includes
#include <mutex>

// Other libraries and framework includes
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/State.h"
#include "lldb/Utility/UUID.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/Symbols.h"
#include "lldb/Host/ThreadLauncher.h"
#include "lldb/Host/common/TCPSocket.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandObject.h"
#include "lldb/Interpreter/CommandObjectMultiword.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionGroupString.h"
#include "lldb/Interpreter/OptionGroupUInt64.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/StringExtractor.h"

#include "llvm/Support/Threading.h"

#define USEC_PER_SEC 1000000

// Project includes
#include "Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.h"
#include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h"
#include "ProcessKDP.h"
#include "ProcessKDPLog.h"
#include "ThreadKDP.h"

using namespace lldb;
using namespace lldb_private;

namespace {

static PropertyDefinition g_properties[] = {
    {"packet-timeout", OptionValue::eTypeUInt64, true, 5, NULL, NULL,
     "Specify the default packet timeout in seconds."},
    {NULL, OptionValue::eTypeInvalid, false, 0, NULL, NULL, NULL}};

enum { ePropertyPacketTimeout };

class PluginProperties : public Properties {
public:
  static ConstString GetSettingName() {
    return ProcessKDP::GetPluginNameStatic();
  }

  PluginProperties() : Properties() {
    m_collection_sp.reset(new OptionValueProperties(GetSettingName()));
    m_collection_sp->Initialize(g_properties);
  }

  virtual ~PluginProperties() {}

  uint64_t GetPacketTimeout() {
    const uint32_t idx = ePropertyPacketTimeout;
    return m_collection_sp->GetPropertyAtIndexAsUInt64(
        NULL, idx, g_properties[idx].default_uint_value);
  }
};

typedef std::shared_ptr<PluginProperties> ProcessKDPPropertiesSP;

static const ProcessKDPPropertiesSP &GetGlobalPluginProperties() {
  static ProcessKDPPropertiesSP g_settings_sp;
  if (!g_settings_sp)
    g_settings_sp.reset(new PluginProperties());
  return g_settings_sp;
}

} // anonymous namespace end

static const lldb::tid_t g_kernel_tid = 1;

ConstString ProcessKDP::GetPluginNameStatic() {
  static ConstString g_name("kdp-remote");
  return g_name;
}

const char *ProcessKDP::GetPluginDescriptionStatic() {
  return "KDP Remote protocol based debugging plug-in for darwin kernel "
         "debugging.";
}

void ProcessKDP::Terminate() {
  PluginManager::UnregisterPlugin(ProcessKDP::CreateInstance);
}

lldb::ProcessSP ProcessKDP::CreateInstance(TargetSP target_sp,
                                           ListenerSP listener_sp,
                                           const FileSpec *crash_file_path) {
  lldb::ProcessSP process_sp;
  if (crash_file_path == NULL)
    process_sp.reset(new ProcessKDP(target_sp, listener_sp));
  return process_sp;
}

bool ProcessKDP::CanDebug(TargetSP target_sp, bool plugin_specified_by_name) {
  if (plugin_specified_by_name)
    return true;

  // For now we are just making sure the file exists for a given module
  Module *exe_module = target_sp->GetExecutableModulePointer();
  if (exe_module) {
    const llvm::Triple &triple_ref = target_sp->GetArchitecture().GetTriple();
    switch (triple_ref.getOS()) {
    case llvm::Triple::Darwin: // Should use "macosx" for desktop and "ios" for
                               // iOS, but accept darwin just in case
    case llvm::Triple::MacOSX: // For desktop targets
    case llvm::Triple::IOS:    // For arm targets
    case llvm::Triple::TvOS:
    case llvm::Triple::WatchOS:
      if (triple_ref.getVendor() == llvm::Triple::Apple) {
        ObjectFile *exe_objfile = exe_module->GetObjectFile();
        if (exe_objfile->GetType() == ObjectFile::eTypeExecutable &&
            exe_objfile->GetStrata() == ObjectFile::eStrataKernel)
          return true;
      }
      break;

    default:
      break;
    }
  }
  return false;
}

//----------------------------------------------------------------------
// ProcessKDP constructor
//----------------------------------------------------------------------
ProcessKDP::ProcessKDP(TargetSP target_sp, ListenerSP listener_sp)
    : Process(target_sp, listener_sp),
      m_comm("lldb.process.kdp-remote.communication"),
      m_async_broadcaster(NULL, "lldb.process.kdp-remote.async-broadcaster"),
      m_dyld_plugin_name(), m_kernel_load_addr(LLDB_INVALID_ADDRESS),
      m_command_sp(), m_kernel_thread_wp() {
  m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit,
                                   "async thread should exit");
  m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue,
                                   "async thread continue");
  const uint64_t timeout_seconds =
      GetGlobalPluginProperties()->GetPacketTimeout();
  if (timeout_seconds > 0)
    m_comm.SetPacketTimeout(std::chrono::seconds(timeout_seconds));
}

//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
ProcessKDP::~ProcessKDP() {
  Clear();
  // We need to call finalize on the process before destroying ourselves
  // to make sure all of the broadcaster cleanup goes as planned. If we
  // destruct this class, then Process::~Process() might have problems
  // trying to fully destroy the broadcaster.
  Finalize();
}

//----------------------------------------------------------------------
// PluginInterface
//----------------------------------------------------------------------
lldb_private::ConstString ProcessKDP::GetPluginName() {
  return GetPluginNameStatic();
}

uint32_t ProcessKDP::GetPluginVersion() { return 1; }

Status ProcessKDP::WillLaunch(Module *module) {
  Status error;
  error.SetErrorString("launching not supported in kdp-remote plug-in");
  return error;
}

Status ProcessKDP::WillAttachToProcessWithID(lldb::pid_t pid) {
  Status error;
  error.SetErrorString(
      "attaching to a by process ID not supported in kdp-remote plug-in");
  return error;
}

Status ProcessKDP::WillAttachToProcessWithName(const char *process_name,
                                               bool wait_for_launch) {
  Status error;
  error.SetErrorString(
      "attaching to a by process name not supported in kdp-remote plug-in");
  return error;
}

bool ProcessKDP::GetHostArchitecture(ArchSpec &arch) {
  uint32_t cpu = m_comm.GetCPUType();
  if (cpu) {
    uint32_t sub = m_comm.GetCPUSubtype();
    arch.SetArchitecture(eArchTypeMachO, cpu, sub);
    // Leave architecture vendor as unspecified unknown
    arch.GetTriple().setVendor(llvm::Triple::UnknownVendor);
    arch.GetTriple().setVendorName(llvm::StringRef());
    return true;
  }
  arch.Clear();
  return false;
}

Status ProcessKDP::DoConnectRemote(Stream *strm, llvm::StringRef remote_url) {
  Status error;

  // Don't let any JIT happen when doing KDP as we can't allocate
  // memory and we don't want to be mucking with threads that might
  // already be handling exceptions
  SetCanJIT(false);

  if (remote_url.empty()) {
    error.SetErrorStringWithFormat("empty connection URL");
    return error;
  }

  std::unique_ptr<ConnectionFileDescriptor> conn_ap(
      new ConnectionFileDescriptor());
  if (conn_ap.get()) {
    // Only try once for now.
    // TODO: check if we should be retrying?
    const uint32_t max_retry_count = 1;
    for (uint32_t retry_count = 0; retry_count < max_retry_count;
         ++retry_count) {
      if (conn_ap->Connect(remote_url, &error) == eConnectionStatusSuccess)
        break;
      usleep(100000);
    }
  }

  if (conn_ap->IsConnected()) {
    const TCPSocket &socket =
        static_cast<const TCPSocket &>(*conn_ap->GetReadObject());
    const uint16_t reply_port = socket.GetLocalPortNumber();

    if (reply_port != 0) {
      m_comm.SetConnection(conn_ap.release());

      if (m_comm.SendRequestReattach(reply_port)) {
        if (m_comm.SendRequestConnect(reply_port, reply_port,
                                      "Greetings from LLDB...")) {
          m_comm.GetVersion();

          Target &target = GetTarget();
          ArchSpec kernel_arch;
          // The host architecture
          GetHostArchitecture(kernel_arch);
          ArchSpec target_arch = target.GetArchitecture();
          // Merge in any unspecified stuff into the target architecture in
          // case the target arch isn't set at all or incompletely.
          target_arch.MergeFrom(kernel_arch);
          target.SetArchitecture(target_arch);

          /* Get the kernel's UUID and load address via KDP_KERNELVERSION
           * packet.  */
          /* An EFI kdp session has neither UUID nor load address. */

          UUID kernel_uuid = m_comm.GetUUID();
          addr_t kernel_load_addr = m_comm.GetLoadAddress();

          if (m_comm.RemoteIsEFI()) {
            // Select an invalid plugin name for the dynamic loader so one
            // doesn't get used
            // since EFI does its own manual loading via python scripting
            static ConstString g_none_dynamic_loader("none");
            m_dyld_plugin_name = g_none_dynamic_loader;

            if (kernel_uuid.IsValid()) {
              // If EFI passed in a UUID= try to lookup UUID
              // The slide will not be provided. But the UUID
              // lookup will be used to launch EFI debug scripts
              // from the dSYM, that can load all of the symbols.
              ModuleSpec module_spec;
              module_spec.GetUUID() = kernel_uuid;
              module_spec.GetArchitecture() = target.GetArchitecture();

              // Lookup UUID locally, before attempting dsymForUUID like action
              module_spec.GetSymbolFileSpec() =
                  Symbols::LocateExecutableSymbolFile(module_spec);
              if (module_spec.GetSymbolFileSpec()) {
                ModuleSpec executable_module_spec =
                    Symbols::LocateExecutableObjectFile(module_spec);
                if (executable_module_spec.GetFileSpec().Exists()) {
                  module_spec.GetFileSpec() =
                      executable_module_spec.GetFileSpec();
                }
              }
              if (!module_spec.GetSymbolFileSpec() ||
                  !module_spec.GetSymbolFileSpec())
                Symbols::DownloadObjectAndSymbolFile(module_spec, true);

              if (module_spec.GetFileSpec().Exists()) {
                ModuleSP module_sp(new Module(module_spec));
                if (module_sp.get() && module_sp->GetObjectFile()) {
                  // Get the current target executable
                  ModuleSP exe_module_sp(target.GetExecutableModule());

                  // Make sure you don't already have the right module loaded
                  // and they will be uniqued
                  if (exe_module_sp.get() != module_sp.get())
                    target.SetExecutableModule(module_sp, false);
                }
              }
            }
          } else if (m_comm.RemoteIsDarwinKernel()) {
            m_dyld_plugin_name =
                DynamicLoaderDarwinKernel::GetPluginNameStatic();
            if (kernel_load_addr != LLDB_INVALID_ADDRESS) {
              m_kernel_load_addr = kernel_load_addr;
            }
          }

          // Set the thread ID
          UpdateThreadListIfNeeded();
          SetID(1);
          GetThreadList();
          SetPrivateState(eStateStopped);
          StreamSP async_strm_sp(target.GetDebugger().GetAsyncOutputStream());
          if (async_strm_sp) {
            const char *cstr;
            if ((cstr = m_comm.GetKernelVersion()) != NULL) {
              async_strm_sp->Printf("Version: %s\n", cstr);
              async_strm_sp->Flush();
            }
            //                      if ((cstr = m_comm.GetImagePath ()) != NULL)
            //                      {
            //                          async_strm_sp->Printf ("Image Path:
            //                          %s\n", cstr);
            //                          async_strm_sp->Flush();
            //                      }
          }
        } else {
          error.SetErrorString("KDP_REATTACH failed");
        }
      } else {
        error.SetErrorString("KDP_REATTACH failed");
      }
    } else {
      error.SetErrorString("invalid reply port from UDP connection");
    }
  } else {
    if (error.Success())
      error.SetErrorStringWithFormat("failed to connect to '%s'",
                                     remote_url.str().c_str());
  }
  if (error.Fail())
    m_comm.Disconnect();

  return error;
}

//----------------------------------------------------------------------
// Process Control
//----------------------------------------------------------------------
Status ProcessKDP::DoLaunch(Module *exe_module,
                            ProcessLaunchInfo &launch_info) {
  Status error;
  error.SetErrorString("launching not supported in kdp-remote plug-in");
  return error;
}

Status
ProcessKDP::DoAttachToProcessWithID(lldb::pid_t attach_pid,
                                    const ProcessAttachInfo &attach_info) {
  Status error;
  error.SetErrorString(
      "attach to process by ID is not suppported in kdp remote debugging");
  return error;
}

Status
ProcessKDP::DoAttachToProcessWithName(const char *process_name,
                                      const ProcessAttachInfo &attach_info) {
  Status error;
  error.SetErrorString(
      "attach to process by name is not suppported in kdp remote debugging");
  return error;
}

void ProcessKDP::DidAttach(ArchSpec &process_arch) {
  Process::DidAttach(process_arch);

  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));
  if (log)
    log->Printf("ProcessKDP::DidAttach()");
  if (GetID() != LLDB_INVALID_PROCESS_ID) {
    GetHostArchitecture(process_arch);
  }
}

addr_t ProcessKDP::GetImageInfoAddress() { return m_kernel_load_addr; }

lldb_private::DynamicLoader *ProcessKDP::GetDynamicLoader() {
  if (m_dyld_ap.get() == NULL)
    m_dyld_ap.reset(DynamicLoader::FindPlugin(
        this,
        m_dyld_plugin_name.IsEmpty() ? NULL : m_dyld_plugin_name.GetCString()));
  return m_dyld_ap.get();
}

Status ProcessKDP::WillResume() { return Status(); }

Status ProcessKDP::DoResume() {
  Status error;
  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));
  // Only start the async thread if we try to do any process control
  if (!m_async_thread.IsJoinable())
    StartAsyncThread();

  bool resume = false;

  // With KDP there is only one thread we can tell what to do
  ThreadSP kernel_thread_sp(m_thread_list.FindThreadByProtocolID(g_kernel_tid));

  if (kernel_thread_sp) {
    const StateType thread_resume_state =
        kernel_thread_sp->GetTemporaryResumeState();

    if (log)
      log->Printf("ProcessKDP::DoResume() thread_resume_state = %s",
                  StateAsCString(thread_resume_state));
    switch (thread_resume_state) {
    case eStateSuspended:
      // Nothing to do here when a thread will stay suspended
      // we just leave the CPU mask bit set to zero for the thread
      if (log)
        log->Printf("ProcessKDP::DoResume() = suspended???");
      break;

    case eStateStepping: {
      lldb::RegisterContextSP reg_ctx_sp(
          kernel_thread_sp->GetRegisterContext());

      if (reg_ctx_sp) {
        if (log)
          log->Printf(
              "ProcessKDP::DoResume () reg_ctx_sp->HardwareSingleStep (true);");
        reg_ctx_sp->HardwareSingleStep(true);
        resume = true;
      } else {
        error.SetErrorStringWithFormat(
            "KDP thread 0x%llx has no register context",
            kernel_thread_sp->GetID());
      }
    } break;

    case eStateRunning: {
      lldb::RegisterContextSP reg_ctx_sp(
          kernel_thread_sp->GetRegisterContext());

      if (reg_ctx_sp) {
        if (log)
          log->Printf("ProcessKDP::DoResume () reg_ctx_sp->HardwareSingleStep "
                      "(false);");
        reg_ctx_sp->HardwareSingleStep(false);
        resume = true;
      } else {
        error.SetErrorStringWithFormat(
            "KDP thread 0x%llx has no register context",
            kernel_thread_sp->GetID());
      }
    } break;

    default:
      // The only valid thread resume states are listed above
      llvm_unreachable("invalid thread resume state");
    }
  }

  if (resume) {
    if (log)
      log->Printf("ProcessKDP::DoResume () sending resume");

    if (m_comm.SendRequestResume()) {
      m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
      SetPrivateState(eStateRunning);
    } else
      error.SetErrorString("KDP resume failed");
  } else {
    error.SetErrorString("kernel thread is suspended");
  }

  return error;
}

lldb::ThreadSP ProcessKDP::GetKernelThread() {
  // KDP only tells us about one thread/core. Any other threads will usually
  // be the ones that are read from memory by the OS plug-ins.

  ThreadSP thread_sp(m_kernel_thread_wp.lock());
  if (!thread_sp) {
    thread_sp.reset(new ThreadKDP(*this, g_kernel_tid));
    m_kernel_thread_wp = thread_sp;
  }
  return thread_sp;
}

bool ProcessKDP::UpdateThreadList(ThreadList &old_thread_list,
                                  ThreadList &new_thread_list) {
  // locker will keep a mutex locked until it goes out of scope
  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_THREAD));
  LLDB_LOGV(log, "pid = {0}", GetID());

  // Even though there is a CPU mask, it doesn't mean we can see each CPU
  // individually, there is really only one. Lets call this thread 1.
  ThreadSP thread_sp(
      old_thread_list.FindThreadByProtocolID(g_kernel_tid, false));
  if (!thread_sp)
    thread_sp = GetKernelThread();
  new_thread_list.AddThread(thread_sp);

  return new_thread_list.GetSize(false) > 0;
}

void ProcessKDP::RefreshStateAfterStop() {
  // Let all threads recover from stopping and do any clean up based
  // on the previous thread state (if any).
  m_thread_list.RefreshStateAfterStop();
}

Status ProcessKDP::DoHalt(bool &caused_stop) {
  Status error;

  if (m_comm.IsRunning()) {
    if (m_destroy_in_process) {
      // If we are attemping to destroy, we need to not return an error to
      // Halt or DoDestroy won't get called.
      // We are also currently running, so send a process stopped event
      SetPrivateState(eStateStopped);
    } else {
      error.SetErrorString("KDP cannot interrupt a running kernel");
    }
  }
  return error;
}

Status ProcessKDP::DoDetach(bool keep_stopped) {
  Status error;
  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));
  if (log)
    log->Printf("ProcessKDP::DoDetach(keep_stopped = %i)", keep_stopped);

  if (m_comm.IsRunning()) {
    // We are running and we can't interrupt a running kernel, so we need
    // to just close the connection to the kernel and hope for the best
  } else {
    // If we are going to keep the target stopped, then don't send the
    // disconnect message.
    if (!keep_stopped && m_comm.IsConnected()) {
      const bool success = m_comm.SendRequestDisconnect();
      if (log) {
        if (success)
          log->PutCString(
              "ProcessKDP::DoDetach() detach packet sent successfully");
        else
          log->PutCString(
              "ProcessKDP::DoDetach() connection channel shutdown failed");
      }
      m_comm.Disconnect();
    }
  }
  StopAsyncThread();
  m_comm.Clear();

  SetPrivateState(eStateDetached);
  ResumePrivateStateThread();

  // KillDebugserverProcess ();
  return error;
}

Status ProcessKDP::DoDestroy() {
  // For KDP there really is no difference between destroy and detach
  bool keep_stopped = false;
  return DoDetach(keep_stopped);
}

//------------------------------------------------------------------
// Process Queries
//------------------------------------------------------------------

bool ProcessKDP::IsAlive() {
  return m_comm.IsConnected() && Process::IsAlive();
}

//------------------------------------------------------------------
// Process Memory
//------------------------------------------------------------------
size_t ProcessKDP::DoReadMemory(addr_t addr, void *buf, size_t size,
                                Status &error) {
  uint8_t *data_buffer = (uint8_t *)buf;
  if (m_comm.IsConnected()) {
    const size_t max_read_size = 512;
    size_t total_bytes_read = 0;

    // Read the requested amount of memory in 512 byte chunks
    while (total_bytes_read < size) {
      size_t bytes_to_read_this_request = size - total_bytes_read;
      if (bytes_to_read_this_request > max_read_size) {
        bytes_to_read_this_request = max_read_size;
      }
      size_t bytes_read = m_comm.SendRequestReadMemory(
          addr + total_bytes_read, data_buffer + total_bytes_read,
          bytes_to_read_this_request, error);
      total_bytes_read += bytes_read;
      if (error.Fail() || bytes_read == 0) {
        return total_bytes_read;
      }
    }

    return total_bytes_read;
  }
  error.SetErrorString("not connected");
  return 0;
}

size_t ProcessKDP::DoWriteMemory(addr_t addr, const void *buf, size_t size,
                                 Status &error) {
  if (m_comm.IsConnected())
    return m_comm.SendRequestWriteMemory(addr, buf, size, error);
  error.SetErrorString("not connected");
  return 0;
}

lldb::addr_t ProcessKDP::DoAllocateMemory(size_t size, uint32_t permissions,
                                          Status &error) {
  error.SetErrorString(
      "memory allocation not suppported in kdp remote debugging");
  return LLDB_INVALID_ADDRESS;
}

Status ProcessKDP::DoDeallocateMemory(lldb::addr_t addr) {
  Status error;
  error.SetErrorString(
      "memory deallocation not suppported in kdp remote debugging");
  return error;
}

Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) {
  if (m_comm.LocalBreakpointsAreSupported()) {
    Status error;
    if (!bp_site->IsEnabled()) {
      if (m_comm.SendRequestBreakpoint(true, bp_site->GetLoadAddress())) {
        bp_site->SetEnabled(true);
        bp_site->SetType(BreakpointSite::eExternal);
      } else {
        error.SetErrorString("KDP set breakpoint failed");
      }
    }
    return error;
  }
  return EnableSoftwareBreakpoint(bp_site);
}

Status ProcessKDP::DisableBreakpointSite(BreakpointSite *bp_site) {
  if (m_comm.LocalBreakpointsAreSupported()) {
    Status error;
    if (bp_site->IsEnabled()) {
      BreakpointSite::Type bp_type = bp_site->GetType();
      if (bp_type == BreakpointSite::eExternal) {
        if (m_destroy_in_process && m_comm.IsRunning()) {
          // We are trying to destroy our connection and we are running
          bp_site->SetEnabled(false);
        } else {
          if (m_comm.SendRequestBreakpoint(false, bp_site->GetLoadAddress()))
            bp_site->SetEnabled(false);
          else
            error.SetErrorString("KDP remove breakpoint failed");
        }
      } else {
        error = DisableSoftwareBreakpoint(bp_site);
      }
    }
    return error;
  }
  return DisableSoftwareBreakpoint(bp_site);
}

Status ProcessKDP::EnableWatchpoint(Watchpoint *wp, bool notify) {
  Status error;
  error.SetErrorString(
      "watchpoints are not suppported in kdp remote debugging");
  return error;
}

Status ProcessKDP::DisableWatchpoint(Watchpoint *wp, bool notify) {
  Status error;
  error.SetErrorString(
      "watchpoints are not suppported in kdp remote debugging");
  return error;
}

void ProcessKDP::Clear() { m_thread_list.Clear(); }

Status ProcessKDP::DoSignal(int signo) {
  Status error;
  error.SetErrorString(
      "sending signals is not suppported in kdp remote debugging");
  return error;
}

void ProcessKDP::Initialize() {
  static llvm::once_flag g_once_flag;

  llvm::call_once(g_once_flag, []() {
    PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                  GetPluginDescriptionStatic(), CreateInstance,
                                  DebuggerInitialize);

    ProcessKDPLog::Initialize();
  });
}

void ProcessKDP::DebuggerInitialize(lldb_private::Debugger &debugger) {
  if (!PluginManager::GetSettingForProcessPlugin(
          debugger, PluginProperties::GetSettingName())) {
    const bool is_global_setting = true;
    PluginManager::CreateSettingForProcessPlugin(
        debugger, GetGlobalPluginProperties()->GetValueProperties(),
        ConstString("Properties for the kdp-remote process plug-in."),
        is_global_setting);
  }
}

bool ProcessKDP::StartAsyncThread() {
  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));

  if (log)
    log->Printf("ProcessKDP::StartAsyncThread ()");

  if (m_async_thread.IsJoinable())
    return true;

  m_async_thread = ThreadLauncher::LaunchThread(
      "<lldb.process.kdp-remote.async>", ProcessKDP::AsyncThread, this, NULL);
  return m_async_thread.IsJoinable();
}

void ProcessKDP::StopAsyncThread() {
  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));

  if (log)
    log->Printf("ProcessKDP::StopAsyncThread ()");

  m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit);

  // Stop the stdio thread
  if (m_async_thread.IsJoinable())
    m_async_thread.Join(nullptr);
}

void *ProcessKDP::AsyncThread(void *arg) {
  ProcessKDP *process = (ProcessKDP *)arg;

  const lldb::pid_t pid = process->GetID();

  Log *log(ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));
  if (log)
    log->Printf("ProcessKDP::AsyncThread (arg = %p, pid = %" PRIu64
                ") thread starting...",
                arg, pid);

  ListenerSP listener_sp(Listener::MakeListener("ProcessKDP::AsyncThread"));
  EventSP event_sp;
  const uint32_t desired_event_mask =
      eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit;

  if (listener_sp->StartListeningForEvents(&process->m_async_broadcaster,
                                           desired_event_mask) ==
      desired_event_mask) {
    bool done = false;
    while (!done) {
      if (log)
        log->Printf("ProcessKDP::AsyncThread (pid = %" PRIu64
                    ") listener.WaitForEvent (NULL, event_sp)...",
                    pid);
      if (listener_sp->GetEvent(event_sp, llvm::None)) {
        uint32_t event_type = event_sp->GetType();
        if (log)
          log->Printf("ProcessKDP::AsyncThread (pid = %" PRIu64
                      ") Got an event of type: %d...",
                      pid, event_type);

        // When we are running, poll for 1 second to try and get an exception
        // to indicate the process has stopped. If we don't get one, check to
        // make sure no one asked us to exit
        bool is_running = false;
        DataExtractor exc_reply_packet;
        do {
          switch (event_type) {
          case eBroadcastBitAsyncContinue: {
            is_running = true;
            if (process->m_comm.WaitForPacketWithTimeoutMicroSeconds(
                    exc_reply_packet, 1 * USEC_PER_SEC)) {
              ThreadSP thread_sp(process->GetKernelThread());
              if (thread_sp) {
                lldb::RegisterContextSP reg_ctx_sp(
                    thread_sp->GetRegisterContext());
                if (reg_ctx_sp)
                  reg_ctx_sp->InvalidateAllRegisters();
                static_cast<ThreadKDP *>(thread_sp.get())
                    ->SetStopInfoFrom_KDP_EXCEPTION(exc_reply_packet);
              }

              // TODO: parse the stop reply packet
              is_running = false;
              process->SetPrivateState(eStateStopped);
            } else {
              // Check to see if we are supposed to exit. There is no way to
              // interrupt a running kernel, so all we can do is wait for an
              // exception or detach...
              if (listener_sp->GetEvent(event_sp,
                                        std::chrono::microseconds(0))) {
                // We got an event, go through the loop again
                event_type = event_sp->GetType();
              }
            }
          } break;

          case eBroadcastBitAsyncThreadShouldExit:
            if (log)
              log->Printf("ProcessKDP::AsyncThread (pid = %" PRIu64
                          ") got eBroadcastBitAsyncThreadShouldExit...",
                          pid);
            done = true;
            is_running = false;
            break;

          default:
            if (log)
              log->Printf("ProcessKDP::AsyncThread (pid = %" PRIu64
                          ") got unknown event 0x%8.8x",
                          pid, event_type);
            done = true;
            is_running = false;
            break;
          }
        } while (is_running);
      } else {
        if (log)
          log->Printf("ProcessKDP::AsyncThread (pid = %" PRIu64
                      ") listener.WaitForEvent (NULL, event_sp) => false",
                      pid);
        done = true;
      }
    }
  }

  if (log)
    log->Printf("ProcessKDP::AsyncThread (arg = %p, pid = %" PRIu64
                ") thread exiting...",
                arg, pid);

  process->m_async_thread.Reset();
  return NULL;
}

class CommandObjectProcessKDPPacketSend : public CommandObjectParsed {
private:
  OptionGroupOptions m_option_group;
  OptionGroupUInt64 m_command_byte;
  OptionGroupString m_packet_data;

  virtual Options *GetOptions() { return &m_option_group; }

public:
  CommandObjectProcessKDPPacketSend(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "process plugin packet send",
                            "Send a custom packet through the KDP protocol by "
                            "specifying the command byte and the packet "
                            "payload data. A packet will be sent with a "
                            "correct header and payload, and the raw result "
                            "bytes will be displayed as a string value. ",
                            NULL),
        m_option_group(),
        m_command_byte(LLDB_OPT_SET_1, true, "command", 'c', 0, eArgTypeNone,
                       "Specify the command byte to use when sending the KDP "
                       "request packet.",
                       0),
        m_packet_data(LLDB_OPT_SET_1, false, "payload", 'p', 0, eArgTypeNone,
                      "Specify packet payload bytes as a hex ASCII string with "
                      "no spaces or hex prefixes.",
                      NULL) {
    m_option_group.Append(&m_command_byte, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
    m_option_group.Append(&m_packet_data, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
    m_option_group.Finalize();
  }

  ~CommandObjectProcessKDPPacketSend() {}

  bool DoExecute(Args &command, CommandReturnObject &result) {
    const size_t argc = command.GetArgumentCount();
    if (argc == 0) {
      if (!m_command_byte.GetOptionValue().OptionWasSet()) {
        result.AppendError(
            "the --command option must be set to a valid command byte");
        result.SetStatus(eReturnStatusFailed);
      } else {
        const uint64_t command_byte =
            m_command_byte.GetOptionValue().GetUInt64Value(0);
        if (command_byte > 0 && command_byte <= UINT8_MAX) {
          ProcessKDP *process =
              (ProcessKDP *)m_interpreter.GetExecutionContext().GetProcessPtr();
          if (process) {
            const StateType state = process->GetState();

            if (StateIsStoppedState(state, true)) {
              std::vector<uint8_t> payload_bytes;
              const char *ascii_hex_bytes_cstr =
                  m_packet_data.GetOptionValue().GetCurrentValue();
              if (ascii_hex_bytes_cstr && ascii_hex_bytes_cstr[0]) {
                StringExtractor extractor(ascii_hex_bytes_cstr);
                const size_t ascii_hex_bytes_cstr_len =
                    extractor.GetStringRef().size();
                if (ascii_hex_bytes_cstr_len & 1) {
                  result.AppendErrorWithFormat("payload data must contain an "
                                               "even number of ASCII hex "
                                               "characters: '%s'",
                                               ascii_hex_bytes_cstr);
                  result.SetStatus(eReturnStatusFailed);
                  return false;
                }
                payload_bytes.resize(ascii_hex_bytes_cstr_len / 2);
                if (extractor.GetHexBytes(payload_bytes, '\xdd') !=
                    payload_bytes.size()) {
                  result.AppendErrorWithFormat("payload data must only contain "
                                               "ASCII hex characters (no "
                                               "spaces or hex prefixes): '%s'",
                                               ascii_hex_bytes_cstr);
                  result.SetStatus(eReturnStatusFailed);
                  return false;
                }
              }
              Status error;
              DataExtractor reply;
              process->GetCommunication().SendRawRequest(
                  command_byte,
                  payload_bytes.empty() ? NULL : payload_bytes.data(),
                  payload_bytes.size(), reply, error);

              if (error.Success()) {
                // Copy the binary bytes into a hex ASCII string for the result
                StreamString packet;
                packet.PutBytesAsRawHex8(
                    reply.GetDataStart(), reply.GetByteSize(),
                    endian::InlHostByteOrder(), endian::InlHostByteOrder());
                result.AppendMessage(packet.GetString());
                result.SetStatus(eReturnStatusSuccessFinishResult);
                return true;
              } else {
                const char *error_cstr = error.AsCString();
                if (error_cstr && error_cstr[0])
                  result.AppendError(error_cstr);
                else
                  result.AppendErrorWithFormat("unknown error 0x%8.8x",
                                               error.GetError());
                result.SetStatus(eReturnStatusFailed);
                return false;
              }
            } else {
              result.AppendErrorWithFormat("process must be stopped in order "
                                           "to send KDP packets, state is %s",
                                           StateAsCString(state));
              result.SetStatus(eReturnStatusFailed);
            }
          } else {
            result.AppendError("invalid process");
            result.SetStatus(eReturnStatusFailed);
          }
        } else {
          result.AppendErrorWithFormat("invalid command byte 0x%" PRIx64
                                       ", valid values are 1 - 255",
                                       command_byte);
          result.SetStatus(eReturnStatusFailed);
        }
      }
    } else {
      result.AppendErrorWithFormat("'%s' takes no arguments, only options.",
                                   m_cmd_name.c_str());
      result.SetStatus(eReturnStatusFailed);
    }
    return false;
  }
};

class CommandObjectProcessKDPPacket : public CommandObjectMultiword {
private:
public:
  CommandObjectProcessKDPPacket(CommandInterpreter &interpreter)
      : CommandObjectMultiword(interpreter, "process plugin packet",
                               "Commands that deal with KDP remote packets.",
                               NULL) {
    LoadSubCommand(
        "send",
        CommandObjectSP(new CommandObjectProcessKDPPacketSend(interpreter)));
  }

  ~CommandObjectProcessKDPPacket() {}
};

class CommandObjectMultiwordProcessKDP : public CommandObjectMultiword {
public:
  CommandObjectMultiwordProcessKDP(CommandInterpreter &interpreter)
      : CommandObjectMultiword(
            interpreter, "process plugin",
            "Commands for operating on a ProcessKDP process.",
            "process plugin <subcommand> [<subcommand-options>]") {
    LoadSubCommand("packet", CommandObjectSP(new CommandObjectProcessKDPPacket(
                                 interpreter)));
  }

  ~CommandObjectMultiwordProcessKDP() {}
};

CommandObject *ProcessKDP::GetPluginCommandObject() {
  if (!m_command_sp)
    m_command_sp.reset(new CommandObjectMultiwordProcessKDP(
        GetTarget().GetDebugger().GetCommandInterpreter()));
  return m_command_sp.get();
}
