• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }