1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "napi_hidebug_dump.h"
17
18 #include <atomic>
19 #include <cinttypes>
20 #include <cstring>
21 #include <fcntl.h>
22 #include <memory>
23
24 #include <sys/statvfs.h>
25
26 #include "application_context.h"
27 #include "common.h"
28 #include "error_code.h"
29 #include "hiappevent_util.h"
30 #include "hidebug_util.h"
31 #include "hisysevent.h"
32 #include "napi_util.h"
33 #include "parameters.h"
34
35 #include "hilog/log.h"
36 #include "napi/native_api.h"
37 #include "napi/native_node_api.h"
38 #include "native_engine/native_engine.h"
39
40 namespace OHOS {
41 namespace HiviewDFX {
42 namespace {
43 #undef LOG_DOMAIN
44 #define LOG_DOMAIN 0xD002D0A
45 #undef LOG_TAG
46 #define LOG_TAG "HidebugDump"
47
48 constexpr auto SLASH_STR = "/";
49 constexpr auto HEAPSNAPSHOT_FILE = ".heapsnapshot";
50 constexpr auto RAW_HEAP_RECORD_FILE = "rawheap";
51 constexpr auto RAW_HEAP_FILE_EXT = ".rawheap";
52 constexpr auto HIDEBUG_DUMP_QUOTA = "user.hidebugdump.quota";
53 constexpr auto DUMP_MAX_COUNT = "persist.hiview.hidebugdump.maxcount";
54 constexpr auto PROCESS_DUMP_MAX_COUNT = "persist.hiview.hidebugdump.process.maxcount";
55 constexpr auto QUOTA_VALUE_LENGTH = 256;
56 constexpr int ONE_VALUE_LIMIT = 1;
57 constexpr int64_t MS_TO_NS = 1000 * 1000;
58 constexpr int64_t S_TO_NS = MS_TO_NS * 1000;
59 std::atomic<int> g_dumpingCount = 0;
60
61 constexpr std::pair<int, const char*> DumpErrCodeMap[] = {
62 {ErrorCode::PARAMETER_ERROR, "Invalid parameter."},
63 {DumpRawHeapErrors::REPEAT_DUMPING, "Repeated data dump."},
64 {DumpRawHeapErrors::FAILED_CREATE_FILE, "Failed to create dump file."},
65 {DumpRawHeapErrors::LOW_DISK_SPACE, "Disk remaining space too low."},
66 {DumpRawHeapErrors::QUOTA_EXCEEDED, "Quota exceeded."},
67 {DumpRawHeapErrors::FORK_FAILED, "Fork operation failed."},
68 {DumpRawHeapErrors::FAILED_WAIT_CHILD_PROCESS_FINISHED, "Failed to wait for the child process to finish."},
69 {DumpRawHeapErrors::TIMEOUT_WAIT_CHILD_PROCESS_FINISHED, "Timeout while waiting for the child process to finish."}
70 };
71
DumpLimitEnable()72 bool DumpLimitEnable()
73 {
74 return !IsDeveloperOptionsEnabled() && !IsDebuggableHap() && !IsBetaVersion();
75 }
76
InitRawHeapFile(std::string & fileName)77 bool InitRawHeapFile(std::string& fileName)
78 {
79 std::string filePath = GetProcessDir(DirectoryType::CACHE);
80 if (filePath.empty()) {
81 return false;
82 }
83 fileName = filePath + SLASH_STR + RAW_HEAP_RECORD_FILE;
84 constexpr unsigned fileMode = 0700;
85 return CreateDirectory(fileName, fileMode);
86 }
87
LoadDumpRecords()88 std::vector<std::string> LoadDumpRecords()
89 {
90 std::vector<std::string> records;
91 std::string rawHeapFile;
92 if (!InitRawHeapFile(rawHeapFile)) {
93 return records;
94 }
95 std::string quota;
96 if (!GetXAttr(rawHeapFile, HIDEBUG_DUMP_QUOTA, quota, QUOTA_VALUE_LENGTH)) {
97 return records;
98 }
99 const int64_t currentTime = GetRealNanoSecondsTimestamp();
100 constexpr int64_t timeout = 24 * 60 * 60 * S_TO_NS;
101 const int64_t validTime = currentTime - timeout;
102 return SplitStr(quota, ',', [validTime, currentTime](const auto& record) {
103 if (record.empty()) {
104 return false;
105 }
106 constexpr int decBase = 10;
107 int64_t num = std::strtoll(record.c_str(), nullptr, decBase);
108 return num > validTime && num < currentTime;
109 });
110 }
111
AppendMetaData(const std::string & newPath)112 bool AppendMetaData(const std::string& newPath)
113 {
114 auto rawHeapFileSize = static_cast<uint32_t>(GetFileSize(newPath));
115 if (rawHeapFileSize == 0) {
116 HILOG_ERROR(LOG_CORE, "%{public}s is not existed or empty.", newPath.c_str());
117 return false;
118 }
119 constexpr auto metaDataPath = "/system/lib64/module/arkcompiler/metadata.json";
120 auto metaDataFileSize = static_cast<uint32_t>(GetFileSize(metaDataPath));
121 if (metaDataFileSize == 0) {
122 HILOG_ERROR(LOG_CORE, "%{public}s is not existed or empty.", metaDataPath);
123 return false;
124 }
125 auto targetFile = SmartFile::OpenFile(newPath, "ab");
126 if (targetFile == nullptr) {
127 return false;
128 }
129 auto metaData = SmartFile::OpenFile(metaDataPath, "rb");
130 if (metaData == nullptr) {
131 return false;
132 }
133 constexpr auto buffSize = 1024;
134 char buffer[buffSize];
135 size_t bytesRead;
136 while ((bytesRead = metaData->Read(buffer, 1, buffSize)) > 0) {
137 if (!targetFile->Write(buffer, 1, bytesRead)) {
138 return false;
139 }
140 }
141 return targetFile->Write(&rawHeapFileSize, sizeof(rawHeapFileSize), 1) &&
142 targetFile->Write(&metaDataFileSize, sizeof(metaDataFileSize), 1);
143 }
144
InsertDumpRecord()145 bool InsertDumpRecord()
146 {
147 std::vector<std::string> records = LoadDumpRecords();
148 records.emplace_back(std::to_string(GetRealNanoSecondsTimestamp()));
149 std::string recordStr;
150 for (const auto& record : records) {
151 recordStr += (record + ",");
152 }
153 std::string rawHeapFile;
154 if (!InitRawHeapFile(rawHeapFile)) {
155 return false;
156 }
157 return SetXAttr(rawHeapFile, HIDEBUG_DUMP_QUOTA, recordStr);
158 }
159
ReportDumpSuccess()160 bool ReportDumpSuccess()
161 {
162 int32_t ret = HiSysEventWrite(OHOS::HiviewDFX::HiSysEvent::Domain::FRAMEWORK,
163 "ARK_STATS_DUMP",
164 OHOS::HiviewDFX::HiSysEvent::EventType::FAULT,
165 "TYPE", "hidebugDump");
166 if (ret != 0) {
167 HILOG_ERROR(LOG_CORE, "failed to report dump success ret %{public}d.", ret);
168 }
169 return true;
170 }
171
CheckDeviceQuota()172 bool CheckDeviceQuota()
173 {
174 return OHOS::system::GetIntParameter(DUMP_MAX_COUNT, 0) > 0;
175 }
176
CheckProcessQuota()177 bool CheckProcessQuota()
178 {
179 int limit = OHOS::system::GetIntParameter(PROCESS_DUMP_MAX_COUNT, 0);
180 if (limit <= 0) {
181 return false;
182 }
183 auto size = LoadDumpRecords().size();
184 if (size >= static_cast<size_t>(limit)) {
185 return false;
186 }
187 return true;
188 }
189
CheckDumpDiskSpace()190 bool CheckDumpDiskSpace()
191 {
192 struct statvfs stat{};
193 std::string appDir = GetProcessDir(DirectoryType::FILE);
194 if (statvfs(appDir.c_str(), &stat) != 0) {
195 return false;
196 }
197 constexpr uint64_t dumpNeedSpace = 30ULL * 1024 * 1024 * 1024;
198 uint64_t freeSize = stat.f_bsize * stat.f_bfree;
199 if (freeSize <= dumpNeedSpace) {
200 HILOG_ERROR(LOG_CORE, "disk space is not enough, remains %{public}" PRIu64 "B.", freeSize);
201 return false;
202 }
203 return true;
204 }
205
GenerateDumpFile(int64_t currentTime)206 std::string GenerateDumpFile(int64_t currentTime)
207 {
208 std::string filesDir = GetProcessDir(DirectoryType::FILE);
209 if (filesDir.empty()) {
210 return "";
211 }
212 return filesDir + SLASH_STR + "hidebug-jsheap-" + std::to_string(getpid()) + "-" + std::to_string(gettid()) +
213 "-" + std::to_string(currentTime / MS_TO_NS) + RAW_HEAP_FILE_EXT;
214 }
215 }
216
DumpHeapSnapshot(const std::string & fileName,napi_env env)217 napi_value DumpHeapSnapshot(const std::string& fileName, napi_env env)
218 {
219 std::string filesDir = GetProcessDir(DirectoryType::FILE);
220 if (filesDir.empty()) {
221 return CreateErrorMessage(env, "Get App files dir failed.");
222 }
223 std::string filePath = filesDir + SLASH_STR + fileName + HEAPSNAPSHOT_FILE;
224 if (!IsLegalPath(filePath)) {
225 return CreateErrorMessage(env, "input fileName is illegal.");
226 }
227 if (!CreateFile(filePath)) {
228 return CreateErrorMessage(env, "file created failed.");
229 }
230 NativeEngine *engine = reinterpret_cast<NativeEngine*>(env);
231 engine->DumpHeapSnapshot(filePath, true, DumpFormat::JSON, false, true);
232 return CreateUndefined(env);
233 }
234
DumpHeapData(napi_env env,napi_callback_info info)235 napi_value DumpHeapData(napi_env env, napi_callback_info info)
236 {
237 ApiInvokeRecorder apiInvokeRecorder("dumpHeapData");
238 std::string fileName = GetFileNameParam(env, info);
239 return DumpHeapSnapshot(fileName, env);
240 }
241
DumpJsHeapData(napi_env env,napi_callback_info info)242 napi_value DumpJsHeapData(napi_env env, napi_callback_info info)
243 {
244 ApiInvokeRecorder apiInvokeRecorder("dumpJsHeapData");
245 std::string fileName;
246 if (!GetTheOnlyStringParam(env, info, fileName)) {
247 std::string paramErrorMessage = "Invalid parameter, require a string parameter.";
248 napi_throw_error(env, std::to_string(ErrorCode::PARAMETER_ERROR).c_str(), paramErrorMessage.c_str());
249 return CreateUndefined(env);
250 }
251 return DumpHeapSnapshot(fileName, env);
252 }
253
GetDumpJsRawHeapParams(napi_env env,napi_callback_info info,std::string & filePath,bool & isGc,int & fd)254 int32_t GetDumpJsRawHeapParams(napi_env env, napi_callback_info info, std::string& filePath, bool& isGc, int& fd)
255 {
256 size_t argc = ONE_VALUE_LIMIT; // expected param length.
257 napi_value argv = nullptr;
258 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
259 if (g_dumpingCount.fetch_add(1) != 0) {
260 return DumpRawHeapErrors::REPEAT_DUMPING;
261 }
262 if (argc > ONE_VALUE_LIMIT || (argc > 0 && !GetNapiBoolValue(env, argv, isGc))) {
263 return ErrorCode::PARAMETER_ERROR;
264 }
265 if (!CheckDumpDiskSpace()) {
266 return DumpRawHeapErrors::LOW_DISK_SPACE;
267 }
268 if (DumpLimitEnable() && (!CheckDeviceQuota() || !CheckProcessQuota())) {
269 return DumpRawHeapErrors::QUOTA_EXCEEDED;
270 }
271 filePath = GenerateDumpFile(GetRealNanoSecondsTimestamp());
272 if (!IsLegalPath(filePath)) {
273 return DumpRawHeapErrors::FAILED_CREATE_FILE;
274 }
275 fd = open(filePath.c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP); // -rw-r-----
276 return fd < 0 ? DumpRawHeapErrors::FAILED_CREATE_FILE : 0;
277 }
278
ResolveErrorCode(napi_env env,const int errCode)279 napi_value ResolveErrorCode(napi_env env, const int errCode)
280 {
281 if (errCode == 0) {
282 return nullptr;
283 }
284 HILOG_ERROR(LOG_CORE, "%{public}s for %{public}d.", __func__, errCode);
285 for (const auto&[code, msg] : DumpErrCodeMap) {
286 if (code == errCode) {
287 return CreateErrorMessage(env, std::to_string(errCode), msg);
288 }
289 }
290 return nullptr;
291 }
292
TransferRetCodeToErrorCode(const int retCode)293 int TransferRetCodeToErrorCode(const int retCode)
294 {
295 constexpr int dumpSuccess = 0;
296 constexpr int forkFailed = 1;
297 constexpr int failedToWait = 2;
298 constexpr int waitTimeOut = 3;
299 switch (retCode) {
300 case dumpSuccess:
301 return 0;
302 case forkFailed:
303 return FORK_FAILED;
304 case failedToWait:
305 return FAILED_WAIT_CHILD_PROCESS_FINISHED;
306 case waitTimeOut:
307 return TIMEOUT_WAIT_CHILD_PROCESS_FINISHED;
308 default:
309 HILOG_ERROR(LOG_CORE, "unknown retCode %{public}d.", retCode);
310 return TIMEOUT_WAIT_CHILD_PROCESS_FINISHED;
311 }
312 }
313
ResolveDumpJsRawHeapData(napi_env env,napi_deferred deferred,const std::string & fileName)314 void ResolveDumpJsRawHeapData(napi_env env, napi_deferred deferred, const std::string& fileName)
315 {
316 if (!AppendMetaData(fileName)) {
317 HILOG_ERROR(LOG_CORE, "failed to append metadata to dump file.");
318 }
319 if (DumpLimitEnable()) {
320 if (!InsertDumpRecord()) {
321 HILOG_ERROR(LOG_CORE, "failed to insert process dump record.");
322 }
323 if (!ReportDumpSuccess()) {
324 HILOG_ERROR(LOG_CORE, "failed to report dump success event.");
325 }
326 }
327 g_dumpingCount.fetch_sub(1);
328 napi_value ret;
329 napi_create_string_utf8(env, fileName.c_str(), fileName.size(), &ret);
330 napi_resolve_deferred(env, deferred, ret);
331 }
332
DumpJsRawHeapData(napi_env env,napi_callback_info info)333 napi_value DumpJsRawHeapData(napi_env env, napi_callback_info info)
334 {
335 auto apiInvokeRecorder = std::make_shared<ApiInvokeRecorder>("dumpJsRawHeapData");
336 napi_deferred deferred = nullptr;
337 napi_value promise = nullptr;
338 napi_create_promise(env, &deferred, &promise);
339 bool isGc = true;
340 std::string fileName;
341 int fd = -1;
342 int32_t errCode = GetDumpJsRawHeapParams(env, info, fileName, isGc, fd);
343 napi_value err = ResolveErrorCode(env, errCode);
344 if (err != nullptr) {
345 g_dumpingCount.fetch_sub(1);
346 apiInvokeRecorder->SetErrorCode(errCode);
347 napi_reject_deferred(env, deferred, err);
348 return promise;
349 }
350 NativeEngine *engine = reinterpret_cast<NativeEngine*>(env);
351 /* When the interface is available, the fd will be set to -1. */
352 engine->DumpHeapSnapshot(fd, isGc, [apiInvokeRecorder, env, deferred, fileName] (uint8_t retCode) {
353 apiInvokeRecorder->SetErrorCode(retCode);
354 napi_send_event(env, [env, deferred, fileName, retCode]() {
355 napi_value err = ResolveErrorCode(env, TransferRetCodeToErrorCode(retCode));
356 if (err != nullptr) {
357 remove(fileName.c_str());
358 g_dumpingCount.fetch_sub(1);
359 napi_reject_deferred(env, deferred, err);
360 } else {
361 ResolveDumpJsRawHeapData(env, deferred, fileName);
362 }
363 }, napi_eprio_high);
364 });
365 if (fd >= 0) {
366 HILOG_ERROR(LOG_CORE, "DumpJsRawHeapData is not supported in current version.");
367 close(fd);
368 remove(fileName.c_str());
369 g_dumpingCount.fetch_sub(1);
370 err = ResolveErrorCode(env, DumpRawHeapErrors::TIMEOUT_WAIT_CHILD_PROCESS_FINISHED);
371 napi_reject_deferred(env, deferred, err);
372 }
373 return promise;
374 }
375 }
376 }