1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "perfetto/base/build_config.h"
18 #include "test/gtest_and_gmock.h"
19
20 // This translation unit is built only on Linux and MacOS. See //gn/BUILD.gn.
21 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
22
23 #include <cstddef>
24
25 #include "src/base/test/tmp_dir_tree.h"
26 #include "src/base/test/utils.h"
27 #include "src/profiling/symbolizer/elf.h"
28 #include "src/profiling/symbolizer/local_symbolizer.h"
29 #include "src/profiling/symbolizer/subprocess.h"
30
31 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
32 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
33 PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
34 #include <unistd.h>
35 #endif
36
37 namespace perfetto {
38 namespace profiling {
39 namespace {
40
RunAndValidateParseLines(std::string raw_contents)41 void RunAndValidateParseLines(std::string raw_contents) {
42 std::istringstream stream(raw_contents);
43 auto read_callback = [&stream](char* buffer, size_t size) {
44 stream.get(buffer, static_cast<int>(size), '\0');
45 return strlen(buffer);
46 };
47 std::vector<std::string> lines = GetLines(read_callback);
48 std::istringstream validation(raw_contents);
49 for (const std::string& actual : lines) {
50 std::string expected;
51 getline(validation, expected);
52 EXPECT_EQ(actual, expected);
53 }
54 }
55
TEST(LocalSymbolizerTest,ParseLineWindows)56 TEST(LocalSymbolizerTest, ParseLineWindows) {
57 std::string file_name;
58 uint32_t lineno;
59 ASSERT_TRUE(
60 ParseLlvmSymbolizerLine("C:\\Foo\\Bar.cc:123:1", &file_name, &lineno));
61 EXPECT_EQ(file_name, "C:\\Foo\\Bar.cc");
62 EXPECT_EQ(lineno, 123u);
63 }
64
TEST(LocalSymbolizerTest,ParseLinesExpectedOutput)65 TEST(LocalSymbolizerTest, ParseLinesExpectedOutput) {
66 std::string raw_contents =
67 "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
68 "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
69 "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
70 "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
71 "FSlateRenderingParams const&)\n"
72 "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
73 "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
74 RunAndValidateParseLines(raw_contents);
75 }
76
TEST(LocalSymbolizerTest,ParseLinesErrorOutput)77 TEST(LocalSymbolizerTest, ParseLinesErrorOutput) {
78 std::string raw_contents =
79 "LLVMSymbolizer: error reading file: No such file or directory\n"
80 "??\n"
81 "??:0:0\n";
82 RunAndValidateParseLines(raw_contents);
83 }
84
TEST(LocalSymbolizerTest,ParseLinesSingleCharRead)85 TEST(LocalSymbolizerTest, ParseLinesSingleCharRead) {
86 std::string raw_contents =
87 "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
88 "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
89 "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
90 "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
91 "FSlateRenderingParams const&)\n"
92 "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
93 "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
94 std::istringstream stream(raw_contents);
95 auto read_callback = [&stream](char* buffer, size_t) {
96 stream.get(buffer, 1, '\0');
97 return strlen(buffer);
98 };
99 std::vector<std::string> lines = GetLines(read_callback);
100 std::istringstream validation(raw_contents);
101 for (const std::string& actual : lines) {
102 std::string expected;
103 getline(validation, expected);
104 EXPECT_EQ(actual, expected);
105 }
106 }
107
108 // Creates a very simple ELF file content with the first 20 bytes of `build_id`
109 // as build id (if build id is shorter the remainin bytes are zero).
CreateElfWithBuildId(const std::string & build_id)110 std::string CreateElfWithBuildId(const std::string& build_id) {
111 struct SimpleElf {
112 Elf64::Ehdr ehdr;
113 Elf64::Shdr shdr;
114 Elf64::Nhdr nhdr;
115 char note_name[4];
116 char note_desc[20];
117 } e;
118 memset(&e, 0, sizeof e);
119
120 e.ehdr.e_ident[EI_MAG0] = ELFMAG0;
121 e.ehdr.e_ident[EI_MAG1] = ELFMAG1;
122 e.ehdr.e_ident[EI_MAG2] = ELFMAG2;
123 e.ehdr.e_ident[EI_MAG3] = ELFMAG3;
124 e.ehdr.e_ident[EI_CLASS] = ELFCLASS64;
125 e.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
126 e.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
127 e.ehdr.e_version = EV_CURRENT;
128 e.ehdr.e_shentsize = sizeof(Elf64::Shdr);
129 e.ehdr.e_shnum = 1;
130 e.ehdr.e_ehsize = sizeof e.ehdr;
131 e.ehdr.e_shoff = offsetof(SimpleElf, shdr);
132
133 e.shdr.sh_type = SHT_NOTE;
134 e.shdr.sh_offset = offsetof(SimpleElf, nhdr);
135
136 e.nhdr.n_type = NT_GNU_BUILD_ID;
137 e.nhdr.n_namesz = sizeof e.note_name;
138 e.nhdr.n_descsz = sizeof e.note_desc;
139 strcpy(e.note_name, "GNU");
140 memcpy(e.note_desc, build_id.c_str(),
141 std::min(build_id.size(), sizeof(e.note_desc)));
142
143 e.shdr.sh_size = offsetof(SimpleElf, note_desc) + sizeof(e.note_desc) -
144 offsetof(SimpleElf, nhdr);
145
146 return std::string(reinterpret_cast<const char*>(&e), sizeof e);
147 }
148
149 #if defined(MEMORY_SANITIZER)
150 // fts_read() causes some error under msan.
151 #define NOMSAN_SimpleTree DISABLED_SimpleTree
152 #else
153 #define NOMSAN_SimpleTree SimpleTree
154 #endif
TEST(LocalBinaryIndexerTest,NOMSAN_SimpleTree)155 TEST(LocalBinaryIndexerTest, NOMSAN_SimpleTree) {
156 base::TmpDirTree tmp;
157 tmp.AddDir("dir1");
158 tmp.AddFile("dir1/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
159 tmp.AddFile("dir1/nonelf1", "OTHERDATA");
160 tmp.AddDir("dir2");
161 tmp.AddFile("dir2/elf1", CreateElfWithBuildId("BBBBBBBBBBBBBBBBBBBB"));
162 tmp.AddFile("dir2/nonelf1", "other text");
163
164 LocalBinaryIndexer indexer({tmp.path() + "/dir1", tmp.path() + "/dir2"});
165
166 std::optional<FoundBinary> bin1 =
167 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
168 ASSERT_TRUE(bin1.has_value());
169 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
170 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/dir1\\elf1");
171 #else
172 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/dir1/elf1");
173 #endif
174 std::optional<FoundBinary> bin2 =
175 indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
176 ASSERT_TRUE(bin2.has_value());
177 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
178 EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2\\elf1");
179 #else
180 EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
181 #endif
182 }
183
184 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
185 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
186 PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
187
188 #if defined(MEMORY_SANITIZER)
189 // fts_read() causes some error under msan.
190 #define NOMSAN_Symlinks DISABLED_Symlinks
191 #else
192 #define NOMSAN_Symlinks Symlinks
193 #endif
TEST(LocalBinaryIndexerTest,NOMSAN_Symlinks)194 TEST(LocalBinaryIndexerTest, NOMSAN_Symlinks) {
195 base::TmpDirTree tmp;
196 tmp.AddDir("real");
197 tmp.AddFile("real/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
198 tmp.AddDir("real/dir1");
199 tmp.AddFile("real/dir1/elf2", CreateElfWithBuildId("BBBBBBBBBBBBBBBBBBBB"));
200 tmp.AddFile("real/dir1/elf3", CreateElfWithBuildId("CCCCCCCCCCCCCCCCCCCC"));
201 tmp.AddDir("sym");
202 EXPECT_EQ(symlink(tmp.AbsolutePath("real/elf1").c_str(),
203 tmp.AbsolutePath("sym/elf1").c_str()),
204 0);
205 tmp.TrackFile("sym/elf1");
206 EXPECT_EQ(symlink(tmp.AbsolutePath("real/dir1").c_str(),
207 tmp.AbsolutePath("sym/dir1").c_str()),
208 0);
209 tmp.TrackFile("sym/dir1");
210
211 LocalBinaryIndexer indexer({tmp.AbsolutePath("sym")});
212
213 std::optional<FoundBinary> bin1 =
214 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
215 ASSERT_TRUE(bin1.has_value());
216 EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("sym/elf1"));
217
218 std::optional<FoundBinary> bin2 =
219 indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
220 ASSERT_TRUE(bin2.has_value());
221 EXPECT_EQ(bin2.value().file_name, tmp.AbsolutePath("sym/dir1/elf2"));
222
223 std::optional<FoundBinary> bin3 =
224 indexer.FindBinary("", "CCCCCCCCCCCCCCCCCCCC");
225 ASSERT_TRUE(bin3.has_value());
226 EXPECT_EQ(bin3.value().file_name, tmp.AbsolutePath("sym/dir1/elf3"));
227 }
228
229 #if defined(MEMORY_SANITIZER)
230 // fts_read() causes some error under msan.
231 #define NOMSAN_RecursiveSymlinks DISABLED_RecursiveSymlinks
232 #else
233 #define NOMSAN_RecursiveSymlinks RecursiveSymlinks
234 #endif
TEST(LocalBinaryIndexerTest,NOMSAN_RecursiveSymlinks)235 TEST(LocalBinaryIndexerTest, NOMSAN_RecursiveSymlinks) {
236 base::TmpDirTree tmp;
237 tmp.AddDir("main");
238 tmp.AddFile("main/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
239 tmp.AddDir("main/dir1");
240 EXPECT_EQ(symlink(tmp.AbsolutePath("main").c_str(),
241 tmp.AbsolutePath("main/dir1/sym").c_str()),
242 0);
243 tmp.TrackFile("main/dir1/sym");
244
245 LocalBinaryIndexer indexer({tmp.AbsolutePath("main")});
246
247 std::optional<FoundBinary> bin1 =
248 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
249 ASSERT_TRUE(bin1.has_value());
250 EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("main/elf1"));
251 }
252
253 #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||
254 // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||
255 // PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
256
TEST(LocalBinaryFinderTest,AbsolutePath)257 TEST(LocalBinaryFinderTest, AbsolutePath) {
258 base::TmpDirTree tmp;
259 tmp.AddDir("root");
260 tmp.AddDir("root/dir");
261 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
262
263 LocalBinaryFinder finder({tmp.path() + "/root"});
264
265 std::optional<FoundBinary> bin1 =
266 finder.FindBinary("/dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
267 ASSERT_TRUE(bin1.has_value());
268 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
269 }
270
TEST(LocalBinaryFinderTest,AbsolutePathWithoutBaseApk)271 TEST(LocalBinaryFinderTest, AbsolutePathWithoutBaseApk) {
272 base::TmpDirTree tmp;
273 tmp.AddDir("root");
274 tmp.AddDir("root/dir");
275 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
276
277 LocalBinaryFinder finder({tmp.path() + "/root"});
278
279 std::optional<FoundBinary> bin1 =
280 finder.FindBinary("/dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
281 ASSERT_TRUE(bin1.has_value());
282 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
283 }
284
TEST(LocalBinaryFinderTest,OnlyFilename)285 TEST(LocalBinaryFinderTest, OnlyFilename) {
286 base::TmpDirTree tmp;
287 tmp.AddDir("root");
288 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
289
290 LocalBinaryFinder finder({tmp.path() + "/root"});
291
292 std::optional<FoundBinary> bin1 =
293 finder.FindBinary("/ignored_dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
294 ASSERT_TRUE(bin1.has_value());
295 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
296 }
297
TEST(LocalBinaryFinderTest,OnlyFilenameWithoutBaseApk)298 TEST(LocalBinaryFinderTest, OnlyFilenameWithoutBaseApk) {
299 base::TmpDirTree tmp;
300 tmp.AddDir("root");
301 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
302
303 LocalBinaryFinder finder({tmp.path() + "/root"});
304
305 std::optional<FoundBinary> bin1 = finder.FindBinary(
306 "/ignored_dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
307 ASSERT_TRUE(bin1.has_value());
308 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
309 }
310
TEST(LocalBinaryFinderTest,BuildIdSubdir)311 TEST(LocalBinaryFinderTest, BuildIdSubdir) {
312 base::TmpDirTree tmp;
313 tmp.AddDir("root");
314 tmp.AddDir("root/.build-id");
315 tmp.AddDir("root/.build-id/41");
316 tmp.AddFile("root/.build-id/41/41414141414141414141414141414141414141.debug",
317 CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
318
319 LocalBinaryFinder finder({tmp.path() + "/root"});
320
321 std::optional<FoundBinary> bin1 =
322 finder.FindBinary("/ignored_dir/ignored_name.so", "AAAAAAAAAAAAAAAAAAAA");
323 ASSERT_TRUE(bin1.has_value());
324 EXPECT_EQ(
325 bin1.value().file_name,
326 tmp.path() +
327 "/root/.build-id/41/41414141414141414141414141414141414141.debug");
328 }
329
330 } // namespace
331 } // namespace profiling
332 } // namespace perfetto
333
334 #endif
335