* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "virtual_runtime_test.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <link.h>
#include <random>
#include <sys/mman.h>
#include <hilog/log.h>
#include "mingw_adapter.h"
#include "symbols_file_test.h"
using namespace testing::ext;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
class VirtualRuntimeTest : public testing::Test {
public:
static void SetUpTestCase(void);
static void TearDownTestCase(void);
void SetUp();
void TearDown();
const std::string TEST_LOG_MESSAGE = "<HELLO_TEST_LOG_MESSAGE>";
void LogLevelTest(std::vector<std::string> args, DebugLevel level);
std::default_random_engine rnd_;
std::unique_ptr<VirtualRuntime> runtime_;
bool RecordCallBack(PerfEventRecord& record);
size_t callbackCount_ = 0;
void PrepareKernelSymbol();
void PrepareUserSymbol();
};
void VirtualRuntimeTest::SetUpTestCase()
{
DebugLogger::GetInstance()->OpenLog(DEFAULT_UT_LOG_DIR + "VirtualRuntimeTest.txt");
}
void VirtualRuntimeTest::TearDownTestCase()
{
DebugLogger::GetInstance()->RestoreLog();
}
void VirtualRuntimeTest::SetUp()
{
runtime_ = std::make_unique<VirtualRuntime>();
callbackCount_ = 0;
}
void VirtualRuntimeTest::TearDown()
{
runtime_.reset();
}
bool VirtualRuntimeTest::RecordCallBack(PerfEventRecord& record)
{
callbackCount_++;
printf("callbackCount_ %zu: type %d\n", callbackCount_, record.GetType());
return true;
}
* @tc.name: SetRecordMode
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, SetRecordMode, TestSize.Level1)
{
auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);
EXPECT_EQ(runtime_->threadManager_->recordCallBack_, nullptr);
runtime_->SetRecordMode(callBack);
EXPECT_NE(runtime_->threadManager_->recordCallBack_, nullptr);
}
* @tc.name: SetRecordMode
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UpdateFromRecord, TestSize.Level1)
{
HLOGD("Func2:%s", __FUNCTION__);
PerfRecordComm recordComm(false, -1, -2, "3");
PerfEventRecord &record = static_cast<PerfEventRecord &>(recordComm);
auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);
runtime_->SetRecordMode(callBack);
runtime_->UpdateFromRecord(record);
EXPECT_EQ(callbackCount_, 2u);
}
* @tc.name: UpdateKernelSymbols
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UpdateKernelSymbols, TestSize.Level1)
{
runtime_->UpdateKernelSymbols();
if (access("/sys/kernel/notes", F_OK) == 0) {
EXPECT_EQ(runtime_->symbolsFiles_.size(), 1u);
} else {
printf("cannot access /sys/kernel/notes\n");
}
}
* @tc.name: UpdateKernelModulesSymbols
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UpdateKernelModulesSymbols, TestSize.Level1)
{
runtime_->UpdateKernelModulesSpaceMaps();
runtime_->UpdateKernelModulesSymbols();
std::string modulesMap = ReadFileToString("/proc/modules");
size_t lines = std::count(modulesMap.begin(), modulesMap.end(), '\n');
std::set<std::string> modulesCount;
if (runtime_->mapManager_->kernelSpaceMemMaps_.size() == 0) {
lines = 0;
}
EXPECT_EQ(runtime_->mapManager_->kernelSpaceMemMaps_.size(), lines);
int hasBuildId = 0;
int noBuildId = 0;
for (const std::unique_ptr<SymbolsFile> &symbolsFile : runtime_->GetSymbolsFiles()) {
if (symbolsFile->GetBuildId().empty()) {
noBuildId++;
} else {
hasBuildId++;
}
}
printf("no BuildId: %d hasBuildId: %d\n", noBuildId, hasBuildId);
}
* @tc.name: SetSymbolsPaths
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, SetSymbolsPaths, TestSize.Level1)
{
std::vector<std::string> symbolsSearchPaths;
runtime_->SetSymbolsPaths(symbolsSearchPaths);
symbolsSearchPaths.clear();
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);
symbolsSearchPaths.clear();
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);
symbolsSearchPaths.clear();
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
symbolsSearchPaths.push_back(PATH_DATA_TEMP);
symbolsSearchPaths.push_back(PATH_NOT_EXISTS);
EXPECT_EQ(runtime_->SetSymbolsPaths(symbolsSearchPaths), true);
}
* @tc.name: GetSymbolsFiles
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, GetSymbolsFiles, TestSize.Level1)
{
EXPECT_EQ(runtime_->GetSymbolsFiles().size(), 0u);
runtime_->UpdateKernelSymbols();
if (access("/sys/kernel/notes", F_OK) == 0) {
EXPECT_EQ(runtime_->GetSymbolsFiles().size(), 1u);
} else {
printf("cannot access /sys/kernel/notes\n");
}
}
* @tc.name: SetCallStackExpend
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, SetCallStackExpend, TestSize.Level1)
{
runtime_->SetCallStackExpend(true);
EXPECT_EQ(runtime_->callStackProcessor_->callstackMergeLevel_, true);
runtime_->SetCallStackExpend(false);
EXPECT_EQ(runtime_->callStackProcessor_->callstackMergeLevel_, false);
}
* @tc.name: SetDisableUnwind
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, SetDisableUnwind, TestSize.Level1)
{
runtime_->SetDisableUnwind(true);
EXPECT_EQ(runtime_->callStackProcessor_->disableUnwind_, true);
runtime_->SetDisableUnwind(false);
EXPECT_EQ(runtime_->callStackProcessor_->disableUnwind_, false);
}
* @tc.name: ReadThreadName_Truncate_Substr
* @tc.desc: Validate truncation at '\0' using substr and removal of CR/LF
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, ReadThreadName_Truncate_Substr, TestSize.Level1)
{
std::string raw = "commName\0extra\r\n";
size_t nullPos = raw.find('\0');
std::string comm = raw;
if (nullPos != std::string::npos) {
comm.resize(nullPos);
}
comm.erase(std::remove(comm.begin(), comm.end(), '\r'), comm.end());
comm.erase(std::remove(comm.begin(), comm.end(), '\n'), comm.end());
EXPECT_EQ(comm, "commName");
}
namespace {
constexpr const pid_t testTid = 1;
constexpr const uint64_t testUserVaddr = 0x1000;
constexpr const uint64_t testKernelVaddr = testUserVaddr / 4;
constexpr const uint64_t testKernelLen = testUserVaddr / 2;
constexpr const uint64_t testUserMapBegin = 0x2000;
constexpr const uint64_t testUserMapLen = 0x4000;
}
void VirtualRuntimeTest::PrepareKernelSymbol()
{
std::string kernelSymbol = "kernel_symbol";
auto kernel = SymbolsFile::CreateSymbolsFile(SYMBOL_KERNEL_FILE);
kernel->filePath_ = kernelSymbol;
kernel->symbols_.emplace_back(testKernelVaddr, 1u, "first_kernel_func", kernel->filePath_);
kernel->symbols_.emplace_back(testKernelVaddr + 1u, 1u, "second_kernel_func",
kernel->filePath_);
runtime_->symbolsFiles_.emplace_back(std::move(kernel));
auto &kernelMap = runtime_->mapManager_->kernelSpaceMemMaps_.emplace_back();
kernelMap.name = kernelSymbol;
kernelMap.begin = 0;
kernelMap.end = 0 + testKernelLen;
kernelMap.offset = 0;
}
void VirtualRuntimeTest::PrepareUserSymbol()
{
std::string userSymbol = "user_symbol";
auto user = SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE);
user->filePath_ = userSymbol;
user->symbols_.emplace_back(testUserVaddr, 1u, "first_user_func", user->filePath_);
user->symbols_.emplace_back(testUserVaddr + 1u, 1u, "second_user_func", user->filePath_);
user->textExecVaddrFileOffset_ = testUserVaddr;
user->textExecVaddr_ = testUserVaddr;
user->debugInfoLoadResult_ = true;
runtime_->symbolsFiles_.emplace_back(std::move(user));
VirtualThread &thread = runtime_->GetThread(testTid, testTid);
thread.CreateMapItem(userSymbol, testUserMapBegin, testUserMapLen, 0);
}
* @tc.name: GetSymbol
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, GetSymbol, TestSize.Level0)
{
DfxSymbol symbol;
PrepareKernelSymbol();
PrepareUserSymbol();
ScopeDebugLevel tempLogLevel(LEVEL_MUCH);
symbol = runtime_->GetSymbol(0u, testTid, testTid);
EXPECT_EQ(symbol.IsValid(), false);
symbol = runtime_->GetSymbol(testKernelVaddr, testTid, testTid);
EXPECT_EQ(symbol.IsValid(), true);
EXPECT_EQ(symbol.funcVaddr_, testKernelVaddr);
EXPECT_STREQ(symbol.name_.data(), "first_kernel_func");
symbol = runtime_->GetSymbol(testUserVaddr + testUserMapBegin, testTid, testTid);
EXPECT_EQ(symbol.IsValid(), true);
EXPECT_EQ(symbol.funcVaddr_, testUserVaddr);
EXPECT_STREQ(symbol.name_.data(), "first_user_func");
}
* @tc.name: GetThread
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, GetThread, TestSize.Level1)
{
runtime_->GetThread(1, 2);
runtime_->GetThread(3, 4);
runtime_->GetThread(5, 6);
EXPECT_EQ(runtime_->GetThreads().size(), 7u);
if (HasFailure()) {
for (auto &pair : runtime_->GetThreads()) {
printf("pid %d tid %d\n", pair.second.pid_, pair.second.tid_);
}
}
}
* @tc.name: UpdateFromPerfData
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UpdateFromPerfData, TestSize.Level1)
{
std::vector<SymbolFileStruct> symbolFileStructs;
SymbolFileStruct &symbolFileStruct = symbolFileStructs.emplace_back();
symbolFileStruct.filePath_ = "a";
symbolFileStruct.textExecVaddr_ = testUserVaddr;
symbolFileStruct.textExecVaddrFileOffset_ = testUserVaddr;
symbolFileStruct.buildId_ = "b";
symbolFileStruct.symbolStructs_.emplace_back(testUserVaddr, 1u, "first_user_func");
symbolFileStruct.symbolStructs_.emplace_back(testUserVaddr + 1u, 1u, "second_user_func");
ASSERT_EQ(runtime_->SetSymbolsPaths({"/data/local/tmp"}), true);
runtime_->UpdateFromPerfData(symbolFileStructs);
ASSERT_EQ(runtime_->GetSymbolsFiles().size(), 1u);
ASSERT_STREQ(runtime_->GetSymbolsFiles().front()->filePath_.c_str(), "a");
ASSERT_STREQ(runtime_->GetSymbolsFiles().front()->GetBuildId().c_str(), "b");
ASSERT_EQ(runtime_->GetSymbolsFiles().front()->GetSymbols().size(), 2u);
}
* @tc.name: UnwindFromRecord
* @tc.desc:
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UnwindFromRecord, TestSize.Level0)
{
auto &symbolsFile = runtime_->symbolsFiles_.emplace_back(
SymbolsFile::CreateSymbolsFile(SYMBOL_ELF_FILE, TEST_DWARF_ELF));
ASSERT_EQ(symbolsFile->setSymbolsFilePath(PATH_RESOURCE_TEST_DWARF_DATA), true);
ASSERT_EQ(symbolsFile->LoadSymbols(nullptr, TEST_DWARF_ELF), true);
symbolsFile->filePath_ = TEST_DWARF_ELF;
VirtualThread &thread = runtime_->GetThread(TEST_DWARF_RECORD_PID, TEST_DWARF_RECORD_TID);
MakeMaps(thread);
std::vector<uint8_t> data;
LoadFromFile(PATH_RESOURCE_TEST_DWARF_DATA + TEST_DWARF_RECORD, data);
ASSERT_NE(data.size(), 0u);
perf_event_attr attr;
if (memset_s(&attr, sizeof(perf_event_attr), 0, sizeof(perf_event_attr)) != 0) {
printf("memset_s perf_event_attr failed");
}
attr.sample_type = TEST_RECORD_SAMPLE_TYPE;
attr.sample_regs_user = TEST_DWARF_RECORD_REGS_USER;
PerfRecordSample sample;
sample.Init(data.data(), attr);
sample.DumpLog("UnwindFromRecord");
ASSERT_EQ(sample.data_.stack_size, TEST_DWARF_RECORD_STACK_SIZE);
runtime_->UnwindFromRecord(sample);
ASSERT_LE(TEST_RECORD_CALLSTACK_IP_FUNC.size(), sample.callFrames_.size());
for (size_t i = 0; i < TEST_RECORD_CALLSTACK_IP_FUNC.size(); i++) {
EXPECT_EQ(TEST_RECORD_CALLSTACK_IP_FUNC[i].first, sample.callFrames_[i].funcOffset);
EXPECT_STREQ(TEST_RECORD_CALLSTACK_IP_FUNC[i].second.data(),
sample.callFrames_[i].funcName.data());
}
}
* @tc.name: Update
* @tc.desc: Test update maps and symbols
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, Update, TestSize.Level2)
{
EXPECT_EQ(runtime_->IsKernelThread(0), false);
runtime_->UpdateServiceSpaceMaps();
runtime_->UpdateDevhostSpaceMaps();
runtime_->UpdateServiceSymbols();
runtime_->UpdateDevhostSymbols();
}
* @tc.name: ClearSymbolCache
* @tc.desc: Test Clear Symbol Cache
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, ClearSymbolCache, TestSize.Level2)
{
VirtualRuntime runtime;
runtime.kernelSymbolLoader_->SetSymbolsPaths({"abc", "def"});
runtime.recordProcessor_->SetSymbolsPaths({"abc", "def"});
runtime.symbolsFiles_.emplace_back(SymbolsFile::CreateSymbolsFile(SYMBOL_KERNEL_FILE));
runtime.ClearSymbolCache();
EXPECT_EQ(runtime.threadManager_->GetThreads().size(), 0u);
EXPECT_EQ(runtime.mapManager_->GetKernelMaps().size(), 0u);
EXPECT_EQ(runtime.callStackProcessor_->GetUniStackTable()->size(), 0u);
EXPECT_EQ(runtime.symbolsFiles_.size(), 0u);
EXPECT_EQ(runtime.kernelSymbolLoader_->GetSymbolsPaths().size(), 0u);
EXPECT_EQ(runtime.recordProcessor_->symbolsPaths_.size(), 0u);
}
* @tc.name: UpdateHapSymbolsWithNull
* @tc.desc: Test UpdateHapSymbols with null
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, UpdateHapSymbolsWithNull, TestSize.Level2)
{
EXPECT_FALSE(runtime_->recordProcessor_->UpdateHapSymbols(nullptr));
}
HWTEST_F(VirtualRuntimeTest, UpdateProcessSymbols, TestSize.Level2)
{
std::vector<std::unique_ptr<SymbolsFile>> files;
pid_t pid = getpid();
VirtualThread thread(pid, files);
thread.ParseMap();
EXPECT_EQ(runtime_->IsKernelThread(0), false);
auto callBack = std::bind(&VirtualRuntimeTest::RecordCallBack, this, std::placeholders::_1);
runtime_->SetRecordMode(callBack);
runtime_->recordProcessor_->UpdateProcessSymbols(thread, pid);
}
* @tc.name: CheckGetElfByMap
* @tc.desc: Test case for GetElfByMap to get elf
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, CheckGetElfByMap, TestSize.Level2)
{
std::vector<std::unique_ptr<SymbolsFile>> files;
VirtualThread thread(getpid(), files);
thread.ParseMap();
auto& maps = thread.GetMaps();
for (const auto& map : maps) {
if (OHOS::HiviewDFX::DfxMaps::IsLegalMapItem(map->name)) {
EXPECT_NE(GetElfByMap(map), nullptr);
break;
}
}
}
* @tc.name: CheckGetElfByInvalidMap
* @tc.desc: Test case for GetElfByMap to get elf fail
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, CheckGetElfByInvalidMap, TestSize.Level2)
{
std::string filename = "";
uint64_t begin = rnd_();
uint64_t len = rnd_();
uint64_t offset = rnd_();
uint32_t prot = rnd_();
std::shared_ptr<DfxMap> map = std::make_shared<DfxMap>(begin, begin + len, offset, prot, filename);
EXPECT_EQ(GetElfByMap(map), nullptr);
}
* @tc.name: TestSetSmoFlag
* @tc.desc: Test SetSmoFlag function
* @tc.type: FUNC
*/
HWTEST_F(VirtualRuntimeTest, TestSetSmoFlag, TestSize.Level2)
{
VirtualRuntime runtime;
runtime.SetSmoFlag(true);
EXPECT_TRUE(runtime.GetRuntimeContext().smoFlag);
runtime.SetSmoFlag(false);
EXPECT_FALSE(runtime.GetRuntimeContext().smoFlag);
}
}
}
}