/*******************************************************************************
* 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.debug.shared.model.Breakpoint;
import org.eclipse.che.api.debug.shared.model.DebuggerInfo;
import org.eclipse.che.api.debug.shared.model.Location;
import org.eclipse.che.api.debug.shared.model.SimpleValue;
import org.eclipse.che.api.debug.shared.model.StackFrameDump;
import org.eclipse.che.api.debug.shared.model.Variable;
import org.eclipse.che.api.debug.shared.model.VariablePath;
import org.eclipse.che.api.debug.shared.model.action.ResumeAction;
import org.eclipse.che.api.debug.shared.model.action.StartAction;
import org.eclipse.che.api.debug.shared.model.action.StepIntoAction;
import org.eclipse.che.api.debug.shared.model.action.StepOutAction;
import org.eclipse.che.api.debug.shared.model.action.StepOverAction;
import org.eclipse.che.api.debug.shared.model.impl.DebuggerInfoImpl;
import org.eclipse.che.api.debug.shared.model.impl.SimpleValueImpl;
import org.eclipse.che.api.debug.shared.model.impl.StackFrameDumpImpl;
import org.eclipse.che.api.debug.shared.model.impl.VariableImpl;
import org.eclipse.che.api.debug.shared.model.impl.VariablePathImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.BreakpointActivatedEventImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.DisconnectEventImpl;
import org.eclipse.che.api.debug.shared.model.impl.event.SuspendEventImpl;
import org.eclipse.che.api.debugger.server.Debugger;
import org.eclipse.che.api.debugger.server.exceptions.DebuggerException;
import org.eclipse.che.plugin.gdb.server.exception.GdbParseException;
import org.eclipse.che.plugin.gdb.server.exception.GdbTerminatedException;
import org.eclipse.che.plugin.gdb.server.parser.GdbContinue;
import org.eclipse.che.plugin.gdb.server.parser.GdbDirectory;
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.GdbInfoProgram;
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.GdbVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static java.nio.file.Files.exists;
import static java.util.Collections.singletonList;
/**
* Connects to GDB.
*
* @author Anatoliy Bazko
*/
public class GdbDebugger implements Debugger {
private static final Logger LOG = LoggerFactory.getLogger(GdbDebugger.class);
private static final int CONNECTION_ATTEMPTS = 5;
private final String host;
private final int port;
private final String name;
private final String version;
private final String file;
private Location currentLocation;
private final Gdb gdb;
private final DebuggerCallback debuggerCallback;
GdbDebugger(String host,
int port,
String name,
String version,
String file,
Gdb gdb,
DebuggerCallback debuggerCallback) {
this.host = host;
this.port = port;
this.name = name;
this.version = version;
this.file = file;
this.gdb = gdb;
this.debuggerCallback = debuggerCallback;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getName() {
return name;
}
public String getVersion() {
return version;
}
public String getFile() {
return file;
}
public static GdbDebugger newInstance(String host,
int port,
String file,
String srcDirectory,
DebuggerCallback debuggerCallback) throws DebuggerException {
if (!exists(Paths.get(file))) {
throw new DebuggerException("Can't start GDB: binary " + file + " not found");
}
if (!exists(Paths.get(srcDirectory))) {
throw new DebuggerException("Can't start GDB: source directory " + srcDirectory + " does not exist");
}
for (int i = 0; i < CONNECTION_ATTEMPTS - 1; i++) {
try {
return init(host, port, file, srcDirectory, debuggerCallback);
} catch (DebuggerException e) {
LOG.error("Connection attempt " + i + ": " + e.getMessage(), e);
}
}
return init(host, port, file, srcDirectory, debuggerCallback);
}
private static GdbDebugger init(String host,
int port,
String file,
String srcDirectory,
DebuggerCallback debuggerCallback) throws DebuggerException {
Gdb gdb;
try {
gdb = Gdb.start();
} catch (IOException e) {
throw new DebuggerException("Can't start GDB: " + e.getMessage(), e);
}
try {
GdbDirectory directory = gdb.directory(srcDirectory);
LOG.debug("Source directories: " + directory.getDirectories());
gdb.file(file);
if (port > 0) {
gdb.targetRemote(host, port);
}
} catch (DebuggerException | IOException | InterruptedException e) {
gdb.stop();
throw new DebuggerException("Can't initialize GDB: " + e.getMessage(), e);
}
GdbVersion gdbVersion = gdb.getGdbVersion();
return new GdbDebugger(host,
port,
gdbVersion.getVersion(),
gdbVersion.getName(),
file,
gdb,
debuggerCallback);
}
@Override
public DebuggerInfo getInfo() throws DebuggerException {
return new DebuggerInfoImpl(host, port, name, version, 0, file);
}
@Override
public void disconnect() {
currentLocation = null;
debuggerCallback.onEvent(new DisconnectEventImpl());
gdb.stop();
}
@Override
public void addBreakpoint(Breakpoint breakpoint) throws DebuggerException {
try {
Location location = breakpoint.getLocation();
if (location.getTarget() == null) {
gdb.breakpoint(location.getLineNumber());
} else {
gdb.breakpoint(location.getTarget(), location.getLineNumber());
}
debuggerCallback.onEvent(new BreakpointActivatedEventImpl(breakpoint));
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't add breakpoint: " + breakpoint + ". " + e.getMessage(), e);
}
}
@Override
public void deleteBreakpoint(Location location) throws DebuggerException {
try {
if (location.getTarget() == null) {
gdb.clear(location.getLineNumber());
} else {
gdb.clear(location.getTarget(), location.getLineNumber());
}
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't delete breakpoint: " + location + ". " + e.getMessage(), e);
}
}
@Override
public void deleteAllBreakpoints() throws DebuggerException {
try {
gdb.delete();
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't delete all breakpoints. " + e.getMessage(), e);
}
}
@Override
public List<Breakpoint> getAllBreakpoints() throws DebuggerException {
try {
GdbInfoBreak gdbInfoBreak = gdb.infoBreak();
return gdbInfoBreak.getBreakpoints();
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't get all breakpoints. " + e.getMessage(), e);
}
}
@Override
public void start(StartAction action) throws DebuggerException {
try {
for (Breakpoint b : action.getBreakpoints()) {
try {
addBreakpoint(b);
} catch (DebuggerException e) {
// can't add breakpoint, skip it
}
}
Breakpoint breakpoint;
if (isRemoteConnection()) {
GdbContinue gdbContinue = gdb.cont();
breakpoint = gdbContinue.getBreakpoint();
} else {
GdbRun gdbRun = gdb.run();
breakpoint = gdbRun.getBreakpoint();
}
if (breakpoint != null) {
currentLocation = breakpoint.getLocation();
debuggerCallback.onEvent(new SuspendEventImpl(breakpoint.getLocation()));
} else {
GdbInfoProgram gdbInfoProgram = gdb.infoProgram();
if (gdbInfoProgram.getStoppedAddress() == null) {
disconnect();
}
}
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Error during running. " + e.getMessage(), e);
}
}
@Override
public void suspend() throws DebuggerException {
try {
currentLocation = gdb.suspend(file, isRemoteConnection());
debuggerCallback.onEvent(new SuspendEventImpl(currentLocation));
} catch (IOException | InterruptedException e) {
throw new DebuggerException("Can not suspend debugger session. " + e.getMessage(), e);
}
}
private boolean isRemoteConnection() {
return getPort() > 0;
}
@Override
public void stepOver(StepOverAction action) throws DebuggerException {
try {
GdbInfoLine gdbInfoLine = gdb.next();
if (gdbInfoLine == null) {
disconnect();
return;
}
currentLocation = gdbInfoLine.getLocation();
debuggerCallback.onEvent(new SuspendEventImpl(gdbInfoLine.getLocation()));
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Step into error. " + e.getMessage(), e);
}
}
@Override
public void stepInto(StepIntoAction action) throws DebuggerException {
try {
GdbInfoLine gdbInfoLine = gdb.step();
if (gdbInfoLine == null) {
disconnect();
return;
}
currentLocation = gdbInfoLine.getLocation();
debuggerCallback.onEvent(new SuspendEventImpl(gdbInfoLine.getLocation()));
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Step into error. " + e.getMessage(), e);
}
}
@Override
public void stepOut(StepOutAction action) throws DebuggerException {
try {
GdbInfoLine gdbInfoLine = gdb.finish();
if (gdbInfoLine == null) {
disconnect();
return;
}
currentLocation = gdbInfoLine.getLocation();
debuggerCallback.onEvent(new SuspendEventImpl(gdbInfoLine.getLocation()));
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Step out error. " + e.getMessage(), e);
}
}
@Override
public void resume(ResumeAction action) throws DebuggerException {
try {
GdbContinue gdbContinue = gdb.cont();
Breakpoint breakpoint = gdbContinue.getBreakpoint();
if (breakpoint != null) {
currentLocation = breakpoint.getLocation();
debuggerCallback.onEvent(new SuspendEventImpl(breakpoint.getLocation()));
} else {
GdbInfoProgram gdbInfoProgram = gdb.infoProgram();
if (gdbInfoProgram.getStoppedAddress() == null) {
disconnect();
}
}
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Resume error. " + e.getMessage(), e);
}
}
@Override
public void setValue(Variable variable) throws DebuggerException {
try {
List<String> path = variable.getVariablePath().getPath();
if (path.isEmpty()) {
throw new DebuggerException("Variable path is empty");
}
gdb.setVar(path.get(0), variable.getValue());
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't set value for " + variable.getName() + ". " + e.getMessage(), e);
}
}
@Override
public SimpleValue getValue(VariablePath variablePath) throws DebuggerException {
try {
List<String> path = variablePath.getPath();
if (path.isEmpty()) {
throw new DebuggerException("Variable path is empty");
}
GdbPrint gdbPrint = gdb.print(path.get(0));
return new SimpleValueImpl(Collections.emptyList(), gdbPrint.getValue());
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't get value for " + variablePath + ". " + e.getMessage(), e);
}
}
@Override
public String evaluate(String expression) throws DebuggerException {
try {
GdbPrint gdbPrint = gdb.print(expression);
return gdbPrint.getValue();
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't evaluate '" + expression + "'. " + e.getMessage(), e);
}
}
/**
* Dump frame.
*/
@Override
public StackFrameDump dumpStackFrame() throws DebuggerException {
try {
Map<String, String> locals = gdb.infoLocals().getVariables();
locals.putAll(gdb.infoArgs().getVariables());
List<Variable> variables = new ArrayList<>(locals.size());
for (Map.Entry<String, String> e : locals.entrySet()) {
String varName = e.getKey();
String varValue = e.getValue();
String varType;
try {
varType = gdb.ptype(varName).getType();
} catch (GdbParseException pe) {
LOG.warn(pe.getMessage(), pe);
varType = "";
}
VariablePath variablePath = new VariablePathImpl(singletonList(varName));
VariableImpl variable = new VariableImpl(varType, varName, varValue, true, variablePath, Collections.emptyList(), true);
variables.add(variable);
}
return new StackFrameDumpImpl(Collections.emptyList(), variables);
} catch (GdbTerminatedException e) {
disconnect();
throw e;
} catch (IOException | GdbParseException | InterruptedException e) {
throw new DebuggerException("Can't dump stack frame. " + e.getMessage(), e);
}
}
}