• 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 "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 }