Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,22 @@ private Address getNextPC(boolean useDwarf) {
}

private boolean isValidFrame(Address nextCFA, ThreadContext context) {
return (nextCFA != null) &&
!nextCFA.lessThan(context.getRegisterAsAddress(AMD64ThreadContext.RSP));
return nextCFA != null;
}

private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context) {
private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context, Address senderFP) {
Address nextCFA;

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;
} else { // Current frame is Native
int nextCFAReg = nextDwarf.getCFARegister();
if (!dwarf.isBPOffsetAvailable() && // Use RBP as CFA
Expand All @@ -132,14 +135,19 @@ private Address getNextCFA(DwarfParser nextDwarf, ThreadContext context) {
}

@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;
}
Expand All @@ -165,7 +173,7 @@ public CFrame sender(ThreadProxy thread) {
}
}

Address nextCFA = getNextCFA(nextDwarf, context);
Address nextCFA = getNextCFA(nextDwarf, context, fp);
return isValidFrame(nextCFA, context) ? new LinuxAMD64CFrame(dbg, nextCFA, nextPC, nextDwarf)
: null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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("<interpreter> ");
InterpreterCodelet ic = interp.getCodeletContaining(pc);
if (ic != null) {
Expand All @@ -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("<Unknown compiled code>");
}
}
Expand All @@ -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();
Expand Down Expand Up @@ -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<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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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 8370176
* @requires vm.hasSA
* @requires os.family == "linux"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do Windows and OSX have a similar problem that should be fixed also?

Copy link
Member Author

@YaSuenag YaSuenag Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem is in mixed mode (PStack) only, thus we need to skip OSX because you mentioned mixed mode is not supported on OSX.

In Windows, I'm not sure, but I guess we need to consider UNWIND_INFO to unwind call frames correctly like DWARF in Linux, however it hasn't done yet. Thus we can think mixed mode is not supported in Windows too, so I didn't add Windows here.
https://learn.microsoft.com/cpp/build/exception-handling-x64

Actually I could not see all of stacks as following in mixed mode. It works in normal mode (without --mixed) of course. (I tested it on Windows 11 x64, upstream JDK built by VS 2022)

----------------- 13 -----------------
"Reference Handler" #15 daemon prio=10 tid=0x00000207280b9f70 nid=12684 waiting on condition [0x000000aaf6aff000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked
0x00007fffa6b45844      ntdll!NtWaitForAlertByThreadId + 0x14
0x00000000ffffffff              ????????

* @requires (os.arch == "amd64" | os.arch == "aarch64")
* @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<String> targetStackTrace = new ArrayList<>();
boolean inStack = false;
System.out.println("DEBUG: before loop");
for (String line : stdout.split("\n")) {
if (line.contains("<nep_invoker_blob>")) {
inStack = true;
System.out.println("DEBUG: IN");
} else if (inStack && line.contains("-----------------")) {
inStack = false;
System.out.println("DEBUG: OUT");
break;
}

if (inStack) {
System.out.println("DEBUG: " + line);
targetStackTrace.add(line);
}
}
System.out.println("DEBUG: after loop");

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