* Copyright (c) 2024-2026 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 "ecmascript/platform/backtrace.h"
#include <cinttypes>
#include <dlfcn.h>
#include <map>
#include "ecmascript/base/config.h"
#include "ecmascript/platform/mutex.h"
#include "securec.h"
#include "ecmascript/mem/mem.h"
#if defined(ENABLE_BACKTRACE_LOCAL)
#include "dfx_frame_formatter.h"
#include "fp_backtrace.h"
#endif
#if defined(ENABLE_UNWINDER)
#include "unwinder.h"
#endif
namespace panda::ecmascript {
static const std::string LIB_HWASAN_SO_NAME = "libclang_rt.hwasan.so";
static const int MAX_STACK_SIZE = 32;
static const int LOG_BUF_LEN = 1024;
using UnwBackTraceFunc = int (*)(void**, int);
using HwasanSetStubFileRangeFunc = void (*)(uintptr_t, uintptr_t);
static std::map<void *, Dl_info> stackInfoCache;
RWLock rwMutex;
Mutex unwinderMutex;
bool GetPcs(size_t &size, void** pcs)
{
#if defined(ENABLE_BACKTRACE_LOCAL)
size = BacktraceHybrid(pcs, MAX_STACK_SIZE);
#endif
return true;
}
bool FindStackInfoCache(void* pc, Dl_info &info)
{
ReadLockHolder lock(rwMutex);
auto iter = stackInfoCache.find(pc);
if (iter != stackInfoCache.end()) {
info = iter->second;
return true;
} else {
return false;
}
}
void EmplaceStackInfoCache(void *pc, const Dl_info &info)
{
WriteLockHolder lock(rwMutex);
stackInfoCache.emplace(pc, info);
}
void Backtrace(std::ostringstream &stack, bool enableCache)
{
void *pcs[MAX_STACK_SIZE] = { nullptr };
size_t stackSize = 0;
#if defined(ENABLE_BACKTRACE_LOCAL)
if (!GetPcs(stackSize, pcs)) {
return;
}
stack << "=====================Backtrace========================";
for (size_t i = 0; i < stackSize; i++) {
Dl_info info;
if (!FindStackInfoCache(pcs[i], info)) {
if (!dladdr(pcs[i], &info)) {
break;
}
if (enableCache) {
EmplaceStackInfoCache(pcs[i], info);
}
}
const char *file = info.dli_fname ? info.dli_fname : "";
uint64_t offset = info.dli_fbase ? ToUintPtr(pcs[i]) - ToUintPtr(info.dli_fbase) : 0;
char buf[LOG_BUF_LEN] = {0};
char frameFormatWithMapName[] = "#%02zu pc %016" PRIx64 " %s";
int ret = 0;
ret = static_cast<int>(snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, frameFormatWithMapName, \
i, offset, file));
if (ret <= 0) {
LOG_ECMA(ERROR) << "Backtrace snprintf_s failed";
return;
}
stack << std::endl;
stack << buf;
}
#elif defined(ENABLE_UNWINDER)
LockHolder lock(unwinderMutex);
static auto unwinder = std::make_shared<OHOS::HiviewDFX::Unwinder>();
unwinder->EnableFillFrames(false);
if (!unwinder->UnwindLocal(false, false, MAX_STACK_SIZE)) {
return;
}
auto frames = unwinder->GetFrames();
for (auto frame: frames) {
unwinder->FillFrame(frame, false);
}
stack << "=====================Backtrace========================";
stack << OHOS::HiviewDFX::Unwinder::GetFramesStr(frames);
#endif
}
#if defined(ENABLE_BACKTRACE_LOCAL)
OHOS::HiviewDFX::FpBacktrace *FpBacktrace()
{
return OHOS::HiviewDFX::FpBacktrace::CreateInstance();
}
#endif
std::string SymbolicAddress(const void* const *pc,
int size,
const EcmaVM *vm)
{
std::string stack;
#if defined(ENABLE_BACKTRACE_LOCAL)
std::vector<OHOS::HiviewDFX::DfxFrame> frames;
int index = 0;
static auto fpBacktrace = FpBacktrace();
if (fpBacktrace == nullptr) {
LOG_ECMA(ERROR) << "FpBacktrace create instance failed";
return stack;
}
for (int i = 0; i < size; i++) {
auto dfx_frame = fpBacktrace->SymbolicAddress(const_cast<void *>(pc[i]));
if (dfx_frame == nullptr) {
continue;
}
dfx_frame->index = index++;
if (dfx_frame->isJsFrame && vm != nullptr) {
auto cb = vm->GetSourceMapTranslateCallback();
if (cb != nullptr) {
cb(dfx_frame->mapName, dfx_frame->line, dfx_frame->column, dfx_frame->packageName);
}
}
frames.push_back(*dfx_frame);
}
stack = OHOS::HiviewDFX::DfxFrameFormatter::GetFramesStr(frames);
#endif
return stack;
}
__attribute__((optnone)) int BacktraceHybrid(void** pcArray, uint32_t maxSize)
{
uint32_t size = 0;
#if defined(ENABLE_BACKTRACE_LOCAL)
static auto fpBacktrace = FpBacktrace();
if (fpBacktrace == nullptr) {
LOG_ECMA(ERROR) << "FpBacktrace create instance failed";
return 0;
}
size = fpBacktrace->BacktraceFromFp(__builtin_frame_address(0), pcArray, maxSize);
#endif
return static_cast<int>(size);
}
void UpdateStubFileRange(uint64_t stubFileStartAddr, uint64_t stubFileSize)
{
#if defined(ENABLE_BACKTRACE_LOCAL)
OHOS::HiviewDFX::FpBacktrace::UpdateArkStackRange(stubFileStartAddr, stubFileStartAddr + stubFileSize);
#endif
void *handle = dlopen(LIB_HWASAN_SO_NAME.c_str(), RTLD_LAZY | RTLD_NOLOAD);
if (handle == nullptr) {
LOG_ECMA(DEBUG) << "Failed to load library: " << dlerror();
return;
}
auto func = reinterpret_cast<HwasanSetStubFileRangeFunc>(dlsym(handle, "__hwasan_set_arkts_stub_range"));
if (func == nullptr) {
LOG_ECMA(ERROR) << "Dlsym failed: " << dlerror();
return;
}
func(stubFileStartAddr, stubFileStartAddr + stubFileSize);
}
}