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 namespace perfetto {
32 namespace profiling {
33 namespace {
34
RunAndValidateParseLines(std::string raw_contents)35 void RunAndValidateParseLines(std::string raw_contents) {
36 std::istringstream stream(raw_contents);
37 auto read_callback = [&stream](char* buffer, size_t size) {
38 stream.get(buffer, static_cast<int>(size), '\0');
39 return strlen(buffer);
40 };
41 std::vector<std::string> lines = GetLines(read_callback);
42 std::istringstream validation(raw_contents);
43 for (const std::string& actual : lines) {
44 std::string expected;
45 getline(validation, expected);
46 EXPECT_EQ(actual, expected);
47 }
48 }
49
TEST(LocalSymbolizerTest,ParseLineWindows)50 TEST(LocalSymbolizerTest, ParseLineWindows) {
51 std::string file_name;
52 uint32_t lineno;
53 ASSERT_TRUE(
54 ParseLlvmSymbolizerLine("C:\\Foo\\Bar.cc:123:1", &file_name, &lineno));
55 EXPECT_EQ(file_name, "C:\\Foo\\Bar.cc");
56 EXPECT_EQ(lineno, 123u);
57 }
58
TEST(LocalSymbolizerTest,ParseLinesExpectedOutput)59 TEST(LocalSymbolizerTest, ParseLinesExpectedOutput) {
60 std::string raw_contents =
61 "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
62 "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
63 "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
64 "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
65 "FSlateRenderingParams const&)\n"
66 "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
67 "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
68 RunAndValidateParseLines(raw_contents);
69 }
70
TEST(LocalSymbolizerTest,ParseLinesErrorOutput)71 TEST(LocalSymbolizerTest, ParseLinesErrorOutput) {
72 std::string raw_contents =
73 "LLVMSymbolizer: error reading file: No such file or directory\n"
74 "??\n"
75 "??:0:0\n";
76 RunAndValidateParseLines(raw_contents);
77 }
78
TEST(LocalSymbolizerTest,ParseLinesSingleCharRead)79 TEST(LocalSymbolizerTest, ParseLinesSingleCharRead) {
80 std::string raw_contents =
81 "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
82 "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
83 "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
84 "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
85 "FSlateRenderingParams const&)\n"
86 "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
87 "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
88 std::istringstream stream(raw_contents);
89 auto read_callback = [&stream](char* buffer, size_t) {
90 stream.get(buffer, 1, '\0');
91 return strlen(buffer);
92 };
93 std::vector<std::string> lines = GetLines(read_callback);
94 std::istringstream validation(raw_contents);
95 for (const std::string& actual : lines) {
96 std::string expected;
97 getline(validation, expected);
98 EXPECT_EQ(actual, expected);
99 }
100 }
101
102 // Creates a very simple ELF file content with the first 20 bytes of `build_id`
103 // as build id (if build id is shorter the remainin bytes are zero).
CreateElfWithBuildId(const std::string & build_id)104 std::string CreateElfWithBuildId(const std::string& build_id) {
105 struct SimpleElf {
106 Elf64::Ehdr ehdr;
107 Elf64::Shdr shdr;
108 Elf64::Nhdr nhdr;
109 char note_name[4];
110 char note_desc[20];
111 } e;
112 memset(&e, 0, sizeof e);
113
114 e.ehdr.e_ident[EI_MAG0] = ELFMAG0;
115 e.ehdr.e_ident[EI_MAG1] = ELFMAG1;
116 e.ehdr.e_ident[EI_MAG2] = ELFMAG2;
117 e.ehdr.e_ident[EI_MAG3] = ELFMAG3;
118 e.ehdr.e_ident[EI_CLASS] = ELFCLASS64;
119 e.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
120 e.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
121 e.ehdr.e_version = EV_CURRENT;
122 e.ehdr.e_shentsize = sizeof(Elf64::Shdr);
123 e.ehdr.e_shnum = 1;
124 e.ehdr.e_ehsize = sizeof e.ehdr;
125 e.ehdr.e_shoff = offsetof(SimpleElf, shdr);
126
127 e.shdr.sh_type = SHT_NOTE;
128 e.shdr.sh_offset = offsetof(SimpleElf, nhdr);
129
130 e.nhdr.n_type = NT_GNU_BUILD_ID;
131 e.nhdr.n_namesz = sizeof e.note_name;
132 e.nhdr.n_descsz = sizeof e.note_desc;
133 strcpy(e.note_name, "GNU");
134 memcpy(e.note_desc, build_id.c_str(),
135 std::min(build_id.size(), sizeof(e.note_desc)));
136
137 e.shdr.sh_size = offsetof(SimpleElf, note_desc) + sizeof(e.note_desc) -
138 offsetof(SimpleElf, nhdr);
139
140 return std::string(reinterpret_cast<const char*>(&e), sizeof e);
141 }
142
TEST(LocalBinaryIndexerTest,SimpleTree)143 TEST(LocalBinaryIndexerTest, SimpleTree) {
144 base::TmpDirTree tmp;
145 tmp.AddDir("dir1");
146 tmp.AddFile("dir1/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
147 tmp.AddFile("dir1/nonelf1", "OTHERDATA");
148 tmp.AddDir("dir2");
149 tmp.AddFile("dir2/elf1", CreateElfWithBuildId("BBBBBBBBBBBBBBBBBBBB"));
150 tmp.AddFile("dir2/nonelf1", "other text");
151
152 LocalBinaryIndexer indexer({tmp.path() + "/dir1", tmp.path() + "/dir2"});
153
154 base::Optional<FoundBinary> bin1 =
155 indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
156 ASSERT_TRUE(bin1.has_value());
157 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/dir1/elf1");
158
159 base::Optional<FoundBinary> bin2 =
160 indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
161 ASSERT_TRUE(bin2.has_value());
162 EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
163 }
164
TEST(LocalBinaryFinderTest,AbsolutePath)165 TEST(LocalBinaryFinderTest, AbsolutePath) {
166 base::TmpDirTree tmp;
167 tmp.AddDir("root");
168 tmp.AddDir("root/dir");
169 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
170
171 LocalBinaryFinder finder({tmp.path() + "/root"});
172
173 base::Optional<FoundBinary> bin1 =
174 finder.FindBinary("/dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
175 ASSERT_TRUE(bin1.has_value());
176 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
177 }
178
TEST(LocalBinaryFinderTest,AbsolutePathWithoutBaseApk)179 TEST(LocalBinaryFinderTest, AbsolutePathWithoutBaseApk) {
180 base::TmpDirTree tmp;
181 tmp.AddDir("root");
182 tmp.AddDir("root/dir");
183 tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
184
185 LocalBinaryFinder finder({tmp.path() + "/root"});
186
187 base::Optional<FoundBinary> bin1 =
188 finder.FindBinary("/dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
189 ASSERT_TRUE(bin1.has_value());
190 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
191 }
192
TEST(LocalBinaryFinderTest,OnlyFilename)193 TEST(LocalBinaryFinderTest, OnlyFilename) {
194 base::TmpDirTree tmp;
195 tmp.AddDir("root");
196 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
197
198 LocalBinaryFinder finder({tmp.path() + "/root"});
199
200 base::Optional<FoundBinary> bin1 =
201 finder.FindBinary("/ignored_dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
202 ASSERT_TRUE(bin1.has_value());
203 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
204 }
205
TEST(LocalBinaryFinderTest,OnlyFilenameWithoutBaseApk)206 TEST(LocalBinaryFinderTest, OnlyFilenameWithoutBaseApk) {
207 base::TmpDirTree tmp;
208 tmp.AddDir("root");
209 tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
210
211 LocalBinaryFinder finder({tmp.path() + "/root"});
212
213 base::Optional<FoundBinary> bin1 = finder.FindBinary(
214 "/ignored_dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
215 ASSERT_TRUE(bin1.has_value());
216 EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
217 }
218
TEST(LocalBinaryFinderTest,BuildIdSubdir)219 TEST(LocalBinaryFinderTest, BuildIdSubdir) {
220 base::TmpDirTree tmp;
221 tmp.AddDir("root");
222 tmp.AddDir("root/.build-id");
223 tmp.AddDir("root/.build-id/41");
224 tmp.AddFile("root/.build-id/41/41414141414141414141414141414141414141.debug",
225 CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
226
227 LocalBinaryFinder finder({tmp.path() + "/root"});
228
229 base::Optional<FoundBinary> bin1 =
230 finder.FindBinary("/ignored_dir/ignored_name.so", "AAAAAAAAAAAAAAAAAAAA");
231 ASSERT_TRUE(bin1.has_value());
232 EXPECT_EQ(
233 bin1.value().file_name,
234 tmp.path() +
235 "/root/.build-id/41/41414141414141414141414141414141414141.debug");
236 }
237
238 } // namespace
239 } // namespace profiling
240 } // namespace perfetto
241
242 #endif
243