mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-20 20:00:28 +00:00
8370176: Mixed mode jhsdb jstack cannot unwind call stack with -Xcomp
Co-authored-by: Fei Yang <fyang@openjdk.org> Reviewed-by: cjplummer, kevinw
This commit is contained in:
parent
8dbefc53a9
commit
045018d5f3
@ -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();
|
||||
|
||||
|
||||
@ -53,24 +53,36 @@ public final class LinuxAARCH64CFrame extends BasicCFrame {
|
||||
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)) {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextFP == null) {
|
||||
nextFP = fp.getAddressAt(0 * ADDRESS_SIZE);
|
||||
}
|
||||
if (nextFP == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check alignment of fp
|
||||
if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) {
|
||||
return null;
|
||||
if (nextPC == null) {
|
||||
nextPC = fp.getAddressAt(1 * ADDRESS_SIZE);
|
||||
}
|
||||
|
||||
Address nextFP = fp.getAddressAt(0 * ADDRESS_SIZE);
|
||||
if (nextFP == null || nextFP.lessThanOrEqual(fp)) {
|
||||
return null;
|
||||
}
|
||||
Address nextPC = fp.getAddressAt(1 * ADDRESS_SIZE);
|
||||
if (nextPC == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -47,11 +47,9 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
|
||||
// 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
|
||||
@ -101,51 +99,72 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
|
||||
}
|
||||
}
|
||||
|
||||
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 never be null.
|
||||
// nextCFA must be greater than current CFA, if frame is native.
|
||||
// Java interpreter frames can share the CFA (frame pointer).
|
||||
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 thread) {
|
||||
public CFrame sender(ThreadProxy th) {
|
||||
return sender(th, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
@ -168,9 +187,9 @@ public final class LinuxAMD64CFrame extends BasicCFrame {
|
||||
}
|
||||
}
|
||||
|
||||
Address nextCFA = getNextCFA(nextDwarf, context);
|
||||
return isValidFrame(nextCFA, context) ? new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf, false, fallback)
|
||||
: null;
|
||||
Address nextCFA = getNextCFA(nextDwarf, context, fp);
|
||||
return nextCFA == null ? null
|
||||
: new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf, false, fallback);
|
||||
}
|
||||
|
||||
private DwarfParser createDwarfParser(Address pc) throws DebuggerException {
|
||||
|
||||
@ -57,27 +57,40 @@ public final class LinuxRISCV64CFrame extends BasicCFrame {
|
||||
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)) {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextFP == null) {
|
||||
nextFP = fp.getAddressAt(C_FRAME_LINK_OFFSET * ADDRESS_SIZE);
|
||||
}
|
||||
if (nextFP == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check alignment of fp
|
||||
if (dbg.getAddressValue(fp) % (2 * ADDRESS_SIZE) != 0) {
|
||||
return null;
|
||||
if (nextPC == null) {
|
||||
nextPC = fp.getAddressAt(C_FRAME_RETURN_ADDR_OFFSET * ADDRESS_SIZE);
|
||||
}
|
||||
|
||||
Address nextFP = fp.getAddressAt(C_FRAME_LINK_OFFSET * ADDRESS_SIZE);
|
||||
if (nextFP == null || nextFP.lessThanOrEqual(fp)) {
|
||||
return null;
|
||||
}
|
||||
Address nextPC = fp.getAddressAt(C_FRAME_RETURN_ADDR_OFFSET * ADDRESS_SIZE);
|
||||
if (nextPC == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new LinuxRISCV64CFrame(dbg, nextFP, nextPC);
|
||||
}
|
||||
|
||||
|
||||
@ -109,6 +109,8 @@ public class PStack extends Tool {
|
||||
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 class PStack extends Tool {
|
||||
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("<interpreter> ");
|
||||
InterpreterCodelet ic = interp.getCodeletContaining(pc);
|
||||
if (ic != null) {
|
||||
@ -154,9 +156,9 @@ public class PStack extends Tool {
|
||||
}
|
||||
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("<Unknown compiled code>");
|
||||
}
|
||||
}
|
||||
@ -168,17 +170,21 @@ public class PStack extends Tool {
|
||||
}
|
||||
}
|
||||
// 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 @@ public class PStack extends Tool {
|
||||
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<String> 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 @@ public class PStack extends Tool {
|
||||
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) {
|
||||
|
||||
@ -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.stderrShouldBeEmptyIgnoreVMWarnings();
|
||||
|
||||
List<String> targetStackTrace = new ArrayList<>();
|
||||
boolean inStack = false;
|
||||
for (String line : stdout.split("\n")) {
|
||||
if (line.contains("<nep_invoker_blob>")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user