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 
33 namespace 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 
42 enum class RuntimeInfoType {
43     AOT_CRASH,
44     JIT,
45     OTHERS,
46     NONE,
47     JS,
48     AOT_BUILD,
49 };
50 
51 class AotRuntimeInfo {
52 public:
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 
GetInstance()65     static AotRuntimeInfo &GetInstance()
66     {
67         static AotRuntimeInfo singleAotRuntimeInfo;
68         return singleAotRuntimeInfo;
69     }
70 
BuildCompileRuntimeInfo(RuntimeInfoType type, const std::string &pgoPath)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 
BuildCrashRuntimeInfo(RuntimeInfoType type)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 
GetCompileCountByType(RuntimeInfoType type, const std::string &pgoRealPath = �)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 
CollectCrashSum(const std::string &pgoRealPath = �)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 
GetRuntimeInfoTypeStr(const RuntimeInfoType type) const170     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 
GetRuntimeInfoTypeByStr(std::string &type) const190     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 
GetRuntimeBuildId(char *buildId, int length) const203     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 
GetMicrosecondsTimeStamp(char *timestamp, size_t length) const225     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 
GetCrashSandBoxRealPath(char *realOutPath, size_t length) const242     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     }
255 protected:
256 
IsCharEmpty(const char *value) const257     bool IsCharEmpty(const char *value) const
258     {
259         if (value == NULL || *value == '\0') {
260             return true;
261         }
262         return false;
263     }
264 
BuildRuntimeInfoPart(char *runtimeInfoPart, const char *soBuildId, const char *timestamp, RuntimeInfoType type) const265     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 
GetInfoFromBuffer(char *line, int index) const286     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 
getLength(char lines[][BUFFER_SIZE], int maxInput) const301     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 
GetCrashRuntimeInfoList(char lines[][BUFFER_SIZE]) const312     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 
GetRealPathRuntimeInfoList(char lines[][BUFFER_SIZE], const std::string &pgoRealPath) const332     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 
SetRuntimeInfo(const char *realOutPath, char lines[][BUFFER_SIZE], int length) const351     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 
GetRuntimeInfoByPath(char lines[][BUFFER_SIZE], const char *realOutPath, const char *soBuildId) const366     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 
ParseELFSectionsForBuildId(ecmascript::MemMap &fileMap, char *buildId, int length) const391     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 
GetReadableBuildId(char *buildIdHex, char *buildId, int length) const433     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 
AlignValues(uint64_t val, uint64_t align) const454     uint64_t AlignValues(uint64_t val, uint64_t align) const
455     {
456         return (val + AlignBytes(align)) & AlignMask(align);
457     }
458 
AlignMask(uint64_t align) const459     uint64_t AlignMask(uint64_t align) const
460     {
461         return ~(static_cast<uint64_t>((align) - 1));
462     }
463 
AlignBytes(uint64_t align) const464     uint64_t AlignBytes(uint64_t align) const
465     {
466         return (align) - 1;
467     }
468 
IsLoadedMap()469     bool IsLoadedMap()
470     {
471         return isLoadedMap_;
472     }
473 
SetLoadedMap(bool value)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