4a83cbb1217fd94e3cad47a7cffdcca5ff95729a
[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.LinkedList;
21 import java.util.List;
22 import java.util.stream.Collectors;
23
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
26
27 import com.google.common.base.Objects;
28 import com.google.common.cache.CacheBuilder;
29 import com.google.common.cache.CacheLoader;
30 import com.google.common.cache.LoadingCache;
31
32 /**
33 * Utility class to get file name, function/symbol name and line number from a
34 * given offset. In TMF this is represented as a {@link TmfCallsite}.
35 *
36 * @author Alexandre Montplaisir
37 */
38 public final class FileOffsetMapper {
39
40 private static final String DISCRIMINATOR = "\\(discriminator.*\\)"; //$NON-NLS-1$
41 private static final String ADDR2LINE_EXECUTABLE = "addr2line"; //$NON-NLS-1$
42
43 private static final long CACHE_SIZE = 1000;
44
45 private FileOffsetMapper() {}
46
47 /**
48 * Class representing an offset in a specific file
49 */
50 private static class FileOffset {
51
52 private final String fFilePath;
53 private final String fBuildId;
54 private final long fOffset;
55
56 public FileOffset(String filePath, String buildId, long offset) {
57 fFilePath = filePath;
58 fBuildId = buildId;
59 fOffset = offset;
60 }
61
62 @Override
63 public int hashCode() {
64 return Objects.hashCode(fFilePath, fBuildId, fOffset);
65 }
66
67 @Override
68 public boolean equals(@Nullable Object obj) {
69 if (this == obj) {
70 return true;
71 }
72 if (obj == null) {
73 return false;
74 }
75 if (getClass() != obj.getClass()) {
76 return false;
77 }
78 FileOffset other = (FileOffset) obj;
79 return Objects.equal(fFilePath, other.fFilePath) &&
80 Objects.equal(fBuildId, other.fBuildId) &&
81 Objects.equal(fOffset, other.fOffset);
82 }
83 }
84
85 /**
86 * Cache of all calls to 'addr2line', so that we can avoid recalling the
87 * external process repeatedly.
88 *
89 * It is static, meaning one cache for the whole application, since the
90 * symbols in a file on disk are independent from the trace referring to it.
91 */
92 private static final LoadingCache<FileOffset, @Nullable Iterable<TmfCallsite>> CALLSITE_CACHE;
93 static {
94 CALLSITE_CACHE = checkNotNull(CacheBuilder.newBuilder()
95 .maximumSize(CACHE_SIZE)
96 .build(new CacheLoader<FileOffset, @Nullable Iterable<TmfCallsite>>() {
97 @Override
98 public @Nullable Iterable<TmfCallsite> load(FileOffset fo) {
99 return getCallsiteFromOffsetWithAddr2line(fo);
100 }
101 }));
102 }
103
104 /**
105 * Generate the callsites from a given binary file and address offset.
106 *
107 * Due to function inlining, it is possible for one offset to actually have
108 * multiple call sites. This is why we can return more than one callsite per
109 * call.
110 *
111 * @param file
112 * The binary file to look at
113 * @param buildId
114 * The expected buildId of the binary file (is not verified at
115 * the moment)
116 * @param offset
117 * The memory offset in the file
118 * @return The list of callsites corresponding to the offset, reported from
119 * the "highest" inlining location, down to the initial definition.
120 */
121 public static @Nullable Iterable<TmfCallsite> getCallsiteFromOffset(File file, String buildId, long offset) {
122 if (!Files.exists((file.toPath()))) {
123 return null;
124 }
125 // TODO We should also eventually verify that the passed buildId matches
126 // the file we are attempting to open.
127
128 FileOffset fo = new FileOffset(checkNotNull(file.toString()), buildId, offset);
129 return CALLSITE_CACHE.getUnchecked(fo);
130 }
131
132 private static @Nullable Iterable<TmfCallsite> getCallsiteFromOffsetWithAddr2line(FileOffset fo) {
133 String filePath = fo.fFilePath;
134 long offset = fo.fOffset;
135
136 List<TmfCallsite> callsites = new LinkedList<>();
137
138 // FIXME Could eventually use CDT's Addr2line class once it implements --inlines
139 List<String> output = getOutputFromCommand(Arrays.asList(
140 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$
141
142 if (output == null) {
143 /* Command returned an error */
144 return null;
145 }
146
147 /*
148 * When passing the -f flag, the output alternates between function
149 * names and file/line location.
150 */
151 boolean oddLine = true;
152 String currentFunctionName = null;
153 for (String outputLine : output) {
154 // Remove discriminator part, for example: /build/buildd/glibc-2.21/elf/dl-object.c:78 (discriminator 8)
155 outputLine = outputLine.replaceFirst(DISCRIMINATOR, "").trim(); //$NON-NLS-1$
156
157 if (oddLine) {
158 /* This is a line indicating the function name */
159 currentFunctionName = outputLine;
160 } else {
161 /* This is a line indicating a call site */
162 String[] elems = outputLine.split(":"); //$NON-NLS-1$
163 String fileName = elems[0];
164 if (fileName.equals("??")) { //$NON-NLS-1$
165 continue;
166 }
167 long lineNumber = Long.parseLong(elems[1]);
168
169 callsites.add(new TmfCallsite(fileName, currentFunctionName, lineNumber));
170 }
171
172 /* Flip the boolean for the following line */
173 oddLine = !oddLine;
174 }
175
176 return callsites;
177 }
178
179 private static @Nullable List<String> getOutputFromCommand(List<String> command) {
180 try {
181 ProcessBuilder builder = new ProcessBuilder(command);
182 builder.redirectErrorStream(true);
183
184 Process p = builder.start();
185 try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
186 int ret = p.waitFor();
187 List<String> lines = br.lines().collect(Collectors.toList());
188
189 return (ret == 0 ? lines : null);
190 }
191 } catch (IOException | InterruptedException e) {
192 return null;
193 }
194 }
195 }
This page took 0.041799 seconds and 4 git commands to generate.