• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-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 #include "ecmascript/dfx/stackinfo/js_stackgetter.h"
17 
18 #include "ecmascript/compiler/aot_file/aot_file_manager.h"
19 #include "ecmascript/global_env_constants-inl.h"
20 #include "ecmascript/jspandafile/js_pandafile_manager.h"
21 
22 namespace panda::ecmascript {
23 class CpuProfiler;
24 class SamplesRecord;
25 
CheckFrameType(JSThread * thread,JSTaggedType * sp)26 bool JsStackGetter::CheckFrameType(JSThread *thread, JSTaggedType *sp)
27 {
28     FrameType type = FrameHandler::GetFrameType(sp);
29     if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
30         return false;
31     }
32 
33     FrameIterator iterator(sp, thread);
34     iterator.Advance();
35     JSTaggedType *preSp = iterator.GetSp();
36     if (preSp == nullptr) {
37         return true;
38     }
39 #if defined(PANDA_TARGET_64)
40     if (thread->IsAsmInterpreter() && !thread->IsLegalSp(reinterpret_cast<uintptr_t>(preSp))) {
41         return false;
42     }
43 #endif
44     type = FrameHandler::GetFrameType(preSp);
45     if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
46         return false;
47     }
48     return true;
49 }
50 
ParseMethodInfo(struct MethodKey & methodKey,const FrameIterator & it,const EcmaVM * vm,FrameInfoTemp & codeEntry,bool isCpuProfiler)51 bool JsStackGetter::ParseMethodInfo(struct MethodKey &methodKey,
52                                     const FrameIterator &it,
53                                     const EcmaVM *vm,
54                                     FrameInfoTemp &codeEntry,
55                                     bool isCpuProfiler)
56 {
57     auto method = it.CheckAndGetMethod();
58     if (method == nullptr) {
59         return false;
60     }
61     codeEntry.methodKey = methodKey;
62     if (method->IsNativeWithCallField()) {
63         FrameIterator itNext(it.GetSp(), it.GetThread());
64         itNext.Advance<GCVisitedFlag::IGNORED>();
65         GetNativeMethodCallPos(itNext, vm, codeEntry);
66         GetNativeStack(vm, it, codeEntry.functionName, sizeof(codeEntry.functionName), isCpuProfiler);
67     } else {
68         const JSPandaFile *jsPandaFile = method->GetJSPandaFile(vm->GetJSThreadNoCheck());
69         if (jsPandaFile == nullptr) {
70             return false;
71         }
72         EntityId methodId = reinterpret_cast<MethodLiteral *>(methodKey.methodIdentifier)->GetMethodId();
73         // function name
74         const char *functionName = MethodLiteral::GetMethodName(jsPandaFile, methodId, true);
75         uint8_t length = strlen(functionName);
76         if (length != 0 && functionName[0] == '#') {
77             uint8_t index = length - 1;
78             while (functionName[index] != '#') {
79                 index--;
80             }
81             functionName += (index + 1);  // #...#functionName
82         }
83         if (strlen(functionName) == 0) {
84             functionName = "anonymous";
85         }
86         if (!CheckAndCopy(codeEntry.functionName, sizeof(codeEntry.functionName), functionName)) {
87             return false;
88         }
89         uint8_t specialIndex = strlen(codeEntry.functionName) - 1;
90         while (specialIndex > 0 && codeEntry.functionName[specialIndex] != '^') {
91             specialIndex--;
92         }
93         if (codeEntry.functionName[specialIndex] == '^') {
94             codeEntry.functionName[specialIndex] = '\0';  // #...#functionName^1
95         }
96         // record name
97         const char *recordName = MethodLiteral::GetRecordNameWithSymbol(jsPandaFile, methodId);
98         if (strlen(recordName) != 0) {
99             if (!CheckAndCopy(codeEntry.recordName, sizeof(codeEntry.recordName), recordName)) {
100                 return false;
101             }
102         }
103 
104         DebugInfoExtractor *debugExtractor =
105             JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
106         if (debugExtractor == nullptr) {
107             return false;
108         }
109         // source file
110         const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
111         if (!sourceFile.empty()) {
112             if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), sourceFile.c_str())) {
113                 return false;
114             }
115         }
116         // line number and clomn number
117         codeEntry.lineNumber = debugExtractor->GetFristLine(methodId);
118         codeEntry.columnNumber = debugExtractor->GetFristColumn(methodId);
119     }
120     return true;
121 }
122 
CheckAndCopy(char * dest,size_t length,const char * src)123 bool JsStackGetter::CheckAndCopy(char *dest, size_t length, const char *src)
124 {
125     int srcLength = strlen(src);
126     if (length <= static_cast<size_t>(srcLength) || strcpy_s(dest, srcLength + 1, src) != EOK) {
127         LOG_ECMA(ERROR) << "JsStackGetter strcpy_s failed, maybe srcLength more than destLength";
128         return false;
129     }
130     dest[srcLength] = '\0';
131     return true;
132 }
133 
GetNativeStack(const EcmaVM * vm,const FrameIterator & it,char * functionName,size_t size,bool isCpuProfiler)134 void JsStackGetter::GetNativeStack(const EcmaVM *vm, const FrameIterator &it, char *functionName, size_t size,
135                                    bool isCpuProfiler)
136 {
137     std::stringstream stream;
138     JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
139     // it not allow thread check here, if enable thread check, it maybe deadlock in IsInThreadPool
140     JSThread *thread = vm->GetJSThreadNoCheck();
141     JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo(thread);
142     const GlobalEnvConstants *globalConst = thread->GlobalConstants();
143     JSHandle<JSTaggedValue> nameKey = globalConst->GetHandledNameString();
144     JSHandle<JSTaggedValue> func(thread, function);
145     JSHandle<JSTaggedValue> funcNameValue = JSObject::GetProperty(thread, func, nameKey).GetValue();
146     std::string methodNameStr;
147     if (funcNameValue->IsString()) {
148         JSHandle<EcmaString> methodName(funcNameValue);
149         methodNameStr = EcmaStringAccessor(methodName).ToStdString(thread);
150     }
151     // napi method
152     if (isCpuProfiler && function->IsCallNapi() && extraInfoValue.CheckIsJSNativePointer()) {
153         JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
154         auto cb = vm->GetNativePtrGetter();
155         if (cb != nullptr  && extraInfo != nullptr) {
156             if (!vm->GetJSThreadNoCheck()->CpuProfilerCheckJSTaggedType(extraInfoValue.GetRawData())) {
157                 return;
158             }
159             auto addr = cb(reinterpret_cast<void *>(extraInfo->GetData()));
160             stream << addr;
161             if (!CheckAndCopy(functionName, size, methodNameStr.c_str())) {
162                 return;
163             }
164             const uint8_t methodNameStrLength = methodNameStr.size();
165             if (!CheckAndCopy(functionName + methodNameStrLength, size - methodNameStrLength, "(")) {
166                 return;
167             }
168             const uint8_t napiBeginLength = 1; // 1:the length of "("
169             if (!CheckAndCopy(functionName + methodNameStrLength + napiBeginLength,
170                 size - methodNameStrLength - napiBeginLength, stream.str().c_str())) {
171                 return;
172             }
173             uint8_t srcLength = stream.str().size();
174             CheckAndCopy(functionName + methodNameStrLength + napiBeginLength + srcLength,
175                 size - methodNameStrLength - napiBeginLength - srcLength, ")");
176             return;
177         }
178     }
179     CheckAndCopy(functionName, size, methodNameStr.c_str());
180 }
181 
GetRunningState(const FrameIterator & it,const EcmaVM * vm,bool isNative,bool topFrame,bool enableVMTag)182 RunningState JsStackGetter::GetRunningState(const FrameIterator &it, const EcmaVM *vm,
183                                             bool isNative, bool topFrame,
184                                             bool enableVMTag)
185 {
186     JSThread *thread = vm->GetAssociatedJSThread();
187     JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
188 
189     if (enableVMTag) {
190         if (topFrame) {
191             if (thread->GetGcState()) {
192                 return RunningState::GC;
193             }
194             if (isNative && (it.IsLeaveFrame() || thread->GetRuntimeState())) {
195                 return RunningState::RUNTIME;
196             }
197         }
198 
199         if (function->IsCallNapi()) {
200             return RunningState::NAPI;
201         }
202         if (isNative) {
203             return function->GetNativeFunctionExtraInfo(thread).CheckIsJSNativePointer() ? RunningState::ARKUI_ENGINE :
204                 RunningState::BUILTIN;
205         }
206         if (it.IsFastJitFunctionFrame()) {
207             return RunningState::JIT;
208         }
209         if (it.IsOptimizedJSFunctionFrame()) {
210             return RunningState::AOT;
211         }
212         if (thread->IsAsmInterpreter()) {
213             // For Methods that is compiled in AOT but deoptimized at runtime, we mark it as AINT-D
214             Method *method = Method::Cast(function->GetMethod(thread));
215             MethodLiteral *methodLiteral = method->GetMethodLiteral(thread);
216             if (methodLiteral != nullptr && MethodLiteral::IsAotWithCallField(methodLiteral->GetCallField())) {
217                 return RunningState::AINT_D;
218             }
219             return RunningState::AINT;
220         }
221         return RunningState::CINT;
222     }
223 
224     if (topFrame) {
225         if (function->IsCallNapi()) {
226             return RunningState::NAPI;
227         }
228         if (thread->GetGcState()) {
229             return RunningState::GC;
230         }
231     }
232 
233     if (function->IsCallNapi()) {
234         return RunningState::NAPI;
235     }
236     if (isNative) {
237         return function->GetNativeFunctionExtraInfo(thread).CheckIsJSNativePointer() ? RunningState::ARKUI_ENGINE :
238             RunningState::BUILTIN;
239     }
240 
241     return RunningState::OTHER;
242 }
243 
GetNativeMethodCallPos(FrameIterator & it,const EcmaVM * vm,FrameInfoTemp & codeEntry)244 void JsStackGetter::GetNativeMethodCallPos(FrameIterator &it, const EcmaVM *vm, FrameInfoTemp &codeEntry)
245 {
246     JSThread *thread = vm->GetJSThreadNoCheck();
247     auto nextMethod = it.CheckAndGetMethod();
248     if (nextMethod == nullptr) {
249         return ;
250     }
251     JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
252     JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo(thread);
253     if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile(thread) != nullptr) {
254         DebugInfoExtractor *debugExtractor =
255             JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile(thread));
256         if (debugExtractor == nullptr) {
257             return;
258         }
259         MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral(thread);
260         if (methodLiteral == nullptr) {
261             return;
262         }
263         panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
264         const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
265         const char *tempVariable;
266         if (sourceFile.empty()) {
267             tempVariable = "";
268         } else {
269             tempVariable = sourceFile.c_str();
270         }
271         if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), tempVariable)) {
272             return;
273         }
274         int lineNumber = 0;
275         auto callbackLineFunc = [&lineNumber](int32_t line) -> bool {
276             lineNumber = line + 1;
277             return true;
278         };
279         int columnNumber = 0;
280         auto callbackColumnFunc = [&columnNumber](int32_t column) -> bool {
281             columnNumber += column + 1;
282             return true;
283         };
284         uint32_t offset = it.GetBytecodeOffset();
285         if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
286             lineNumber = 0;
287         }
288         if (!debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
289             columnNumber = 0;
290         }
291         codeEntry.lineNumber = lineNumber;
292         codeEntry.columnNumber = columnNumber;
293     }
294 }
295 
GetMethodIdentifier(Method * method,const FrameIterator & it,const EcmaVM * vm)296 void *JsStackGetter::GetMethodIdentifier(Method *method, const FrameIterator &it, const EcmaVM *vm)
297 {
298     JSThread *thread = vm->GetJSThreadNoCheck();
299     JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
300     JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo(thread);
301     if (method->IsNativeWithCallField()) {
302         if (extraInfoValue.CheckIsJSNativePointer()) {
303             JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
304             return reinterpret_cast<void *>(extraInfo->GetData());
305         }
306         return function->GetNativePointer();
307     }
308 
309     MethodLiteral *methodLiteral = method->GetMethodLiteral(thread);
310     return reinterpret_cast<void *>(methodLiteral);
311 }
GetCallLineNumber(const FrameIterator & it,const EcmaVM * vm,int & LineNumber)312 void JsStackGetter::GetCallLineNumber(const FrameIterator &it, const EcmaVM *vm, int &LineNumber)
313 {
314     JSThread *thread = vm->GetJSThreadNoCheck();
315     FrameIterator itNext(it.GetSp(), it.GetThread());
316     itNext.Advance<GCVisitedFlag::IGNORED>();
317     auto nextMethod = itNext.CheckAndGetMethod();
318     if (nextMethod == nullptr) {
319         return ;
320     }
321     JSFunction* function = JSFunction::Cast(itNext.GetFunction().GetTaggedObject());
322     JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo(thread);
323     if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile(thread) != nullptr) {
324         DebugInfoExtractor *debugExtractor =
325             JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile(thread));
326         if (debugExtractor == nullptr) {
327             return;
328         }
329         MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral(thread);
330         if (methodLiteral == nullptr) {
331             return;
332         }
333         panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
334         int lineNum = 0;
335         auto callbackLineFunc = [&lineNum](int32_t line) -> bool {
336             lineNum = line + 1;
337             return true;
338         };
339         uint32_t offset = itNext.GetBytecodeOffset();
340         if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
341             lineNum = 0;
342         }
343         LineNumber = lineNum;
344     }
345 }
346 } // namespace panda::ecmascript
347