| /******************************************************************************* |
| * 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; |
| } |
| } |