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