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 "error_code.h"
28 #include "hiappevent_util.h"
29 #include "hidebug_util.h"
30 #include "hisysevent.h"
31 #include "napi_util.h"
32 #include "parameters.h"
33
34 #include "hilog/log.h"
35 #include "napi/native_api.h"
36 #include "napi/native_node_api.h"
37 #include "native_engine/native_engine.h"
38
39 namespace OHOS {
40 namespace HiviewDFX {
41 namespace {
42 #undef LOG_DOMAIN
43 #define LOG_DOMAIN 0xD002D0A
44 #undef LOG_TAG
45 #define LOG_TAG "HidebugDump"
46
47 constexpr auto SLASH_STR = "/";
48 constexpr auto HEAPSNAPSHOT_FILE = ".heapsnapshot";
49 constexpr auto RAW_HEAP_RECORD_FILE = "rawheap";
50 constexpr auto RAW_HEAP_FILE_EXT = ".rawheap";
51 constexpr auto HIDEBUG_DUMP_QUOTA = "user.hidebugdump.quota";
52 constexpr auto DUMP_MAX_COUNT = "persist.hiview.hidebugdump.maxcount";
53 constexpr auto PROCESS_DUMP_MAX_COUNT = "persist.hiview.hidebugdump.process.maxcount";
54 constexpr auto QUOTA_VALUE_LENGTH = 256;
55 constexpr int ONE_VALUE_LIMIT = 1;
56 constexpr int64_t MS_TO_NS = 1000 * 1000;
57 constexpr int64_t S_TO_NS = MS_TO_NS * 1000;
58 std::atomic<int> g_dumpingCount = 0;
59
60 constexpr std::pair<int, const char*> DumpErrCodeMap[] = {
61 {ErrorCode::PARAMETER_ERROR, "Invalid parameter."},
62 {DumpRawHeapErrors::REPEAT_DUMPING, "It is currently dumping. Please wait until the dumping is completed "
63 "before making another call."},
64 {DumpRawHeapErrors::FAILED_CREATE_FILE, "Failed to create dump file."},
65 {DumpRawHeapErrors::LOW_DISK_SPACE, "Disk remaining space too low."},
66 {DumpRawHeapErrors::QUOTA_EXCEEDED, "The number of available invocations for the interface is insufficient. "
67 "Please wait until the number of available invocations for the interface is reset."},
68 {DumpRawHeapErrors::FORK_FAILED, "Fork operation failed."},
69 {DumpRawHeapErrors::FAILED_WAIT_CHILD_PROCESS_FINISHED, "Failed to wait for the child process to finish."},
70 {DumpRawHeapErrors::TIMEOUT_WAIT_CHILD_PROCESS_FINISHED, "Timeout while waiting for the child process to finish."}
71 };
72
DumpLimitEnable()73 bool DumpLimitEnable()
74 {
75 return !IsDeveloperOptionsEnabled() && !IsDebuggableHap() && !IsBetaVersion();
76 }
77
InitRawHeapFile(std::string & fileName)78 bool InitRawHeapFile(std::string& fileName)
79 {
80 std::string filePath = GetProcessDir(DirectoryType::CACHE);
81 if (filePath.empty()) {
82 return false;
83 }
84 fileName = filePath + SLASH_STR + RAW_HEAP_RECORD_FILE;
85 constexpr unsigned fileMode = 0700;
86 return CreateDirectory(fileName, fileMode);
87 }
88
LoadDumpRecords()89 std::vector<std::string> LoadDumpRecords()
90 {
91 std::vector<std::string> records;
92 std::string rawHeapFile;
93 if (!InitRawHeapFile(rawHeapFile)) {
94 return records;
95 }
96 std::string quota;
97 if (!GetXAttr(rawHeapFile, HIDEBUG_DUMP_QUOTA, quota, QUOTA_VALUE_LENGTH)) {
98 return records;
99 }
100 const int64_t currentTime = GetRealNanoSecondsTimestamp();
101 constexpr int64_t timeout = 24 * 60 * 60 * S_TO_NS;
102 const int64_t validTime = currentTime - timeout;
103 return SplitStr(quota, ',', [validTime, currentTime](const auto& record) {
104 if (record.empty()) {
105 return false;
106 }
107 constexpr int decBase = 10;
108 int64_t num = std::strtoll(record.c_str(), nullptr, decBase);
109 return num > validTime && num < currentTime;
110 });
111 }
112
AppendMetaData(const std::string & newPath)113 bool AppendMetaData(const std::string& newPath)
114 {
115 auto rawHeapFileSize = static_cast<uint32_t>(GetFileSize(newPath));
116 if (rawHeapFileSize == 0) {
117 HILOG_ERROR(LOG_CORE, "%{public}s is not existed or empty.", newPath.c_str());
118 return false;
119 }
120 constexpr auto metaDataPath = "/system/lib64/module/arkcompiler/metadata.json";
121 auto metaDataFileSize = static_cast<uint32_t>(GetFileSize(metaDataPath));
122 if (metaDataFileSize == 0) {
123 HILOG_ERROR(LOG_CORE, "%{public}s is not existed or empty.", metaDataPath);
124 return false;
125 }
126 auto targetFile = SmartFile::OpenFile(newPath, "ab");
127 if (targetFile == nullptr) {
128 return false;
129 }
130 auto metaData = SmartFile::OpenFile(metaDataPath, "rb");
131 if (metaData == nullptr) {
132 return false;
133 }
134 constexpr auto buffSize = 1024;
135 char buffer[buffSize];
136 size_t bytesRead;
137 while ((bytesRead = metaData->Read(buffer, 1, buffSize)) > 0) {
138 if (!targetFile->Write(buffer, 1, bytesRead)) {
139 return false;
140 }
141 }
142 return targetFile->Write(&rawHeapFileSize, sizeof(rawHeapFileSize), 1) &&
143 targetFile->Write(&metaDataFileSize, sizeof(metaDataFileSize), 1);
144 }
145
InsertDumpRecord()146 bool InsertDumpRecord()
147 {
148 std::vector<std::string> records = LoadDumpRecords();
149 records.emplace_back(std::to_string(GetRealNanoSecondsTimestamp()));
150 std::string recordStr;
151 for (const auto& record : records) {
152 recordStr += (record + ",");
153 }
154 std::string rawHeapFile;
155 if (!InitRawHeapFile(rawHeapFile)) {
156 return false;
157 }
158 return SetXAttr(rawHeapFile, HIDEBUG_DUMP_QUOTA, recordStr);
159 }
160
ReportDumpSuccess()161 bool ReportDumpSuccess()
162 {
163 int32_t ret = HiSysEventWrite(OHOS::HiviewDFX::HiSysEvent::Domain::FRAMEWORK,
164 "ARK_STATS_DUMP",
165 OHOS::HiviewDFX::HiSysEvent::EventType::FAULT,
166 "TYPE", "hidebugDump");
167 if (ret != 0) {
168 HILOG_ERROR(LOG_CORE, "failed to report dump success ret %{public}d.", ret);
169 }
170 return true;
171 }
172
CheckDeviceQuota()173 bool CheckDeviceQuota()
174 {
175 return OHOS::system::GetIntParameter(DUMP_MAX_COUNT, 0) > 0;
176 }
177
CheckProcessQuota()178 bool CheckProcessQuota()
179 {
180 int limit = OHOS::system::GetIntParameter(PROCESS_DUMP_MAX_COUNT, 0);
181 if (limit <= 0) {
182 return false;
183 }
184 auto size = LoadDumpRecords().size();
185 if (size >= static_cast<size_t>(limit)) {
186 return false;
187 }
188 return true;
189 }
190
CheckDumpDiskSpace()191 bool CheckDumpDiskSpace()
192 {
193 struct statvfs stat{};
194 std::string appDir = GetProcessDir(DirectoryType::FILE);
195 if (statvfs(appDir.c_str(), &stat) != 0) {
196 return false;
197 }
198 constexpr uint64_t dumpNeedSpace = 30ULL * 1024 * 1024 * 1024;
199 uint64_t freeSize = stat.f_bsize * stat.f_bfree;
200 if (freeSize <= dumpNeedSpace) {
201 HILOG_ERROR(LOG_CORE, "disk space is not enough, remains %{public}" PRIu64 "B.", freeSize);
202 return false;
203 }
204 return true;
205 }
206
GenerateDumpFile(int64_t currentTime)207 std::string GenerateDumpFile(int64_t currentTime)
208 {
209 std::string filesDir = GetProcessDir(DirectoryType::FILE);
210 if (filesDir.empty()) {
211 return "";
212 }
213 return filesDir + SLASH_STR + "hidebug-jsheap-" + std::to_string(getpid()) + "-" + std::to_string(gettid()) +
214 "-" + std::to_string(currentTime / MS_TO_NS) + RAW_HEAP_FILE_EXT;
215 }
216 }
217
DumpHeapSnapshot(const std::string & fileName,napi_env env)218 napi_value DumpHeapSnapshot(const std::string& fileName, napi_env env)
219 {
220 std::string filesDir = GetProcessDir(DirectoryType::FILE);
221 if (filesDir.empty()) {
222 return CreateErrorMessage(env, "Get App files dir failed.");
223 }
224 std::string filePath = filesDir + SLASH_STR + fileName + HEAPSNAPSHOT_FILE;
225 if (!IsLegalPath(filePath)) {
226 return CreateErrorMessage(env, "input fileName is illegal.");
227 }
228 if (!CreateFile(filePath)) {
229 return CreateErrorMessage(env, "file created failed.");
230 }
231 NativeEngine *engine = reinterpret_cast<NativeEngine*>(env);
232 engine->DumpHeapSnapshot(filePath, true, DumpFormat::JSON, false, true);
233 return CreateUndefined(env);
234 }
235
DumpHeapData(napi_env env,napi_callback_info info)236 napi_value DumpHeapData(napi_env env, napi_callback_info info)
237 {
238 ApiInvokeRecorder apiInvokeRecorder("dumpHeapData");
239 std::string fileName = GetFileNameParam(env, info);
240 return DumpHeapSnapshot(fileName, env);
241 }
242
DumpJsHeapData(napi_env env,napi_callback_info info)243 napi_value DumpJsHeapData(napi_env env, napi_callback_info info)
244 {
245 ApiInvokeRecorder apiInvokeRecorder("dumpJsHeapData");
246 std::string fileName;
247 if (!GetTheOnlyStringParam(env, info, fileName)) {
248 std::string paramErrorMessage = "Invalid parameter, require a string parameter.";
249 napi_throw_error(env, std::to_string(ErrorCode::PARAMETER_ERROR).c_str(), paramErrorMessage.c_str());
250 return CreateUndefined(env);
251 }
252 return DumpHeapSnapshot(fileName, env);
253 }
254
GetDumpJsRawHeapParams(napi_env env,napi_callback_info info,std::string & filePath,bool & isGc)255 int32_t GetDumpJsRawHeapParams(napi_env env, napi_callback_info info, std::string& filePath, bool& isGc)
256 {
257 size_t argc = ONE_VALUE_LIMIT; // expected param length.
258 napi_value argv = nullptr;
259 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
260 if (g_dumpingCount.fetch_add(1) != 0) {
261 return DumpRawHeapErrors::REPEAT_DUMPING;
262 }
263 if (argc > ONE_VALUE_LIMIT || (argc > 0 && !GetNapiBoolValue(env, argv, isGc))) {
264 return ErrorCode::PARAMETER_ERROR;
265 }
266 if (!CheckDumpDiskSpace()) {
267 return DumpRawHeapErrors::LOW_DISK_SPACE;
268 }
269 if (DumpLimitEnable() && (!CheckDeviceQuota() || !CheckProcessQuota())) {
270 return DumpRawHeapErrors::QUOTA_EXCEEDED;
271 }
272 filePath = GenerateDumpFile(GetRealNanoSecondsTimestamp());
273 if (!CreateFile(filePath)) {
274 return DumpRawHeapErrors::FAILED_CREATE_FILE;
275 }
276 return 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 int32_t errCode = GetDumpJsRawHeapParams(env, info, fileName, isGc);
342 napi_value err = ResolveErrorCode(env, errCode);
343 if (err != nullptr) {
344 g_dumpingCount.fetch_sub(1);
345 apiInvokeRecorder->SetErrorCode(errCode);
346 napi_reject_deferred(env, deferred, err);
347 return promise;
348 }
349 NativeEngine *engine = reinterpret_cast<NativeEngine*>(env);
350 /* When the interface is available, the fd will be set to -1. */
351 engine->DumpHeapSnapshot(isGc, fileName, [apiInvokeRecorder, env, deferred, fileName] (uint8_t retCode) {
352 apiInvokeRecorder->SetErrorCode(retCode);
353 napi_send_event(env, [env, deferred, fileName, retCode]() {
354 napi_value err = ResolveErrorCode(env, TransferRetCodeToErrorCode(retCode));
355 if (err != nullptr) {
356 remove(fileName.c_str());
357 g_dumpingCount.fetch_sub(1);
358 napi_reject_deferred(env, deferred, err);
359 } else {
360 ResolveDumpJsRawHeapData(env, deferred, fileName);
361 }
362 }, napi_eprio_high);
363 });
364 return promise;
365 }
366 }
367 }