lttng.ust: Do not skip unknown lines in addr2line output
[deliverable/tracecompass.git] / lttng / org.eclipse.tracecompass.lttng2.ust.core / src / org / eclipse / tracecompass / internal / lttng2 / ust / core / analysis / debuginfo / FileOffsetMapper.java
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
10 package org.eclipse.tracecompass.internal.lttng2.ust.core.analysis.debuginfo;
11
12 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13
14 import java.io.BufferedReader;
15 import java.io.File;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.nio.file.Files;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.logging.Logger;
24 import java.util.stream.Collectors;
25
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
29 import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
30
31 import com.google.common.base.Objects;
32 import com.google.common.cache.CacheBuilder;
33 import com.google.common.cache.CacheLoader;
34 import com.google.common.cache.LoadingCache;
35 import com.google.common.collect.Iterables;
36
37 /**
38 * Utility class to get file name, function/symbol name and line number from a
39 * given offset. In TMF this is represented as a {@link TmfCallsite}.
40 *
41 * @author Alexandre Montplaisir
42 */
43 public final class FileOffsetMapper {
44
45 private static final Logger LOGGER = TraceCompassLog.getLogger(FileOffsetMapper.class);
46
47 private static final String DISCRIMINATOR = "\\(discriminator.*\\)"; //$NON-NLS-1$
48 private static final String ADDR2LINE_EXECUTABLE = "addr2line"; //$NON-NLS-1$
49
50 private static final long CACHE_SIZE = 1000;
51
52 private FileOffsetMapper() {}
53
54 /**
55 * Class representing an offset in a specific file
56 */
57 private static class FileOffset {
58
59 private final String fFilePath;
60 private final @Nullable String fBuildId;
61 private final long fOffset;
62
63 public FileOffset(String filePath, @Nullable String buildId, long offset) {
64 fFilePath = filePath;
65 fBuildId = buildId;
66 fOffset = offset;
67 }
68
69 @Override
70 public int hashCode() {
71 return Objects.hashCode(fFilePath, fBuildId, fOffset);
72 }
73
74 @Override
75 public boolean equals(@Nullable Object obj) {
76 if (this == obj) {
77 return true;
78 }
79 if (obj == null) {
80 return false;
81 }
82 if (getClass() != obj.getClass()) {
83 return false;
84 }
85 FileOffset other = (FileOffset) obj;
86 return Objects.equal(fFilePath, other.fFilePath) &&
87 Objects.equal(fBuildId, other.fBuildId) &&
88 Objects.equal(fOffset, other.fOffset);
89 }
90
91 @Override
92 public String toString() {
93 return Objects.toStringHelper(this)
94 .add("fFilePath", fFilePath) //$NON-NLS-1$
95 .add("fBuildId", fBuildId) //$NON-NLS-1$
96 .add("fOffset", String.format("0x%h", fOffset)) //$NON-NLS-1$ //$NON-NLS-2$
97 .toString();
98 }
99 }
100
101
102 /**
103 * Generate the callsite from a given binary file and address offset.
104 *
105 * Due to function inlining, it is possible for one offset to actually have
106 * multiple call sites. We will return the most precise one (inner-most) we
107 * have available.
108 *
109 * @param file
110 * The binary file to look at
111 * @param buildId
112 * The expected buildId of the binary file (is not verified at
113 * the moment)
114 * @param offset
115 * The memory offset in the file
116 * @return The corresponding call site
117 */
118 public static @Nullable TmfCallsite getCallsiteFromOffset(File file, @Nullable String buildId, long offset) {
119 Iterable<Addr2lineInfo> output = getAddr2lineInfo(file, buildId, offset);
120 if (output == null || Iterables.isEmpty(output)) {
121 return null;
122 }
123 Addr2lineInfo info = Iterables.getLast(output);
124 String sourceFile = info.fSourceFileName;
125 Long sourceLine = info.fSourceLineNumber;
126
127 if (sourceFile == null) {
128 /* Not enough information to provide a callsite */
129 return null;
130 }
131 return new TmfCallsite(sourceFile, sourceLine);
132 }
133
134 /**
135 * Get the function/symbol name corresponding to binary file and offset.
136 *
137 * @param file
138 * The binary file to look at
139 * @param buildId
140 * The expected buildId of the binary file (is not verified at
141 * the moment)
142 * @param offset
143 * The memory offset in the file
144 * @return The corresponding function/symbol name
145 */
146 public static @Nullable String getFunctionNameFromOffset(File file, @Nullable String buildId, long offset) {
147 /*
148 * TODO We are currently also using 'addr2line' to resolve function
149 * names, which requires the binary's DWARF information to be available.
150 * A better approach would be to use the binary's symbol table (if it is
151 * not stripped), since this is usually more readily available than
152 * DWARF.
153 */
154 Iterable<Addr2lineInfo> output = getAddr2lineInfo(file, buildId, offset);
155 if (output == null || Iterables.isEmpty(output)) {
156 return null;
157 }
158 Addr2lineInfo info = Iterables.getLast(output);
159 return info.fFunctionName;
160 }
161
162 // ------------------------------------------------------------------------
163 // Utility methods making use of 'addr2line'
164 // ------------------------------------------------------------------------
165
166 /**
167 * Value used in addr2line output to represent unknown function names or
168 * source files.
169 */
170 private static final String UNKNOWN_VALUE = "??"; //$NON-NLS-1$
171
172 /**
173 * Cache of all calls to 'addr2line', so that we can avoid recalling the
174 * external process repeatedly.
175 *
176 * It is static, meaning one cache for the whole application, since the
177 * symbols in a file on disk are independent from the trace referring to it.
178 */
179 private static final LoadingCache<FileOffset, @NonNull Iterable<Addr2lineInfo>> ADDR2LINE_INFO_CACHE;
180 static {
181 ADDR2LINE_INFO_CACHE = checkNotNull(CacheBuilder.newBuilder()
182 .maximumSize(CACHE_SIZE)
183 .build(new CacheLoader<FileOffset, @NonNull Iterable<Addr2lineInfo>>() {
184 @Override
185 public @NonNull Iterable<Addr2lineInfo> load(FileOffset fo) {
186 LOGGER.fine(() -> "[FileOffsetMapper:CacheMiss] file/offset=" + fo.toString()); //$NON-NLS-1$
187 return callAddr2line(fo);
188 }
189 }));
190 }
191
192 private static class Addr2lineInfo {
193
194 private final @Nullable String fSourceFileName;
195 private final @Nullable Long fSourceLineNumber;
196 private final @Nullable String fFunctionName;
197
198 public Addr2lineInfo(@Nullable String sourceFileName, @Nullable String functionName, @Nullable Long sourceLineNumber) {
199 fSourceFileName = sourceFileName;
200 fSourceLineNumber = sourceLineNumber;
201 fFunctionName = functionName;
202 }
203
204 @Override
205 public String toString() {
206 return Objects.toStringHelper(this)
207 .add("fSourceFileName", fSourceFileName) //$NON-NLS-1$
208 .add("fSourceLineNumber", fSourceLineNumber) //$NON-NLS-1$
209 .add("fFunctionName", fFunctionName) //$NON-NLS-1$
210 .toString();
211 }
212 }
213
214 private static @Nullable Iterable<Addr2lineInfo> getAddr2lineInfo(File file, @Nullable String buildId, long offset) {
215 LOGGER.finer(() -> String.format("[FileOffsetMapper:Addr2lineRequest] file=%s, buildId=%s, offset=0x%h", //$NON-NLS-1$
216 file.toString(), buildId, offset));
217
218 if (!Files.exists((file.toPath()))) {
219 LOGGER.finer(() -> "[FileOffsetMapper:RequestFailed] File not found"); //$NON-NLS-1$
220 return null;
221 }
222 // TODO We should also eventually verify that the passed buildId matches
223 // the file we are attempting to open.
224 FileOffset fo = new FileOffset(checkNotNull(file.toString()), buildId, offset);
225
226 @Nullable Iterable<Addr2lineInfo> callsites = ADDR2LINE_INFO_CACHE.getUnchecked(fo);
227 LOGGER.finer(() -> String.format("[FileOffsetMapper:RequestComplete] callsites=%s", callsites)); //$NON-NLS-1$
228 return callsites;
229 }
230
231 private static Iterable<Addr2lineInfo> callAddr2line(FileOffset fo) {
232 String filePath = fo.fFilePath;
233 long offset = fo.fOffset;
234
235 List<Addr2lineInfo> callsites = new LinkedList<>();
236
237 // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
238 List<String> output = getOutputFromCommand(Arrays.asList(
239 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$
240
241 if (output == null) {
242 /* Command returned an error */
243 return Collections.EMPTY_SET;
244 }
245
246 /*
247 * When passing the -f flag, the output alternates between function
248 * names and file/line location.
249 */
250 boolean oddLine = false; // We flip at the start, first loop will be odd
251 String currentFunctionName = null;
252 for (String outputLine : output) {
253 /* Flip the boolean for the following line */
254 oddLine = !oddLine;
255
256 // Remove discriminator part, for example: /build/buildd/glibc-2.21/elf/dl-object.c:78 (discriminator 8)
257 outputLine = outputLine.replaceFirst(DISCRIMINATOR, "").trim(); //$NON-NLS-1$
258
259 if (oddLine) {
260 /* This is a line indicating the function name */
261 if (outputLine.equals(UNKNOWN_VALUE)) {
262 currentFunctionName = null;
263 } else {
264 currentFunctionName = outputLine;
265 }
266 } else {
267 /* This is a line indicating a call site */
268 String[] elems = outputLine.split(":"); //$NON-NLS-1$
269 String fileName = elems[0];
270 if (fileName.equals(UNKNOWN_VALUE)) {
271 fileName = null;
272 }
273 Long lineNumber;
274 try {
275 lineNumber = Long.valueOf(elems[1]);
276 } catch (NumberFormatException e) {
277 /* Probably a '?' output, meaning unknown line number. */
278 lineNumber = null;
279 }
280 callsites.add(new Addr2lineInfo(fileName, currentFunctionName, lineNumber));
281 }
282 }
283
284 return callsites;
285 }
286
287 private static @Nullable List<String> getOutputFromCommand(List<String> command) {
288 try {
289 ProcessBuilder builder = new ProcessBuilder(command);
290 builder.redirectErrorStream(true);
291
292 Process p = builder.start();
293 try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
294 int ret = p.waitFor();
295 List<String> lines = br.lines().collect(Collectors.toList());
296
297 return (ret == 0 ? lines : null);
298 }
299 } catch (IOException | InterruptedException e) {
300 return null;
301 }
302 }
303 }
This page took 0.041122 seconds and 5 git commands to generate.