8243500: SA: Incorrect BCI and Line Number with jstack if the top frame is in the interpreter (BSD and Windows)
Reviewed-by: amenkov, sspitsyn
This commit is contained in:
parent
5d2740b524
commit
6911667eb7
@ -103,6 +103,10 @@ public class BsdAMD64JavaThreadPDAccess implements JavaThreadPDAccess {
|
||||
}
|
||||
if (guesser.getPC() == null) {
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP());
|
||||
} else if (VM.getVM().getInterpreter().contains(guesser.getPC())) {
|
||||
// pass the value of R13 which contains the bcp for the top level frame
|
||||
Address bcp = context.getRegisterAsAddress(AMD64ThreadContext.R13);
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC(), null, bcp);
|
||||
} else {
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC());
|
||||
}
|
||||
|
@ -107,6 +107,10 @@ public class Win32AMD64JavaThreadPDAccess implements JavaThreadPDAccess {
|
||||
}
|
||||
if (guesser.getPC() == null) {
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP());
|
||||
} else if (VM.getVM().getInterpreter().contains(guesser.getPC())) {
|
||||
// pass the value of R13 which contains the bcp for the top level frame
|
||||
Address bcp = context.getRegisterAsAddress(AMD64ThreadContext.R13);
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC(), null, bcp);
|
||||
} else {
|
||||
return new X86Frame(guesser.getSP(), guesser.getFP(), guesser.getPC());
|
||||
}
|
||||
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.io.OutputStream;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import jdk.test.lib.apps.LingeredApp;
|
||||
import jdk.test.lib.JDKToolLauncher;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.SA.SATestUtils;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.hasSA
|
||||
* @requires os.arch=="amd64" | os.arch=="x86_64"
|
||||
* @requires os.family=="windows" | os.family == "linux" | os.family == "mac"
|
||||
* @library /test/lib
|
||||
* @run main/othervm TestJhsdbJstackLineNumbers
|
||||
*/
|
||||
|
||||
/*
|
||||
* This test makes sure that SA gets the most accurate value for the line number of
|
||||
* the current (topmost) frame. Many SA ports just rely on frame->bcp, but it is
|
||||
* usually out of date since the current BCP is cached in a register and only flushed
|
||||
* to frame->bcp when the register is needed for something else. Therefore SA ports
|
||||
* need to fetch the register that the BCP is stored in and see if it is valid,
|
||||
* and only defer to frame->bcp if it is not valid.
|
||||
*
|
||||
* The test works by spawning a process that sits in a 10 line loop in the busywork() method,
|
||||
* all while the main test does repeated jstacks on the process. The expectation is
|
||||
* that at least 5 of the lines in the busywork() loop will eventually show up in at
|
||||
* least one of the jstack runs.
|
||||
*/
|
||||
|
||||
class LingeredAppWithBusyWork extends LingeredApp {
|
||||
static volatile boolean stop = false;
|
||||
|
||||
private static int busywork(int[] x) {
|
||||
int i = 0;
|
||||
while (!stop) {
|
||||
i = x[0];
|
||||
i += x[1];
|
||||
i += x[2];
|
||||
i += x[3];
|
||||
i += x[4];
|
||||
i += x[5];
|
||||
i += x[6];
|
||||
i += x[7];
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
Thread t = new Thread(() -> {
|
||||
busywork(new int[]{0,1,2,3,4,5,6,7});
|
||||
});
|
||||
|
||||
try {
|
||||
t.setName("BusyWorkThread");
|
||||
t.start();
|
||||
LingeredApp.main(args);
|
||||
stop = true;
|
||||
t.join();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TestJhsdbJstackLineNumbers {
|
||||
// This is the number of lines in the busywork main loop
|
||||
static final int TOTAL_BUSYWORK_LOOP_LINES = 10;
|
||||
// The minimum number of lines that we must at some point see in the jstack output
|
||||
static final int MIN_BUSYWORK_LOOP_LINES = 5;
|
||||
|
||||
static final int MAX_NUMBER_OF_JSTACK_RUNS = 25;
|
||||
|
||||
private static OutputAnalyzer runJstack(String... toolArgs) throws Exception {
|
||||
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb");
|
||||
launcher.addToolArg("jstack");
|
||||
if (toolArgs != null) {
|
||||
for (String toolArg : toolArgs) {
|
||||
launcher.addToolArg(toolArg);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessBuilder processBuilder = SATestUtils.createProcessBuilder(launcher);
|
||||
System.out.println(processBuilder.command().stream().collect(Collectors.joining(" ")));
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void runTest(long pid) throws Exception {
|
||||
// Keep running jstack until the target app is in the "busywork" method.
|
||||
String output;
|
||||
int maxRetries = 5;
|
||||
do {
|
||||
if (maxRetries-- == 0) {
|
||||
throw new RuntimeException("Failed: LingeredAppWithBusyWork never entered busywork() method.");
|
||||
}
|
||||
OutputAnalyzer jstackOut = runJstack("--pid", Long.toString(pid));
|
||||
output = jstackOut.getOutput();
|
||||
System.out.println(output);
|
||||
} while (!output.contains("busywork"));
|
||||
|
||||
// This is for tracking all the line numbers in busywork() that we've seen.
|
||||
// Since it is a TreeSet, it will always be sorted and have no duplicates.
|
||||
TreeSet<Integer> lineNumbersSeen = new TreeSet<Integer>();
|
||||
|
||||
// Keep running jstack until we see a sufficient number of different line
|
||||
// numbers in the busywork() loop.
|
||||
for (int x = 0; x < MAX_NUMBER_OF_JSTACK_RUNS; x++) {
|
||||
OutputAnalyzer jstackOut = runJstack("--pid", Long.toString(pid));
|
||||
output = jstackOut.getOutput();
|
||||
// The stack dump will have a line that looks like:
|
||||
// - LingeredAppWithBusyWork.busywork(int[]) @bci=32, line=74 (Interpreted frame)
|
||||
// We want to match on the line number, "74" in this example. We also match on the
|
||||
// full line just so we can print it out.
|
||||
Pattern LINE_PATTERN = Pattern.compile(
|
||||
".+(- LingeredAppWithBusyWork.busywork\\(int\\[\\]\\) \\@bci\\=[0-9]+, line\\=([0-9]+) \\(Interpreted frame\\)).+", Pattern.DOTALL);
|
||||
Matcher matcher = LINE_PATTERN.matcher(output);
|
||||
if (matcher.matches()) {
|
||||
System.out.println(matcher.group(1)); // print matching stack trace line
|
||||
int lineNum = Integer.valueOf(matcher.group(2)); // get matching line number
|
||||
lineNumbersSeen.add(lineNum);
|
||||
if (lineNumbersSeen.size() == MIN_BUSYWORK_LOOP_LINES) {
|
||||
// We're done!
|
||||
System.out.println("Found needed line numbers after " + (x+1) + " iterations");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
System.out.println("failed to match");
|
||||
System.out.println(output);
|
||||
continue; // Keep trying. This can happen on rare occasions when the stack cannot be determined.
|
||||
}
|
||||
}
|
||||
System.out.println("Found Line Numbers: " + lineNumbersSeen);
|
||||
|
||||
// Make sure we saw the minimum required number of lines in busywork().
|
||||
if (lineNumbersSeen.size() < MIN_BUSYWORK_LOOP_LINES) {
|
||||
throw new RuntimeException("Failed: Didn't find enough line numbers: " + lineNumbersSeen);
|
||||
}
|
||||
|
||||
// Make sure the distance between the lowest and highest line numbers seen
|
||||
// is not more than the number of lines in the busywork() loop.
|
||||
if (lineNumbersSeen.last() - lineNumbersSeen.first() > TOTAL_BUSYWORK_LOOP_LINES) {
|
||||
throw new RuntimeException("Failed: lowest and highest line numbers are too far apart: " + lineNumbersSeen);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work.
|
||||
|
||||
LingeredApp theApp = null;
|
||||
try {
|
||||
// Launch the LingeredAppWithBusyWork process with the busywork() loop
|
||||
theApp = new LingeredAppWithBusyWork();
|
||||
LingeredApp.startAppExactJvmOpts(theApp, "-Xint");
|
||||
System.out.println("Started LingeredApp with pid " + theApp.getPid());
|
||||
|
||||
runTest(theApp.getPid());
|
||||
} finally {
|
||||
LingeredApp.stopApp(theApp);
|
||||
System.out.println("LingeredAppWithBusyWork finished");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user