/*
 * Copyright (c) 2023-2024 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 <gtest/gtest.h>

#include <cstdio>
#include <malloc.h>
#include <map>
#include <securec.h>
#include <thread>
#include <unistd.h>

#include "dfx_config.h"
#include "dfx_frame_formatter.h"
#include "dfx_ptrace.h"
#include "dfx_regs_get.h"
#include "dfx_test_util.h"
#include "elapsed_time.h"
#include "unwinder.h"

#if defined(__x86_64__)
#include <unwind.h> // GCC's internal unwinder, part of libgcc
#endif

using namespace testing;
using namespace testing::ext;

namespace OHOS {
namespace HiviewDFX {
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_TAG "DfxUnwinderTest"
#define LOG_DOMAIN 0xD002D11
#define TIME_SLEEP 3

class UnwinderTest : public testing::Test {
public:
    static void SetUpTestCase() {}
    static void TearDownTestCase() {}
    void SetUp() {}
    void TearDown() {}

    std::map<int, std::shared_ptr<Unwinder>> unwinders_;
    const size_t skipFrameNum = 2;
};

/**
 * @tc.name: GetStackRangeTest001
 * @tc.desc: test unwinder GetStackRange interface in pid == tid
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, GetStackRangeTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "GetStackRangeTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1;
    uintptr_t stackTop = static_cast<uintptr_t>(-1);
    GTEST_LOG_(INFO) << "when pid == tid and maps_ != null, GetStackRange(stackBottom, stackTop) is true";
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    // When the param is less than -1, maps_ = null when method Unwinder is constructed
    auto unwinderNegative = std::make_shared<Unwinder>(-2);
    GTEST_LOG_(INFO) << "when pid == tid and maps_ == null, GetStackRange(stackBottom, stackTop) is false";
    ASSERT_TRUE(unwinderNegative->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "GetStackRangeTest001: end.";
}

/**
 * @tc.name: GetStackRangeTest002
 * @tc.desc: test unwinder GetStackRange interface in pid != tid
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, GetStackRangeTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "GetStackRangeTest002: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1;
    uintptr_t stackTop = static_cast<uintptr_t>(-1);
    bool result = false;
    GTEST_LOG_(INFO) << "Run the function with thread will get pid != tid, "
                        "GetStackRange(stackBottom, stackTop) is true";
    std::thread th([unwinder, &stackBottom, &stackTop, &result] {
        result = unwinder->GetStackRange(stackBottom, stackTop);
    });
    if (th.joinable()) {
        th.join();
    }
    ASSERT_TRUE(result);
    GTEST_LOG_(INFO) << "GetStackRangeTest002: end.";
}

/**
 * @tc.name: UnwinderLocalTest001
 * @tc.desc: test unwinder local unwind
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderLocalTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderLocalTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    ElapsedTime counter;
    MAYBE_UNUSED bool unwRet = unwinder->UnwindLocal();
    unwinder->EnableMethodIdLocal(true);
    time_t elapsed1 = counter.Elapsed();
    EXPECT_EQ(true, unwRet) << "UnwinderLocalTest001: Unwind:" << unwRet;
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);
    time_t elapsed2 = counter.Elapsed();
    GTEST_LOG_(INFO) << "Elapsed-: " << elapsed1 << "\tElapsed+: " << elapsed2;
    GTEST_LOG_(INFO) << "UnwinderLocalTest001: frames:\n" << Unwinder::GetFramesStr(frames);
    unwRet = unwinder->UnwindLocal(false, false, DEFAULT_MAX_FRAME_NUM, skipFrameNum);
    EXPECT_EQ(true, unwRet) << "UnwinderLocalTest001: Unwind:" << unwRet;
    auto frames2 = unwinder->GetFrames();
    ASSERT_GT(frames.size(), frames2.size());
    GTEST_LOG_(INFO) << "UnwinderLocalTest001: frames2:\n" << Unwinder::GetFramesStr(frames2);
    GTEST_LOG_(INFO) << "UnwinderLocalTest001: end.";
}

/**
 * @tc.name: UnwinderLocalTest002
 * @tc.desc: test unwinder local unwind n counts
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderLocalTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderLocalTest002: start.";
    unwinders_.clear();
    std::shared_ptr<Unwinder> unwinder = nullptr;
    pid_t pid = getpid();
    GTEST_LOG_(INFO) << "pid: " << pid;
    for (int i = 0; i < 10; ++i) {
        auto it = unwinders_.find(pid);
        if (it != unwinders_.end()) {
            unwinder = it->second;
        } else {
            unwinder = std::make_shared<Unwinder>();
        }
        ElapsedTime counter;
        MAYBE_UNUSED bool unwRet = unwinder->UnwindLocal();
        time_t elapsed1 = counter.Elapsed();
        EXPECT_EQ(true, unwRet) << "UnwinderLocalTest002: Unwind:" << unwRet;
        auto frames = unwinder->GetFrames();
        ASSERT_GT(frames.size(), 1);
        time_t elapsed2 = counter.Elapsed();
        GTEST_LOG_(INFO) << "Elapsed-: " << elapsed1 << "\tElapsed+: " << elapsed2;
        GTEST_LOG_(INFO) << "UnwinderLocalTest002: frames:\n" << Unwinder::GetFramesStr(frames);
        unwinders_[pid] = unwinder;
        sleep(1);
    };
    GTEST_LOG_(INFO) << "UnwinderLocalTest002: end.";
}

/**
 * @tc.name: UnwinderLocalTest003
 * @tc.desc: test unwinder UnwindLocal interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderLocalTest003, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderLocalTest003: start.";
    // When the param is less than -1, maps_ = null when method Unwinder is constructed
    auto unwinderNegative = std::make_shared<Unwinder>(-2);
    GTEST_LOG_(INFO) << "when pid == tid and maps_ == null, "
                         "UnwindLocal(maxFrameNum, skipFrameNum) is false";
    ASSERT_FALSE(unwinderNegative->UnwindLocal());
    auto unwinder = std::make_shared<Unwinder>();
    GTEST_LOG_(INFO) << "when pid == tid and maps_ != null, "
                        "UnwindLocal(maxFrameNum, skipFrameNum) is true";
    ASSERT_TRUE(unwinder->UnwindLocal());
    GTEST_LOG_(INFO) << "UnwinderLocalTest003: end.";
}

/**
 * @tc.name: UnwinderRemoteTest001
 * @tc.desc: test unwinder remote unwind
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderRemoteTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderRemoteTest001: start.";
    pid_t child = fork();
    if (child == 0) {
        sleep(TIME_SLEEP);
        _exit(0);
    }

    GTEST_LOG_(INFO) << "pid: " << child << ", ppid:" << getpid();
    auto unwinder = std::make_shared<Unwinder>(child);
    bool unwRet = DfxPtrace::Attach(child);
    EXPECT_EQ(true, unwRet) << "UnwinderRemoteTest001: Attach:" << unwRet;
    ElapsedTime counter;
    unwRet = unwinder->UnwindRemote(child);
    time_t elapsed1 = counter.Elapsed();
    EXPECT_EQ(true, unwRet) << "UnwinderRemoteTest001: unwRet:" << unwRet;
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);
    time_t elapsed2 = counter.Elapsed();
    GTEST_LOG_(INFO) << "Elapsed-: " << elapsed1 << "\tElapsed+: " << elapsed2;
    GTEST_LOG_(INFO) << "UnwinderRemoteTest001: frames:\n" << Unwinder::GetFramesStr(frames);
    unwRet = unwinder->UnwindRemote(child, false, DEFAULT_MAX_FRAME_NUM, skipFrameNum);
    EXPECT_EQ(true, unwRet) << "UnwinderRemoteTest001: unwRet:" << unwRet;
    auto frames2 = unwinder->GetFrames();
    ASSERT_GT(frames.size(), frames2.size());
    GTEST_LOG_(INFO) << "UnwinderRemoteTest001: frames2:\n" << Unwinder::GetFramesStr(frames2);
    DfxPtrace::Detach(child);
    GTEST_LOG_(INFO) << "UnwinderRemoteTest001: end.";
}

/**
 * @tc.name: UnwinderRemoteTest002
 * @tc.desc: test unwinder remote unwind n counts
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderRemoteTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderRemoteTest002: start.";
    pid_t child = fork();
    if (child == 0) {
        sleep(TIME_SLEEP);
        _exit(0);
    }

    GTEST_LOG_(INFO) << "pid: " << child << ", ppid:" << getpid();
    unwinders_.clear();
    std::shared_ptr<Unwinder> unwinder = nullptr;
    bool unwRet = DfxPtrace::Attach(child);
    EXPECT_EQ(true, unwRet) << "UnwinderRemoteTest002: Attach:" << unwRet;
    for (int i = 0; i < 10; ++i) {
        auto it = unwinders_.find(child);
        if (it != unwinders_.end()) {
            unwinder = it->second;
        } else {
            unwinder = std::make_shared<Unwinder>(child);
        }
        ElapsedTime counter;
        unwRet = unwinder->UnwindRemote(child);
        time_t elapsed1 = counter.Elapsed();
        EXPECT_EQ(true, unwRet) << "UnwinderRemoteTest002: Unwind:" << unwRet;
        auto frames = unwinder->GetFrames();
        ASSERT_GT(frames.size(), 1);
        time_t elapsed2 = counter.Elapsed();
        GTEST_LOG_(INFO) << "Elapsed-: " << elapsed1 << "\tElapsed+: " << elapsed2;
        GTEST_LOG_(INFO) << "UnwinderRemoteTest002: frames:\n" << Unwinder::GetFramesStr(frames);
        unwinders_[child] = unwinder;
        sleep(1);
    }
    DfxPtrace::Detach(child);
    GTEST_LOG_(INFO) << "UnwinderRemoteTest002: end.";
}

/**
 * @tc.name: UnwinderRemoteTest003
 * @tc.desc: test unwinder UnwindRemote interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderRemoteTest003, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderRemoteTest003: start.";
    // When the param is less than -1, pid_ < 0 when method Unwinder is constructed
    auto unwinderNegative = std::make_shared<Unwinder>(-2);
    size_t maxFrameNum = 64;
    size_t skipFrameNum = 0;
    GTEST_LOG_(INFO) << "when pid <= 0, UnwindRemote(maxFrameNum, skipFrameNum) is false";
    ASSERT_FALSE(unwinderNegative->UnwindRemote(-2, maxFrameNum, skipFrameNum));
    GTEST_LOG_(INFO) << "UnwinderRemoteTest003: end.";
}

/**
 * @tc.name: UnwindTest001
 * @tc.desc: test unwinder unwind interface in remote case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindTest001: start.";
    pid_t child = fork();
    if (child == 0) {
        sleep(TIME_SLEEP);
        _exit(0);
    }

    GTEST_LOG_(INFO) << "pid: " << child << ", ppid:" << getpid();
    auto unwinder = std::make_shared<Unwinder>(child);
    bool unwRet = DfxPtrace::Attach(child);
    EXPECT_EQ(true, unwRet) << "UnwindTest001: Attach:" << unwRet;
    auto regs = DfxRegs::CreateRemoteRegs(child);
    unwinder->SetRegs(regs);
    auto maps = DfxMaps::Create(child);
    UnwindContext context;
    context.pid = child;
    context.regs = regs;
    context.maps = maps;
    ElapsedTime counter;
    unwRet = unwinder->Unwind(&context);
    time_t elapsed1 = counter.Elapsed();
    EXPECT_EQ(true, unwRet) << "UnwindTest001: Unwind:" << unwRet;
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);
    time_t elapsed2 = counter.Elapsed();
    GTEST_LOG_(INFO) << "Elapsed-: " << elapsed1 << "\tElapsed+: " << elapsed2;
    GTEST_LOG_(INFO) << "UnwindTest001: frames:\n" << Unwinder::GetFramesStr(frames);
    DfxPtrace::Detach(child);
    GTEST_LOG_(INFO) << "UnwindTest001: end.";
}

/**
 * @tc.name: UnwindTest002
 * @tc.desc: test unwinder unwind interface in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindTest002: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1, stackTop = static_cast<uintptr_t>(-1);
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "UnwindTest002: GetStackRange.";
    UnwindContext context;
    context.stackCheck = false;
    context.stackBottom = stackBottom;
    context.stackTop = stackTop;

    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    unwinder->SetRegs(regs);
    auto maps = DfxMaps::Create(getpid());
    context.pid = UNWIND_TYPE_LOCAL;
    context.regs = regs;
    context.maps = maps;
    bool unwRet = unwinder->Unwind(&context);
    EXPECT_EQ(true, unwRet) << "UnwindTest002: unwRet:" << unwRet;
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);
    GTEST_LOG_(INFO) << "UnwindTest002:frames:\n" << Unwinder::GetFramesStr(frames);
    GTEST_LOG_(INFO) << "UnwindTest002: end.";
}

/**
 * @tc.name: UnwindTest003
 * @tc.desc: test GetLastErrorCode GetLastErrorAddr functions
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindTest003, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindTest003: start.";

    auto unwinder = std::make_shared<Unwinder>();
    unwinder->IgnoreMixstack(true);
    MAYBE_UNUSED bool unwRet = unwinder->UnwindLocal();
    EXPECT_EQ(true, unwRet) << "UnwindTest003: Unwind ret:" << unwRet;
    unwinder->EnableFillFrames(false);
    const auto& frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1) << "frames.size() error";

    uint16_t errorCode = unwinder->GetLastErrorCode();
    uint64_t errorAddr = unwinder->GetLastErrorAddr();
    GTEST_LOG_(INFO) << "errorCode:" << errorCode;
    GTEST_LOG_(INFO) << "errorAddr:" << errorAddr;
    GTEST_LOG_(INFO) << "UnwindTest003: end.";
}

/**
 * @tc.name: UnwindTest004
 * @tc.desc: test unwinder local unwind for
 * GetFramesStr(const std::vector<std::shared_ptr<DfxFrame>>& frames)
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindTest004, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindTest004: start.";

    auto unwinder = std::make_shared<Unwinder>();
    ElapsedTime counter;
    MAYBE_UNUSED bool unwRet = unwinder->UnwindLocal();
    ASSERT_EQ(true, unwRet) << "UnwindTest004: Unwind:" << unwRet;
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);

    auto framesVec = DfxFrameFormatter::ConvertFrames(frames);
    std::string framesStr = DfxFrameFormatter::GetFramesStr(framesVec);
    GTEST_LOG_(INFO) << "UnwindTest004: frames:\n" << framesStr;

    string log[] = {"pc", "test_unwind", "#00", "#01", "#02"};
    int len = sizeof(log) / sizeof(log[0]);
    int count = GetKeywordsNum(framesStr, log, len);
    ASSERT_EQ(count, len) << "UnwindTest004 Failed";
    GTEST_LOG_(INFO) << "UnwindTest004: end.";
}

/**
 * @tc.name: StepTest001
 * @tc.desc: test unwinder Step interface in remote case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest001: start.";
    pid_t child = fork();
    if (child == 0) {
        sleep(TIME_SLEEP);
        _exit(0);
    }

    GTEST_LOG_(INFO) << "pid: " << child << ", ppid:" << getpid();
    auto unwinder = std::make_shared<Unwinder>(child);
    bool unwRet = DfxPtrace::Attach(child);
    EXPECT_EQ(true, unwRet) << "StepTest001: Attach:" << unwRet;
    auto regs = DfxRegs::CreateRemoteRegs(child);
    auto maps = DfxMaps::Create(child);
    unwinder->SetRegs(regs);
    UnwindContext context;
    context.pid = child;
    context.regs = regs;
    context.maps = maps;

    uintptr_t pc, sp;
    pc = regs->GetPc();
    sp = regs->GetSp();
    std::shared_ptr<DfxMap> map = nullptr;
    ASSERT_TRUE(maps->FindMapByAddr(pc, map));
    context.map = map;
    unwRet = unwinder->Step(pc, sp, &context);
    ASSERT_TRUE(unwRet) << "StepTest001: Unwind:" << unwRet;
    DfxPtrace::Detach(child);
    GTEST_LOG_(INFO) << "StepTest001: end.";
}

/**
 * @tc.name: StepTest002
 * @tc.desc: test unwinder Step interface in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest002: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1, stackTop = static_cast<uintptr_t>(-1);
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "StepTest002: GetStackRange.";
    auto maps = DfxMaps::Create(getpid());
    UnwindContext context;
    context.pid = UNWIND_TYPE_LOCAL;
    context.stackCheck = false;
    context.stackBottom = stackBottom;
    context.stackTop = stackTop;

    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    unwinder->SetRegs(regs);
    context.regs = regs;
    context.maps = maps;

    uintptr_t pc, sp;
    pc = regs->GetPc();
    sp = regs->GetSp();
    bool unwRet = unwinder->Step(pc, sp, &context);
    ASSERT_TRUE(unwRet) << "StepTest002: unwRet:" << unwRet;
    GTEST_LOG_(INFO) << "StepTest002: end.";
}

#if defined(__aarch64__)
/**
 * @tc.name: StepTest003
 * @tc.desc: test unwinder UnwindByFp interface in remote case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest003, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest003: start.";
    pid_t child = fork();
    if (child == 0) {
        sleep(TIME_SLEEP);
        _exit(0);
    }

    GTEST_LOG_(INFO) << "pid: " << child << ", ppid:" << getpid();
    auto unwinder = std::make_shared<Unwinder>(child);
    bool unwRet = DfxPtrace::Attach(child);
    EXPECT_EQ(true, unwRet) << "StepTest003: Attach:" << unwRet;
    auto regs = DfxRegs::CreateRemoteRegs(child);
    unwinder->SetRegs(regs);
    UnwindContext context;
    context.pid = child;
    ElapsedTime counter;
    unwRet = unwinder->UnwindByFp(&context);
    ASSERT_TRUE(unwRet) << "StepTest003: unwind:" << unwRet;
    DfxPtrace::Detach(child);
    time_t elapsed = counter.Elapsed();
    GTEST_LOG_(INFO) << "StepTest003: Elapsed: " << elapsed;
    auto pcs = unwinder->GetPcs();
    std::vector<DfxFrame> frames;
    unwinder->GetFramesByPcs(frames, pcs);
    ASSERT_GT(frames.size(), 1);
    GTEST_LOG_(INFO) << "StepTest003: frames:\n" << Unwinder::GetFramesStr(frames);
    GTEST_LOG_(INFO) << "StepTest003: end.";
}

/**
 * @tc.name: StepTest004
 * @tc.desc: test unwinder FpStep interface in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest004, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest004: start.";
    auto unwinder = std::make_shared<Unwinder>();
    ElapsedTime counter;
    uintptr_t stackBottom = 1, stackTop = static_cast<uintptr_t>(-1);
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "StepTest004: GetStackRange.";

    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    UnwindContext context;
    context.pid = UNWIND_TYPE_LOCAL;
    context.stackCheck = false;
    context.stackBottom = stackBottom;
    context.stackTop = stackTop;
    unwinder->SetRegs(regs);

    bool unwRet = unwinder->UnwindByFp(&context);
    ASSERT_TRUE(unwRet) << "StepTest004: unwRet:" << unwRet;
    auto unwSize = unwinder->GetPcs().size();
    ASSERT_GT(unwSize, 1) << "pcs.size() error";

    uintptr_t miniRegs[FP_MINI_REGS_SIZE] = {0};
    GetFramePointerMiniRegs(miniRegs, sizeof(miniRegs) / sizeof(miniRegs[0]));
    regs = DfxRegs::CreateFromRegs(UnwindMode::FRAMEPOINTER_UNWIND, miniRegs, sizeof(miniRegs) / sizeof(miniRegs[0]));
    unwinder->SetRegs(regs);
    size_t idx = 0;
    uintptr_t pc, fp;
    while (true) {
        pc = regs->GetPc();
        fp = regs->GetFp();
        idx++;
        if (!unwinder->FpStep(fp, pc, &context) || (pc == 0)) {
            break;
        }
    };
    ASSERT_EQ(idx, unwSize) << "StepTest004: idx:" << idx;
    time_t elapsed = counter.Elapsed();
    GTEST_LOG_(INFO) << "StepTest004: Elapsed: " << elapsed;
    GTEST_LOG_(INFO) << "StepTest004: end.";
}
#endif

#if defined(__arm__) || defined(__aarch64__)
/**
 * @tc.name: StepTest005
 * @tc.desc: test unwinder Step interface in lr callback with apply failed case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest005, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest005: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1, stackTop = static_cast<uintptr_t>(-1);
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "StepTest005: GetStackRange.";

    UnwindContext context;
    context.pid = UNWIND_TYPE_LOCAL;
    context.stackCheck = false;
    context.stackBottom = stackBottom;
    context.stackTop = stackTop;

    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    unwinder->SetRegs(regs);
    context.regs = regs;
    context.maps = unwinder->GetMaps();

    uintptr_t lr = *(regs->GetReg(REG_LR));
    uintptr_t pc = regs->GetPc();
    uintptr_t failSp = stackTop + 1; // arm cfa get from sp
    regs->SetSp(failSp);
    uintptr_t failFp = stackTop + 1; // arm64 cfa get from fp
    regs->SetFp(failFp);
    bool unwRet = unwinder->Step(pc, failSp, &context);
    ASSERT_TRUE(unwRet) << "StepTest005: unwRet:" << unwRet;
    ASSERT_EQ(lr, pc) << "StepTest005: lr callback";
    GTEST_LOG_(INFO) << "StepTest005: end.";
}

/**
 * @tc.name: StepTest006
 * @tc.desc: test unwinder Step interface in lr callback with step failed case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, StepTest006, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "StepTest006: start.";
    auto unwinder = std::make_shared<Unwinder>();
    uintptr_t stackBottom = 1, stackTop = static_cast<uintptr_t>(-1);
    ASSERT_TRUE(unwinder->GetStackRange(stackBottom, stackTop));
    GTEST_LOG_(INFO) << "StepTest006: GetStackRange.";

    UnwindContext context;
    context.pid = UNWIND_TYPE_LOCAL;
    context.stackCheck = true;
    context.stackBottom = stackBottom;
    context.stackTop = stackTop;

    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    unwinder->SetRegs(regs);
    context.regs = regs;
    context.maps = unwinder->GetMaps();

    uintptr_t lr = *(regs->GetReg(REG_LR));
    uintptr_t sp = regs->GetSp();
    uintptr_t failPc = stackTop + 1;
    regs->SetPc(failPc);
    bool unwRet = unwinder->Step(failPc, sp, &context);
    ASSERT_TRUE(unwRet) << "StepTest006: unwRet:" << unwRet;
    ASSERT_EQ(lr, failPc) << "StepTest006: lr callback";

    GTEST_LOG_(INFO) << "StepTest006: end.";
}
#endif

/**
 * @tc.name: DfxConfigTest001
 * @tc.desc: test DfxConfig class functions
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, DfxConfigTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "DfxConfigTest001: start.";
    ASSERT_EQ(DfxConfig::GetConfig().logPersist, false);
    ASSERT_EQ(DfxConfig::GetConfig().displayRegister, true);
    ASSERT_EQ(DfxConfig::GetConfig().displayBacktrace, true);
    ASSERT_EQ(DfxConfig::GetConfig().displayMaps, true);
    ASSERT_EQ(DfxConfig::GetConfig().displayFaultStack, true);
    ASSERT_EQ(DfxConfig::GetConfig().dumpOtherThreads, true);
    ASSERT_EQ(DfxConfig::GetConfig().highAddressStep, 512);
    ASSERT_EQ(DfxConfig::GetConfig().lowAddressStep, 16);
    ASSERT_EQ(DfxConfig::GetConfig().maxFrameNums, 256);
    GTEST_LOG_(INFO) << "DfxConfigTest001: end.";
}

/**
 * @tc.name: FillFrameTest001
 * @tc.desc: test unwinder FillFrame interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, FillFrameTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "FillFrameTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    DfxFrame frame;
    unwinder->FillFrame(frame);
    GTEST_LOG_(INFO) << " when DfxFrame::map is null, frame.buildId.size() is 0";
    ASSERT_EQ(frame.buildId.size(), 0);
    string testMap = "f6d83000-f6d84000 r--p 00001000 b3:07 1892 /system/lib/init/libinit_context.z.so.noexit";
    auto map = DfxMap::Create(testMap, sizeof(testMap));
    frame.map = map;
    unwinder->FillFrame(frame);
    GTEST_LOG_(INFO) << " when DfxFrame::map is not null and file not exist, frame.buildId.size() is 0";
    ASSERT_EQ(frame.buildId.size(), 0);
#ifdef __arm__
    testMap = "f6d83000-f6d84000 r--p 00001000 b3:07 1892 /system/lib/init/libinit_context.z.so";
#else
    testMap = "7f0ab40000-7f0ab41000 r--p 00000000 b3:07 1882 /system/lib64/init/libinit_context.z.so";
#endif
    map = DfxMap::Create(testMap, sizeof(testMap));
    frame.map = map;
    unwinder->FillFrame(frame);
    GTEST_LOG_(INFO) << " when DfxFrame::map is not null and file exist, frame.buildId.size() is bigger than 0";
    ASSERT_EQ(frame.buildId.size() == 0, false);
    GTEST_LOG_(INFO) << "FillFrameTest001: end.";
}

/**
 * @tc.name: FillJsFrameTest001
 * @tc.desc: test unwinder FillJsFrame interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, FillJsFrameTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "FillJsFrameTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    DfxFrame frame;
    unwinder->FillJsFrame(frame);
    GTEST_LOG_(INFO) << " when DfxFrame::map is null, frame.map is nullptr";
    ASSERT_EQ(frame.map, nullptr);
    string testMap = "f6d83000-f6d84000 r--p 00001000 b3:07 1892 /system/lib/init/libinit_context.z.so";
    auto map = DfxMap::Create(testMap, sizeof(testMap));
    frame.map = map;
    unwinder->FillJsFrame(frame);
    GTEST_LOG_(INFO) << " when DfxFrame::map is not null and file exist, frame.map.GetHap is not nullptr";
    ASSERT_NE(frame.map->GetHap(), nullptr);
    GTEST_LOG_(INFO) << "FillJsFrameTest001: end.";
}

/**
 * @tc.name: FillFramesTest001
 * @tc.desc: test unwinder FillFrames interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, FillFramesTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "FillFramesTest001: start.";
#ifdef __arm__
    const string testMap = "f6d83000-f6d84000 r--p 00001000 b3:07 1892 /system/lib/init/libinit_context.z.so";
#else
    const string testMap = "7f0ab40000-7f0ab41000 r--p 00000000 b3:07 1882 /system/lib64/init/libinit_context.z.so";
#endif
    auto unwinder = std::make_shared<Unwinder>();
    std::vector<DfxFrame> frames;
    DfxFrame frame;
    auto map = DfxMap::Create(testMap, sizeof(testMap));
    frame.map = map;
    frames.push_back(frame);
    ASSERT_EQ(frames[0].buildId.size(), 0);
    unwinder->FillFrames(frames);
    ASSERT_EQ(frames[0].buildId.size() == 0, false);
    GTEST_LOG_(INFO) << "FillFramesTest001: end.";
}

#if defined(__arm__) || defined(__aarch64__)
/**
 * @tc.name: UnwindLocalWithContextTest001
 * @tc.desc: test unwinder UnwindLocalWithContext interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindLocalWithContextTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindLocalWithContextTest001: start.";
    auto regs = DfxRegs::Create();
    auto regsData = regs->RawData();
    GetLocalRegs(regsData);
    ucontext_t context;
    (void)memset_s(&context, sizeof(context), 0, sizeof(context));
#ifdef __arm__
    context.uc_mcontext.arm_r0 = *(regs->GetReg(REG_ARM_R0));
    context.uc_mcontext.arm_r1 = *(regs->GetReg(REG_ARM_R1));
    context.uc_mcontext.arm_r2 = *(regs->GetReg(REG_ARM_R2));
    context.uc_mcontext.arm_r3 = *(regs->GetReg(REG_ARM_R3));
    context.uc_mcontext.arm_r4 = *(regs->GetReg(REG_ARM_R4));
    context.uc_mcontext.arm_r5 = *(regs->GetReg(REG_ARM_R5));
    context.uc_mcontext.arm_r6 = *(regs->GetReg(REG_ARM_R6));
    context.uc_mcontext.arm_r7 = *(regs->GetReg(REG_ARM_R7));
    context.uc_mcontext.arm_r8 = *(regs->GetReg(REG_ARM_R8));
    context.uc_mcontext.arm_r9 = *(regs->GetReg(REG_ARM_R9));
    context.uc_mcontext.arm_r10 = *(regs->GetReg(REG_ARM_R10));
    context.uc_mcontext.arm_fp = *(regs->GetReg(REG_ARM_R11));
    context.uc_mcontext.arm_ip = *(regs->GetReg(REG_ARM_R12));
    context.uc_mcontext.arm_sp = *(regs->GetReg(REG_ARM_R13));
    context.uc_mcontext.arm_lr = *(regs->GetReg(REG_ARM_R14));
    context.uc_mcontext.arm_pc = *(regs->GetReg(REG_ARM_R15));
#else
    for (int i = 0; i < REG_LAST; ++i) {
        context.uc_mcontext.regs[i] = *(regs->GetReg(i));
    }
#endif
    auto unwinder = std::make_shared<Unwinder>();
    ASSERT_TRUE(unwinder->UnwindLocalWithContext(context));
    auto frames = unwinder->GetFrames();
    ASSERT_GT(frames.size(), 1);
    GTEST_LOG_(INFO) << "UnwindLocalWithContextTest001: frames:\n" << Unwinder::GetFramesStr(frames);
    GTEST_LOG_(INFO) << "UnwindLocalWithContextTest001: end.";
}
#endif

static int32_t g_tid = 0;
static std::mutex g_mutex;
__attribute__((noinline)) void ThreadTest002()
{
    printf("ThreadTest002\n");
    g_mutex.lock();
    g_mutex.unlock();
}

__attribute__((noinline)) void ThreadTest001()
{
    g_tid = gettid();
    printf("ThreadTest001: tid: %d\n", g_tid);
    ThreadTest002();
}

/**
 * @tc.name: UnwindLocalWithTidTest001
 * @tc.desc: test unwinder UnwindLocalWithTid interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindLocalWithTidTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindLocalWithTidTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    g_mutex.lock();
    std::thread unwThread(ThreadTest001);
    sleep(1);
    if (g_tid <= 0) {
        FAIL() << "UnwindLocalWithTidTest001: Failed to create child thread.\n";
    }
    ASSERT_TRUE(unwinder->UnwindLocalWithTid(g_tid));
#if defined(__aarch64__)
    auto pcs = unwinder->GetPcs();
    std::vector<DfxFrame> frames;
    unwinder->GetFramesByPcs(frames, pcs);
#else
    auto frames = unwinder->GetFrames();
#endif
    ASSERT_GT(frames.size(), 1);
    GTEST_LOG_(INFO) << "UnwindLocalWithTidTest001: frames:\n" << Unwinder::GetFramesStr(frames);
    g_mutex.unlock();
    g_tid = 0;
    if (unwThread.joinable()) {
        unwThread.join();
    }
    GTEST_LOG_(INFO) << "UnwindLocalWithTidTest001: end.";
}

#if defined(__x86_64__)
static _Unwind_Reason_Code TraceFunc(_Unwind_Context *ctx, void *d)
{
    int *depth = (int*)d;
    printf("\t#%d: program counter at %p\n", *depth, reinterpret_cast<void *>(_Unwind_GetIP(ctx)));
    (*depth)++;
    return _URC_NO_REASON;
}

static void PrintUnwindBacktrace()
{
    int depth = 0;
    _Unwind_Backtrace(&TraceFunc, &depth);
}

/**
 * @tc.name: UnwindLocalX86_64Test001
 * @tc.desc: test unwinder UnwindLocal interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindLocalX86_64Test001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindLocalX86_64Test001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    if (unwinder->UnwindLocal()) {
        auto frames = unwinder->GetFrames();
        printf("Unwinder frame size: %zu\n", frames.size());
        auto framesStr = Unwinder::GetFramesStr(frames);
        printf("Unwinder frames:\n%s\n", framesStr.c_str());
    }

    PrintUnwindBacktrace();
    GTEST_LOG_(INFO) << "UnwindLocalX86_64Test001: end.";
}

/**
 * @tc.name: UnwindRemoteX86_64Test001
 * @tc.desc: test unwinder UnwindRemote interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwindRemoteX86_64Test001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwindLocalX86_64Test001: start.";
    const pid_t initPid = 1;
    auto unwinder = std::make_shared<Unwinder>(initPid);
    DfxPtrace::Attach(initPid);
    if (unwinder->UnwindRemote(initPid)) {
        auto frames = unwinder->GetFrames();
        printf("Unwinder frame size: %zu\n", frames.size());
        auto framesStr = Unwinder::GetFramesStr(frames);
        printf("Unwinder frames:\n%s\n", framesStr.c_str());
    }
    DfxPtrace::Detach(initPid);

    GTEST_LOG_(INFO) << "UnwindRemoteX86_64Test001: end.";
}
#endif

/**
 * @tc.name: GetSymbolByPcTest001
 * @tc.desc: test unwinder GetSymbolByPc interface
 *  in local case
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, GetSymbolByPcTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "GetSymbolByPcTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    unwinder->UnwindLocal();
    auto frames = unwinder->GetFrames();
    uintptr_t pc0 = static_cast<uintptr_t>(frames[0].pc);
    std::string funcName;
    uint64_t funcOffset;
    std::shared_ptr<DfxMaps> maps = std::make_shared<DfxMaps>();
    ASSERT_FALSE(unwinder->GetSymbolByPc(0x00000000, maps, funcName, funcOffset)); // Find map is null
    ASSERT_FALSE(unwinder->GetSymbolByPc(pc0, maps, funcName, funcOffset)); // Get elf is null
    GTEST_LOG_(INFO) << "GetSymbolByPcTest001: end.";
}

/**
 * @tc.name: AccessMemTest001
 * @tc.desc: test unwinder AccessMem interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, AccessMemTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "AccessMemTest001: start.";
    auto unwinder = std::make_shared<Unwinder>();
    auto acc = std::make_shared<DfxAccessorsLocal>();
    auto memory = std::make_shared<DfxMemory>(acc);
    uintptr_t val;
    EXPECT_FALSE(memory->ReadReg(0, &val));
    uintptr_t regs[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
    UnwindContext ctx;
    ctx.regs = DfxRegs::CreateFromRegs(UnwindMode::DWARF_UNWIND, regs, sizeof(regs) / sizeof(regs[0]));
    memory->SetCtx(&ctx);
    EXPECT_FALSE(memory->ReadReg(-1, &val));

    uint8_t values[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
    uintptr_t addr = reinterpret_cast<uintptr_t>(&values[0]);
    EXPECT_FALSE(unwinder->AccessMem(&memory, addr, nullptr));
    GTEST_LOG_(INFO) << "AccessMemTest001: end.";
}

/**
 * @tc.name: UnwinderTest001
 * @tc.desc: test Unwinder::xxxx interface
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderTest001, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderTest001: start.";
    auto unwinder = std::make_shared<Unwinder>(getpid());
    unwinder->EnableUnwindCache(false);
    unwinder->EnableFpCheckMapExec(false);
    auto regs = unwinder->GetRegs();
    ASSERT_EQ(regs, nullptr);
    DfxFrame frame;
    unwinder->FillFrame(frame);
    unwinder->AddFrame(frame);
    unwinder->ArkWriteJitCodeToFile(1);
    auto jitCache = unwinder->GetJitCache();
    ASSERT_EQ(jitCache.size(), 0);
    GTEST_LOG_(INFO) << "UnwinderTest001: end.";
}

/**
 * @tc.name: UnwinderTest002
 * @tc.desc: test DfxFrameFormatter GetFrameStr
 * @tc.type: FUNC
 */
HWTEST_F(UnwinderTest, UnwinderTest002, TestSize.Level2)
{
    GTEST_LOG_(INFO) << "UnwinderTest002: start.";
    std::shared_ptr<DfxFrame> frame = nullptr;
    std::string str = DfxFrameFormatter::GetFrameStr(frame);
    ASSERT_EQ(str, "");
    std::vector<std::shared_ptr<DfxFrame>> frames;
    str = DfxFrameFormatter::GetFramesStr(frames);
    ASSERT_EQ(str, "");
    GTEST_LOG_(INFO) << "UnwinderTest002: end.";
}
} // namespace HiviewDFX
} // namepsace OHOS