1 /*
2 * Copyright (c) 2021 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/cpu_profiler/cpu_profiler.h"
17
18 #include <atomic>
19 #include <chrono>
20 #include <climits>
21 #include <fstream>
22
23 #include "ecmascript/compiler/assembler/assembler.h"
24 #include "ecmascript/dfx/cpu_profiler/sampling_processor.h"
25 #include "ecmascript/frames.h"
26 #include "ecmascript/aot_file_manager.h"
27 #include "ecmascript/jspandafile/js_pandafile_manager.h"
28 #include "ecmascript/taskpool/taskpool.h"
29
30 namespace panda::ecmascript {
31 os::memory::Mutex CpuProfiler::synchronizationMutex_;
32 CMap<pthread_t, const EcmaVM *> CpuProfiler::profilerMap_ = CMap<pthread_t, const EcmaVM *>();
CpuProfiler(const EcmaVM * vm,const int interval)33 CpuProfiler::CpuProfiler(const EcmaVM *vm, const int interval) : vm_(vm), interval_(interval)
34 {
35 generator_ = new SamplesRecord();
36 generator_->SetEnableVMTag(const_cast<EcmaVM *>(vm)->GetJSOptions().EnableCpuProfilerVMTag());
37 if (generator_->SemInit(0, 0, 0) != 0) {
38 LOG_ECMA(ERROR) << "sem_[0] init failed";
39 }
40 if (generator_->SemInit(1, 0, 0) != 0) {
41 LOG_ECMA(ERROR) << "sem_[1] init failed";
42 }
43 if (generator_->SemInit(2, 0, 0) != 0) { // 2: signal 2
44 LOG_ECMA(ERROR) << "sem_[2] init failed";
45 }
46 }
47
StartCpuProfilerForInfo()48 void CpuProfiler::StartCpuProfilerForInfo()
49 {
50 LOG_ECMA(INFO) << "StartCpuProfilerForInfo, Sampling interval is: " << interval_
51 << " ,napi active record is: " << callNapiGetStack_;
52 if (isProfiling_) {
53 LOG_ECMA(ERROR) << "Can not StartCpuProfilerForInfo when CpuProfiler is Profiling";
54 return;
55 }
56 isProfiling_ = true;
57 struct sigaction sa;
58 sa.sa_sigaction = &GetStackSignalHandler;
59 if (sigemptyset(&sa.sa_mask) != 0) {
60 LOG_ECMA(ERROR) << "Parameter set signal set initialization and emptying failed";
61 isProfiling_ = false;
62 return;
63 }
64 sa.sa_flags = SA_RESTART | SA_SIGINFO;
65 if (sigaction(SIGPROF, &sa, nullptr) != 0) {
66 LOG_ECMA(ERROR) << "sigaction failed to set signal";
67 isProfiling_ = false;
68 return;
69 }
70 tid_ = static_cast<pthread_t>(syscall(SYS_gettid));
71 {
72 os::memory::LockHolder lock(synchronizationMutex_);
73 profilerMap_[tid_] = vm_;
74 }
75 vm_->GetJSThread()->SetCallNapiGetStack(callNapiGetStack_);
76 generator_->SetIsStart(true);
77 Taskpool::GetCurrentTaskpool()->PostTask(
78 std::make_unique<SamplingProcessor>(vm_->GetJSThread()->GetThreadId(), generator_, interval_));
79 }
80
StartCpuProfilerForFile(const std::string & fileName)81 void CpuProfiler::StartCpuProfilerForFile(const std::string &fileName)
82 {
83 LOG_ECMA(INFO) << "StartCpuProfilerForFile, Sampling interval is: " << interval_
84 << " ,napi active record is: " << callNapiGetStack_;
85 if (isProfiling_) {
86 LOG_ECMA(ERROR) << "Can not StartCpuProfilerForFile when CpuProfiler is Profiling";
87 return;
88 }
89 isProfiling_ = true;
90 std::string absoluteFilePath("");
91 if (!CheckFileName(fileName, absoluteFilePath)) {
92 LOG_ECMA(ERROR) << "The filename contains illegal characters";
93 isProfiling_ = false;
94 return;
95 }
96 fileName_ = absoluteFilePath;
97 if (fileName_.empty()) {
98 LOG_ECMA(ERROR) << "CpuProfiler filename is empty!";
99 isProfiling_ = false;
100 return;
101 }
102 generator_->SetFileName(fileName_);
103 generator_->fileHandle_.open(fileName_.c_str());
104 if (generator_->fileHandle_.fail()) {
105 LOG_ECMA(ERROR) << "File open failed";
106 isProfiling_ = false;
107 return;
108 }
109 struct sigaction sa;
110 sa.sa_sigaction = &GetStackSignalHandler;
111 if (sigemptyset(&sa.sa_mask) != 0) {
112 LOG_ECMA(ERROR) << "Parameter set signal set initialization and emptying failed";
113 isProfiling_ = false;
114 return;
115 }
116 sa.sa_flags = SA_RESTART | SA_SIGINFO;
117 if (sigaction(SIGPROF, &sa, nullptr) != 0) {
118 LOG_ECMA(ERROR) << "sigaction failed to set signal";
119 isProfiling_ = false;
120 return;
121 }
122 tid_ = static_cast<pthread_t>(syscall(SYS_gettid));
123 {
124 os::memory::LockHolder lock(synchronizationMutex_);
125 profilerMap_[tid_] = vm_;
126 }
127 outToFile_ = true;
128 vm_->GetJSThread()->SetCallNapiGetStack(callNapiGetStack_);
129 generator_->SetIsStart(true);
130 Taskpool::GetCurrentTaskpool()->PostTask(
131 std::make_unique<SamplingProcessor>(vm_->GetJSThread()->GetThreadId(), generator_, interval_));
132 }
133
StopCpuProfilerForInfo()134 std::unique_ptr<struct ProfileInfo> CpuProfiler::StopCpuProfilerForInfo()
135 {
136 LOG_ECMA(INFO) << "StopCpuProfilerForInfo enter";
137 std::unique_ptr<struct ProfileInfo> profileInfo;
138 if (!isProfiling_) {
139 LOG_ECMA(ERROR) << "Do not execute stop cpuprofiler twice in a row or didn't execute the start\
140 or the sampling thread is not started";
141 return profileInfo;
142 }
143 if (outToFile_) {
144 LOG_ECMA(ERROR) << "Can not Stop a CpuProfiler sampling which is for file output by this stop method";
145 return profileInfo;
146 }
147 isProfiling_ = false;
148 vm_->GetJSThread()->SetCallNapiGetStack(false);
149 generator_->SetIsStart(false);
150 generator_->SetIsBreakSampleFlag(true);
151 if (generator_->SemPost(0) != 0) {
152 LOG_ECMA(ERROR) << "sem_[0] post failed";
153 return profileInfo;
154 }
155 if (generator_->SemWait(1) != 0) {
156 LOG_ECMA(ERROR) << "sem_[1] wait failed";
157 return profileInfo;
158 }
159 profileInfo = generator_->GetProfileInfo();
160 return profileInfo;
161 }
162
SetCpuSamplingInterval(int interval)163 void CpuProfiler::SetCpuSamplingInterval(int interval)
164 {
165 interval_ = static_cast<uint32_t>(interval);
166 }
167
SetCallNapiGetStack(bool getStack)168 void CpuProfiler::SetCallNapiGetStack(bool getStack)
169 {
170 callNapiGetStack_ = getStack;
171 }
172
StopCpuProfilerForFile()173 void CpuProfiler::StopCpuProfilerForFile()
174 {
175 LOG_ECMA(INFO) << "StopCpuProfilerForFile enter";
176 if (!isProfiling_) {
177 LOG_ECMA(ERROR) << "Do not execute stop cpuprofiler twice in a row or didn't execute the start\
178 or the sampling thread is not started";
179 return;
180 }
181
182 if (!outToFile_) {
183 LOG_ECMA(ERROR) << "Can not Stop a CpuProfiler sampling which is for return profile info by\
184 this stop method";
185 return;
186 }
187
188 isProfiling_ = false;
189 vm_->GetJSThread()->SetCallNapiGetStack(false);
190 generator_->SetIsStart(false);
191 generator_->SetIsBreakSampleFlag(true);
192 if (generator_->SemPost(0) != 0) {
193 LOG_ECMA(ERROR) << "sem_[0] post failed";
194 return;
195 }
196 if (generator_->SemWait(1) != 0) {
197 LOG_ECMA(ERROR) << "sem_[1] wait failed";
198 return;
199 }
200 generator_->StringifySampleData();
201 std::string fileData = generator_->GetSampleData();
202 generator_->fileHandle_ << fileData;
203 }
204
~CpuProfiler()205 CpuProfiler::~CpuProfiler()
206 {
207 if (generator_->SemDestroy(0) != 0) {
208 LOG_ECMA(ERROR) << "sem_[0] destroy failed";
209 }
210 if (generator_->SemDestroy(1) != 0) {
211 LOG_ECMA(ERROR) << "sem_[1] destroy failed";
212 }
213 if (generator_->SemDestroy(2) != 0) { // 2: signal 2
214 LOG_ECMA(ERROR) << "sem_[2] destroy failed";
215 }
216 if (generator_ != nullptr) {
217 delete generator_;
218 generator_ = nullptr;
219 }
220 }
221
SetProfileStart(uint64_t nowTimeStamp)222 void CpuProfiler::SetProfileStart(uint64_t nowTimeStamp)
223 {
224 uint64_t ts = SamplingProcessor::GetMicrosecondsTimeStamp();
225 struct CurrentProcessInfo currentProcessInfo = {0};
226 GetCurrentProcessInfo(currentProcessInfo);
227 std::string data = "";
228 data = "[{\"args\":{\"data\":{\"frames\":[{\"processId\":" + std::to_string(currentProcessInfo.pid) + "}]"
229 + ",\"persistentIds\":true}},\"cat\":\"disabled-by-default-devtools.timeline\","
230 + "\"name\":\"TracingStartedInBrowser\",\"ph\":\"I\",\"pid\":"
231 + std::to_string(currentProcessInfo.pid) + ",\"s\":\"t\",\"tid\":"
232 + std::to_string(currentProcessInfo.tid) + ",\"ts\":"
233 + std::to_string(ts) + ",\"tts\":178460227},\n";
234 ts = SamplingProcessor::GetMicrosecondsTimeStamp();
235 data += "{\"args\":{\"data\":{\"startTime\":" + std::to_string(nowTimeStamp) + "}},"
236 + "\"cat\":\"disabled-by-default-ark.cpu_profiler\",\"id\":\"0x2\","
237 + "\"name\":\"Profile\",\"ph\":\"P\",\"pid\":"
238 + std::to_string(currentProcessInfo.pid) + ",\"tid\":"
239 + std::to_string(currentProcessInfo.tid) + ",\"ts\":"
240 + std::to_string(ts) + ",\"tts\":" + std::to_string(currentProcessInfo.tts)
241 + "},\n";
242 generator_->SetStartsampleData(data);
243 }
244
GetCurrentProcessInfo(struct CurrentProcessInfo & currentProcessInfo)245 void CpuProfiler::GetCurrentProcessInfo(struct CurrentProcessInfo ¤tProcessInfo)
246 {
247 currentProcessInfo.nowTimeStamp = SamplingProcessor::GetMicrosecondsTimeStamp();
248 currentProcessInfo.pid = getpid();
249 if (syscall(SYS_gettid) == -1) {
250 LOG_FULL(FATAL) << "syscall failed";
251 UNREACHABLE();
252 }
253 currentProcessInfo.tts = SamplingProcessor::GetMicrosecondsTimeStamp();
254 }
255
GetRunningState(const FrameIterator & it,const JSPandaFile * jsPandaFile,bool topFrame) const256 RunningState CpuProfiler::GetRunningState(const FrameIterator &it,
257 const JSPandaFile *jsPandaFile, bool topFrame) const
258 {
259 JSThread *thread = vm_->GetAssociatedJSThread();
260 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
261
262 if (topFrame && function->IsCallNapi()) {
263 return RunningState::NAPI;
264 }
265 if (topFrame && thread->GetGcState()) {
266 return RunningState::GC;
267 }
268 // napi method
269 if (function->IsCallNapi()) {
270 return RunningState::NAPI;
271 }
272 JSTaggedValue extraInfoValue = function->GetFunctionExtraInfo();
273 if (extraInfoValue.IsJSNativePointer() || jsPandaFile == nullptr) {
274 if (extraInfoValue.IsJSNativePointer()) {
275 return RunningState::ARKUI_ENGINE;
276 }
277 return RunningState::BUILTIN;
278 }
279 if (it.IsLeaveFrame()) {
280 return RunningState::RUNTIME;
281 }
282 if (it.IsOptimizedJSFunctionFrame()) {
283 return RunningState::AOT;
284 }
285 if (topFrame && thread->GetRuntimeState()) {
286 return RunningState::RUNTIME;
287 }
288 if (thread->IsAsmInterpreter()) {
289 return RunningState::AINT;
290 }
291 return RunningState::CINT;
292 }
293
GetStack(FrameIterator & it)294 void CpuProfiler::GetStack(FrameIterator &it)
295 {
296 const CMap<struct MethodKey, struct FrameInfo> &stackInfo = generator_->GetStackInfo();
297 bool topFrame = true;
298 generator_->ResetFrameLength();
299 for (; !it.Done(); it.Advance<>()) {
300 auto method = it.CheckAndGetMethod();
301 if (method == nullptr) {
302 continue;
303 }
304 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
305 struct MethodKey methodKey;
306 methodKey.deoptType = method->GetDeoptType();
307 if (topFrame) {
308 methodKey.state = GetRunningState(it, jsPandaFile, true);
309 topFrame = false;
310 } else {
311 methodKey.state = GetRunningState(it, jsPandaFile, false);
312 }
313 void *methodIdentifier = GetMethodIdentifier(method, it);
314 if (methodIdentifier == nullptr) {
315 generator_->SetIsBreakSampleFlag(true);
316 return;
317 }
318 methodKey.methodIdentifier = methodIdentifier;
319 if (stackInfo.count(methodKey) == 0) {
320 if (UNLIKELY(!ParseMethodInfo(methodKey, it, jsPandaFile, false))) {
321 generator_->SetIsBreakSampleFlag(true);
322 return;
323 }
324 }
325 if (UNLIKELY(!generator_->PushFrameStack(methodKey))) {
326 generator_->SetIsBreakSampleFlag(true);
327 return;
328 }
329 }
330 generator_->PostFrame();
331 }
332
GetStackCallNapi(JSThread * thread,bool beforeCallNapi)333 void CpuProfiler::GetStackCallNapi(JSThread *thread, bool beforeCallNapi)
334 {
335 uint64_t tempTimeStamp = SamplingProcessor::GetMicrosecondsTimeStamp();
336 if (beforeCallNapi) {
337 if (tempTimeStamp - beforeCallNapiTimeStamp_ < INTERVAL_OF_ACTIVE_SAMPLING) {
338 beforeCallNapiTimeStamp_ = tempTimeStamp;
339 return;
340 }
341 beforeCallNapiTimeStamp_ = tempTimeStamp;
342 } else {
343 if (tempTimeStamp - beforeCallNapiTimeStamp_ < CPUPROFILER_DEFAULT_INTERVAL) {
344 return;
345 }
346 }
347 [[maybe_unused]] CallNapiScope scope(this);
348 const CMap<struct MethodKey, struct FrameInfo> &stackInfo = generator_->GetStackInfo();
349 generator_->ClearNapiStack();
350 bool topFrame = true;
351 auto currentFrame = const_cast<JSTaggedType *>(thread->GetCurrentFrame());
352 FrameIterator it(currentFrame, thread);
353 if (!beforeCallNapi) {
354 it.Advance<GCVisitedFlag::IGNORED>();
355 }
356 for (; !it.Done(); it.Advance<GCVisitedFlag::IGNORED>()) {
357 auto method = it.CheckAndGetMethod();
358 if (method == nullptr) {
359 continue;
360 }
361
362 struct MethodKey methodKey;
363 methodKey.deoptType = method->GetDeoptType();
364 if (topFrame) {
365 if (beforeCallNapi) {
366 methodKey.state = RunningState::NAPI;
367 } else {
368 methodKey.state = GetRunningState(it, method->GetJSPandaFile(), true);
369 }
370 topFrame = false;
371 } else {
372 methodKey.state = GetRunningState(it, method->GetJSPandaFile(), false);
373 }
374 void *methodIdentifier = GetMethodIdentifier(method, it);
375 if (methodIdentifier == nullptr) {
376 return;
377 }
378 methodKey.methodIdentifier = methodIdentifier;
379 if (stackInfo.count(methodKey) == 0) {
380 if (UNLIKELY(!ParseMethodInfo(methodKey, it, method->GetJSPandaFile(), true))) {
381 return;
382 }
383 }
384 if (UNLIKELY(!generator_->PushNapiFrameStack(methodKey))) {
385 return;
386 }
387 }
388 generator_->PostNapiFrame();
389 }
390
ParseMethodInfo(struct MethodKey & methodKey,const FrameIterator & it,const JSPandaFile * jsPandaFile,bool isCallNapi)391 bool CpuProfiler::ParseMethodInfo(struct MethodKey &methodKey,
392 const FrameIterator &it,
393 const JSPandaFile *jsPandaFile,
394 bool isCallNapi)
395 {
396 struct FrameInfoTemp codeEntry;
397 codeEntry.methodKey = methodKey;
398 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
399 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
400 if (extraInfoValue.CheckIsJSNativePointer() || jsPandaFile == nullptr) {
401 if (!CheckAndCopy(codeEntry.codeType, sizeof(codeEntry.codeType), "other")) {
402 return false;
403 }
404 FrameIterator itNext(it.GetSp(), it.GetThread());
405 itNext.Advance<GCVisitedFlag::IGNORED>();
406 GetNativeMethodCallPos(itNext, codeEntry);
407 GetNativeStack(it, codeEntry.functionName, sizeof(codeEntry.functionName));
408 } else {
409 if (!CheckAndCopy(codeEntry.codeType, sizeof(codeEntry.codeType), "JS")) {
410 return false;
411 }
412 const char *tempVariable = MethodLiteral::GetMethodName(jsPandaFile,
413 reinterpret_cast<MethodLiteral *>(methodKey.methodIdentifier)->GetMethodId());
414 uint8_t length = strlen(tempVariable);
415 if (length != 0 && tempVariable[0] == '#') {
416 uint8_t index = length - 1;
417 while (tempVariable[index] != '#') {
418 index--;
419 }
420 tempVariable += (index + 1);
421 }
422 if (strlen(tempVariable) == 0) {
423 tempVariable = "anonymous";
424 }
425 if (!CheckAndCopy(codeEntry.functionName, sizeof(codeEntry.functionName), tempVariable)) {
426 return false;
427 }
428 // source file
429 DebugInfoExtractor *debugExtractor =
430 JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
431 const std::string &sourceFile =
432 debugExtractor->GetSourceFile(reinterpret_cast<MethodLiteral *>(methodKey.methodIdentifier)->GetMethodId());
433 if (sourceFile.empty()) {
434 tempVariable = "";
435 } else {
436 tempVariable = sourceFile.c_str();
437 }
438 if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), tempVariable)) {
439 return false;
440 }
441 // line number and clomn number
442 panda_file::File::EntityId methodId =
443 reinterpret_cast<MethodLiteral *>(methodKey.methodIdentifier)->GetMethodId();
444 codeEntry.lineNumber = debugExtractor->GetFristLine(methodId);
445 codeEntry.columnNumber = debugExtractor->GetFristColumn(methodId);
446 }
447 if (isCallNapi) {
448 return generator_->PushNapiStackInfo(codeEntry);
449 }
450 return generator_->PushStackInfo(codeEntry);
451 }
452
GetNativeStack(const FrameIterator & it,char * functionName,size_t size)453 void CpuProfiler::GetNativeStack(const FrameIterator &it, char *functionName, size_t size)
454 {
455 std::stringstream stream;
456 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
457 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
458 JSThread *thread = vm_->GetJSThread();
459 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
460 JSHandle<JSTaggedValue> nameKey = globalConst->GetHandledNameString();
461 JSHandle<JSTaggedValue> func(thread, function);
462 JSHandle<JSTaggedValue> funcNameValue = JSObject::GetProperty(thread, func, nameKey).GetValue();
463 std::string methodNameStr;
464 if (funcNameValue->IsString()) {
465 JSHandle<EcmaString> methodName(funcNameValue);
466 methodNameStr = EcmaStringAccessor(methodName).ToStdString();
467 }
468 // napi method
469 if (function->IsCallNapi() && extraInfoValue.CheckIsJSNativePointer()) {
470 JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
471 auto cb = vm_->GetNativePtrGetter();
472 if (cb != nullptr && extraInfo != nullptr) {
473 if (!vm_->GetJSThread()->CpuProfilerCheckJSTaggedType(extraInfoValue.GetRawData())) {
474 return;
475 }
476 auto addr = cb(reinterpret_cast<void *>(extraInfo->GetData()));
477 stream << addr;
478 CheckAndCopy(functionName, size, methodNameStr.c_str());
479 const uint8_t methodNameStrLength = methodNameStr.size();
480 CheckAndCopy(functionName + methodNameStrLength, size - methodNameStrLength, "(");
481 const uint8_t napiBeginLength = 1; // 1:the length of "("
482 CheckAndCopy(functionName + methodNameStrLength + napiBeginLength,
483 size - methodNameStrLength - napiBeginLength, stream.str().c_str());
484 uint8_t srcLength = stream.str().size();
485 CheckAndCopy(functionName + methodNameStrLength + napiBeginLength + srcLength,
486 size - methodNameStrLength - napiBeginLength - srcLength, ")");
487 return;
488 }
489 }
490 CheckAndCopy(functionName, size, methodNameStr.c_str());
491 }
492
GetStackSignalHandler(int signal,siginfo_t * siginfo,void * context)493 void CpuProfiler::GetStackSignalHandler(int signal, [[maybe_unused]] siginfo_t *siginfo, void *context)
494 {
495 if (signal != SIGPROF) {
496 return;
497 }
498 CpuProfiler *profiler = nullptr;
499 JSThread *thread = nullptr;
500 {
501 os::memory::LockHolder lock(synchronizationMutex_);
502 pthread_t tid = static_cast<pthread_t>(syscall(SYS_gettid));
503 const EcmaVM *vm = profilerMap_[tid];
504 if (vm == nullptr) {
505 LOG_ECMA(ERROR) << "CpuProfiler GetStackSignalHandler vm is nullptr";
506 return;
507 }
508 profiler = vm->GetProfiler();
509 thread = vm->GetAssociatedJSThread();
510 if (profiler == nullptr) {
511 LOG_ECMA(ERROR) << "CpuProfiler GetStackSignalHandler profiler is nullptr";
512 return;
513 }
514 }
515
516 if (profiler->GetBuildNapiStack()) {
517 if (profiler->generator_->SemPost(0) != 0) {
518 LOG_ECMA(ERROR) << "sem_[0] post failed";
519 }
520 return;
521 }
522
523 uint64_t pc = 0;
524 if (thread->IsAsmInterpreter()) {
525 // If the attempt fails, the callback will be terminated directly to avoid the reentrancy deadlock,
526 // and a sampling will be abandoned. Failures are rare, so the impact on the overall sampling results
527 // is very limited.
528 if (!thread->GetEcmaVM()->GetAOTFileManager()->TryReadLock()) {
529 if (profiler->generator_->SemPost(0) != 0) {
530 LOG_ECMA(ERROR) << "sem_[0] post failed";
531 }
532 return;
533 }
534 pc = GetPcFromContext(context);
535 }
536 if (thread->IsAsmInterpreter() && profiler->IsAddrAtStubOrAot(pc) &&
537 !profiler->IsEntryFrameHeaderOrTail(thread, pc)) {
538 [[maybe_unused]] ucontext_t *ucontext = reinterpret_cast<ucontext_t*>(context);
539 [[maybe_unused]] mcontext_t &mcontext = ucontext->uc_mcontext;
540 [[maybe_unused]] void *fp = nullptr;
541 [[maybe_unused]] void *sp = nullptr;
542 #if defined(PANDA_TARGET_AMD64)
543 fp = reinterpret_cast<void*>(mcontext.gregs[REG_RBP]);
544 sp = reinterpret_cast<void*>(mcontext.gregs[REG_RSP]);
545 #elif defined(PANDA_TARGET_ARM64)
546 fp = reinterpret_cast<void*>(mcontext.regs[29]); // FP is an alias for x29.
547 sp = reinterpret_cast<void*>(mcontext.sp);
548 #else
549 LOG_FULL(FATAL) << "AsmInterpreter does not currently support other platforms, please run on x64 and arm64";
550 return;
551 #endif
552 if (reinterpret_cast<uint64_t*>(sp) > reinterpret_cast<uint64_t*>(fp)) {
553 LOG_ECMA(ERROR) << "sp > fp, stack frame exception";
554 if (profiler->generator_->SemPost(0) != 0) {
555 LOG_ECMA(ERROR) << "sem_[0] post failed";
556 return;
557 }
558 return;
559 }
560 if (profiler->CheckFrameType(thread, reinterpret_cast<JSTaggedType *>(fp))) {
561 FrameIterator it(reinterpret_cast<JSTaggedType *>(fp), thread);
562 profiler->GetStack(it);
563 }
564 } else if (thread->IsAsmInterpreter()) {
565 if (thread->GetLastLeaveFrame() != nullptr) {
566 JSTaggedType *leaveFrame = const_cast<JSTaggedType *>(thread->GetLastLeaveFrame());
567 if (profiler->CheckFrameType(thread, leaveFrame)) {
568 FrameIterator it(leaveFrame, thread);
569 profiler->GetStack(it);
570 }
571 }
572 } else {
573 if (thread->GetCurrentFrame() != nullptr) {
574 if (profiler->CheckFrameType(thread, const_cast<JSTaggedType *>(thread->GetCurrentFrame()))) {
575 FrameHandler frameHandler(thread);
576 FrameIterator it(frameHandler.GetSp(), thread);
577 profiler->GetStack(it);
578 }
579 }
580 }
581 if (profiler->generator_->SemPost(0) != 0) {
582 LOG_ECMA(ERROR) << "sem_[0] post failed";
583 return;
584 }
585 }
586
CheckFrameType(JSThread * thread,JSTaggedType * sp)587 bool CpuProfiler::CheckFrameType(JSThread *thread, JSTaggedType *sp)
588 {
589 FrameType type = FrameHandler::GetFrameType(sp);
590 if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
591 return false;
592 }
593
594 FrameIterator iterator(sp, thread);
595 iterator.Advance();
596 JSTaggedType *preSp = iterator.GetSp();
597 if (preSp == nullptr) {
598 return true;
599 }
600 #if defined(PANDA_TARGET_64)
601 if (thread->IsAsmInterpreter() && !thread->IsLegalSp(reinterpret_cast<uintptr_t>(preSp))) {
602 return false;
603 }
604 #endif
605 type = FrameHandler::GetFrameType(preSp);
606 if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
607 return false;
608 }
609 return true;
610 }
611
InHeaderOrTail(uint64_t pc,uint64_t entryBegin,uint64_t entryDuration,uint64_t headerSize,uint64_t tailSize) const612 bool CpuProfiler::InHeaderOrTail(uint64_t pc, uint64_t entryBegin, uint64_t entryDuration, uint64_t headerSize,
613 uint64_t tailSize) const
614 {
615 uintptr_t entryEnd = entryBegin + entryDuration;
616 if (pc >= entryBegin && pc <= (entryBegin + headerSize)) {
617 return true;
618 }
619 if (pc <= entryEnd && pc >= (entryEnd - tailSize)) {
620 return true;
621 }
622 return false;
623 }
624
IsEntryFrameHeaderOrTail(JSThread * thread,uint64_t pc) const625 bool CpuProfiler::IsEntryFrameHeaderOrTail(JSThread *thread, uint64_t pc) const
626 {
627 uint64_t headerSize = 0;
628 uint64_t tailSize = 0;
629 uint64_t entryDuration = 0;
630 Assembler::GetFrameCompletionPos(headerSize, tailSize, entryDuration);
631 uintptr_t entryBegin = thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_AsmInterpreterEntry);
632 bool inAsmInterpreterEntry = InHeaderOrTail(pc, entryBegin, entryDuration, headerSize, tailSize);
633 entryBegin = thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_GeneratorReEnterAsmInterp);
634 bool inGeneratorReEnterAsmInterp = InHeaderOrTail(pc, entryBegin, entryDuration, headerSize, tailSize);
635 return (inAsmInterpreterEntry || inGeneratorReEnterAsmInterp);
636 }
637
GetPcFromContext(void * context)638 uint64_t CpuProfiler::GetPcFromContext(void *context)
639 {
640 [[maybe_unused]] ucontext_t *ucontext = reinterpret_cast<ucontext_t*>(context);
641 [[maybe_unused]] mcontext_t &mcontext = ucontext->uc_mcontext;
642 uint64_t pc = 0;
643 #if defined(PANDA_TARGET_AMD64)
644 pc = static_cast<uint64_t>(mcontext.gregs[REG_RIP]);
645 #elif defined(PANDA_TARGET_ARM64)
646 pc = static_cast<uint64_t>(mcontext.pc);
647 #else
648 LOG_FULL(FATAL) << "AsmInterpreter does not currently support other platforms, please run on x64 and arm64";
649 pc = 0;
650 #endif
651 return pc;
652 }
653
IsAddrAtStubOrAot(uint64_t pc) const654 bool CpuProfiler::IsAddrAtStubOrAot(uint64_t pc) const
655 {
656 AOTFileManager *loader = vm_->GetAOTFileManager();
657 return loader->InsideStub(pc) || loader->InsideAOT(pc);
658 }
659
CheckFileName(const std::string & fileName,std::string & absoluteFilePath) const660 bool CpuProfiler::CheckFileName(const std::string &fileName, std::string &absoluteFilePath) const
661 {
662 if (fileName.empty()) {
663 return true;
664 }
665
666 if (fileName.size() > PATH_MAX) {
667 return false;
668 }
669
670 CVector<char> resolvedPath(PATH_MAX);
671 auto result = realpath(fileName.c_str(), resolvedPath.data());
672 if (result == nullptr) {
673 LOG_ECMA(INFO) << "The file path does not exist";
674 return false;
675 }
676 std::ofstream file(resolvedPath.data());
677 if (!file.good()) {
678 return false;
679 }
680 file.close();
681 absoluteFilePath = resolvedPath.data();
682 return true;
683 }
684
CheckAndCopy(char * dest,size_t length,const char * src) const685 bool CpuProfiler::CheckAndCopy(char *dest, size_t length, const char *src) const
686 {
687 int srcLength = strlen(src);
688 if (length <= static_cast<size_t>(srcLength) || strcpy_s(dest, srcLength + 1, src) != EOK) {
689 LOG_ECMA(ERROR) << "CpuProfiler parseMethodInfo strcpy_s failed, maybe srcLength more than destLength";
690 return false;
691 }
692 dest[srcLength] = '\0';
693 return true;
694 }
695
GetMethodIdentifier(Method * method,const FrameIterator & it)696 void *CpuProfiler::GetMethodIdentifier(Method *method, const FrameIterator &it)
697 {
698 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
699 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
700 if (extraInfoValue.CheckIsJSNativePointer()) {
701 JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
702 return reinterpret_cast<void *>(extraInfo->GetData());
703 }
704
705 if (method->GetJSPandaFile() == nullptr) {
706 return const_cast<void *>(method->GetNativePointer());
707 }
708
709 MethodLiteral *methodLiteral = method->GetMethodLiteral();
710 return reinterpret_cast<void *>(methodLiteral);
711 }
712
RecordCallNapiInfo(const std::string & methodAddr)713 void CpuProfiler::RecordCallNapiInfo(const std::string &methodAddr)
714 {
715 uint64_t currentTime = SamplingProcessor::GetMicrosecondsTimeStamp();
716 generator_->RecordCallNapiTime(currentTime);
717 generator_->RecordCallNapiAddr(methodAddr);
718 }
719
GetNativeMethodCallPos(FrameIterator & it,FrameInfoTemp & codeEntry)720 void CpuProfiler::GetNativeMethodCallPos(FrameIterator &it, FrameInfoTemp &codeEntry)
721 {
722 auto nextMethod = it.CheckAndGetMethod();
723 if (nextMethod == nullptr) {
724 return ;
725 }
726 JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
727 JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
728 if (!extraInfoValue.IsJSNativePointer() && nextMethod->GetJSPandaFile() != nullptr) {
729 DebugInfoExtractor *debugExtractor =
730 JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile());
731 panda_file::File::EntityId methodId = nextMethod->GetMethodLiteral()->GetMethodId();
732 const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
733 const char *tempVariable;
734 if (sourceFile.empty()) {
735 tempVariable = "";
736 } else {
737 tempVariable = sourceFile.c_str();
738 }
739 if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), tempVariable)) {
740 return;
741 }
742 int lineNumber = 0;
743 auto callbackLineFunc = [&lineNumber](int32_t line) -> bool {
744 lineNumber = line + 1;
745 return true;
746 };
747 int columnNumber = 0;
748 auto callbackColumnFunc = [&columnNumber](int32_t column) -> bool {
749 columnNumber += column + 1;
750 return true;
751 };
752 uint32_t offset = it.GetBytecodeOffset();
753 if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
754 lineNumber = 0;
755 }
756 if (!debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
757 columnNumber = 0;
758 }
759 codeEntry.lineNumber = lineNumber;
760 codeEntry.columnNumber = columnNumber;
761 }
762 }
763
SetBuildNapiStack(bool flag)764 void CpuProfiler::SetBuildNapiStack(bool flag)
765 {
766 isBuildNapiStack_.store(flag);
767 }
768
GetBuildNapiStack()769 bool CpuProfiler::GetBuildNapiStack()
770 {
771 return isBuildNapiStack_.load();
772 }
773 } // namespace panda::ecmascript
774