1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
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#include <atomic>
16#include <chrono>
17#include <fcntl.h>
18#include <sys/wait.h>
19#include <sys/prctl.h>
20#include <sys/stat.h>
21#include <sys/syscall.h>
22#include <thread>
23#include <unistd.h>
24#include "ecmascript/dfx/hprof/heap_profiler.h"
25
26#include "ecmascript/checkpoint/thread_state_transition.h"
27#include "ecmascript/dfx/hprof/heap_snapshot.h"
28#include "ecmascript/jspandafile/js_pandafile_manager.h"
29#include "ecmascript/mem/heap-inl.h"
30#include "ecmascript/mem/shared_heap/shared_concurrent_sweeper.h"
31#include "ecmascript/base/block_hook_scope.h"
32#include "ecmascript/dfx/hprof/heap_root_visitor.h"
33#include "ecmascript/mem/object_xray.h"
34
35#if defined(ENABLE_DUMP_IN_FAULTLOG)
36#include "faultloggerd_client.h"
37#endif
38
39namespace panda::ecmascript {
40static pid_t ForkBySyscall(void)
41{
42#ifdef SYS_fork
43    return syscall(SYS_fork);
44#else
45    return syscall(SYS_clone, SIGCHLD, 0);
46#endif
47}
48
49std::pair<bool, NodeId> EntryIdMap::FindId(JSTaggedType addr)
50{
51    auto it = idMap_.find(addr);
52    if (it == idMap_.end()) {
53        return std::make_pair(false, GetNextId()); // return nextId if entry not exits
54    } else {
55        return std::make_pair(true, it->second);
56    }
57}
58
59bool EntryIdMap::InsertId(JSTaggedType addr, NodeId id)
60{
61    auto it = idMap_.find(addr);
62    if (it == idMap_.end()) {
63        idMap_.emplace(addr, id);
64        return true;
65    }
66    idMap_[addr] = id;
67    return false;
68}
69
70bool EntryIdMap::EraseId(JSTaggedType addr)
71{
72    auto it = idMap_.find(addr);
73    if (it == idMap_.end()) {
74        return false;
75    }
76    idMap_.erase(it);
77    return true;
78}
79
80bool EntryIdMap::Move(JSTaggedType oldAddr, JSTaggedType forwardAddr)
81{
82    if (oldAddr == forwardAddr) {
83        return true;
84    }
85    auto it = idMap_.find(oldAddr);
86    if (it != idMap_.end()) {
87        NodeId id = it->second;
88        idMap_.erase(it);
89        idMap_[forwardAddr] = id;
90        return true;
91    }
92    return false;
93}
94
95void EntryIdMap::UpdateEntryIdMap(HeapSnapshot *snapshot)
96{
97    if (snapshot == nullptr) {
98        LOG_ECMA(FATAL) << "EntryIdMap::UpdateEntryIdMap:snapshot is nullptr";
99        UNREACHABLE();
100    }
101    auto nodes = snapshot->GetNodes();
102    CUnorderedMap<JSTaggedType, NodeId> newIdMap;
103    for (auto node : *nodes) {
104        auto addr = node->GetAddress();
105        auto it = idMap_.find(addr);
106        if (it != idMap_.end()) {
107            newIdMap.emplace(addr, it->second);
108        }
109    }
110    idMap_.clear();
111    idMap_ = newIdMap;
112}
113
114HeapProfiler::HeapProfiler(const EcmaVM *vm) : vm_(vm), stringTable_(vm), chunk_(vm->GetNativeAreaAllocator())
115{
116    isProfiling_ = false;
117    entryIdMap_ = GetChunk()->New<EntryIdMap>();
118}
119
120HeapProfiler::~HeapProfiler()
121{
122    JSPandaFileManager::GetInstance()->ClearNameMap();
123    ClearSnapshot();
124    GetChunk()->Delete(entryIdMap_);
125}
126
127void HeapProfiler::AllocationEvent(TaggedObject *address, size_t size)
128{
129    DISALLOW_GARBAGE_COLLECTION;
130    if (isProfiling_) {
131        // Id will be allocated later while add new node
132        if (heapTracker_ != nullptr) {
133            heapTracker_->AllocationEvent(address, size);
134        }
135    }
136}
137
138void HeapProfiler::MoveEvent(uintptr_t address, TaggedObject *forwardAddress, size_t size)
139{
140    LockHolder lock(mutex_);
141    if (isProfiling_) {
142        entryIdMap_->Move(static_cast<JSTaggedType>(address), reinterpret_cast<JSTaggedType>(forwardAddress));
143        if (heapTracker_ != nullptr) {
144            heapTracker_->MoveEvent(address, forwardAddress, size);
145        }
146    }
147}
148
149void HeapProfiler::UpdateHeapObjects(HeapSnapshot *snapshot)
150{
151    SharedHeap::GetInstance()->GetSweeper()->WaitAllTaskFinished();
152    snapshot->UpdateNodes();
153}
154
155void HeapProfiler::DumpHeapSnapshot([[maybe_unused]] const DumpSnapShotOption &dumpOption)
156{
157#if defined(ENABLE_DUMP_IN_FAULTLOG)
158    // Write in faultlog for heap leak.
159    int32_t fd;
160    if (dumpOption.isDumpOOM && dumpOption.dumpFormat == DumpFormat::BINARY) {
161        fd = RequestFileDescriptor(static_cast<int32_t>(FaultLoggerType::JS_RAW_SNAPSHOT));
162    } else {
163        fd = RequestFileDescriptor(static_cast<int32_t>(FaultLoggerType::JS_HEAP_SNAPSHOT));
164    }
165    if (fd < 0) {
166        LOG_ECMA(ERROR) << "OOM Dump Write FD failed, fd" << fd;
167        return;
168    }
169    FileDescriptorStream stream(fd);
170    DumpHeapSnapshot(&stream, dumpOption);
171#endif
172}
173
174bool HeapProfiler::DoDump(Stream *stream, Progress *progress, const DumpSnapShotOption &dumpOption)
175{
176    DISALLOW_GARBAGE_COLLECTION;
177    int32_t heapCount = 0;
178    HeapSnapshot *snapshot = nullptr;
179    {
180        if (dumpOption.isFullGC) {
181            size_t heapSize = vm_->GetHeap()->GetLiveObjectSize();
182            LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot heap size " << heapSize;
183            heapCount = static_cast<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
184            if (progress != nullptr) {
185                progress->ReportProgress(0, heapCount);
186            }
187        }
188        snapshot = MakeHeapSnapshot(SampleType::ONE_SHOT, dumpOption);
189        ASSERT(snapshot != nullptr);
190    }
191    entryIdMap_->UpdateEntryIdMap(snapshot);
192    isProfiling_ = true;
193    if (progress != nullptr) {
194        progress->ReportProgress(heapCount, heapCount);
195    }
196    if (!stream->Good()) {
197        FileStream newStream(GenDumpFileName(dumpOption.dumpFormat));
198        auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, &newStream);
199        GetChunk()->Delete(snapshot);
200        return serializerResult;
201    }
202    auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, stream);
203    GetChunk()->Delete(snapshot);
204    return serializerResult;
205}
206
207ChunkDecoder::ChunkDecoder(char *mAddr, uint64_t fSize) : mapAddr(mAddr), fileSize(fSize)
208{
209    auto u64Ptr = reinterpret_cast<uint64_t *>(mapAddr);
210    size_t currInd = 0;
211    heapObjCnt = u64Ptr[currInd++];
212    rootObjCnt = u64Ptr[currInd++];
213    shareObjCnt = u64Ptr[currInd++];
214    strTableOffset = u64Ptr[currInd++];
215    LOG_ECMA(INFO) << "ChunkDecoder init: heapObjCnt=" << heapObjCnt << ", rootObjCnt=" << rootObjCnt
216                    << ", ShareObjCnt=" << shareObjCnt << std::hex << ", strTableOffset=0x" << strTableOffset;
217    auto cPtr = mapAddr;
218    CUnorderedSet<uint64_t> rootAddrSet;
219    for (uint64_t i = 0; i < rootObjCnt; i++) {
220        rootAddrSet.insert(u64Ptr[currInd++]);
221    }
222    auto &objInfoVec = rawHeapArgs.rawObjInfoVec;
223    auto table = reinterpret_cast<AddrTableItem *>(&u64Ptr[currInd]);
224    for (uint64_t i = 0; i < heapObjCnt; ++i) {
225        auto objInfo = new RawHeapObjInfo();
226        objInfo->tInfo = &table[i];
227        if (rootAddrSet.find(objInfo->tInfo->addr) != rootAddrSet.end()) {
228            objInfo->isRoot = true;
229        } else {
230            objInfo->isRoot = false;
231        }
232        objInfoVec.push_back(objInfo);
233        auto objMem = cPtr + objInfo->tInfo->offset;
234        objInfo->newAddr = objMem;
235        rawHeapArgs.oldAddrMapObjInfo.emplace(objInfo->tInfo->addr, objInfo);
236    }
237    DecodeStrTable(cPtr);
238}
239
240void ChunkDecoder::DecodeStrTable(const char *charPtr)
241{
242    auto currInd = strTableOffset;
243    if (currInd >= fileSize) {
244        LOG_ECMA(ERROR) << "DecodeStrTable no str table: str=" << charPtr;
245        return;
246    }
247    auto &strTableIdMap = rawHeapArgs.strTableIdMapNewStr;
248
249    auto u64Ptr = reinterpret_cast<const uint64_t *>(charPtr + currInd);
250    auto strCnt = *u64Ptr;
251    LOG_ECMA(INFO) << "DecodeStrTable: strCnt=" << std::dec << strCnt;
252    while (currInd < fileSize && strTableIdMap.size() < strCnt) {
253        auto id = *reinterpret_cast<const uint64_t *>(charPtr + currInd);
254        currInd += sizeof(uint64_t);
255        if (currInd >= fileSize) {
256            break;
257        }
258        auto *currPtr = &charPtr[currInd];
259        auto currSize = strlen(currPtr) + 1;
260        if (currSize == 1) {
261            currInd += currSize;
262            continue;
263        }
264        strTableIdMap[id] = currPtr;
265        currInd += currSize;
266    }
267    LOG_ECMA(INFO) << "DecodeStrTable finished: strTableVec.size=" << strTableIdMap.size();
268}
269
270static uint64_t CheckAndRemoveWeak(JSTaggedValue &value, uint64_t originalAddr)
271{
272    if (!value.IsWeak()) {
273        return originalAddr;
274    }
275    JSTaggedValue weakValue(originalAddr);
276    weakValue.RemoveWeakTag();
277    return weakValue.GetRawData();
278}
279
280static uint64_t CheckAndAddWeak(JSTaggedValue &value, uint64_t originalAddr)
281{
282    if (!value.IsWeak()) {
283        return originalAddr;
284    }
285    JSTaggedValue weakValue(originalAddr);
286    weakValue.CreateWeakRef();
287    return weakValue.GetRawData();
288}
289
290void DecodeObj(RawHeapInfoArgs &rawHeapArgs, HeapSnapshot *snapshot)
291{
292    std::set<uint64_t> notFoundObj;
293    CUnorderedSet<uint64_t> *refVec = nullptr;
294    auto visitor = [&notFoundObj, &rawHeapArgs, &refVec] ([[maybe_unused]] TaggedObject *root,
295                    ObjectSlot start, ObjectSlot end, VisitObjectArea area) {
296        if (area == VisitObjectArea::RAW_DATA || area == VisitObjectArea::NATIVE_POINTER) {
297            return;
298        }
299        for (ObjectSlot slot = start; slot < end; slot++) {
300            auto taggedPointerAddr = reinterpret_cast<uint64_t **>(slot.SlotAddress());
301            JSTaggedValue value(reinterpret_cast<TaggedObject *>(*taggedPointerAddr));
302            auto originalAddr = reinterpret_cast<uint64_t>(*taggedPointerAddr);
303            originalAddr = CheckAndRemoveWeak(value, originalAddr);
304            if (!value.IsHeapObject() || originalAddr == 0) {
305                continue;
306            }
307            auto toItemInfo = rawHeapArgs.oldAddrMapObjInfo.find(originalAddr);
308            if (toItemInfo == rawHeapArgs.oldAddrMapObjInfo.end()) {
309                notFoundObj.insert(reinterpret_cast<uint64_t>(*taggedPointerAddr));
310                continue;
311            }
312            auto newAddr = reinterpret_cast<uint64_t>(toItemInfo->second->newAddr);
313            newAddr = CheckAndAddWeak(value, newAddr);
314            refVec->insert(newAddr);
315            slot.Update(reinterpret_cast<TaggedObject *>(newAddr));
316        }
317    };
318    for (auto v : rawHeapArgs.rawObjInfoVec) {
319        refVec = &v->refSet;
320        auto jsHclassAddr = *reinterpret_cast<uint64_t *>(v->newAddr);
321        auto jsHclassItem = rawHeapArgs.oldAddrMapObjInfo.find(jsHclassAddr);
322        if (jsHclassItem == rawHeapArgs.oldAddrMapObjInfo.end()) {
323            LOG_ECMA(ERROR) << "ark DecodeObj hclass not find jsHclassAddr=" << std::hex << jsHclassAddr;
324            continue;
325        }
326        TaggedObject *obj = reinterpret_cast<TaggedObject *>(v->newAddr);
327        *reinterpret_cast<uint64_t *>(v->newAddr) = reinterpret_cast<uint64_t>(jsHclassItem->second->newAddr);
328        auto hclassObj = reinterpret_cast<JSHClass *>(jsHclassItem->second->newAddr);
329        ObjectXRay::VisitObjectBody<VisitType::OLD_GC_VISIT>(obj, hclassObj, visitor);
330    }
331    LOG_ECMA(INFO) << "ark visitor: not found obj num= " << notFoundObj.size() << ", generate nodes";
332    for (auto v : rawHeapArgs.rawObjInfoVec) {
333        TaggedObject *obj = reinterpret_cast<TaggedObject *>(v->newAddr);
334        snapshot->GenerateNodeForBinMod(obj, v, rawHeapArgs.strTableIdMapNewStr);
335    }
336}
337
338static uint64_t GetFileSize(std::string &inputFilePath)
339{
340    if (inputFilePath.empty()) {
341        return 0;
342    }
343    struct stat fileInfo;
344    if (stat(inputFilePath.c_str(), &fileInfo) == 0) {
345        return fileInfo.st_size;
346    }
347    return 0;
348}
349
350bool HeapProfiler::GenerateHeapSnapshot(std::string &inputFilePath, std::string &outputPath)
351{
352    LOG_ECMA(INFO) << "ark raw heap GenerateHeapSnapshot start";
353    uint64_t fileSize = GetFileSize(inputFilePath);
354    if (fileSize == 0) {
355        LOG_ECMA(ERROR) << "ark raw heap get file size=0";
356        return false;
357    }
358    std::ifstream file(inputFilePath, std::ios::binary);
359    if (!file.is_open()) {
360        LOG_ECMA(ERROR) << "ark raw heap open file failed:" << inputFilePath.c_str();
361        return false;
362    }
363    static uint64_t maxSupportFileSize8GB = 8 * 1024 * 1024 * 1024ULL;
364    if (fileSize > maxSupportFileSize8GB) {
365        LOG_ECMA(ERROR) << "ark raw heap get file size > 10GB, unsupported";
366        return false;
367    }
368    auto buf = new char[fileSize];
369    file.read(buf, fileSize);
370    file.close();
371    ChunkDecoder *chunk = new ChunkDecoder(buf, fileSize);
372    auto &rawHeapArgs = chunk->GetRawHeapInfoArgs();
373    auto &strTableIdMap = rawHeapArgs.strTableIdMapNewStr;
374    auto &objInfoVec = rawHeapArgs.rawObjInfoVec;
375    bool traceAllocation = false;
376    DumpSnapShotOption dumpOption;
377    LOG_ECMA(INFO) << "ark GenerateHeapSnapshot rebuild ref and generate nodes count=" << objInfoVec.size();
378    for (auto item : strTableIdMap) {
379        GetEcmaStringTable()->GetString(item.second);
380    }
381    auto *snapshot = new HeapSnapshot(vm_, GetEcmaStringTable(), dumpOption, traceAllocation, entryIdMap_, GetChunk());
382    DecodeObj(rawHeapArgs, snapshot);
383    LOG_ECMA(INFO) << "ark GenerateHeapSnapshot generate edges";
384    snapshot->BuildSnapshotForBinMod(objInfoVec);
385    delete[] buf;
386    delete chunk;
387    if (outputPath.empty()) {
388        outputPath = GenDumpFileName(dumpOption.dumpFormat);
389    } else if (outputPath.back() == '/') {
390        outputPath += GenDumpFileName(dumpOption.dumpFormat);
391    }
392    LOG_GC(INFO) << "ark GenerateHeapSnapshot output file=" << outputPath.c_str();
393    FileStream newStream(outputPath);
394    auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, &newStream);
395    delete snapshot;
396    LOG_ECMA(INFO) << "ark raw heap GenerateHeapSnapshot finish";
397    return serializerResult;
398}
399
400[[maybe_unused]]static void WaitProcess(pid_t pid)
401{
402    time_t startTime = time(nullptr);
403    constexpr int DUMP_TIME_OUT = 300;
404    constexpr int DEFAULT_SLEEP_TIME = 100000;
405    while (true) {
406        int status = 0;
407        pid_t p = waitpid(pid, &status, WNOHANG);
408        if (p < 0 || p == pid) {
409            break;
410        }
411        if (time(nullptr) > startTime + DUMP_TIME_OUT) {
412            LOG_GC(ERROR) << "DumpHeapSnapshot kill thread, wait " << DUMP_TIME_OUT << " s";
413            kill(pid, SIGTERM);
414            break;
415        }
416        usleep(DEFAULT_SLEEP_TIME);
417    }
418}
419
420template<typename Callback>
421void IterateSharedHeap(Callback &cb)
422{
423    auto heap = SharedHeap::GetInstance();
424    heap->GetOldSpace()->IterateOverObjects(cb);
425    heap->GetNonMovableSpace()->IterateOverObjects(cb);
426    heap->GetHugeObjectSpace()->IterateOverObjects(cb);
427    heap->GetAppSpawnSpace()->IterateOverObjects(cb);
428    heap->GetReadOnlySpace()->IterateOverObjects(cb);
429}
430
431std::pair<uint64_t, uint64_t> GetHeapCntAndSize(const EcmaVM *vm)
432{
433    uint64_t cnt = 0;
434    uint64_t objectSize = 0;
435    auto cb = [&objectSize, &cnt]([[maybe_unused]] TaggedObject *obj) {
436        objectSize += obj->GetClass()->SizeFromJSHClass(obj);
437        ++cnt;
438    };
439    vm->GetHeap()->IterateOverObjects(cb, false);
440    return std::make_pair(cnt, objectSize);
441}
442
443std::pair<uint64_t, uint64_t> GetSharedCntAndSize()
444{
445    uint64_t cnt = 0;
446    uint64_t size = 0;
447    auto cb = [&cnt, &size](TaggedObject *obj) {
448        cnt++;
449        size += obj->GetClass()->SizeFromJSHClass(obj);
450    };
451    IterateSharedHeap(cb);
452    return std::make_pair(cnt, size);
453}
454
455static CUnorderedSet<TaggedObject*> GetRootObjects(const EcmaVM *vm)
456{
457    CUnorderedSet<TaggedObject*> result {};
458    HeapRootVisitor visitor;
459    uint32_t rootCnt1 = 0;
460    RootVisitor rootEdgeBuilder = [&result, &rootCnt1](
461        [[maybe_unused]] Root type, ObjectSlot slot) {
462        JSTaggedValue value((slot).GetTaggedType());
463        if (!value.IsHeapObject()) {
464            return;
465        }
466        ++rootCnt1;
467        TaggedObject *root = value.GetTaggedObject();
468        result.insert(root);
469    };
470    RootBaseAndDerivedVisitor rootBaseEdgeBuilder = []
471        ([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot base, [[maybe_unused]] ObjectSlot derived,
472         [[maybe_unused]] uintptr_t baseOldObject) {
473    };
474    uint32_t rootCnt2 = 0;
475    RootRangeVisitor rootRangeEdgeBuilder = [&result, &rootCnt2]([[maybe_unused]] Root type,
476        ObjectSlot start, ObjectSlot end) {
477        for (ObjectSlot slot = start; slot < end; slot++) {
478            JSTaggedValue value((slot).GetTaggedType());
479            if (!value.IsHeapObject()) {
480                continue;
481            }
482            ++rootCnt2;
483            TaggedObject *root = value.GetTaggedObject();
484            result.insert(root);
485        }
486    };
487    visitor.VisitHeapRoots(vm->GetJSThread(), rootEdgeBuilder, rootRangeEdgeBuilder, rootBaseEdgeBuilder);
488    SharedModuleManager::GetInstance()->Iterate(rootEdgeBuilder);
489    Runtime::GetInstance()->IterateCachedStringRoot(rootRangeEdgeBuilder);
490    return result;
491}
492
493size_t GetNotFoundObj(const EcmaVM *vm)
494{
495    size_t heapTotalSize = 0;
496    CUnorderedSet<TaggedObject*> allHeapObjSet {};
497    auto handleObj = [&allHeapObjSet, &heapTotalSize](TaggedObject *obj) {
498        allHeapObjSet.insert(obj);
499        uint64_t objSize = obj->GetClass()->SizeFromJSHClass(obj);
500        heapTotalSize += objSize;
501    };
502    vm->GetHeap()->IterateOverObjects(handleObj, false);
503    IterateSharedHeap(handleObj);
504    LOG_ECMA(INFO) << "ark GetNotFound heap count:" << allHeapObjSet.size() << ", heap size=" << heapTotalSize;
505    CUnorderedSet<TaggedObject *> notFoundObjSet {};
506    auto visitor = [&notFoundObjSet, &allHeapObjSet] ([[maybe_unused]]TaggedObject *root, ObjectSlot start,
507                                                      ObjectSlot end, VisitObjectArea area) {
508        if (area == VisitObjectArea::RAW_DATA || area == VisitObjectArea::NATIVE_POINTER) {
509            return;
510        }
511        for (ObjectSlot slot = start; slot < end; slot++) {
512            auto taggedPointerAddr = reinterpret_cast<uint64_t **>(slot.SlotAddress());
513            JSTaggedValue value(reinterpret_cast<TaggedObject *>(*taggedPointerAddr));
514            auto originalAddr = reinterpret_cast<uint64_t>(*taggedPointerAddr);
515            if (!value.IsHeapObject() || originalAddr == 0) {
516                continue;
517            }
518            if (value.IsWeakForHeapObject()) {
519                originalAddr -= 1;
520            }
521            if (allHeapObjSet.find(reinterpret_cast<TaggedObject *>(originalAddr)) != allHeapObjSet.end()) {
522                continue;
523            }
524            auto obj = reinterpret_cast<TaggedObject *>(*taggedPointerAddr);
525            if (notFoundObjSet.find(obj) != notFoundObjSet.end()) {
526                continue;
527            }
528            notFoundObjSet.insert(obj);
529        }
530    };
531    for (auto obj : allHeapObjSet) {
532        ObjectXRay::VisitObjectBody<VisitType::OLD_GC_VISIT>(obj, obj->GetClass(), visitor);
533    }
534    LOG_ECMA(INFO) << "ark GetNotFound not found count:" << notFoundObjSet.size();
535    return notFoundObjSet.size();
536}
537
538bool FillAddrTable(const EcmaVM *vm, EntryIdMap &idMap, AddrTableItem *table, HeapSnapshot *snapshot)
539{
540    uint64_t index = 0;
541    auto handleObj = [&index, &table, &idMap, &snapshot](TaggedObject *obj) {
542        auto taggedType = JSTaggedValue(obj).GetRawData();
543        auto [exist, id] = idMap.FindId(taggedType);
544        if (!exist) {
545            idMap.InsertId(taggedType, id);
546        }
547        table[index].addr = reinterpret_cast<uint64_t>(obj);
548        table[index].id = id;
549        table[index].stringId = snapshot->GenerateStringId(obj);
550        index++;
551    };
552    vm->GetHeap()->IterateOverObjects(handleObj, false);
553    IterateSharedHeap(handleObj);
554    LOG_ECMA(INFO) << "ark FillAddrTable obj count: " << index;
555#ifdef OHOS_UNIT_TEST
556    size_t ret = GetNotFoundObj(vm);
557    return ret == 0;
558#else
559    return true;
560#endif
561}
562
563void CopyObjectMem(char *chunk, AddrTableItem *table, uint64_t len, std::atomic<uint64_t> &index,
564                   std::atomic<uint64_t> &offset, uint64_t offBase)
565{
566    auto curIdx = index.fetch_add(1);
567    while (curIdx < len) {
568        auto& item = table[curIdx];
569        auto obj = reinterpret_cast<TaggedObject *>(item.addr);
570        uint64_t objSize = obj->GetClass()->SizeFromJSHClass(obj);
571        auto curOffset = offset.fetch_add(objSize);
572        item.objSize = objSize;
573        item.offset = curOffset + offBase;
574        auto ret = memcpy_s(chunk + curOffset, objSize, reinterpret_cast<void *>(item.addr), objSize);
575        if (ret != 0) {
576            LOG_ECMA(ERROR) << "ark BinaryDump CopyObjectMem memcpy_s failed";
577            return;
578        }
579        curIdx = index.fetch_add(1);
580    }
581}
582
583bool HeapProfiler::BinaryDump(Stream *stream, [[maybe_unused]] const DumpSnapShotOption &dumpOption)
584{
585    LOG_ECMA(INFO) << "ark BinaryDump dump raw heap start";
586    auto [localCnt, heapSize] = GetHeapCntAndSize(vm_);
587    auto [sharedCnt, sharedSize] = GetSharedCntAndSize();
588    auto roots = GetRootObjects(vm_);
589    uint64_t heapTotalCnt = localCnt + sharedCnt;
590    uint64_t totalSize = sizeof(uint64_t) * (4 + roots.size()) + sizeof(AddrTableItem) * heapTotalCnt; // 4 : file head
591    uint64_t heapTotalSize = heapSize + sharedSize;
592    LOG_ECMA(INFO) << "ark rootNum=" << roots.size() << ", ObjSize=" << heapTotalSize << ", ObjNum=" << heapTotalCnt;
593    char *chunk = new char[totalSize];
594    uint64_t *header = reinterpret_cast<uint64_t *>(chunk);
595    header[0] = heapTotalCnt; // 0: obj total count offset in file
596    header[1] = roots.size(); // 1: root obj num offset in file
597    header[2] = sharedCnt; // 2: share obj num offset in file
598    auto currInd = 4; // 4 : file head num is 4, then is obj table
599    for (auto root : roots) {
600        header[currInd++] = reinterpret_cast<uint64_t>(root);
601    }
602    auto table = reinterpret_cast<AddrTableItem *>(&header[currInd]);
603    DumpSnapShotOption op;
604    auto *snapshotPtr = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), op, false, entryIdMap_, GetChunk());
605    auto ret = FillAddrTable(vm_, *entryIdMap_, table, snapshotPtr);
606    char *heapObjData = new char[heapTotalSize];
607    uint64_t objMemStart = reinterpret_cast<uint64_t>(&table[heapTotalCnt]);
608    uint64_t offBase = objMemStart - reinterpret_cast<uint64_t>(chunk);
609    std::atomic<uint64_t> offset(0);
610    std::atomic<uint64_t> index(0);
611    auto threadMain = [&offset, &index, heapObjData, table, heapTotalCnt, offBase]() {
612        CopyObjectMem(heapObjData, table, heapTotalCnt, index, offset, offBase);
613    };
614    std::vector<std::thread> threads;
615    const uint32_t THREAD_NUM = 8; // 8 : thread num is 8
616    for (uint32_t i = 0; i < THREAD_NUM; i++) {
617        threads.emplace_back(threadMain);
618    }
619    for (uint32_t i = 0; i < THREAD_NUM; i++) {
620        threads[i].join();
621    }
622    header[3] = offBase + heapTotalSize; // 3: string table offset
623    LOG_ECMA(INFO) << "ark BinaryDump write to file";
624    stream->WriteBinBlock(chunk, offBase);
625    stream->WriteBinBlock(heapObjData, heapTotalSize);
626    delete[] heapObjData;
627    delete[] chunk;
628    LOG_ECMA(INFO) << "ark BinaryDump dump DumpStringTable";
629    HeapSnapshotJSONSerializer::DumpStringTable(snapshotPtr, stream);
630    GetChunk()->Delete(snapshotPtr);
631    LOG_ECMA(INFO) << "ark BinaryDump dump raw heap finished";
632    return ret;
633}
634
635void HeapProfiler::FillIdMap()
636{
637    EntryIdMap* newEntryIdMap = GetChunk()->New<EntryIdMap>();
638    // Iterate SharedHeap Object
639    SharedHeap* sHeap = SharedHeap::GetInstance();
640    if (sHeap != nullptr) {
641        sHeap->IterateOverObjects([newEntryIdMap, this](TaggedObject *obj) {
642            JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
643            auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
644            newEntryIdMap->InsertId(addr, sequenceId);
645        });
646        sHeap->GetReadOnlySpace()->IterateOverObjects([newEntryIdMap, this](TaggedObject *obj) {
647            JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
648            auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
649            newEntryIdMap->InsertId(addr, sequenceId);
650        });
651    }
652
653    // Iterate LocalHeap Object
654    auto heap = vm_->GetHeap();
655    if (heap != nullptr) {
656        heap->IterateOverObjects([newEntryIdMap, this](TaggedObject *obj) {
657            JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
658            auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
659            newEntryIdMap->InsertId(addr, sequenceId);
660        });
661    }
662
663    // copy entryIdMap
664    CUnorderedMap<JSTaggedType, NodeId>* idMap = entryIdMap_->GetIdMap();
665    CUnorderedMap<JSTaggedType, NodeId>* newIdMap = newEntryIdMap->GetIdMap();
666    *idMap = *newIdMap;
667
668    GetChunk()->Delete(newEntryIdMap);
669}
670
671bool HeapProfiler::DumpHeapSnapshot(Stream *stream, const DumpSnapShotOption &dumpOption, Progress *progress)
672{
673    bool res = false;
674    base::BlockHookScope blockScope;
675    ThreadManagedScope managedScope(vm_->GetJSThread());
676    pid_t pid = -1;
677    {
678        if (dumpOption.isFullGC) {
679            [[maybe_unused]] bool heapClean = ForceFullGC(vm_);
680            ForceSharedGC();
681            ASSERT(heapClean);
682        }
683        SuspendAllScope suspendScope(vm_->GetAssociatedJSThread()); // suspend All.
684        const_cast<Heap*>(vm_->GetHeap())->Prepare();
685        SharedHeap::GetInstance()->Prepare(true);
686        Runtime::GetInstance()->GCIterateThreadList([&](JSThread *thread) {
687            ASSERT(!thread->IsInRunningState());
688            const_cast<Heap*>(thread->GetEcmaVM()->GetHeap())->FillBumpPointerForTlab();
689        });
690        // OOM and ThresholdReachedDump.
691        if (dumpOption.isDumpOOM) {
692            res = BinaryDump(stream, dumpOption);
693            stream->EndOfStream();
694            return res;
695        }
696        // ide.
697        if (dumpOption.isSync) {
698            return DoDump(stream, progress, dumpOption);
699        }
700        // hidumper do fork and fillmap.
701        if (dumpOption.isBeforeFill) {
702            FillIdMap();
703        }
704        // fork
705        if ((pid = ForkBySyscall()) < 0) {
706            LOG_ECMA(ERROR) << "DumpHeapSnapshot fork failed!";
707            return false;
708        }
709        if (pid == 0) {
710            vm_->GetAssociatedJSThread()->EnableCrossThreadExecution();
711            prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("dump_process"), 0, 0, 0);
712            res = DoDump(stream, progress, dumpOption);
713            _exit(0);
714        }
715    }
716    if (pid != 0) {
717        std::thread thread(&WaitProcess, pid);
718        thread.detach();
719        stream->EndOfStream();
720    }
721    isProfiling_ = true;
722    return res;
723}
724
725bool HeapProfiler::StartHeapTracking(double timeInterval, bool isVmMode, Stream *stream,
726                                     bool traceAllocation, bool newThread)
727{
728    vm_->CollectGarbage(TriggerGCType::OLD_GC);
729    ForceSharedGC();
730    SuspendAllScope suspendScope(vm_->GetAssociatedJSThread());
731    DumpSnapShotOption dumpOption;
732    dumpOption.isVmMode = isVmMode;
733    dumpOption.isPrivate = false;
734    dumpOption.captureNumericValue = false;
735    HeapSnapshot *snapshot = MakeHeapSnapshot(SampleType::REAL_TIME, dumpOption, traceAllocation);
736    if (snapshot == nullptr) {
737        return false;
738    }
739    isProfiling_ = true;
740    UpdateHeapObjects(snapshot);
741    heapTracker_ = std::make_unique<HeapTracker>(snapshot, timeInterval, stream);
742    const_cast<EcmaVM *>(vm_)->StartHeapTracking();
743    if (newThread) {
744        heapTracker_->StartTracing();
745    }
746
747    return true;
748}
749
750bool HeapProfiler::UpdateHeapTracking(Stream *stream)
751{
752    if (heapTracker_ == nullptr) {
753        return false;
754    }
755    HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot();
756    if (snapshot == nullptr) {
757        return false;
758    }
759
760    {
761        vm_->CollectGarbage(TriggerGCType::OLD_GC);
762        ForceSharedGC();
763        SuspendAllScope suspendScope(vm_->GetAssociatedJSThread());
764        snapshot->RecordSampleTime();
765        UpdateHeapObjects(snapshot);
766    }
767
768    if (stream != nullptr) {
769        snapshot->PushHeapStat(stream);
770    }
771
772    return true;
773}
774
775bool HeapProfiler::StopHeapTracking(Stream *stream, Progress *progress, bool newThread)
776{
777    if (heapTracker_ == nullptr) {
778        return false;
779    }
780    int32_t heapCount = static_cast<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
781
782    const_cast<EcmaVM *>(vm_)->StopHeapTracking();
783    if (newThread) {
784        heapTracker_->StopTracing();
785    }
786
787    HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot();
788    if (snapshot == nullptr) {
789        return false;
790    }
791
792    if (progress != nullptr) {
793        progress->ReportProgress(0, heapCount);
794    }
795    {
796        ForceSharedGC();
797        SuspendAllScope suspendScope(vm_->GetAssociatedJSThread());
798        SharedHeap::GetInstance()->GetSweeper()->WaitAllTaskFinished();
799        snapshot->FinishSnapshot();
800    }
801
802    isProfiling_ = false;
803    if (progress != nullptr) {
804        progress->ReportProgress(heapCount, heapCount);
805    }
806    return HeapSnapshotJSONSerializer::Serialize(snapshot, stream);
807}
808
809std::string HeapProfiler::GenDumpFileName(DumpFormat dumpFormat)
810{
811    CString filename("hprof_");
812    switch (dumpFormat) {
813        case DumpFormat::JSON:
814            filename.append(GetTimeStamp());
815            break;
816        case DumpFormat::BINARY:
817            filename.append("unimplemented");
818            break;
819        case DumpFormat::OTHER:
820            filename.append("unimplemented");
821            break;
822        default:
823            filename.append("unimplemented");
824            break;
825    }
826    filename.append(".heapsnapshot");
827    return ConvertToStdString(filename);
828}
829
830CString HeapProfiler::GetTimeStamp()
831{
832    std::time_t timeSource = std::time(nullptr);
833    struct tm tm {
834    };
835    struct tm *timeData = localtime_r(&timeSource, &tm);
836    if (timeData == nullptr) {
837        LOG_FULL(FATAL) << "localtime_r failed";
838        UNREACHABLE();
839    }
840    CString stamp;
841    const int TIME_START = 1900;
842    stamp.append(ToCString(timeData->tm_year + TIME_START))
843        .append("-")
844        .append(ToCString(timeData->tm_mon + 1))
845        .append("-")
846        .append(ToCString(timeData->tm_mday))
847        .append("_")
848        .append(ToCString(timeData->tm_hour))
849        .append("-")
850        .append(ToCString(timeData->tm_min))
851        .append("-")
852        .append(ToCString(timeData->tm_sec));
853    return stamp;
854}
855
856bool HeapProfiler::ForceFullGC(const EcmaVM *vm)
857{
858    if (vm->IsInitialized()) {
859        const_cast<Heap *>(vm->GetHeap())->CollectGarbage(TriggerGCType::FULL_GC);
860        return true;
861    }
862    return false;
863}
864
865void HeapProfiler::ForceSharedGC()
866{
867    SharedHeap *sHeap = SharedHeap::GetInstance();
868    sHeap->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::OTHER>(vm_->GetAssociatedJSThread());
869    sHeap->GetSweeper()->WaitAllTaskFinished();
870}
871
872HeapSnapshot *HeapProfiler::MakeHeapSnapshot(SampleType sampleType, const DumpSnapShotOption &dumpOption,
873                                             bool traceAllocation)
874{
875    LOG_ECMA(INFO) << "HeapProfiler::MakeHeapSnapshot";
876    if (dumpOption.isFullGC) {
877        DISALLOW_GARBAGE_COLLECTION;
878        const_cast<Heap *>(vm_->GetHeap())->Prepare();
879    }
880    switch (sampleType) {
881        case SampleType::ONE_SHOT: {
882            auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), dumpOption,
883                                                           traceAllocation, entryIdMap_, GetChunk());
884            if (snapshot == nullptr) {
885                LOG_FULL(FATAL) << "alloc snapshot failed";
886                UNREACHABLE();
887            }
888            snapshot->BuildUp(dumpOption.isSimplify);
889            return snapshot;
890        }
891        case SampleType::REAL_TIME: {
892            auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), dumpOption,
893                                                           traceAllocation, entryIdMap_, GetChunk());
894            if (snapshot == nullptr) {
895                LOG_FULL(FATAL) << "alloc snapshot failed";
896                UNREACHABLE();
897            }
898            AddSnapshot(snapshot);
899            snapshot->PrepareSnapshot();
900            return snapshot;
901        }
902        default:
903            return nullptr;
904    }
905}
906
907void HeapProfiler::AddSnapshot(HeapSnapshot *snapshot)
908{
909    if (hprofs_.size() >= MAX_NUM_HPROF) {
910        ClearSnapshot();
911    }
912    ASSERT(snapshot != nullptr);
913    hprofs_.emplace_back(snapshot);
914}
915
916void HeapProfiler::ClearSnapshot()
917{
918    for (auto *snapshot : hprofs_) {
919        GetChunk()->Delete(snapshot);
920    }
921    hprofs_.clear();
922}
923
924bool HeapProfiler::StartHeapSampling(uint64_t samplingInterval, int stackDepth)
925{
926    if (heapSampling_.get()) {
927        LOG_ECMA(ERROR) << "Do not start heap sampling twice in a row.";
928        return false;
929    }
930    heapSampling_ = std::make_unique<HeapSampling>(vm_, const_cast<Heap *>(vm_->GetHeap()),
931                                                   samplingInterval, stackDepth);
932    return true;
933}
934
935void HeapProfiler::StopHeapSampling()
936{
937    heapSampling_.reset();
938}
939
940const struct SamplingInfo *HeapProfiler::GetAllocationProfile()
941{
942    if (!heapSampling_.get()) {
943        LOG_ECMA(ERROR) << "Heap sampling was not started, please start firstly.";
944        return nullptr;
945    }
946    return heapSampling_->GetAllocationProfile();
947}
948}  // namespace panda::ecmascript
949