1/*
2 * Copyright (c) 2024 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
16#ifndef ECMASCRIPT_COMPILER_OHOS_RUNTIME_BUILD_INFO_H
17#define ECMASCRIPT_COMPILER_OHOS_RUNTIME_BUILD_INFO_H
18
19#include <sys/time.h>
20#include <fcntl.h>
21#include <mutex>
22#include <stdio.h>
23
24#include "ecmascript/platform/directory.h"
25#include "ecmascript/platform/file.h"
26#include "ecmascript/platform/map.h"
27#include "libpandafile/file.h"
28#include "llvm/BinaryFormat/ELF.h"
29#include "ohos_constants.h"
30#include "utils/json_parser.h"
31#include "utils/json_builder.h"
32
33namespace panda::ecmascript::ohos {
34#define RUNTIME_INFO_TYPE(V)                                         \
35    V(AOT_CRASH)                                                     \
36    V(OTHERS)                                                        \
37    V(NONE)                                                          \
38    V(JIT)                                                           \
39    V(JS)                                                            \
40    V(AOT_BUILD)                                                     \
41
42enum class RuntimeInfoType {
43    AOT_CRASH,
44    JIT,
45    OTHERS,
46    NONE,
47    JS,
48    AOT_BUILD,
49};
50
51class AotRuntimeInfo {
52public:
53    constexpr static const int USEC_PER_SEC = 1000 * 1000;
54    constexpr static const int NSEC_PER_USEC = 1000;
55    constexpr static const int NT_GNU_BUILD_ID = 3;
56    constexpr static const int CRASH_INFO_SIZE = 3;
57    constexpr static const int MAX_LENGTH = 255;
58    constexpr static const int BUFFER_SIZE = 4096;
59    constexpr static const int TIME_STAMP_SIZE = 21;
60
61    constexpr static const int RUNTIME_INDEX_BUILDID = 0;
62    constexpr static const int RUNTIME_INDEX_TIMESTAMP = 1;
63    constexpr static const int RUNTIME_INDEX_TYPE = 2;
64
65    static AotRuntimeInfo &GetInstance()
66    {
67        static AotRuntimeInfo singleAotRuntimeInfo;
68        return singleAotRuntimeInfo;
69    }
70
71    void BuildCompileRuntimeInfo(RuntimeInfoType type, const std::string &pgoPath)
72    {
73        std::unique_lock<std::mutex> lock(fileMutex_);
74        static char soBuildId[NAME_MAX] = { '\0' };
75        if (!GetRuntimeBuildId(soBuildId, NAME_MAX) || IsCharEmpty(soBuildId)) {
76            LOG_ECMA(INFO) << "can't get so buildId.";
77            return;
78        }
79        std::string realOutPath;
80        if (!ecmascript::RealPath(pgoPath, realOutPath, false)) {
81            LOG_ECMA(INFO) << "Build compile pgo path fail.";
82            return;
83        }
84        static char lines[MAX_LENGTH][BUFFER_SIZE];
85        for (int i = 0; i < MAX_LENGTH; i++) {
86            memset_s(lines[i], BUFFER_SIZE, '\0', BUFFER_SIZE);
87        }
88        GetRuntimeInfoByPath(lines, realOutPath.c_str(), soBuildId);
89        static char timestamp[TIME_STAMP_SIZE] = { '\0' };
90        if (!GetMicrosecondsTimeStamp(timestamp, TIME_STAMP_SIZE)) {
91            return;
92        }
93
94        int lineCount = getLength(lines, MAX_LENGTH);
95        if (lineCount < MAX_LENGTH) {
96            if (!BuildRuntimeInfoPart(lines[lineCount], soBuildId, timestamp, type)) {
97                return;
98            }
99        }
100        SetRuntimeInfo(realOutPath.c_str(), lines, MAX_LENGTH);
101    }
102
103    void BuildCrashRuntimeInfo(RuntimeInfoType type)
104    {
105        std::unique_lock<std::mutex> lock(fileMutex_);
106        static char soBuildId[NAME_MAX] = { '\0' };
107        if (!GetRuntimeBuildId(soBuildId, NAME_MAX) || IsCharEmpty(soBuildId)) {
108            return;
109        }
110        static char lines[MAX_LENGTH][BUFFER_SIZE];
111        for (int i = 0; i < MAX_LENGTH; i++) {
112            memset_s(lines[i], BUFFER_SIZE, '\0', BUFFER_SIZE);
113        }
114        GetCrashRuntimeInfoList(lines);
115        static char timestamp[TIME_STAMP_SIZE] = { '\0' };
116        if (!GetMicrosecondsTimeStamp(timestamp, TIME_STAMP_SIZE)) {
117            return;
118        }
119        int lineCount = getLength(lines, MAX_LENGTH);
120        if (lineCount < MAX_LENGTH) {
121            if (!BuildRuntimeInfoPart(lines[lineCount], soBuildId, timestamp, type)) {
122                return;
123            }
124        }
125        static char realOutPath[PATH_MAX] = { '\0' };
126        if (!GetCrashSandBoxRealPath(realOutPath, PATH_MAX) || IsCharEmpty(realOutPath)) {
127            return;
128        }
129        SetRuntimeInfo(realOutPath, lines, MAX_LENGTH);
130    }
131
132    int GetCompileCountByType(RuntimeInfoType type, const std::string &pgoRealPath = "")
133    {
134        std::map<RuntimeInfoType, int> escapeMap = CollectCrashSum(pgoRealPath);
135        if (escapeMap.count(type) == 0) {
136            return 0;
137        }
138        return escapeMap[type];
139    }
140
141    std::map<RuntimeInfoType, int> CollectCrashSum(const std::string &pgoRealPath = "")
142    {
143        if (IsLoadedMap()) {
144            return escapeMap_;
145        }
146        char lines[MAX_LENGTH][BUFFER_SIZE];
147        for (int i = 0; i < MAX_LENGTH; i++) {
148            memset_s(lines[i], BUFFER_SIZE, '\0', BUFFER_SIZE);
149        }
150        if (IsCharEmpty(pgoRealPath.c_str())) {
151            GetCrashRuntimeInfoList(lines);
152        } else {
153            GetRealPathRuntimeInfoList(lines, pgoRealPath);
154        }
155        char *typeChar = new char[NAME_MAX];
156        for (int i = 0; i < MAX_LENGTH; i++) {
157            if (lines[i][0] != '\0') {
158                if (strcpy_s(typeChar, NAME_MAX, GetInfoFromBuffer(lines[i], RUNTIME_INDEX_TYPE)) !=0) {
159                    continue;
160                }
161                std::string typeStr(typeChar);
162                escapeMap_[GetRuntimeInfoTypeByStr(typeStr)]++;
163            }
164        }
165        SetLoadedMap(true);
166        delete[] typeChar;
167        return escapeMap_;
168    }
169
170    const char *GetRuntimeInfoTypeStr(const RuntimeInfoType type) const
171    {
172        switch (type) {
173            case RuntimeInfoType::AOT_CRASH:
174                return "AOT_CRASH";
175            case RuntimeInfoType::JIT:
176                return "JIT";
177            case RuntimeInfoType::OTHERS:
178                return "OTHERS";
179            case RuntimeInfoType::NONE:
180                return "NONE";
181            case RuntimeInfoType::JS:
182                return "JS";
183            case RuntimeInfoType::AOT_BUILD:
184                return "AOT_BUILD";
185            default:
186                return "NONE";
187        }
188    }
189
190    RuntimeInfoType GetRuntimeInfoTypeByStr(std::string &type) const
191    {
192        const std::map<std::string, RuntimeInfoType> strMap = {
193#define RUNTIME_INFO_TYPE_MAP(name) { #name, RuntimeInfoType::name },
194        RUNTIME_INFO_TYPE(RUNTIME_INFO_TYPE_MAP)
195#undef RUNTIME_INFO_TYPE_MAP
196        };
197        if (strMap.count(type) > 0) {
198            return strMap.at(type);
199        }
200        return RuntimeInfoType::NONE;
201    }
202
203    virtual bool GetRuntimeBuildId(char *buildId, int length) const
204    {
205        if (!FileExist(OhosConstants::RUNTIME_SO_PATH)) {
206            return false;
207        }
208        static char realPath[PATH_MAX] = { '\0' };
209        if (!ecmascript::RealPathByChar(OhosConstants::RUNTIME_SO_PATH, realPath, PATH_MAX, false)) {
210            return false;
211        }
212        if (!FileExist(realPath)) {
213            return false;
214        }
215        ecmascript::MemMap fileMap = ecmascript::FileMap(realPath, FILE_RDONLY, PAGE_PROT_READ);
216        if (fileMap.GetOriginAddr() == nullptr) {
217            return false;
218        }
219        ParseELFSectionsForBuildId(fileMap, buildId, length);
220        ecmascript::FileUnMap(fileMap);
221        fileMap.Reset();
222        return true;
223    }
224
225    virtual bool GetMicrosecondsTimeStamp(char *timestamp, size_t length) const
226    {
227        time_t current_time;
228        if (time(&current_time) == -1) {
229            return false;
230        }
231        struct tm *local_time = localtime(&current_time);
232        if (local_time == NULL) {
233            return false;
234        }
235        size_t result = strftime(timestamp, length, "%Y-%m-%d %H:%M:%S", local_time);
236        if (result == 0) {
237            return false;
238        }
239        return true;
240    }
241
242    virtual bool GetCrashSandBoxRealPath(char *realOutPath, size_t length) const
243    {
244        if (!ecmascript::RealPathByChar(OhosConstants::SANDBOX_ARK_PROFILE_PATH, realOutPath, length, false)) {
245            return false;
246        }
247        if (strcat_s(realOutPath, NAME_MAX, OhosConstants::PATH_SEPARATOR) != 0) {
248            return false;
249        }
250        if (strcat_s(realOutPath, NAME_MAX, OhosConstants::AOT_RUNTIME_INFO_NAME) !=0) {
251            return false;
252        }
253        return true;
254    }
255protected:
256
257    bool IsCharEmpty(const char *value) const
258    {
259        if (value == NULL || *value == '\0') {
260            return true;
261        }
262        return false;
263    }
264
265    bool BuildRuntimeInfoPart(char *runtimeInfoPart, const char *soBuildId, const char *timestamp,
266        RuntimeInfoType type) const
267    {
268        if (strcat_s(runtimeInfoPart, NAME_MAX, soBuildId) != 0) {
269            return false;
270        }
271        if (strcat_s(runtimeInfoPart, NAME_MAX, OhosConstants::SPLIT_STR) != 0) {
272            return false;
273        }
274        if (strcat_s(runtimeInfoPart, NAME_MAX, timestamp) != 0) {
275            return false;
276        }
277        if (strcat_s(runtimeInfoPart, NAME_MAX, OhosConstants::SPLIT_STR) != 0) {
278            return false;
279        }
280        if (strcat_s(runtimeInfoPart, NAME_MAX, GetRuntimeInfoTypeStr(type)) != 0) {
281            return false;
282        }
283        return true;
284    }
285
286    const char *GetInfoFromBuffer(char *line, int index) const
287    {
288        char *saveptr;
289        char buffer[BUFFER_SIZE] = { '\0' };
290        if (strncpy_s(buffer, BUFFER_SIZE - 1, line, sizeof(buffer) - 1) != 0) {
291            return "";
292        }
293        char *token = strtok_r(buffer, OhosConstants::SPLIT_STR, &saveptr);
294
295        for (int i = 0; i < index; i++) {
296            token = strtok_r(NULL, OhosConstants::SPLIT_STR, &saveptr);
297        }
298        return token;
299    }
300
301    int getLength(char lines[][BUFFER_SIZE], int maxInput) const
302    {
303        int count = 0;
304        for (int i = 0; i < maxInput; i++) {
305            if (lines[i][0] != '\0') {
306                count++;
307            }
308        }
309        return count;
310    }
311
312    void GetCrashRuntimeInfoList(char lines[][BUFFER_SIZE]) const
313    {
314        static char realOutPath[PATH_MAX] = { '\0' };
315        if (!GetCrashSandBoxRealPath(realOutPath, PATH_MAX)) {
316            return;
317        }
318        if (!FileExist(realOutPath)) {
319            return;
320        }
321        static char soBuildId[NAME_MAX] = { '\0' };
322        if (!GetRuntimeBuildId(soBuildId, NAME_MAX)) {
323            return;
324        }
325        if (IsCharEmpty(soBuildId)) {
326            return;
327        }
328        GetRuntimeInfoByPath(lines, realOutPath, soBuildId);
329        return;
330    }
331
332    void GetRealPathRuntimeInfoList(char lines[][BUFFER_SIZE], const std::string &pgoRealPath) const
333    {
334        std::string realOutPath;
335        if (!ecmascript::RealPath(pgoRealPath, realOutPath, false)) {
336            return;
337        }
338        if (!FileExist(realOutPath.c_str())) {
339            return;
340        }
341        char soBuildId[NAME_MAX] = { '\0' };
342        if (!GetRuntimeBuildId(soBuildId, NAME_MAX)) {
343            return;
344        }
345        if (IsCharEmpty(soBuildId)) {
346            return;
347        }
348        GetRuntimeInfoByPath(lines, realOutPath.c_str(), soBuildId);
349    }
350
351    virtual void SetRuntimeInfo(const char *realOutPath, char lines[][BUFFER_SIZE], int length) const
352    {
353        int fd = open(realOutPath,  O_WRONLY | O_CREAT | O_TRUNC, 0666);
354        if (fd == -1) {
355            return;
356        }
357        for (int i = 0; i < length && lines[i] != NULL; i++) {
358            if (lines[i][0] != '\0') {
359                write(fd, lines[i], strlen(lines[i]));
360                write(fd, "\n", 1);
361            }
362        }
363        close(fd);
364    }
365
366    void GetRuntimeInfoByPath(char lines[][BUFFER_SIZE], const char *realOutPath, const char *soBuildId) const
367    {
368        int fd = open(realOutPath, O_RDONLY);
369        if (fd == -1) {
370            return;
371        }
372        char buffer[BUFFER_SIZE] = { '\0' };
373        char *saveptr;
374        char *token;
375        ssize_t bytesRead;
376        int lineCount = 0;
377        while ((bytesRead = read(fd, buffer, BUFFER_SIZE - 1)) > 0) {
378            token = strtok_r(buffer, "\n", &saveptr);
379            while (token != NULL) {
380                if (strcmp(GetInfoFromBuffer(token, RUNTIME_INDEX_BUILDID), soBuildId) == 0 &&
381                    lineCount < MAX_LENGTH &&
382                    strcpy_s(lines[lineCount], BUFFER_SIZE, token) == 0) {
383                    lineCount++;
384                }
385                token = strtok_r(NULL, "\n", &saveptr);
386            }
387        }
388        close(fd);
389    }
390
391    void ParseELFSectionsForBuildId(ecmascript::MemMap &fileMap, char *buildId, int length) const
392    {
393        llvm::ELF::Elf64_Ehdr *ehdr = reinterpret_cast<llvm::ELF::Elf64_Ehdr *>(fileMap.GetOriginAddr());
394        char *addr = reinterpret_cast<char *>(ehdr);
395        llvm::ELF::Elf64_Shdr *shdr = reinterpret_cast<llvm::ELF::Elf64_Shdr *>(addr + ehdr->e_shoff);
396        ASSERT(ehdr->e_shstrndx != static_cast<llvm::ELF::Elf64_Half>(-1));
397        llvm::ELF::Elf64_Shdr strdr = shdr[ehdr->e_shstrndx];
398        int secId = -1;
399        static const char sectionName[] = ".note.gnu.build-id";
400        for (size_t i = 0; i < ehdr->e_shnum; i++) {
401            llvm::ELF::Elf64_Word shName = shdr[i].sh_name;
402            char *curShName = reinterpret_cast<char *>(addr) + shName + strdr.sh_offset;
403            if (strcmp(sectionName, curShName) == 0) {
404                secId = static_cast<int>(i);
405                break;
406            }
407        }
408        if (secId == -1) {
409            return;
410        }
411        llvm::ELF::Elf64_Shdr secShdr = shdr[secId];
412        uint64_t buildIdOffset = secShdr.sh_offset;
413        uint64_t buildIdSize = secShdr.sh_size;
414        llvm::ELF::Elf64_Nhdr *nhdr = reinterpret_cast<llvm::ELF::Elf64_Nhdr *>(addr + buildIdOffset);
415        char *curShNameForNhdr = reinterpret_cast<char *>(addr + buildIdOffset + sizeof(*nhdr));
416        if (buildIdSize - sizeof(*nhdr) < nhdr->n_namesz) {
417            return;
418        }
419
420        static const char gnu[] = "GNU";
421        if (strcmp(curShNameForNhdr, gnu) != 0 || nhdr->n_type != NT_GNU_BUILD_ID) {
422            return;
423        }
424        if ((buildIdSize - sizeof(*nhdr) - AlignValues(nhdr->n_namesz, 4) < nhdr->n_descsz) || nhdr->n_descsz == 0) {
425            return;
426        }
427
428        char *curShNameValueForNhdr = reinterpret_cast<char *>(addr + buildIdOffset + sizeof(*nhdr) +
429            AlignValues(nhdr->n_namesz, 4));
430        GetReadableBuildId(curShNameValueForNhdr, buildId, length);
431    }
432
433    void GetReadableBuildId(char *buildIdHex, char *buildId, int length) const
434    {
435        if (IsCharEmpty(buildIdHex)) {
436            return;
437        }
438        static const char HEXTABLE[] = "0123456789abcdef";
439        static const int HEXLENGTH = 16;
440        static const int HEX_EXPAND_PARAM = 2;
441        const int len = strlen(buildIdHex);
442
443        for (int i = 0; i < len; i++) {
444            int lowHexExpand = i * HEX_EXPAND_PARAM + 1;
445            if (lowHexExpand >= length) {
446                break;
447            }
448            unsigned int n = buildIdHex[i];
449            buildId[lowHexExpand - 1] = HEXTABLE[(n >> 4) % HEXLENGTH]; // 4 : higher 4 bit of uint8
450            buildId[lowHexExpand] = HEXTABLE[n % HEXLENGTH];
451        }
452    }
453
454    uint64_t AlignValues(uint64_t val, uint64_t align) const
455    {
456        return (val + AlignBytes(align)) & AlignMask(align);
457    }
458
459    uint64_t AlignMask(uint64_t align) const
460    {
461        return ~(static_cast<uint64_t>((align) - 1));
462    }
463
464    uint64_t AlignBytes(uint64_t align) const
465    {
466        return (align) - 1;
467    }
468
469    bool IsLoadedMap()
470    {
471        return isLoadedMap_;
472    }
473
474    void SetLoadedMap(bool value)
475    {
476        isLoadedMap_ = value;
477    }
478
479    std::map<RuntimeInfoType, int> escapeMap_;
480    bool isLoadedMap_ = false;
481    std::mutex fileMutex_;
482};
483}  // namespace panda::ecmascript
484#endif  // ECMASCRIPT_COMPILER_OHOS_RUNTIME_BUILD_INFO_H