1090 lines
43 KiB
Java
1090 lines
43 KiB
Java
/*
|
|
* Copyright (c) 1998, 2016, 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. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* This source code is provided to illustrate the usage of a given feature
|
|
* or technique and has been deliberately simplified. Additional steps
|
|
* required for a production-quality application, such as security checks,
|
|
* input validation and proper error handling, might not be present in
|
|
* this sample code.
|
|
*/
|
|
|
|
|
|
package com.sun.tools.example.debug.tty;
|
|
|
|
import com.sun.jdi.*;
|
|
import com.sun.jdi.event.*;
|
|
import com.sun.jdi.request.*;
|
|
import com.sun.jdi.connect.*;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.io.*;
|
|
|
|
public class TTY implements EventNotifier {
|
|
EventHandler handler = null;
|
|
|
|
/**
|
|
* List of Strings to execute at each stop.
|
|
*/
|
|
private List<String> monitorCommands = new CopyOnWriteArrayList<>();
|
|
private int monitorCount = 0;
|
|
|
|
/**
|
|
* The name of this tool.
|
|
*/
|
|
private static final String progname = "jdb";
|
|
|
|
private volatile boolean shuttingDown = false;
|
|
|
|
public void setShuttingDown(boolean s) {
|
|
shuttingDown = s;
|
|
}
|
|
|
|
public boolean isShuttingDown() {
|
|
return shuttingDown;
|
|
}
|
|
|
|
@Override
|
|
public void vmStartEvent(VMStartEvent se) {
|
|
Thread.yield(); // fetch output
|
|
MessageOutput.lnprint("VM Started:");
|
|
}
|
|
|
|
@Override
|
|
public void vmDeathEvent(VMDeathEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void vmDisconnectEvent(VMDisconnectEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void threadStartEvent(ThreadStartEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void threadDeathEvent(ThreadDeathEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void classPrepareEvent(ClassPrepareEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void classUnloadEvent(ClassUnloadEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void breakpointEvent(BreakpointEvent be) {
|
|
Thread.yield(); // fetch output
|
|
MessageOutput.lnprint("Breakpoint hit:");
|
|
}
|
|
|
|
@Override
|
|
public void fieldWatchEvent(WatchpointEvent fwe) {
|
|
Field field = fwe.field();
|
|
ObjectReference obj = fwe.object();
|
|
Thread.yield(); // fetch output
|
|
|
|
if (fwe instanceof ModificationWatchpointEvent) {
|
|
MessageOutput.lnprint("Field access encountered before after",
|
|
new Object [] {field,
|
|
fwe.valueCurrent(),
|
|
((ModificationWatchpointEvent)fwe).valueToBe()});
|
|
} else {
|
|
MessageOutput.lnprint("Field access encountered", field.toString());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void stepEvent(StepEvent se) {
|
|
Thread.yield(); // fetch output
|
|
MessageOutput.lnprint("Step completed:");
|
|
}
|
|
|
|
@Override
|
|
public void exceptionEvent(ExceptionEvent ee) {
|
|
Thread.yield(); // fetch output
|
|
Location catchLocation = ee.catchLocation();
|
|
if (catchLocation == null) {
|
|
MessageOutput.lnprint("Exception occurred uncaught",
|
|
ee.exception().referenceType().name());
|
|
} else {
|
|
MessageOutput.lnprint("Exception occurred caught",
|
|
new Object [] {ee.exception().referenceType().name(),
|
|
Commands.locationString(catchLocation)});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void methodEntryEvent(MethodEntryEvent me) {
|
|
Thread.yield(); // fetch output
|
|
/*
|
|
* These can be very numerous, so be as efficient as possible.
|
|
* If we are stopping here, then we will see the normal location
|
|
* info printed.
|
|
*/
|
|
if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
|
|
// We are stopping; the name will be shown by the normal mechanism
|
|
MessageOutput.lnprint("Method entered:");
|
|
} else {
|
|
// We aren't stopping, show the name
|
|
MessageOutput.print("Method entered:");
|
|
printLocationOfEvent(me);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean methodExitEvent(MethodExitEvent me) {
|
|
Thread.yield(); // fetch output
|
|
/*
|
|
* These can be very numerous, so be as efficient as possible.
|
|
*/
|
|
Method mmm = Env.atExitMethod();
|
|
Method meMethod = me.method();
|
|
|
|
if (mmm == null || mmm.equals(meMethod)) {
|
|
// Either we are not tracing a specific method, or we are
|
|
// and we are exitting that method.
|
|
|
|
if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
|
|
// We will be stopping here, so do a newline
|
|
MessageOutput.println();
|
|
}
|
|
if (Env.vm().canGetMethodReturnValues()) {
|
|
MessageOutput.print("Method exitedValue:", me.returnValue() + "");
|
|
} else {
|
|
MessageOutput.print("Method exited:");
|
|
}
|
|
|
|
if (me.request().suspendPolicy() == EventRequest.SUSPEND_NONE) {
|
|
// We won't be stopping here, so show the method name
|
|
printLocationOfEvent(me);
|
|
|
|
}
|
|
|
|
// In case we want to have a one shot trace exit some day, this
|
|
// code disables the request so we don't hit it again.
|
|
if (false) {
|
|
// This is a one shot deal; we don't want to stop
|
|
// here the next time.
|
|
Env.setAtExitMethod(null);
|
|
EventRequestManager erm = Env.vm().eventRequestManager();
|
|
for (EventRequest eReq : erm.methodExitRequests()) {
|
|
if (eReq.equals(me.request())) {
|
|
eReq.disable();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// We are tracing a specific method, and this isn't it. Keep going.
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void vmInterrupted() {
|
|
Thread.yield(); // fetch output
|
|
printCurrentLocation();
|
|
for (String cmd : monitorCommands) {
|
|
StringTokenizer t = new StringTokenizer(cmd);
|
|
t.nextToken(); // get rid of monitor number
|
|
executeCommand(t);
|
|
}
|
|
MessageOutput.printPrompt();
|
|
}
|
|
|
|
@Override
|
|
public void receivedEvent(Event event) {
|
|
}
|
|
|
|
private void printBaseLocation(String threadName, Location loc) {
|
|
MessageOutput.println("location",
|
|
new Object [] {threadName,
|
|
Commands.locationString(loc)});
|
|
}
|
|
|
|
private void printCurrentLocation() {
|
|
ThreadInfo threadInfo = ThreadInfo.getCurrentThreadInfo();
|
|
StackFrame frame;
|
|
try {
|
|
frame = threadInfo.getCurrentFrame();
|
|
} catch (IncompatibleThreadStateException exc) {
|
|
MessageOutput.println("<location unavailable>");
|
|
return;
|
|
}
|
|
if (frame == null) {
|
|
MessageOutput.println("No frames on the current call stack");
|
|
} else {
|
|
Location loc = frame.location();
|
|
printBaseLocation(threadInfo.getThread().name(), loc);
|
|
// Output the current source line, if possible
|
|
if (loc.lineNumber() != -1) {
|
|
String line;
|
|
try {
|
|
line = Env.sourceLine(loc, loc.lineNumber());
|
|
} catch (java.io.IOException e) {
|
|
line = null;
|
|
}
|
|
if (line != null) {
|
|
MessageOutput.println("source line number and line",
|
|
new Object [] {new Integer(loc.lineNumber()),
|
|
line});
|
|
}
|
|
}
|
|
}
|
|
MessageOutput.println();
|
|
}
|
|
|
|
private void printLocationOfEvent(LocatableEvent theEvent) {
|
|
printBaseLocation(theEvent.thread().name(), theEvent.location());
|
|
}
|
|
|
|
void help() {
|
|
MessageOutput.println("zz help text");
|
|
}
|
|
|
|
private static final String[][] commandList = {
|
|
/*
|
|
* NOTE: this list must be kept sorted in ascending ASCII
|
|
* order by element [0]. Ref: isCommand() below.
|
|
*
|
|
*Command OK when OK when
|
|
* name disconnected? readonly?
|
|
*------------------------------------
|
|
*/
|
|
{"!!", "n", "y"},
|
|
{"?", "y", "y"},
|
|
{"bytecodes", "n", "y"},
|
|
{"catch", "y", "n"},
|
|
{"class", "n", "y"},
|
|
{"classes", "n", "y"},
|
|
{"classpath", "n", "y"},
|
|
{"clear", "y", "n"},
|
|
{"connectors", "y", "y"},
|
|
{"cont", "n", "n"},
|
|
{"disablegc", "n", "n"},
|
|
{"down", "n", "y"},
|
|
{"dump", "n", "y"},
|
|
{"enablegc", "n", "n"},
|
|
{"eval", "n", "y"},
|
|
{"exclude", "y", "n"},
|
|
{"exit", "y", "y"},
|
|
{"extension", "n", "y"},
|
|
{"fields", "n", "y"},
|
|
{"gc", "n", "n"},
|
|
{"help", "y", "y"},
|
|
{"ignore", "y", "n"},
|
|
{"interrupt", "n", "n"},
|
|
{"kill", "n", "n"},
|
|
{"lines", "n", "y"},
|
|
{"list", "n", "y"},
|
|
{"load", "n", "y"},
|
|
{"locals", "n", "y"},
|
|
{"lock", "n", "n"},
|
|
{"memory", "n", "y"},
|
|
{"methods", "n", "y"},
|
|
{"monitor", "n", "n"},
|
|
{"next", "n", "n"},
|
|
{"pop", "n", "n"},
|
|
{"print", "n", "y"},
|
|
{"quit", "y", "y"},
|
|
{"read", "y", "y"},
|
|
{"redefine", "n", "n"},
|
|
{"reenter", "n", "n"},
|
|
{"resume", "n", "n"},
|
|
{"run", "y", "n"},
|
|
{"save", "n", "n"},
|
|
{"set", "n", "n"},
|
|
{"sourcepath", "y", "y"},
|
|
{"step", "n", "n"},
|
|
{"stepi", "n", "n"},
|
|
{"stop", "y", "n"},
|
|
{"suspend", "n", "n"},
|
|
{"thread", "n", "y"},
|
|
{"threadgroup", "n", "y"},
|
|
{"threadgroups", "n", "y"},
|
|
{"threadlocks", "n", "y"},
|
|
{"threads", "n", "y"},
|
|
{"trace", "n", "n"},
|
|
{"unmonitor", "n", "n"},
|
|
{"untrace", "n", "n"},
|
|
{"unwatch", "y", "n"},
|
|
{"up", "n", "y"},
|
|
{"use", "y", "y"},
|
|
{"version", "y", "y"},
|
|
{"watch", "y", "n"},
|
|
{"where", "n", "y"},
|
|
{"wherei", "n", "y"},
|
|
};
|
|
|
|
/*
|
|
* Look up the command string in commandList.
|
|
* If found, return the index.
|
|
* If not found, return index < 0
|
|
*/
|
|
private int isCommand(String key) {
|
|
//Reference: binarySearch() in java/util/Arrays.java
|
|
// Adapted for use with String[][0].
|
|
int low = 0;
|
|
int high = commandList.length - 1;
|
|
while (low <= high) {
|
|
int mid = (low + high) >>> 1;
|
|
String midVal = commandList[mid][0];
|
|
int compare = midVal.compareTo(key);
|
|
if (compare < 0) {
|
|
low = mid + 1;
|
|
} else if (compare > 0) {
|
|
high = mid - 1;
|
|
}
|
|
else {
|
|
return mid; // key found
|
|
}
|
|
}
|
|
return -(low + 1); // key not found.
|
|
};
|
|
|
|
/*
|
|
* Return true if the command is OK when disconnected.
|
|
*/
|
|
private boolean isDisconnectCmd(int ii) {
|
|
if (ii < 0 || ii >= commandList.length) {
|
|
return false;
|
|
}
|
|
return (commandList[ii][1].equals("y"));
|
|
}
|
|
|
|
/*
|
|
* Return true if the command is OK when readonly.
|
|
*/
|
|
private boolean isReadOnlyCmd(int ii) {
|
|
if (ii < 0 || ii >= commandList.length) {
|
|
return false;
|
|
}
|
|
return (commandList[ii][2].equals("y"));
|
|
};
|
|
|
|
|
|
void executeCommand(StringTokenizer t) {
|
|
String cmd = t.nextToken().toLowerCase();
|
|
// Normally, prompt for the next command after this one is done
|
|
boolean showPrompt = true;
|
|
|
|
|
|
/*
|
|
* Anything starting with # is discarded as a no-op or 'comment'.
|
|
*/
|
|
if (!cmd.startsWith("#")) {
|
|
/*
|
|
* Next check for an integer repetition prefix. If found,
|
|
* recursively execute cmd that number of times.
|
|
*/
|
|
if (Character.isDigit(cmd.charAt(0)) && t.hasMoreTokens()) {
|
|
try {
|
|
int repeat = Integer.parseInt(cmd);
|
|
String subcom = t.nextToken("");
|
|
while (repeat-- > 0) {
|
|
executeCommand(new StringTokenizer(subcom));
|
|
showPrompt = false; // Bypass the printPrompt() below.
|
|
}
|
|
} catch (NumberFormatException exc) {
|
|
MessageOutput.println("Unrecognized command. Try help...", cmd);
|
|
}
|
|
} else {
|
|
int commandNumber = isCommand(cmd);
|
|
/*
|
|
* Check for an unknown command
|
|
*/
|
|
if (commandNumber < 0) {
|
|
MessageOutput.println("Unrecognized command. Try help...", cmd);
|
|
} else if (!Env.connection().isOpen() && !isDisconnectCmd(commandNumber)) {
|
|
MessageOutput.println("Command not valid until the VM is started with the run command",
|
|
cmd);
|
|
} else if (Env.connection().isOpen() && !Env.vm().canBeModified() &&
|
|
!isReadOnlyCmd(commandNumber)) {
|
|
MessageOutput.println("Command is not supported on a read-only VM connection",
|
|
cmd);
|
|
} else {
|
|
|
|
Commands evaluator = new Commands();
|
|
try {
|
|
if (cmd.equals("print")) {
|
|
evaluator.commandPrint(t, false);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("eval")) {
|
|
evaluator.commandPrint(t, false);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("set")) {
|
|
evaluator.commandSet(t);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("dump")) {
|
|
evaluator.commandPrint(t, true);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("locals")) {
|
|
evaluator.commandLocals();
|
|
} else if (cmd.equals("classes")) {
|
|
evaluator.commandClasses();
|
|
} else if (cmd.equals("class")) {
|
|
evaluator.commandClass(t);
|
|
} else if (cmd.equals("connectors")) {
|
|
evaluator.commandConnectors(Bootstrap.virtualMachineManager());
|
|
} else if (cmd.equals("methods")) {
|
|
evaluator.commandMethods(t);
|
|
} else if (cmd.equals("fields")) {
|
|
evaluator.commandFields(t);
|
|
} else if (cmd.equals("threads")) {
|
|
evaluator.commandThreads(t);
|
|
} else if (cmd.equals("thread")) {
|
|
evaluator.commandThread(t);
|
|
} else if (cmd.equals("suspend")) {
|
|
evaluator.commandSuspend(t);
|
|
} else if (cmd.equals("resume")) {
|
|
evaluator.commandResume(t);
|
|
} else if (cmd.equals("cont")) {
|
|
evaluator.commandCont();
|
|
} else if (cmd.equals("threadgroups")) {
|
|
evaluator.commandThreadGroups();
|
|
} else if (cmd.equals("threadgroup")) {
|
|
evaluator.commandThreadGroup(t);
|
|
} else if (cmd.equals("catch")) {
|
|
evaluator.commandCatchException(t);
|
|
} else if (cmd.equals("ignore")) {
|
|
evaluator.commandIgnoreException(t);
|
|
} else if (cmd.equals("step")) {
|
|
evaluator.commandStep(t);
|
|
} else if (cmd.equals("stepi")) {
|
|
evaluator.commandStepi();
|
|
} else if (cmd.equals("next")) {
|
|
evaluator.commandNext();
|
|
} else if (cmd.equals("kill")) {
|
|
evaluator.commandKill(t);
|
|
} else if (cmd.equals("interrupt")) {
|
|
evaluator.commandInterrupt(t);
|
|
} else if (cmd.equals("trace")) {
|
|
evaluator.commandTrace(t);
|
|
} else if (cmd.equals("untrace")) {
|
|
evaluator.commandUntrace(t);
|
|
} else if (cmd.equals("where")) {
|
|
evaluator.commandWhere(t, false);
|
|
} else if (cmd.equals("wherei")) {
|
|
evaluator.commandWhere(t, true);
|
|
} else if (cmd.equals("up")) {
|
|
evaluator.commandUp(t);
|
|
} else if (cmd.equals("down")) {
|
|
evaluator.commandDown(t);
|
|
} else if (cmd.equals("load")) {
|
|
evaluator.commandLoad(t);
|
|
} else if (cmd.equals("run")) {
|
|
evaluator.commandRun(t);
|
|
/*
|
|
* Fire up an event handler, if the connection was just
|
|
* opened. Since this was done from the run command
|
|
* we don't stop the VM on its VM start event (so
|
|
* arg 2 is false).
|
|
*/
|
|
if ((handler == null) && Env.connection().isOpen()) {
|
|
handler = new EventHandler(this, false);
|
|
}
|
|
} else if (cmd.equals("memory")) {
|
|
evaluator.commandMemory();
|
|
} else if (cmd.equals("gc")) {
|
|
evaluator.commandGC();
|
|
} else if (cmd.equals("stop")) {
|
|
evaluator.commandStop(t);
|
|
} else if (cmd.equals("clear")) {
|
|
evaluator.commandClear(t);
|
|
} else if (cmd.equals("watch")) {
|
|
evaluator.commandWatch(t);
|
|
} else if (cmd.equals("unwatch")) {
|
|
evaluator.commandUnwatch(t);
|
|
} else if (cmd.equals("list")) {
|
|
evaluator.commandList(t);
|
|
} else if (cmd.equals("lines")) { // Undocumented command: useful for testing.
|
|
evaluator.commandLines(t);
|
|
} else if (cmd.equals("classpath")) {
|
|
evaluator.commandClasspath(t);
|
|
} else if (cmd.equals("use") || cmd.equals("sourcepath")) {
|
|
evaluator.commandUse(t);
|
|
} else if (cmd.equals("monitor")) {
|
|
monitorCommand(t);
|
|
} else if (cmd.equals("unmonitor")) {
|
|
unmonitorCommand(t);
|
|
} else if (cmd.equals("lock")) {
|
|
evaluator.commandLock(t);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("threadlocks")) {
|
|
evaluator.commandThreadlocks(t);
|
|
} else if (cmd.equals("disablegc")) {
|
|
evaluator.commandDisableGC(t);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("enablegc")) {
|
|
evaluator.commandEnableGC(t);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("save")) { // Undocumented command: useful for testing.
|
|
evaluator.commandSave(t);
|
|
showPrompt = false; // asynchronous command
|
|
} else if (cmd.equals("bytecodes")) { // Undocumented command: useful for testing.
|
|
evaluator.commandBytecodes(t);
|
|
} else if (cmd.equals("redefine")) {
|
|
evaluator.commandRedefine(t);
|
|
} else if (cmd.equals("pop")) {
|
|
evaluator.commandPopFrames(t, false);
|
|
} else if (cmd.equals("reenter")) {
|
|
evaluator.commandPopFrames(t, true);
|
|
} else if (cmd.equals("extension")) {
|
|
evaluator.commandExtension(t);
|
|
} else if (cmd.equals("exclude")) {
|
|
evaluator.commandExclude(t);
|
|
} else if (cmd.equals("read")) {
|
|
readCommand(t);
|
|
} else if (cmd.equals("help") || cmd.equals("?")) {
|
|
help();
|
|
} else if (cmd.equals("version")) {
|
|
evaluator.commandVersion(progname,
|
|
Bootstrap.virtualMachineManager());
|
|
} else if (cmd.equals("quit") || cmd.equals("exit")) {
|
|
if (handler != null) {
|
|
handler.shutdown();
|
|
}
|
|
Env.shutdown();
|
|
} else {
|
|
MessageOutput.println("Unrecognized command. Try help...", cmd);
|
|
}
|
|
} catch (VMCannotBeModifiedException rovm) {
|
|
MessageOutput.println("Command is not supported on a read-only VM connection", cmd);
|
|
} catch (UnsupportedOperationException uoe) {
|
|
MessageOutput.println("Command is not supported on the target VM", cmd);
|
|
} catch (VMNotConnectedException vmnse) {
|
|
MessageOutput.println("Command not valid until the VM is started with the run command",
|
|
cmd);
|
|
} catch (Exception e) {
|
|
MessageOutput.printException("Internal exception:", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (showPrompt) {
|
|
MessageOutput.printPrompt();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Maintain a list of commands to execute each time the VM is suspended.
|
|
*/
|
|
void monitorCommand(StringTokenizer t) {
|
|
if (t.hasMoreTokens()) {
|
|
++monitorCount;
|
|
monitorCommands.add(monitorCount + ": " + t.nextToken(""));
|
|
} else {
|
|
for (String cmd : monitorCommands) {
|
|
MessageOutput.printDirectln(cmd);// Special case: use printDirectln()
|
|
}
|
|
}
|
|
}
|
|
|
|
void unmonitorCommand(StringTokenizer t) {
|
|
if (t.hasMoreTokens()) {
|
|
String monTok = t.nextToken();
|
|
int monNum;
|
|
try {
|
|
monNum = Integer.parseInt(monTok);
|
|
} catch (NumberFormatException exc) {
|
|
MessageOutput.println("Not a monitor number:", monTok);
|
|
return;
|
|
}
|
|
String monStr = monTok + ":";
|
|
for (String cmd : monitorCommands) {
|
|
StringTokenizer ct = new StringTokenizer(cmd);
|
|
if (ct.nextToken().equals(monStr)) {
|
|
monitorCommands.remove(cmd);
|
|
MessageOutput.println("Unmonitoring", cmd);
|
|
return;
|
|
}
|
|
}
|
|
MessageOutput.println("No monitor numbered:", monTok);
|
|
} else {
|
|
MessageOutput.println("Usage: unmonitor <monitor#>");
|
|
}
|
|
}
|
|
|
|
|
|
void readCommand(StringTokenizer t) {
|
|
if (t.hasMoreTokens()) {
|
|
String cmdfname = t.nextToken();
|
|
if (!readCommandFile(new File(cmdfname))) {
|
|
MessageOutput.println("Could not open:", cmdfname);
|
|
}
|
|
} else {
|
|
MessageOutput.println("Usage: read <command-filename>");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read and execute a command file. Return true if the file was read
|
|
* else false;
|
|
*/
|
|
boolean readCommandFile(File f) {
|
|
BufferedReader inFile = null;
|
|
try {
|
|
if (f.canRead()) {
|
|
// Process initial commands.
|
|
MessageOutput.println("*** Reading commands from", f.getPath());
|
|
inFile = new BufferedReader(new FileReader(f));
|
|
String ln;
|
|
while ((ln = inFile.readLine()) != null) {
|
|
StringTokenizer t = new StringTokenizer(ln);
|
|
if (t.hasMoreTokens()) {
|
|
executeCommand(t);
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
} finally {
|
|
if (inFile != null) {
|
|
try {
|
|
inFile.close();
|
|
} catch (Exception exc) {
|
|
}
|
|
}
|
|
}
|
|
return inFile != null;
|
|
}
|
|
|
|
/**
|
|
* Try to read commands from dir/fname, unless
|
|
* the canonical path passed in is the same as that
|
|
* for dir/fname.
|
|
* Return null if that file doesn't exist,
|
|
* else return the canonical path of that file.
|
|
*/
|
|
String readStartupCommandFile(String dir, String fname, String canonPath) {
|
|
File dotInitFile = new File(dir, fname);
|
|
if (!dotInitFile.exists()) {
|
|
return null;
|
|
}
|
|
|
|
String myCanonFile;
|
|
try {
|
|
myCanonFile = dotInitFile.getCanonicalPath();
|
|
} catch (IOException ee) {
|
|
MessageOutput.println("Could not open:", dotInitFile.getPath());
|
|
return null;
|
|
}
|
|
if (canonPath == null || !canonPath.equals(myCanonFile)) {
|
|
if (!readCommandFile(dotInitFile)) {
|
|
MessageOutput.println("Could not open:", dotInitFile.getPath());
|
|
}
|
|
}
|
|
return myCanonFile;
|
|
}
|
|
|
|
|
|
public TTY() throws Exception {
|
|
|
|
MessageOutput.println("Initializing progname", progname);
|
|
|
|
if (Env.connection().isOpen() && Env.vm().canBeModified()) {
|
|
/*
|
|
* Connection opened on startup. Start event handler
|
|
* immediately, telling it (through arg 2) to stop on the
|
|
* VM start event.
|
|
*/
|
|
this.handler = new EventHandler(this, true);
|
|
}
|
|
try {
|
|
BufferedReader in =
|
|
new BufferedReader(new InputStreamReader(System.in));
|
|
|
|
String lastLine = null;
|
|
|
|
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
|
|
|
|
/*
|
|
* Read start up files. This mimics the behavior
|
|
* of gdb which will read both ~/.gdbinit and then
|
|
* ./.gdbinit if they exist. We have the twist that
|
|
* we allow two different names, so we do this:
|
|
* if ~/jdb.ini exists,
|
|
* read it
|
|
* else if ~/.jdbrc exists,
|
|
* read it
|
|
*
|
|
* if ./jdb.ini exists,
|
|
* if it hasn't been read, read it
|
|
* It could have been read above because ~ == .
|
|
* or because of symlinks, ...
|
|
* else if ./jdbrx exists
|
|
* if it hasn't been read, read it
|
|
*/
|
|
{
|
|
String userHome = System.getProperty("user.home");
|
|
String canonPath;
|
|
|
|
if ((canonPath = readStartupCommandFile(userHome, "jdb.ini", null)) == null) {
|
|
// Doesn't exist, try alternate spelling
|
|
canonPath = readStartupCommandFile(userHome, ".jdbrc", null);
|
|
}
|
|
|
|
String userDir = System.getProperty("user.dir");
|
|
if (readStartupCommandFile(userDir, "jdb.ini", canonPath) == null) {
|
|
// Doesn't exist, try alternate spelling
|
|
readStartupCommandFile(userDir, ".jdbrc", canonPath);
|
|
}
|
|
}
|
|
|
|
// Process interactive commands.
|
|
MessageOutput.printPrompt();
|
|
while (true) {
|
|
String ln = in.readLine();
|
|
if (ln == null) {
|
|
/*
|
|
* Jdb is being shutdown because debuggee exited, ignore any 'null'
|
|
* returned by readLine() during shutdown. JDK-8154144.
|
|
*/
|
|
if (!isShuttingDown()) {
|
|
MessageOutput.println("Input stream closed.");
|
|
}
|
|
ln = "quit";
|
|
}
|
|
|
|
if (ln.startsWith("!!") && lastLine != null) {
|
|
ln = lastLine + ln.substring(2);
|
|
MessageOutput.printDirectln(ln);// Special case: use printDirectln()
|
|
}
|
|
|
|
StringTokenizer t = new StringTokenizer(ln);
|
|
if (t.hasMoreTokens()) {
|
|
lastLine = ln;
|
|
executeCommand(t);
|
|
} else {
|
|
MessageOutput.printPrompt();
|
|
}
|
|
}
|
|
} catch (VMDisconnectedException e) {
|
|
handler.handleDisconnectedException();
|
|
}
|
|
}
|
|
|
|
private static void usage() {
|
|
MessageOutput.println("zz usage text", new Object [] {progname,
|
|
File.pathSeparator});
|
|
System.exit(1);
|
|
}
|
|
|
|
static void usageError(String messageKey) {
|
|
MessageOutput.println(messageKey);
|
|
MessageOutput.println();
|
|
usage();
|
|
}
|
|
|
|
static void usageError(String messageKey, String argument) {
|
|
MessageOutput.println(messageKey, argument);
|
|
MessageOutput.println();
|
|
usage();
|
|
}
|
|
|
|
private static boolean supportsSharedMemory() {
|
|
for (Connector connector :
|
|
Bootstrap.virtualMachineManager().allConnectors()) {
|
|
if (connector.transport() == null) {
|
|
continue;
|
|
}
|
|
if ("dt_shmem".equals(connector.transport().name())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static String addressToSocketArgs(String address) {
|
|
int index = address.indexOf(':');
|
|
if (index != -1) {
|
|
String hostString = address.substring(0, index);
|
|
String portString = address.substring(index + 1);
|
|
return "hostname=" + hostString + ",port=" + portString;
|
|
} else {
|
|
return "port=" + address;
|
|
}
|
|
}
|
|
|
|
private static boolean hasWhitespace(String string) {
|
|
int length = string.length();
|
|
for (int i = 0; i < length; i++) {
|
|
if (Character.isWhitespace(string.charAt(i))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static String addArgument(String string, String argument) {
|
|
if (hasWhitespace(argument) || argument.indexOf(',') != -1) {
|
|
// Quotes were stripped out for this argument, add 'em back.
|
|
StringBuffer buffer = new StringBuffer(string);
|
|
buffer.append('"');
|
|
for (int i = 0; i < argument.length(); i++) {
|
|
char c = argument.charAt(i);
|
|
if (c == '"') {
|
|
buffer.append('\\');
|
|
}
|
|
buffer.append(c);
|
|
}
|
|
buffer.append("\" ");
|
|
return buffer.toString();
|
|
} else {
|
|
return string + argument + ' ';
|
|
}
|
|
}
|
|
|
|
public static void main(String argv[]) throws MissingResourceException {
|
|
String cmdLine = "";
|
|
String javaArgs = "";
|
|
int traceFlags = VirtualMachine.TRACE_NONE;
|
|
boolean launchImmediately = false;
|
|
String connectSpec = null;
|
|
|
|
MessageOutput.textResources = ResourceBundle.getBundle
|
|
("com.sun.tools.example.debug.tty.TTYResources",
|
|
Locale.getDefault());
|
|
|
|
for (int i = 0; i < argv.length; i++) {
|
|
String token = argv[i];
|
|
if (token.equals("-dbgtrace")) {
|
|
if ((i == argv.length - 1) ||
|
|
! Character.isDigit(argv[i+1].charAt(0))) {
|
|
traceFlags = VirtualMachine.TRACE_ALL;
|
|
} else {
|
|
String flagStr = "";
|
|
try {
|
|
flagStr = argv[++i];
|
|
traceFlags = Integer.decode(flagStr).intValue();
|
|
} catch (NumberFormatException nfe) {
|
|
usageError("dbgtrace flag value must be an integer:",
|
|
flagStr);
|
|
return;
|
|
}
|
|
}
|
|
} else if (token.equals("-X")) {
|
|
usageError("Use java minus X to see");
|
|
return;
|
|
} else if (
|
|
// Standard VM options passed on
|
|
token.equals("-v") || token.startsWith("-v:") || // -v[:...]
|
|
token.startsWith("-verbose") || // -verbose[:...]
|
|
token.startsWith("-D") ||
|
|
// -classpath handled below
|
|
// NonStandard options passed on
|
|
token.startsWith("-X") ||
|
|
// Old-style options (These should remain in place as long as
|
|
// the standard VM accepts them)
|
|
token.equals("-noasyncgc") || token.equals("-prof") ||
|
|
token.equals("-verify") || token.equals("-noverify") ||
|
|
token.equals("-verifyremote") ||
|
|
token.equals("-verbosegc") ||
|
|
token.startsWith("-ms") || token.startsWith("-mx") ||
|
|
token.startsWith("-ss") || token.startsWith("-oss") ) {
|
|
|
|
javaArgs = addArgument(javaArgs, token);
|
|
} else if (token.equals("-tclassic")) {
|
|
usageError("Classic VM no longer supported.");
|
|
return;
|
|
} else if (token.equals("-tclient")) {
|
|
// -client must be the first one
|
|
javaArgs = "-client " + javaArgs;
|
|
} else if (token.equals("-tserver")) {
|
|
// -server must be the first one
|
|
javaArgs = "-server " + javaArgs;
|
|
} else if (token.equals("-sourcepath")) {
|
|
if (i == (argv.length - 1)) {
|
|
usageError("No sourcepath specified.");
|
|
return;
|
|
}
|
|
Env.setSourcePath(argv[++i]);
|
|
} else if (token.equals("-classpath")) {
|
|
if (i == (argv.length - 1)) {
|
|
usageError("No classpath specified.");
|
|
return;
|
|
}
|
|
javaArgs = addArgument(javaArgs, token);
|
|
javaArgs = addArgument(javaArgs, argv[++i]);
|
|
} else if (token.equals("-attach")) {
|
|
if (connectSpec != null) {
|
|
usageError("cannot redefine existing connection", token);
|
|
return;
|
|
}
|
|
if (i == (argv.length - 1)) {
|
|
usageError("No attach address specified.");
|
|
return;
|
|
}
|
|
String address = argv[++i];
|
|
|
|
/*
|
|
* -attach is shorthand for one of the reference implementation's
|
|
* attaching connectors. Use the shared memory attach if it's
|
|
* available; otherwise, use sockets. Build a connect
|
|
* specification string based on this decision.
|
|
*/
|
|
if (supportsSharedMemory()) {
|
|
connectSpec = "com.sun.jdi.SharedMemoryAttach:name=" +
|
|
address;
|
|
} else {
|
|
String suboptions = addressToSocketArgs(address);
|
|
connectSpec = "com.sun.jdi.SocketAttach:" + suboptions;
|
|
}
|
|
} else if (token.equals("-listen") || token.equals("-listenany")) {
|
|
if (connectSpec != null) {
|
|
usageError("cannot redefine existing connection", token);
|
|
return;
|
|
}
|
|
String address = null;
|
|
if (token.equals("-listen")) {
|
|
if (i == (argv.length - 1)) {
|
|
usageError("No attach address specified.");
|
|
return;
|
|
}
|
|
address = argv[++i];
|
|
}
|
|
|
|
/*
|
|
* -listen[any] is shorthand for one of the reference implementation's
|
|
* listening connectors. Use the shared memory listen if it's
|
|
* available; otherwise, use sockets. Build a connect
|
|
* specification string based on this decision.
|
|
*/
|
|
if (supportsSharedMemory()) {
|
|
connectSpec = "com.sun.jdi.SharedMemoryListen:";
|
|
if (address != null) {
|
|
connectSpec += ("name=" + address);
|
|
}
|
|
} else {
|
|
connectSpec = "com.sun.jdi.SocketListen:";
|
|
if (address != null) {
|
|
connectSpec += addressToSocketArgs(address);
|
|
}
|
|
}
|
|
} else if (token.equals("-launch")) {
|
|
launchImmediately = true;
|
|
} else if (token.equals("-listconnectors")) {
|
|
Commands evaluator = new Commands();
|
|
evaluator.commandConnectors(Bootstrap.virtualMachineManager());
|
|
return;
|
|
} else if (token.equals("-connect")) {
|
|
/*
|
|
* -connect allows the user to pick the connector
|
|
* used in bringing up the target VM. This allows
|
|
* use of connectors other than those in the reference
|
|
* implementation.
|
|
*/
|
|
if (connectSpec != null) {
|
|
usageError("cannot redefine existing connection", token);
|
|
return;
|
|
}
|
|
if (i == (argv.length - 1)) {
|
|
usageError("No connect specification.");
|
|
return;
|
|
}
|
|
connectSpec = argv[++i];
|
|
} else if (token.equals("-help")) {
|
|
usage();
|
|
} else if (token.equals("-version")) {
|
|
Commands evaluator = new Commands();
|
|
evaluator.commandVersion(progname,
|
|
Bootstrap.virtualMachineManager());
|
|
System.exit(0);
|
|
} else if (token.startsWith("-")) {
|
|
usageError("invalid option", token);
|
|
return;
|
|
} else {
|
|
// Everything from here is part of the command line
|
|
cmdLine = addArgument("", token);
|
|
for (i++; i < argv.length; i++) {
|
|
cmdLine = addArgument(cmdLine, argv[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unless otherwise specified, set the default connect spec.
|
|
*/
|
|
|
|
/*
|
|
* Here are examples of jdb command lines and how the options
|
|
* are interpreted as arguments to the program being debugged.
|
|
* arg1 arg2
|
|
* ---- ----
|
|
* jdb hello a b a b
|
|
* jdb hello "a b" a b
|
|
* jdb hello a,b a,b
|
|
* jdb hello a, b a, b
|
|
* jdb hello "a, b" a, b
|
|
* jdb -connect "com.sun.jdi.CommandLineLaunch:main=hello a,b" illegal
|
|
* jdb -connect com.sun.jdi.CommandLineLaunch:main=hello "a,b" illegal
|
|
* jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a,b"' arg1 = a,b
|
|
* jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a b"' arg1 = a b
|
|
* jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello a b' arg1 = a arg2 = b
|
|
* jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a," b' arg1 = a, arg2 = b
|
|
*/
|
|
if (connectSpec == null) {
|
|
connectSpec = "com.sun.jdi.CommandLineLaunch:";
|
|
} else if (!connectSpec.endsWith(",") && !connectSpec.endsWith(":")) {
|
|
connectSpec += ","; // (Bug ID 4285874)
|
|
}
|
|
|
|
cmdLine = cmdLine.trim();
|
|
javaArgs = javaArgs.trim();
|
|
|
|
if (cmdLine.length() > 0) {
|
|
if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
|
|
usageError("Cannot specify command line with connector:",
|
|
connectSpec);
|
|
return;
|
|
}
|
|
connectSpec += "main=" + cmdLine + ",";
|
|
}
|
|
|
|
if (javaArgs.length() > 0) {
|
|
if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
|
|
usageError("Cannot specify target vm arguments with connector:",
|
|
connectSpec);
|
|
return;
|
|
}
|
|
connectSpec += "options=" + javaArgs + ",";
|
|
}
|
|
|
|
try {
|
|
if (! connectSpec.endsWith(",")) {
|
|
connectSpec += ","; // (Bug ID 4285874)
|
|
}
|
|
Env.init(connectSpec, launchImmediately, traceFlags);
|
|
new TTY();
|
|
} catch(Exception e) {
|
|
MessageOutput.printException("Internal exception:", e);
|
|
}
|
|
}
|
|
}
|