/*
 * Copyright (c) 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.
 */
#ifndef FP_UNWINDER_H
#define FP_UNWINDER_H

#include <atomic>
#include <cinttypes>
#include <csignal>
#include <dlfcn.h>
#include <fcntl.h>
#include <memory>
#include <mutex>
#include <nocopyable.h>
#include <securec.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "dfx_ark.h"
#include "dfx_define.h"
#include "stack_util.h"

namespace OHOS {
namespace HiviewDFX {
class FpUnwinder {
public:
    static FpUnwinder* GetPtr()
    {
        static std::unique_ptr<FpUnwinder> ptr = nullptr;
        if (ptr == nullptr) {
            static std::once_flag flag;
            std::call_once(flag, [&] {
                ptr.reset(new FpUnwinder());
            });
        }
        return ptr.get();
    }

    size_t Unwind(uintptr_t pc, uintptr_t fp, uintptr_t* pcs, size_t maxSize, size_t skipFrameNum = 0)
    {
        if (pcs == nullptr) {
            return 0;
        }
        if (gettid() == getpid()) {
            if (!GetMainStackRange(stackBottom_, stackTop_)) {
                return 0;
            }
        } else {
            if (!GetSelfStackRange(stackBottom_, stackTop_)) {
                return 0;
            }
        }
        uintptr_t firstFp = fp;
        size_t index = 0;
        MAYBE_UNUSED uintptr_t sp = 0;
        MAYBE_UNUSED bool isJsFrame = false;
        while ((index < maxSize) && (fp - firstFp < maxUnwindAddrRange_)) {
            if (index >= skipFrameNum) {
                pcs[index - skipFrameNum] = pc;
            }
            index++;
            uintptr_t prevFp = fp;
            if ((!ReadUptr(prevFp, fp)) ||
                (!ReadUptr(prevFp + sizeof(uintptr_t), pc))) {
                break;
            }
            if (fp <= prevFp || fp == 0) {
                break;
            }
        }
        return (index - skipFrameNum);
    }

    NO_SANITIZE size_t UnwindSafe(uintptr_t pc, uintptr_t fp, uintptr_t* pcs, size_t maxSize, size_t skipFrameNum = 0)
    {
        if (pcs == nullptr) {
            return 0;
        }
        int32_t pfd[PIPE_NUM_SZ] = {-1, -1};
        if (syscall(SYS_pipe2, pfd, O_CLOEXEC | O_NONBLOCK) != 0) {
            return 0;
        }
        size_t index = 0;
        MAYBE_UNUSED uintptr_t sp = 0;
        MAYBE_UNUSED bool isJsFrame = false;
        while (index < maxSize) {
            if (index >= skipFrameNum) {
                pcs[index - skipFrameNum] = pc;
            }
            index++;
            uintptr_t prevFp = fp;
            if ((!ReadUptrSafe(prevFp, pfd[PIPE_WRITE], fp)) ||
                (!ReadUptrSafe(prevFp + sizeof(uintptr_t), pfd[PIPE_WRITE], pc))) {
                break;
            }
            if (fp <= prevFp || fp == 0) {
                break;
            }
        }
        syscall(SYS_close, pfd[PIPE_WRITE]);
        syscall(SYS_close, pfd[PIPE_READ]);
        return (index - skipFrameNum);
    }

    static inline AT_ALWAYS_INLINE void GetPcFpRegs(void *regs)
    {
#if defined(__aarch64__)
        asm volatile(
        "1:\n"
        "adr x12, 1b\n"
        "stp x12, x29, [%[base], #0]\n"
        : [base] "+r"(regs)
        :
        : "x12", "memory");
#endif
    }

private:
    FpUnwinder() = default;
    DISALLOW_COPY_AND_MOVE(FpUnwinder);

    NO_SANITIZE bool ReadUptr(uintptr_t addr, uintptr_t& value)
    {
        if ((addr < stackBottom_) || (addr + sizeof(uintptr_t) >= stackTop_)) {
            return false;
        }
        value = *reinterpret_cast<uintptr_t *>(addr);
        return true;
    }

    NO_SANITIZE bool ReadUptrSafe(uintptr_t addr, uint32_t fd, uintptr_t& value)
    {
        if (OHOS_TEMP_FAILURE_RETRY(syscall(SYS_write, fd, addr, sizeof(uintptr_t))) == -1) {
            return false;
        }

        value = *reinterpret_cast<uintptr_t *>(addr);
        return true;
    }

private:
    MAYBE_UNUSED const uintptr_t maxUnwindAddrRange_ = 16 * 1024; // 16: 16k
    uintptr_t stackBottom_ = 0;
    uintptr_t stackTop_ = 0;
    MAYBE_UNUSED uintptr_t arkMapStart_ = 0;
    MAYBE_UNUSED uintptr_t arkMapEnd_ = 0;
};
}
} // namespace OHOS
#endif