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