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, codeEntry);
66 GetNativeStack(vm, it, codeEntry.functionName, sizeof(codeEntry.functionName), isCpuProfiler);
67 } else {
68 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
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 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
140 // it not allow thread check here, if enable thread check, it maybe deadlock in IsInThreadPool
141 JSThread *thread = vm->GetJSThreadNoCheck();
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();
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().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());
215 MethodLiteral *methodLiteral = method->GetMethodLiteral();
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().CheckIsJSNativePointer() ? RunningState::ARKUI_ENGINE :
238 RunningState::BUILTIN;
239 }
240
241 return RunningState::OTHER;
242 }
243
GetNativeMethodCallPos(FrameIterator & it,FrameInfoTemp & codeEntry)244 void JsStackGetter::GetNativeMethodCallPos(FrameIterator &it, FrameInfoTemp &codeEntry)
245 {
246 auto nextMethod = it.CheckAndGetMethod();
247 if (nextMethod == nullptr) {
248 return ;
249 }
250 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
251 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
252 if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile() != nullptr) {
253 DebugInfoExtractor *debugExtractor =
254 JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile());
255 if (debugExtractor == nullptr) {
256 return;
257 }
258 MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral();
259 if (methodLiteral == nullptr) {
260 return;
261 }
262 panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
263 const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
264 const char *tempVariable;
265 if (sourceFile.empty()) {
266 tempVariable = "";
267 } else {
268 tempVariable = sourceFile.c_str();
269 }
270 if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), tempVariable)) {
271 return;
272 }
273 int lineNumber = 0;
274 auto callbackLineFunc = [&lineNumber](int32_t line) -> bool {
275 lineNumber = line + 1;
276 return true;
277 };
278 int columnNumber = 0;
279 auto callbackColumnFunc = [&columnNumber](int32_t column) -> bool {
280 columnNumber += column + 1;
281 return true;
282 };
283 uint32_t offset = it.GetBytecodeOffset();
284 if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
285 lineNumber = 0;
286 }
287 if (!debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
288 columnNumber = 0;
289 }
290 codeEntry.lineNumber = lineNumber;
291 codeEntry.columnNumber = columnNumber;
292 }
293 }
294
GetMethodIdentifier(Method * method,const FrameIterator & it)295 void *JsStackGetter::GetMethodIdentifier(Method *method, const FrameIterator &it)
296 {
297 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
298 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
299 if (method->IsNativeWithCallField()) {
300 if (extraInfoValue.CheckIsJSNativePointer()) {
301 JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
302 return reinterpret_cast<void *>(extraInfo->GetData());
303 }
304 return function->GetNativePointer();
305 }
306
307 MethodLiteral *methodLiteral = method->GetMethodLiteral();
308 return reinterpret_cast<void *>(methodLiteral);
309 }
GetCallLineNumber(const FrameIterator & it,int & LineNumber)310 void JsStackGetter::GetCallLineNumber(const FrameIterator &it, int &LineNumber)
311 {
312 FrameIterator itNext(it.GetSp(), it.GetThread());
313 itNext.Advance<GCVisitedFlag::IGNORED>();
314 auto nextMethod = itNext.CheckAndGetMethod();
315 if (nextMethod == nullptr) {
316 return ;
317 }
318 JSFunction* function = JSFunction::Cast(itNext.GetFunction().GetTaggedObject());
319 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
320 if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile() != nullptr) {
321 DebugInfoExtractor *debugExtractor =
322 JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile());
323 if (debugExtractor == nullptr) {
324 return;
325 }
326 MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral();
327 if (methodLiteral == nullptr) {
328 return;
329 }
330 panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
331 int lineNum = 0;
332 auto callbackLineFunc = [&lineNum](int32_t line) -> bool {
333 lineNum = line + 1;
334 return true;
335 };
336 uint32_t offset = itNext.GetBytecodeOffset();
337 if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
338 lineNum = 0;
339 }
340 LineNumber = lineNum;
341 }
342 }
343 } // namespace panda::ecmascript
344