Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8296324

JVMTI GetStackTrace truncates vthread stack trace for agents loaded into running VM

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P4
    • 20
    • 20
    • hotspot
    • b25
    • generic
    • generic

    Description

      ADDITIONAL SYSTEM INFORMATION :
      Ubunu 22.10 x86_64
      openjdk version "20-ea" 2023-03-21
      OpenJDK Runtime Environment (build 20-ea+21-1545)
      OpenJDK 64-Bit Server VM (build 20-ea+21-1545, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      JVMTI GetStackTrace returns a truncated trace for the current thread (with nullptr for the jthread parameter) when the agent was attached. When the agent is loaded at start up the JVMTI trace matches Thread.getStackTrace.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      With the attached sources and JAVA_HOME set to a JDK 20:

      g++ -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VirtualStackTraceTest.cpp -o libVirtualStackTraceTest.so

      $JAVA_HOME/bin/javac --enable-preview --release=20 VirtualStackTraceTest.java

      LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -Djdk.attach.allowAttachSelf=true VirtualStackTraceTest attach

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The test should show two identical stack traces and exit with 0.
      ACTUAL -
      The test shows a truncated stack trace, prints "Stack traces do not match" and exits with 1.

      When started with

      LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -agentlib:VirtualStackTraceTest VirtualStackTraceTest

      it works as expected.

      ---------- BEGIN SOURCE ----------
      -- VirtualStackTraceTest.java -----------------------------------------------------------
      import com.sun.tools.attach.VirtualMachine;

      import java.util.Arrays;
      import java.util.List;
      import java.util.Objects;
      import java.util.concurrent.locks.LockSupport;

      public class VirtualStackTraceTest {
          public static native String[] getStackTrace();

          public static void main(String[] args) throws Exception {
              if (args.length == 1 && args[0].equals("attach")) {
                  VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
                  vm.loadAgentLibrary("VirtualStackTraceTest");
              }
              Thread.sleep(2000);
              Thread.ofVirtual().name("virtual-test").start(VirtualStackTraceTest::testVirtual).join();
          }

          private static void testVirtual() {
              work();
          }

          private static void work() {
              LockSupport.parkNanos(1000);
              inner();
          }

          private static void inner() {
              checkCurrentThread();
          }

          private static void checkCurrentThread() {
              System.out.println("Stack trace for " + Thread.currentThread() + ": ");
              var jvmtiStackTrace = List.of(getStackTrace());
              var javaStackTrace = Arrays.stream(Thread.currentThread().getStackTrace()).map(StackTraceElement::getMethodName).toList();
              System.out.println("JVM TI: " + jvmtiStackTrace);
              System.out.println("Java : " + javaStackTrace);
              if (!Objects.equals(jvmtiStackTrace, javaStackTrace)) {
                  System.out.println("Stack traces do not match");
                  System.exit(1);
              }
          }
      }
      -----------------------------------------------------------------------------------------

      -- VirtualStackTraceTest.cpp ------------------------------------------------------------
      #include <jvmti.h>
      #include <cstdlib>
      #include <cstring>
      #include <vector>

      namespace {
          jvmtiEnv *jvmti = nullptr;

          void checkJvmti(int code, const char* message) {
              if (code != JVMTI_ERROR_NONE) {
                  printf("Error %s: %d\n", message, code);
                  abort();
              }
          }
      }

      extern "C" JNIEXPORT jobjectArray JNICALL Java_VirtualStackTraceTest_getStackTrace(JNIEnv* jni_env, jclass clazz) {
          jvmtiFrameInfo frameInfo[50];
          jint count;
          checkJvmti(jvmti->GetStackTrace(nullptr, 0, 50, frameInfo, &count), "GetStackTrace");
          std::vector<jobject> visibleFrames;
          for (int frameIndex = 0; frameIndex < count; frameIndex++) {
              jclass declaringClass = nullptr;
              checkJvmti(jvmti->GetMethodDeclaringClass(frameInfo[frameIndex].method, &declaringClass), "GetMethodDeclaringClass");
              if (declaringClass) {
                  char *clasSignature = nullptr, *methodName = nullptr;
                  checkJvmti(jvmti->GetClassSignature(declaringClass, &clasSignature, nullptr), "GetClassSignature");
                  checkJvmti(jvmti->GetMethodName(frameInfo[frameIndex].method, &methodName, nullptr, nullptr), "GetMethodName");
                  if (clasSignature && methodName) {
                      if (!strchr(clasSignature, '.')) {
                          visibleFrames.push_back(jni_env->NewStringUTF(methodName));
                      }
                      jvmti->Deallocate(reinterpret_cast<unsigned char*>(methodName));
                      jvmti->Deallocate(reinterpret_cast<unsigned char*>(clasSignature));
                  }
              }
          }
          jobjectArray methodStrings = jni_env->NewObjectArray(visibleFrames.size(), jni_env->FindClass("java/lang/String"), nullptr);
          if (methodStrings) {
              for (int frameIndex = 0; frameIndex < visibleFrames.size(); frameIndex++) {
                  jni_env->SetObjectArrayElement(methodStrings, frameIndex, visibleFrames[frameIndex]);
              }
          }
          return methodStrings;
      }

      extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
          if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
              printf("Could not initialize JVMTI\n");
              abort();
          }
          return JVMTI_ERROR_NONE;
      }

      extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
          return Agent_OnLoad(vm, options, reserved);
      }
      -----------------------------------------------------------------------------------------
      ---------- END SOURCE ----------

      FREQUENCY : always


      Attachments

        Issue Links

          Activity

            People

              sspitsyn Serguei Spitsyn
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: