1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
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 "stack_builder.h"
17
18 namespace OHOS::Developtools::NativeDaemon {
19 static thread_local std::vector<std::string> g_fpJsCallStacks;
20 static thread_local std::vector<u64> g_u64regs;
21 const std::string JS_CALL_STACK_DEPTH_SEP = ","; // ',' is js call stack depth separator
22 const std::string JS_SYMBOL_FILEPATH_SEP = "|"; // '|' is js symbol and filepath separator
23
FillStatsVirtualFrames(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)24 void StackBuilder::FillStatsVirtualFrames(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
25 {
26 auto rawStack = hookRecord->GetRawStack();
27 callFrames.emplace_back(rawStack->stackContext->mallocSize | SIZE_MASK);
28 }
29
FpStackBuilder(NativeHookConfig * config,std::shared_ptr<VirtualRuntime> runtime,std::mutex & mtx)30 FpStackBuilder::FpStackBuilder(NativeHookConfig* config, std::shared_ptr<VirtualRuntime> runtime,
31 std::mutex& mtx) : StackBuilder(config, runtime, mtx)
32 {
33 if (hookConfig_->fp_unwind() && hookConfig_->js_stack_report() > 0) {
34 g_fpJsCallStacks.reserve(hookConfig_->max_js_stack_depth());
35 }
36 }
37
PrepareUnwind(HookRecordPtr hookRecord)38 void FpStackBuilder::PrepareUnwind(HookRecordPtr hookRecord)
39 {
40 auto rawData = hookRecord->GetRawStack();
41 if (runtimeInstance_ != nullptr) {
42 std::lock_guard<std::mutex> guard(mtx_);
43 runtimeInstance_->UpdateThread(rawData->stackContext->pid, rawData->stackContext->tid);
44 }
45 }
46
FillIps(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)47 void FpStackBuilder::FillIps(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
48 {
49 auto rawData = hookRecord->GetRawStack();
50 uint64_t* fpIp = reinterpret_cast<uint64_t *>(rawData->data);
51 uint8_t depth = rawData->fpDepth;
52 for (uint8_t idx = 0; idx < depth; ++idx) {
53 if (fpIp[idx] == 0) {
54 break;
55 }
56 callFrames.emplace_back(StripPac(fpIp[idx], 0));
57 }
58 }
59
FillJsFrame(CallFrame & jsCallFrame)60 void FpStackBuilder::FillJsFrame(CallFrame& jsCallFrame)
61 {
62 DfxSymbol symbol;
63 if (!runtimeInstance_->ArktsGetSymbolCache(jsCallFrame, symbol)) {
64 symbol.filePathId_ = runtimeInstance_->FillArkTsFilePath(jsCallFrame.filePath_);
65 symbol.symbolName_ = jsCallFrame.symbolName_;
66 symbol.module_ = jsCallFrame.filePath_;
67 symbol.symbolId_ = runtimeInstance_->GetJsSymbolCacheSize();
68 runtimeInstance_->FillSymbolNameId(jsCallFrame, symbol);
69 runtimeInstance_->FillFileSet(jsCallFrame, symbol);
70 jsCallFrame.needReport_ |= CALL_FRAME_REPORT;
71 runtimeInstance_->FillJsSymbolCache(jsCallFrame, symbol);
72 }
73 jsCallFrame.callFrameId_ = symbol.symbolId_;
74 jsCallFrame.symbolNameId_ = symbol.symbolNameId_;
75 jsCallFrame.filePathId_ = symbol.filePathId_;
76 jsCallFrame.filePath_ = symbol.module_;
77 jsCallFrame.symbolName_ = symbol.symbolName_;
78 }
79
FillJsSymbols(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)80 void FpStackBuilder::FillJsSymbols(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
81 {
82 auto rawData = hookRecord->GetRawStack();
83 if (!(rawData->stackContext->jsChainId > 0 && rawData->jsStackData)) {
84 return;
85 }
86 if (hookConfig_->statistics_interval() > 0) {
87 uint16_t type = hookRecord->GetType();
88 if (type == FREE_MSG || type == MUNMAP_MSG || type == MEMORY_UNUSING_MSG) {
89 return;
90 }
91 }
92 g_fpJsCallStacks.clear();
93 if (g_fpJsCallStacks.capacity() == 0) {
94 g_fpJsCallStacks.reserve(hookConfig_->max_js_stack_depth());
95 }
96 AdvancedSplitString(rawData->jsStackData, JS_CALL_STACK_DEPTH_SEP, g_fpJsCallStacks);
97 std::lock_guard<std::mutex> guard(mtx_);
98 for (std::string& jsCallStack: g_fpJsCallStacks) {
99 std::string::size_type jsSymbolFilePathSepPos = jsCallStack.find_first_of(JS_SYMBOL_FILEPATH_SEP);
100 if (jsSymbolFilePathSepPos == std::string::npos) {
101 PROFILER_LOG_ERROR(LOG_CORE, "%s: jsCallStack find FAILED!", __func__);
102 continue;
103 }
104 std::string::size_type jsFilePathPos = jsSymbolFilePathSepPos + 1;
105 jsCallStack[jsSymbolFilePathSepPos] = '\0'; // "ts_malloc1'\0'entry/src/main/ets/pages/Index.ets:5:5"
106 CallFrame& jsCallFrame = callFrames.emplace_back(0, 0, true);
107 jsCallFrame.symbolName_ = StringViewMemoryHold::GetInstance().HoldStringView(jsCallStack.c_str());
108 jsCallFrame.filePath_ = StringViewMemoryHold::GetInstance().HoldStringView(jsCallStack.c_str() + jsFilePathPos);
109 if (hookConfig_->offline_symbolization()) {
110 FillJsFrame(jsCallFrame);
111 } else {
112 runtimeInstance_->GetSymbolName(rawData->stackContext->pid, rawData->stackContext->tid,
113 callFrames, 0, true, SymbolType::JS_SYMBOL);
114 }
115 }
116 }
117
FillNativeSymbols(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)118 void FpStackBuilder::FillNativeSymbols(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
119 {
120 auto rawData = hookRecord->GetRawStack();
121 std::lock_guard<std::mutex> guard(mtx_);
122 runtimeInstance_->GetSymbolName(rawData->stackContext->pid, rawData->stackContext->tid,
123 callFrames, 0, true, SymbolType::NATIVE_SYMBOL);
124 }
125
IsRecordInfoValid(HookRecordPtr hookRecord)126 bool FpStackBuilder::IsRecordInfoValid(HookRecordPtr hookRecord)
127 {
128 return hookRecord->IsValid();
129 }
130
DwarfStackBuilder(NativeHookConfig * config,std::shared_ptr<VirtualRuntime> runtime,std::mutex & mtx)131 DwarfStackBuilder::DwarfStackBuilder(NativeHookConfig* config, std::shared_ptr<VirtualRuntime> runtime,
132 std::mutex& mtx) : StackBuilder(config, runtime, mtx)
133 {
134 minStackDepth_ = std::min(hookConfig_->max_stack_depth(), MIN_STACK_DEPTH);
135 if (hookConfig_->blocked()) {
136 minStackDepth_ = static_cast<size_t>(hookConfig_->max_stack_depth());
137 }
138 minStackDepth_ += FILTER_STACK_DEPTH;
139 stackDepth_ = (static_cast<size_t>(hookConfig_->max_stack_depth()) > MAX_CALL_FRAME_UNWIND_SIZE)
140 ? MAX_CALL_FRAME_UNWIND_SIZE
141 : static_cast<size_t>(hookConfig_->max_stack_depth()) + FILTER_STACK_DEPTH;
142 }
143
FillIps(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)144 void DwarfStackBuilder::FillIps(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
145 {
146 auto rawData = hookRecord->GetRawStack();
147 #if defined(__arm__)
148 if (g_u64regs.size() == 0) {
149 g_u64regs.resize(PERF_REG_ARM_MAX);
150 }
151 uint32_t *regAddrArm = reinterpret_cast<uint32_t *>(rawData->data);
152 g_u64regs.assign(regAddrArm, regAddrArm + PERF_REG_ARM_MAX);
153 #else
154 if (g_u64regs.size() == 0) {
155 g_u64regs.resize(PERF_REG_ARM64_MAX);
156 }
157 if (memcpy_s(g_u64regs.data(), sizeof(uint64_t) * PERF_REG_ARM64_MAX, rawData->data,
158 sizeof(uint64_t) * PERF_REG_ARM64_MAX) != EOK) {
159 PROFILER_LOG_ERROR(LOG_CORE, "memcpy_s regs failed");
160 return;
161 }
162 #endif
163 const size_t unwindDepth = rawData->reduceStackFlag ? minStackDepth_ : stackDepth_;
164 std::lock_guard<std::mutex> guard(mtx_);
165 runtimeInstance_->UnwindStack(g_u64regs, rawData->stackData, rawData->stackSize,
166 rawData->stackContext->pid, rawData->stackContext->tid, callFrames, unwindDepth);
167 }
168
FillJsSymbols(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)169 void DwarfStackBuilder::FillJsSymbols(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
170 {
171 if (callFrames.size() <= FILTER_STACK_DEPTH) {
172 return;
173 }
174 auto rawData = hookRecord->GetRawStack();
175 std::lock_guard<std::mutex> guard(mtx_);
176 runtimeInstance_->GetSymbolName(rawData->stackContext->pid, rawData->stackContext->tid, callFrames,
177 FILTER_STACK_DEPTH, true, SymbolType::JS_SYMBOL);
178 }
179
FillNativeSymbols(std::vector<CallFrame> & callFrames,HookRecordPtr hookRecord)180 void DwarfStackBuilder::FillNativeSymbols(std::vector<CallFrame>& callFrames, HookRecordPtr hookRecord)
181 {
182 auto rawData = hookRecord->GetRawStack();
183 if (callFrames.size() <= FILTER_STACK_DEPTH) {
184 return;
185 }
186 std::lock_guard<std::mutex> guard(mtx_);
187 runtimeInstance_->GetSymbolName(rawData->stackContext->pid, rawData->stackContext->tid, callFrames,
188 FILTER_STACK_DEPTH, true, SymbolType::NATIVE_SYMBOL);
189 }
190
IsRecordInfoValid(HookRecordPtr hookRecord)191 bool DwarfStackBuilder::IsRecordInfoValid(HookRecordPtr hookRecord)
192 {
193 auto rawData = hookRecord->GetRawStack();
194 return ((hookRecord->IsValid()) && (rawData->stackSize != 0));
195 }
196
ReplaceErrStack(std::vector<CallFrame> & callFrames)197 void DwarfStackBuilder::ReplaceErrStack(std::vector<CallFrame>& callFrames)
198 {
199 CallFrame& jsCallFrame = callFrames.emplace_back(0);
200 jsCallFrame.symbolName_ = "UnwindErrorDwarf";
201 jsCallFrame.isJsFrame_ = true;
202 jsCallFrame.needReport_ |= (CALL_FRAME_REPORT | SYMBOL_NAME_ID_REPORT | FILE_PATH_ID_REPORT);
203 jsCallFrame.callFrameId_ = DWARF_ERROR_ID;
204 jsCallFrame.symbolNameId_ = DWARF_ERROR_ID;
205 jsCallFrame.filePathId_ = DWARF_ERROR_ID;
206 jsCallFrame.filePath_ = "no-file-path";
207 }
208
BuildStackDirector(NativeHookConfig * config,std::shared_ptr<VirtualRuntime> runtime,std::mutex & mtx)209 BuildStackDirector::BuildStackDirector(NativeHookConfig* config, std::shared_ptr<VirtualRuntime> runtime,
210 std::mutex& mtx) : hookConfig_(config)
211 {
212 if (hookConfig_->fp_unwind()) {
213 builder_ = std::make_shared<FpStackBuilder>(hookConfig_, runtime, mtx);
214 } else {
215 builder_ = std::make_shared<DwarfStackBuilder>(hookConfig_, runtime, mtx);
216 }
217 }
218
ConstructCallFrames(HookRecordPtr hookRecord)219 std::vector<CallFrame>& BuildStackDirector::ConstructCallFrames(HookRecordPtr hookRecord)
220 {
221 callFrames_.clear();
222 if (builder_ == nullptr || hookConfig_ == nullptr) {
223 return callFrames_;
224 }
225 if (callFrames_.capacity() == 0) {
226 callFrames_.reserve(hookConfig_->max_stack_depth() + hookConfig_->max_js_stack_depth());
227 }
228 if (!IsRecordUnwindable(hookRecord)) {
229 return callFrames_;
230 }
231 if (!builder_->IsRecordInfoValid(hookRecord)) {
232 builder_->ReplaceErrStack(callFrames_);
233 return callFrames_;
234 }
235 builder_->PrepareUnwind(hookRecord);
236 builder_->FillIps(callFrames_, hookRecord);
237 if (hookConfig_->js_stack_report()) {
238 builder_->FillJsSymbols(callFrames_, hookRecord);
239 }
240 if (!hookConfig_->offline_symbolization()) {
241 builder_->FillNativeSymbols(callFrames_, hookRecord);
242 }
243 return callFrames_;
244 }
245
IsRecordUnwindable(HookRecordPtr hookRecord)246 bool BuildStackDirector::IsRecordUnwindable(HookRecordPtr hookRecord)
247 {
248 return (hookRecord->GetType() != PR_SET_VMA_MSG);
249 }
250 }