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 symlink(tmp.AbsolutePath("real/elf1").c_str(),
203 tmp.AbsolutePath("sym/elf1").c_str());
204 tmp.TrackFile("sym/elf1");
205 symlink(tmp.AbsolutePath("real/dir1").c_str(),
206 tmp.AbsolutePath("sym/dir1").c_str());
207 tmp.TrackFile("sym/dir1");
208
209 LocalBinaryIndexer indexer({tmp.AbsolutePath("sym")});
210
211 std::optional<FoundBinary> bin1 =
212 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
213 ASSERT_TRUE(bin1.has_value());
214 EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("sym/elf1"));
215
216 std::optional<FoundBinary> bin2 =
217 indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
218 ASSERT_TRUE(bin2.has_value());
219 EXPECT_EQ(bin2.value().file_name, tmp.AbsolutePath("sym/dir1/elf2"));
220
221 std::optional<FoundBinary> bin3 =
222 indexer.FindBinary("", "CCCCCCCCCCCCCCCCCCCC");
223 ASSERT_TRUE(bin3.has_value());
224 EXPECT_EQ(bin3.value().file_name, tmp.AbsolutePath("sym/dir1/elf3"));
225 }
226
227 #if defined(MEMORY_SANITIZER)
228 // fts_read() causes some error under msan.
229 #define NOMSAN_RecursiveSymlinks DISABLED_RecursiveSymlinks
230 #else
231 #define NOMSAN_RecursiveSymlinks RecursiveSymlinks
232 #endif
TEST(LocalBinaryIndexerTest,NOMSAN_RecursiveSymlinks)233 TEST(LocalBinaryIndexerTest, NOMSAN_RecursiveSymlinks) {
234 base::TmpDirTree tmp;
235 tmp.AddDir("main");
236 tmp.AddFile("main/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
237 tmp.AddDir("main/dir1");
238 symlink(tmp.AbsolutePath("main").c_str(),
239 tmp.AbsolutePath("main/dir1/sym").c_str());
240 tmp.TrackFile("main/dir1/sym");
241
242 LocalBinaryIndexer indexer({tmp.AbsolutePath("main")});
243
244 std::optional<FoundBinary> bin1 =
245 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
246 ASSERT_TRUE(bin1.has_value());
247 EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("main/elf1"));
248 }
249
250 #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||
251 // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||
252 // PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
253
TEST(LocalBinaryFinderTest,AbsolutePath)254 TEST(LocalBinaryFinderTest, AbsolutePath) {
255 base::TmpDirTree tmp;
256 tmp.AddDir("root");
257 tmp.AddDir("root/dir");
258 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
259
260 LocalBinaryFinder finder({tmp.path() + "/root"});
261
262 std::optional<FoundBinary> bin1 =
263 finder.FindBinary("/dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
264 ASSERT_TRUE(bin1.has_value());
265 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
266 }
267
TEST(LocalBinaryFinderTest,AbsolutePathWithoutBaseApk)268 TEST(LocalBinaryFinderTest, AbsolutePathWithoutBaseApk) {
269 base::TmpDirTree tmp;
270 tmp.AddDir("root");
271 tmp.AddDir("root/dir");
272 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
273
274 LocalBinaryFinder finder({tmp.path() + "/root"});
275
276 std::optional<FoundBinary> bin1 =
277 finder.FindBinary("/dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
278 ASSERT_TRUE(bin1.has_value());
279 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
280 }
281
TEST(LocalBinaryFinderTest,OnlyFilename)282 TEST(LocalBinaryFinderTest, OnlyFilename) {
283 base::TmpDirTree tmp;
284 tmp.AddDir("root");
285 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
286
287 LocalBinaryFinder finder({tmp.path() + "/root"});
288
289 std::optional<FoundBinary> bin1 =
290 finder.FindBinary("/ignored_dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
291 ASSERT_TRUE(bin1.has_value());
292 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
293 }
294
TEST(LocalBinaryFinderTest,OnlyFilenameWithoutBaseApk)295 TEST(LocalBinaryFinderTest, OnlyFilenameWithoutBaseApk) {
296 base::TmpDirTree tmp;
297 tmp.AddDir("root");
298 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
299
300 LocalBinaryFinder finder({tmp.path() + "/root"});
301
302 std::optional<FoundBinary> bin1 = finder.FindBinary(
303 "/ignored_dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
304 ASSERT_TRUE(bin1.has_value());
305 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
306 }
307
TEST(LocalBinaryFinderTest,BuildIdSubdir)308 TEST(LocalBinaryFinderTest, BuildIdSubdir) {
309 base::TmpDirTree tmp;
310 tmp.AddDir("root");
311 tmp.AddDir("root/.build-id");
312 tmp.AddDir("root/.build-id/41");
313 tmp.AddFile("root/.build-id/41/41414141414141414141414141414141414141.debug",
314 CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
315
316 LocalBinaryFinder finder({tmp.path() + "/root"});
317
318 std::optional<FoundBinary> bin1 =
319 finder.FindBinary("/ignored_dir/ignored_name.so", "AAAAAAAAAAAAAAAAAAAA");
320 ASSERT_TRUE(bin1.has_value());
321 EXPECT_EQ(
322 bin1.value().file_name,
323 tmp.path() +
324 "/root/.build-id/41/41414141414141414141414141414141414141.debug");
325 }
326
327 } // namespace
328 } // namespace profiling
329 } // namespace perfetto
330
331 #endif
332