/*
 * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

// Disable CRT security warning against strcpy/strcat
#pragma warning(disable: 4996)

// this is source code windbg based SA debugger agent to debug
// Dr. Watson dump files and process snapshots.

#include "sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal.h"

#ifdef _M_AMD64
  #include "sun_jvm_hotspot_debugger_amd64_AMD64ThreadContext.h"
  #define NPRGREG sun_jvm_hotspot_debugger_amd64_AMD64ThreadContext_NPRGREG
#elif _M_ARM64
  #include "sun_jvm_hotspot_debugger_aarch64_AARCH64ThreadContext.h"
  #define NPRGREG sun_jvm_hotspot_debugger_aarch64_AARCH64ThreadContext_NPRGREG
#else
  #error "SA windbg back-end is not supported for your cpu!"
#endif

#include <limits.h>
#include <windows.h>
#include <inttypes.h>

#define DEBUG_NO_IMPLEMENTATION
#include <dbgeng.h>
#include <dbghelp.h>


// Wrappers to simplify cleanup on errors.
namespace {

template <class T>
class AutoArrayPtr {
  T* m_ptr;
public:
  AutoArrayPtr(T* ptr) : m_ptr(ptr) {
  }

  ~AutoArrayPtr() {
    delete [] m_ptr;
  }

  operator T* () const {
    return m_ptr;
  }
};

// Manage COM 'auto' pointers to avoid multiple Release
// calls at every early (exception) returns.

template <class T>
class AutoCOMPtr {
  T* m_ptr;

public:
  AutoCOMPtr(T* ptr) : m_ptr(ptr) {
  }

  ~AutoCOMPtr() {
    if (m_ptr) {
      m_ptr->Release();
    }
  }

  T* operator->() const {
    return m_ptr;
  }
};

class AutoJavaString {
  JNIEnv* m_env;
  jstring m_str;
  const char* m_buf;

public:
  // check env->ExceptionCheck() after ctor
  AutoJavaString(JNIEnv* env, jstring str)
    : m_env(env), m_str(str), m_buf(str == nullptr ? nullptr : env->GetStringUTFChars(str, nullptr)) {
  }

  ~AutoJavaString() {
    if (m_buf) {
      m_env->ReleaseStringUTFChars(m_str, m_buf);
    }
  }

  operator const char* () const {
    return m_buf;
  }
};

class AutoJavaByteArray {
  JNIEnv* env;
  jbyteArray byteArray;
  jbyte* bytePtr;
  jint releaseMode;

public:
  // check env->ExceptionCheck() after ctor
  AutoJavaByteArray(JNIEnv* env, jbyteArray byteArray, jint releaseMode = JNI_ABORT)
    : env(env), byteArray(byteArray),
      bytePtr(env->GetByteArrayElements(byteArray, nullptr)),
      releaseMode(releaseMode) {
  }

  ~AutoJavaByteArray() {
    if (bytePtr) {
      env->ReleaseByteArrayElements(byteArray, bytePtr, releaseMode);
    }
  }

  void setReleaseMode(jint mode) {
    releaseMode = mode;
  }

  operator jbyte* () const {
    return bytePtr;
  }
};

} // unnamed namespace


// field and method IDs we want here

static jfieldID imagePath_ID                    = 0;
static jfieldID symbolPath_ID                   = 0;
static jfieldID ptrIDebugClient_ID              = 0;
static jfieldID ptrIDebugControl_ID             = 0;
static jfieldID ptrIDebugDataSpaces_ID          = 0;
static jfieldID ptrIDebugOutputCallbacks_ID     = 0;
static jfieldID ptrIDebugAdvanced_ID            = 0;
static jfieldID ptrIDebugSymbols_ID             = 0;
static jfieldID ptrIDebugSystemObjects_ID       = 0;

static jmethodID addLoadObject_ID               = 0;
static jmethodID addThread_ID                   = 0;
static jmethodID createClosestSymbol_ID         = 0;
static jmethodID setThreadIntegerRegisterSet_ID = 0;

#define CHECK_EXCEPTION_(value) if (env->ExceptionCheck()) { return value; }
#define CHECK_EXCEPTION if (env->ExceptionCheck()) { return; }

#define THROW_NEW_DEBUGGER_EXCEPTION_(str, value) { \
                          throwNewDebuggerException(env, str); return value; }

#define THROW_NEW_DEBUGGER_EXCEPTION(str) { \
                          throwNewDebuggerException(env, str); return; }

static void throwNewDebuggerException(JNIEnv* env, const char* errMsg) {
  jclass clazz = env->FindClass("sun/jvm/hotspot/debugger/DebuggerException");
  CHECK_EXCEPTION;
  env->ThrowNew(clazz, errMsg);
}

// Verifies COM call result is S_OK, throws DebuggerException and exits otherwise.
// Note: other success results (like S_FALSE) are considered errors.
#define COM_VERIFY_OK_(v, str, retValue) \
  do { \
    const HRESULT hr = (v); \
    if (hr != S_OK) { \
      size_t errmsg_size = strlen(str) + 32; \
      AutoArrayPtr<char> errmsg(new char[errmsg_size]); \
      if (errmsg == nullptr) { \
        THROW_NEW_DEBUGGER_EXCEPTION_(str, retValue); \
      } else { \
        snprintf(errmsg, errmsg_size, "%s (hr: 0x%08X)", str, hr); \
        THROW_NEW_DEBUGGER_EXCEPTION_(errmsg, retValue); \
      } \
    } \
  } while (false)

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_initIDs
      (JNIEnv *env, jclass clazz) {
  imagePath_ID = env->GetStaticFieldID(clazz, "imagePath", "Ljava/lang/String;");
  CHECK_EXCEPTION;

  symbolPath_ID = env->GetStaticFieldID(clazz, "symbolPath", "Ljava/lang/String;");
  CHECK_EXCEPTION;

  ptrIDebugClient_ID = env->GetFieldID(clazz, "ptrIDebugClient", "J");
  CHECK_EXCEPTION;

  ptrIDebugControl_ID = env->GetFieldID(clazz, "ptrIDebugControl", "J");
  CHECK_EXCEPTION;

  ptrIDebugDataSpaces_ID = env->GetFieldID(clazz, "ptrIDebugDataSpaces", "J");
  CHECK_EXCEPTION;

  ptrIDebugOutputCallbacks_ID = env->GetFieldID(clazz, "ptrIDebugOutputCallbacks", "J");
  CHECK_EXCEPTION;

  ptrIDebugAdvanced_ID = env->GetFieldID(clazz, "ptrIDebugAdvanced", "J");
  CHECK_EXCEPTION;

  ptrIDebugSymbols_ID = env->GetFieldID(clazz, "ptrIDebugSymbols", "J");
  CHECK_EXCEPTION;

  ptrIDebugSystemObjects_ID = env->GetFieldID(clazz, "ptrIDebugSystemObjects", "J");
  CHECK_EXCEPTION;

  addLoadObject_ID = env->GetMethodID(clazz, "addLoadObject", "(Ljava/lang/String;JJ)V");
  CHECK_EXCEPTION;

  addThread_ID = env->GetMethodID(clazz, "addThread", "(J)V");
  CHECK_EXCEPTION;

  createClosestSymbol_ID = env->GetMethodID(clazz, "createClosestSymbol",
                            "(Ljava/lang/String;J)Lsun/jvm/hotspot/debugger/cdbg/ClosestSymbol;");
  CHECK_EXCEPTION;

  setThreadIntegerRegisterSet_ID = env->GetMethodID(clazz,
                                         "setThreadIntegerRegisterSet", "(J[J)V");
  CHECK_EXCEPTION;
}

// class for IDebugOutputCallbacks

class SAOutputCallbacks : public IDebugOutputCallbacks {
  LONG  m_refCount;
  char* m_msgBuffer;

public:
  SAOutputCallbacks() : m_refCount(1), m_msgBuffer(nullptr) {
  }

  ~SAOutputCallbacks() {
    clearBuffer();
  }

  const char* getBuffer() const {
    return m_msgBuffer;
  }

  void clearBuffer() {
    if (m_msgBuffer) {
      free(m_msgBuffer);
      m_msgBuffer = 0;
    }
  }

  STDMETHOD_(ULONG, AddRef)(THIS);
  STDMETHOD_(ULONG, Release)(THIS);
  STDMETHOD(QueryInterface)(THIS_
                            IN REFIID interfaceId,
                            OUT PVOID* ppInterface);
  STDMETHOD(Output)(THIS_
                    IN ULONG mask,
                    IN PCSTR msg);
};

STDMETHODIMP_(ULONG) SAOutputCallbacks::AddRef(THIS) {
  return InterlockedIncrement(&m_refCount);
}

STDMETHODIMP_(ULONG) SAOutputCallbacks::Release(THIS) {
  LONG retVal = InterlockedDecrement(&m_refCount);
  if (retVal == 0) {
    delete this;
  }
  return retVal;
}

STDMETHODIMP SAOutputCallbacks::QueryInterface(THIS_
                                          IN REFIID interfaceId,
                                          OUT PVOID* ppInterface) {
  *ppInterface = nullptr;
  if (IsEqualIID(interfaceId, __uuidof(IUnknown)) ||
      IsEqualIID(interfaceId, __uuidof(IDebugOutputCallbacks))) {
    *ppInterface = static_cast<IDebugOutputCallbacks*>(this);
  } else {
    return E_NOINTERFACE;
  }
  AddRef();
  return S_OK;
}

STDMETHODIMP SAOutputCallbacks::Output(THIS_
                                       IN ULONG mask,
                                       IN PCSTR msg) {
  size_t len = strlen(msg) + 1;
  if (m_msgBuffer == 0) {
    m_msgBuffer = (char*) malloc(len);
    if (m_msgBuffer == 0) {
      fprintf(stderr, "out of memory debugger output!\n");
      return S_FALSE;
    }
    strcpy(m_msgBuffer, msg);
  } else {
    char* newBuffer = (char*)realloc(m_msgBuffer, len + strlen(m_msgBuffer));
    if (newBuffer == nullptr) {
      // old m_msgBuffer buffer is still valid
      fprintf(stderr, "out of memory debugger output!\n");
      return S_FALSE;
    }
    m_msgBuffer = newBuffer;
    strcat(m_msgBuffer, msg);
  }
  return S_OK;
}

static bool getWindbgInterfaces(JNIEnv* env, jobject obj) {
  // get windbg interfaces ..

  IDebugClient* ptrIDebugClient = 0;
  COM_VERIFY_OK_(DebugCreate(__uuidof(IDebugClient), (PVOID*) &ptrIDebugClient),
                 "Windbg Error: not able to create IDebugClient object!", false);
  env->SetLongField(obj, ptrIDebugClient_ID, (jlong) ptrIDebugClient);

  IDebugControl* ptrIDebugControl = 0;
  COM_VERIFY_OK_(ptrIDebugClient->QueryInterface(
                    __uuidof(IDebugControl), (PVOID*) &ptrIDebugControl),
                 "Windbg Error: not able to get IDebugControl", false);
  env->SetLongField(obj, ptrIDebugControl_ID, (jlong) ptrIDebugControl);

  IDebugDataSpaces* ptrIDebugDataSpaces = 0;
  COM_VERIFY_OK_(ptrIDebugClient->QueryInterface(
                    __uuidof(IDebugDataSpaces), (PVOID*) &ptrIDebugDataSpaces),
                 "Windbg Error: not able to get IDebugDataSpaces object!", false);
  env->SetLongField(obj, ptrIDebugDataSpaces_ID, (jlong) ptrIDebugDataSpaces);

  SAOutputCallbacks* ptrIDebugOutputCallbacks = new SAOutputCallbacks();
  env->SetLongField(obj, ptrIDebugOutputCallbacks_ID, (jlong) ptrIDebugOutputCallbacks);
  CHECK_EXCEPTION_(false);

  IDebugAdvanced* ptrIDebugAdvanced = 0;
  COM_VERIFY_OK_(ptrIDebugClient->QueryInterface(
                    __uuidof(IDebugAdvanced), (PVOID*) &ptrIDebugAdvanced),
                 "Windbg Error: not able to get IDebugAdvanced object!", false);
  env->SetLongField(obj, ptrIDebugAdvanced_ID, (jlong) ptrIDebugAdvanced);

  IDebugSymbols* ptrIDebugSymbols = 0;
  COM_VERIFY_OK_(ptrIDebugClient->QueryInterface(
                    __uuidof(IDebugSymbols), (PVOID*) &ptrIDebugSymbols),
                 "Windbg Error: not able to get IDebugSymbols object!", false);
  env->SetLongField(obj, ptrIDebugSymbols_ID, (jlong) ptrIDebugSymbols);

  IDebugSystemObjects* ptrIDebugSystemObjects = 0;
  COM_VERIFY_OK_(ptrIDebugClient->QueryInterface(
                    __uuidof(IDebugSystemObjects), (PVOID*) &ptrIDebugSystemObjects),
                 "Windbg Error: not able to get IDebugSystemObjects object!", false);
  env->SetLongField(obj, ptrIDebugSystemObjects_ID, (jlong) ptrIDebugSystemObjects);

  return true;
}

static bool setImageAndSymbolPath(JNIEnv* env, jobject obj) {
  jclass clazz = env->GetObjectClass(obj);
  CHECK_EXCEPTION_(false);
  jstring path;

  path = (jstring) env->GetStaticObjectField(clazz, imagePath_ID);
  CHECK_EXCEPTION_(false);
  if (path == nullptr) {
     THROW_NEW_DEBUGGER_EXCEPTION_("Windbg Error: not able to get imagePath field ID!", false);
  }
  AutoJavaString imagePath(env, path);
  CHECK_EXCEPTION_(false);

  path = (jstring) env->GetStaticObjectField(clazz, symbolPath_ID);
  CHECK_EXCEPTION_(false);
  if (path == nullptr) {
     THROW_NEW_DEBUGGER_EXCEPTION_("Windbg Error: not able to get symbolPath field ID!", false);
  }
  AutoJavaString symbolPath(env, path);
  CHECK_EXCEPTION_(false);

  IDebugSymbols* ptrIDebugSymbols = (IDebugSymbols*)env->GetLongField(obj, ptrIDebugSymbols_ID);
  CHECK_EXCEPTION_(false);

  COM_VERIFY_OK_(ptrIDebugSymbols->SetImagePath(imagePath),
                 "Windbg Error: SetImagePath failed!", false);
  COM_VERIFY_OK_(ptrIDebugSymbols->SetSymbolPath(symbolPath),
                 "Windbg Error: SetSymbolPath failed!", false);

  return true;
}

static HRESULT WaitForEvent(IDebugControl *ptrIDebugControl) {
  HRESULT hr = ptrIDebugControl->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE);
  // see JDK-8204994: sometimes WaitForEvent fails with E_ACCESSDENIED,
  // but succeeds on 2nd call.
  // To minimize possible noise retry 3 times.
  for (int i = 0; hr == E_ACCESSDENIED && i < 3; i++) {
    // yield current thread use of a processor (short delay).
    SwitchToThread();
    hr = ptrIDebugControl->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE);
  }
  return hr;
}

static bool openDumpFile(JNIEnv* env, jobject obj, jstring coreFileName) {
  // open the dump file
  AutoJavaString coreFile(env, coreFileName);
  CHECK_EXCEPTION_(false);
  if (!setImageAndSymbolPath(env, obj)) {
     return false;
  }

  IDebugClient* ptrIDebugClient = (IDebugClient*)env->GetLongField(obj, ptrIDebugClient_ID);
  CHECK_EXCEPTION_(false);
  COM_VERIFY_OK_(ptrIDebugClient->OpenDumpFile(coreFile),
                 "Windbg Error: OpenDumpFile failed!", false);

  IDebugControl* ptrIDebugControl = (IDebugControl*)env->GetLongField(obj, ptrIDebugControl_ID);
  CHECK_EXCEPTION_(false);
  COM_VERIFY_OK_(WaitForEvent(ptrIDebugControl),
                 "Windbg Error: WaitForEvent failed!", false);

  return true;
}


static bool attachToProcess(JNIEnv* env, jobject obj, jint pid) {
  if (!setImageAndSymbolPath(env, obj)) {
     return false;
  }
  IDebugClient* ptrIDebugClient = (IDebugClient*)env->GetLongField(obj, ptrIDebugClient_ID);
  CHECK_EXCEPTION_(false);

  /***********************************************************************************

     We are attaching to a process in 'read-only' mode. i.e., we do not want to
     put breakpoints, suspend/resume threads etc. For read-only JDI and HSDB kind of
     usage this should suffice.

     Please refer to DEBUG_ATTACH_NONINVASIVE mode source comments from dbgeng.h.
     In this mode, debug engine does not call DebugActiveProrcess. i.e., we are not
     actually debugging at all. We can safely 'detach' from the process anytime
     we want and debuggee process is left as is on all Windows variants.

     This also makes JDI-on-SA installation/usage simpler because with this we would
     not need a tool like ServiceInstaller from http://www.kcmultimedia.com/smaster.

  ***********************************************************************************/


  COM_VERIFY_OK_(ptrIDebugClient->AttachProcess(0, pid, DEBUG_ATTACH_NONINVASIVE),
                 "Windbg Error: AttachProcess failed!", false);

  IDebugControl* ptrIDebugControl = (IDebugControl*) env->GetLongField(obj,
                                                     ptrIDebugControl_ID);
  CHECK_EXCEPTION_(false);
  COM_VERIFY_OK_(WaitForEvent(ptrIDebugControl),
                 "Windbg Error: WaitForEvent failed!", false);

  return true;
}


static bool addLoadObjects(JNIEnv* env, jobject obj) {
  IDebugSymbols* ptrIDebugSymbols = (IDebugSymbols*) env->GetLongField(obj,
                                                      ptrIDebugSymbols_ID);
  CHECK_EXCEPTION_(false);
  ULONG loaded = 0, unloaded = 0;
  COM_VERIFY_OK_(ptrIDebugSymbols->GetNumberModules(&loaded, &unloaded),
                 "Windbg Error: GetNumberModules failed!", false);

  AutoArrayPtr<DEBUG_MODULE_PARAMETERS> params(new DEBUG_MODULE_PARAMETERS[loaded]);

  if (params == nullptr) {
      THROW_NEW_DEBUGGER_EXCEPTION_("out of memory to allocate debug module params!", false);
  }

  COM_VERIFY_OK_(ptrIDebugSymbols->GetModuleParameters(loaded, nullptr, 0, params),
                 "Windbg Error: GetModuleParameters failed!", false);

  for (int u = 0; u < (int)loaded; u++) {
    TCHAR imageName[MAX_PATH];
    COM_VERIFY_OK_(ptrIDebugSymbols->GetModuleNames(DEBUG_ANY_ID, params[u].Base,
                                                    imageName, MAX_PATH, nullptr, nullptr,
                                                    0, nullptr, nullptr, 0, nullptr),
                   "Windbg Error: GetModuleNames failed!", false);

    jstring strName = env->NewStringUTF(imageName);
    CHECK_EXCEPTION_(false);
    env->CallVoidMethod(obj, addLoadObject_ID, strName, (jlong) params[u].Size,
                        (jlong) params[u].Base);
    CHECK_EXCEPTION_(false);
    env->DeleteLocalRef(strName);
  }

  return true;
}

static bool addThreads(JNIEnv* env, jobject obj) {
  IDebugSystemObjects* ptrIDebugSystemObjects = (IDebugSystemObjects*) env->GetLongField(obj,
                                                      ptrIDebugSystemObjects_ID);
  CHECK_EXCEPTION_(false);

  ULONG numThreads = 0;
  COM_VERIFY_OK_(ptrIDebugSystemObjects->GetNumberThreads(&numThreads),
                 "Windbg Error: GetNumberThreads failed!", false);

  AutoArrayPtr<ULONG> ptrSysThreadIds(new ULONG[numThreads]);

  if (ptrSysThreadIds == nullptr) {
     THROW_NEW_DEBUGGER_EXCEPTION_("out of memory to allocate thread ids!", false);
  }

  AutoArrayPtr<ULONG> ptrThreadIds(new ULONG[numThreads]);

  if (ptrThreadIds == nullptr) {
     THROW_NEW_DEBUGGER_EXCEPTION_("out of memory to allocate thread ids!", false);
  }

  COM_VERIFY_OK_(ptrIDebugSystemObjects->GetThreadIdsByIndex(0, numThreads,
                                      ptrThreadIds, ptrSysThreadIds),
                 "Windbg Error: GetThreadIdsByIndex failed!", false);


  IDebugAdvanced* ptrIDebugAdvanced = (IDebugAdvanced*) env->GetLongField(obj,
                                                      ptrIDebugAdvanced_ID);
  CHECK_EXCEPTION_(false);

  // for each thread, get register context and save it.
  for (ULONG t = 0; t < numThreads; t++) {
    COM_VERIFY_OK_(ptrIDebugSystemObjects->SetCurrentThreadId(ptrThreadIds[t]),
                   "Windbg Error: SetCurrentThread failed!", false);

    jlongArray regs = env->NewLongArray(NPRGREG);
    CHECK_EXCEPTION_(false);

    jlong* ptrRegs = env->GetLongArrayElements(regs, nullptr);
    CHECK_EXCEPTION_(false);

    // copy register values from the CONTEXT struct
    CONTEXT context;
    memset(&context, 0, sizeof(CONTEXT));

#undef REG_INDEX
#ifdef _M_AMD64
    #define REG_INDEX(x) sun_jvm_hotspot_debugger_amd64_AMD64ThreadContext_##x

    context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    ptrIDebugAdvanced->GetThreadContext(&context, sizeof(CONTEXT));

    // Segment Registers and processor flags
    ptrRegs[REG_INDEX(CS)]  = context.SegCs;
    ptrRegs[REG_INDEX(DS)]  = context.SegDs;
    ptrRegs[REG_INDEX(ES)]  = context.SegEs;
    ptrRegs[REG_INDEX(FS)]  = context.SegFs;
    ptrRegs[REG_INDEX(GS)]  = context.SegGs;
    ptrRegs[REG_INDEX(SS)]  = context.SegSs;
    ptrRegs[REG_INDEX(RFL)] = context.EFlags;

    // Integer registers
    ptrRegs[REG_INDEX(RDI)] = context.Rdi;
    ptrRegs[REG_INDEX(RSI)] = context.Rsi;
    ptrRegs[REG_INDEX(RAX)] = context.Rax;
    ptrRegs[REG_INDEX(RCX)] = context.Rcx;
    ptrRegs[REG_INDEX(RDX)] = context.Rdx;
    ptrRegs[REG_INDEX(RBX)] = context.Rbx;
    ptrRegs[REG_INDEX(RBP)] = context.Rbp;
    ptrRegs[REG_INDEX(RSP)] = context.Rsp;

    ptrRegs[REG_INDEX(R8)]  = context.R8;
    ptrRegs[REG_INDEX(R9)]  = context.R9;
    ptrRegs[REG_INDEX(R10)] = context.R10;
    ptrRegs[REG_INDEX(R11)] = context.R11;
    ptrRegs[REG_INDEX(R12)] = context.R12;
    ptrRegs[REG_INDEX(R13)] = context.R13;
    ptrRegs[REG_INDEX(R14)] = context.R14;
    ptrRegs[REG_INDEX(R15)] = context.R15;

    // Program counter
    ptrRegs[REG_INDEX(RIP)] = context.Rip;
#endif

    env->ReleaseLongArrayElements(regs, ptrRegs, 0);
    CHECK_EXCEPTION_(false);

    env->CallVoidMethod(obj, setThreadIntegerRegisterSet_ID, (jlong)ptrThreadIds[t], regs);
    CHECK_EXCEPTION_(false);
    env->DeleteLocalRef(regs);

    ULONG sysId;
    COM_VERIFY_OK_(ptrIDebugSystemObjects->GetCurrentThreadSystemId(&sysId),
                   "Windbg Error: GetCurrentThreadSystemId failed!", false);

    env->CallVoidMethod(obj, addThread_ID, (jlong) sysId);
    CHECK_EXCEPTION_(false);
  }

  return true;
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    attach0
 * Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_attach0__Ljava_lang_String_2Ljava_lang_String_2
  (JNIEnv *env, jobject obj, jstring execName, jstring coreFileName) {

  if (!getWindbgInterfaces(env, obj)) {
     return;
  }

  if (!openDumpFile(env, obj, coreFileName)) {
     return;
  }

  if (!addLoadObjects(env, obj)) {
     return;
  }

  if (!addThreads(env, obj)) {
     return;
  }
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    attach0
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_attach0__I
  (JNIEnv *env, jobject obj, jint pid) {

  if (!getWindbgInterfaces(env, obj)) {
     return;
  }

  if (!attachToProcess(env, obj, pid)) {
     return;
  }

  if (!addLoadObjects(env, obj)) {
     return;
  }

  if (!addThreads(env, obj)) {
     return;
  }
}


#define RELEASE(fieldID) \
  do { \
    IUnknown* ptr = (IUnknown*)env->GetLongField(obj, fieldID); \
    CHECK_EXCEPTION_(false); \
    if (ptr) { \
      ptr->Release(); \
    } \
  } while (false)

static bool releaseWindbgInterfaces(JNIEnv* env, jobject obj) {
  RELEASE(ptrIDebugDataSpaces_ID);
  RELEASE(ptrIDebugOutputCallbacks_ID);
  RELEASE(ptrIDebugAdvanced_ID);
  RELEASE(ptrIDebugSymbols_ID);
  RELEASE(ptrIDebugSystemObjects_ID);
  RELEASE(ptrIDebugControl_ID);
  RELEASE(ptrIDebugClient_ID);

  return true;
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    detach0
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_detach0
    (JNIEnv *env, jobject obj) {
  IDebugClient* ptrIDebugClient = (IDebugClient*) env->GetLongField(obj, ptrIDebugClient_ID);
  CHECK_EXCEPTION;
  ptrIDebugClient->DetachProcesses();
  releaseWindbgInterfaces(env, obj);
}


/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    readBytesFromProcess0
 * Signature: (JJ)[B
 */
JNIEXPORT jbyteArray JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_readBytesFromProcess0
    (JNIEnv *env, jobject obj, jlong address, jlong numBytes) {
  jbyteArray byteArray = env->NewByteArray((jsize)numBytes);
  CHECK_EXCEPTION_(0);

  AutoJavaByteArray arrayBytes(env, byteArray);
  CHECK_EXCEPTION_(0);

  IDebugDataSpaces* ptrIDebugDataSpaces = (IDebugDataSpaces*) env->GetLongField(obj,
                                                       ptrIDebugDataSpaces_ID);
  CHECK_EXCEPTION_(0);

  ULONG bytesRead;
  const HRESULT hr = ptrIDebugDataSpaces->ReadVirtual((ULONG64)address, arrayBytes,
                                                      (ULONG)numBytes, &bytesRead);
  if (hr != S_OK || bytesRead != numBytes) {
     return 0;
  }

  arrayBytes.setReleaseMode(0);

  return byteArray;
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    getThreadIdFromSysId0
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_getThreadIdFromSysId0
    (JNIEnv *env, jobject obj, jlong sysId) {
  IDebugSystemObjects* ptrIDebugSystemObjects = (IDebugSystemObjects*) env->GetLongField(obj,
                                                    ptrIDebugSystemObjects_ID);
  CHECK_EXCEPTION_(0);

  ULONG id = 0;
  HRESULT hr = ptrIDebugSystemObjects->GetThreadIdBySystemId((ULONG)sysId, &id);
  if (hr != S_OK) {
    // This is not considered fatal and does happen on occasion, usually with an
    // 0x80004002 "No such interface supported". The root cause is not fully understood,
    // but by ignoring this error and returning NULL, stacking walking code will get
    // null registers and fallback to using the "last java frame" if setup.
   printf("WARNING: GetThreadIdBySystemId failed with 0x%x for sysId (%" PRIu64 ")\n",
           hr, sysId);
    return -1;
  }
  return (jlong) id;
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    consoleExecuteCommand0
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_consoleExecuteCommand0
    (JNIEnv *env, jobject obj, jstring cmd) {
  AutoJavaString command(env, cmd);
  CHECK_EXCEPTION_(0);

  IDebugClient* ptrIDebugClient = (IDebugClient*) env->GetLongField(obj, ptrIDebugClient_ID);
  CHECK_EXCEPTION_(0);

  IDebugClient*  tmpClientPtr = 0;
  COM_VERIFY_OK_(ptrIDebugClient->CreateClient(&tmpClientPtr),
                 "Windbg Error: CreateClient failed!", 0);
  AutoCOMPtr<IDebugClient> tmpClient(tmpClientPtr);

  IDebugControl* tmpControlPtr = 0;
  COM_VERIFY_OK_(tmpClient->QueryInterface(__uuidof(IDebugControl), (PVOID*) &tmpControlPtr),
                 "Windbg Error: QueryInterface (IDebugControl) failed", 0);
  AutoCOMPtr<IDebugControl> tmpControl(tmpControlPtr);

  SAOutputCallbacks* saOutputCallbacks = (SAOutputCallbacks*) env->GetLongField(obj,
                                                                   ptrIDebugOutputCallbacks_ID);
  CHECK_EXCEPTION_(0);

  saOutputCallbacks->clearBuffer();

  COM_VERIFY_OK_(tmpClient->SetOutputCallbacks(saOutputCallbacks),
                 "Windbg Error: SetOutputCallbacks failed!", 0);

  tmpControl->Execute(DEBUG_OUTPUT_VERBOSE, command, DEBUG_EXECUTE_DEFAULT);

  const char* output = saOutputCallbacks->getBuffer();
  if (output == 0) {
     output = "";
  }

  jstring res = env->NewStringUTF(output);
  saOutputCallbacks->clearBuffer();
  return res;
}

#define SYMBOL_BUFSIZE 512

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    lookupByName0
 * Signature: (Ljava/lang/String;Ljava/lang/String;)J
 */

JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_lookupByName0
    (JNIEnv *env, jobject obj, jstring objName, jstring sym) {
  IDebugSymbols* ptrIDebugSymbols = (IDebugSymbols*)env->GetLongField(obj, ptrIDebugSymbols_ID);
  CHECK_EXCEPTION_(0);

  AutoJavaString name(env, sym);
  CHECK_EXCEPTION_(0);

  ULONG64 offset = 0L;
  if (strstr(name, "::") != 0) {
    ptrIDebugSymbols->AddSymbolOptions(SYMOPT_UNDNAME);
  } else {
    ptrIDebugSymbols->RemoveSymbolOptions(SYMOPT_UNDNAME);
  }
  if (ptrIDebugSymbols->GetOffsetByName(name, &offset) != S_OK) {
    return (jlong) 0;
  }

  // See JDK-8311993: WinDbg intermittently returns offset of "module!class::`vftable'" symbol
  // when requested for decorated "class" or "class*" (i.e. "??_7class@@6B@"/"??_7class*@@6B@").
  // As a workaround check if returned symbol contains requested symbol.
  ULONG64 disp = 0L;
  char buf[SYMBOL_BUFSIZE];
  memset(buf, 0, sizeof(buf));
  if (ptrIDebugSymbols->GetNameByOffset(offset, buf, sizeof(buf), 0, &disp) == S_OK) {
    if (strstr(buf, name) == nullptr) {
      return (jlong)0;
    }
  }

  return (jlong) offset;
}

/*
 * Class:     sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal
 * Method:    lookupByAddress0
 * Signature: (J)Lsun/jvm/hotspot/debugger/cdbg/ClosestSymbol;
 */
JNIEXPORT jobject JNICALL Java_sun_jvm_hotspot_debugger_windbg_WindbgDebuggerLocal_lookupByAddress0
    (JNIEnv *env, jobject obj, jlong address) {
  IDebugSymbols* ptrIDebugSymbols = (IDebugSymbols*) env->GetLongField(obj, ptrIDebugSymbols_ID);
  CHECK_EXCEPTION_(0);

  ULONG64 disp = 0L;
  char buf[SYMBOL_BUFSIZE];
  memset(buf, 0, sizeof(buf));

  if (ptrIDebugSymbols->GetNameByOffset(address, buf, sizeof(buf), 0, &disp) != S_OK) {
    return 0;
  }

  jstring sym = env->NewStringUTF(buf);
  CHECK_EXCEPTION_(0);
  jobject res = env->CallObjectMethod(obj, createClosestSymbol_ID, sym, disp);
  CHECK_EXCEPTION_(0);
  return res;
}
