/******************************************************************************* * Copyright (c) 2009-2016 STMicroelectronics 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: * Xavier Raynaud <xavier.raynaud@st.com> - initial API and implementation * Red Hat Inc. - ongoing maintenance * Ingenico - Vincent Guignot <vincent.guignot@ingenico.com> - Add binutils strings *******************************************************************************/ package org.eclipse.linuxtools.binutils.utils; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.IAddress; import org.eclipse.cdt.core.IBinaryParser; import org.eclipse.cdt.core.IBinaryParser.IBinaryFile; import org.eclipse.cdt.core.IBinaryParser.IBinaryObject; import org.eclipse.cdt.core.IBinaryParser.ISymbol; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.CoreModelUtil; import org.eclipse.cdt.core.model.IBinary; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; import org.eclipse.cdt.core.settings.model.ICProjectDescription; import org.eclipse.cdt.utils.Addr2line; import org.eclipse.cdt.utils.CPPFilt; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.linuxtools.internal.Activator; /** * This class Is a utility on top of c++filt and addr2line. It allows an easy conversion between address and source * location, and between mangled and demangled symbols. */ public class STSymbolManager { /** * Auto dispose timeout: If some tools has been unused since more that this time (in ms), they are disposed. */ private final static long AUTO_DISPOSE_TIMEOUT = 30000; /** * Singleton instance */ public final static STSymbolManager sharedInstance = new STSymbolManager(); private final static class AutoDisposeAddr2line { private Addr2line addr2line; private long startTime; } private final static class AutoDisposeCPPFilt { private CPPFilt cppfilt; private long startTime; } /** Map of all living instance of addr2line */ private final HashMap<IBinaryObject, AutoDisposeAddr2line> addr2lines = new HashMap<>(); /** Map of all living instance of cppfilt */ private final HashMap<String, AutoDisposeCPPFilt> cppfilts = new HashMap<>(); /** * Constructor */ private STSymbolManager() { Runnable worker = () -> { try { do { try { Thread.sleep(AUTO_DISPOSE_TIMEOUT); } catch (InterruptedException e1) { break; } cleanup(); } while (true); } catch (Exception e2) { Status s = new Status(IStatus.ERROR, Activator.PLUGIN_ID, e2.getMessage(), e2); Activator.getDefault().getLog().log(s); } }; new Thread(worker, "ST System Analysis Symbol Manager").start(); //$NON-NLS-1$ // TODO: perhaps this thread has to be lazy-created ? // and perhaps this thread has to destroy itself when it is no longer // used ? } /** * @since 4.1 */ public synchronized void reset() { Iterator<Entry<IBinaryObject, AutoDisposeAddr2line>> iter = addr2lines.entrySet().iterator(); while (iter.hasNext()) { Entry<IBinaryObject, AutoDisposeAddr2line> entry = iter.next(); AutoDisposeAddr2line ada2l = entry.getValue(); ada2l.addr2line.dispose(); ada2l.addr2line = null; iter.remove(); } Iterator<Entry<String, AutoDisposeCPPFilt>> iter2 = cppfilts.entrySet().iterator(); while (iter2.hasNext()) { Entry<String, AutoDisposeCPPFilt> entry = iter2.next(); AutoDisposeCPPFilt adcppf = entry.getValue(); adcppf.cppfilt.dispose(); adcppf.cppfilt = null; } } /** * each {@link #AUTO_DISPOSE_TIMEOUT} ms, the unused addr2line and c++filt programs are disposed. */ private synchronized void cleanup() { long currentTime = System.currentTimeMillis(); Iterator<Entry<IBinaryObject, AutoDisposeAddr2line>> iter = addr2lines.entrySet().iterator(); while (iter.hasNext()) { Entry<IBinaryObject, AutoDisposeAddr2line> entry = iter.next(); AutoDisposeAddr2line ada2l = entry.getValue(); long diff = currentTime - ada2l.startTime; if (diff > AUTO_DISPOSE_TIMEOUT) { if (ada2l.addr2line != null) { ada2l.addr2line.dispose(); ada2l.addr2line = null; } iter.remove(); } } Iterator<Entry<String, AutoDisposeCPPFilt>> iter2 = cppfilts.entrySet().iterator(); while (iter2.hasNext()) { Entry<String, AutoDisposeCPPFilt> entry = iter2.next(); AutoDisposeCPPFilt adcppf = entry.getValue(); long diff = currentTime - adcppf.startTime; if (diff > AUTO_DISPOSE_TIMEOUT) { if (adcppf.cppfilt != null) { adcppf.cppfilt.dispose(); adcppf.cppfilt = null; } iter2.remove(); } } } /** * Demangle the given symbol * @param symbol * @param project The project to be * @return The demangled symbol. */ public synchronized String demangle(ISymbol symbol, IProject project) { String cpu = symbol.getBinaryObject().getCPU(); String symbolName = symbol.getName(); return demangleImpl(symbolName, cpu, project); } /** * Demangle the given symbol * @param program * @param symbolName * @param project * @return The demangled symbol. */ public synchronized String demangle(IBinaryObject program, String symbolName, IProject project) { String cpu = program.getCPU(); return demangleImpl(symbolName, cpu, project); } /** * Demangle the given symbol * @param symbolName * @param cpu * @param project * @param symbol * @return */ private synchronized String demangleImpl(String symbolName, String cpu, IProject project) { CPPFilt cppfilt = getCppFilt(cpu, project); if (cppfilt != null && (symbolName.startsWith("_Z") || symbolName.startsWith("_G"))) { //$NON-NLS-1$ //$NON-NLS-2$ try { symbolName = cppfilt.getFunction(symbolName); } catch (IOException e) { // TODO: log the error ? } } return symbolName; } /** * @param program * @param address * @param project * @return the line number of the given address */ public synchronized int getLineNumber(IBinaryObject program, IAddress address, IProject project) { Addr2line addr2line = getAddr2line(program, project); if (addr2line == null) { return -1; } try { return addr2line.getLineNumber(address); } catch (IOException e) { // TODO: log the error ?; // Perhaps log the error only once, because // this method is called many many times... return -1; } } /** * @param symbol * @param project * @return the line number of the given symbol */ public int getLineNumber(ISymbol symbol, IProject project) { IBinaryObject obj = symbol.getBinaryObject(); IAddress address = symbol.getAddress(); return getLineNumber(obj, address, project); } /** * @param program * @param address * @param project * @return the file name of the given address */ public synchronized String getFileName(IBinaryObject program, IAddress address, IProject project) { Addr2line addr2line = getAddr2line(program, project); if (addr2line == null) { return null; } try { return addr2line.getFileName(address); } catch (IOException e) { // TODO: log the error ?; // Perhaps log the error only once, because // this method is called many many times... return null; } } /** * @param symbol * @param project * @return the filename of the given symbol */ public String getFilename(ISymbol symbol, IProject project) { IBinaryObject obj = symbol.getBinaryObject(); IAddress address = symbol.getAddress(); return getFileName(obj, address, project); } /** * Gets the c++filt support for the given program Note that the instance if kept in a local hashmap, and discarded * after 30 seconds of inactivity. * @param cpu * @param project * @param program * @return an instance of CPPFilt suitable for the given program */ private synchronized CPPFilt getCppFilt(String cpu, IProject project) { AutoDisposeCPPFilt adCppfilt = cppfilts.get(cpu); if (adCppfilt == null) { adCppfilt = new AutoDisposeCPPFilt(); cppfilts.put(cpu, adCppfilt); } if (adCppfilt.cppfilt == null) { try { adCppfilt.cppfilt = STBinutilsFactoryManager.getCPPFilt(cpu, project); } catch (IOException e) { // TODO: log the error ?; // Perhaps log the error only once, because // this method is called many many times... return null; } } adCppfilt.startTime = System.currentTimeMillis(); return adCppfilt.cppfilt; } /** * Gets the addr2line support for the given program Note that the instance if kept in a local hashmap, and discarded * after 30 seconds of inactivity. * @param program * @param project * @return an instance of Addr2line suitable for the given program */ private synchronized Addr2line getAddr2line(IBinaryObject program, IProject project) { AutoDisposeAddr2line adAddr2line = addr2lines.get(program); if (adAddr2line == null) { adAddr2line = new AutoDisposeAddr2line(); addr2lines.put(program, adAddr2line); } if (adAddr2line.addr2line == null) { try { adAddr2line.addr2line = STBinutilsFactoryManager.getAddr2line(program.getCPU(), program.getPath() .toOSString(), project); } catch (IOException e) { // TODO: log the error ?; // Perhaps log the error only once, because // this method is called many many times... return null; } } adAddr2line.startTime = System.currentTimeMillis(); return adAddr2line.addr2line; } /** * Gets the strings support for the given program. * @param program * @param project * @return an instance of Strings suitable for the given program * @since 6.0 */ public synchronized STStrings getStrings(IBinaryObject program, IProject project) { STStrings strings = null; try { strings = STBinutilsFactoryManager.getStrings(program.getCPU(), project); } catch (IOException e) { e.printStackTrace(); return null; } return strings; } /** * Gets the IBinaryObject corresponding to the given path (absolute path in filesystem). If a IBinaryObject * corresponding to the given path has been already built by eclipse, return it. Otherwise build a new * IBinaryObject, according to project preferences. Note that it may return null if the path is invalid, or is not a * valid binary file. * @param loc * @return a IBinaryObject */ public IBinaryObject getBinaryObject(String loc) { IPath path = new Path(loc); return getBinaryObject(path); } /** * Gets the IBinaryObject corresponding to the given path (absolute path in filesystem). If a IBinaryObject * corresponding to the given path has been already built by eclipse, return it. Otherwise build a new * IBinaryObject, according to project preferences. Note that it may return null if the path is invalid, or is not a * valid binary file. * @param path * @return a IBinaryObject */ public IBinaryObject getBinaryObject(IPath path) { return getBinaryObject(path, null); } /** * Gets the IBinaryObject corresponding to the given path (absolute path in filesystem). If a IBinaryObject * corresponding to the given path has been already built by eclipse, return it. Otherwise build a new * IBinaryObject, according to project preferences. Note that it may return null if the path is invalid, or is not a * valid binary file. * @param path * @param defaultparser * @return a IBinaryObject */ private IBinaryObject getBinaryObject(IPath path, IBinaryParser defaultparser) { IFile c = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path); List<IBinaryParser> parsers; if (c != null) { IBinaryObject object = getAlreadyExistingBinaryObject(c); if (object != null) { return object; } parsers = getBinaryParser(c.getProject()); } else { parsers = new LinkedList<>(); } if (defaultparser == null) { try { defaultparser = CCorePlugin.getDefault().getDefaultBinaryParser(); } catch (CoreException e) { Activator.getDefault().getLog().log(e.getStatus()); } } if (defaultparser != null) { parsers.add(defaultparser); } IBinaryObject ret = buildBinaryObject(path, parsers); if (ret == null) { // trying all BinaryParsers... parsers.clear(); IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, CCorePlugin.BINARY_PARSER_SIMPLE_ID); for (IExtension extension : extensionPoint.getExtensions()) { if (extension != null) { IConfigurationElement element[] = extension.getConfigurationElements(); for (IConfigurationElement element2 : element) { if (element2.getName().equalsIgnoreCase("cextension")) { //$NON-NLS-1$ IBinaryParser parser; try { parser = (IBinaryParser) element2.createExecutableExtension("run"); //$NON-NLS-1$ parsers.add(parser); } catch (CoreException e) { } } } } } ret = buildBinaryObject(path, parsers); } return ret; } /** * Validate the binary file. In particular, verify that this binary file can be decoded. * @param o * @return the binary object, or null. */ private IBinaryObject validateBinary(IBinaryFile o) { if (o instanceof IBinaryObject) { IBinaryObject object = (IBinaryObject) o; String s = object.getCPU(); // if (s != null && !s.isEmpty()) { return object; } } return null; } private IBinaryObject buildBinaryObject(IPath path, List<IBinaryParser> parsers) { for (IBinaryParser iBinaryParser : parsers) { IBinaryObject o = buildBinaryObject(path, iBinaryParser); if (o != null) { return o; } } return null; } /** * Build a binary object with the given file and parser. Also verify that the builded binary object is valid (@see * #validateBinary) * @param path The path to the binary object. * @param parser Parser to use to parse the object. * @return The newly parsed object. */ private IBinaryObject buildBinaryObject(IPath path, IBinaryParser parser) { if (parser == null) { return null; } IBinaryFile bf = null; try { bf = parser.getBinary(path); } catch (IOException e) { // do nothing ? } return validateBinary(bf); } /** * Ask the workbench to find if a binary object already exist for the given file * @param c The file to look binary object for. * @return The binary object if found, null otherwise. */ private IBinaryObject getAlreadyExistingBinaryObject(IFile c) { IProject project = c.getProject(); if (project != null && project.exists()) { ICProject cproject = CoreModel.getDefault().create(project); if (cproject != null) { try { IBinary[] b = cproject.getBinaryContainer().getBinaries(); for (IBinary binary : b) { IResource r = binary.getResource(); if (r.equals(c)) { IBinaryObject binaryObject = binary.getAdapter(IBinaryObject.class); return validateBinary(binaryObject); } } } catch (CModelException e) { } } } return null; } /** * Retrieve the list of binary parsers defined for the given project. * @param project The project. * @return The binary parsers for this project. */ private List<IBinaryParser> getBinaryParser(IProject project) { List<IBinaryParser> parsers = new LinkedList<>(); ICProjectDescription projDesc = CCorePlugin.getDefault().getProjectDescription(project); if (projDesc == null) { return parsers; } ICConfigurationDescription[] cfgs = projDesc.getConfigurations(); String[] binaryParserIds = CoreModelUtil.getBinaryParserIds(cfgs); IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, CCorePlugin.BINARY_PARSER_SIMPLE_ID); for (String id : binaryParserIds) { IExtension extension = extensionPoint.getExtension(id); if (extension != null) { IConfigurationElement element[] = extension.getConfigurationElements(); for (IConfigurationElement element2 : element) { if (element2.getName().equalsIgnoreCase("cextension")) { //$NON-NLS-1$ try { IBinaryParser parser = (IBinaryParser) element2.createExecutableExtension("run"); //$NON-NLS-1$ if (parser != null) { parsers.add(parser); } } catch (CoreException e) { // TODO: handle exception ? } } } } } return parsers; } }