1 /*
2 * Copyright (c) 2021-2023 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 #define HILOG_TAG "CallStack"
16
17 #include "callstack.h"
18
19 #include <dlfcn.h>
20 #include <pthread.h>
21 #include <iostream>
22
23 #include <string>
24 #include <utility>
25
26 #include "dfx_regs.h"
27 #include "hiperf_hilog.h"
28 #include "register.h"
29
30 #ifdef target_cpu_arm
31 // reg size is int (unw_word_t)
32 #define UNW_WORD_PFLAG "x"
33 #else
34 // reg size is long (unw_word_t)
35 #define UNW_WORD_PFLAG "zx"
36 #endif
37 namespace OHOS {
38 namespace Developtools {
39 namespace HiPerf {
40 using namespace OHOS::HiviewDFX;
41
ReadVirtualThreadMemory(UnwindInfo & unwindInfoPtr,const ADDR_TYPE vaddr,ADDR_TYPE * data)42 bool CallStack::ReadVirtualThreadMemory(UnwindInfo &unwindInfoPtr, const ADDR_TYPE vaddr, ADDR_TYPE *data)
43 {
44 if (__builtin_expect(unwindInfoPtr.thread.pid_ == unwindInfoPtr.callStack.lastPid_ &&
45 vaddr == unwindInfoPtr.callStack.lastAddr_, true)) {
46 *data = unwindInfoPtr.callStack.lastData_;
47 return true;
48 }
49
50 if (unwindInfoPtr.thread.ReadRoMemory(vaddr, reinterpret_cast<uint8_t*>(data), sizeof(ADDR_TYPE))) {
51 unwindInfoPtr.callStack.lastPid_ = unwindInfoPtr.thread.pid_;
52 unwindInfoPtr.callStack.lastAddr_ = vaddr;
53 unwindInfoPtr.callStack.lastData_ = *data;
54 return true;
55 } else {
56 unwindInfoPtr.callStack.lastPid_ = -1;
57 unwindInfoPtr.callStack.lastAddr_ = 0;
58 return false;
59 }
60 }
61
GetIpSP(uint64_t & ip,uint64_t & sp,const u64 * regs,const size_t regNum) const62 bool CallStack::GetIpSP(uint64_t &ip, uint64_t &sp, const u64 *regs, const size_t regNum) const
63 {
64 if (regNum > 0) {
65 CHECK_TRUE(RegisterGetSPValue(sp, arch_, regs, regNum), false, 1, "unable get sp");
66 CHECK_TRUE(RegisterGetIPValue(ip, arch_, regs, regNum), false, 1, "unable get ip");
67 if (ip != 0) {
68 return true;
69 }
70 } else {
71 HLOGW("reg size is 0");
72 return false;
73 }
74 return false;
75 }
76
UnwindCallStack(const VirtualThread & thread,const bool abi32,u64 * regs,const u64 regsNum,const u8 * stack,const u64 stackSize,std::vector<DfxFrame> & callStack,const size_t maxStackLevel)77 bool CallStack::UnwindCallStack(const VirtualThread &thread, const bool abi32, u64 *regs, const u64 regsNum,
78 const u8 *stack, const u64 stackSize, std::vector<DfxFrame> &callStack,
79 const size_t maxStackLevel)
80 {
81 regs_ = regs;
82 regsNum_ = regsNum;
83 stack_ = stack;
84 stackSize_ = stackSize;
85
86 arch_ = GetArchTypeFromABI(abi32);
87 UpdateRegForABI(arch_, regs_);
88 if (!RegisterGetSPValue(stackPoint_, arch_, regs_, regsNum_)) {
89 HLOGE("RegisterGetSPValue failed");
90 return false;
91 } else {
92 stackEnd_ = stackPoint_ + stackSize_;
93 }
94
95 uint64_t ip;
96 uint64_t sp;
97 if (!GetIpSP(ip, sp, regs_, regsNum_)) {
98 HLOGW("unable get sp or sp , unable unwind");
99 return false;
100 } else {
101 if (ip != 0) {
102 HLOGV("unwind:%zu: ip 0x%" PRIx64 " sp 0x%" PRIx64 "", callStack.size(), ip, sp);
103 callStack.emplace_back(ip, sp);
104 }
105 }
106
107 /*
108 * If we need more than one entry, do the DWARF
109 * unwind itself.
110 */
111 if (maxStackLevel - 1 > 0) {
112 return DoUnwind2(thread, callStack, maxStackLevel);
113 }
114 return true;
115 }
116
LogFrame(const std::string msg,const std::vector<DfxFrame> & frames)117 void CallStack::LogFrame(const std::string msg, const std::vector<DfxFrame> &frames)
118 {
119 HLOGM("%s", msg.c_str());
120 int level = 0;
121 for (auto& frame : frames) {
122 HLOGM("%d:%s", level++, frame.ToString().c_str());
123 }
124 }
125
126 /*
127 we should have CallStack cache for each thread
128 end begin
129 0. A -> B -> C -> E -> F
130 1. C -> E -> F
131 2. B -> C
132 3. A -> B -> C
133 4. B -> G -> H
134 5. J -> C
135
136 0 is our cache
137 1 2 3... is from record
138
139 use expandLimit to setup how may frame match is needs
140
141 */
DoExpandCallStack(std::vector<DfxFrame> & newCallFrames,const std::vector<DfxFrame> & cachedCallFrames,const size_t expandLimit)142 size_t CallStack::DoExpandCallStack(std::vector<DfxFrame> &newCallFrames,
143 const std::vector<DfxFrame> &cachedCallFrames,
144 const size_t expandLimit)
145 {
146 int maxCycle = 0;
147
148 if (expandLimit == 0 || newCallFrames.size() < expandLimit ||
149 cachedCallFrames.size() < expandLimit ||
150 cachedCallFrames.size() >= MAX_CALL_FRAME_UNWIND_SIZE) {
151 HLOGM("expandLimit %zu not match new %zu cache %zu", expandLimit, newCallFrames.size(),
152 cachedCallFrames.size());
153 return 0; // size not enough
154 }
155
156 // called (Stack Bottom) , this will NOT change when compare
157 // in case1 newIt -> C
158 // in case2 newIt -> B
159 const auto newIt = newCallFrames.end() - expandLimit;
160 if (newIt != newCallFrames.end()) {
161 HLOGM("try find new call chain bottom %s for limit %zu", newIt->ToString().c_str(),
162 expandLimit);
163 }
164
165 // first frame search, from called - > caller
166 // for case 2 it should found B
167 size_t distances = expandLimit - 1;
168 auto cachedIt = find(cachedCallFrames.begin(), cachedCallFrames.end(), *newIt);
169 if (cachedIt == cachedCallFrames.end()) {
170 HLOGM("not found in first search");
171 }
172
173 // cache frame found
174 while (std::distance(cachedIt, cachedCallFrames.end()) >= signed(expandLimit)) {
175 HLOG_ASSERT_MESSAGE(maxCycle++ < MAX_CALL_FRAME_EXPAND_CYCLE, "MAX_UNWIND_CYCLE = %d reach",
176 MAX_CALL_FRAME_EXPAND_CYCLE);
177
178 if (std::equal(newIt, newIt + expandLimit, cachedIt)) {
179 HLOGM("match %s + %zu", newIt->ToString().c_str(), expandLimit);
180 cachedIt += expandLimit; // in while we check the boundary safe
181 if (cachedIt == cachedCallFrames.end()) {
182 // same but no more need expand
183 break;
184 }
185
186 // expand the frame and make some log ?
187 LogFrame("newCallStack:", newCallFrames);
188 LogFrame("cachedCallStack:", cachedCallFrames);
189
190 newCallFrames.insert(newCallFrames.end(), cachedIt, cachedCallFrames.end());
191 auto expands = std::distance(cachedIt, cachedCallFrames.end());
192 HLOGV("merge callstack increse to %zu (+%zd) ", newCallFrames.size(), expands);
193 // we done the deal
194 return expands;
195 } else {
196 // quick search next same farme again
197 cachedIt++;
198 if (cachedIt != cachedCallFrames.end()) {
199 HLOGM("search next");
200 cachedIt = find(cachedIt, cachedCallFrames.end(), *newIt);
201 }
202 }
203 }
204 HLOGM("cachedIt distance %zd , need %zd", std::distance(cachedCallFrames.begin(), cachedIt),
205 distances);
206 return 0u; // nothing expand
207 }
208
ExpandCallStack(const pid_t tid,std::vector<DfxFrame> & callFrames,const size_t expandLimit)209 size_t CallStack::ExpandCallStack(const pid_t tid, std::vector<DfxFrame> &callFrames, const size_t expandLimit)
210 {
211 size_t expand = 0u;
212 if (expandLimit == 0) {
213 return expand; // nothing need to do
214 } else if (callFrames.size() < expandLimit) {
215 HLOGM("new callstack is too small, skip it");
216 return expand;
217 }
218 if (!cachedCallFramesMap_.count(tid)) {
219 cachedCallFramesMap_[tid].reserve(MAX_CALL_FRAME_EXPAND_CACHE_SIZE);
220 }
221 if (callFrames.size() >= 1u) {
222 // get top (Earliest caller)
223 HashList<uint64_t, std::vector<DfxFrame>> &cachedCallFrames = cachedCallFramesMap_[tid];
224 HLOGV("find call stack frames in cache size %zu", cachedCallFrames.size());
225 // compare
226 using namespace std::rel_ops; // enable complement comparing operators
227 for (auto itr = cachedCallFrames.begin(); itr < cachedCallFrames.end(); ++itr) {
228 // each cached callstack
229 /*
230 stack 2 1 0
231 cache A -> B -> C
232 new B -> C
233 check:
234 1 if new B == cache C
235 2 if new B == cache B
236 3 if new C == new C (if limit > 0)
237 4 insert A after B in new stack
238 */
239 const std::vector<DfxFrame> &cachedCallStack = *itr;
240 if (cachedCallStack.size() < expandLimit) {
241 HLOGM("cache callstack is too small, skip it");
242 continue; // check next
243 }
244 expand = DoExpandCallStack(callFrames, cachedCallStack, expandLimit);
245 if (expand > 0) {
246 break;
247 }
248 }
249 // add new one in to cache cachedCallFrames.
250 // further optimization can be done by caching pointer which avoids copying
251 // vector
252 cachedCallFrames[callFrames[0].pc] = callFrames;
253 }
254 HLOGM("expand %zu", expand);
255 return expand;
256 }
257
258 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
DoUnwind2(const VirtualThread & thread,std::vector<DfxFrame> & callStack,const size_t maxStackLevel)259 bool CallStack::DoUnwind2(const VirtualThread &thread, std::vector<DfxFrame> &callStack,
260 const size_t maxStackLevel)
261 {
262 #ifdef target_cpu_x86_64
263 return false;
264 #else
265 UnwindInfo unwindInfo = {
266 .thread = thread,
267 .callStack = *this,
268 };
269
270 if (pidUnwinder_.count(thread.pid_) == 0) {
271 pidUnwinder_.emplace(thread.pid_, std::make_shared<Unwinder>(accessor_));
272 }
273 auto unwinder = pidUnwinder_[thread.pid_];
274
275 #ifdef target_cpu_arm
276 thread_local std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm>();
277 std::vector<uintptr_t> tempRegs;
278 for (u64 i = 0; i < regsNum_; ++i) {
279 tempRegs.push_back(static_cast<uintptr_t>(regs_[i]));
280 }
281 regs->SetRegsData(tempRegs);
282 #else
283 thread_local std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm64>();
284 regs->SetRegsData(reinterpret_cast<uintptr_t*>(regs_), regsNum_);
285 #endif
286 CHECK_TRUE(unwinder != nullptr, false, 0, "");
287 unwinder->EnableJsvmstack(true);
288 unwinder->SetRegs(regs);
289 unwinder->Unwind(&unwindInfo);
290 callStack = unwinder->GetFrames();
291 HLOGD("callStack size:%zu", callStack.size());
292 for (auto frame: callStack) {
293 HLOGD("pc 0x%" PRIx64 " sp 0x%" PRIx64 "", frame.pc, frame.sp);
294 }
295 auto lastIt = callStack.end() - 1;
296 auto preIt = lastIt - 1;
297 if (lastIt != callStack.end() && preIt != callStack.end() &&
298 callStack.size() > 1 && lastIt->pc == preIt->pc && lastIt->sp == preIt->sp) {
299 callStack.erase(lastIt);
300 HLOGD("remove last callframe");
301 }
302 return true;
303 #endif
304 }
305
DumpTableInfo(UnwindTableInfo & outTableInfo)306 void CallStack::DumpTableInfo(UnwindTableInfo &outTableInfo)
307 {
308 HLOGV("unwind_table info: ");
309 HLOGV(" start_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.startPc);
310 HLOGV(" end_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.endPc);
311 HLOGV(" segbase: 0x%016" UNW_WORD_PFLAG "", outTableInfo.segbase);
312 HLOGV(" table_data: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableData);
313 HLOGV(" table_len: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableLen);
314 }
315
FillUnwindTable(SymbolsFile * symbolsFile,std::shared_ptr<DfxMap> map,UnwindInfo * unwindInfoPtr,const uintptr_t pc,UnwindTableInfo & outTableInfo)316 int CallStack::FillUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr,
317 const uintptr_t pc, UnwindTableInfo& outTableInfo)
318 {
319 HLOGM("try search debug info at %s", symbolsFile->filePath_.c_str());
320 CHECK_TRUE(unwindInfoPtr != nullptr, -1, 0, "");
321 auto &tableInfoMap = unwindInfoPtr->callStack.unwindTableInfoMap_;
322 // all the thread in same process have same mmap and symbols
323 if (tableInfoMap.find(unwindInfoPtr->thread.pid_) == tableInfoMap.end()) {
324 tableInfoMap.emplace(unwindInfoPtr->thread.pid_, DsoUnwindTableInfoMap {});
325 }
326 DsoUnwindTableInfoMap &unwTabMap = tableInfoMap[unwindInfoPtr->thread.pid_];
327 // find use dso name as key
328 if (unwTabMap.find(symbolsFile->filePath_) == unwTabMap.end()) {
329 UnwindTableInfo uti;
330 auto elf = symbolsFile->GetElfFile();
331 if (elf == nullptr) {
332 return -1;
333 }
334 if (elf->FindUnwindTableInfo(pc, map, uti) == 0) {
335 CHECK_TRUE(uti.format != -1, -1, 1, "parse unwind table failed.");
336 unwTabMap[symbolsFile->filePath_] = uti;
337 outTableInfo = unwTabMap[symbolsFile->filePath_];
338 DumpTableInfo(uti);
339 return 0;
340 } else {
341 HLOGV("FillUnwindTable failed");
342 return -1;
343 }
344 } else {
345 outTableInfo = unwTabMap[symbolsFile->filePath_];
346 return 0;
347 }
348 return -1;
349 }
350
FindUnwindTable(const uintptr_t pc,UnwindTableInfo & outTableInfo,void * arg)351 int CallStack::FindUnwindTable(const uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg)
352 {
353 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
354 CHECK_TRUE(unwindInfoPtr != nullptr, -1, 0, "");
355 int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
356 if (mapIndex >= 0) {
357 auto map = unwindInfoPtr->thread.GetMaps()[mapIndex];
358 if (map != nullptr) {
359 SymbolsFile *symbolsFile = unwindInfoPtr->thread.FindSymbolsFileByMap(map);
360 if (symbolsFile != nullptr) {
361 return FillUnwindTable(symbolsFile, map, unwindInfoPtr, pc, outTableInfo);
362 } else {
363 HLOGD("no symbols file found for thread %d:%s", unwindInfoPtr->thread.tid_,
364 unwindInfoPtr->thread.name_.c_str());
365 }
366 } else {
367 HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
368 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
369 }
370 } else {
371 HLOGD("map index is -1");
372 }
373 return -1;
374 }
375
AccessMem2(const uintptr_t addr,uintptr_t * val,void * arg)376 int CallStack::AccessMem2(const uintptr_t addr, uintptr_t *val, void *arg)
377 {
378 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
379 *val = 0;
380
381 /* Check overflow. */
382 CHECK_TRUE(unwindInfoPtr != nullptr && (addr + sizeof(uintptr_t) >= addr), -1, 1,
383 "unwindInfoPtr is null or address overflow at 0x%" UNW_WORD_PFLAG " increase 0x%zu",
384 addr, sizeof(uintptr_t));
385
386 if (addr < unwindInfoPtr->callStack.stackPoint_ ||
387 addr + sizeof(uintptr_t) >= unwindInfoPtr->callStack.stackEnd_) {
388 if (ReadVirtualThreadMemory(*unwindInfoPtr, addr, val)) {
389 HLOGM("access_mem addr get val 0x%" UNW_WORD_PFLAG ", from mmap", *val);
390 } else {
391 HLOGW("access_mem mmap 0x%" PRIx64 " failed, STACK RANGE 0x%" PRIx64 "- 0x%" PRIx64 "(0x%" PRIx64 ")",
392 (uint64_t)addr,
393 unwindInfoPtr->callStack.stackPoint_, unwindInfoPtr->callStack.stackEnd_,
394 unwindInfoPtr->callStack.stackEnd_ - unwindInfoPtr->callStack.stackPoint_);
395 return -1;
396 }
397 } else {
398 size_t stackOffset = addr - unwindInfoPtr->callStack.stackPoint_;
399 if (memcpy_s(val, sizeof(uintptr_t), &unwindInfoPtr->callStack.stack_[stackOffset], sizeof(uintptr_t)) != 0) {
400 HLOGE("memcpy_s failed for stack offset %zu", stackOffset);
401 return -1;
402 }
403 HLOGM("access_mem addr val %" UNW_WORD_PFLAG ", from stack offset %zu",
404 *val, stackOffset);
405 }
406
407 return 0;
408 }
GetMapByPc(const uintptr_t pc,std::shared_ptr<DfxMap> & map,void * arg)409 int CallStack::GetMapByPc(const uintptr_t pc, std::shared_ptr<DfxMap>& map, void *arg)
410 {
411 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
412 int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
413 if (mapIndex >= 0) {
414 map = unwindInfoPtr->thread.GetMaps()[mapIndex];
415 if (map != nullptr) {
416 return 0;
417 }
418 }
419 HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
420 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
421 return -1;
422 }
423 #endif
424
CallStack()425 CallStack::CallStack()
426 {
427 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
428 accessor_ = std::make_shared<OHOS::HiviewDFX::UnwindAccessors>();
429 accessor_->FindUnwindTable = &CallStack::FindUnwindTable;
430 accessor_->AccessMem = &CallStack::AccessMem2;
431 accessor_->AccessReg = nullptr;
432 accessor_->GetMapByPc = &CallStack::GetMapByPc;
433 #endif
434 }
435
ClearCache()436 void CallStack::ClearCache()
437 {
438 cachedCallFramesMap_.clear();
439 lastPid_ = -1;
440 lastAddr_ = 0;
441 lastData_ = 0;
442 stackPoint_ = 0;
443 stackEnd_ = 0;
444 regs_ = nullptr;
445 regsNum_ = 0;
446 stack_ = nullptr;
447 stackSize_ = 0;
448
449 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
450 pidUnwinder_.clear();
451 unwindTableInfoMap_.clear();
452 #endif
453 }
454 } // namespace HiPerf
455 } // namespace Developtools
456 } // namespace OHOS
457