blob: 6bd8a35c4ae411abd6f1da998b51d3de43ddf057 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir
*
* 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
*******************************************************************************/
package org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
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.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.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
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.io.BaseEncoding;
/**
* State provider for the debuginfo analysis. It tracks the layout of shared
* libraries loaded in memory by the application.
*
* The layout of the generated attribute tree will look like this:
*
* <pre>
* Key Value
* /vpid null
* /vpid/<baddr> Integer 1 if the memory mapping active at this
* point in time, null otherwise.
* /vpid/<baddr>/build_id Build ID of the binary as an hex string, e.g.
* "0123456789abcdef", or null if it doesn't have a
* build id.
* /vpid/<baddr>/debug_link Path to the separate debug info of the binary, e.g.
* "/usr/lib/libhello.so.debug", or null if it doesn't
* have separate debug info.
* /vpid/<baddr>/memsz Size of the memory mapping in bytes.
* /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.
* </pre>
*
* The "baddr" attribute name represents the memory mapping base address a
* string (in decimal).
*
* @author Alexandre Montplaisir
* @author Simon Marchi
*/
public class UstDebugInfoStateProvider extends AbstractTmfStateProvider {
/** State system attribute name for the in-memory binary size */
public static final String MEMSZ_ATTRIB = "memsz"; //$NON-NLS-1$
/** State system attribute name for the binary path */
public static final String PATH_ATTRIB = "path"; //$NON-NLS-1$
/** State system attribute name for the PICness of the binary */
public static final String IS_PIC_ATTRIB = "is_pic"; //$NON-NLS-1$
/** State system attribute name for the build ID of the binary */
public static final String BUILD_ID_ATTRIB = "build_id"; //$NON-NLS-1$
/** State system attribute name for the debug link of the binary */
public static final String DEBUG_LINK_ATTRIB = "debug_link"; //$NON-NLS-1$
/** Version of this state provider */
private static final int VERSION = 5;
private static final Logger LOGGER = TraceCompassLog.getLogger(UstDebugInfoStateProvider.class);
private static final int DL_DLOPEN_INDEX = 1;
private static final int DL_BUILD_ID_INDEX = 2;
private static final int DL_DEBUG_LINK_INDEX = 3;
private static final int DL_DLCLOSE_INDEX = 4;
private static final int STATEDUMP_BIN_INFO_INDEX = 5;
private static final int STATEDUMP_BUILD_ID_INDEX = 6;
private static final int STATEDUMP_DEBUG_LINK_INDEX = 7;
private static final int STATEDUMP_START_INDEX = 8;
private final LttngUst28EventLayout fLayout;
private final Map<String, Integer> fEventNames;
/**
* Map of the latest statedump's timestamps, per VPID: Map<vpid, timestamp>
*/
private final Map<Integer, Long> fLatestStatedumpStarts = new HashMap<>();
/*
* Store for data that is incomplete, for which we are waiting for some
* upcoming events (build_id or debug_link). Maps <vpid, baddr> to
* PendingBinInfo object.
*/
private final Map<Pair<Long, Long>, PendingBinInfo> fPendingBinInfos = new HashMap<>();
private class PendingBinInfo {
/* The event data, saved here until we put everything in the state system. */
private final long fVpid;
private final long fBaddr;
private final long fMemsz;
private final String fPath;
private final boolean fIsPIC;
private @Nullable String fBuildId = null;
private @Nullable String fDebugLink = null;
/* Which info are we waiting for? */
private boolean fBuildIdPending;
private boolean fDebugLinkPending;
public PendingBinInfo(boolean hasBuildId, boolean hasDebugLink,
long vpid, long baddr, long memsz, String path, boolean isPIC) {
fVpid = vpid;
fBaddr = baddr;
fMemsz = memsz;
fPath = path;
fIsPIC = isPIC;
fBuildIdPending = hasBuildId;
fDebugLinkPending = hasDebugLink;
}
boolean done() {
return !(fBuildIdPending || fDebugLinkPending);
}
public void setBuildId(String buildId) {
fBuildIdPending = false;
fBuildId = buildId;
}
public @Nullable String getBuildId() {
return fBuildId;
}
public void setDebugLink(String debugLink) {
fDebugLinkPending = false;
fDebugLink = debugLink;
}
public @Nullable String getDebugLink() {
return fDebugLink;
}
}
/**
* Constructor
*
* @param trace
* trace
*/
public UstDebugInfoStateProvider(LttngUstTrace trace) {
super(trace, "Ust:DebugInfo"); //$NON-NLS-1$
ILttngUstEventLayout layout = trace.getEventLayout();
if (!(layout instanceof LttngUst28EventLayout)) {
/* This analysis only support UST 2.8+ traces */
throw new IllegalStateException("Debug info analysis was started with an incompatible trace."); //$NON-NLS-1$
}
fLayout = (LttngUst28EventLayout) layout;
fEventNames = buildEventNames(fLayout);
}
private static Map<String, Integer> buildEventNames(LttngUst28EventLayout layout) {
ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
builder.put(layout.eventDlOpen(), DL_DLOPEN_INDEX);
builder.put(layout.eventDlBuildId(), DL_BUILD_ID_INDEX);
builder.put(layout.eventDlDebugLink(), DL_DEBUG_LINK_INDEX);
builder.put(layout.eventDlClose(), DL_DLCLOSE_INDEX);
builder.put(layout.eventStatedumpBinInfo(), STATEDUMP_BIN_INFO_INDEX);
builder.put(layout.eventStateDumpBuildId(), STATEDUMP_BUILD_ID_INDEX);
builder.put(layout.eventStateDumpDebugLink(), STATEDUMP_DEBUG_LINK_INDEX);
builder.put(layout.eventStatedumpStart(), STATEDUMP_START_INDEX);
return builder.build();
}
@Override
protected void eventHandle(ITmfEvent event) {
// Should we handle this event?
String name = event.getName();
Integer index = fEventNames.get(name);
if (index == null) {
/* Untracked event type */
return;
}
/*
* We require the "vpid" context to build the state system. The rest of
* the analysis also needs the "ip" context, but the state provider part
* does not.
*/
Integer vpid = TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), LinuxPidAspect.class, event);
if (vpid == null) {
return;
}
final @NonNull ITmfStateSystemBuilder ss = checkNotNull(getStateSystemBuilder());
int intIndex = index.intValue();
switch (intIndex) {
case STATEDUMP_START_INDEX: {
handleStatedumpStart(event, vpid, ss);
break;
}
case STATEDUMP_BIN_INFO_INDEX:
handleBinInfo(event, vpid, ss, true);
break;
case DL_DLOPEN_INDEX:
handleBinInfo(event, vpid, ss, false);
break;
case STATEDUMP_BUILD_ID_INDEX:
handleBuildId(event, vpid, ss, true);
break;
case DL_BUILD_ID_INDEX:
handleBuildId(event, vpid, ss, false);
break;
case STATEDUMP_DEBUG_LINK_INDEX:
handleDebugLink(event, vpid, ss, true);
break;
case DL_DEBUG_LINK_INDEX:
handleDebugLink(event, vpid, ss, false);
break;
case DL_DLCLOSE_INDEX: {
handleClose(event, vpid, ss);
break;
}
default:
/* Ignore other events */
break;
}
}
/**
* Commit the binary information contained in pending to the state system.
*
* This method should only be called when there is no more pending
* information for that binary.
*/
private static void commitPendingToStateSystem(PendingBinInfo pending,
long ts, ITmfStateSystemBuilder ss) {
if (!pending.done()) {
throw new IllegalStateException();
}
long vpid = pending.fVpid;
long baddr = pending.fBaddr;
long memsz = pending.fMemsz;
String path = pending.fPath;
String buildId = pending.getBuildId();
String debugLink = pending.getDebugLink();
boolean isPIC = pending.fIsPIC;
/* Create the "top-level" attribute for this object. */
int baddrQuark = ss.getQuarkAbsoluteAndAdd(Long.toString(vpid), Long.toString(baddr));
/* Create the attributes that contain actual data. */
int memszQuark = ss.getQuarkRelativeAndAdd(baddrQuark, MEMSZ_ATTRIB);
int pathQuark = ss.getQuarkRelativeAndAdd(baddrQuark, PATH_ATTRIB);
int isPICQuark = ss.getQuarkRelativeAndAdd(baddrQuark, IS_PIC_ATTRIB);
int buildIdQuark = ss.getQuarkRelativeAndAdd(baddrQuark, BUILD_ID_ATTRIB);
int debugLinkQuark = ss.getQuarkRelativeAndAdd(baddrQuark, DEBUG_LINK_ATTRIB);
try {
ss.modifyAttribute(ts, 1, baddrQuark);
ss.modifyAttribute(ts, memsz, memszQuark);
ss.modifyAttribute(ts, path, pathQuark);
ss.modifyAttribute(ts, isPIC ? 1 : 0, isPICQuark);
if (buildId != null) {
ss.modifyAttribute(ts, buildId, buildIdQuark);
} else {
ss.modifyAttribute(ts, (Object) null, buildIdQuark);
}
if (debugLink != null) {
ss.modifyAttribute(ts, debugLink, debugLinkQuark);
} else {
ss.modifyAttribute(ts, (Object) null, debugLinkQuark);
}
} catch (StateValueTypeException e) {
/* Something went very wrong. */
throw new IllegalStateException(e);
}
}
/**
* Locate a PendingBinInfo object in the map of pending binary informations
* with the key <vpid, baddr>. Remove it from the map and return it if it is
* found, return null otherwise.
*/
private @Nullable PendingBinInfo retrievePendingBinInfo(long vpid, long baddr) {
Pair<Long, Long> key = new Pair<>(vpid, baddr);
return fPendingBinInfos.remove(key);
}
/**
* Check whether we know everything there is to know about the binary
* described by pending, and if so, commit it to the state system.
* Otherwise, put it in the map of pending binary informations.
*/
private void processPendingBinInfo(PendingBinInfo pending, long ts,
ITmfStateSystemBuilder ss) {
if (pending.done()) {
commitPendingToStateSystem(pending, ts, ss);
} else {
/* We are expecting more data for this binary, put in the pending map. */
Pair<Long, Long> key = new Pair<>(pending.fVpid, pending.fBaddr);
fPendingBinInfos.put(key, pending);
}
}
/**
* Handle the start of a statedump.
*
* When a process does an exec, a new statedump is done and all previous
* mappings are now invalid.
*/
private void handleStatedumpStart(ITmfEvent event, final Integer vpid, final ITmfStateSystemBuilder ss) {
long ts = event.getTimestamp().getValue();
fLatestStatedumpStarts.put(vpid, ts);
try {
int vpidQuark = ss.getQuarkAbsolute(vpid.toString());
ss.removeAttribute(ts, vpidQuark);
} catch (AttributeNotFoundException e) {
/* We didn't know anything about this vpid yet, so there is nothing to remove. */
}
}
/**
* Handle a bin_info event, which gives information about a binary or shared
* library loaded in the process's memory space.
*
*
* Uses fields: Long baddr, Long memsz, String path, Long is_pic
*
* @param statedump
* Indicates if it comes from a statedump event, or a
* dlopen/lib:load event.
*/
private void handleBinInfo(ITmfEvent event, final Integer vpid,
final ITmfStateSystemBuilder ss, boolean statedump) {
Long baddr = event.getContent().getFieldValue(Long.class, fLayout.fieldBaddr());
Long memsz = event.getContent().getFieldValue(Long.class, fLayout.fieldMemsz());
String path = event.getContent().getFieldValue(String.class, fLayout.fieldPath());
Long hasBuildIdValue = event.getContent().getFieldValue(Long.class, fLayout.fieldHasBuildId());
Long hasDebugLinkValue = event.getContent().getFieldValue(Long.class, fLayout.fieldHasDebugLink());
Long isPicValue = event.getContent().getFieldValue(Long.class, fLayout.fieldIsPic());
if (baddr == null ||
memsz == null ||
path == null ||
hasBuildIdValue == null ||
hasDebugLinkValue == null ||
(statedump && isPicValue == null)) {
TraceCompassLogUtils.traceInstant(LOGGER, Level.CONFIG, "UstDebugInfoStateProvider:InvalidBinInfoEvent", "event", event); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
/*
* Dlopen/load events do not have an is_pic field, it is taken for
* granted.
*/
boolean isPic = (statedump ? (checkNotNull(isPicValue).longValue() != 0) : true);
long ts = getBinInfoTimeStamp(event, vpid, statedump);
boolean hasBuildId = (hasBuildIdValue != 0);
boolean hasDebugLink = (hasDebugLinkValue != 0);
PendingBinInfo p = new PendingBinInfo(hasBuildId, hasDebugLink, vpid, baddr, memsz, path, isPic);
processPendingBinInfo(p, ts, ss);
}
/**
* Handle shared library build id
*
* Uses fields: Long baddr, long[] build_id
*
* @param statedump
* Indicates if it comes from a statedump event, or a
* dlopen/lib:build_id event.
*/
private void handleBuildId(ITmfEvent event, final Integer vpid,
final ITmfStateSystemBuilder ss, boolean statedump) {
long[] buildIdArray = event.getContent().getFieldValue(long[].class, fLayout.fieldBuildId());
Long baddr = event.getContent().getFieldValue(Long.class, fLayout.fieldBaddr());
if (buildIdArray == null || baddr == null) {
TraceCompassLogUtils.traceInstant(LOGGER, Level.CONFIG, "UstDebugInfoStateProvider:InvalidBinIdEvent", "event", event); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
/*
* Decode the buildID from the byte array in the trace field.
* Use lower-case encoding, since this is how eu-readelf
* displays it.
*/
String buildId = checkNotNull(BaseEncoding.base16().encode(longArrayToByteArray(buildIdArray)).toLowerCase());
long ts = getBinInfoTimeStamp(event, vpid, statedump);
PendingBinInfo p = retrievePendingBinInfo(vpid, baddr);
/*
* We have never seen the bin_info event this event is related to,
* there's nothing much we can do.
*/
if (p == null) {
return;
}
p.setBuildId(buildId);
processPendingBinInfo(p, ts, ss);
}
private void handleDebugLink(ITmfEvent event, final Integer vpid,
final ITmfStateSystemBuilder ss, boolean statedump) {
Long baddr = event.getContent().getFieldValue(Long.class, fLayout.fieldBaddr());
String debugLink = event.getContent().getFieldValue(String.class, fLayout.fieldDebugLinkFilename());
if (baddr == null || debugLink == null) {
TraceCompassLogUtils.traceInstant(LOGGER, Level.CONFIG, "UstDebugInfoStateProvider:InvalidDebugLinkEvent", "event", event); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
long ts = getBinInfoTimeStamp(event, vpid, statedump);
PendingBinInfo pendingBinInfo = retrievePendingBinInfo(vpid, baddr);
if (pendingBinInfo == null) {
return;
}
pendingBinInfo.setDebugLink(debugLink);
processPendingBinInfo(pendingBinInfo, ts, ss);
}
/**
* Handle shared library being closed
*
* Uses fields: Long baddr
*/
private void handleClose(ITmfEvent event, final Integer vpid, final ITmfStateSystemBuilder ss) {
Long baddr = event.getContent().getFieldValue(Long.class, fLayout.fieldBaddr());
if (baddr == null) {
TraceCompassLogUtils.traceInstant(LOGGER, Level.CONFIG, "UstDebugInfoStateProvider:InvalidDlCloseEvent", "event", event); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
try {
int quark = ss.getQuarkAbsolute(vpid.toString(), baddr.toString());
long ts = event.getTimestamp().getValue();
ss.removeAttribute(ts, quark);
} catch (AttributeNotFoundException e) {
/*
* We have never seen a matching dlopen() for this
* dlclose(). Possible that it happened before the start of
* the trace, or that it was lost through lost events.
*/
}
}
/**
* Get the effective timestamp of a bin_info/dlopen/lib:load event.
*
* This is used to consider bin_info events to all virtually happen at the
* statedump start, so that symbol mappings become available as soon as
* possible. The UST statedump happens while a libdl lock is taken, so no
* mapping change can happen while it is ongoing.
*
* dlopen/lib:load events directly use their own timestamp, since they
* happen independently of the statedump.
*
* @param event
* The event, its timestamp will be used if we do not find a
* statedump start
* @param vpid
* The pid of the process
* @param statedump
* True if the event is a statedump (aka bin_info) event, false
* if it is a dlopen/lib:load event.
* @return The timestamp to use
*/
private long getBinInfoTimeStamp(ITmfEvent event, final Integer vpid, boolean statedump) {
if (statedump) {
Long statedumpStartTime = fLatestStatedumpStarts.get(vpid);
if (statedumpStartTime != null) {
return statedumpStartTime;
}
}
return event.getTimestamp().getValue();
}
/**
* FIXME: Until we can use Java 8 IntStream, see
* http://stackoverflow.com/a/28008477/4227853.
*/
private static byte[] longArrayToByteArray(long[] array) {
byte[] ret = new byte[array.length];
for (int i = 0; i < array.length; i++) {
ret[i] = (byte) array[i];
}
return ret;
}
@Override
public ITmfStateProvider getNewInstance() {
return new UstDebugInfoStateProvider(getTrace());
}
@Override
public LttngUstTrace getTrace() {
return (LttngUstTrace) super.getTrace();
}
@Override
public int getVersion() {
return VERSION;
}
}