Package org.epic.debug.db

Source Code of org.epic.debug.db.DebuggerInterface

package org.epic.debug.db;

import gnu.regexp.REMatch;

import java.io.*;

import org.eclipse.core.runtime.*;

/**
* A low-level interface to the "perl -d" debugger process.
* This class supports the (synchronous) execution of commands
* which would normally be entered manually through the debugger's
* console. DebuggerInterface does not support concurrent use by
* multiple threads.
*
* Note that every debugger command can throw SessionTerminatedException.
* If you wish to distinguish between IOExceptions and the event of
* debugger termination, you should catch this exception type explicitly.
*
* @author jploski
*/
public class DebuggerInterface
{
    private final RE re = new RE();
    private final char[] buf = new char[1024];

    private BufferedReader in;
    private PrintWriter out;

    private final String perlVersion;
    private Boolean hasPadWalker;

    public static final int CMD_NONE = 0;
    public static final int CMD_STEP_INTO = 1;
    public static final int CMD_STEP_OVER = 2;
    public static final int CMD_STEP_RETURN = 4;
    public static final int CMD_RESUME = 8;
    public static final int CMD_SUSPEND = 16;
    public static final int CMD_TERMINATE = 32;
    public static final int CMD_CLEAR_OUTPUT = 64;
    public static final int CMD_EXEC = 128;
    public static final int CMD_EVAL = 256; // TODO: not needed?
    public static final int CMD_MODIFIER_RANGE_START = 1024;
    public static final int CMD_MODIFIER_SKIP_EVAL_CMD_RESULT = CMD_MODIFIER_RANGE_START;
   
    public DebuggerInterface(BufferedReader in, PrintWriter out)
        throws IOException
    {
        assert in != null;
        assert out != null;
       
        this.in = in;
        this.out = out;
       
        runSyncCommand(CMD_CLEAR_OUTPUT, null);
        perlVersion = getPerlVersion();
    }
   
    public synchronized void dispose()
    {
        if (in != null)
        {
            try { quit(); } catch (IOException e) { }
            try { in.close(); } catch (IOException e) { }
            out.close();
           
            in = null;
            out = null;
        }
    }
   
    public synchronized String eval(String code) throws IOException
    {
        return runSyncCommand(CMD_EXEC, code);
    }
   
    /**
     * @return true if the given path corresponds to a file in
     *         the debugger's file system; false otherwise
     */
    public synchronized boolean fileExists(IPath path)
        throws IOException
    {
        // Get an OS-specific path with escaped backslashes
        String osPath = getOSPath(path).replaceAll("\\\\", "\\\\\\\\");
        return "1".equals(eval("print $DB::OUT -f '" + osPath + "'"));
    }
   
    public synchronized IPPosition getCurrentIP() throws IOException
    {
        String output = runSyncCommand(CMD_EXEC, ".");

        // mRe_IP_Pos_CODE handles locations like
        //     main::CODE(0x814f960)(/some/path/trycatch.pl:7):
        // mRe_IP_Pos handles locations like
        //     main::(/some/path/foobar.pl:7):
        // mRe_IP_Pos_Eval handles locations like
        //

        REMatch result = re.IP_POS_CODE.getMatch(output);
        if (result == null)
        {
            result = re.IP_POS.getMatch(output);
            if (result == null)
                throw new IOException("could not match re.IP_POS in {" + output + "}");
            String filename = result.toString(1);
            REMatch temp = re.IP_POS_EVAL.getMatch(filename);
            if (temp != null) result = temp;
        }

        return new IPPosition(
            new Path(result.toString(1)),
            Integer.parseInt(result.toString(2)));
    }

    public synchronized String getOS() throws IOException
    {
        return runSyncCommand(CMD_EXEC, "print $DB::OUT $^O;").trim();
    }
   
    public synchronized String getPerlVersion() throws IOException
    {       
        return runSyncCommand(CMD_EXEC, "printf $DB::OUT \"%vd\", $^V;").trim();
    }
   
    public synchronized String getStackTrace() throws IOException
    {
        return runSyncCommand(CMD_EXEC, "T");
    }
   
    /**
     * @return true if we have an installed version of PadWalker required
     *         for dumping lexical variables; false otherwise
     */
    public synchronized boolean hasPadWalker() throws IOException
    {
        if (hasPadWalker == null)
        {
            String result = runSyncCommand(
                CMD_EXEC,
                "print $DB::OUT eval { require PadWalker; PadWalker->VERSION(0.08) }");
            hasPadWalker = new Boolean(result.length() > 0);
        }

        return hasPadWalker.booleanValue();
    }
   
    public synchronized String quit() throws IOException
    {
        return runSyncCommand(CMD_TERMINATE, null);
    }
   
    public synchronized String redirectError(String host, int port) throws IOException
    {
        String command = "require IO::Socket; {my $OUT;"
            + "$OUT = new IO::Socket::INET("
            + "Timeout  => \'10\',"
            + "PeerAddr => \'" + host + ":" + port + "\',"
            + "Proto    => 'tcp',);" + "STDERR->fdopen($OUT,\"w\");}";

        return runSyncCommand(CMD_EXEC, command);
    }
   
    public synchronized String redirectIO(String host, int port) throws IOException
    {
        String command = "require IO::Socket; {my $OUT;"
            + "$OUT = new IO::Socket::INET("
            + "Timeout  => \'10\',"
            + "PeerAddr => \'" + host + ":" + port + "\',"
            + "Proto    => 'tcp',);" + "STDOUT->fdopen($OUT,\"w\");"
            + "STDIN->fdopen($OUT,\"r\");}";

        return runSyncCommand(CMD_EXEC, command);
    }
   
    public synchronized String resume() throws IOException
    {
        return runSyncCommand(CMD_RESUME, null);
    }
   
    public synchronized String setFrame(int count) throws IOException
    {
        String cmd = perlVersion.startsWith("5.6")
            ? "O frame=" + count
            : "o frame=" + count;
       
        return runSyncCommand(CMD_EXEC, cmd);
    }
   
    public synchronized String removeLineBreakpoint(int line) throws IOException
    {
        String command = perlVersion.startsWith("5.6")
            ? "d " + line
            : "B " + line;

        return runSyncCommand(CMD_EXEC, command);
    }
   
    public synchronized boolean setLineBreakpoint(int line, String condition)
        throws IOException
    {
        String output = runSyncCommand(
            CMD_EXEC, "b " + line +
            (condition != null ? (" " + condition) : ""));
        return re.SET_LINE_BREAKPOINT.getAllMatches(output).length == 0;
    }
   
    /**
     * Sets a breakpoint that will be triggered on loading of a given
     * source file.
     *
     * @param path
     *        path to the source file, specific to the debugger's
     *        environment
     */
    public synchronized String setLoadBreakpoint(IPath path) throws IOException
    {
        return runSyncCommand(CMD_EXEC, "b load " + getOSPath(path));
    }
   
    public synchronized String stepInto() throws IOException
    {
        return runSyncCommand(CMD_STEP_INTO, null);
    }
   
    public synchronized String stepOver() throws IOException
    {
        return runSyncCommand(CMD_STEP_OVER, null);
    }
   
    public synchronized String stepReturn() throws IOException
    {
        return runSyncCommand(CMD_STEP_RETURN, null);
    }
   
    /**
     * Switches the debugging context to the given source file.
     * This affects commands such as {@link #setLineBreakpoint(int)}.
     *
     * @param path
     *        path to the source file, specific to the debugger's
     *        environment
     * @return true if the operation succeeded,
     *         false if the source file has not been loaded yet
     */
    public synchronized boolean switchToFile(IPath path) throws IOException
    {
        String output = runSyncCommand(CMD_EXEC, "f " + getOSPath(path));

        return re.SWITCH_FILE_FAIL.getAllMatches(output).length == 0;
    }

    private String getOSPath(IPath path) throws IOException
    {
        return path.toString();
    }

    private String runSyncCommand(int command, String code) throws IOException
    {
        assert Thread.currentThread().getName().indexOf("Main") == -1
            : "forbidden DebuggerInterface use from display thread";

        Command cmd = new Command(command, code);
        return cmd.run();
    }
   
    private String runCommand(int command, String code) throws IOException
    {
        command = maskCommandModifiers(command);

        switch (command)
        {
        case CMD_STEP_INTO:
            outputLine("s");
            break;
        case CMD_STEP_OVER:
            outputLine("n");
            break;
        case CMD_STEP_RETURN:
            outputLine("r");
            break;
        case CMD_RESUME:
            outputLine("c");
            break;
        case CMD_TERMINATE:
            outputLine("q");
            break;
        case CMD_SUSPEND:
        case CMD_CLEAR_OUTPUT:
            break;
        case CMD_EXEC:
        case CMD_EVAL:
            outputLine(code);
            break;
        default:
            throw new RuntimeException("unrecognized command " + command);
        }
       
        synchronized (this)
        {
            if (out == null) throw new SessionTerminatedException("disposed");
        }
        return readCommandOutput();
    }
   
    private int maskCommandModifiers(int command)
    {
        return command & (CMD_MODIFIER_RANGE_START - 1);
    }

    private int hasCommandTerminated(String output)
    {
        // TODO: this way of finding out whether a command has terminated
        // is not fool-proof. The debugger output may contain a fake
        // command termination token, so there's a bug waiting to be
        // uncovered here...
       
        REMatch[] matches;

        matches = re.CMD_FINISHED1.getAllMatches(output);
        if (matches != null && matches.length > 0)
            return matches[matches.length-1].getStartIndex();

        matches = re.CMD_FINISHED2.getAllMatches(output);
        if (matches != null && matches.length > 0)
            return matches[matches.length-1].getStartIndex();

        return -1;
    }

    private boolean hasSessionTerminated(String fOutput)
    {
        boolean erg;
        int count;

        erg = re.SESSION_FINISHED1.isMatch(fOutput);
        count = re.SESSION_FINISHED1.getAllMatches(fOutput).length;
        if (erg || (count > 0)) return true;

        erg = re.SESSION_FINISHED2.isMatch(fOutput);
        count = re.SESSION_FINISHED2.getAllMatches(fOutput).length;
        if (erg || (count > 0)) return true;

        return false;
    }
   
    private synchronized void outputLine(String line)
    {
        //System.err.println("->D: {" + line + "}");
        if (out != null)
        {
            out.println(line);
            out.flush();
        }
    }
   
    private String readCommandOutput()
        throws IOException, SessionTerminatedException
    {
        StringBuffer output = new StringBuffer();
        while (true)
        {
            int count;
            try
            {
                count = in.read(buf);
                //System.err.println("<-D: {" + String.valueOf(buf, 0, count) + "}");
                if (count > 0) output.append(buf, 0, count);
                try { if (in.ready() && count != -1) continue; } catch (IOException e) { }
            }
            catch (IOException e)
            {
                throw new SessionTerminatedException(
                    "IOException while reading debugger's response");
            }
           
            // Note that we apply the regular expressions used to find out
            // whether the command/session has terminated only to the last few
            // characters of the output; applying them to the whole output
            // (which can become *very* long) causes major performance problems

            int inspectLen = Math.min(output.length(), 350);           
            String currentOutput = output.substring(
                output.length() - inspectLen,
                output.length());
           
            int endIndex = hasCommandTerminated(currentOutput);

            if (count < 0)
            {
                throw new SessionTerminatedException("EOF from debugger");
            }
            else if (hasSessionTerminated(currentOutput))
            {
                throw new SessionTerminatedException(
                    "Debugger session terminated normally");
            }
            else if (endIndex != -1)
            {
                // Return command output without the trailing
                // DB<nn> prompt
                String ret = output.substring(
                    0, output.length() - inspectLen + endIndex);
                return ret;
            }
        }
    }
   
    public class Command
    {
        private final int type;
        private final String code;
       
        public Command(int type, String code)
        {
            this.type = type;
            this.code = code;
        }

        public String getCode()
        {
            return code;
        }

        public boolean isStepCommand()
        {
            return
                type == CMD_STEP_INTO ||
                type == CMD_STEP_OVER ||
                type == CMD_STEP_RETURN;
        }
       
        public int getType()
        {
            return type;
        }
               
        public String run() throws IOException, SessionTerminatedException
        {
            return runCommand(type, code);          
        }
       
        public String toString()
        {
            return "Command #" + type + " {" + code + "}";
       
    }
   
    /**
     * Thrown when an executed debugger command does not terminate
     * normally because the debugging session has finished.
     * For convenience of clients who consider abnormal command
     * termination as bad regardless of its reason, this is a subtype
     * of IOException.
     */
    public static class SessionTerminatedException extends IOException
    {
        private static final long serialVersionUID = 1L;

        public SessionTerminatedException(final String msg)
        {
            super(msg);
        }
    }
}
TOP

Related Classes of org.epic.debug.db.DebuggerInterface

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.