/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.gdb.server; import org.eclipse.che.api.core.util.AbstractLineConsumer; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.core.util.ProcessUtil; import org.eclipse.che.api.debug.shared.model.Location; import org.eclipse.che.api.debugger.server.exceptions.DebuggerException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.plugin.gdb.server.exception.GdbException; import org.eclipse.che.plugin.gdb.server.exception.GdbTerminatedException; import org.eclipse.che.plugin.gdb.server.parser.GdbBacktrace; import org.eclipse.che.plugin.gdb.server.parser.GdbBreak; import org.eclipse.che.plugin.gdb.server.parser.GdbClear; import org.eclipse.che.plugin.gdb.server.parser.GdbContinue; import org.eclipse.che.plugin.gdb.server.parser.GdbDelete; import org.eclipse.che.plugin.gdb.server.parser.GdbDirectory; import org.eclipse.che.plugin.gdb.server.parser.GdbFile; import org.eclipse.che.plugin.gdb.server.parser.GdbInfoArgs; import org.eclipse.che.plugin.gdb.server.parser.GdbInfoBreak; import org.eclipse.che.plugin.gdb.server.parser.GdbInfoLine; import org.eclipse.che.plugin.gdb.server.parser.GdbInfoLocals; import org.eclipse.che.plugin.gdb.server.parser.GdbInfoProgram; import org.eclipse.che.plugin.gdb.server.parser.GdbOutput; import org.eclipse.che.plugin.gdb.server.parser.GdbPType; import org.eclipse.che.plugin.gdb.server.parser.GdbPrint; import org.eclipse.che.plugin.gdb.server.parser.GdbRun; import org.eclipse.che.plugin.gdb.server.parser.GdbTargetRemote; import org.eclipse.che.plugin.gdb.server.parser.GdbVersion; import org.eclipse.che.plugin.gdb.server.parser.ProcessInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import static java.lang.String.format; /** * GDB. * * @author Anatoliy Bazko */ public class Gdb extends GdbProcess { private static final Logger LOG = LoggerFactory.getLogger(GdbProcess.class); private static final String PROCESS_NAME = "gdb"; private static final String OUTPUT_SEPARATOR = "(gdb) "; private GdbVersion gdbVersion; Gdb() throws IOException { super(OUTPUT_SEPARATOR, PROCESS_NAME); try { gdbVersion = GdbVersion.parse(grabGdbOutput()); } catch (InterruptedException | DebuggerException e) { LOG.error(e.getMessage(), e); gdbVersion = new GdbVersion("Unknown", "Unknown"); } } /** * Starts GDB. */ public static Gdb start() throws IOException { return new Gdb(); } public GdbVersion getGdbVersion() { return gdbVersion; } /** * `run` command. */ public GdbRun run() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("run"); return GdbRun.parse(gdbOutput); } public Location suspend(final String file, boolean isRemoteConnection) throws IOException, InterruptedException, DebuggerException { if (pid < 0) { throw new DebuggerException("Gdb process not found."); } if (isRemoteConnection) { Runtime.getRuntime().exec("kill -SIGINT " + pid).waitFor(); sendCommand("signal SIGSTOP "); } else { final List<String> outputs = new ArrayList<>(); final ProcessBuilder processBuilder = new ProcessBuilder().command("ps", "-o", "pid,cmd", "--ppid", String.valueOf(pid)); final Process process = processBuilder.start(); LineConsumer stdout = new AbstractLineConsumer() { @Override public void writeLine(String line) throws IOException { outputs.add(line); } }; ProcessUtil.process(process, stdout); int processId = -1; for (String output : outputs) { try { final ProcessInfo processInfo = ProcessInfo.parse(output); if (file.equals(processInfo.getProcessName())) { processId = processInfo.getProcessId(); } } catch (Exception e) { //we can't get info about current process, but we are trying to get info about another processes } } if (processId == -1) { throw new DebuggerException(format("Process %s not found.", file)); } Runtime.getRuntime().exec("kill -SIGINT " + processId).waitFor(); } final GdbOutput gdbOutput = sendCommand("backtrace"); final GdbBacktrace backtrace = GdbBacktrace.parse(gdbOutput); final Map<Integer, Location> frames = backtrace.getFrames(); if (frames.containsKey(0)) { return frames.get(0); } throw new DebuggerException("Unable recognize current location for debugger session. "); } /** * `set var` command. */ public void setVar(String varName, String value) throws IOException, InterruptedException, DebuggerException { String command = "set var " + varName + "=" + value; sendCommand(command); } /** * `ptype` command. */ public GdbPType ptype(String variable) throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("ptype " + variable); return GdbPType.parse(gdbOutput); } /** * `print` command. */ public GdbPrint print(String variable) throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("print " + variable); return GdbPrint.parse(gdbOutput); } /** * `continue` command. */ public GdbContinue cont() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("continue"); return GdbContinue.parse(gdbOutput); } /** * `step` command. */ public GdbInfoLine step() throws IOException, InterruptedException, DebuggerException { sendCommand("step"); return infoLine(); } /** * `finish` command. */ public GdbInfoLine finish() throws IOException, InterruptedException, DebuggerException { sendCommand("finish"); return infoLine(); } /** * `next` command. */ @Nullable public GdbInfoLine next() throws IOException, InterruptedException, DebuggerException { sendCommand("next"); GdbInfoProgram gdbInfoProgram = infoProgram(); if (gdbInfoProgram.getStoppedAddress() == null) { return null; } return infoLine(); } /** * `quit` command. */ public void quit() throws IOException, GdbException, InterruptedException { try { sendCommand("quit", false); } finally { stop(); } } /** * `break` command */ public void breakpoint(@NotNull String file, int lineNumber) throws IOException, InterruptedException, DebuggerException { String command = "break " + file + ":" + lineNumber; GdbOutput gdbOutput = sendCommand(command); GdbBreak.parse(gdbOutput); } /** * `break` command */ public void breakpoint(int lineNumber) throws IOException, InterruptedException, DebuggerException { String command = "break " + lineNumber; GdbOutput gdbOutput = sendCommand(command); GdbBreak.parse(gdbOutput); } /** * `directory` command. */ public GdbDirectory directory(@NotNull String directory) throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("directory " + directory); return GdbDirectory.parse(gdbOutput); } /** * `file` command. */ public void file(@NotNull String file) throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("file " + file); GdbFile.parse(gdbOutput); } /** * `clear` command. */ public void clear(@NotNull String file, int lineNumber) throws IOException, InterruptedException, DebuggerException { String command = "clear " + file + ":" + lineNumber; GdbOutput gdbOutput = sendCommand(command); GdbClear.parse(gdbOutput); } /** * `clear` command. */ public void clear(int lineNumber) throws IOException, InterruptedException, DebuggerException { String command = "clear " + lineNumber; GdbOutput gdbOutput = sendCommand(command); GdbClear.parse(gdbOutput); } /** * `delete` command. */ public void delete() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("delete"); GdbDelete.parse(gdbOutput); } /** * `target remote` command. */ public void targetRemote(String host, int port) throws IOException, InterruptedException, DebuggerException { String command = "target remote " + (host != null ? host : "") + ":" + port; GdbOutput gdbOutput = sendCommand(command); GdbTargetRemote.parse(gdbOutput); } /** * `info break` command. */ public GdbInfoBreak infoBreak() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("info break"); return GdbInfoBreak.parse(gdbOutput); } /** * `info args` command. */ public GdbInfoArgs infoArgs() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("info args"); return GdbInfoArgs.parse(gdbOutput); } /** * `info locals` command. */ public GdbInfoLocals infoLocals() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("info locals"); return GdbInfoLocals.parse(gdbOutput); } /** * `info line` command. */ public GdbInfoLine infoLine() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("info line"); return GdbInfoLine.parse(gdbOutput); } /** * `info program` command. */ public GdbInfoProgram infoProgram() throws IOException, InterruptedException, DebuggerException { GdbOutput gdbOutput = sendCommand("info program"); return GdbInfoProgram.parse(gdbOutput); } private GdbOutput sendCommand(String command) throws IOException, GdbTerminatedException, InterruptedException { return sendCommand(command, true); } private synchronized GdbOutput sendCommand(String command, boolean grabOutput) throws IOException, GdbTerminatedException, InterruptedException { LOG.debug(command); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); writer.write(command); writer.newLine(); writer.flush(); return grabOutput ? grabGdbOutput() : null; } private GdbOutput grabGdbOutput() throws InterruptedException, GdbTerminatedException { GdbOutput gdbOutput = outputs.take(); if (gdbOutput.isTerminated()) { String errorMsg = "GDB has been terminated with output: " + gdbOutput.getOutput(); LOG.error(errorMsg); throw new GdbTerminatedException(errorMsg); } return gdbOutput; } }