1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "virtual_thread_test.h"
17
18 #include <gtest/gtest.h>
19 #include <link.h>
20 #include <random>
21 #include <sys/mman.h>
22
23 #include <hilog/log.h>
24
25 #include "symbols_file_test.h"
26
27 using namespace testing::ext;
28 namespace OHOS {
29 namespace Developtools {
30 namespace HiPerf {
31 class VirtualThreadTest : public testing::Test {
32 public:
33 static void SetUpTestCase(void);
34 static void TearDownTestCase(void);
35 void SetUp();
36 void TearDown();
37 const std::string TEST_LOG_MESSAGE = "<HELLO_TEST_LOG_MESSAGE>";
38 void LogLevelTest(std::vector<std::string> args, DebugLevel level);
39 std::default_random_engine rnd_;
40 static std::string myFilePath_;
GetFullPath()41 static std::string GetFullPath()
42 {
43 char path[PATH_MAX];
44 int i = readlink("/proc/self/exe", path, sizeof(path));
45 path[i] = '\0';
46 return path;
47 }
48 static int PhdrCallBack(struct dl_phdr_info *info, size_t size, void *data);
49 static void MakeMapsFromDlpi(const std::string &dlpiName, const struct dl_phdr_info *info,
50 std::vector<DfxMap> &phdrMaps);
51 };
52
53 std::string VirtualThreadTest::myFilePath_;
54
SetUpTestCase()55 void VirtualThreadTest::SetUpTestCase()
56 {
57 DebugLogger::GetInstance()->OpenLog(DEFAULT_UT_LOG_DIR + "VirtualThreadTest.txt");
58 }
59
TearDownTestCase()60 void VirtualThreadTest::TearDownTestCase()
61 {
62 DebugLogger::GetInstance()->RestoreLog();
63 }
64
SetUp()65 void VirtualThreadTest::SetUp() {}
66
TearDown()67 void VirtualThreadTest::TearDown() {}
68
69
MakeMapsFromDlpi(const std::string & dlpiName,const struct dl_phdr_info * info,std::vector<DfxMap> & phdrMaps)70 void VirtualThreadTest::MakeMapsFromDlpi(const std::string &dlpiName,
71 const struct dl_phdr_info *info,
72 std::vector<DfxMap> &phdrMaps)
73 {
74 if (info == nullptr) {
75 HLOGE("param is null");
76 return;
77 }
78 int phdrType;
79 HLOGV("Name: \"%s\" (%d segments)", dlpiName.c_str(), info->dlpi_phnum);
80 for (int i = 0; i < info->dlpi_phnum; i++) {
81 phdrType = info->dlpi_phdr[i].p_type;
82 if (phdrType != PT_LOAD) {
83 continue;
84 }
85 HLOGV(" %2d: [%14p; memsz:%7jx] align %jx flags: %#jx [(%#x)]", i,
86 (void *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr),
87 (uintmax_t)info->dlpi_phdr[i].p_memsz, (uintmax_t)info->dlpi_phdr[i].p_align,
88 (uintmax_t)info->dlpi_phdr[i].p_flags, phdrType);
89
90 DfxMap &item = phdrMaps.emplace_back();
91 item.begin = (info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
92 item.end = RoundUp(item.begin + info->dlpi_phdr[i].p_memsz, info->dlpi_phdr[i].p_align);
93 item.offset = info->dlpi_phdr[i].p_offset;
94 item.name = dlpiName;
95 }
96 for (auto& item : phdrMaps) {
97 HLOGV("%s", item.ToString().c_str());
98 }
99 EXPECT_NE(phdrMaps.size(), 0u);
100 }
101
PhdrCallBack(struct dl_phdr_info * info,size_t size,void * data)102 int VirtualThreadTest::PhdrCallBack(struct dl_phdr_info *info, size_t size, void *data)
103 {
104 if (info == nullptr || data == nullptr) {
105 HLOGE("param is null");
106 return -1;
107 }
108 VirtualThread *thread = static_cast<VirtualThread *>(data);
109 std::vector<DfxMap> phdrMaps {};
110 std::vector<std::shared_ptr<DfxMap>> memMaps {};
111 static std::string myFilePath = GetFullPath();
112 EXPECT_NE(thread->GetMaps().size(), 0u);
113 std::string dlpiName = info->dlpi_name;
114 if (StringStartsWith(dlpiName, "./") and !StringEndsWith(dlpiName, ".so")) {
115 dlpiName = myFilePath;
116 }
117 if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
118 // dont care empty pt
119 return 0;
120 } else {
121 MakeMapsFromDlpi(dlpiName, info, phdrMaps);
122 }
123
124 for (auto item : thread->GetMaps()) {
125 if (item->name == dlpiName) {
126 HLOGV("%s", item->ToString().c_str());
127 memMaps.emplace_back(item);
128 }
129 }
130
131 if (memMaps.size() == 0u) {
132 // show all the items if not any match mapitem found
133 for (auto item : thread->GetMaps()) {
134 HLOGV("%s", item->ToString().c_str());
135 }
136 return 0;
137 }
138
139 if (memMaps.size() == phdrMaps.size()) {
140 EXPECT_EQ(memMaps.front()->begin, phdrMaps.front().begin);
141 EXPECT_EQ(memMaps.front()->offset, phdrMaps.front().offset);
142 }
143 return 0;
144 }
145
146 /**
147 * @tc.name: ParseMap
148 * @tc.desc:
149 * @tc.type: FUNC
150 */
151 HWTEST_F(VirtualThreadTest, ParseMap, TestSize.Level1)
152 {
153 std::vector<std::unique_ptr<SymbolsFile>> files;
154 VirtualThread thread(getpid(), files);
155 thread.ParseMap();
156
157 dl_iterate_phdr(PhdrCallBack, static_cast<void *>(&thread));
158
159 for (auto item : thread.GetMaps()) {
160 EXPECT_EQ(item->name.empty(), false);
161 EXPECT_STRNE(item->name.c_str(), MMAP_NAME_HEAP.c_str());
162 EXPECT_STRNE(item->name.c_str(), MMAP_NAME_ANON.c_str());
163 }
164 }
165
166 /**
167 * @tc.name: CreateMapItem
168 * @tc.desc:
169 * @tc.type: FUNC
170 */
171 HWTEST_F(VirtualThreadTest, CreateMapItem, TestSize.Level1)
172 {
173 std::vector<std::unique_ptr<SymbolsFile>> files;
174 VirtualThread thread(getpid(), files);
175 thread.CreateMapItem("0.so", 1000, 2000, 3000);
176 thread.CreateMapItem("1.so", 3000, 4000, 5000);
177 thread.CreateMapItem("2.so", 10000, 20000, 30000);
178
179 auto& maps = thread.GetMaps();
180
181 EXPECT_EQ(maps.size(), 3u);
182 EXPECT_EQ(maps.at(0)->begin, 1000u);
183 EXPECT_EQ(maps.at(1)->begin, 3000u);
184 EXPECT_EQ(maps.at(2)->begin, 10000u);
185
186 EXPECT_EQ(maps.at(0)->end, 1000u + 2000u);
187 EXPECT_EQ(maps.at(1)->end, 3000u + 4000u);
188 EXPECT_EQ(maps.at(2)->end, 10000u + 20000u);
189
190 EXPECT_EQ(maps.at(0)->offset, 3000u);
191 EXPECT_EQ(maps.at(1)->offset, 5000u);
192 EXPECT_EQ(maps.at(2)->offset, 30000u);
193
194 EXPECT_STREQ(maps.at(0)->name.c_str(), "0.so");
195 EXPECT_STREQ(maps.at(1)->name.c_str(), "1.so");
196 EXPECT_STREQ(maps.at(2)->name.c_str(), "2.so");
197 }
198
199 /**
200 * @tc.name: InheritMaps
201 * @tc.desc:
202 * @tc.type: FUNC
203 */
204 HWTEST_F(VirtualThreadTest, InheritMaps, TestSize.Level1)
205 {
206 std::vector<std::unique_ptr<SymbolsFile>> files;
207 VirtualThread thread(getpid(), files);
208 thread.ParseMap();
209
210 VirtualThread thread2(getpid(), gettid() + 1u, thread, files);
211
212 auto& maps = thread.GetMaps();
213 auto& maps2 = thread2.GetMaps();
214
215 ASSERT_EQ(maps.size(), maps2.size());
216 for (size_t i = 0; i < maps.size(); i++) {
217 EXPECT_STREQ(maps[i]->ToString().c_str(), maps2[i]->ToString().c_str());
218 }
219
220 size_t oldSize = thread.GetMaps().size();
221 thread.CreateMapItem("new", 0u, 1u, 2u); // update maps
222 size_t newSize = thread.GetMaps().size();
223 ASSERT_EQ(oldSize + 1, newSize);
224 ASSERT_EQ(maps.size(), maps2.size());
225 for (size_t i = 0; i < maps.size(); i++) {
226 EXPECT_STREQ(maps[i]->ToString().c_str(), maps2[i]->ToString().c_str());
227 }
228 }
229
230 /**
231 * @tc.name: FindMapByAddr
232 * @tc.desc:
233 * @tc.type: FUNC
234 */
235 HWTEST_F(VirtualThreadTest, FindMapByAddr, TestSize.Level1)
236 {
237 std::vector<std::unique_ptr<SymbolsFile>> files;
238 VirtualThread thread(getpid(), files);
239
240 thread.CreateMapItem("0.so", 1000u, 2000u, 3000u);
241 thread.CreateMapItem("1.so", 3000u, 4000u, 5000u);
242 thread.CreateMapItem("2.so", 10000u, 20000u, 30000u);
243
244 std::shared_ptr<DfxMap> outMap = nullptr;
245 outMap = thread.FindMapByAddr(0000u);
246 EXPECT_EQ(outMap != nullptr, false);
247
248 outMap = thread.FindMapByAddr(1000u);
249 ASSERT_EQ(outMap != nullptr, true);
250 EXPECT_EQ(outMap->begin, 1000u);
251
252 outMap = thread.FindMapByAddr(2000u);
253 ASSERT_EQ(outMap != nullptr, true);
254 EXPECT_EQ(outMap->begin, 1000u);
255
256 outMap = thread.FindMapByAddr(2999u);
257 ASSERT_EQ(outMap != nullptr, true);
258 EXPECT_EQ(outMap->begin, 1000u);
259
260 outMap = thread.FindMapByAddr(3000u);
261 ASSERT_EQ(outMap != nullptr, true);
262 EXPECT_EQ(outMap->begin, 3000u);
263
264 EXPECT_EQ(thread.FindMapByAddr(30000u - 1u) != nullptr, true);
265 EXPECT_EQ(thread.FindMapByAddr(30000u) != nullptr, false);
266 EXPECT_EQ(thread.FindMapByAddr(30000u + 1u) != nullptr, false);
267 }
268
269 /**
270 * @tc.name: FindMapByFileInfo
271 * @tc.desc:
272 * @tc.type: FUNC
273 */
274 HWTEST_F(VirtualThreadTest, FindMapByFileInfo, TestSize.Level1)
275 {
276 std::vector<std::unique_ptr<SymbolsFile>> files;
277 VirtualThread thread(getpid(), files);
278
279 thread.CreateMapItem("0.so", 1000u, 2000u, 3000u);
280 thread.CreateMapItem("1.so", 3000u, 4000u, 5000u);
281 thread.CreateMapItem("2.so", 10000u, 20000u, 30000u);
282
283 std::shared_ptr<DfxMap> outMap = nullptr;
284 EXPECT_EQ(thread.FindMapByFileInfo("", 0000u), nullptr);
285 EXPECT_EQ(thread.FindMapByFileInfo("0.so", 0000u), nullptr);
286
287 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 3000u), nullptr);
288 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 3000u), nullptr);
289 EXPECT_EQ(outMap->begin, 1000u);
290
291 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 4000u), nullptr);
292 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 4000u), nullptr);
293 EXPECT_EQ(outMap->begin, 1000u);
294
295 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 4999u), nullptr);
296 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 4999u), nullptr);
297 EXPECT_EQ(outMap->begin, 1000u);
298
299 EXPECT_EQ(thread.FindMapByFileInfo("0.so", 5000u), nullptr);
300 ASSERT_NE(outMap = thread.FindMapByFileInfo("1.so", 5000u), nullptr);
301 EXPECT_EQ(outMap->begin, 3000u);
302
303 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 50000u - 1), nullptr);
304 EXPECT_EQ(thread.FindMapByFileInfo("x.so", 50000u - 1), nullptr);
305 EXPECT_NE(thread.FindMapByFileInfo("2.so", 50000u - 1), nullptr);
306 EXPECT_EQ(thread.FindMapByFileInfo("2.so", 50000u), nullptr);
307 EXPECT_EQ(thread.FindMapByFileInfo("2.so", 50000u + 1), nullptr);
308 }
309
310 /**
311 * @tc.name: FindSymbolsFileByMap
312 * @tc.desc:
313 * @tc.type: FUNC
314 */
315 HWTEST_F(VirtualThreadTest, FindSymbolsFileByMap, TestSize.Level1)
316 {
317 std::vector<std::unique_ptr<SymbolsFile>> files;
318 SymbolFileStruct symbolFileStruct;
319 symbolFileStruct.filePath_ = "1.elf";
320 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
321 symbolFileStruct.filePath_ = "2.elf";
322 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
323 symbolFileStruct.filePath_ = "3.elf";
324 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
325 VirtualThread thread(getpid(), files);
326
327 std::shared_ptr<DfxMap> inMap = std::make_shared<DfxMap>();
328
329 inMap->name = "";
330 EXPECT_EQ(thread.FindSymbolsFileByMap(inMap), nullptr);
331
332 inMap->name = "1";
333 EXPECT_EQ(thread.FindSymbolsFileByMap(inMap), nullptr);
334
335 inMap->name = "1.elf";
336 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
337 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap->name.c_str());
338
339 inMap->name = "2.elf";
340 inMap->symbolFileIndex = -1;
341 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
342 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap->name.c_str());
343
344 inMap->name = "3.elf";
345 inMap->symbolFileIndex = -1;
346 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
347 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap->name.c_str());
348 }
349
350 /**
351 * @tc.name: ReadRoMemory
352 * @tc.desc:
353 * @tc.type: FUNC
354 */
355 HWTEST_F(VirtualThreadTest, ReadRoMemory, TestSize.Level1)
356 {
357 std::vector<std::unique_ptr<SymbolsFile>> symbolsFiles;
358 VirtualThread thread(getpid(), symbolsFiles);
359 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(TEST_FILE_ELF_FULL_PATH.c_str(), "rb"),
360 fclose);
361 if (fp) {
362 struct stat sb = {};
363 if (fstat(fileno(fp.get()), &sb) == -1) {
364 HLOGE("unable to check the file size");
365 } else {
366 HLOGV("file stat size %" PRIu64 "", sb.st_size);
367 }
368
369 thread.CreateMapItem(TEST_FILE_ELF_FULL_PATH, 0u, sb.st_size, 0u);
370 ASSERT_EQ(thread.GetMaps().size(), 1u);
371
372 std::unique_ptr<SymbolsFile> symbolsFile =
373 SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, TEST_FILE_ELF_FULL_PATH);
374 ASSERT_NE(symbolsFile, nullptr);
375 ASSERT_EQ(symbolsFile->LoadSymbols(), true);
376
377 // add to symbols list
378 symbolsFiles.emplace_back(std::move(symbolsFile));
379
380 uint8_t freadByte = '\0';
381 uint8_t readRoByte = '\0';
382 uint64_t addr = 0x0;
383
384 // first byte
385 ASSERT_EQ(fread(&freadByte, 1, 1, fp.get()), 1u);
386
387 auto map = thread.FindMapByAddr(addr);
388 ASSERT_EQ(map != nullptr, true);
389 if (HasFailure()) {
390 printf("map: %s\n", thread.GetMaps().at(0)->ToString().c_str());
391 }
392
393 EXPECT_NE(thread.FindSymbolsFileByMap(map), nullptr);
394 if (HasFailure()) {
395 printf("symbols: %s\n", thread.symbolsFiles_.front().get()->filePath_.c_str());
396 }
397
398 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), true);
399 ASSERT_EQ(freadByte, readRoByte);
400
401 while (fread(&freadByte, 1, 1, fp.get()) == 1u) {
402 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), true);
403 ASSERT_EQ(freadByte, readRoByte);
404 }
405
406 // EOF , out of file size should return 0
407 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), false);
408 thread.ParseServiceMap(TEST_FILE_ELF_FULL_PATH);
409 thread.ParseDevhostMap(getpid());
410 }
411 }
412 } // namespace HiPerf
413 } // namespace Developtools
414 } // namespace OHOS
415