/*******************************************************************************
* Copyright (c) 2004 Vlad Dumitrescu and others. 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: Vlad Dumitrescu
*******************************************************************************/
package org.erlide.backend.debug.model;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdt.annotation.NonNull;
import org.erlide.backend.BackendUtils;
import org.erlide.backend.api.IBackend;
import org.erlide.backend.debug.BeamUtil;
import org.erlide.backend.debug.DebuggerEventDaemon;
import org.erlide.backend.debug.ErlangLineBreakpoint;
import org.erlide.backend.debug.ErlideDebug;
import org.erlide.backend.debug.IErlangDebugNode;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.model.root.ErlangProjectProperties;
import org.erlide.engine.model.root.IErlModel;
import org.erlide.runtime.api.ErlDebugFlags;
import org.erlide.util.ErlLogger;
import org.erlide.util.IDisposable;
import org.erlide.util.erlang.OtpErlang;
import org.erlide.util.erlang.OtpParserException;
import org.erlide.util.erlang.SignatureException;
import org.osgi.framework.Bundle;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangBinary;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangPid;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.google.common.collect.Lists;
public class ErlangDebugTarget extends ErlangDebugElement
implements IDebugTarget, IErlangDebugNode, IDisposable {
public static final IThread[] NO_PROCS = new IThread[] {};
public static final int INTERPRETED_MODULES_CHANGED = 0;
public static final int TRACE_CHANGED = 1;
private final List<ErlangProcess> allProcesses;
private final List<ErlangProcess> localProcesses;
final IBackend backend;
private final ILaunch launch;
private boolean disconnected = false;
// private final DebuggerListener fDbgListener;
// private final DebuggerEventListener fDebuggerEventListener;
private boolean terminated = false;
private boolean showSystemProcesses = false;
private boolean showErlideProcesses = false;
private final Set<String> interpretedModules;
private final Collection<IProject> projects;
private final Map<OtpErlangPid, OtpErlangPid> metaPids = new TreeMap<>();
private final Map<OtpErlangPid, OtpErlangPid> pidsFromMeta = new TreeMap<>();
private final DebuggerEventDaemon debuggerDaemon;
private boolean disposed = false;
public ErlangDebugTarget(final ILaunch launch, final IBackend backend,
final Collection<IProject> projects) throws DebugException {
super(null);
this.backend = backend;
this.launch = launch;
this.projects = projects;
allProcesses = new ArrayList<>();
localProcesses = new ArrayList<>();
interpretedModules = new HashSet<>();
debuggerDaemon = new DebuggerEventDaemon(backend, this);
debuggerDaemon.start();
// interpret everything we can
final EnumSet<ErlDebugFlags> debugFlags = backend.getData().getDebugFlags();
final boolean distributed = debugFlags.contains(ErlDebugFlags.DISTRIBUTED_DEBUG);
if (distributed) {
distributeDebuggerCode();
addNodesAsDebugTargets(launch);
}
final OtpErlangPid pid = ErlideDebug.startDebug(backend.getOtpRpc(),
ErlDebugFlags.getFlag(debugFlags), debuggerDaemon.getMBox());
ErlLogger.debug("debug started " + pid);
DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this);
interpretModules(backend.getData().getInitialInterpretedModules(), distributed);
}
@Override
public ILaunch getLaunch() {
return launch;
}
@Override
public IDebugTarget getDebugTarget() {
return this;
}
@Override
public IProcess getProcess() {
return null;
}
@Override
public IThread[] getThreads() throws DebugException {
if (isTerminated()) {
return NO_PROCS;
}
return localProcesses.toArray(new IThread[localProcesses.size()]);
}
@Override
public boolean hasThreads() throws DebugException {
return !isTerminated();
}
@Override
public String getName() throws DebugException {
return backend.getName();
}
@Override
public boolean supportsBreakpoint(final IBreakpoint breakpoint) {
// TODO we should ask the Erlang debugger too...
if (!isTerminated()
&& breakpoint.getModelIdentifier().equals(getModelIdentifier())) {
final IProject bpProject = breakpoint.getMarker().getResource().getProject();
for (final IProject p : projects) {
if (p == bpProject) {
return true;
}
}
}
return false;
}
@Override
public boolean canTerminate() {
return true;
}
@Override
public boolean isTerminated() {
return terminated;
}
@Override
public void terminate() throws DebugException {
if (terminated) {
return;
}
terminated = true;
if (backend != null) {
if (backend.getOtpRpc() != null) {
backend.getOtpRpc().send("dbg_mon", new OtpErlangAtom("stop"));
}
final DebugPlugin dbgPlugin = DebugPlugin.getDefault();
if (dbgPlugin != null) {
dbgPlugin.getBreakpointManager().removeBreakpointListener(this);
}
if (debuggerDaemon != null) {
debuggerDaemon.stop();
}
backend.dispose();
}
if (launch != null) {
launch.terminate();
}
fireTerminateEvent();
}
/**
* Notification we have connected to the VM and it has started. Resume the VM.
*/
public void started() {
fireCreationEvent();
installDeferredBreakpoints();
try {
resume();
} catch (final DebugException e) {
ErlLogger.warn(e);
}
}
/**
* Install breakpoints that are already registered with the breakpoint manager.
*/
public void installDeferredBreakpoints() {
final IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager()
.getBreakpoints(getModelIdentifier());
for (int i = 0; i < breakpoints.length; i++) {
breakpointAdded(breakpoints[i]);
}
}
@Override
public boolean canResume() {
return false;
}
@Override
public boolean canSuspend() {
return false;
}
@Override
public boolean isSuspended() {
return false;
}
@Override
public void resume() throws DebugException {
}
@Override
public void suspend() throws DebugException {
}
@Override
public void breakpointAdded(final IBreakpoint breakpoint) {
if (supportsBreakpoint(breakpoint)) {
try {
if (breakpoint.isEnabled()
&& DebugPlugin.getDefault().getBreakpointManager().isEnabled()
|| !breakpoint.isRegistered()) {
final ErlangLineBreakpoint erlangLineBreakpoint = (ErlangLineBreakpoint) breakpoint;
erlangLineBreakpoint.install(this);
}
} catch (final CoreException e) {
ErlLogger.error(e);
}
}
}
@Override
public void breakpointRemoved(final IBreakpoint breakpoint,
final IMarkerDelta delta) {
try {
ErlLogger.debug("breakpointRemoved " + breakpoint.getMarker().toString()
+ breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER));
} catch (final CoreException e) {
}
if (supportsBreakpoint(breakpoint)) {
final ErlangLineBreakpoint erlangLineBreakpoint = (ErlangLineBreakpoint) breakpoint;
erlangLineBreakpoint.remove(this);
}
}
@Override
public void breakpointChanged(final IBreakpoint breakpoint,
final IMarkerDelta delta) {
if (supportsBreakpoint(breakpoint)) {
try {
if (breakpoint.isEnabled()
&& DebugPlugin.getDefault().getBreakpointManager().isEnabled()) {
breakpointAdded(breakpoint);
} else {
breakpointRemoved(breakpoint, null);
}
} catch (final CoreException e) {
ErlLogger.warn(e);
}
}
}
@Override
public boolean canDisconnect() {
return true;
}
@Override
public void disconnect() throws DebugException {
// tell backend to stop debugging
disconnected = true;
}
@Override
public boolean isDisconnected() {
return disconnected;
}
@Override
public boolean supportsStorageRetrieval() {
return false;
}
@Override
public IMemoryBlock getMemoryBlock(final long startAddress, final long length)
throws DebugException {
return null;
}
public IBackend getBackend() {
return backend;
}
public boolean isShowErlideProcesses() {
return showErlideProcesses;
}
public void setShowErlideProcesses(final boolean showErlideProcesses) {
this.showErlideProcesses = showErlideProcesses;
}
public boolean isShowSystemProcesses() {
return showSystemProcesses;
}
public void setShowSystemProcesses(final boolean showSystemProcesses) {
this.showSystemProcesses = showSystemProcesses;
}
public ErlangProcess getOrCreateErlangProcess(final OtpErlangPid pid) {
ErlangProcess erlangProcess = getErlangProcess(pid);
if (erlangProcess == null) {
erlangProcess = createErlangProcess(pid);
}
return erlangProcess;
}
public Set<String> getInterpretedModules() {
return interpretedModules;
}
private ErlangProcess createErlangProcess(final OtpErlangPid pid) {
final String theNodeName = pid.node();
final IDebugTarget[] targets = getLaunch().getDebugTargets();
for (final IDebugTarget debugTarget : targets) {
try {
if (debugTarget.getName().equals(theNodeName)) {
if (debugTarget instanceof IErlangDebugNode) {
final IErlangDebugNode edn = (IErlangDebugNode) debugTarget;
final ErlangProcess p = new ErlangProcess(debugTarget,
getBackend(), pid);
edn.addErlangProcess(p);
allProcesses.add(p);
return p;
}
}
} catch (final DebugException e) {
ErlLogger.error(e);
}
}
final ErlangProcess p = new ErlangProcess(this, getBackend(), pid);
addErlangProcess(p);
allProcesses.add(p);
return p;
}
public ErlangProcess getErlangProcess(final OtpErlangPid pid) {
for (int i = 0; i < allProcesses.size(); ++i) {
final ErlangProcess p = allProcesses.get(i);
if (p.getPid().equals(pid)) {
return p;
}
}
return null;
}
@SuppressWarnings("unused")
private void removeErlangProcess(final OtpErlangPid pid) {
final ErlangProcess p = getErlangProcess(pid);
if (p != null) {
allProcesses.remove(p);
removeErlangProcess(p);
p.fireTerminateEvent();
}
}
public void sendStarted() {
ErlideDebug.sendStarted(backend, debuggerDaemon.getMBox());
}
public OtpErlangPid getMetaFromPid(final OtpErlangPid pid) {
return metaPids.get(pid);
}
public OtpErlangPid getPidFromMeta(final OtpErlangPid metaPid) {
return pidsFromMeta.get(metaPid);
}
public void putMetaPid(final OtpErlangPid metaPid, final OtpErlangPid pid) {
metaPids.put(pid, metaPid);
pidsFromMeta.put(metaPid, pid);
}
public Collection<IProject> getProjects() {
return Collections.unmodifiableCollection(projects);
}
@Override
public void addErlangProcess(final ErlangProcess p) {
localProcesses.add(p);
}
@Override
public void removeErlangProcess(final ErlangProcess p) {
localProcesses.remove(p);
}
@Override
public ErlangDebugTarget getErlangDebugTarget() {
return this;
}
public Collection<OtpErlangPid> getAllMetaPids() {
return metaPids.values();
}
public OtpErlangPid getEventMBox() {
return debuggerDaemon.getMBox();
}
public void interpretModules(final Collection<String> modules,
final boolean distributed) {
for (final String pm : modules) {
final String[] pms = pm.split(":");
final IProject project = ResourcesPlugin.getWorkspace().getRoot()
.getProject(pms[0]);
final String moduleName = pms[1].replace(".erl", "");
interpret(project, moduleName, distributed, true);
}
}
public void interpret(final IProject project, final String moduleName,
final boolean distributed, final boolean interpret) {
ErlLogger.debug((interpret ? "" : "de") + "interpret " + moduleName);
final OtpErlangList options = getProjectDirs(project);
ErlideDebug.interpret(backend.getOtpRpc(), moduleName, options, distributed,
interpret);
}
private OtpErlangList getProjectDirs(final IProject project) {
final IErlModel model = ErlangEngine.getInstance().getModel();
final ErlangProjectProperties properties = model.findProject(project)
.getProperties();
final String ebin = properties.getOutputDir().toPortableString();
final Collection<IPath> srcs = properties.getSourceDirs();
try {
return (OtpErlangList) OtpErlang.format("[{ebin_dir, ~s}, {src_dirs, ~ls}]",
ebin, srcs);
} catch (final OtpParserException e) {
ErlLogger.warn(e);
} catch (final SignatureException e) {
ErlLogger.warn(e);
}
return new OtpErlangList();
}
private void distributeDebuggerCode() {
final List<String> debuggerModules = getDebuggerModules();
final List<OtpErlangTuple> modules = new ArrayList<>(debuggerModules.size());
final String ver = backend.getRuntime().getVersion().asMajor().toString()
.toLowerCase();
for (final String module : debuggerModules) {
final OtpErlangBinary b = getDebuggerBeam(module, "org.erlide.kernel");
if (b != null) {
final OtpErlangString filename = new OtpErlangString(module + ".erl");
final OtpErlangTuple t = OtpErlang.mkTuple(new OtpErlangAtom(module),
filename, b);
modules.add(t);
} else {
ErlLogger.warn("Could not find debugger module %s (%s)", module, ver);
}
}
ErlideDebug.distributeDebuggerCode(backend.getOtpRpc(), modules);
}
private void unloadDebuggerCode() {
final List<String> debuggerModules = getDebuggerModules();
ErlideDebug.unloadDebuggerCode(backend.getOtpRpc(), debuggerModules);
}
/**
* Get a named beam-file as a binary from the debugger plug-in bundle
*/
private OtpErlangBinary getDebuggerBeam(final String module,
final String bundleName) {
final String beamname = module + ".beam";
final Bundle bundle = Platform.getBundle(bundleName);
final IConfigurationElement[] els = BackendUtils
.getCodepathConfigurationElements();
for (final IConfigurationElement el : els) {
final IContributor c = el.getContributor();
final String name = c.getName();
if (name.equals(bundle.getSymbolicName())
&& "beam_dir".equals(el.getName())) {
final String dirPath = el.getAttribute("path");
final Enumeration<String> e = bundle.getEntryPaths(dirPath);
if (e == null) {
ErlLogger.error(
"* !!! error loading plugin " + bundle.getSymbolicName());
return null;
}
while (e.hasMoreElements()) {
final String s = e.nextElement();
final Path path = new Path(s);
if (path.lastSegment().equals(beamname)) {
return getBeamFromBundlePath(bundle, s, path);
}
}
}
}
return null;
}
private OtpErlangBinary getBeamFromBundlePath(@NonNull final Bundle bundle,
final String s, final Path path) {
final String m = path.removeFileExtension().lastSegment();
try {
return BeamUtil.getBeamBinary(m, bundle.getEntry(s));
} catch (final Exception ex) {
ErlLogger.warn(ex);
return null;
}
}
private void addNodesAsDebugTargets(final ILaunch aLaunch) {
final OtpErlangList nodes = ErlideDebug.nodes(backend.getOtpRpc());
if (nodes != null) {
for (int i = 1, n = nodes.arity(); i < n; ++i) {
final OtpErlangAtom a = (OtpErlangAtom) nodes.elementAt(i);
final IDebugTarget edn = new ErlangDebugNode(this, a.atomValue());
aLaunch.addDebugTarget(edn);
}
}
}
private List<String> getDebuggerModules() {
final Bundle debugger = Platform.getBundle("org.erlide.kernel");
if (debugger == null) {
ErlLogger.warn("engine bundle was not found...");
return new ArrayList<>();
}
final List<String> dbg_modules = getModulesFromBundle(debugger, null);
final String ver = backend.getRuntime().getVersion().asMajor().toString()
.toLowerCase();
final List<String> dbg_otp_modules = getModulesFromBundle(debugger, ver);
dbg_modules.addAll(dbg_otp_modules);
return dbg_modules;
}
private List<String> getModulesFromBundle(final Bundle bundle, final String ver) {
final List<String> modules = Lists.newArrayList();
final String path = ver == null ? "/debugger" : "/debugger/" + ver;
@SuppressWarnings("rawtypes")
final Enumeration beams = bundle.findEntries(path, "*.beam", false);
if (beams == null) {
ErlLogger.error("No beams found in %s!", bundle);
return modules;
}
while (beams.hasMoreElements()) {
final URL beam = (URL) beams.nextElement();
modules.add(new Path(beam.getPath()).removeFileExtension().lastSegment());
}
return modules;
}
@Override
public void dispose() {
if (disposed) {
return;
}
disposed = true;
unloadDebuggerCode();
}
}