/*******************************************************************************
* Copyright (c) 2010, 2011 Ericsson 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:
* Ericsson - Initial API and implementation
* Ericsson - Added support for Mac OS
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.launching;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IBinaryParser;
import org.eclipse.cdt.core.IBinaryParser.IBinaryObject;
import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.CoreModelUtil;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.settings.model.ICConfigExtensionReference;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.SessionType;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunchConfiguration;
public class LaunchUtils {
/**
* A prefix that we use to indicate that a GDB version is for MAC OS
* @since 3.0
*/
public static final String MACOS_GDB_MARKER = "APPLE"; //$NON-NLS-1$
/**
* Verify the following things about the project:
* - is a valid project name given
* - does the project exist
* - is the project open
* - is the project a C/C++ project
*/
public static ICProject verifyCProject(ILaunchConfiguration configuration) throws CoreException {
String name = getProjectName(configuration);
if (name == null) {
abort(LaunchMessages.getString("AbstractCLaunchDelegate.C_Project_not_specified"), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_UNSPECIFIED_PROJECT);
return null;
}
ICProject cproject = getCProject(configuration);
if (cproject == null && name.length() > 0) {
IProject proj = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
if (!proj.exists()) {
abort(LaunchMessages.getFormattedString("AbstractCLaunchDelegate.Project_NAME_does_not_exist", name), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
} else if (!proj.isOpen()) {
abort(LaunchMessages.getFormattedString("AbstractCLaunchDelegate.Project_NAME_is_closed", name), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
}
abort(LaunchMessages.getString("AbstractCLaunchDelegate.Not_a_C_CPP_project"), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
}
return cproject;
}
/**
* Verify that program name of the configuration can be found as a file.
*
* @return Absolute path of the program location
*/
public static IPath verifyProgramPath(ILaunchConfiguration configuration, ICProject cproject) throws CoreException {
String programName = configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, (String)null);
if (programName == null) {
abort(LaunchMessages.getString("AbstractCLaunchDelegate.Program_file_not_specified"), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
}
IPath programPath = new Path(programName);
if (programPath.isEmpty()) {
abort(LaunchMessages.getString("AbstractCLaunchDelegate.Program_file_does_not_exist"), null, //$NON-NLS-1$
ICDTLaunchConfigurationConstants.ERR_NOT_A_C_PROJECT);
}
if (!programPath.isAbsolute() && cproject != null) {
// Find the specified program within the specified project
IFile wsProgramPath = cproject.getProject().getFile(programPath);
programPath = wsProgramPath.getLocation();
}
if (!programPath.toFile().exists()) {
abort(LaunchMessages.getString("AbstractCLaunchDelegate.Program_file_does_not_exist"), //$NON-NLS-1$
new FileNotFoundException(
LaunchMessages.getFormattedString("AbstractCLaunchDelegate.PROGRAM_PATH_not_found", //$NON-NLS-1$
programPath.toOSString())),
ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_EXIST);
}
return programPath;
}
/**
* Verify that the executable path points to a valid binary file.
*
* @return An object representing the binary file.
*/
public static IBinaryObject verifyBinary(ILaunchConfiguration configuration, IPath exePath) throws CoreException {
ICProject cproject = getCProject(configuration);
if (cproject != null) {
ICConfigExtensionReference[] parserRefs = CCorePlugin.getDefault().getDefaultBinaryParserExtensions(cproject.getProject());
for (ICConfigExtensionReference parserRef : parserRefs) {
try {
IBinaryParser parser = CoreModelUtil.getBinaryParser(parserRef);
IBinaryObject exe = (IBinaryObject)parser.getBinary(exePath);
if (exe != null) {
return exe;
}
} catch (ClassCastException e) {
} catch (IOException e) {
}
}
}
IBinaryParser parser = CCorePlugin.getDefault().getDefaultBinaryParser();
try {
return (IBinaryObject)parser.getBinary(exePath);
} catch (ClassCastException e) {
} catch (IOException e) {
}
abort(LaunchMessages.getString("AbstractCLaunchDelegate.Program_is_not_a_recognized_executable"), //$NON-NLS-1$
new FileNotFoundException(
LaunchMessages.getFormattedString("AbstractCLaunchDelegate.Program_is_not_a_recognized_executable", //$NON-NLS-1$
exePath.toOSString())),
ICDTLaunchConfigurationConstants.ERR_PROGRAM_NOT_BINARY);
return null;
}
/**
* Throws a core exception with an error status object built from the given
* message, lower level exception, and error code.
*
* @param message
* the status message
* @param exception
* lower level exception associated with the error, or
* <code>null</code> if none
* @param code
* error code
*/
private static void abort(String message, Throwable exception, int code) throws CoreException {
MultiStatus status = new MultiStatus(GdbPlugin.PLUGIN_ID, code, message, exception);
status.add(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, code,
exception == null ? "" : exception.getLocalizedMessage(), //$NON-NLS-1$
exception));
throw new CoreException(status);
}
/**
* Returns an ICProject based on the project name provided in the configuration.
* First look for a project by name, and then confirm it is a C/C++ project.
*/
public static ICProject getCProject(ILaunchConfiguration configuration) throws CoreException {
String projectName = getProjectName(configuration);
if (projectName != null) {
projectName = projectName.trim();
if (projectName.length() > 0) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
ICProject cProject = CCorePlugin.getDefault().getCoreModel().create(project);
if (cProject != null && cProject.exists()) {
return cProject;
}
}
}
return null;
}
private static String getProjectName(ILaunchConfiguration configuration) throws CoreException {
return configuration.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String)null);
}
public static IPath getGDBPath(ILaunchConfiguration configuration) {
String defaultGdbCommand = Platform.getPreferencesService().getString(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_DEFAULT_GDB_COMMAND,
IGDBLaunchConfigurationConstants.DEBUGGER_DEBUG_NAME_DEFAULT, null);
IPath retVal = new Path(defaultGdbCommand);
try {
String gdb = configuration.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUG_NAME, defaultGdbCommand);
gdb = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(gdb, false);
retVal = new Path(gdb);
} catch (CoreException e) {
}
return retVal;
}
/**
* Find gdb version info from a string object which is supposed to
* contain output text of "gdb --version" command.
*
* @param versionOutput
* output text from "gdb --version" command .
* @return
* String representation of version of gdb such as "6.8" on success;
* empty string otherwise.
* @since 2.0
*/
public static String getGDBVersionFromText(String versionOutput) {
String version = "";//$NON-NLS-1$
// These are the GDB version patterns I have seen up to now
// The pattern works for all of them extracting the version of 6.8.50.20080730
// GNU gdb 6.8.50.20080730
// GNU gdb (GDB) 6.8.50.20080730-cvs
// GNU gdb (Ericsson GDB 1.0-10) 6.8.50.20080730-cvs
// GNU gdb (GDB) Fedora (7.0-3.fc12)
// GNU gdb Red Hat Linux (6.3.0.0-1.162.el4rh)
Pattern pattern = Pattern.compile(" gdb( \\(.*?\\))? (\\w* )*\\(?(\\d*(\\.\\d*)*)", Pattern.MULTILINE); //$NON-NLS-1$
Matcher matcher = pattern.matcher(versionOutput);
if (matcher.find()) {
version = matcher.group(3);
// Temporary for cygwin, until GDB 7 is released
// Any cygwin GDB staring with 6.8 should be treated as plain 6.8
if (versionOutput.toLowerCase().indexOf("cygwin") != -1 && //$NON-NLS-1$
version.startsWith("6.8")) { //$NON-NLS-1$
version = "6.8"; //$NON-NLS-1$
}
}
// Look for the case of Apple's GDB, since the version must be handled differently
// The format is:
// GNU gdb 6.3.50-20050815 (Apple version gdb-696) (Sat Oct 20 18:20:28 GMT 2007)
// GNU gdb 6.3.50-20050815 (Apple version gdb-966) (Tue Mar 10 02:43:13 UTC 2009)
// GNU gdb 6.3.50-20050815 (Apple version gdb-1346) (Fri Sep 18 20:40:51 UTC 2009)
// GNU gdb 6.3.50-20050815 (Apple version gdb-1461.2) (Fri Mar 5 04:43:10 UTC 2010)
// It seems the version that changes is the "Apple version" but we still use both.
// The Mac OS prefix and version are appended to the normal version so the
// returned string has this format: 6.3.50-20050815APPLE1346. The normal version and the
// Apple version are extracted later and passed to the MacOS services factory.
if (versionOutput.indexOf("Apple") != -1) { //$NON-NLS-1$
// Add a prefix to indicate we are dealing with an Apple GDB
version += MACOS_GDB_MARKER;
Pattern aPattern = Pattern.compile(" \\(Apple version gdb-(\\d+(\\.\\d+)*)\\)", Pattern.MULTILINE); //$NON-NLS-1$
Matcher aMatcher = aPattern.matcher(versionOutput);
if (aMatcher.find()) {
version += aMatcher.group(1);
}
}
return version;
}
/**
* This method actually launches 'gdb --vesion' to determine the version
* of the GDB that is being used. This method should ideally be called
* only once and the resulting version string stored for future uses.
*/
public static String getGDBVersion(final ILaunchConfiguration configuration) throws CoreException {
Process process = null;
String cmd = getGDBPath(configuration).toOSString() + " --version"; //$NON-NLS-1$
try {
process = ProcessFactory.getFactory().exec(cmd, getLaunchEnvironment(configuration));
} catch(IOException e) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Error while launching command: " + cmd, e.getCause()));//$NON-NLS-1$
}
InputStream stream = null;
StringBuilder cmdOutput = new StringBuilder(200);
try {
stream = process.getInputStream();
Reader r = new InputStreamReader(stream);
BufferedReader reader = new BufferedReader(r);
String line;
while ((line = reader.readLine()) != null) {
cmdOutput.append(line);
}
} catch (IOException e) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED,
"Error reading GDB STDOUT after sending: " + cmd, e.getCause()));//$NON-NLS-1$
} finally {
// Cleanup to avoid leaking pipes
// Close the stream we used, and then destroy the process
// Bug 345164
if (stream != null) {
try {
stream.close();
} catch (IOException e) {}
}
process.destroy();
}
return getGDBVersionFromText(cmdOutput.toString());
}
public static boolean getIsAttach(ILaunchConfiguration config) {
try {
String debugMode = config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN );
if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN)) {
return false;
} else if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_ATTACH)) {
return true;
} else if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_CORE)) {
return false;
} else if (debugMode.equals(IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE)) {
return false;
} else if (debugMode.equals(IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE_ATTACH)) {
return true;
}
} catch (CoreException e) {
}
return false;
}
public static SessionType getSessionType(ILaunchConfiguration config) {
try {
String debugMode = config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN );
if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN)) {
return SessionType.LOCAL;
} else if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_ATTACH)) {
return SessionType.LOCAL;
} else if (debugMode.equals(ICDTLaunchConfigurationConstants.DEBUGGER_MODE_CORE)) {
return SessionType.CORE;
} else if (debugMode.equals(IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE)) {
return SessionType.REMOTE;
} else if (debugMode.equals(IGDBLaunchConfigurationConstants.DEBUGGER_MODE_REMOTE_ATTACH)) {
return SessionType.REMOTE;
} else {
assert false : "Unexpected session-type attribute in launch config: " + debugMode; //$NON-NLS-1$
}
} catch (CoreException e) {
}
return SessionType.LOCAL;
}
/**
* Gets the CDT environment from the CDT project's configuration referenced by the
* launch
* @since 3.0
*/
public static String[] getLaunchEnvironment(ILaunchConfiguration config) throws CoreException {
// Get the project
String projectName = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String)null);
if (projectName == null) {
return null;
}
projectName = projectName.trim();
if (projectName.length() == 0) {
return null;
}
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (project == null || !project.isAccessible())
return null;
ICProjectDescription projDesc = CoreModel.getDefault().getProjectDescription(project, false);
// Not a CDT project?
if (projDesc == null)
return null;
String buildConfigID = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_BUILD_CONFIG_ID, ""); //$NON-NLS-1$
ICConfigurationDescription cfg = null;
if (buildConfigID.length() != 0)
cfg = projDesc.getConfigurationById(buildConfigID);
// if configuration is null fall-back to active
if (cfg == null)
cfg = projDesc.getActiveConfiguration();
// Environment variables and inherited vars
HashMap<String, String> envMap = new HashMap<String, String>();
IEnvironmentVariable[] vars = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariables(cfg, true);
for (IEnvironmentVariable var : vars)
envMap.put(var.getName(), var.getValue());
// Add variables from build info
ICdtVariable[] build_vars = CCorePlugin.getDefault().getCdtVariableManager().getVariables(cfg);
for (ICdtVariable var : build_vars) {
try {
envMap.put(var.getName(), var.getStringValue());
} catch (CdtVariableException e) {
// Some Eclipse dynamic variables can't be resolved dynamically... we don't care.
}
}
// Turn it into an envp format
List<String> strings= new ArrayList<String>(envMap.size());
for (Entry<String, String> entry : envMap.entrySet()) {
StringBuffer buffer= new StringBuffer(entry.getKey());
buffer.append('=').append(entry.getValue());
strings.add(buffer.toString());
}
return strings.toArray(new String[strings.size()]);
}
/**
* Returns <code>true</code> if the launch is meant to be in Non-Stop mode.
* Returns <code>false</code> otherwise.
*
* @since 4.0
*/
public static boolean getIsNonStopMode(ILaunchConfiguration config) {
try {
return config.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP,
getIsNonStopModeDefault());
} catch (CoreException e) {
}
return false;
}
/**
* Returns workspace-level default for the Non-Stop mode.
*
* @since 4.0
*/
public static boolean getIsNonStopModeDefault() {
return Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_DEFAULT_NON_STOP,
IGDBLaunchConfigurationConstants.DEBUGGER_NON_STOP_DEFAULT, null);
}
/**
* Returns workspace-level default for the stop at main option.
*
* @since 4.0
*/
public static boolean getStopAtMainDefault() {
return Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_DEFAULT_STOP_AT_MAIN,
ICDTLaunchConfigurationConstants.DEBUGGER_STOP_AT_MAIN_DEFAULT, null);
}
/**
* Returns workspace-level default for the stop at main symbol.
*
* @since 4.0
*/
public static String getStopAtMainSymbolDefault() {
return Platform.getPreferencesService().getString(GdbPlugin.PLUGIN_ID,
IGdbDebugPreferenceConstants.PREF_DEFAULT_STOP_AT_MAIN_SYMBOL,
ICDTLaunchConfigurationConstants.DEBUGGER_STOP_AT_MAIN_SYMBOL_DEFAULT, null);
}
/**
* Returns <code>true</code> if the launch is meant to be for post-mortem
* tracing. Returns <code>false</code> otherwise.
*
* @since 4.0
*/
public static boolean getIsPostMortemTracing(ILaunchConfiguration config) {
SessionType sessionType = LaunchUtils.getSessionType(config);
if (sessionType == SessionType.CORE) {
try {
String coreType = config.getAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_POST_MORTEM_TYPE,
IGDBLaunchConfigurationConstants.DEBUGGER_POST_MORTEM_TYPE_DEFAULT);
return coreType.equals(IGDBLaunchConfigurationConstants.DEBUGGER_POST_MORTEM_TRACE_FILE);
} catch (CoreException e) {
}
}
return false;
}
}