• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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