blob: bf8d039006e207685d6a80beedc2a5130a24cb71 [file] [log] [blame]
/*******************************************************************************
* 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] &gt; file.txt
* </pre>
*
* @param mappingFile
* The file to import
* @return A map&lt;address, function name&gt; 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&lt;address, function name&gt; 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&lt;address, function name&gt; 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;
}
}