Skip to content

Commit

Permalink
lttng2.ust: use nm to resolve functions
Browse files Browse the repository at this point in the history
Use nm to resolve functions names, source and line.

Before this commit, the addr2line utility was often used to resolve
symbols to function names, source code and line number when trace
compass was able to identify the traced binary on the local machine.
This was shown to be suboptimal when having to resolve a large number
of symbols, because:
- addr2line was called at least once per symbol;
- the result of addr2line was stored in a cached data structure, and a
  slow cache trashing mechanism was executed once the cache was full.

Due to the reasons above, trace compass was non-responsive, especially
for Flame Chart or Function Stats views.

Increasing cache size was improving performances, but still at the
expenses of memory consumption. In addition, its cache size can be
increased only when trace compass is started, which is not intuitive
for the user.

After this commit, the nm utility is used (executed once on the
binary) to fill the debuginfo analysis SS. Then the debuginfo SS is
queried before spawning an addr2line process. Due to the fact that nm
is executed only once per binary, and that it can solve the great
majority of symbols, multiple calls to addr2line are minimized and
trace compass is much more responsive. Memory is also saved.

[Added] use nm to resolve functions implicitly and keep results on disk.

Change-Id: I9e562b0ac2d5fffc08390a0053df7ba2dbff307f
Signed-off-by: Francesco Robino <francesco.robino@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/195485
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
  • Loading branch information
frallax authored and marco-miller committed Nov 1, 2022
1 parent b90fbe6 commit 6463d29
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 4 deletions.
Expand Up @@ -3195,9 +3195,9 @@ associate trace events with their location in the original source code.
To make use of this feature, first make sure your binaries are compiled with
debug information (-g), so that the instruction pointers can be mapped to source
code locations. This lookup is made using the ''addr2line'' command-line utility,
which needs to be installed and on the '''$PATH''' of the system running Trace
Compass. ''addr2line'' is available in most Linux distributions, Mac OS X, Windows using Cygwin and others.
code locations. This lookup is made using the ''addr2line'' and ''nm'' command-line utilities,
which need to be installed and on the '''$PATH''' of the system running Trace
Compass. ''addr2line'' and ''nm'' are available in most Linux distributions, Mac OS X, Windows using Cygwin and others.
The following trace events need to be present in the trace:
Expand Down
Expand Up @@ -13,29 +13,41 @@

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.event.aspect.LinuxPidAspect;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils;
import org.eclipse.tracecompass.common.core.process.ProcessUtils;
import org.eclipse.tracecompass.internal.lttng2.ust.core.trace.layout.LttngUst28EventLayout;
import org.eclipse.tracecompass.lttng2.ust.core.trace.LttngUstTrace;
import org.eclipse.tracecompass.lttng2.ust.core.trace.layout.ILttngUstEventLayout;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
import org.eclipse.tracecompass.statesystem.core.StateSystemUtils;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.statesystem.AbstractTmfStateProvider;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.util.Pair;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.BaseEncoding;

/**
Expand All @@ -59,6 +71,13 @@
* /vpid/<baddr>/path Path to the binary, e.g. "/usr/lib/libhello.so".
* /vpid/<baddr>/is_pic Integer 1 if the binary contains position
* independent code, 0 otherwise.
* /<binPath> null
* /<binPath>/functionName The symbols resolved using nm (when the nm utility is
* available). Time is used to represent the address range.
* /<binPath>/lineNr Function line nr retrieved from nm (when the nm utility is
* available). Time is used to represent the address range.
* /<binPath>/sourceFileName The file name retrieved from nm (when the nm utility is
* available). Time is used to represent the address range.
* </pre>
*
* The "baddr" attribute name represents the memory mapping base address a
Expand All @@ -84,6 +103,15 @@ public class UstDebugInfoStateProvider extends AbstractTmfStateProvider {
/** State system attribute name for the debug link of the binary */
public static final String DEBUG_LINK_ATTRIB = "debug_link"; //$NON-NLS-1$

/** State system attribute name for the function names identified with nm */
public static final String FUNCTION_NAME = "functionName"; //$NON-NLS-1$

/** State system attribute name for the file names identified with nm */
public static final String SOURCE_FILE_NAME = "sourceFileName"; //$NON-NLS-1$

/** State system attribute name for the line numbers identified with nm */
public static final String LINE_NUMBER = "lineNr"; //$NON-NLS-1$

/** Version of this state provider */
private static final int VERSION = 5;

Expand All @@ -98,6 +126,14 @@ public class UstDebugInfoStateProvider extends AbstractTmfStateProvider {
private static final int STATEDUMP_DEBUG_LINK_INDEX = 7;
private static final int STATEDUMP_START_INDEX = 8;

private static final String NM_EXECUTABLE = "nm"; //$NON-NLS-1$
/*
* Pattern to match nm console output. Usually in the following form:
*
* <value> <type> <name> <source>:<line>
*/
private static final Pattern fNmPattern = Pattern.compile("(\\w+)(\\s+)(\\w)(\\s+)([^\\t]*)([\\t]*)([^\\:]*)([\\:]*)(.*)"); //$NON-NLS-1$

private final LttngUst28EventLayout fLayout;
private final Map<String, Integer> fEventNames;

Expand Down Expand Up @@ -550,4 +586,99 @@ public LttngUstTrace getTrace() {
public int getVersion() {
return VERSION;
}

@Override
public void done() {
Set<String> binPaths = new HashSet<>();
@NonNull ITmfStateSystemBuilder ssb = checkNotNull(getStateSystemBuilder());
List<Integer> vPidQuarks = ssb.getQuarks("*"); //$NON-NLS-1$
for (int vPidQuark : vPidQuarks) {
List<Integer> bAddrQuarks = ssb.getQuarks(vPidQuark, "*"); //$NON-NLS-1$
for (int bAddrQuark : bAddrQuarks) {
try {
Integer pathQuark = ssb.getQuarkRelative(bAddrQuark, PATH_ATTRIB);
List<ITmfStateInterval> states = StateSystemUtils.queryHistoryRange(ssb, pathQuark, ssb.getStartTime(), ssb.getCurrentEndTime());
for (ITmfStateInterval state : states) {
String binPath = state.getValueString();
/*
* The retrieved binary path can be null if we are
* analysing a state when the process represented by the
* vPid is not active. Add to the set only if non null,
* otherwise ignore.
*/
if (binPath != null) {
binPaths.add(binPath);
}
}
} catch (AttributeNotFoundException e) {
/* Something went very wrong. */
throw new IllegalStateException(e);
} catch (StateSystemDisposedException e) {
/*
* This can happen if user closes a trace during the
* analysis execution. Continue with what we have.
*/
}
}
}

for (String binPath : binPaths) {
// run nm and push in ss
getNmInfo(ssb, binPath);
}

super.done();
}

// ------------------------------------------------------------------------
// Utility methods making use of 'nm'
// ------------------------------------------------------------------------

/**
* Given a binary path, returns the nm results sorted by offset/address
*
* @param filePath
* The absolute path of the binary to be used with nm
* @return the nm results sorted by offset/address
*/
private static Iterable<String> callNm(String filePath) {
List<String> command = Arrays.asList(NM_EXECUTABLE, "-l", "-C", filePath); //$NON-NLS-1$ //$NON-NLS-2$
List<String> output = ProcessUtils.getOutputFromCommand(command);
if (output != null) {
// Sort by address (low to high)
Collections.sort(output);
return output;
}
/* Command returned an error */
return Collections.emptySet();
}

private static void getNmInfo(ITmfStateSystemBuilder ssb, String binFilePath) {
Iterable<String> sortedNmResults = callNm(binFilePath);
if (Iterables.size(sortedNmResults) > 0) {
for (String nmLine : sortedNmResults) {
Matcher nmLineMatcher = fNmPattern.matcher(nmLine);
if (nmLineMatcher.matches()) {
String offset = nmLineMatcher.group(1).replaceFirst("^0+(?!$)", ""); //$NON-NLS-1$ //$NON-NLS-2$
String functionName = nmLineMatcher.group(5);
if ((offset != null) && (functionName != null)) {
String sourceFile = nmLineMatcher.group(7);
String lineNr = nmLineMatcher.group(9);
// push in ss
int funNameQuark = ssb.getQuarkAbsoluteAndAdd(binFilePath, FUNCTION_NAME);
int sourceFileQuark = ssb.getQuarkAbsoluteAndAdd(binFilePath, SOURCE_FILE_NAME);
int lineNrQuark = ssb.getQuarkAbsoluteAndAdd(binFilePath, LINE_NUMBER);
try {
Long offsetLong = Long.parseLong(offset, 16) + ssb.getStartTime();
ssb.modifyAttribute(offsetLong, functionName, funNameQuark);
ssb.modifyAttribute(offsetLong, sourceFile, sourceFileQuark);
ssb.modifyAttribute(offsetLong, lineNr, lineNrQuark);
} catch (NumberFormatException e) {
// Do not write in ss
}
}
}
}
}
}
}
Expand Up @@ -12,6 +12,7 @@
package org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
Expand All @@ -23,9 +24,15 @@
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoBinaryAspect;
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoFunctionAspect;
import org.eclipse.tracecompass.lttng2.ust.core.trace.LttngUstTrace;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.symbols.DefaultSymbolProvider;
import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager;
import org.eclipse.tracecompass.tmf.core.symbols.TmfResolvedSymbol;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;

/**
* Symbol provider for UST traces with debug information.
Expand Down Expand Up @@ -77,11 +84,21 @@ public void loadConfiguration(@Nullable IProgressMonitor monitor) {

@Override
public @Nullable TmfResolvedSymbol getSymbol(int pid, long timestamp, long address) {
BinaryCallsite bc = UstDebugInfoBinaryAspect.getBinaryCallsite(getTrace(), pid, timestamp, address);
LttngUstTrace trace = getTrace();
BinaryCallsite bc = UstDebugInfoBinaryAspect.getBinaryCallsite(trace, pid, timestamp, address);
if (bc == null) {
return null;
}

String functionName = getFunctionNameFromSS(bc, trace);
/*
* Return function information only if it is non null, otherwise try to
* resolve the symbol in another way (see code below).
*/
if (functionName != null) {
return new TmfResolvedSymbol(bc.getOffset(), functionName);
}

FunctionLocation loc = UstDebugInfoFunctionAspect.getFunctionFromBinaryLocation(bc);
if (loc != null) {
return new TmfResolvedSymbol(bc.getOffset(), loc.getFunctionName());
Expand All @@ -97,4 +114,44 @@ public void loadConfiguration(@Nullable IProgressMonitor monitor) {
}
return new TmfLibrarySymbol(bc.getOffset(), bc.getBinaryFilePath());
}

/**
* Try to resolve the function name using information stored in the SS of
* the {@link UstDebugInfoAnalysisModule}
*
* @param bc
* the {@link BinaryCallsite}, containing information
* corresponding to an instruction pointer
* @param trace
* the lttng ust trace of the monitored application
* @return the function name, or null if not found
*/
public static @Nullable String getFunctionNameFromSS(BinaryCallsite bc, ITmfTrace trace) {
Iterator<UstDebugInfoAnalysisModule> ustDebugModules = TmfTraceUtils.getAnalysisModulesOfClass(trace, UstDebugInfoAnalysisModule.class).iterator();
if (ustDebugModules.hasNext()) {
UstDebugInfoAnalysisModule ustDebugModule = ustDebugModules.next();
ITmfStateSystem ustDebugSsb = ustDebugModule.getStateSystem();
if (ustDebugSsb != null) {
String binFilePath = bc.getBinaryFilePath();
long offset = bc.getOffset() + ustDebugSsb.getStartTime();
try {
int functionNameQuark = ustDebugSsb.getQuarkAbsolute(binFilePath, UstDebugInfoStateProvider.FUNCTION_NAME);
ITmfStateInterval functionInterval = ustDebugSsb.querySingleState(offset, functionNameQuark);
return functionInterval.getValueString();
} catch (AttributeNotFoundException e) {
/*
* It's fine, sometimes a function could not be found with
* the info stored in the SS using nm. Try to resolve the
* symbol in another way.
*/
} catch (StateSystemDisposedException e) {
/*
* This can happen if user closes a trace during the
* analysis execution. Continue with what we have.
*/
}
}
}
return null;
}
}
Expand Up @@ -17,6 +17,7 @@

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo.FileOffsetMapper;
import org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo.UstDebugInfoSymbolProvider;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;

Expand Down Expand Up @@ -53,6 +54,15 @@ public String getHelpText() {
return null;
}

String functionName = UstDebugInfoSymbolProvider.getFunctionNameFromSS(bc, event.getTrace());
/*
* Return function information only if it is non null, otherwise try to
* resolve the symbol in another way (see code below).
*/
if (functionName != null) {
return new FunctionLocation(functionName, null);
}

return getFunctionFromBinaryLocation(bc);
}

Expand Down
Expand Up @@ -14,13 +14,20 @@
import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;

import java.io.File;
import java.util.Iterator;

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo.FileOffsetMapper;
import org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo.UstDebugInfoStateProvider;
import org.eclipse.tracecompass.lttng2.ust.core.trace.LttngUstTrace;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;

/**
* Event aspect of UST traces to generate a {@link TmfCallsite} using the debug
Expand Down Expand Up @@ -68,6 +75,44 @@ public String getHelpText() {
return null;
}

Iterator<UstDebugInfoAnalysisModule> ustDebugModules = TmfTraceUtils.getAnalysisModulesOfClass(event.getTrace(), UstDebugInfoAnalysisModule.class).iterator();
if (ustDebugModules.hasNext()) {
UstDebugInfoAnalysisModule ustDebugModule = ustDebugModules.next();
ITmfStateSystem ustDebugSsq = ustDebugModule.getStateSystem();
if (ustDebugSsq != null) {
String binFilePath = bc.getBinaryFilePath();
long offset = bc.getOffset() + ustDebugSsq.getStartTime();
try {
int srcFileNameQuark = ustDebugSsq.getQuarkAbsolute(binFilePath, UstDebugInfoStateProvider.SOURCE_FILE_NAME);
int lineNrQuark = ustDebugSsq.getQuarkAbsolute(binFilePath, UstDebugInfoStateProvider.LINE_NUMBER);
ITmfStateInterval srcFileInterval = ustDebugSsq.querySingleState(offset, srcFileNameQuark);
ITmfStateInterval lineNrInterval = ustDebugSsq.querySingleState(offset, lineNrQuark);
String lineNrValue = lineNrInterval.getValueString();
long lineNr = Long.parseLong(lineNrValue);
String srcFileName = srcFileInterval.getValueString();
/*
* Return function information only if it is non null,
* otherwise try to resolve the symbol in another way (see
* code below).
*/
if (srcFileName != null) {
return new TmfCallsite(srcFileName, lineNr);
}
} catch (AttributeNotFoundException e) {
/*
* It's fine, sometimes a function could not be found with
* nm. Try to resolve the symbol in another way (see code
* below).
*/
} catch (StateSystemDisposedException e) {
/*
* This can happen if user closes a trace during the
* analysis execution. Continue with what we have.
*/
}
}
}

TmfCallsite callsite = FileOffsetMapper.getCallsiteFromOffset(
new File(bc.getBinaryFilePath()),
bc.getBuildId(),
Expand Down

0 comments on commit 6463d29

Please sign in to comment.