diff --git a/src/hotspot/share/runtime/continuationEntry.hpp b/src/hotspot/share/runtime/continuationEntry.hpp index 8361f2f912be6..490293f5b118f 100644 --- a/src/hotspot/share/runtime/continuationEntry.hpp +++ b/src/hotspot/share/runtime/continuationEntry.hpp @@ -39,6 +39,7 @@ class RegisterMap; // Metadata stored in the continuation entry frame class ContinuationEntry { + friend class VMStructs; friend class JVMCIVMStructs; ContinuationEntryPD _pd; #ifdef ASSERT diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index dee0a5d4eb7d4..8dc4b660f9133 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -616,6 +616,7 @@ nonstatic_field(JavaThread, _active_handles, JNIHandleBlock*) \ nonstatic_field(JavaThread, _monitor_owner_id, int64_t) \ volatile_nonstatic_field(JavaThread, _terminated, JavaThread::TerminatedTypes) \ + nonstatic_field(JavaThread, _cont_entry, ContinuationEntry*) \ nonstatic_field(Thread, _osthread, OSThread*) \ \ /************/ \ @@ -796,7 +797,8 @@ nonstatic_field(Mutex, _name, const char*) \ static_field(Mutex, _mutex_array, Mutex**) \ static_field(Mutex, _num_mutex, int) \ - volatile_nonstatic_field(Mutex, _owner, Thread*) + volatile_nonstatic_field(Mutex, _owner, Thread*) \ + static_field(ContinuationEntry, _return_pc, address) //-------------------------------------------------------------------------------- // VM_TYPES @@ -1270,6 +1272,7 @@ declare_toplevel_type(FileMapHeader) \ declare_toplevel_type(CDSFileMapRegion) \ declare_toplevel_type(UpcallStub::FrameData) \ + declare_toplevel_type(ContinuationEntry) \ \ /************/ \ /* GC types */ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/CodeBlob.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/CodeBlob.java index 20c5fabf8bc95..12469efc67e43 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/CodeBlob.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/code/CodeBlob.java @@ -180,6 +180,8 @@ public ImmutableOopMapSet getOopMaps() { public boolean isUpcallStub() { return getKind() == UpcallKind; } + public boolean isContinuationStub() { return getName().equals("StubRoutines (continuation stubs)"); } + public boolean isJavaMethod() { return false; } public boolean isNativeMethod() { return false; } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ContinuationEntry.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ContinuationEntry.java new file mode 100644 index 0000000000000..73152bdee8421 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ContinuationEntry.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, NTT DATA. + * 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. + * + */ + +package sun.jvm.hotspot.runtime; + +import sun.jvm.hotspot.debugger.*; +import sun.jvm.hotspot.runtime.*; +import sun.jvm.hotspot.types.*; + + +public class ContinuationEntry extends VMObject { + private static long size; + private static Address returnPC; + + static { + VM.registerVMInitializedObserver((o, d) -> initialize(VM.getVM().getTypeDataBase())); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + Type type = db.lookupType("ContinuationEntry"); + size = type.getSize(); + returnPC = type.getAddressField("_return_pc").getValue(); + } + + public ContinuationEntry(Address addr) { + super(addr); + } + + public Address getEntryPC() { + return returnPC; + } + + public Address getEntrySP(){ + return this.getAddress(); + } + + public Address getEntryFP(){ + return this.getAddress().addOffsetTo(size); + } + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java index d92f464f0d2f6..826b5cecfd568 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/JavaThread.java @@ -47,6 +47,7 @@ public class JavaThread extends Thread { private static AddressField stackBaseField; private static CIntegerField stackSizeField; private static CIntegerField terminatedField; + private static AddressField contEntryField; private static AddressField activeHandlesField; private static CIntegerField monitorOwnerIDField; private static long oopPtrSize; @@ -95,6 +96,7 @@ private static synchronized void initialize(TypeDataBase db) { stackBaseField = type.getAddressField("_stack_base"); stackSizeField = type.getCIntegerField("_stack_size"); terminatedField = type.getCIntegerField("_terminated"); + contEntryField = type.getAddressField("_cont_entry"); activeHandlesField = type.getAddressField("_active_handles"); monitorOwnerIDField = type.getCIntegerField("_monitor_owner_id"); @@ -340,6 +342,10 @@ public int getTerminated() { return (int) terminatedField.getValue(addr); } + public ContinuationEntry getContEntry() { + return VMObjectFactory.newObject(ContinuationEntry.class, contEntryField.getValue(addr)); + } + /** Gets the Java-side thread object for this JavaThread */ public Oop getThreadObj() { Oop obj = null; diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java index a5aa7ce440506..5ae4cb703b331 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/aarch64/AARCH64Frame.java @@ -270,7 +270,13 @@ public Frame sender(RegisterMap regMap, CodeBlob cb) { } if (cb != null) { - return cb.isUpcallStub() ? senderForUpcallStub(map, (UpcallStub)cb) : senderForCompiledFrame(map, cb); + if (cb.isUpcallStub()) { + return senderForUpcallStub(map, (UpcallStub)cb); + } else if (cb.isContinuationStub()) { + return senderForContinuationStub(map, cb); + } else { + return senderForCompiledFrame(map, cb); + } } // Must be native-compiled frame, i.e. the marshaling code for native @@ -356,6 +362,16 @@ private void updateMapWithSavedLink(RegisterMap map, Address savedFPAddr) { map.setLocation(fp, savedFPAddr); } + private Frame senderForContinuationStub(AARCH64RegisterMap map, CodeBlob cb) { + var contEntry = map.getThread().getContEntry(); + + Address senderSP = contEntry.getEntrySP(); + Address senderPC = contEntry.getEntryPC(); + Address senderFP = contEntry.getEntryFP(); + + return new AARCH64Frame(senderSP, senderFP, senderPC); + } + private Frame senderForCompiledFrame(AARCH64RegisterMap map, CodeBlob cb) { if (DEBUG) { System.out.println("senderForCompiledFrame"); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/riscv64/RISCV64Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/riscv64/RISCV64Frame.java index e02e056f028cd..44c8f4c679ca8 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/riscv64/RISCV64Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/riscv64/RISCV64Frame.java @@ -262,7 +262,13 @@ public Frame sender(RegisterMap regMap, CodeBlob cb) { } if (cb != null) { - return cb.isUpcallStub() ? senderForUpcallStub(map, (UpcallStub)cb) : senderForCompiledFrame(map, cb); + if (cb.isUpcallStub()) { + return senderForUpcallStub(map, (UpcallStub)cb); + } else if (cb.isContinuationStub()) { + return senderForContinuationStub(map, cb); + } else { + return senderForCompiledFrame(map, cb); + } } // Must be native-compiled frame, i.e. the marshaling code for native @@ -348,6 +354,16 @@ private void updateMapWithSavedLink(RegisterMap map, Address savedFPAddr) { map.setLocation(fp, savedFPAddr); } + private Frame senderForContinuationStub(RISCV64RegisterMap map, CodeBlob cb) { + var contEntry = map.getThread().getContEntry(); + + Address senderSP = contEntry.getEntrySP(); + Address senderPC = contEntry.getEntryPC(); + Address senderFP = contEntry.getEntryFP(); + + return new RISCV64Frame(senderSP, senderFP, senderPC); + } + private Frame senderForCompiledFrame(RISCV64RegisterMap map, CodeBlob cb) { if (DEBUG) { System.out.println("senderForCompiledFrame"); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java index 3ee4f0a815892..2d972d3df176c 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/x86/X86Frame.java @@ -270,7 +270,13 @@ public Frame sender(RegisterMap regMap, CodeBlob cb) { } if (cb != null) { - return cb.isUpcallStub() ? senderForUpcallStub(map, (UpcallStub)cb) : senderForCompiledFrame(map, cb); + if (cb.isUpcallStub()) { + return senderForUpcallStub(map, (UpcallStub)cb); + } else if (cb.isContinuationStub()) { + return senderForContinuationStub(map, cb); + } else { + return senderForCompiledFrame(map, cb); + } } // Must be native-compiled frame, i.e. the marshaling code for native @@ -356,6 +362,16 @@ private void updateMapWithSavedLink(RegisterMap map, Address savedFPAddr) { map.setLocation(rbp, savedFPAddr); } + private Frame senderForContinuationStub(X86RegisterMap map, CodeBlob cb) { + var contEntry = map.getThread().getContEntry(); + + Address senderSP = contEntry.getEntrySP(); + Address senderPC = contEntry.getEntryPC(); + Address senderFP = contEntry.getEntryFP(); + + return new X86Frame(senderSP, senderFP, senderPC); + } + private Frame senderForCompiledFrame(X86RegisterMap map, CodeBlob cb) { if (DEBUG) { System.out.println("senderForCompiledFrame"); diff --git a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithVirtualThread.java b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithVirtualThread.java new file mode 100644 index 0000000000000..ca98506e13381 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithVirtualThread.java @@ -0,0 +1,87 @@ + +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, NTT DATA + * 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. + */ + +import java.lang.invoke.MethodHandle; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.util.concurrent.CountDownLatch; + +import jdk.test.lib.apps.LingeredApp; + +public class LingeredAppWithVirtualThread extends LingeredApp implements Runnable { + + private static final String THREAD_NAME = "target thread"; + + private static final MethodHandle hndSleep; + + private static final int sleepArg; + + private static final CountDownLatch signal = new CountDownLatch(1); + + static { + MemorySegment func; + if (System.getProperty("os.name").startsWith("Windows")) { + func = SymbolLookup.libraryLookup("Kernel32", Arena.global()) + .findOrThrow("Sleep"); + sleepArg = 3600_000; // 1h in milliseconds + } else { + func = Linker.nativeLinker() + .defaultLookup() + .findOrThrow("sleep"); + sleepArg = 3600; // 1h in seconds + } + + var desc = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT); + hndSleep = Linker.nativeLinker().downcallHandle(func, desc); + } + + @Override + public void run() { + Thread.yield(); + signal.countDown(); + try { + hndSleep.invoke(sleepArg); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + public static void main(String[] args) { + try { + Thread.ofVirtual() + .name(THREAD_NAME) + .start(new LingeredAppWithVirtualThread()); + + signal.await(); + LingeredApp.main(args); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackWithVirtualThread.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackWithVirtualThread.java new file mode 100644 index 0000000000000..fce9906ca94ef --- /dev/null +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackWithVirtualThread.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, NTT DATA + * 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. + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.SA.SATestUtils; +import jdk.test.lib.Utils; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.OutputAnalyzer; + +/** + * @test + * @bug 8369505 + * @requires vm.hasSA + * @requires (os.arch == "amd64" | os.arch == "x86_64" | os.arch == "aarch64" | os.arch == "riscv64") + * @library /test/lib + * @run driver TestJhsdbJstackWithVirtualThread + */ +public class TestJhsdbJstackWithVirtualThread { + + private static void runJstack(LingeredApp app) throws Exception { + JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb"); + launcher.addVMArgs(Utils.getTestJavaOpts()); + launcher.addToolArg("jstack"); + launcher.addToolArg("--pid"); + launcher.addToolArg(Long.toString(app.getPid())); + + ProcessBuilder pb = SATestUtils.createProcessBuilder(launcher); + Process jhsdb = pb.start(); + OutputAnalyzer out = new OutputAnalyzer(jhsdb); + + jhsdb.waitFor(); + + System.out.println(out.getStdout()); + System.err.println(out.getStderr()); + + out.stderrShouldBeEmptyIgnoreDeprecatedWarnings(); + out.shouldNotContain("must have non-zero frame size"); + } + + public static void main(String... args) throws Exception { + SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. + LingeredApp app = null; + + try { + app = new LingeredAppWithVirtualThread(); + LingeredApp.startApp(app); + System.out.println("Started LingeredApp with pid " + app.getPid()); + runJstack(app); + System.out.println("Test Completed"); + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } finally { + LingeredApp.stopApp(app); + } + } +}