diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/cdbg/CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/cdbg/CFrame.java index 7640ddaa35e0a..13951447903e4 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/cdbg/CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/cdbg/CFrame.java @@ -36,6 +36,11 @@ public interface CFrame { /** Returns null when no more frames on stack */ public CFrame sender(ThreadProxy th); + /** Find sender frame with given FP and PC */ + public default CFrame sender(ThreadProxy th, Address fp, Address pc) { + return sender(th); + } + /** Get the program counter of this frame */ public Address pc(); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java index eb463d53a2e91..93edb43eb822c 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/aarch64/LinuxAARCH64CFrame.java @@ -53,24 +53,36 @@ public Address localVariableBase() { return fp; } + @Override public CFrame sender(ThreadProxy thread) { - AARCH64ThreadContext context = (AARCH64ThreadContext) thread.getContext(); - Address rsp = context.getRegisterAsAddress(AARCH64ThreadContext.SP); + return sender(thread, null, null); + } - if ((fp == null) || fp.lessThan(rsp)) { - return null; + @Override + public CFrame sender(ThreadProxy thread, Address nextFP, Address nextPC) { + // Check fp + // Skip if both nextFP and nextPC are given - do not need to load from fp. + if (nextFP == null && nextPC == null) { + if (fp == null) { + return null; + } + + // Check alignment of fp + if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) { + return null; + } } - // Check alignment of fp - if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) { + if (nextFP == null) { + nextFP = fp.getAddressAt(0 * ADDRESS_SIZE); + } + if (nextFP == null) { return null; } - Address nextFP = fp.getAddressAt(0 * ADDRESS_SIZE); - if (nextFP == null || nextFP.lessThanOrEqual(fp)) { - return null; + if (nextPC == null) { + nextPC = fp.getAddressAt(1 * ADDRESS_SIZE); } - Address nextPC = fp.getAddressAt(1 * ADDRESS_SIZE); if (nextPC == null) { return null; } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java index 3dfb83c9f5a43..a758f402a2152 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/amd64/LinuxAMD64CFrame.java @@ -47,11 +47,9 @@ public static LinuxAMD64CFrame getTopFrame(LinuxDebugger dbg, Address rip, Threa // personality routine and/or Language Specific Data Area (LSDA). return new LinuxAMD64CFrame(dbg, cfa, rip, dwarf, true); } - cfa = ((dwarf.getCFARegister() == AMD64ThreadContext.RBP) && - !dwarf.isBPOffsetAvailable()) - ? context.getRegisterAsAddress(AMD64ThreadContext.RBP) - : context.getRegisterAsAddress(dwarf.getCFARegister()) - .addOffsetTo(dwarf.getCFAOffset()); + + cfa = context.getRegisterAsAddress(dwarf.getCFARegister()) + .addOffsetTo(dwarf.getCFAOffset()); } return (cfa == null) ? null @@ -95,51 +93,72 @@ private Address getNextPC(boolean useDwarf) { } } - private boolean isValidFrame(Address nextCFA, ThreadContext context) { - return (nextCFA != null) && - !nextCFA.lessThan(context.getRegisterAsAddress(AMD64ThreadContext.RSP)); + private boolean isValidFrame(Address nextCFA, boolean isNative) { + // CFA should not be null even if it is Java frame. + // nextCFA should be greater than (current) cfa between native call. + // It is ok if the frame is Java-related call. + return nextCFA != null && + (!isNative || (isNative && nextCFA.greaterThan(cfa))); } - private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context) { + private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context, Address senderFP) { Address nextCFA; + boolean isNative = false; + + if (senderFP == null) { + senderFP = cfa.getAddressAt(0); // RBP by default + } if (nextDwarf == null) { // Next frame is Java - nextCFA = (dwarf == null) ? cfa.getAddressAt(0) // Current frame is Java (Use RBP) + nextCFA = (dwarf == null) ? senderFP // Current frame is Java : cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA()); // Current frame is Native } else { // Next frame is Native - if (dwarf == null) { // Current frame is Java (Use RBP) - nextCFA = cfa.getAddressAt(0); + if (dwarf == null) { // Current frame is Java + nextCFA = senderFP.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA()); } else { // Current frame is Native + isNative = true; int nextCFAReg = nextDwarf.getCFARegister(); - if (!dwarf.isBPOffsetAvailable() && // Use RBP as CFA - (nextCFAReg == AMD64ThreadContext.RBP) && - (nextCFAReg != dwarf.getCFARegister())) { - nextCFA = context.getRegisterAsAddress(AMD64ThreadContext.RBP); - if (nextCFA == null) { - return null; - } - nextCFA = nextCFA.getAddressAt(0); + if (nextCFAReg == AMD64ThreadContext.RBP) { + Address rbp = dwarf.isBPOffsetAvailable() ? cfa.addOffsetTo(dwarf.getBasePointerOffsetFromCFA()) + : context.getRegisterAsAddress(AMD64ThreadContext.RBP); + Address nextRBP = rbp.getAddressAt(0); + nextCFA = nextRBP.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA()); + } else if (nextCFAReg == AMD64ThreadContext.RSP) { + // next RSP should be previous slot of return address. + Address nextRSP = cfa.addOffsetTo(dwarf.getReturnAddressOffsetFromCFA()) + .addOffsetTo(ADDRESS_SIZE); + nextCFA = nextRSP.addOffsetTo(nextDwarf.getCFAOffset()); } else { - nextCFA = cfa.getAddressAt(dwarf.getBasePointerOffsetFromCFA()); + throw new DebuggerException("Unsupported CFA register: " + nextCFAReg); } } - if (nextCFA != null) { - nextCFA = nextCFA.addOffsetTo(-nextDwarf.getBasePointerOffsetFromCFA()); - } } - return isValidFrame(nextCFA, context) ? nextCFA : null; + // Sanity check for next CFA address + try { + nextCFA.getAddressAt(0); + } catch (Exception e) { + // return null if next CFA address is invalid + return null; + } + + return isValidFrame(nextCFA, isNative) ? nextCFA : null; + } + + @Override + public CFrame sender(ThreadProxy th) { + return sender(th, null, null); } @Override - public CFrame sender(ThreadProxy thread) { + public CFrame sender(ThreadProxy th, Address fp, Address pc) { if (finalFrame) { return null; } - ThreadContext context = thread.getContext(); + ThreadContext context = th.getContext(); - Address nextPC = getNextPC(dwarf != null); + Address nextPC = pc != null ? pc : getNextPC(dwarf != null); if (nextPC == null) { return null; } @@ -165,9 +184,9 @@ public CFrame sender(ThreadProxy thread) { } } - Address nextCFA = getNextCFA(nextDwarf, context); - return isValidFrame(nextCFA, context) ? new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf) - : null; + Address nextCFA = getNextCFA(nextDwarf, context, fp); + return nextCFA == null ? null + : new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf); } // package/class internals only diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/riscv64/LinuxRISCV64CFrame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/riscv64/LinuxRISCV64CFrame.java index f06da24bd0e30..76f45891c47db 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/riscv64/LinuxRISCV64CFrame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/debugger/linux/riscv64/LinuxRISCV64CFrame.java @@ -57,27 +57,40 @@ public Address localVariableBase() { return fp; } + @Override public CFrame sender(ThreadProxy thread) { - RISCV64ThreadContext context = (RISCV64ThreadContext) thread.getContext(); - Address rsp = context.getRegisterAsAddress(RISCV64ThreadContext.SP); + return sender(thread, null, null); + } - if ((fp == null) || fp.lessThan(rsp)) { - return null; + @Override + public CFrame sender(ThreadProxy thread, Address nextFP, Address nextPC) { + // Check fp + // Skip if both nextFP and nextPC are given - do not need to load from fp. + if (nextFP == null && nextPC == null) { + if (fp == null) { + return null; + } + + // Check alignment of fp + if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) { + return null; + } } - // Check alignment of fp - if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) { + if (nextFP == null) { + nextFP = fp.getAddressAt(C_FRAME_LINK_OFFSET * ADDRESS_SIZE); + } + if (nextFP == null) { return null; } - Address nextFP = fp.getAddressAt(C_FRAME_LINK_OFFSET * ADDRESS_SIZE); - if (nextFP == null || nextFP.lessThanOrEqual(fp)) { - return null; + if (nextPC == null) { + nextPC = fp.getAddressAt(C_FRAME_RETURN_ADDR_OFFSET * ADDRESS_SIZE); } - Address nextPC = fp.getAddressAt(C_FRAME_RETURN_ADDR_OFFSET * ADDRESS_SIZE); if (nextPC == null) { return null; } + return new LinuxRISCV64CFrame(dbg, nextFP, nextPC); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java index a0099b4c4de88..9865fbbe0f202 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java @@ -109,6 +109,8 @@ public void run(PrintStream out, Debugger dbg) { jthread.printThreadInfoOn(out); } while (f != null) { + Address senderFP = null; + Address senderPC = null; ClosestSymbol sym = f.closestSymbolToPC(); Address pc = f.pc(); out.print(pc + "\t"); @@ -125,13 +127,13 @@ public void run(PrintStream out, Debugger dbg) { out.println(); } else { // look for one or more java frames - String[] names = null; + JavaNameInfo nameInfo = null; // check interpreter frame Interpreter interp = VM.getVM().getInterpreter(); if (interp.contains(pc)) { - names = getJavaNames(th, f.localVariableBase()); + nameInfo = getJavaNames(th, f.localVariableBase()); // print codelet name if we can't determine method - if (names == null || names.length == 0) { + if (nameInfo == null || nameInfo.names() == null || nameInfo.names().length == 0) { out.print(" "); InterpreterCodelet ic = interp.getCodeletContaining(pc); if (ic != null) { @@ -154,9 +156,9 @@ public void run(PrintStream out, Debugger dbg) { } out.println(" (Native method)"); } else { - names = getJavaNames(th, f.localVariableBase()); + nameInfo = getJavaNames(th, f.localVariableBase()); // just print compiled code, if can't determine method - if (names == null || names.length == 0) { + if (nameInfo == null || nameInfo.names() == null || nameInfo.names().length == 0) { out.println(""); } } @@ -168,17 +170,21 @@ public void run(PrintStream out, Debugger dbg) { } } // print java frames, if any - if (names != null && names.length != 0) { - // print java frame(s) - for (int i = 0; i < names.length; i++) { - if (i > 0) { - out.print(fillerForAddress); + if (nameInfo != null) { + if (nameInfo.names() != null && nameInfo.names().length != 0) { + // print java frame(s) + for (int i = 0; i < nameInfo.names().length; i++) { + if (i > 0) { + out.print(fillerForAddress); + } + out.println(nameInfo.names()[i]); } - out.println(names[i]); } + senderFP = nameInfo.senderFP(); + senderPC = nameInfo.senderPC(); } } - f = f.sender(th); + f = f.sender(th, senderFP, senderPC); } } catch (Exception exp) { exp.printStackTrace(); @@ -239,17 +245,22 @@ private void printUnknown(PrintStream out) { out.println("\t????????"); } - private String[] getJavaNames(ThreadProxy th, Address fp) { + private static record JavaNameInfo(String[] names, Address senderFP, Address senderPC) {}; + + private JavaNameInfo getJavaNames(ThreadProxy th, Address fp) { if (fp == null) { return null; } JavaVFrame[] jvframes = jframeCache.get(th); if (jvframes == null) return null; // not a java thread + List names = new ArrayList<>(10); + JavaVFrame bottomJVFrame = null; for (int fCount = 0; fCount < jvframes.length; fCount++) { JavaVFrame vf = jvframes[fCount]; Frame f = vf.getFrame(); if (fp.equals(f.getFP())) { + bottomJVFrame = vf; StringBuilder sb = new StringBuilder(); Method method = vf.getMethod(); // a special char to identify java frames in output @@ -280,8 +291,16 @@ private String[] getJavaNames(ThreadProxy th, Address fp) { names.add(sb.toString()); } } - String[] res = names.toArray(new String[0]); - return res; + + Address senderFP = null; + Address senderPC = null; + if (bottomJVFrame != null) { + Frame senderFrame = bottomJVFrame.getFrame().sender((RegisterMap)bottomJVFrame.getRegisterMap().clone()); + senderFP = senderFrame.getFP(); + senderPC = senderFrame.getPC(); + } + + return new JavaNameInfo(names.toArray(new String[0]), senderFP, senderPC); } public void setVerbose(boolean verbose) { diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java new file mode 100644 index 0000000000000..9f94d44e24aa4 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java @@ -0,0 +1,104 @@ +/* + * 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 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 8370176 + * @requires vm.hasSA + * @requires os.family == "linux" + * @requires os.arch == "amd64" + * @library /test/lib + * @run driver TestJhsdbJstackMixedWithXComp + */ +public class TestJhsdbJstackMixedWithXComp { + + private static void runJstack(LingeredApp app) throws Exception { + JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb"); + launcher.addVMArgs(Utils.getFilteredTestJavaOpts("-showversion")); + launcher.addToolArg("jstack"); + launcher.addToolArg("--mixed"); + 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(); + + String stdout = out.getStdout(); + System.out.println(stdout); + System.err.println(out.getStderr()); + + out.stderrShouldBeEmptyIgnoreDeprecatedWarnings(); + + List targetStackTrace = new ArrayList<>(); + boolean inStack = false; + for (String line : stdout.split("\n")) { + if (line.contains("")) { + inStack = true; + } else if (inStack && line.contains("-----------------")) { + inStack = false; + break; + } + + if (inStack) { + targetStackTrace.add(line); + } + } + + boolean found = targetStackTrace.stream() + .anyMatch(l -> l.contains("thread_native_entry")); + if (!found) { + throw new RuntimeException("Test failed!"); + } + } + + 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, "-Xcomp"); + 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); + } + } +}