diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.c b/src/jdk.jdwp.agent/share/native/libjdwp/util.c
index 7e198be9a4637..335acc62dcf4f 100644
--- a/src/jdk.jdwp.agent/share/native/libjdwp/util.c
+++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.c
@@ -1652,6 +1652,185 @@ setAgentPropertyValue(JNIEnv *env, char *propertyName, char* propertyValue)
     }
 }
 
+#ifdef DEBUG
+// APIs that can be called when debugging the debug agent
+
+#define check_jvmti_status(err, msg) \
+  if (err != JVMTI_ERROR_NONE) { \
+      EXIT_ERROR(err, msg); \
+  }
+
+char*
+translateThreadState(jint flags) {
+    char str[15 * 20];
+    str[0] = '\0';
+
+    if (flags & JVMTI_THREAD_STATE_ALIVE) {
+        strcat(str, " ALIVE");
+    }
+    if (flags & JVMTI_THREAD_STATE_TERMINATED) {
+        strcat(str, " TERMINATED");
+    }
+    if (flags & JVMTI_THREAD_STATE_RUNNABLE) {
+        strcat(str, " RUNNABLE");
+    }
+    if (flags & JVMTI_THREAD_STATE_WAITING) {
+        strcat(str, " WAITING");
+    }
+    if (flags & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) {
+        strcat(str, " WAITING_INDEFINITELY");
+    }
+    if (flags & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) {
+        strcat(str, " WAITING_WITH_TIMEOUT");
+    }
+    if (flags & JVMTI_THREAD_STATE_SLEEPING) {
+        strcat(str, " SLEEPING");
+    }
+    if (flags & JVMTI_THREAD_STATE_IN_OBJECT_WAIT) {
+        strcat(str, " IN_OBJECT_WAIT");
+    }
+    if (flags & JVMTI_THREAD_STATE_PARKED) {
+        strcat(str, " PARKED");
+    }
+    if (flags & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) {
+        strcat(str, " BLOCKED_ON_MONITOR_ENTER");
+    }
+    if (flags & JVMTI_THREAD_STATE_SUSPENDED) {
+        strcat(str, " SUSPENDED");
+    }
+    if (flags & JVMTI_THREAD_STATE_INTERRUPTED) {
+        strcat(str, " INTERRUPTED");
+    }
+    if (flags & JVMTI_THREAD_STATE_IN_NATIVE) {
+        strcat(str, " IN_NATIVE");
+    }
+
+    if (strlen(str) == 0) {
+        strcpy(str, "<none>");
+    }
+
+    char* tstate = (char*)jvmtiAllocate((int)strlen(str) + 1);
+    strcpy(tstate, str);
+
+    return tstate;
+}
+
+char*
+getThreadName(jthread thread) {
+    jvmtiThreadInfo thr_info;
+    jvmtiError err;
+
+    memset(&thr_info, 0, sizeof(thr_info));
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo)
+        (gdata->jvmti, thread, &thr_info);
+    if (err == JVMTI_ERROR_WRONG_PHASE || err == JVMTI_ERROR_THREAD_NOT_ALIVE) {
+        return NULL; // VM or target thread completed its work
+    }
+    check_jvmti_status(err, "getThreadName: error in JVMTI GetThreadInfo call");
+
+    char* tname = thr_info.name;
+    if (tname == NULL) {
+        const char* UNNAMED_STR = "<Unnamed thread>";
+        size_t UNNAMED_LEN = strlen(UNNAMED_STR);
+        tname = (char*)jvmtiAllocate((int)UNNAMED_LEN + 1);
+        strcpy(tname, UNNAMED_STR);
+    }
+    return tname;
+}
+
+char*
+getMethodName(jmethodID method) {
+    char*  mname = NULL;
+    jvmtiError err;
+
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodName)
+        (gdata->jvmti, method, &mname, NULL, NULL);
+    check_jvmti_status(err, "getMethodName: error in JVMTI GetMethodName call");
+
+    return mname;
+}
+
+static char*
+get_method_class_name(jmethodID method) {
+    jclass klass = NULL;
+    char*  cname = NULL;
+    char*  result = NULL;
+    jvmtiError err;
+
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodDeclaringClass)
+        (gdata->jvmti, method, &klass);
+    check_jvmti_status(err, "get_method_class_name: error in JVMTI GetMethodDeclaringClass");
+
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetClassSignature)
+        (gdata->jvmti, klass, &cname, NULL);
+    check_jvmti_status(err, "get_method_class_name: error in JVMTI GetClassSignature");
+
+    size_t len = strlen(cname) - 2; // get rid of leading 'L' and trailing ';'
+    result = (char*)jvmtiAllocate((int)len + 1);
+    strncpy(result, cname + 1, len); // skip leading 'L'
+    result[len] = '\0';
+    jvmtiDeallocate((void*)cname);
+    return result;
+}
+
+static void
+print_method(jmethodID method, jint depth) {
+    char*  cname = NULL;
+    char*  mname = NULL;
+    char*  msign = NULL;
+    jvmtiError err;
+
+    cname = get_method_class_name(method);
+
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodName)
+        (gdata->jvmti, method, &mname, &msign, NULL);
+    check_jvmti_status(err, "print_method: error in JVMTI GetMethodName");
+
+    tty_message("%2d: %s: %s%s", depth, cname, mname, msign);
+    jvmtiDeallocate((void*)cname);
+    jvmtiDeallocate((void*)mname);
+    jvmtiDeallocate((void*)msign);
+}
+
+#define MAX_FRAME_COUNT_PRINT_STACK_TRACE 200
+
+void
+printStackTrace(jthread thread) {
+    jvmtiFrameInfo frames[MAX_FRAME_COUNT_PRINT_STACK_TRACE];
+    char* tname = getThreadName(thread);
+    jint count = 0;
+
+    jvmtiError err = JVMTI_FUNC_PTR(gdata->jvmti,GetStackTrace)
+        (gdata->jvmti, thread, 0, MAX_FRAME_COUNT_PRINT_STACK_TRACE, frames, &count);
+    check_jvmti_status(err, "printStackTrace: error in JVMTI GetStackTrace");
+
+    tty_message("JVMTI Stack Trace for thread %s: frame count: %d", tname, count);
+    for (int depth = 0; depth < count; depth++) {
+        print_method(frames[depth].method, depth);
+    }
+    jvmtiDeallocate((void*)tname);
+}
+
+void
+printThreadInfo(jthread thread) {
+    jvmtiThreadInfo thread_info;
+    jint thread_state;
+    jvmtiError err;
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo)
+        (gdata->jvmti, thread, &thread_info);
+    check_jvmti_status(err, "Error in GetThreadInfo");
+    err = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadState)
+        (gdata->jvmti, thread, &thread_state);
+    check_jvmti_status(err, "Error in GetThreadState");
+    const char* state = translateThreadState(thread_state);
+    tty_message("Thread: %p, name: %s, state(%x): %s, attrs: %s %s",
+                thread, thread_info.name, thread_state, state,
+                (isVThread(thread) ? "virtual": "platform"),
+                (thread_info.is_daemon ? "daemon": ""));
+}
+
+#endif /* DEBUG*/
+
 /**
  * Return property value as JDWP allocated string in UTF8 encoding
  */
diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.h b/src/jdk.jdwp.agent/share/native/libjdwp/util.h
index 75281813709e0..3d499d7d56985 100644
--- a/src/jdk.jdwp.agent/share/native/libjdwp/util.h
+++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.h
@@ -386,6 +386,15 @@ jvmtiError allNestedClasses(jclass clazz, jclass **ppnested, jint *pcount);
 
 void setAgentPropertyValue(JNIEnv *env, char *propertyName, char* propertyValue);
 
+#ifdef DEBUG
+// APIs that can be called when debugging the debug agent
+char* translateThreadState(jint flags);
+char* getThreadName(jthread thread);
+char* getMethodName(jmethodID method);
+void printStackTrace(jthread thread);
+void printThreadInfo(jthread thread);
+#endif
+
 void *jvmtiAllocate(jint numBytes);
 void jvmtiDeallocate(void *buffer);