blob: 4febfa35543d5ea464109f03be67e12f25971f1a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
*
* 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
*******************************************************************************/
package org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
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.StateSystemUtils;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.ctf.core.trace.CtfUtils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
/**
* Analysis to provide TMF Callsite information by mapping IP (instruction
* pointer) contexts to address/line numbers via debug information.
*
* @author Alexandre Montplaisir
* @since 2.0
*/
public class UstDebugInfoAnalysisModule extends TmfStateSystemAnalysisModule {
/**
* Analysis ID, it should match that in the plugin.xml file
*/
public static final String ID = "org.eclipse.linuxtools.lttng2.ust.analysis.debuginfo"; //$NON-NLS-1$
@Override
protected ITmfStateProvider createStateProvider() {
return new UstDebugInfoStateProvider(checkNotNull(getTrace()));
}
@Override
public boolean setTrace(ITmfTrace trace) throws TmfAnalysisException {
if (!(trace instanceof LttngUstTrace)) {
return false;
}
return super.setTrace(trace);
}
/**
* @since 3.0
*/
@Override
@Nullable
public LttngUstTrace getTrace() {
return (LttngUstTrace) super.getTrace();
}
@Override
public Iterable<TmfAbstractAnalysisRequirement> getAnalysisRequirements() {
// TODO specify actual requirements once the requirement-checking is
// implemented. This analysis needs "ip" and "vpid" contexts.
return Collections.emptySet();
}
@Override
public boolean canExecute(ITmfTrace trace) {
/* The analysis can only work with LTTng-UST traces... */
if (!(trace instanceof LttngUstTrace)) {
return false;
}
LttngUstTrace ustTrace = (LttngUstTrace) trace;
String tracerName = CtfUtils.getTracerName(ustTrace);
int majorVersion = CtfUtils.getTracerMajorVersion(ustTrace);
int minorVersion = CtfUtils.getTracerMinorVersion(ustTrace);
/* ... taken with UST >= 2.8 ... */
if (!LttngUstTrace.TRACER_NAME.equals(tracerName)) {
return false;
}
if (majorVersion < 2) {
return false;
}
if (majorVersion == 2 && minorVersion < 8) {
return false;
}
/* ... that respect the ip/vpid contexts requirements. */
return super.canExecute(trace);
}
// ------------------------------------------------------------------------
// Class-specific operations
// ------------------------------------------------------------------------
/**
* Return all the binaries that were detected in the trace.
*
* @return The binaries (executables or libraries) referred to in the trace.
*/
public Collection<UstDebugInfoBinaryFile> getAllBinaries() {
ITmfStateSystem ss = getStateSystem();
if (ss == null) {
/* State system might not yet be initialized */
return Collections.emptySet();
}
final @NonNull Set<UstDebugInfoBinaryFile> files = new TreeSet<>();
ImmutableList.Builder<Integer> builder = ImmutableList.builder();
List<Integer> vpidQuarks = ss.getSubAttributes(ITmfStateSystem.ROOT_ATTRIBUTE, false);
for (Integer vpidQuark : vpidQuarks) {
builder.addAll(ss.getSubAttributes(vpidQuark, false));
}
List<Integer> baddrQuarks = builder.build();
try {
for (Integer baddrQuark : baddrQuarks) {
int buildIdQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.BUILD_ID_ATTRIB);
int debugLinkQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.DEBUG_LINK_ATTRIB);
int pathQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.PATH_ATTRIB);
int isPICQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.IS_PIC_ATTRIB);
long ts = ss.getStartTime();
/*
* Iterate over each mapping there ever was at this base
* address.
*/
ITmfStateInterval interval = StateSystemUtils.queryUntilNonNullValue(ss, baddrQuark, ts, Long.MAX_VALUE);
while (interval != null) {
ts = interval.getStartTime();
ITmfStateValue filePathStateValue = ss.querySingleState(ts, pathQuark).getStateValue();
String filePath = filePathStateValue.unboxStr();
ITmfStateValue buildIdStateValue = ss.querySingleState(ts, buildIdQuark).getStateValue();
String buildId = unboxStrOrNull(buildIdStateValue);
ITmfStateValue debuglinkStateValue = ss.querySingleState(ts, debugLinkQuark).getStateValue();
String debugLink = unboxStrOrNull(debuglinkStateValue);
ITmfStateValue isPICStateValue = ss.querySingleState(ts, isPICQuark).getStateValue();
Boolean isPIC = isPICStateValue.unboxInt() != 0;
files.add(new UstDebugInfoBinaryFile(filePath, buildId, debugLink, isPIC));
/*
* Go one past the end of the interval, and perform the
* query again to find the next mapping at this address.
*/
ts = interval.getEndTime() + 1;
interval = StateSystemUtils.queryUntilNonNullValue(ss, baddrQuark, ts, Long.MAX_VALUE);
}
}
} catch (AttributeNotFoundException | TimeRangeException | StateSystemDisposedException e) {
/* Oh well, such is life. */
}
return files;
}
/**
* Get the binary file (executable or library) that corresponds to a given
* instruction pointer, at a given time.
*
* @param ts
* The timestamp
* @param vpid
* The VPID of the process we are querying for
* @param ip
* The instruction pointer of the trace event. Normally comes
* from a 'ip' context.
* @return A {@link UstDebugInfoLoadedBinaryFile} object, describing the
* binary file and its base address.
* @noreference Meant to be used internally by
* {@link UstDebugInfoBinaryAspect} only.
*/
@VisibleForTesting
public @Nullable UstDebugInfoLoadedBinaryFile getMatchingFile(long ts, long vpid, long ip) {
try {
final ITmfStateSystem ss = getStateSystem();
if (ss == null) {
/* State system might not yet be initialized */
return null;
}
List<Integer> possibleBaddrQuarks = ss.getQuarks(String.valueOf(vpid), "*"); //$NON-NLS-1$
List<ITmfStateInterval> state = ss.queryFullState(ts);
/* Get the most probable base address from all the known ones */
OptionalLong potentialBaddr = possibleBaddrQuarks.stream()
/* Keep only currently (at ts) mapped objects. */
.filter(quark -> Objects.equals(1, state.get(quark).getValue()))
.map(ss::getAttributeName)
.mapToLong(Long::parseLong)
.filter(baddr -> baddr <= ip)
.max();
if (!potentialBaddr.isPresent()) {
return null;
}
long baddr = potentialBaddr.getAsLong();
final int baddrQuark = ss.getQuarkAbsolute(String.valueOf(vpid),
String.valueOf(baddr));
final int memszQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.MEMSZ_ATTRIB);
final long memsz = state.get(memszQuark).getStateValue().unboxLong();
/* Make sure the 'ip' fits the range of this object. */
if (!(ip < baddr + memsz)) {
/*
* Not the correct memory range after all. We do not have
* information about the library that was loaded here.
*/
return null;
}
final int pathQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.PATH_ATTRIB);
String filePath = state.get(pathQuark).getStateValue().unboxStr();
final int buildIdQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.BUILD_ID_ATTRIB);
ITmfStateValue buildIdValue = state.get(buildIdQuark).getStateValue();
String buildId = unboxStrOrNull(buildIdValue);
final int debugLinkQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.DEBUG_LINK_ATTRIB);
ITmfStateValue debugLinkValue = state.get(debugLinkQuark).getStateValue();
String debugLink = unboxStrOrNull(debugLinkValue);
final int isPicQuark = ss.getQuarkRelative(baddrQuark, UstDebugInfoStateProvider.IS_PIC_ATTRIB);
boolean isPic = state.get(isPicQuark).getStateValue().unboxInt() != 0;
// The baddrQuark interval lasts for the time this file is loaded
ITmfStateInterval validityInterval = state.get(baddrQuark);
return new UstDebugInfoLoadedBinaryFile(baddr, filePath, buildId, debugLink, isPic, validityInterval.getStartTime(), validityInterval.getEndTime());
} catch (AttributeNotFoundException | TimeRangeException | StateSystemDisposedException e) {
/* Either the data is not available yet, or incomplete. */
return null;
}
}
private static @Nullable String unboxStrOrNull(ITmfStateValue value) {
return (value.isNull() ? null : value.unboxStr());
}
}