| /******************************************************************************* |
| * Copyright (c) 2013, 2015, 2018 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Alexandre Montplaisir - Initial API and implementation |
| * Marc-Andre Laperle - Map from binary file |
| * Mikael Ferland - Improve validation for function name mapping from a text file |
| * Viet-Hung Phan - Use CPPFILT for c++ symbols demangling |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.internal.tmf.core.callstack; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.TreeMap; |
| 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.IBinaryFile; |
| import org.eclipse.cdt.core.IBinaryParser.ISymbol; |
| import org.eclipse.cdt.utils.CPPFilt; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.internal.tmf.core.Activator; |
| import org.eclipse.tracecompass.tmf.core.symbols.TmfResolvedSymbol; |
| |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * Class containing the different methods to import an address->name mapping. |
| * |
| * @author Alexandre Montplaisir |
| */ |
| public final class FunctionNameMapper { |
| |
| /** |
| * Arbitrary value used to guess the mapping type, if one pattern has this more |
| * hits than others, stop |
| */ |
| private static final int DIFF_LIMIT = 10; |
| private static final Pattern REMOVE_ZEROS_PATTERN = Pattern.compile("^0+(?!$)"); //$NON-NLS-1$ |
| private static final Pattern NM_PATTERN = Pattern.compile("([0-9a-f]+)([\\s][a-zA-Z][\\s])(.+)"); //$NON-NLS-1$ |
| private static final Pattern MAP_WITH_SIZE_PATTERN = Pattern.compile("([0-9a-f]+)[\\s]([a-f0-9]+)[\\s](.+)"); //$NON-NLS-1$ |
| |
| /** |
| * The type of mapping used in a file. Each type of mapping has its pattern |
| * and they may overlap |
| * |
| * @author Geneviève Bastien |
| */ |
| public enum MappingType { |
| /** |
| * The format of the mapping is the same as the one generated with the |
| * nm command |
| */ |
| NM, |
| /** |
| * The format of the mapping is address size symbol_text |
| */ |
| MAP_WITH_SIZE, |
| /** |
| * The format is unknown |
| */ |
| UNKNOWN |
| } |
| |
| private FunctionNameMapper() { |
| // No to be instantiated |
| } |
| |
| /** |
| * Get the function name mapping from a text file obtained by doing |
| * |
| * <pre> |
| * nm[--demangle][binary] > file.txt |
| * </pre> |
| * |
| * @param mappingFile |
| * The file to import |
| * @return A map<address, function name> of the results |
| */ |
| public static @Nullable Map<@NonNull Long, @NonNull TmfResolvedSymbol> mapFromNmTextFile(File mappingFile) { |
| Map<@NonNull Long, @NonNull TmfResolvedSymbol> map = new TreeMap<>(); |
| |
| CPPFilt cppFilt = null; |
| try (FileReader fr = new FileReader(mappingFile); |
| BufferedReader reader = new BufferedReader(fr);) { |
| try { |
| cppFilt = new CPPFilt(); |
| } catch (IOException e) { |
| Activator.logError("Error to instantiate the c++filt", e); //$NON-NLS-1$ |
| } |
| for (String line = reader.readLine(); line != null; line = reader.readLine()) { |
| Matcher matcher = NM_PATTERN.matcher(line); |
| if (matcher.find()) { |
| long address = Long.parseUnsignedLong(stripLeadingZeros(Objects.requireNonNull(matcher.group(1))), 16); |
| String name = Objects.requireNonNull(matcher.group(3)); |
| name = (cppFilt == null) ? name : nameFromCppFilt(cppFilt, name); |
| map.put(address, new TmfResolvedSymbol(address, Objects.requireNonNull(name))); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| return null; |
| } catch (IOException e) { |
| /* Stop reading the file at this point */ |
| } finally { |
| if (cppFilt != null) { |
| cppFilt.dispose(); |
| } |
| } |
| |
| return map.isEmpty() ? null : ImmutableMap.copyOf(map); |
| } |
| |
| /** |
| * Get the function name mapping from a text file formatted as address size |
| * name, for example, files obtained using the perf-map-agent for java |
| * |
| * @param mappingFile |
| * The file to import |
| * @return A map<address, function name> of the results |
| */ |
| public static @Nullable Map<@NonNull Long, @NonNull TmfResolvedSymbol> mapFromSizedTextFile(File mappingFile) { |
| Map<@NonNull Long, @NonNull TmfResolvedSymbol> map = new TreeMap<>(); |
| |
| CPPFilt cppFilt = null; |
| try (FileReader fr = new FileReader(mappingFile); |
| BufferedReader reader = new BufferedReader(fr);) { |
| try { |
| cppFilt = new CPPFilt(); |
| } catch (IOException e) { |
| Activator.logError("Error to instantiate the c++filt", e); //$NON-NLS-1$ |
| } |
| for (String line = reader.readLine(); line != null; line = reader.readLine()) { |
| Matcher matcher = MAP_WITH_SIZE_PATTERN.matcher(line); |
| if (matcher.find()) { |
| long address = Long.parseUnsignedLong(stripLeadingZeros(Objects.requireNonNull(matcher.group(1))), 16); |
| long size = Long.parseUnsignedLong(stripLeadingZeros(Objects.requireNonNull(matcher.group(2))), 16); |
| String name = Objects.requireNonNull(matcher.group(3)); |
| name = (cppFilt == null) ? name : nameFromCppFilt(cppFilt, name); |
| map.put(address, new TmfResolvedSizedSymbol(address, Objects.requireNonNull(name), size)); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| return null; |
| } catch (IOException e) { |
| /* Stop reading the file at this point */ |
| } finally { |
| if (cppFilt != null) { |
| cppFilt.dispose(); |
| } |
| } |
| |
| return map.isEmpty() ? null : ImmutableMap.copyOf(map); |
| } |
| |
| /** |
| * Use c++filt to decipher the c++ symbols |
| * @param cppFilt |
| * |
| * @param symbol |
| * to be demangled |
| * @return if the demangler returns null, return the symbol as is |
| */ |
| public static @Nullable String nameFromCppFilt(CPPFilt cppFilt, String symbol) { |
| try { |
| String functionName = cppFilt.getFunction(symbol); |
| return functionName != null ? functionName : symbol; |
| } catch (IOException e) { |
| Activator.logError("Error getting function name from c++filt", e); //$NON-NLS-1$ |
| return symbol; |
| } |
| } |
| |
| /** |
| * Guesses the type of mapping in this file by parsing its line and finding |
| * the one with the most hits |
| * |
| * @param mappingFile |
| * The file containing the symbol mapping |
| * @return The most likely mapping type or {@link MappingType#UNKNOWN} if |
| * the file corresponds to no known mapping |
| */ |
| public static MappingType guessMappingType(File mappingFile) { |
| int nmHits = 0; |
| int sizeHits = 0; |
| try (FileReader fr = new FileReader(mappingFile); |
| BufferedReader reader = new BufferedReader(fr);) { |
| for (String line = reader.readLine(); line != null; line = reader.readLine()) { |
| Matcher matcher = NM_PATTERN.matcher(line); |
| if (matcher.find()) { |
| nmHits++; |
| } |
| matcher = MAP_WITH_SIZE_PATTERN.matcher(line); |
| if (matcher.find()) { |
| sizeHits++; |
| } |
| if (Math.abs(sizeHits - nmHits) > DIFF_LIMIT) { |
| break; |
| } |
| } |
| // No hits either way, return null |
| if (!(nmHits > 0 || sizeHits > 0)) { |
| return MappingType.UNKNOWN; |
| } |
| return nmHits > sizeHits ? MappingType.NM : MappingType.MAP_WITH_SIZE; |
| } catch (IOException e) { |
| // Simply return unknown type |
| } |
| return MappingType.UNKNOWN; |
| } |
| |
| /** |
| * Get the function name mapping from an executable binary. |
| * |
| * @param file |
| * The file to import |
| * @return A map<address, function name> of the results |
| */ |
| public static Map<@NonNull Long, @NonNull TmfResolvedSymbol> mapFromBinaryFile(File file) { |
| Map<@NonNull Long, @NonNull TmfResolvedSymbol> map = new TreeMap<>(); |
| IBinaryParser.IBinaryObject binaryObject = getBinaryObject(file); |
| if (binaryObject != null) { |
| ISymbol[] symbols = binaryObject.getSymbols(); |
| for (ISymbol symbol : symbols) { |
| String address = symbol.getAddress().toHexAddressString(); |
| Long decodedAddr = Long.decode(address); |
| map.put(decodedAddr, new TmfResolvedSymbol(decodedAddr, Objects.requireNonNull(symbol.getName()))); |
| } |
| } |
| |
| return ImmutableMap.copyOf(map); |
| } |
| |
| /** |
| * Strip the leading zeroes from the address |
| */ |
| private static String stripLeadingZeros(String address) { |
| return Objects.requireNonNull(REMOVE_ZEROS_PATTERN.matcher(address).replaceFirst("")); //$NON-NLS-1$ |
| } |
| |
| private static IBinaryParser.@Nullable IBinaryObject getBinaryObject(File file) { |
| IPath filePath = new Path(file.toString()); |
| |
| /* Get all the available binary parsers */ |
| final List<IBinaryParser> binaryParsers = new ArrayList<>(); |
| IConfigurationElement[] elements = Platform.getExtensionRegistry() |
| .getConfigurationElementsFor(CCorePlugin.BINARY_PARSER_UNIQ_ID); |
| for (IConfigurationElement element : elements) { |
| IConfigurationElement[] children = element.getChildren("run"); //$NON-NLS-1$ |
| for (final IConfigurationElement run : children) { |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void run() throws Exception { |
| IBinaryParser binaryParser = (IBinaryParser) run.createExecutableExtension("class"); //$NON-NLS-1$ |
| binaryParsers.add(Objects.requireNonNull(binaryParser)); |
| } |
| |
| @Override |
| public void handleException(@Nullable Throwable exception) { |
| Activator.logError("Error creating binary parser", exception); //$NON-NLS-1$ |
| } |
| }); |
| } |
| } |
| |
| /* |
| * Find the maximum "hint" buffer size we'll need from all the parsers |
| */ |
| int hintBufferSize = 0; |
| for (IBinaryParser parser : binaryParsers) { |
| if (parser.getHintBufferSize() > hintBufferSize) { |
| hintBufferSize = Math.max(hintBufferSize, parser.getHintBufferSize()); |
| } |
| } |
| |
| /* Read the initial "hint" bytes */ |
| byte[] hintBuffer = new byte[hintBufferSize]; |
| if (hintBufferSize > 0) { |
| try (InputStream is = new FileInputStream(file)) { |
| |
| int count = 0; |
| // Make sure we read up to 'hints' bytes if we possibly can |
| while (count < hintBufferSize) { |
| int bytesRead = is.read(hintBuffer, count, hintBufferSize - count); |
| if (bytesRead < 0) { |
| break; |
| } |
| count += bytesRead; |
| } |
| if (count > 0 && count < hintBuffer.length) { |
| byte[] array = new byte[count]; |
| System.arraycopy(hintBuffer, 0, array, 0, count); |
| hintBuffer = array; |
| } |
| } catch (IOException e) { |
| Activator.logError("Error reading initial bytes of binary file", e); //$NON-NLS-1$ |
| return null; |
| } |
| } |
| |
| /* For all binary parsers, try to get a binary object */ |
| for (IBinaryParser parser : binaryParsers) { |
| if (parser.isBinary(hintBuffer, filePath)) { |
| IBinaryFile binFile; |
| try { |
| binFile = parser.getBinary(hintBuffer, filePath); |
| if (binFile instanceof IBinaryParser.IBinaryObject) { |
| return (IBinaryParser.IBinaryObject) binFile; |
| } |
| } catch (IOException e) { |
| Activator.logError("Error parsing binary file", e); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| } |