package xtc.lang.blink;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.concurrent.LinkedBlockingQueue;
import xtc.lang.blink.CallStack.JavaCallFrame;
import xtc.lang.blink.Event.DummyCallCompletionEvent;
import xtc.lang.blink.Event.J2CBreakPointHitEvent;
import xtc.lang.blink.Event.Java2NativeCallEvent;
import xtc.lang.blink.Event.Java2NativeCompletionEvent;
import xtc.lang.blink.Event.Java2NativeReturnEvent;
import xtc.lang.blink.Event.JavaStepCompletionEvent;
import xtc.lang.blink.Event.LanguageTransitionEvent;
import xtc.lang.blink.Event.Native2JavaCallEvent;
import xtc.lang.blink.Event.Native2JavaCompletionEvent;
import xtc.lang.blink.Event.Native2JavaReturnEvent;
import xtc.lang.blink.Event.NativeBreakPointHitEvent;
import xtc.lang.blink.Event.JavaBreakPointHitEvent;
import xtc.lang.blink.Event.NativeStepCompletionEvent;
import xtc.lang.blink.JavaDebugger.InitializedEvent;
import xtc.lang.blink.JavaDebugger.ListenAddressEvent;
import xtc.lang.blink.NativeDebugger.LanguageTransitionEventType;
import xtc.lang.blink.SymbolMapper.SourceFileAndLine;
import xtc.lang.blink.agent.AgentNativeDeclaration;
import xtc.lang.blink.EventLoop.ReplyHandler;
import xtc.lang.blink.EventUtil.EventReplyHandler;
import xtc.lang.blink.EventUtil.ConjunctiveReplyHandler;
import xtc.lang.blink.EventUtil.DeathReplyHandler;
import xtc.lang.blink.EventUtil.J2CCompletionEventHandler;
import xtc.lang.blink.EventUtil.EventReplyHandler.EventFilter;
/**
* The Blink debugger for the Java/C mixed mode source level debugging.
*
* @author Byeongcheol Lee
*/
public class Blink implements AgentNativeDeclaration {
/** The debugger control status. */
enum DebugerControlStatus {
NONE, JDB, GDB, JDB_IN_GDB, GDB_IN_JDB,
}
/**
* Prints a usage message to the user and shows what is wrong in the command
* line arguments. Then, terminates the Blink debugger with an error code
* (-1).
*
* @param msg The message.
*/
private static void usage(String msg) {
StringBuffer buf = new StringBuffer();
if (msg != null && msg.length() > 0) {
buf.append(msg).append("\n\n");
}
String usage =
"Usage: xtc.lang.blink.Blink [options] CLASS [arguments]\n"
+ "Blink options:\n"
+ "\t-help\n"
+ "\t-jniassert\n"
+'\n'
+ "options forwarded to JDB:\n"
+ "\t-sourcepath <directories separated by \":\">\n"
+ "\t-dbgtrace\n"
+'\n'
+ "options forwarded to debuggee JVM:\n"
+ "\t-v -verbose[:class|gc|jni]\n"
+ "\t-D<name>=<value> system property\n"
+ "\t-classpath <directories separated by \":\">\n"
+ "\t-X<option>\n"
+'\n'
+ "environment variables:\n"
+ "\tJAVA_DEV_ROOT xtc installation path\n"
+ "\tCLASSPATH class path\n"
+ "\tJAVA_HOME JDK installation path\n"
+ "\tOSTYPE OS type (linux,cygwin,win32, ...)\n"
;
buf.append(usage);
System.out.println(buf.toString());
System.exit(-1);
}
/**
* Print the help message for the Blink command.
*/
final void help() {
String msg =
"\n"
+ "help print help\n"
+ "exit exit the Blink debugger\n"
+ "run start the program run\n"
+ "\n"
+ "break [file:line] add a break point e.g.) break Main.jni:9\n"
+ "stop at <classid>:<line> add a break point e.g.) stop at Main:15\n"
+ "stop in <classid>:<method> add a break point e.g.) stop in Main:main\n"
+ "info break list break points\n"
+ "delete [n] delete a break/watch point with its id [n].\n"
+ "\n"
+ "where dump stack trace\n"
+ "up [n] select n frames up\n"
+ "down [n] select n frames down\n"
+ "list print source code.\n"
+ "locals print local variables in selected frame\n"
+ "print <jexpr> print Jeannie expression\n"
+ "\n"
+ "continue coninue running.\n"
+ "step execute until another line reached\n"
+ "next execute the next line, including function calls\n";
out(msg);
}
/**
* The main method for the Blink debugger.
*
* @param args The command line arguments.
*/
public static void main(String[] args) {
InternalOption debuggerOptions = new InternalOption();
StringBuffer sbJVMOptions = new StringBuffer();
StringBuffer sbJDBOptions = new StringBuffer();
StringBuffer sbGDBOPtions = new StringBuffer();
String mainClass = null;
StringBuffer sbMainOptions = new StringBuffer();
// parse arguments
for (int i = 0; i < args.length; i++) {
String arg = args[i];
// [options] CLASS
if (mainClass == null) {
if (arg.equals("-help")) {
usage("");
// BEGIN - internal options for debugging purpose.
} else if (arg.equals("-bv") || arg.equals("-bverbose")) {
debuggerOptions.moreVerbose();
} else if (arg.equals("-ex")) {
if ((i + 1) >= args.length)
usage("Please, specify a Blink command after -ex");
String cmd = "";
for (i++; i < args.length && !args[i].equals("-xe"); i++) {
cmd += " " + args[i];
}
if (i >= args.length)
usage("Please, specify -xe to end the Blink initial command.");
debuggerOptions.addInitialBlinkCommand(cmd);
} else if (arg.equals("-jniassert")) {
debuggerOptions.setJniCheck(false);
// END - internal options for debugging purpose.
//jdb options
} else if (arg.equals("-sourcepath")) {
if ((i + 1) >= args.length)
usage("Please, specify path after -sourcepath.");
String argPath = args[++i];
sbJDBOptions.append(' ').append(arg).append(' ').append(argPath);
} else if (arg.equals("-dbgtrace")) {
sbJDBOptions.append(' ').append(arg);
//jvm options
} else if (arg.equals("-classpath")) {
if ((i + 1) >= args.length)
usage("Please, specify path after -classpath.");
String argPath = args[++i];
sbJVMOptions.append(' ').append(arg);
sbJVMOptions.append(' ').append(argPath);
} else if (arg.equals("-v") || Pattern.matches("-verbose(:(class|gc|jni))?", arg)) {
sbJVMOptions.append(' ').append(arg);
} else if (arg.matches("-X.+")) {
sbJVMOptions.append(' ').append(arg);
} else if (arg.matches("-D[^=]+=.*")) {
sbJVMOptions.append(' ').append(arg);
}else {
mainClass = arg;
}
} else {
// app options
sbMainOptions.append(' ').append(arg);
}
}
// check arguments
if (mainClass == null) {
usage("Please, specify the main CLASS name.");
}
//agent native files
if (debuggerOptions.getAgentLibrarypath() != null) {
ensureAgentLibrary(debuggerOptions.getAgentLibrarypath());
} else {
debuggerOptions.setAgentLibrarypath(ensureAgentLibraryPathFromEnv());
}
// build jvm arguments
sbJVMOptions.append(' ').append(mainClass);
sbJVMOptions.append(' ').append(sbMainOptions);
if (debuggerOptions.getVerboseLevel() >= 1) {
StringBuffer sb = new StringBuffer();
sb.append("xtc.lang.blink.Debugger");
for (final String a : args) { sb.append(' ').append(a);}
System.out.println("running: " + sb + "\n");
}
//now actually launch the Blink.
try {
Blink debugger = new Blink(mainClass, sbJVMOptions.toString(),
sbJDBOptions.toString(), sbGDBOPtions.toString(), debuggerOptions);
debugger.startSession();
} catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
/** Ensure the JAVA_DEV_ROOT directory is available. */
private static String ensureJavaDevRoot() {
String java_dev_root= System.getenv("JAVA_DEV_ROOT");
if (java_dev_root == null || java_dev_root.equals("")) {
usage("JAVA_DEV_ROOT environment variable is not set");
assert false:"not reachable";
}
if (!new File(java_dev_root).isDirectory()) {
usage(("JAVA_DEV_ROOT(="
+ java_dev_root + ") is expected to be directory"));
assert false:"not reachable";
}
return java_dev_root;
}
/**
* Ensure the debug agent's native shared library path from envorinment
* variable.
*/
static String ensureAgentLibraryPathFromEnv() {
String java_dev_root= ensureJavaDevRoot();
String java_dev_bin = java_dev_root + java.io.File.separator + "bin";
ensureAgentLibrary(java_dev_bin);
return java_dev_bin;
}
/** Ensure the Agent dll exists and return its full path. */
static String ensureAgentLibraryPath() {
return ensureAgentLibrary(ensureAgentLibraryPathFromEnv());
}
/**
* Ensure debug agent's native shared library exists.
* @param libpath The library path.
*/
private static String ensureAgentLibrary(String libpath) {
assert libpath != null;
String dll_windows = libpath + java.io.File.separator + BDA_SHARED_LIBRARY_NAME + ".dll";
String dll_unix = libpath + File.separator + "lib" + BDA_SHARED_LIBRARY_NAME + ".so";
if (new File(dll_windows).exists()) {
return dll_windows;
} else if (new File(dll_unix).exists()) {
return dll_unix;
} else {
usage("can not find Blink agent native library in " + libpath);
assert false : "not reachable";
return null;
}
}
/**
* check if the Blink run jdb process, and create Blink's jdb controller. If
* the Blink can not correctly create the jdb process, terminate the current
* debugging session.
*/
private static JavaDebugger ensureJavaDebugger(Blink dbg) {
try {
Process p = Runtime.getRuntime().exec("jdb -version");
BufferedReader r = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String firstLine = r.readLine();
if (firstLine.startsWith("This is jdb version")) {
int exitCode = p.waitFor();
r.close();
if (exitCode != 0) {
usage("can not correctly access jdb in the current environment.");
}
}
} catch(InterruptedException ie) {
usage("can not correctly access jdb in the current environment.");
System.exit(-1);
} catch(IOException e) {
usage("can not correctly access jdb in the current environment.");
System.exit(-1);
}
return new JavaDebugger(dbg, "jdb");
}
/**
* Choose a native debugger, check Blink can run this native debugger, and
* create Blink's native debugger handler. If Blink can not correctly use the
* native debugger, terminate the current debugging session.
*
* The OSTYPE environment variable controls which native debugger to use.
*/
private static NativeDebugger ensureNativeDebugger(Blink dbg) {
String ostype = System.getenv("OSTYPE");
if (ostype == null || !ostype.equals("win32")) {
// default is gdb.
try {
Process p = Runtime.getRuntime().exec("gdb -version");
BufferedReader r = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String firstLine = r.readLine();
if (firstLine.startsWith("GNU gdb")) {
int exitCode = p.waitFor();
r.close();
if (exitCode != 0) {
usage("can not correctly access gdb in the current environment.");
}
}
} catch(InterruptedException ie) {
usage("can not correctly access gdb in the current environment.");
} catch(IOException e) {
usage("can not correctly access gdb in the current environment.");
}
return new NativeGDB(dbg, "gdb");
} else {
//if OSTYPE is win32, use cdb.
assert ostype.equals("win32");
try {
Process p = Runtime.getRuntime().exec("cdb -version");
BufferedReader r = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String versionLine = r.readLine();
if (versionLine.matches("cdb version \\d+\\.\\d+\\.\\d+\\.\\d+")) {
int exitCode = p.waitFor();
r.close();
if (exitCode != 0) {
usage("cdb returned non-zero exit code: "
+ exitCode + " when running cdb -version.");
}
}
} catch(InterruptedException ie) {
usage("can not correctly run cdb in the current environemnt.");
} catch (IOException e) {
usage("can not correctly run cdb in the current environment.");
}
return new NativeCDB(dbg, "cdb");
}
}
/** The application main class. */
private final String mainClass;
/** The option string to be forwarded to the jvm. */
private final String jvmArguments;
/** The option string to be forwarded to the jdb. */
private final String jdbArguments;
/** The user command monitor. */
private final CommandLineInterface user;
/** The application. */
final DebugeeJVM jvm;
/** The jdb message monitor. */
final JavaDebugger jdb;
/** The gdb message monitor. */
final NativeDebugger ndb;
/**
* The Blink event queue. Let the main thread be thread who initiated the
* eventLoop() method, and this thread consumes events in the queue. There are
* three event producers: user, jdb and gdb.
*/
private final LinkedBlockingQueue<Event> eventQueue =
new LinkedBlockingQueue<Event>();
final EventLoop eventLoop;
/** The current debugger control status. */
private DebugerControlStatus debugControl = DebugerControlStatus.NONE;
/**
* This is a flag to recode a fact that the jdb and gdb are initialized for
* j2c and c2j debugger switching.
*/
private boolean gdbAttached = false;
/** The break points manager. */
final BreakPointManager breakpointManager =
new BreakPointManager(this);
/** The Blink debugger options. */
final InternalOption options;
/** the jni env value for the current context. */
String jnienvValue;
/** The unified log queue for debugging purpose. */
final BoundedLogQueue logQueue = new BoundedLogQueue(4*1024);
/**
* Construct the Blink debugger.
*
* @param mainClass The main class name.
* @param jvmArguments The jvm command line options.
* @param jdbArguments The jdb command line options.
* @param gdbOptions The gdb command line options.
* @param debugOption The Blink debugger options.
*/
private Blink(final String mainClass, final String jvmArguments,
final String jdbArguments, final String gdbOptions,
final InternalOption debugOptions) {
this.mainClass = mainClass;
this.jvmArguments = jvmArguments;
this.jdbArguments = jdbArguments;
this.options = debugOptions;
this.jdb =ensureJavaDebugger(this);
this.ndb = ensureNativeDebugger(this);
this.user = new CommandLineInterface(this);
this.jvm = new DebugeeJVM(this, "jvm");
this.eventLoop = new EventLoop(this);
}
/**
* Start the Blink debugger and wait until the end of this Blink session.
*/
private void startSession() throws IOException {
// launch Java debugger and get the listening address.
jdb.startListening(jdbArguments);
final String address = (String) EventLoop.subLoop(this,
new EventLoop.ReplyHandler() {
boolean dispatch(Event e) {
if (e instanceof ListenAddressEvent && e.getSource() == Blink.this.jdb) {
ListenAddressEvent listenEvent = (ListenAddressEvent) e;
setResult(listenEvent.getAddress());
return true;
} else {
return false;
}
}
});
// launch JVM and attach the JVM to the JDB.
jvm.beginDebugSession(jvmArguments, address);
EventLoop.subLoop(this, new EventLoop.ReplyHandler() {
boolean dispatch(Event e) {
if (e instanceof InitializedEvent && e.getSource() == Blink.this.jdb) {
setResult(new Boolean(true));
return true;
} else {
return false;
}
}
});
//advance the program execution to the main method.
jdb.setBreakPoint(mainClass + ".main");
jdb.run();
EventLoop.subLoop(this, new EventLoop.ReplyHandler() {
boolean dispatch(Event e) {
if (e instanceof JavaBreakPointHitEvent && e.getSource() == Blink.this.jdb) {
JavaBreakPointHitEvent je = (JavaBreakPointHitEvent)e;
if (je.getClassName().equals(mainClass) && je.getMethodName().equals("main")) {
setResult(new Boolean(true));
return true;
} else {
assert false : "unknown Java breakpoint hit";
return false;
}
} else {
return false;
}
}
});
changeDebugControlStatus(DebugerControlStatus.JDB);
jdb.clearBreakPoint(mainClass + ".main");
initj();
// show welcome message.
out("Blink a Java/C mixed language debugger.\n");
changeDebugControlStatus(DebugerControlStatus.JDB);
// execute some initial Blink commands.
for (final String line : options.getInitialBlinkCommandList()) {
eventLoop.executeBlinkCommand(line);
}
// Now, enable the user command line processing.
user.start();
showPrompt();
// Now, get into the event loop.
eventLoop.main();
}
/**
* Present a terminal command prompt to the user.
*/
void showPrompt() {
switch(debugControl) {
case JDB:
out("(bdb-j) ");
break;
case GDB:
out("(bdb-c) ");
break;
case JDB_IN_GDB:
out("(bdb-c2j) ");
break;
case GDB_IN_JDB:
out("(bdb-j2c) ");
break;
}
}
/** Implement Blink "run" command. */
void run() {
try {
assert (getDebugControlStatus() == DebugerControlStatus.JDB);
jdb.run();
changeDebugControlStatus(Blink.DebugerControlStatus.NONE);
} catch (IOException e) {
err("could not successfully run the application");
}
}
/**
* Prepare jdb and gdb for j2c and c2j debugger switching. Internally, this
* will initilize the Blink debugger agent by running a number of jdb and gdb
* commands.
*/
boolean initj() {
if (gdbAttached) {
err("the initj was run before.");
return true;
}
assert debugControl == DebugerControlStatus.JDB;
// try to initialize the DebugAgent
try {
if (!jdb.initAgent()) {
return false;
}
} catch (IOException e) {
err("failed in executing initj for jdb\n");
return false;
}
// get debugee process id through the DebugAgent Code.
int pid;
try {
pid = jdb.getJVMProcessID();
} catch (IOException e) {
err("failed in getting debugged process id\n");
return false;
}
// gdb - attach the debugee
try {
ndb.attach(pid);
gdbAttached = true;
} catch (IOException e) {
err("could not attach the native code debugger.\n");
return false;
}
ensurePureContext();
assert getDebugControlStatus() == DebugerControlStatus.JDB;
return true;
}
/**
* Implements the j2c macro command. This will activate the gdb assuming that
* the jdb has control over the debuggee.
*/
void j2c() {
if (!IsNativeDebuggerAttached()) {
err("please run initj before j2c\n");
return;
}
if (options.getVerboseLevel() >= 1) {
out("switching to gdb mode due to j2c command.\n");
}
try {
jdb.j2c();
Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(ndb, J2CBreakPointHitEvent.class),
new EventFilter(ndb, LanguageTransitionEvent.class),
}));
if (e instanceof LanguageTransitionEvent) {
ndb.cont();
EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(ndb, J2CBreakPointHitEvent.class),
}));
}
changeDebugControlStatus(DebugerControlStatus.GDB_IN_JDB);
} catch (IOException e) {
err("failed in executing j2c\n");
e.printStackTrace();
}
}
/**
* Implements the c2j macro command. This will activate the jdb assuming that
* gdb has control over the debugee.
*/
void c2j() {
if (!IsNativeDebuggerAttached()) {
err("please run initj before c2j\n");
return;
}
if (options.getVerboseLevel() >= 1) {
out("switching to jdb mode due to c2j command,\n");
}
try {
ndb.callNative2Java();
EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(jdb, JavaBreakPointHitEvent.class),
}));
changeDebugControlStatus(DebugerControlStatus.JDB_IN_GDB);
} catch (IOException e) {
err("failed in executing c2j\n");
e.printStackTrace();
}
}
/**
* Implements the jret macro command. If the current debugger nesting is
* jdb->gdb, then this will resume the gdb j2c break point, and the user input
* redirection will be switched to the jdb. If the current debugger nesting is
* gdb->jdb, this will resume the jdb c2j break point, and the user input
* redirection will be switched to the gdb.
*/
void jret() {
if (!IsNativeDebuggerAttached()) {
err("please run initj before jret\n");
return;
}
switch (getDebugControlStatus()) {
case NONE:
case JDB:
case GDB:
err("jret is for jdb and gdb nesting.");
break;
case JDB_IN_GDB:
if (options.getVerboseLevel() >= 1) {
out("return to gdb\n");
}
try {
jdb.cont();
EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(ndb, Native2JavaCompletionEvent.class),
new EventFilter(ndb, LanguageTransitionEvent.class),
}));
changeDebugControlStatus(DebugerControlStatus.GDB);
} catch (IOException e) {
err("failed in resuming jdb control nested in the gdb\n");
}
break;
case GDB_IN_JDB:
if (options.getVerboseLevel() >= 1) {
out("returning to jdb\n");
}
try {
ndb.cont();
EventLoop.subLoop(this, new ReplyHandler() {
boolean dispatch(Event e) {
if (e instanceof Java2NativeCompletionEvent && e.getSource() == jdb) {
setResult(new Boolean(true));
return true;
} else if (e instanceof NativeBreakPointHitEvent && e.getSource() == ndb) {
NativeBreakPointHitEvent ne = (NativeBreakPointHitEvent )e;
try {
ndb.cont();
} catch(IOException ioe) {
err("could not continue from breakpoint from the native breakpoint:" + e);
EventLoop.reportEvent(Blink.this, ne);
}
return false;
} else {
return false;
}
}
});
changeDebugControlStatus(DebugerControlStatus.JDB);
} catch (IOException e) {
err("failed in resuming gdb control nested in the jdb\n");
}
break;
default:
break;
}
}
/**
* Implement Blink "continue/cont" command.
*/
void cont() {
assert debugControl != DebugerControlStatus.NONE;
jnienvValue = null;
switch (getDebugControlStatus()) {
case JDB:
case GDB:
if (breakpointManager.hasDeferredNativeBreakpoint()) {
try {
ensureJDBContext();
jdb.setLoadLibraryEvent();
}catch(IOException e) {
err("could not handle deferred gdb break point");
}
}
break;
}
switch (getDebugControlStatus()) {
case JDB:
try {
jdb.cont();
changeDebugControlStatus(Blink.DebugerControlStatus.NONE);
} catch (IOException e) {
err("failed in executing the continue\n");
}
break;
case GDB:
try {
ndb.cont();
changeDebugControlStatus(Blink.DebugerControlStatus.NONE);
} catch (IOException e) {
err("failed in executing the continue\n");
}
break;
case JDB_IN_GDB:
case GDB_IN_JDB:
err("\"continue\" is not allowed in this nested mode:"
+ getDebugControlStatus() + "\n" + "use jret to return to the gdb.\n");
break;
default:
assert false : "not allowed";
break;
}
}
/**
* Perform inter-language source level stepping.
*/
Event step() throws IOException {
ensurePureContext();
Event e = null;
SourceFileAndLine start = getCurrentSourceLevelLocation();
assert start != null;
SymbolMapper.SourceFileAndLine now = null;
do {
switch(getDebugControlStatus()) {
case JDB:
e = stepj();
now = getCurrentSourceLevelLocation();
break;
case GDB:
e = stepc();
now = getCurrentSourceLevelLocation();
break;
default:
assert false;
break;
}
} while(now == null || start.equals(now));
return e;
}
/**
* Perform the inter-language source level stepping from Java context, and
* return component debugger that finished this single stepping.
*
* @return The component event.
*/
private Event stepj() throws IOException {
assert getDebugControlStatus() == DebugerControlStatus.JDB;
// try JDB step-into and expect pause at Java or native code.
j2c();
ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL, 0);
ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN, 0);
jret();
jdb.step();
boolean reachedSourceLine = false;
Event e;
do {
changeDebugControlStatus(DebugerControlStatus.NONE);
e = (Event)EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] {
new EventFilter(jdb, JavaStepCompletionEvent.class),
new EventFilter(jdb, JavaBreakPointHitEvent.class),
new EventFilter(ndb, Java2NativeCallEvent.class),
new EventFilter(ndb, Native2JavaReturnEvent.class)
}));
if (e.getSource() == jdb) {
// stepping from Java remains inside the Java area.
changeDebugControlStatus(DebugerControlStatus.JDB);
j2c();
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN);
jret();
reachedSourceLine = true; // assume all Java byte code has the source line here.
} else {
// the control will stay in the native code.
assert e.getSource() == ndb;
changeDebugControlStatus(DebugerControlStatus.GDB);
if (ndb.getCurrentLocation() == null ) {
changeDebugControlStatus(DebugerControlStatus.NONE);
ndb.cont();
} else {
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_CALL);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN);
// flush the JDB's stepping status.
ndb.callJavaDummy();
EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] {
new EventFilter(jdb, JavaStepCompletionEvent.class)
}));
jdb.cont();
EventLoop.subLoop(this, new EventReplyHandler(new EventFilter[] {
new EventFilter(ndb, DummyCallCompletionEvent.class)
}));
reachedSourceLine = true;
}
}
} while(!reachedSourceLine);
return e;
}
/**
* Perform an inter-language source level stepping from native code, and return
* component debugger event that finished this stepping.
*
* @return The component event.
*/
private Event stepc() throws IOException {
assert getDebugControlStatus() == DebugerControlStatus.GDB;
//set breakpoints for escaping to the Java.
ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_CALL, 0);
ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN, 0);
ndb.step();
Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(ndb, Native2JavaCallEvent.class),
new EventFilter(ndb, Java2NativeReturnEvent.class),
new EventFilter(ndb, NativeStepCompletionEvent.class),
new EventFilter(ndb, NativeBreakPointHitEvent.class),
}));
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_CALL);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN);
if (e instanceof NativeStepCompletionEvent || e instanceof NativeBreakPointHitEvent) {
//done!
} else if (e instanceof Native2JavaCallEvent) {
Native2JavaCallEvent ne = (Native2JavaCallEvent)e;
//reach the Java target method entry.
String className= ne.getClassName();
int lineNumber = ne.getLineNumber();
c2j();
jdb.setBreakPoint(className, lineNumber);
jret();
changeDebugControlStatus(DebugerControlStatus.NONE);
ndb.cont();
EventLoop.subLoop(
this, new EventReplyHandler( new EventFilter[] {
new EventFilter(jdb, JavaBreakPointHitEvent.class),
}));
changeDebugControlStatus(DebugerControlStatus.JDB);
jdb.clearBreakPoint(className, lineNumber);
} else if (e instanceof Java2NativeReturnEvent) {
Java2NativeReturnEvent ne = (Java2NativeReturnEvent)e;
//reach the Java return site.
String cname = ne.getJavaTarget();
int line = ne.getTargetLineNumber();
c2j();
jdb.setBreakPoint(cname, line);
jret();
ensureGDBContext();
changeDebugControlStatus(DebugerControlStatus.NONE);
ndb.cont();
EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(jdb, JavaBreakPointHitEvent.class),
}));
changeDebugControlStatus(DebugerControlStatus.JDB);
jdb.clearBreakPoint(cname, line);
}
return e;
}
/**
* Perform an inter-language source level stepping, and return the component
* debugger event that terminated the single stepping.
*
* @return The component debugger event.
*/
Event next() throws IOException {
Event e = null;
ensurePureContext();
SymbolMapper.SourceFileAndLine start = getCurrentSourceLevelLocation();
assert start != null;
SymbolMapper.SourceFileAndLine now = null;
do {
switch(getDebugControlStatus()) {
case JDB:
e = nextj();
now = getCurrentSourceLevelLocation();
break;
case GDB:
e = nextc();
now = getCurrentSourceLevelLocation();
break;
default:
assert false;
break;
}
} while(now == null || start.equals(now));
ensurePureContext();
assert e != null;
return e;
}
/**
* Perform a source-level step-over from the Java code, and return the
* component debugger event that terminated this single stepping. At the end
* of this operation, this debuggee is in either Java or native code.
*
* @return The component debugger event.
*/
private Event nextj() throws IOException {
assert debugControl == DebugerControlStatus.JDB;
//set c2j_return breakpoint.
j2c();
int languageTransitionCountOnStack = ndb.getLanguageTransitionCount();
ndb.setTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN,
languageTransitionCountOnStack);
jret();
//start a single step from Java.
changeDebugControlStatus(DebugerControlStatus.NONE);
jdb.next();
Event e = (Event)EventLoop.subLoop(this,
new EventReplyHandler(new EventFilter[] {
new EventFilter(jdb, JavaStepCompletionEvent.class),
new EventFilter(jdb, JavaBreakPointHitEvent.class),
new EventFilter(ndb, Native2JavaReturnEvent.class),
new EventFilter(ndb, NativeBreakPointHitEvent.class),
}));
if (e.getSource() == jdb) {
changeDebugControlStatus(DebugerControlStatus.JDB);
j2c();
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN);
jret();
return e;
} else {
assert e.getSource() == ndb;
changeDebugControlStatus(DebugerControlStatus.GDB);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.C2J_RETURN);
return e;
}
}
/**
* Perform a source-level step-over from the native code, and return the
* component debugger event that terminated this single stepping. At the end
* of this operation, this debuggee is in either Java or native code.
*
* @return The component debugger event.
*/
private Event nextc() throws IOException {
assert debugControl == DebugerControlStatus.GDB;
//set language transition breakpoint.
ensureGDBContext();
int languageTransitionCountOnStack = ndb.getLanguageTransitionCount();
ndb.setTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN,
languageTransitionCountOnStack);
//resume the execution with native stepping over.
changeDebugControlStatus(DebugerControlStatus.NONE);
ndb.next();
Event e = (Event)EventLoop.subLoop(this, new EventReplyHandler(
new EventFilter[] {
new EventFilter(ndb, NativeStepCompletionEvent.class),
new EventFilter(ndb, NativeBreakPointHitEvent.class),
new EventFilter(ndb, Java2NativeReturnEvent.class),
new EventFilter(jdb, JavaBreakPointHitEvent.class),
}));
//handle the step completion cases
if (e instanceof NativeStepCompletionEvent ||
e instanceof NativeBreakPointHitEvent) {
changeDebugControlStatus(DebugerControlStatus.GDB);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN);
} else if (e instanceof Java2NativeReturnEvent) {
changeDebugControlStatus(DebugerControlStatus.GDB);
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN);
//continue until the program reaches the return site.
ensureJDBContext();
Java2NativeReturnEvent event = (Java2NativeReturnEvent)e;
String cname = event.getJavaTarget();
int line = event.getTargetLineNumber();
assert cname != null && line > 0 : "can not put byte code index break point with jdb.";
jdb.setBreakPoint(cname, line);
ensureGDBContext();
changeDebugControlStatus(DebugerControlStatus.NONE);
ndb.cont();
boolean returnSiteReached = false;
do {
JavaBreakPointHitEvent jbpHitEvent = (JavaBreakPointHitEvent)
EventLoop.subLoop(this, new EventReplyHandler( new EventFilter[] {
new EventFilter(jdb, JavaBreakPointHitEvent.class),
}));
changeDebugControlStatus(DebugerControlStatus.JDB);
returnSiteReached =jbpHitEvent.getClassName().equals(cname) && jbpHitEvent.getLineNumber() == line;
} while (!returnSiteReached);
jdb.clearBreakPoint(cname, line);
changeDebugControlStatus(DebugerControlStatus.JDB);
} else if (e instanceof JavaBreakPointHitEvent) {
changeDebugControlStatus(DebugerControlStatus.JDB);
j2c(); //this will flush the native single stepping.
ndb.clearTransitionBreakPoint(LanguageTransitionEventType.J2C_RETURN);
jret();
} else {
assert false : " not reachable";
}
return e;
}
/** Exit the current debugging session. */
void exit() {
if (getDebugControlStatus() == DebugerControlStatus.NONE ) {
out("can not terminate the current debugging session.\n");
return;
}
ensurePureContext();
if (getDebugControlStatus() == DebugerControlStatus.JDB) {
try {
// ensure gdb is detached.
if (gdbAttached) {
j2c();
ndb.detach();
EventLoop.subLoop(this,
new ConjunctiveReplyHandler(
new J2CCompletionEventHandler(jdb),
new DeathReplyHandler(ndb)
));
debugControl = DebugerControlStatus.JDB;
gdbAttached = false;
}
jdb.exit();
EventLoop.subLoop(this,
new ConjunctiveReplyHandler(
new DeathReplyHandler(jvm),
new DeathReplyHandler(jdb)
));
debugControl = DebugerControlStatus.NONE;
} catch (IOException e) {
err("could not successfully run the exit sequent from jdb.");
}
} else if (getDebugControlStatus() == DebugerControlStatus.GDB) {
try {
ndb.quit();
EventLoop.subLoop(this,
new ConjunctiveReplyHandler(
new DeathReplyHandler(jvm),
new DeathReplyHandler(jdb),
new DeathReplyHandler(ndb)
));
} catch (IOException e) {
err("could not successfully run the exit sequent from gdb.");
}
} else {
assert false : "should not reach here";
return;
}
}
/**
* Print a message to the console.
*
* @param msg The message to print.
*/
void out(String msg) {
user.out(msg);
}
/**
* Print an message to the console.
*
* @param b The byte buffer.
* @param off The offset.
* @param len The length.
*/
void out(char[] b) {
user.out(new String(b));
}
/**
* Print an error message to the console.
*
* @param msg The error message to print.
*/
void err(String msg) {
user.err(msg);
}
/**
* Getter method for the debuggerSwitchingInitialized.
*
* @return true if the debug context switching is ready, or false otherwise.
*/
boolean IsNativeDebuggerAttached() {
return gdbAttached;
}
/**
* Ensure the debug agent is ready.
*/
boolean ensureDebugAgent() throws IOException {
assert debugControl == DebugerControlStatus.JDB;
if (IsNativeDebuggerAttached()) {
return true;
}
return initj();
}
/**
* Ensure jdb is available.
*/
public boolean ensureJDBContext() {
switch (debugControl) {
case JDB:
case JDB_IN_GDB:
return true;
case GDB:
c2j();
return true;
case GDB_IN_JDB:
jret();
return true;
default:
assert false : "not allowed state";
return false;
}
}
/**
* try to ensure gdb is available.
*
* @return true if the gdb context. false otherwise.
*/
public boolean ensureGDBContext() {
if (!IsNativeDebuggerAttached()) {
return false;
}
switch (debugControl) {
case JDB:
j2c();
break;
case JDB_IN_GDB:
jret();
break;
case GDB:
case GDB_IN_JDB:
break;
default:
assert false : "not allowed state";
break;
}
return true;
}
/**
* Ensure a pure debug context.
*/
public final void ensurePureContext() {
switch(getDebugControlStatus()) {
case JDB:
case GDB:
break;
case JDB_IN_GDB:
case GDB_IN_JDB:
jret();
break;
case NONE:
break;
}
}
/**
* Ensure the JNIENV pointer is available.
*
* @return The JNIEnv pointer value.
*/
public String ensureJNIENV() {
if (jnienvValue != null) {
return jnienvValue;
}
if (!ensureGDBContext()) {
assert false : "panic";
}
try {
jnienvValue = ndb.getJNIEnv();
} catch (IOException e) {
err("could not get jnienv value.\n");
}
return jnienvValue;
}
/**
* Dispatch the internal Blink user command.
*
* @param command The command.
*/
void executeDebugCommand(String command) {
assert command.startsWith("bdb ");
if (command.equals("bdb log")) {
out(logQueue.getLastTrace());
Thread.dumpStack();
} else if (Pattern.matches("bdb log (jdb|gdb)", command)) {
Matcher m = Pattern.compile("bdb log (jdb|gdb)").matcher(command);
if (!m.matches()) { assert false: "impossible!"; }
String mdbg =m.group(1);
if (mdbg.equals("jdb")) {
out(jdb.getLastOutputMessage() + "\n");
} else {
assert mdbg.equals("gdb") : "impossible!";
out(ndb.getLastOutputMessage() + "\n");
}
} else if (command.equals("bdb verbose")) {
options.moreVerbose();
} else if (command.equals("bdb quiet")) {
options.setVerboseLevel(0);
} else if (command.equals("bdb where")) {
Thread.dumpStack();
} else {
err("can not recognize: " + command + "\n");
}
}
/**
* Switch to the new debugger state.
*
* @param newStatus The requested new state.
*/
synchronized void changeDebugControlStatus(DebugerControlStatus newStatus) {
DebugerControlStatus oldStatus = debugControl;
if (oldStatus == newStatus) {
return;
}
boolean success = false;
switch (debugControl) {
case NONE:
success = newStatus == DebugerControlStatus.JDB
|| newStatus == DebugerControlStatus.GDB;
break;
case JDB:
success = newStatus == DebugerControlStatus.NONE
|| newStatus == DebugerControlStatus.GDB_IN_JDB;
break;
case GDB:
success = newStatus == DebugerControlStatus.NONE
|| newStatus == DebugerControlStatus.JDB_IN_GDB;
break;
case JDB_IN_GDB:
success = newStatus == DebugerControlStatus.GDB;
break;
case GDB_IN_JDB:
success = newStatus == DebugerControlStatus.JDB;
break;
}
debugControl = newStatus;
if (!success) {
err("warning: not an expected transition in the following context\n"
+ oldStatus + "->" + newStatus);
Thread.dumpStack();
}
}
/**
* @return The debugger control status.
*/
synchronized DebugerControlStatus getDebugControlStatus() {
return debugControl;
}
/**
* @return The current language context
*/
synchronized String getCurrentLanguageContext() {
switch (debugControl) {
case JDB_IN_GDB:
case JDB:
return "Java";
case GDB_IN_JDB:
case GDB:
return "C";
default:
return "None";
}
}
/**
* Get current location where the debugee suspened.
* @return The location.
*/
SourceFileAndLine getCurrentSourceLevelLocation() throws IOException {
ensurePureContext();
SourceFileAndLine location;
switch(debugControl) {
case JDB:
location = jdb.getCurrentLocation();
break;
case GDB:
location = ndb.getCurrentLocation();
break;
default:
assert false: "not reachable";
location = null;
break;
}
return location;
}
String getCurrentSourceLine() throws IOException {
ensurePureContext();
String line;
switch(debugControl) {
case JDB:
List<JavaCallFrame> frames = jdb.getFrames();
assert frames.size() > 0;
JavaCallFrame top = frames.get(0);
line = jdb.getSourceLine(top);
break;
case GDB:
SourceFileAndLine loc = getCurrentSourceLevelLocation();
line = ndb.getSourceLines(loc.getSourceFile(), loc.getSourceLine(), 1);
break;
default:
assert false: "not reachable";
line = null;
break;
}
return line;
}
/**
* Enqueue an event.
*
* @param e The event.
*/
void enqueEvent(Event e) {
if (!eventQueue.add(e)) {
err("can not successfully insert event: " + e + " from "
+ Thread.currentThread() + "\n");
err("Now, I'm discarding that event\n");
}
}
/**
* Dequeue an event.
*
* @return The event.
*/
Event dequeEvent() {
Event event = null;
try {
event = eventQueue.take();
} catch (InterruptedException e) {
if (event == null) {
err("could not get non-null event from queue\n");
return null;
}
}
return event;
}
/**
* Getter method for mainClass.
*
* @return The mainClass.
*/
public String getMainClass() {
return mainClass;
}
}