lttng.ust: Add logging to FileOffsetMapper
[deliverable/tracecompass.git] / lttng / org.eclipse.tracecompass.lttng2.ust.core / src / org / eclipse / tracecompass / internal / lttng2 / ust / core / analysis / debuginfo / FileOffsetMapper.java
CommitLineData
522dff53
AM
1/*******************************************************************************
2 * Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
9
10package org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo;
11
4d60469d
AM
12import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13
522dff53
AM
14import java.io.BufferedReader;
15import java.io.File;
16import java.io.IOException;
17import java.io.InputStreamReader;
18import java.nio.file.Files;
19import java.util.Arrays;
20import java.util.LinkedList;
21import java.util.List;
ba50b376 22import java.util.logging.Logger;
522dff53
AM
23import java.util.stream.Collectors;
24
25import org.eclipse.jdt.annotation.Nullable;
ba50b376 26import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
3335f36e 27import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.SourceCallsite;
522dff53
AM
28import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
29
4d60469d
AM
30import com.google.common.base.Objects;
31import com.google.common.cache.CacheBuilder;
32import com.google.common.cache.CacheLoader;
33import com.google.common.cache.LoadingCache;
34
522dff53
AM
35/**
36 * Utility class to get file name, function/symbol name and line number from a
37 * given offset. In TMF this is represented as a {@link TmfCallsite}.
38 *
39 * @author Alexandre Montplaisir
40 */
41public final class FileOffsetMapper {
42
ba50b376
AM
43 private static final Logger LOGGER = TraceCompassLog.getLogger(FileOffsetMapper.class);
44
23a8deea 45 private static final String DISCRIMINATOR = "\\(discriminator.*\\)"; //$NON-NLS-1$
522dff53
AM
46 private static final String ADDR2LINE_EXECUTABLE = "addr2line"; //$NON-NLS-1$
47
4d60469d
AM
48 private static final long CACHE_SIZE = 1000;
49
522dff53
AM
50 private FileOffsetMapper() {}
51
4d60469d
AM
52 /**
53 * Class representing an offset in a specific file
54 */
55 private static class FileOffset {
56
57 private final String fFilePath;
1633ee0d 58 private final @Nullable String fBuildId;
4d60469d
AM
59 private final long fOffset;
60
1633ee0d 61 public FileOffset(String filePath, @Nullable String buildId, long offset) {
4d60469d 62 fFilePath = filePath;
c84cc3cc 63 fBuildId = buildId;
4d60469d
AM
64 fOffset = offset;
65 }
66
67 @Override
68 public int hashCode() {
c84cc3cc 69 return Objects.hashCode(fFilePath, fBuildId, fOffset);
4d60469d
AM
70 }
71
72 @Override
73 public boolean equals(@Nullable Object obj) {
74 if (this == obj) {
75 return true;
76 }
77 if (obj == null) {
78 return false;
79 }
80 if (getClass() != obj.getClass()) {
81 return false;
82 }
83 FileOffset other = (FileOffset) obj;
c84cc3cc
AM
84 return Objects.equal(fFilePath, other.fFilePath) &&
85 Objects.equal(fBuildId, other.fBuildId) &&
86 Objects.equal(fOffset, other.fOffset);
4d60469d 87 }
ba50b376
AM
88
89 @Override
90 public String toString() {
91 return Objects.toStringHelper(this)
92 .add("fFilePath", fFilePath) //$NON-NLS-1$
93 .add("fBuildId", fBuildId) //$NON-NLS-1$
94 .add("fOffset", String.format("0x%h", fOffset)) //$NON-NLS-1$ //$NON-NLS-2$
95 .toString();
96 }
4d60469d
AM
97 }
98
99 /**
100 * Cache of all calls to 'addr2line', so that we can avoid recalling the
101 * external process repeatedly.
102 *
103 * It is static, meaning one cache for the whole application, since the
104 * symbols in a file on disk are independent from the trace referring to it.
105 */
3335f36e 106 private static final LoadingCache<FileOffset, @Nullable Iterable<SourceCallsite>> CALLSITE_CACHE;
4d60469d
AM
107 static {
108 CALLSITE_CACHE = checkNotNull(CacheBuilder.newBuilder()
109 .maximumSize(CACHE_SIZE)
3335f36e 110 .build(new CacheLoader<FileOffset, @Nullable Iterable<SourceCallsite>>() {
4d60469d 111 @Override
3335f36e 112 public @Nullable Iterable<SourceCallsite> load(FileOffset fo) {
ba50b376 113 LOGGER.fine(() -> "[FileOffsetMapper:CacheMiss] file/offset=" + fo.toString()); //$NON-NLS-1$
4d60469d
AM
114 return getCallsiteFromOffsetWithAddr2line(fo);
115 }
116 }));
117 }
118
522dff53
AM
119 /**
120 * Generate the callsites from a given binary file and address offset.
121 *
122 * Due to function inlining, it is possible for one offset to actually have
123 * multiple call sites. This is why we can return more than one callsite per
124 * call.
125 *
126 * @param file
127 * The binary file to look at
c84cc3cc
AM
128 * @param buildId
129 * The expected buildId of the binary file (is not verified at
130 * the moment)
522dff53
AM
131 * @param offset
132 * The memory offset in the file
133 * @return The list of callsites corresponding to the offset, reported from
134 * the "highest" inlining location, down to the initial definition.
135 */
1633ee0d 136 public static @Nullable Iterable<SourceCallsite> getCallsiteFromOffset(File file, @Nullable String buildId, long offset) {
ba50b376
AM
137 LOGGER.finer(() -> String.format("[FileOffsetMapper:Request] file=%s, buildId=%s, offset=0x%h", //$NON-NLS-1$
138 file.toString(), buildId, offset));
139
522dff53 140 if (!Files.exists((file.toPath()))) {
ba50b376 141 LOGGER.finer(() -> "[FileOffsetMapper:RequestFailed] File not found"); //$NON-NLS-1$
11f39f99 142 return null;
522dff53 143 }
c84cc3cc
AM
144 // TODO We should also eventually verify that the passed buildId matches
145 // the file we are attempting to open.
c84cc3cc 146 FileOffset fo = new FileOffset(checkNotNull(file.toString()), buildId, offset);
ba50b376
AM
147
148 Iterable<SourceCallsite> callsites = CALLSITE_CACHE.getUnchecked(fo);
149 LOGGER.finer(() -> String.format("[FileOffsetMapper:RequestComplete] callsites=%s", callsites)); //$NON-NLS-1$
150 return callsites;
522dff53
AM
151 }
152
3335f36e 153 private static @Nullable Iterable<SourceCallsite> getCallsiteFromOffsetWithAddr2line(FileOffset fo) {
4d60469d
AM
154 String filePath = fo.fFilePath;
155 long offset = fo.fOffset;
156
3335f36e 157 List<SourceCallsite> callsites = new LinkedList<>();
522dff53 158
38c5f989 159 // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
0e4f957e 160 List<String> output = getOutputFromCommand(Arrays.asList(
4d60469d 161 ADDR2LINE_EXECUTABLE, "-i", "-f", "-C", "-e", filePath, "0x" + Long.toHexString(offset))); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
522dff53
AM
162
163 if (output == null) {
164 /* Command returned an error */
165 return null;
166 }
167
38c5f989
AM
168 /*
169 * When passing the -f flag, the output alternates between function
170 * names and file/line location.
171 */
1da28b13 172 boolean oddLine = false; // We flip at the start, first loop will be odd
38c5f989 173 String currentFunctionName = null;
522dff53 174 for (String outputLine : output) {
1da28b13
AM
175 /* Flip the boolean for the following line */
176 oddLine = !oddLine;
177
91fdda3e
MAL
178 // Remove discriminator part, for example: /build/buildd/glibc-2.21/elf/dl-object.c:78 (discriminator 8)
179 outputLine = outputLine.replaceFirst(DISCRIMINATOR, "").trim(); //$NON-NLS-1$
180
38c5f989
AM
181 if (oddLine) {
182 /* This is a line indicating the function name */
183 currentFunctionName = outputLine;
184 } else {
185 /* This is a line indicating a call site */
186 String[] elems = outputLine.split(":"); //$NON-NLS-1$
187 String fileName = elems[0];
188 if (fileName.equals("??")) { //$NON-NLS-1$
189 continue;
190 }
1da28b13
AM
191 try {
192 long lineNumber = Long.parseLong(elems[1]);
193 callsites.add(new SourceCallsite(fileName, currentFunctionName, lineNumber));
194
195 } catch (NumberFormatException e) {
196 /*
197 * Probably a '?' output, meaning unknown line number.
198 * Ignore this entry.
199 */
200 continue;
201 }
522dff53 202 }
522dff53
AM
203 }
204
205 return callsites;
206 }
207
208 private static @Nullable List<String> getOutputFromCommand(List<String> command) {
209 try {
210 ProcessBuilder builder = new ProcessBuilder(command);
211 builder.redirectErrorStream(true);
212
213 Process p = builder.start();
a6c5c267
MK
214 try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
215 int ret = p.waitFor();
216 List<String> lines = br.lines().collect(Collectors.toList());
522dff53 217
a6c5c267
MK
218 return (ret == 0 ? lines : null);
219 }
522dff53
AM
220 } catch (IOException | InterruptedException e) {
221 return null;
222 }
223 }
224}
This page took 0.045621 seconds and 5 git commands to generate.