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 22namespace panda::ecmascript { 23class CpuProfiler; 24class SamplesRecord; 25 26bool 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 51bool 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 123bool 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 134void 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 182RunningState 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 244void 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 295void *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 const_cast<void *>(method->GetNativePointer()); 305 } 306 307 MethodLiteral *methodLiteral = method->GetMethodLiteral(); 308 return reinterpret_cast<void *>(methodLiteral); 309} 310void 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