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 <gmock/gmock.h>
19 #include <gtest/gtest.h>
20 #include <link.h>
21 #include <random>
22 #include <sys/mman.h>
23
24 #include <hilog/log.h>
25
26 #include "symbols_file_test.h"
27
28 using namespace testing::ext;
29 using namespace std;
30 using namespace OHOS::HiviewDFX;
31 namespace OHOS {
32 namespace Developtools {
33 namespace HiPerf {
34 class VirtualThreadTest : public testing::Test {
35 public:
36 static void SetUpTestCase(void);
37 static void TearDownTestCase(void);
38 void SetUp();
39 void TearDown();
40 const std::string TEST_LOG_MESSAGE = "<HELLO_TEST_LOG_MESSAGE>";
41 void LogLevelTest(std::vector<std::string> args, DebugLevel level);
42 default_random_engine rnd_;
43 static std::string myFilePath_;
GetFullPath()44 static std::string GetFullPath()
45 {
46 char path[PATH_MAX];
47 int i = readlink("/proc/self/exe", path, sizeof(path));
48 path[i] = '\0';
49 return path;
50 }
51 static int PhdrCallBack(struct dl_phdr_info *info, size_t size, void *data);
52 static void MakeMapsFromDlpi(const std::string &dlpiName, const struct dl_phdr_info *info,
53 std::vector<MemMapItem> &phdrMaps);
54 };
55
56 std::string VirtualThreadTest::myFilePath_;
57
SetUpTestCase()58 void VirtualThreadTest::SetUpTestCase()
59 {
60 DebugLogger::GetInstance()->OpenLog(DEFAULT_UT_LOG_DIR + "VirtualThreadTest.txt");
61 }
62
TearDownTestCase()63 void VirtualThreadTest::TearDownTestCase()
64 {
65 DebugLogger::GetInstance()->RestoreLog();
66 }
67
SetUp()68 void VirtualThreadTest::SetUp() {}
69
TearDown()70 void VirtualThreadTest::TearDown() {}
71
72 /**
73 * @tc.name: MemMapItem Same
74 * @tc.desc:
75 * @tc.type: FUNC
76 */
77 HWTEST_F(VirtualThreadTest, MemMapItemSame, TestSize.Level1)
78 {
79 MemMapItem a;
80 EXPECT_EQ(a == "a", false);
81 EXPECT_EQ(a == "b", false);
82 EXPECT_EQ(a == "", true);
83
84 a.name_ = "a";
85 EXPECT_EQ(a == "a", true);
86 EXPECT_EQ(a == "b", false);
87
88 a.name_ = "a1";
89 EXPECT_EQ(a == "a", false);
90 EXPECT_EQ(a == "b", false);
91
92 a.name_ = "1";
93 EXPECT_EQ(a == "a", false);
94 EXPECT_EQ(a == "b", false);
95
96 a.name_ = "";
97 EXPECT_EQ(a == "a", false);
98 EXPECT_EQ(a == "b", false);
99 }
100
101 /**
102 * @tc.name: MemMapItem FileOffsetFromAddr
103 * @tc.desc:
104 * @tc.type: FUNC
105 */
106 HWTEST_F(VirtualThreadTest, MemMapItemFileOffsetFromAddr, TestSize.Level1)
107 {
108 MemMapItem a;
109
110 a.begin_ = 100;
111 a.pageoffset_ = 200;
112 EXPECT_EQ(a.FileOffsetFromAddr(0), 100u);
113 EXPECT_EQ(a.FileOffsetFromAddr(10), 110u);
114 EXPECT_EQ(a.FileOffsetFromAddr(100), 200u);
115 EXPECT_EQ(a.FileOffsetFromAddr(1000), 1100u);
116
117 a.begin_ = 300;
118 a.pageoffset_ = 200;
119 EXPECT_EQ(a.FileOffsetFromAddr(0), std::numeric_limits<uint64_t>::max() - 100u + 1u);
120 EXPECT_EQ(a.FileOffsetFromAddr(10), std::numeric_limits<uint64_t>::max() - 90u + 1u);
121 EXPECT_EQ(a.FileOffsetFromAddr(100), 0u);
122 EXPECT_EQ(a.FileOffsetFromAddr(1000), 900u);
123 }
124
125 /**
126 * @tc.name: MemMapItem ToString
127 * @tc.desc:
128 * @tc.type: FUNC
129 */
130 HWTEST_F(VirtualThreadTest, MemMapItemFileToString, TestSize.Level1)
131 {
132 MemMapItem a;
133
134 a.begin_ = 0x100;
135 a.pageoffset_ = 0x200;
136 EXPECT_EQ(a.ToString().find("100") != std::string::npos, true);
137 EXPECT_EQ(a.ToString().find("200") != std::string::npos, true);
138 EXPECT_EQ(a.ToString().find("300") != std::string::npos, false);
139
140 a.begin_ = 0x300;
141 a.pageoffset_ = 0x200;
142 EXPECT_EQ(a.ToString().find("100") != std::string::npos, false);
143 EXPECT_EQ(a.ToString().find("200") != std::string::npos, true);
144 EXPECT_EQ(a.ToString().find("300") != std::string::npos, true);
145 }
146
MakeMapsFromDlpi(const std::string & dlpiName,const struct dl_phdr_info * info,std::vector<MemMapItem> & phdrMaps)147 void VirtualThreadTest::MakeMapsFromDlpi(const std::string &dlpiName,
148 const struct dl_phdr_info *info,
149 std::vector<MemMapItem> &phdrMaps)
150 {
151 if (info == nullptr) {
152 HLOGE("param is null");
153 return;
154 }
155 int phdrType;
156 HLOGV("Name: \"%s\" (%d segments)", dlpiName.c_str(), info->dlpi_phnum);
157 for (int i = 0; i < info->dlpi_phnum; i++) {
158 phdrType = info->dlpi_phdr[i].p_type;
159 if (phdrType != PT_LOAD) {
160 continue;
161 }
162 HLOGV(" %2d: [%14p; memsz:%7jx] align %jx flags: %#jx [(%#x)]", i,
163 (void *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr),
164 (uintmax_t)info->dlpi_phdr[i].p_memsz, (uintmax_t)info->dlpi_phdr[i].p_align,
165 (uintmax_t)info->dlpi_phdr[i].p_flags, phdrType);
166
167 MemMapItem &item = phdrMaps.emplace_back();
168 item.begin_ = (info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
169 item.end_ = RoundUp(item.begin_ + info->dlpi_phdr[i].p_memsz, info->dlpi_phdr[i].p_align);
170 item.pageoffset_ = info->dlpi_phdr[i].p_offset;
171 item.name_ = dlpiName;
172 }
173 for (const MemMapItem &item : phdrMaps) {
174 HLOGV("%s", item.ToString().c_str());
175 }
176 EXPECT_NE(phdrMaps.size(), 0u);
177 }
178
PhdrCallBack(struct dl_phdr_info * info,size_t size,void * data)179 int VirtualThreadTest::PhdrCallBack(struct dl_phdr_info *info, size_t size, void *data)
180 {
181 if (info == nullptr || data == nullptr) {
182 HLOGE("param is null");
183 return -1;
184 }
185 VirtualThread *thread = static_cast<VirtualThread *>(data);
186 std::vector<MemMapItem> phdrMaps {};
187 std::vector<const MemMapItem *> memMaps {};
188 static std::string myFilePath = GetFullPath();
189 EXPECT_NE(thread->GetMaps().size(), 0u);
190 std::string dlpiName = info->dlpi_name;
191 if (StringStartsWith(dlpiName, "./") and !StringEndsWith(dlpiName, ".so")) {
192 dlpiName = myFilePath;
193 }
194 if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
195 // dont care empty pt
196 return 0;
197 } else {
198 MakeMapsFromDlpi(dlpiName, info, phdrMaps);
199 }
200
201 for (const MemMapItem &item : thread->GetMaps()) {
202 if (item.name_ == dlpiName) {
203 HLOGV("%s", item.ToString().c_str());
204 memMaps.emplace_back(&item);
205 }
206 }
207
208 if (memMaps.size() == 0u) {
209 // show all the items if not any match mapitem found
210 for (const MemMapItem &item : thread->GetMaps()) {
211 HLOGV("%s", item.ToString().c_str());
212 }
213 return 0;
214 }
215
216 if (memMaps.size() == phdrMaps.size()) {
217 EXPECT_EQ(memMaps.front()->begin_, phdrMaps.front().begin_);
218 EXPECT_EQ(memMaps.front()->pageoffset_, phdrMaps.front().pageoffset_);
219 }
220 return 0;
221 }
222
223 /**
224 * @tc.name: ParseMap
225 * @tc.desc:
226 * @tc.type: FUNC
227 */
228 HWTEST_F(VirtualThreadTest, ParseMap, TestSize.Level1)
229 {
230 std::vector<std::unique_ptr<SymbolsFile>> files;
231 VirtualThread thread(getpid(), files);
232 thread.ParseMap();
233
234 dl_iterate_phdr(PhdrCallBack, static_cast<void *>(&thread));
235
236 for (const MemMapItem &item : thread.GetMaps()) {
237 EXPECT_EQ(item.name_.empty(), false);
238 EXPECT_STRNE(item.name_.c_str(), MMAP_NAME_HEAP.c_str());
239 EXPECT_STRNE(item.name_.c_str(), MMAP_NAME_ANON.c_str());
240 }
241 }
242
243 /**
244 * @tc.name: CreateMapItem
245 * @tc.desc:
246 * @tc.type: FUNC
247 */
248 HWTEST_F(VirtualThreadTest, CreateMapItem, TestSize.Level1)
249 {
250 std::vector<std::unique_ptr<SymbolsFile>> files;
251 VirtualThread thread(getpid(), files);
252 thread.CreateMapItem("0.so", 1000, 2000, 3000);
253 thread.CreateMapItem("1.so", 3000, 4000, 5000);
254 thread.CreateMapItem("2.so", 10000, 20000, 30000);
255
256 const std::vector<MemMapItem> &maps = thread.GetMaps();
257
258 EXPECT_EQ(maps.size(), 3u);
259 EXPECT_EQ(maps.at(0).begin_, 1000u);
260 EXPECT_EQ(maps.at(1).begin_, 3000u);
261 EXPECT_EQ(maps.at(2).begin_, 10000u);
262
263 EXPECT_EQ(maps.at(0).end_, 1000u + 2000u);
264 EXPECT_EQ(maps.at(1).end_, 3000u + 4000u);
265 EXPECT_EQ(maps.at(2).end_, 10000u + 20000u);
266
267 EXPECT_EQ(maps.at(0).pageoffset_, 3000u);
268 EXPECT_EQ(maps.at(1).pageoffset_, 5000u);
269 EXPECT_EQ(maps.at(2).pageoffset_, 30000u);
270
271 EXPECT_STREQ(maps.at(0).name_.c_str(), "0.so");
272 EXPECT_STREQ(maps.at(1).name_.c_str(), "1.so");
273 EXPECT_STREQ(maps.at(2).name_.c_str(), "2.so");
274 }
275
276 /**
277 * @tc.name: InheritMaps
278 * @tc.desc:
279 * @tc.type: FUNC
280 */
281 HWTEST_F(VirtualThreadTest, InheritMaps, TestSize.Level1)
282 {
283 std::vector<std::unique_ptr<SymbolsFile>> files;
284 VirtualThread thread(getpid(), files);
285 thread.ParseMap();
286
287 VirtualThread thread2(getpid(), gettid() + 1u, thread, files);
288
289 const std::vector<MemMapItem> &maps = thread.GetMaps();
290 const std::vector<MemMapItem> &maps2 = thread2.GetMaps();
291
292 ASSERT_EQ(maps.size(), maps2.size());
293 for (size_t i = 0; i < maps.size(); i++) {
294 EXPECT_STREQ(maps[i].ToString().c_str(), maps2[i].ToString().c_str());
295 }
296
297 size_t oldSize = thread.GetMaps().size();
298 thread.CreateMapItem("new", 0u, 1u, 2u);
299 size_t newSize = thread.GetMaps().size();
300 ASSERT_EQ(oldSize, newSize);
301 ASSERT_EQ(maps.size(), maps2.size());
302 for (size_t i = 0; i < maps.size(); i++) {
303 EXPECT_STREQ(maps[i].ToString().c_str(), maps2[i].ToString().c_str());
304 }
305 }
306
307 /**
308 * @tc.name: FindMapByAddr
309 * @tc.desc:
310 * @tc.type: FUNC
311 */
312 HWTEST_F(VirtualThreadTest, FindMapByAddr, TestSize.Level1)
313 {
314 std::vector<std::unique_ptr<SymbolsFile>> files;
315 VirtualThread thread(getpid(), files);
316
317 thread.CreateMapItem("0.so", 1000u, 2000u, 3000u);
318 thread.CreateMapItem("1.so", 3000u, 4000u, 5000u);
319 thread.CreateMapItem("2.so", 10000u, 20000u, 30000u);
320
321 const MemMapItem *outMap;
322 outMap = thread.FindMapByAddr(0000u);
323 EXPECT_EQ(outMap != nullptr, false);
324
325 outMap = thread.FindMapByAddr(1000u);
326 ASSERT_EQ(outMap != nullptr, true);
327 EXPECT_EQ(outMap->begin_, 1000u);
328
329 outMap = thread.FindMapByAddr(2000u);
330 ASSERT_EQ(outMap != nullptr, true);
331 EXPECT_EQ(outMap->begin_, 1000u);
332
333 outMap = thread.FindMapByAddr(2999u);
334 ASSERT_EQ(outMap != nullptr, true);
335 EXPECT_EQ(outMap->begin_, 1000u);
336
337 outMap = thread.FindMapByAddr(3000u);
338 ASSERT_EQ(outMap != nullptr, true);
339 EXPECT_EQ(outMap->begin_, 3000u);
340
341 EXPECT_EQ(thread.FindMapByAddr(30000u - 1u) != nullptr, true);
342 EXPECT_EQ(thread.FindMapByAddr(30000u) != nullptr, false);
343 EXPECT_EQ(thread.FindMapByAddr(30000u + 1u) != nullptr, false);
344 }
345
346 /**
347 * @tc.name: FindMapByFileInfo
348 * @tc.desc:
349 * @tc.type: FUNC
350 */
351 HWTEST_F(VirtualThreadTest, FindMapByFileInfo, TestSize.Level1)
352 {
353 std::vector<std::unique_ptr<SymbolsFile>> files;
354 VirtualThread thread(getpid(), files);
355
356 thread.CreateMapItem("0.so", 1000u, 2000u, 3000u);
357 thread.CreateMapItem("1.so", 3000u, 4000u, 5000u);
358 thread.CreateMapItem("2.so", 10000u, 20000u, 30000u);
359
360 const MemMapItem *outMap;
361 EXPECT_EQ(thread.FindMapByFileInfo("", 0000u), nullptr);
362 EXPECT_EQ(thread.FindMapByFileInfo("0.so", 0000u), nullptr);
363
364 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 3000u), nullptr);
365 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 3000u), nullptr);
366 EXPECT_EQ(outMap->begin_, 1000u);
367
368 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 4000u), nullptr);
369 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 4000u), nullptr);
370 EXPECT_EQ(outMap->begin_, 1000u);
371
372 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 4999u), nullptr);
373 ASSERT_NE(outMap = thread.FindMapByFileInfo("0.so", 4999u), nullptr);
374 EXPECT_EQ(outMap->begin_, 1000u);
375
376 EXPECT_EQ(thread.FindMapByFileInfo("0.so", 5000u), nullptr);
377 ASSERT_NE(outMap = thread.FindMapByFileInfo("1.so", 5000u), nullptr);
378 EXPECT_EQ(outMap->begin_, 3000u);
379
380 EXPECT_EQ(thread.FindMapByFileInfo("1.so", 50000u - 1), nullptr);
381 EXPECT_EQ(thread.FindMapByFileInfo("x.so", 50000u - 1), nullptr);
382 EXPECT_NE(thread.FindMapByFileInfo("2.so", 50000u - 1), nullptr);
383 EXPECT_EQ(thread.FindMapByFileInfo("2.so", 50000u), nullptr);
384 EXPECT_EQ(thread.FindMapByFileInfo("2.so", 50000u + 1), nullptr);
385 }
386
387 /**
388 * @tc.name: FindSymbolsFileByMap
389 * @tc.desc:
390 * @tc.type: FUNC
391 */
392 HWTEST_F(VirtualThreadTest, FindSymbolsFileByMap, TestSize.Level1)
393 {
394 std::vector<std::unique_ptr<SymbolsFile>> files;
395 SymbolFileStruct symbolFileStruct;
396 symbolFileStruct.filePath_ = "1.elf";
397 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
398 symbolFileStruct.filePath_ = "2.elf";
399 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
400 symbolFileStruct.filePath_ = "3.elf";
401 files.emplace_back(SymbolsFile::LoadSymbolsFromSaved(symbolFileStruct));
402 VirtualThread thread(getpid(), files);
403
404 MemMapItem inMap;
405
406 inMap.name_ = "";
407 EXPECT_EQ(thread.FindSymbolsFileByMap(inMap), nullptr);
408
409 inMap.name_ = "1";
410 EXPECT_EQ(thread.FindSymbolsFileByMap(inMap), nullptr);
411
412 inMap.name_ = "1.elf";
413 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
414 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap.name_.c_str());
415
416 inMap.name_ = "2.elf";
417 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
418 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap.name_.c_str());
419
420 inMap.name_ = "3.elf";
421 ASSERT_NE(thread.FindSymbolsFileByMap(inMap), nullptr);
422 EXPECT_STREQ(thread.FindSymbolsFileByMap(inMap)->filePath_.c_str(), inMap.name_.c_str());
423 }
424
425 /**
426 * @tc.name: ReadRoMemory
427 * @tc.desc:
428 * @tc.type: FUNC
429 */
430 HWTEST_F(VirtualThreadTest, ReadRoMemory, TestSize.Level1)
431 {
432 std::vector<std::unique_ptr<SymbolsFile>> symbolsFiles;
433 VirtualThread thread(getpid(), symbolsFiles);
434 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(TEST_FILE_ELF_FULL_PATH.c_str(), "rb"),
435 fclose);
436 if (fp) {
437 struct stat sb = {};
438 if (fstat(fileno(fp.get()), &sb) == -1) {
439 HLOGE("unable to check the file size");
440 } else {
441 HLOGV("file stat size %" PRIu64 "", sb.st_size);
442 }
443
444 thread.CreateMapItem(TEST_FILE_ELF_FULL_PATH, 0u, sb.st_size, 0u);
445 ASSERT_EQ(thread.GetMaps().size(), 1u);
446
447 std::unique_ptr<SymbolsFile> symbolsFile =
448 SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, TEST_FILE_ELF_FULL_PATH);
449 ASSERT_NE(symbolsFile, nullptr);
450 ASSERT_EQ(symbolsFile->LoadSymbols(), true);
451
452 // add to symbols list
453 symbolsFiles.emplace_back(std::move(symbolsFile));
454
455 uint8_t freadByte = '\0';
456 uint8_t readRoByte = '\0';
457 uint64_t addr = 0x0;
458
459 // first byte
460 ASSERT_EQ(fread(&freadByte, 1, 1, fp.get()), 1u);
461
462 const MemMapItem *map = thread.FindMapByAddr(addr);
463 ASSERT_EQ(map != nullptr, true);
464 if (HasFailure()) {
465 printf("map: %s\n", thread.GetMaps().at(0).ToString().c_str());
466 }
467
468 EXPECT_NE(thread.FindSymbolsFileByMap(*map), nullptr);
469 if (HasFailure()) {
470 printf("symbols: %s\n", thread.symbolsFiles_.front().get()->filePath_.c_str());
471 }
472
473 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), true);
474 ASSERT_EQ(freadByte, readRoByte);
475
476 while (fread(&freadByte, 1, 1, fp.get()) == 1u) {
477 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), true);
478 ASSERT_EQ(freadByte, readRoByte);
479 }
480
481 // EOF , out of file size should return 0
482 ASSERT_EQ(thread.ReadRoMemory(addr++, &readRoByte, 1u), false);
483 }
484 }
485 } // namespace HiPerf
486 } // namespace Developtools
487 } // namespace OHOS
488