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
;
14 import java
.io
.BufferedReader
;
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
.LinkedList
;
21 import java
.util
.List
;
22 import java
.util
.stream
.Collectors
;
24 import org
.eclipse
.jdt
.annotation
.Nullable
;
25 import org
.eclipse
.tracecompass
.lttng2
.ust
.core
.analysis
.debuginfo
.SourceCallsite
;
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
;
34 * Utility class to get file name, function/symbol name and line number from a
35 * given offset. In TMF this is represented as a {@link TmfCallsite}.
37 * @author Alexandre Montplaisir
39 public final class FileOffsetMapper
{
41 private static final String DISCRIMINATOR
= "\\(discriminator.*\\)"; //$NON-NLS-1$
42 private static final String ADDR2LINE_EXECUTABLE
= "addr2line"; //$NON-NLS-1$
44 private static final long CACHE_SIZE
= 1000;
46 private FileOffsetMapper() {}
49 * Class representing an offset in a specific file
51 private static class FileOffset
{
53 private final String fFilePath
;
54 private final String fBuildId
;
55 private final long fOffset
;
57 public FileOffset(String filePath
, String buildId
, long offset
) {
64 public int hashCode() {
65 return Objects
.hashCode(fFilePath
, fBuildId
, fOffset
);
69 public boolean equals(@Nullable Object obj
) {
76 if (getClass() != obj
.getClass()) {
79 FileOffset other
= (FileOffset
) obj
;
80 return Objects
.equal(fFilePath
, other
.fFilePath
) &&
81 Objects
.equal(fBuildId
, other
.fBuildId
) &&
82 Objects
.equal(fOffset
, other
.fOffset
);
87 * Cache of all calls to 'addr2line', so that we can avoid recalling the
88 * external process repeatedly.
90 * It is static, meaning one cache for the whole application, since the
91 * symbols in a file on disk are independent from the trace referring to it.
93 private static final LoadingCache
<FileOffset
, @Nullable Iterable
<SourceCallsite
>> CALLSITE_CACHE
;
95 CALLSITE_CACHE
= checkNotNull(CacheBuilder
.newBuilder()
96 .maximumSize(CACHE_SIZE
)
97 .build(new CacheLoader
<FileOffset
, @Nullable Iterable
<SourceCallsite
>>() {
99 public @Nullable Iterable
<SourceCallsite
> load(FileOffset fo
) {
100 return getCallsiteFromOffsetWithAddr2line(fo
);
106 * Generate the callsites from a given binary file and address offset.
108 * Due to function inlining, it is possible for one offset to actually have
109 * multiple call sites. This is why we can return more than one callsite per
113 * The binary file to look at
115 * The expected buildId of the binary file (is not verified at
118 * The memory offset in the file
119 * @return The list of callsites corresponding to the offset, reported from
120 * the "highest" inlining location, down to the initial definition.
122 public static @Nullable Iterable
<SourceCallsite
> getCallsiteFromOffset(File file
, String buildId
, long offset
) {
123 if (!Files
.exists((file
.toPath()))) {
126 // TODO We should also eventually verify that the passed buildId matches
127 // the file we are attempting to open.
129 FileOffset fo
= new FileOffset(checkNotNull(file
.toString()), buildId
, offset
);
130 return CALLSITE_CACHE
.getUnchecked(fo
);
133 private static @Nullable Iterable
<SourceCallsite
> getCallsiteFromOffsetWithAddr2line(FileOffset fo
) {
134 String filePath
= fo
.fFilePath
;
135 long offset
= fo
.fOffset
;
137 List
<SourceCallsite
> callsites
= new LinkedList
<>();
139 // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
140 List
<String
> output
= getOutputFromCommand(Arrays
.asList(
141 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$
143 if (output
== null) {
144 /* Command returned an error */
149 * When passing the -f flag, the output alternates between function
150 * names and file/line location.
152 boolean oddLine
= true;
153 String currentFunctionName
= null;
154 for (String outputLine
: output
) {
155 // Remove discriminator part, for example: /build/buildd/glibc-2.21/elf/dl-object.c:78 (discriminator 8)
156 outputLine
= outputLine
.replaceFirst(DISCRIMINATOR
, "").trim(); //$NON-NLS-1$
159 /* This is a line indicating the function name */
160 currentFunctionName
= outputLine
;
162 /* This is a line indicating a call site */
163 String
[] elems
= outputLine
.split(":"); //$NON-NLS-1$
164 String fileName
= elems
[0];
165 if (fileName
.equals("??")) { //$NON-NLS-1$
168 long lineNumber
= Long
.parseLong(elems
[1]);
170 callsites
.add(new SourceCallsite(fileName
, currentFunctionName
, lineNumber
));
173 /* Flip the boolean for the following line */
180 private static @Nullable List
<String
> getOutputFromCommand(List
<String
> command
) {
182 ProcessBuilder builder
= new ProcessBuilder(command
);
183 builder
.redirectErrorStream(true);
185 Process p
= builder
.start();
186 try (BufferedReader br
= new BufferedReader(new InputStreamReader(p
.getInputStream()));) {
187 int ret
= p
.waitFor();
188 List
<String
> lines
= br
.lines().collect(Collectors
.toList());
190 return (ret
== 0 ? lines
: null);
192 } catch (IOException
| InterruptedException e
) {