8369505: jhsdb jstack cannot handle continuation stub

Reviewed-by: cjplummer, pchilanomate
This commit is contained in:
Yasumasa Suenaga 2025-10-16 12:45:05 +00:00
parent 5fc3904bfe
commit 1653999871
10 changed files with 297 additions and 4 deletions

View File

@ -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

View File

@ -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 */ \

View File

@ -180,6 +180,8 @@ public class CodeBlob extends VMObject {
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; }

View File

@ -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);
}
}

View File

@ -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 @@ public class JavaThread extends Thread {
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 class JavaThread extends Thread {
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;

View File

@ -270,7 +270,13 @@ public class AARCH64Frame extends Frame {
}
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 @@ public class AARCH64Frame extends Frame {
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");

View File

@ -262,7 +262,13 @@ public class RISCV64Frame extends Frame {
}
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 @@ public class RISCV64Frame extends Frame {
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");

View File

@ -270,7 +270,13 @@ public class X86Frame extends Frame {
}
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 @@ public class X86Frame extends Frame {
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");

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}