69c7b1f6661ddd5382bbeab5a5945c3bdfdfe783
[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.lttng2.ust.core.analysis.debuginfo.SourceCallsite;
26 import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
27
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
33 /**
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}.
36 *
37 * @author Alexandre Montplaisir
38 */
39 public final class FileOffsetMapper {
40
41 private static final String DISCRIMINATOR = "\\(discriminator.*\\)"; //$NON-NLS-1$
42 private static final String ADDR2LINE_EXECUTABLE = "addr2line"; //$NON-NLS-1$
43
44 private static final long CACHE_SIZE = 1000;
45
46 private FileOffsetMapper() {}
47
48 /**
49 * Class representing an offset in a specific file
50 */
51 private static class FileOffset {
52
53 private final String fFilePath;
54 private final @Nullable String fBuildId;
55 private final long fOffset;
56
57 public FileOffset(String filePath, @Nullable String buildId, long offset) {
58 fFilePath = filePath;
59 fBuildId = buildId;
60 fOffset = offset;
61 }
62
63 @Override
64 public int hashCode() {
65 return Objects.hashCode(fFilePath, fBuildId, fOffset);
66 }
67
68 @Override
69 public boolean equals(@Nullable Object obj) {
70 if (this == obj) {
71 return true;
72 }
73 if (obj == null) {
74 return false;
75 }
76 if (getClass() != obj.getClass()) {
77 return false;
78 }
79 FileOffset other = (FileOffset) obj;
80 return Objects.equal(fFilePath, other.fFilePath) &&
81 Objects.equal(fBuildId, other.fBuildId) &&
82 Objects.equal(fOffset, other.fOffset);
83 }
84 }
85
86 /**
87 * Cache of all calls to 'addr2line', so that we can avoid recalling the
88 * external process repeatedly.
89 *
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.
92 */
93 private static final LoadingCache<FileOffset, @Nullable Iterable<SourceCallsite>> CALLSITE_CACHE;
94 static {
95 CALLSITE_CACHE = checkNotNull(CacheBuilder.newBuilder()
96 .maximumSize(CACHE_SIZE)
97 .build(new CacheLoader<FileOffset, @Nullable Iterable<SourceCallsite>>() {
98 @Override
99 public @Nullable Iterable<SourceCallsite> load(FileOffset fo) {
100 return getCallsiteFromOffsetWithAddr2line(fo);
101 }
102 }));
103 }
104
105 /**
106 * Generate the callsites from a given binary file and address offset.
107 *
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
110 * call.
111 *
112 * @param file
113 * The binary file to look at
114 * @param buildId
115 * The expected buildId of the binary file (is not verified at
116 * the moment)
117 * @param offset
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.
121 */
122 public static @Nullable Iterable<SourceCallsite> getCallsiteFromOffset(File file, @Nullable String buildId, long offset) {
123 if (!Files.exists((file.toPath()))) {
124 return null;
125 }
126 // TODO We should also eventually verify that the passed buildId matches
127 // the file we are attempting to open.
128
129 FileOffset fo = new FileOffset(checkNotNull(file.toString()), buildId, offset);
130 return CALLSITE_CACHE.getUnchecked(fo);
131 }
132
133 private static @Nullable Iterable<SourceCallsite> getCallsiteFromOffsetWithAddr2line(FileOffset fo) {
134 String filePath = fo.fFilePath;
135 long offset = fo.fOffset;
136
137 List<SourceCallsite> callsites = new LinkedList<>();
138
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$
142
143 if (output == null) {
144 /* Command returned an error */
145 return null;
146 }
147
148 /*
149 * When passing the -f flag, the output alternates between function
150 * names and file/line location.
151 */
152 boolean oddLine = false; // We flip at the start, first loop will be odd
153 String currentFunctionName = null;
154 for (String outputLine : output) {
155 /* Flip the boolean for the following line */
156 oddLine = !oddLine;
157
158 // Remove discriminator part, for example: /build/buildd/glibc-2.21/elf/dl-object.c:78 (discriminator 8)
159 outputLine = outputLine.replaceFirst(DISCRIMINATOR, "").trim(); //$NON-NLS-1$
160
161 if (oddLine) {
162 /* This is a line indicating the function name */
163 currentFunctionName = outputLine;
164 } else {
165 /* This is a line indicating a call site */
166 String[] elems = outputLine.split(":"); //$NON-NLS-1$
167 String fileName = elems[0];
168 if (fileName.equals("??")) { //$NON-NLS-1$
169 continue;
170 }
171 try {
172 long lineNumber = Long.parseLong(elems[1]);
173 callsites.add(new SourceCallsite(fileName, currentFunctionName, lineNumber));
174
175 } catch (NumberFormatException e) {
176 /*
177 * Probably a '?' output, meaning unknown line number.
178 * Ignore this entry.
179 */
180 continue;
181 }
182 }
183 }
184
185 return callsites;
186 }
187
188 private static @Nullable List<String> getOutputFromCommand(List<String> command) {
189 try {
190 ProcessBuilder builder = new ProcessBuilder(command);
191 builder.redirectErrorStream(true);
192
193 Process p = builder.start();
194 try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
195 int ret = p.waitFor();
196 List<String> lines = br.lines().collect(Collectors.toList());
197
198 return (ret == 0 ? lines : null);
199 }
200 } catch (IOException | InterruptedException e) {
201 return null;
202 }
203 }
204 }
This page took 0.03649 seconds and 4 git commands to generate.