Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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              ????????

Copy link
Member

@RealFYang RealFYang Oct 23, 2025

Choose a reason for hiding this comment

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

Mind you that this new test seems to fail even on linux systems without pstack. This is happening on both of my AMD64 machine running Debian 12 and ARM64 machine running Ubuntu 22.04.4.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you share .jtr file?

Copy link
Member

@RealFYang RealFYang Oct 23, 2025

Choose a reason for hiding this comment

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

Sure. This is what I got on my amd64 machine:

$ make test TEST="serviceability/sa/TestJhsdbJstackMixedWithXComp.java"

TestJhsdbJstackMixedWithXComp.jtr.txt

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated this PR to check glibc version in TestJhsdbJstackMixedWithXComp.java added by this PR. It skips the test on Ubuntu 22.04, OTOH it works on Fedora 43. It is expected.
I attempted to add this check to SATestUtils at first, but it seems to be difficult because native access have to be allowed all of SATestUtils users - the impact is too significant.

I will file another issue to apply this check to other tests of jhsdb jstack --mixed user after this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated testcase again. It is more scalable for other mixed jstack tests. It works fine on both Fedora 43 and Ubuntu 22.04 .

Copy link
Member

Choose a reason for hiding this comment

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

Still fails on other distros like Debian 13 on AMD64. It has glibc version 2.41. Attached please fine the JTR file.

TestJhsdbJstackMixedWithXComp.jtr.txt

Copy link
Member Author

Choose a reason for hiding this comment

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

@RealFYang Thanks a lot for sharing JTR file!

I could find out the problem, and I fixed. We need to handle RSP more carefully when parsing DWARF. This PR works fine on Fedora 43 and Ubuntu 22. I believe it works on your Debian/Ubuntu.

But I think the failure what you saw on AArch64 is caused by problem(s) on stack unwinder for Linux AArch64, it is different from AMD64. Currently DWARF is supported on Linux AMD64 only, other platforms would attempt to unwind relies on base pointer. It is traditional, but it might not work on DWARF based binaries. I saw DWARF is contained in AArch64 binary in Fedora Rawhide for AArch64 at least. So the test added by this PR might not work on other platforms includes AArch64, RISC-V. Thus I removed them from @requires in the test. I think we can enable them if the unwinder (e.g. LinuxAARCH64CFrame.java) supports DWARF, but it is not a scope of this bug.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, the latest version now works on my Debian 13 AMD64 machine. Thanks for finding it out. It's good to know the difference.

* @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();
Copy link
Contributor

Choose a reason for hiding this comment

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

I might want you to change this to stderrShouldBeEmptyIgnoreVMWarnings(). We have issues with our internal testing when using -XX:+UseLargePages that sometimes results in a VM warning on stderr that causes this (and some other tests) to fail. We are still deciding on the best approach to fixing this, but I think the solution is most likely to be switching to stderrShouldBeEmptyIgnoreDeprecatedWarnings(). Hopefully I'll know within the next day.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I will update with the right way. Let me know what should I do when you know.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated the test to use stderrShouldBeEmptyIgnoreVMWarnings(). @plummercj let me know if I should make more changes.


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