1 /*******************************************************************************
2 * Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir
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 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.lttng2
.ust
.core
.analysis
.debuginfo
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
15 import java
.nio
.file
.Files
;
16 import java
.util
.Arrays
;
17 import java
.util
.Collections
;
18 import java
.util
.LinkedList
;
19 import java
.util
.List
;
20 import java
.util
.logging
.Logger
;
22 import org
.eclipse
.jdt
.annotation
.NonNull
;
23 import org
.eclipse
.jdt
.annotation
.Nullable
;
24 import org
.eclipse
.tracecompass
.common
.core
.log
.TraceCompassLog
;
25 import org
.eclipse
.tracecompass
.common
.core
.process
.ProcessUtils
;
26 import org
.eclipse
.tracecompass
.tmf
.core
.event
.lookup
.TmfCallsite
;
28 import com
.google
.common
.base
.Objects
;
29 import com
.google
.common
.cache
.CacheBuilder
;
30 import com
.google
.common
.cache
.CacheLoader
;
31 import com
.google
.common
.cache
.LoadingCache
;
32 import com
.google
.common
.collect
.Iterables
;
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}.
38 * @author Alexandre Montplaisir
40 public final class FileOffsetMapper
{
42 private static final Logger LOGGER
= TraceCompassLog
.getLogger(FileOffsetMapper
.class);
44 private static final String DISCRIMINATOR
= "\\(discriminator.*\\)"; //$NON-NLS-1$
45 private static final String ADDR2LINE_EXECUTABLE
= "addr2line"; //$NON-NLS-1$
47 private static final long CACHE_SIZE
= 1000;
49 private FileOffsetMapper() {}
52 * Class representing an offset in a specific file
54 private static class FileOffset
{
56 private final String fFilePath
;
57 private final @Nullable String fBuildId
;
58 private final long fOffset
;
60 public FileOffset(String filePath
, @Nullable String buildId
, long offset
) {
67 public int hashCode() {
68 return Objects
.hashCode(fFilePath
, fBuildId
, fOffset
);
72 public boolean equals(@Nullable Object obj
) {
79 if (getClass() != obj
.getClass()) {
82 FileOffset other
= (FileOffset
) obj
;
83 return Objects
.equal(fFilePath
, other
.fFilePath
) &&
84 Objects
.equal(fBuildId
, other
.fBuildId
) &&
85 Objects
.equal(fOffset
, other
.fOffset
);
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$
100 * Generate the callsite from a given binary file and address offset.
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
107 * The binary file to look at
109 * The expected buildId of the binary file (is not verified at
112 * The memory offset in the file
113 * @return The corresponding call site
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
)) {
120 Addr2lineInfo info
= Iterables
.getLast(output
);
121 String sourceFile
= info
.fSourceFileName
;
122 Long sourceLine
= info
.fSourceLineNumber
;
124 if (sourceFile
== null) {
125 /* Not enough information to provide a callsite */
128 return new TmfCallsite(sourceFile
, sourceLine
);
132 * Get the function/symbol name corresponding to binary file and offset.
135 * The binary file to look at
137 * The expected buildId of the binary file (is not verified at
140 * The memory offset in the file
141 * @return The corresponding function/symbol name
143 public static @Nullable String
getFunctionNameFromOffset(File file
, @Nullable String buildId
, long offset
) {
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
151 Iterable
<Addr2lineInfo
> output
= getAddr2lineInfo(file
, buildId
, offset
);
152 if (output
== null || Iterables
.isEmpty(output
)) {
155 Addr2lineInfo info
= Iterables
.getLast(output
);
156 return info
.fFunctionName
;
159 // ------------------------------------------------------------------------
160 // Utility methods making use of 'addr2line'
161 // ------------------------------------------------------------------------
164 * Value used in addr2line output to represent unknown function names or
167 private static final String UNKNOWN_VALUE
= "??"; //$NON-NLS-1$
170 * Cache of all calls to 'addr2line', so that we can avoid recalling the
171 * external process repeatedly.
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.
176 private static final LoadingCache
<FileOffset
, @NonNull Iterable
<Addr2lineInfo
>> ADDR2LINE_INFO_CACHE
;
178 ADDR2LINE_INFO_CACHE
= checkNotNull(CacheBuilder
.newBuilder()
179 .maximumSize(CACHE_SIZE
)
180 .build(new CacheLoader
<FileOffset
, @NonNull Iterable
<Addr2lineInfo
>>() {
182 public @NonNull Iterable
<Addr2lineInfo
> load(FileOffset fo
) {
183 LOGGER
.fine(() -> "[FileOffsetMapper:CacheMiss] file/offset=" + fo
.toString()); //$NON-NLS-1$
184 return callAddr2line(fo
);
189 private static class Addr2lineInfo
{
191 private final @Nullable String fSourceFileName
;
192 private final @Nullable Long fSourceLineNumber
;
193 private final @Nullable String fFunctionName
;
195 public Addr2lineInfo(@Nullable String sourceFileName
, @Nullable String functionName
, @Nullable Long sourceLineNumber
) {
196 fSourceFileName
= sourceFileName
;
197 fSourceLineNumber
= sourceLineNumber
;
198 fFunctionName
= functionName
;
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$
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$
213 file
.toString(), buildId
, offset
));
215 if (!Files
.exists((file
.toPath()))) {
216 LOGGER
.finer(() -> "[FileOffsetMapper:RequestFailed] File not found"); //$NON-NLS-1$
219 // TODO We should also eventually verify that the passed buildId matches
220 // the file we are attempting to open.
221 FileOffset fo
= new FileOffset(checkNotNull(file
.toString()), buildId
, offset
);
223 @Nullable Iterable
<Addr2lineInfo
> callsites
= ADDR2LINE_INFO_CACHE
.getUnchecked(fo
);
224 LOGGER
.finer(() -> String
.format("[FileOffsetMapper:RequestComplete] callsites=%s", callsites
)); //$NON-NLS-1$
228 private static Iterable
<Addr2lineInfo
> callAddr2line(FileOffset fo
) {
229 String filePath
= fo
.fFilePath
;
230 long offset
= fo
.fOffset
;
232 List
<Addr2lineInfo
> callsites
= new LinkedList
<>();
234 // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
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
);
238 if (output
== null) {
239 /* Command returned an error */
240 return Collections
.EMPTY_SET
;
244 * When passing the -f flag, the output alternates between function
245 * names and file/line location.
247 boolean oddLine
= false; // We flip at the start, first loop will be odd
248 String currentFunctionName
= null;
249 for (String outputLine
: output
) {
250 /* Flip the boolean for the following line */
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$
257 /* This is a line indicating the function name */
258 if (outputLine
.equals(UNKNOWN_VALUE
)) {
259 currentFunctionName
= null;
261 currentFunctionName
= outputLine
;
264 /* This is a line indicating a call site */
265 String
[] elems
= outputLine
.split(":"); //$NON-NLS-1$
266 String fileName
= elems
[0];
267 if (fileName
.equals(UNKNOWN_VALUE
)) {
272 lineNumber
= Long
.valueOf(elems
[1]);
273 } catch (NumberFormatException e
) {
274 /* Probably a '?' output, meaning unknown line number. */
277 callsites
.add(new Addr2lineInfo(fileName
, currentFunctionName
, lineNumber
));