diff --git a/.classpath b/.classpath index 1ed5c4b3..009529c2 100644 --- a/.classpath +++ b/.classpath @@ -7,5 +7,6 @@ + diff --git a/lib/commons-cli-1.1.jar b/lib/commons-cli-1.1.jar new file mode 100644 index 00000000..e633afbe Binary files /dev/null and b/lib/commons-cli-1.1.jar differ diff --git a/src/main/java/lua/LuaJVM.java b/src/main/java/lua/LuaJVM.java new file mode 100644 index 00000000..ec6f8eef --- /dev/null +++ b/src/main/java/lua/LuaJVM.java @@ -0,0 +1,226 @@ +/******************************************************************************* +* Copyright (c) 2007 LuaJ. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ +package lua; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import lua.addon.luajava.LuaJava; +import lua.debug.DebugRequestListener; +import lua.debug.DebugServer; +import lua.io.Closure; +import lua.io.LoadState; +import lua.io.Proto; +import lua.value.LString; +import lua.value.LValue; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * LuaJVM executes a lua program in normal run mode or debug mode. + * + * @author: Shu Lei + * @version: 1.0 + */ +public class LuaJVM implements DebugRequestListener { + protected Options options = new Options(); + protected boolean isDebugMode = false; + protected DebugServer debugServer; + protected int requestPort; + protected int eventPort; + protected String script; + protected String[] scriptArgs; + + @SuppressWarnings("static-access") + public LuaJVM() { + options.addOption(OptionBuilder.withArgName("requestPort eventPort"). + hasArgs(2). + isRequired(false). + withValueSeparator(' '). + withDescription("run LuaJ VM in debug mode"). + create("debug")); + options.addOption(OptionBuilder.withArgName("LuaJProgram"). + withDescription("lua program to be executed"). + isRequired(). + hasArgs(). + withValueSeparator(' '). + create("file")); + } + + protected void printUsage() { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java LuaJVM", options); + } + + protected void parse(String[] args) throws ParseException { + CommandLineParser parser = new GnuParser(); + try { + CommandLine line = parser.parse(options, args); + if (line.hasOption("debug")) { + this.isDebugMode = true; + String[] ports = line.getOptionValues("debug"); + + this.requestPort = Integer.parseInt(ports[0]); + if (this.requestPort <= 0) { + throw new ParseException("Invalid request port: it must be greater than zero."); + } + + this.eventPort = Integer.parseInt(ports[1]); + if (this.eventPort <= 0) { + throw new ParseException("Invalid event port: it must be greater than zero."); + } + + if (this.requestPort == this.eventPort) { + throw new ParseException("Invalid ports: request port and event port must be different"); + } + } + + if (line.hasOption("file")) { + String[] fileArgs = line.getOptionValues("file"); + this.script = fileArgs[0]; + this.scriptArgs = new String[fileArgs.length - 1]; + for (int i = 1; i < fileArgs.length; i++) { + this.scriptArgs[i-1] = fileArgs[i]; + } + } + } catch(NumberFormatException e) { + throw new ParseException("Invalid port number: " + e.getMessage()); + } + } + + protected boolean isDebug() { + return this.isDebugMode; + } + + protected int getRequestPort() { + return this.requestPort; + } + + protected int getEventPort() { + return this.eventPort; + } + + protected String getScript() { + return this.script; + } + + protected boolean hasScriptArgs() { + return (this.scriptArgs != null && this.scriptArgs.length > 0); + } + + protected String[] getScriptArgs() { + return this.scriptArgs; + } + + public void run() throws IOException { + if (isDebug()) { + setupDebugHooks(getRequestPort(), getEventPort()); + } + + // TODO: VM hook for debugging + + // add LuaJava bindings + LuaJava.install(); + + // new lua state + StackState state = new StackState(); + + // convert args to lua + int numOfScriptArgs = getScriptArgs().length; + LValue[] vargs = new LValue[numOfScriptArgs]; + for (int i = 0; i < numOfScriptArgs; i++) { + vargs[i] = new LString(getScriptArgs()[i]); + } + + // load the Lua file + System.out.println("loading Lua script '" + getScript() + "'"); + InputStream is = new FileInputStream(new File(getScript())); + Proto p = LoadState.undump(state, is, getScript()); + + // create closure and execute + Closure c = new Closure(state, p); + state.doCall(c, vargs); + } + + protected void setupDebugHooks(int requestPort, int eventPort) + throws IOException { + this.debugServer = new DebugServer(this, requestPort, eventPort); + this.debugServer.start(); + } + + /* (non-Javadoc) + * @see lua.debug.DebugRequestListener#handleRequest(java.lang.String) + */ + public String handleRequest(String request) { + //TODO: handle the following requests: + // suspend -- suspend the execution and listen for debug requests + // resume -- resume the execution + // exit -- exit the VM + // set N -- set breakpoint at line N + // clear N -- clear breakpoint at line N + // callgraph -- return the current call graph (i.e. stack frames from + // old to new, include information about file, method, etc.) + // stack -- return the content of the current stack frame, + // listing the (variable, value) pairs + // step -- single step forward (go to next statement) + // variable N M + // -- return the value of variable M from the stack frame N + // (stack frames are indexed from 0) + return null; + } + + public void stop() { + if (this.debugServer != null) { + this.debugServer.stop(); + this.debugServer = null; + } + } + + /** + * Parses the command line arguments and executes/debugs the lua program. + * @param args -- command line arguments: + * [-debug requestPort eventPort] -file luaProgram args + * @throws IOException + */ + public static void main(String[] args) throws IOException { + LuaJVM vm = new LuaJVM(); + + try { + vm.parse(args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + System.out.println(); + vm.printUsage(); + return; + } + + vm.run(); + } +} diff --git a/src/main/java/lua/debug/DebugRequestListener.java b/src/main/java/lua/debug/DebugRequestListener.java new file mode 100644 index 00000000..3f695f1e --- /dev/null +++ b/src/main/java/lua/debug/DebugRequestListener.java @@ -0,0 +1,49 @@ +/******************************************************************************* +* Copyright (c) 2007 LuaJ. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ +package lua.debug; + +/** + * DebugRequestListener handles debugging requests. + * + * @author: Shu Lei + * @version: 1.0 + */ +public interface DebugRequestListener { + + /** + * Debugging client can send the following requests to the server: + * suspend -- suspend the execution and listen for debug requests + * resume -- resume the execution + * exit -- terminate the execution + * set N -- set breakpoint at line N + * clear N -- clear breakpoint at line N + * callgraph -- return the current call graph (i.e. stack frames from + * old to new, include information about file, method, etc.) + * stack -- return the content of the current stack frame, + * listing the (variable, value) pairs + * step -- single step forward (go to next statement) + * variable N M + * -- return the value of variable M from the stack frame N + * (stack frames are indexed from 0) + */ + public String handleRequest(String request); +} diff --git a/src/main/java/lua/debug/DebugServer.java b/src/main/java/lua/debug/DebugServer.java new file mode 100644 index 00000000..b22c9914 --- /dev/null +++ b/src/main/java/lua/debug/DebugServer.java @@ -0,0 +1,184 @@ +/******************************************************************************* +* Copyright (c) 2007 LuaJ. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ +package lua.debug; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * DebugServer manages the communications between LuaJ VM and + * the debugging client. + * + * @author: Shu Lei + * @version: + */ +public class DebugServer { + public enum State { + UNKNOWN, + RUNNING, + STOPPED + } + + protected DebugRequestListener listener; + protected int requestPort; + protected int eventPort; + protected Thread requestWatcherThread; + protected State state = State.UNKNOWN; + + protected ServerSocket requestSocket; + protected Socket clientRequestSocket; + protected BufferedReader requestReader; + protected PrintWriter requestWriter; + + protected ServerSocket eventSocket; + protected Socket clientEventSocket; + protected PrintWriter eventWriter; + + public DebugServer(DebugRequestListener listener, + int requestPort, + int eventPort) { + this.listener = listener; + this.requestPort = requestPort; + this.eventPort = eventPort; + } + + protected void destroy() { + if (requestReader != null) { + try { + requestReader.close(); + } catch (IOException e) {} + } + + if (requestWriter != null) { + requestWriter.close(); + } + + if (clientRequestSocket != null) { + try { + clientRequestSocket.close(); + } catch (IOException e) {} + } + + if (requestSocket != null) { + try { + requestSocket.close(); + } catch (IOException e) {} + } + + if (eventWriter != null) { + eventWriter.close(); + } + + if (clientEventSocket != null) { + try { + clientEventSocket.close(); + } catch (IOException e) {} + } + + if (eventSocket != null){ + try { + eventSocket.close(); + } catch (IOException e) {} + } + } + + public synchronized void start() throws IOException { + this.requestSocket = new ServerSocket(requestPort); + this.clientRequestSocket = requestSocket.accept(); + this.requestReader = new BufferedReader( + new InputStreamReader(clientRequestSocket.getInputStream())); + this.requestWriter = new PrintWriter(clientRequestSocket.getOutputStream()); + + this.eventSocket = new ServerSocket(eventPort); + this.clientEventSocket = eventSocket.accept(); + this.eventWriter = new PrintWriter(clientEventSocket.getOutputStream()); + + this.requestWatcherThread = new Thread(new Runnable() { + public void run() { + if (getState() != State.STOPPED) { + handleRequest(); + } else { + destroy(); + } + } + }); + this.requestWatcherThread.start(); + this.state = State.RUNNING; + } + + public synchronized State getState() { + return this.state; + } + + public synchronized void stop() { + this.state = State.STOPPED; + } + + public void handleRequest() { + synchronized (clientRequestSocket) { + String request = null; + try { + while (getState() != State.STOPPED && + (request = requestReader.readLine()) != null) { + System.out.println("SERVER receives request: " + request); + String response = listener.handleRequest(request); + requestWriter.write(response); + requestWriter.flush(); + } + + if (getState() == State.STOPPED) { + destroy(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * This method provides the second communication channel with the debugging + * client. The server can send events via this channel to notify the client + * about debug events (see below) asynchonously. + * + * The following events can be fired: + * 1. started -- the vm is started and ready to receive debugging requests + * (guaranteed to be the first event sent) + * 2. terminated -- the vm is terminated (guaranteed to be the last event sent) + * 3. suspended client|step|breakpoint N + * -- the vm is suspended by client, due to a stepping request or + * the breakpoint at line N is hit + * 4. resumed client|step + * -- the vm resumes execution by client or step + * + * @param event + */ + public void fireEvent(String event) { + synchronized (eventSocket) { + eventWriter.println(event); + eventWriter.flush(); + } + } +}