8219143: jdb should support breakpoint thread filters

Add thread filter to stop command.

Reviewed-by: sspitsyn, amenkov
This commit is contained in:
Chris Plummer 2019-02-25 18:54:40 -08:00
parent ab5cedabe1
commit b0eedd125f
6 changed files with 291 additions and 51 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2019, 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
@ -44,20 +44,24 @@ class BreakpointSpec extends EventRequestSpec {
String methodId;
List<String> methodArgs;
int lineNumber;
ThreadReference threadFilter; /* Thread to break in. null if global breakpoint. */
public static final String locationTokenDelimiter = ":( \t\n\r";
BreakpointSpec(ReferenceTypeSpec refSpec, int lineNumber) {
BreakpointSpec(ReferenceTypeSpec refSpec, int lineNumber, ThreadReference threadFilter) {
super(refSpec);
this.methodId = null;
this.methodArgs = null;
this.lineNumber = lineNumber;
this.threadFilter = threadFilter;
}
BreakpointSpec(ReferenceTypeSpec refSpec, String methodId,
BreakpointSpec(ReferenceTypeSpec refSpec, String methodId, ThreadReference threadFilter,
List<String> methodArgs) throws MalformedMemberNameException {
super(refSpec);
this.methodId = methodId;
this.methodArgs = methodArgs;
this.lineNumber = 0;
this.threadFilter = threadFilter;
if (!isValidMethodName(methodId)) {
throw new MalformedMemberNameException(methodId);
}
@ -78,8 +82,11 @@ class BreakpointSpec extends EventRequestSpec {
throw new InvalidTypeException();
}
EventRequestManager em = refType.virtualMachine().eventRequestManager();
EventRequest bp = em.createBreakpointRequest(location);
BreakpointRequest bp = em.createBreakpointRequest(location);
bp.setSuspendPolicy(suspendPolicy);
if (threadFilter != null) {
bp.addThreadFilter(threadFilter);
}
bp.enable();
return bp;
}
@ -104,7 +111,8 @@ class BreakpointSpec extends EventRequestSpec {
public int hashCode() {
return refSpec.hashCode() + lineNumber +
((methodId != null) ? methodId.hashCode() : 0) +
((methodArgs != null) ? methodArgs.hashCode() : 0);
((methodArgs != null) ? methodArgs.hashCode() : 0) +
((threadFilter != null) ? threadFilter.hashCode() : 0);
}
@Override
@ -118,6 +126,9 @@ class BreakpointSpec extends EventRequestSpec {
((methodArgs != null) ?
methodArgs.equals(breakpoint.methodArgs)
: methodArgs == breakpoint.methodArgs) &&
((threadFilter != null) ?
threadFilter.equals(breakpoint.threadFilter)
: threadFilter == breakpoint.threadFilter) &&
refSpec.equals(breakpoint.refSpec) &&
(lineNumber == breakpoint.lineNumber);
} else {

View File

@ -1037,16 +1037,16 @@ class Commands {
}
private void printBreakpointCommandUsage(String atForm, String inForm) {
MessageOutput.println("printbreakpointcommandusage",
new Object [] {atForm, inForm});
private void printBreakpointCommandUsage(String usageMessage) {
MessageOutput.println(usageMessage);
}
protected BreakpointSpec parseBreakpointSpec(StringTokenizer t,
String atForm, String inForm) {
protected BreakpointSpec parseBreakpointSpec(StringTokenizer t, String next_token,
ThreadReference threadFilter,
String usageMessage) {
BreakpointSpec breakpoint = null;
try {
String token = t.nextToken(":( \t\n\r");
String token = next_token;
// We can't use hasMoreTokens here because it will cause any leading
// paren to be lost.
@ -1064,16 +1064,24 @@ class Commands {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setParseIntegerOnly(true);
Number n = nf.parse(lineToken);
Number n;
try {
n = nf.parse(lineToken);
} catch (java.text.ParseException pe) {
MessageOutput.println("Invalid line number specified");
printBreakpointCommandUsage(usageMessage);
return null;
}
int lineNumber = n.intValue();
if (t.hasMoreTokens()) {
printBreakpointCommandUsage(atForm, inForm);
MessageOutput.println("Extra tokens after breakpoint location");
printBreakpointCommandUsage(usageMessage);
return null;
}
try {
breakpoint = Env.specList.createBreakpoint(classId,
lineNumber);
lineNumber, threadFilter);
} catch (ClassNotFoundException exc) {
MessageOutput.println("is not a valid class name", classId);
}
@ -1082,7 +1090,8 @@ class Commands {
int idot = token.lastIndexOf('.');
if ( (idot <= 0) || /* No dot or dot in first char */
(idot >= token.length() - 1) ) { /* dot in last char */
printBreakpointCommandUsage(atForm, inForm);
MessageOutput.println("Invalid <class>.<method_name> specification");
printBreakpointCommandUsage(usageMessage);
return null;
}
String methodName = token.substring(idot + 1);
@ -1090,9 +1099,9 @@ class Commands {
List<String> argumentList = null;
if (rest != null) {
if (!rest.startsWith("(") || !rest.endsWith(")")) {
MessageOutput.println("Invalid method specification:",
MessageOutput.println("Invalid <method_name> specification:",
methodName + rest);
printBreakpointCommandUsage(atForm, inForm);
printBreakpointCommandUsage(usageMessage);
return null;
}
// Trim the parens
@ -1107,6 +1116,7 @@ class Commands {
try {
breakpoint = Env.specList.createBreakpoint(classId,
methodName,
threadFilter,
argumentList);
} catch (MalformedMemberNameException exc) {
MessageOutput.println("is not a valid method name", methodName);
@ -1115,7 +1125,7 @@ class Commands {
}
}
} catch (Exception e) {
printBreakpointCommandUsage(atForm, inForm);
printBreakpointCommandUsage(usageMessage);
return null;
}
return breakpoint;
@ -1145,33 +1155,74 @@ class Commands {
}
void commandStop(StringTokenizer t) {
String atIn;
byte suspendPolicy = EventRequest.SUSPEND_ALL;
ThreadReference threadFilter = null;
if (t.hasMoreTokens()) {
atIn = t.nextToken();
if (atIn.equals("go") && t.hasMoreTokens()) {
suspendPolicy = EventRequest.SUSPEND_NONE;
atIn = t.nextToken();
} else if (atIn.equals("thread") && t.hasMoreTokens()) {
suspendPolicy = EventRequest.SUSPEND_EVENT_THREAD;
atIn = t.nextToken();
}
} else {
/*
* Allowed syntax:
* stop [go|thread] [<thread_id>] <at|in> <location>
* If no options are given, the current list of breakpoints is printed.
* If "go" is specified, then immediately resume after stopping. No threads are suspended.
* If "thread" is specified, then only suspend the thread we stop in.
* If neither "go" nor "thread" are specified, then suspend all threads.
* If an integer <thread_id> is specified, then only stop in the specified thread.
* <location> can either be a line number or a method:
* - <class id>:<line>
* - <class id>.<method>[(argument_type,...)]
*/
if (!t.hasMoreTokens()) {
listBreakpoints();
return;
}
BreakpointSpec spec = parseBreakpointSpec(t, "stop at", "stop in");
if (spec != null) {
// Enforcement of "at" vs. "in". The distinction is really
// unnecessary and we should consider not checking for this
// (and making "at" and "in" optional).
if (atIn.equals("at") && spec.isMethodBreakpoint()) {
MessageOutput.println("Use stop at to set a breakpoint at a line number");
printBreakpointCommandUsage("stop at", "stop in");
String token = t.nextToken();
/* Check for "go" or "thread" modifiers. */
if (token.equals("go") && t.hasMoreTokens()) {
suspendPolicy = EventRequest.SUSPEND_NONE;
token = t.nextToken();
} else if (token.equals("thread") && t.hasMoreTokens()) {
suspendPolicy = EventRequest.SUSPEND_EVENT_THREAD;
token = t.nextToken();
}
/* Handle <thread_id> modifier. */
if (!token.equals("at") && !token.equals("in")) {
Long threadid;
try {
threadid = Long.decode(token);
} catch (NumberFormatException nfe) {
MessageOutput.println("Expected at, in, or an integer <thread_id>:", token);
printBreakpointCommandUsage("printstopcommandusage");
return;
}
try {
ThreadInfo threadInfo = ThreadInfo.getThreadInfo(token);
if (threadInfo == null) {
MessageOutput.println("Invalid <thread_id>:", token);
return;
}
threadFilter = threadInfo.getThread();
token = t.nextToken(BreakpointSpec.locationTokenDelimiter);
} catch (VMNotConnectedException vmnce) {
MessageOutput.println("<thread_id> option not valid until the VM is started with the run command");
return;
}
}
/* Make sure "at" or "in" comes next. */
if (!token.equals("at") && !token.equals("in")) {
MessageOutput.println("Missing at or in");
printBreakpointCommandUsage("printstopcommandusage");
return;
}
token = t.nextToken(BreakpointSpec.locationTokenDelimiter);
BreakpointSpec spec = parseBreakpointSpec(t, token, threadFilter, "printstopcommandusage");
if (spec != null) {
spec.suspendPolicy = suspendPolicy;
resolveNow(spec);
}
@ -1183,7 +1234,8 @@ class Commands {
return;
}
BreakpointSpec spec = parseBreakpointSpec(t, "clear", "clear");
String token = t.nextToken(BreakpointSpec.locationTokenDelimiter);
BreakpointSpec spec = parseBreakpointSpec(t, token, null, "printclearcommandusage");
if (spec != null) {
if (Env.specList.delete(spec)) {
MessageOutput.println("Removed:", spec.toString());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2019, 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
@ -36,6 +36,7 @@ package com.sun.tools.example.debug.tty;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.ThreadReference;
import java.util.ArrayList;
import java.util.Collections;
@ -108,21 +109,21 @@ class EventRequestSpecList {
}
}
BreakpointSpec createBreakpoint(String classPattern, int line)
BreakpointSpec createBreakpoint(String classPattern, int line, ThreadReference threadFilter)
throws ClassNotFoundException {
ReferenceTypeSpec refSpec =
new PatternReferenceTypeSpec(classPattern);
return new BreakpointSpec(refSpec, line);
return new BreakpointSpec(refSpec, line, threadFilter);
}
BreakpointSpec createBreakpoint(String classPattern,
String methodId,
String methodId, ThreadReference threadFilter,
List<String> methodArgs)
throws MalformedMemberNameException,
ClassNotFoundException {
ReferenceTypeSpec refSpec =
new PatternReferenceTypeSpec(classPattern);
return new BreakpointSpec(refSpec, methodId, methodArgs);
return new BreakpointSpec(refSpec, methodId, threadFilter, methodArgs);
}
EventRequestSpec createExceptionCatch(String classPattern,

View File

@ -120,12 +120,14 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Exception occurred caught", "Exception occurred: {0} (to be caught at: {1})"},
{"Exception occurred uncaught", "Exception occurred: {0} (uncaught)"},
{"Exceptions caught:", "Break when these exceptions occur:"},
{"Expected at, in, or an integer <thread_id>:", "Expected \"at\", \"in\", or an integer <thread_id>: {0}"},
{"expr is null", "{0} = null"},
{"expr is value", "{0} = {1}"},
{"expr is value <collected>", " {0} = {1} <collected>"},
{"Expression cannot be void", "Expression cannot be void"},
{"Expression must evaluate to an object", "Expression must evaluate to an object"},
{"extends:", "extends: {0}"},
{"Extra tokens after breakpoint location", "Extra tokens after breakpoint location"},
{"Failed reading output", "Failed reading output of child java interpreter."},
{"Fatal error", "Fatal error:"},
{"Field access encountered before after", "Field ({0}) is {1}, will be {2}: "},
@ -154,11 +156,14 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Invalid connect type", "Invalid connect type"},
{"Invalid consecutive invocations", "Invalid consecutive invocations"},
{"Invalid exception object", "Invalid exception object"},
{"Invalid method specification:", "Invalid method specification: {0}"},
{"Invalid line number specified", "Invalid line number specified"},
{"Invalid <method_name> specification:", "Invalid <method_name> specification: {0}"},
{"Invalid option on class command", "Invalid option on class command"},
{"invalid option", "invalid option: {0}"},
{"Invalid thread status.", "Invalid thread status."},
{"Invalid <thread_id>:", "Invalid <thread_id>: {0}"},
{"Invalid transport name:", "Invalid transport name: {0}"},
{"Invalid <class>.<method_name> specification", "Invalid <class>.<method_name> specification"},
{"I/O exception occurred:", "I/O Exception occurred: {0}"},
{"is an ambiguous method name in", "\"{0}\" is an ambiguous method name in \"{1}\""},
{"is an invalid line number for", "{0,number,integer} is an invalid line number for {1}"},
@ -191,6 +196,7 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Method exitedValue:", "Method exited: return value = {0}, "},
{"Method is overloaded; specify arguments", "Method {0} is overloaded; specify arguments"},
{"minus version", "This is {0} version {1,number,integer}.{2,number,integer} (Java SE version {3})"},
{"Missing at or in", "Missing \"at\" or \"in\""},
{"Monitor information for thread", "Monitor information for thread {0}:"},
{"Monitor information for expr", "Monitor information for {0} ({1}):"},
{"More than one class named", "More than one class named: ''{0}''"},
@ -241,7 +247,18 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Owned by:", " Owned by: {0}, entry count: {1,number,integer}"},
{"Owned monitor:", " Owned monitor: {0}"},
{"Parse exception:", "Parse Exception: {0}"},
{"printbreakpointcommandusage", "Usage: {0} <class>:<line_number> or\n {1} <class>.<method_name>[(argument_type,...)]"},
{"printclearcommandusage", "Usage clear <class>:<line_number> or\n clear <class>.<method_name>[(argument_type,...)]"},
{"printstopcommandusage",
"Usage: stop [go|thread] [<thread_id>] <at|in> <location>\n" +
" If \"go\" is specified, immediately resume after stopping\n" +
" If \"thread\" is specified, only suspend the thread we stop in\n" +
" If neither \"go\" nor \"thread\" are specified, suspend all threads\n" +
" If an integer <thread_id> is specified, only stop in the specified thread\n" +
" \"at\" and \"in\" have the same meaning\n" +
" <location> can either be a line number or a method:\n" +
" <class_id>:<line_number>\n" +
" <class_id>.<method>[(argument_type,...)]"
},
{"Removed:", "Removed: {0}"},
{"Requested stack frame is no longer active:", "Requested stack frame is no longer active: {0,number,integer}"},
{"run <args> command is valid only with launched VMs", "'run <args>' command is valid only with launched VMs"},
@ -292,6 +309,8 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Thread not suspended", "Thread not suspended"},
{"thread group number description name", "{0,number,integer}. {1} {2}"},
{"Threadgroup name not specified.", "Threadgroup name not specified."},
{"<thread_id> option not valid until the VM is started with the run command",
"<thread_id> option not valid until the VM is started with the run command"},
{"Threads must be suspended", "Threads must be suspended"},
{"trace method exit in effect for", "trace method exit in effect for {0}"},
{"trace method exits in effect", "trace method exits in effect"},
@ -318,7 +337,6 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Usage: unmonitor <monitor#>", "Usage: unmonitor <monitor#>"},
{"Usage: up [n frames]", "Usage: up [n frames]"},
{"Use java minus X to see", "Use 'java -X' to see the available non-standard options"},
{"Use stop at to set a breakpoint at a line number", "Use 'stop at' to set a breakpoint at a line number"},
{"VM already running. use cont to continue after events.", "VM already running. Use 'cont' to continue after events."},
{"VM Started:", "VM Started: "},
{"vmstartexception", "VM start exception: {0}"},
@ -357,9 +375,17 @@ public class TTYResources extends java.util.ListResourceBundle {
"threadgroups -- list threadgroups\n" +
"threadgroup <name> -- set current threadgroup\n" +
"\n" +
"stop in <class id>.<method>[(argument_type,...)]\n" +
" -- set a breakpoint in a method\n" +
"stop at <class id>:<line> -- set a breakpoint at a line\n" +
"stop [go|thread] [<thread_id>] <at|in> <location>\n" +
" -- set a breakpoint\n" +
" -- if no options are given, the current list of breakpoints is printed\n" +
" -- if \"go\" is specified, immediately resume after stopping\n" +
" -- if \"thread\" is specified, only suspend the thread we stop in\n" +
" -- if neither \"go\" nor \"thread\" are specified, suspend all threads\n" +
" -- if an integer <thread_id> is specified, only stop in the specified thread\n" +
" -- \"at\" and \"in\" have the same meaning\n" +
" -- <location> can either be a line number or a method:\n" +
" -- <class_id>:<line_number>\n" +
" -- <class_id>.<method>[(argument_type,...)]\n" +
"clear <class id>.<method>[(argument_type,...)]\n" +
" -- clear a breakpoint in a method\n" +
"clear <class id>:<line> -- clear a breakpoint at a line\n" +
@ -412,7 +438,7 @@ public class TTYResources extends java.util.ListResourceBundle {
"<n> <command> -- repeat command n times\n" +
"# <command> -- discard (no-op)\n" +
"help (or ?) -- list commands\n" +
"dbgtrace [flag] -- same as dbgtrace command line option" +
"dbgtrace [flag] -- same as dbgtrace command line option\n" +
"version -- print version information\n" +
"exit (or quit) -- exit debugger\n" +
"\n" +

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2019, 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.
*/
/*
* @test
* @bug 8219143
* @summary Tests that using the "stop in" threadid option will properly cause the
* breakpoint to only be triggered when hit in the specified thread.
*
* @library /test/lib
* @run compile -g JdbStopThreadidTest.java
* @run main/othervm JdbStopThreadidTest
*/
import lib.jdb.Jdb;
import lib.jdb.JdbCommand;
import lib.jdb.JdbTest;
import java.util.regex.*;
class JdbStopThreadidTestTarg {
static Object lockObj = new Object();
public static void main(String[] args) {
test();
}
private static void test() {
JdbStopThreadidTestTarg test = new JdbStopThreadidTestTarg();
MyThread myThread1 = test.new MyThread("MYTHREAD-1");
MyThread myThread2 = test.new MyThread("MYTHREAD-2");
MyThread myThread3 = test.new MyThread("MYTHREAD-3");
synchronized (lockObj) {
myThread1.start();
myThread2.start();
myThread3.start();
// Wait for all threads to have started. Note they all block on lockObj after starting.
while (!myThread1.started || !myThread2.started || !myThread3.started) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
// Stop here so the test can setup the breakpoint in MYTHREAD-2
brkMethod();
}
// Wait for all threads to finish before exiting
try {
myThread1.join();
myThread2.join();
myThread3.join();
} catch (InterruptedException e) {
}
}
static void brkMethod() {
}
public static void print(Object obj) {
System.out.println(obj);
}
class MyThread extends Thread {
volatile boolean started = false;
public MyThread(String name) {
super(name);
}
public void run() {
started = true;
synchronized (JdbStopThreadidTestTarg.lockObj) {
}
brkMethod();
}
void brkMethod() {
}
}
}
public class JdbStopThreadidTest extends JdbTest {
public static void main(String argv[]) {
new JdbStopThreadidTest().run();
}
private JdbStopThreadidTest() {
super(DEBUGGEE_CLASS);
}
private static final String DEBUGGEE_CLASS = JdbStopThreadidTestTarg.class.getName();
private static final String DEBUGGEE_THREAD_CLASS = JdbStopThreadidTestTarg.class.getName() + "$MyThread";
private static Pattern threadidPattern = Pattern.compile("MyThread\\)(\\S+)\\s+MYTHREAD-2");
@Override
protected void runCases() {
jdb.command(JdbCommand.stopIn(DEBUGGEE_CLASS, "brkMethod"));
jdb.command(JdbCommand.run().waitForPrompt("Breakpoint hit: \"thread=main\"", true));
jdb.command(JdbCommand.threads());
// Find the threadid for MYTHREAD-2 in the "threads" command output
String output = jdb.getJdbOutput();
Matcher m = threadidPattern.matcher(output);
String threadid = null;
if (m.find()) {
threadid = m.group(1);
} else {
throw new RuntimeException("FAILED: Did not match threadid pattern.");
}
// Setup a breakpoint in MYTHREAD-2.
jdb.command(JdbCommand.stopInThreadid(DEBUGGEE_THREAD_CLASS, "brkMethod", threadid));
// Continue until MYTHREAD-2 breakpoint is hit. If we hit any other breakpoint before
// then (we aren't suppose to), then this test will fail.
jdb.command(JdbCommand.cont().waitForPrompt("Breakpoint hit: \"thread=MYTHREAD-2\", \\S+MyThread.brkMethod", true));
// Continue until the application exits. Once again, hitting a breakpoint will cause
// a failure because we are not suppose to hit one.
jdb.command(JdbCommand.cont().waitForPrompt(Jdb.APPLICATION_EXIT, true));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2019, 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
@ -162,6 +162,9 @@ public class JdbCommand {
public static JdbCommand stopIn(String targetClass, String methodName) {
return new JdbCommand("stop in " + targetClass + "." + methodName);
}
public static JdbCommand stopInThreadid(String targetClass, String methodName, String threadid) {
return new JdbCommand("stop " + threadid + " in " + targetClass + "." + methodName);
}
public static JdbCommand thread(int threadNumber) {
return new JdbCommand("thread " + threadNumber);
}
@ -226,6 +229,10 @@ public class JdbCommand {
return new JdbCommand("methods " + classId);
}
public static JdbCommand threads() {
return new JdbCommand("threads");
}
// trace [go] methods [thread]
// -- trace method entries and exits.
// -- All threads are suspended unless 'go' is specified