* Copyright (c) 2022-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 "application_data_manager.h"
#include <dfx_signal_handler.h>
#include <sys/xattr.h>
#include <nlohmann/json.hpp>
#include "app_recovery.h"
#include "hilog_tag_wrapper.h"
#include "native_engine.h"
namespace OHOS {
namespace AppExecFwk {
namespace {
constexpr size_t STACK_MAX_SIZE = 1024;
constexpr size_t AT_SKIP_SIZE = 3;
constexpr size_t PAIR_SIZE = 2;
constexpr const char* TASK_POOL_THREAD = "Taskpool Thread";
constexpr const char *RSRC_OBSV_PATH = "/data/storage/el2/base/cache";
constexpr const char *RSRC_OBSV_XATTR = "user.resource_observer";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_PSS = "user.resource_observer_threshold.pss";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_GPU = "user.resource_observer_threshold.gpu";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_FD = "user.resource_observer_threshold.fd";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_RAT = "user.resource_observer_threshold.rss_ark_ts";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_RNH = "user.resource_observer_threshold.rss_native_heap";
constexpr const char *RSRC_OBSV_TRSHD_XATTR_KH = "user.resource_observer_threshold.kmp_heap";
enum class InstanceType {
DEFAULT_TYPE = -1,
WORKER_THREAD_TYPE = 1,
TASK_POOL_THREAD_TYPE = 2,
};
thread_local bool g_hasNotified = false;
}
std::atomic<bool> ApplicationDataManager::jsErrorHasReport_{false};
std::atomic<napi_env> ApplicationDataManager::jsErrorEnv_{nullptr};
ApplicationDataManager::ApplicationDataManager() {}
ApplicationDataManager::~ApplicationDataManager() {}
ApplicationDataManager &ApplicationDataManager::GetInstance()
{
static ApplicationDataManager manager;
return manager;
}
void ApplicationDataManager::AddErrorObserver(const std::shared_ptr<IErrorObserver> &observer)
{
errorObserver_ = observer;
}
bool ApplicationDataManager::NotifyUnhandledException(const std::string &errMsg)
{
if (errorObserver_) {
errorObserver_->OnUnhandledException(errMsg);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::JS_ERROR);
}
bool ApplicationDataManager::NotifyCJUnhandledException(const std::string &errMsg)
{
if (errorObserver_) {
errorObserver_->OnUnhandledException(errMsg);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::CJ_ERROR);
}
bool ApplicationDataManager::NotifyETSUnhandledException(const std::string &errMsg)
{
if (errorObserver_) {
errorObserver_->OnUnhandledException(errMsg);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::JS_ERROR);
}
void ApplicationDataManager::RemoveErrorObserver()
{
errorObserver_ = nullptr;
}
bool ApplicationDataManager::NotifyExceptionObject(const AppExecFwk::ErrorObject &errorObj)
{
if (errorObserver_) {
errorObserver_->OnExceptionObject(errorObj);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::JS_ERROR);
}
bool ApplicationDataManager::NotifyCJExceptionObject(const AppExecFwk::ErrorObject &errorObj)
{
TAG_LOGD(AAFwkTag::APPKIT, "Notify Exception error observer come");
if (errorObserver_) {
errorObserver_->OnExceptionObject(errorObj);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::CJ_ERROR);
}
bool ApplicationDataManager::NotifyETSExceptionObject(const AppExecFwk::ErrorObject &errorObj)
{
TAG_LOGD(AAFwkTag::APPKIT, "Notify Exception error observer come");
if (errorObserver_) {
errorObserver_->OnExceptionObject(errorObj);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::JS_ERROR);
}
bool ApplicationDataManager::NotifyETSErrorObject(const AppExecFwk::ErrorObject &errorObj)
{
TAG_LOGD(AAFwkTag::APPKIT, "Notify Exception error observer start");
if (errorCallback_) {
errorCallback_(errorObj);
return true;
}
return AppRecovery::GetInstance().TryRecoverApp(StateReason::JS_ERROR);
}
void ApplicationDataManager::SetErrorHandlerCallback(EtsErrorCallback errorCallback)
{
errorCallback_ = errorCallback;
}
void ApplicationDataManager::SetIsUncatchable(bool isUncatchable)
{
isUncatchable_.store(isUncatchable);
}
bool ApplicationDataManager::GetIsUncatchable()
{
bool isUncatchable = isUncatchable_.load();
return isUncatchable;
}
std::string ApplicationDataManager::GetFuncNameFromError(napi_env env, napi_value error)
{
if (error == nullptr) {
return TASK_POOL_THREAD;
}
napi_value stack;
if (napi_get_named_property(env, error, "stack", &stack) != napi_ok || stack == nullptr) {
return TASK_POOL_THREAD;
}
std::string rawStack;
size_t rawStackSize = 0;
napi_get_value_string_utf8(env, stack, nullptr, 0, &rawStackSize);
rawStackSize = std::min(rawStackSize, STACK_MAX_SIZE);
rawStack.reserve(rawStackSize + 1);
rawStack.resize(rawStackSize);
napi_get_value_string_utf8(env, stack, rawStack.data(), rawStack.size() + 1, &rawStackSize);
size_t pos = rawStack.find("at");
if (pos == std::string::npos) {
return TASK_POOL_THREAD;
}
size_t endPos = rawStack.find("(", pos);
if (endPos == std::string::npos) {
return TASK_POOL_THREAD;
}
size_t startPos = pos + AT_SKIP_SIZE;
if (endPos <= startPos + 1) {
return TASK_POOL_THREAD;
}
std::string funcName = std::string(TASK_POOL_THREAD);
funcName.append(rawStack.substr(startPos, endPos - startPos - 1));
return funcName;
}
bool ApplicationDataManager::NotifyUncaughtException(const ExceptionParams ¶ms,
const AppExecFwk::ErrorObject &errorObj)
{
if (params.isUncatchable && g_hasNotified) {
return false;
}
g_hasNotified = params.isUncatchable;
auto napiEnv = params.env ? params.env : params.mainEnv;
if (params.env == params.mainEnv) {
TAG_LOGI(AAFwkTag::APPKIT, "main thread");
if (NapiErrorManager::GetInstance()->NotifyUncaughtException(napiEnv, params.summary,
errorObj.name, errorObj.message, errorObj.stack)) {
TAG_LOGI(AAFwkTag::APPKIT, "Complete all callbacks");
if (!params.isUncatchable) {
return true;
}
}
} else if (params.isUncatchable) {
NativeEngine* engine = reinterpret_cast<NativeEngine*>(napiEnv);
if (engine == nullptr) {
return false;
}
std::string name;
InstanceType type = InstanceType::DEFAULT_TYPE;
if (engine->IsWorkerThread()) {
type = InstanceType::WORKER_THREAD_TYPE;
napi_value workerGlobalObject = nullptr;
napi_get_global(napiEnv, &workerGlobalObject);
napi_value valueStr = nullptr;
if (napi_get_named_property(napiEnv, workerGlobalObject, "name", &valueStr) != napi_ok) {
return false;
}
napi_valuetype valueType = napi_undefined;
napi_typeof(napiEnv, valueStr, &valueType);
if (valueType == napi_string) {
size_t nameSize = 0;
napi_get_value_string_utf8(napiEnv, valueStr, nullptr, 0, &nameSize);
name.reserve(nameSize + 1);
name.resize(nameSize);
napi_get_value_string_utf8(napiEnv, valueStr, name.data(), name.size() + 1, &nameSize);
}
TAG_LOGE(AAFwkTag::APPKIT, "worker thread, instanceType=1, instanceName=%{public}s", name.c_str());
} else if (engine->IsTaskPoolThread()) {
type = InstanceType::TASK_POOL_THREAD_TYPE;
name = GetFuncNameFromError(napiEnv, params.exception);
TAG_LOGE(AAFwkTag::APPKIT, "task pool thread, instanceType=2, instanceName=%{public}s", name.c_str());
}
NapiErrorManager::GetInstance()->NotifyUncaughtException(napiEnv, params.exception, name,
static_cast<uint32_t>(type));
}
return false;
}
bool ApplicationDataManager::NotifyLeakObject(const LeakObject &leakObj)
{
std::lock_guard<std::mutex> lock(leakObserverMutex_);
if (leakObserver_) {
return leakObserver_(leakObj);
}
return false;
}
void ApplicationDataManager::SetLeakObserver(LeakObserverFunction leakCallback)
{
std::lock_guard<std::mutex> lock(leakObserverMutex_);
leakObserver_ = leakCallback;
}
void ApplicationDataManager::RegisterHasOnErrorCallback(HasOnErrorCallback hasOnErrorCallback)
{
std::lock_guard<std::mutex> lock(hasOnErrorCallbackMutex_);
hasOnErrorCallback_ = hasOnErrorCallback;
}
bool ApplicationDataManager::GetHasOnErrorCallback()
{
std::lock_guard<std::mutex> lock(hasOnErrorCallbackMutex_);
if (hasOnErrorCallback_) {
return hasOnErrorCallback_();
}
return false;
}
bool ApplicationDataManager::RegisterResourceObserver(RegisterResourceParams params, ResourceOverlimitCB cb)
{
std::lock_guard<std::mutex> lock(resourceMutex_);
if (cb == nullptr || params.appTelemetryLeakType == 0) {
cb = nullptr;
params.appTelemetryLeakType = 0;
TAG_LOGI(AAFwkTag::APPKIT, "will unregister");
}
resourceOverlimitCB_ = cb;
bool ret = WriteSandBoxXattr(params);
TAG_LOGI(AAFwkTag::APPKIT, "RegisterResourceObserver execute result: %{public}d", ret);
return ret;
}
bool ApplicationDataManager::WriteSandBoxXattr(RegisterResourceParams params)
{
std::string xattrValue = DFX_GetAppRunningUniqueId();
xattrValue += "," + std::to_string(params.appTelemetryLeakType);
bool ret = (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_XATTR, xattrValue.c_str(), strlen(xattrValue.c_str()), 0) == 0);
if (!ret) {
TAG_LOGE(AAFwkTag::APPKIT, "failed write atlt, path: %{public}s, xattr: %{public}s, err: %{public}s",
RSRC_OBSV_PATH, RSRC_OBSV_XATTR, strerror(errno));
return false;
}
string threshold = std::to_string(params.thresholdPss);
ret = (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_PSS, threshold.c_str(), threshold.length(), 0) == 0);
threshold = std::to_string(params.thresholdGpu);
ret = ret && (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_GPU, threshold.c_str(), threshold.length(), 0) == 0);
threshold = std::to_string(params.thresholdFd);
ret = ret && (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_FD, threshold.c_str(), threshold.length(), 0) == 0);
threshold = std::to_string(params.thresholdRAT);
ret = ret && (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_RAT, threshold.c_str(), threshold.length(), 0) == 0);
threshold = std::to_string(params.thresholdRNH);
ret = ret && (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_RNH, threshold.c_str(), threshold.length(), 0) == 0);
threshold = std::to_string(params.thresholdKH);
ret = ret && (setxattr(RSRC_OBSV_PATH, RSRC_OBSV_TRSHD_XATTR_KH, threshold.c_str(), threshold.length(), 0) == 0);
if (!ret) {
TAG_LOGE(AAFwkTag::APPKIT, "failed write threshold, path: %{public}s, err: %{public}s", RSRC_OBSV_PATH,
strerror(errno));
}
return ret;
}
GpuHookSize ApplicationDataManager::ParseGpuHookSize(const std::string &gpuHookSizeStr)
{
GpuHookSize gpuHookSize;
if (gpuHookSizeStr.empty()) {
return gpuHookSize;
}
constexpr size_t MAX_GPU_HOOK_SIZE_STR_LEN = 4096;
constexpr size_t MAX_GPU_HOOK_ENTRIES = 64;
if (gpuHookSizeStr.size() > MAX_GPU_HOOK_SIZE_STR_LEN) {
TAG_LOGE(AAFwkTag::APPKIT, "gpuHookSize string too long: %{public}zu", gpuHookSizeStr.size());
return gpuHookSize;
}
nlohmann::json jsonObj;
try {
jsonObj = nlohmann::json::parse(gpuHookSizeStr);
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) {
const std::string &type = it.key();
const auto &typeObj = it.value();
if (!typeObj.contains("first") || !typeObj.contains("second")) {
continue;
}
if (!typeObj["first"].is_array() || !typeObj["second"].is_array()) {
continue;
}
if (typeObj["first"].size() != PAIR_SIZE || typeObj["second"].size() != PAIR_SIZE) {
continue;
}
if (!typeObj["first"][0].is_number_unsigned() || !typeObj["first"][1].is_number_unsigned() ||
!typeObj["second"][0].is_number_unsigned() || !typeObj["second"][1].is_number_unsigned()) {
TAG_LOGE(AAFwkTag::APPKIT, "Invalid gpuHookSize type %{public}s: array elements must unsigned numbers",
type.c_str());
continue;
}
Range firstRange = {typeObj["first"][0].get<uint64_t>(), typeObj["first"][1].get<uint64_t>()};
Range secondRange = {typeObj["second"][0].get<uint64_t>(), typeObj["second"][1].get<uint64_t>()};
gpuHookSize[type] = HookSize{firstRange, secondRange};
}
} catch (const nlohmann::json::exception &e) {
TAG_LOGE(AAFwkTag::APPKIT, "Failed to parse gpuHookSize json: %{public}s", e.what());
return gpuHookSize;
}
return gpuHookSize;
}
void ApplicationDataManager::LogGpuHookSize(const GpuHookSize &gpuHookSize)
{
if (!HiLogIsLoggable(static_cast<uint32_t>(AAFwkTag::APPKIT), GetTagInfoFromDomainId(AAFwkTag::APPKIT),
LOG_DEBUG)) {
return;
}
if (gpuHookSize.empty()) {
TAG_LOGD(AAFwkTag::APPKIT, "gpuHookSize is empty");
return;
}
for (const auto &entry : gpuHookSize) {
const std::string &type = entry.first;
const HookSize &hookSize = entry.second;
const Range &firstRange = hookSize.first;
const Range &secondRange = hookSize.second;
TAG_LOGD(
AAFwkTag::APPKIT, "gpuHookSize type: %{public}s, first: [%{public}" PRIu64 ", %{public}" PRIu64
"], second: [%{public}" PRIu64 ", %{public}" PRIu64 "]",
type.c_str(), static_cast<uint64_t>(firstRange.first), static_cast<uint64_t>(firstRange.second),
static_cast<uint64_t>(secondRange.first), static_cast<uint64_t>(secondRange.second));
}
}
void ApplicationDataManager::NotifyAppTelemetry(AppTelemetryLeakType atLeakType, const std::string &gpuHookSizeStr)
{
GpuHookSize gpuHookSize = ParseGpuHookSize(gpuHookSizeStr);
LogGpuHookSize(gpuHookSize);
std::lock_guard<std::mutex> lock(resourceMutex_);
if (resourceOverlimitCB_ == nullptr) {
TAG_LOGE(AAFwkTag::APPKIT, "resourceOverlimitCB_ is nullptr");
return;
}
AppTelemetryObject atObj{
.atLeakType = atLeakType,
.runningId = DFX_GetAppRunningUniqueId(),
.gpuHookSize = gpuHookSize,
};
TAG_LOGI(AAFwkTag::APPKIT, "start callback resource overlimit");
resourceOverlimitCB_(atObj);
}
void ApplicationDataManager::NotifyAppFault(const FaultData &faultData)
{
if (!faultData.notifyApp) {
return;
}
if (faultData.faultType == FaultDataType::APP_TELEMETRY) {
NotifyAppTelemetry(faultData.atLeakType, faultData.gpuHookSize);
return;
}
if (faultData.faultType == FaultDataType::RESOURCE_CONTROL) {
LeakObject leakObj = {
.leakType = faultData.leakObject.leakType,
.leakSize = faultData.leakObject.leakSize,
.detailInfo = faultData.leakObject.detailInfo,
};
NotifyLeakObject(leakObj);
return;
}
ErrorObject faultErrorObj = {
.name = faultData.errorObject.name,
.message = faultData.errorObject.message,
.stack = faultData.errorObject.stack
};
NotifyExceptionObject(faultErrorObj);
}
}
}