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,ADDR_TYPE vaddr,ADDR_TYPE * data)42 bool CallStack::ReadVirtualThreadMemory(UnwindInfo &unwindInfoPtr, 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,size_t regNum) const62 bool CallStack::GetIpSP(uint64_t &ip, uint64_t &sp, const u64 *regs, 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,bool abi32,u64 * regs,u64 regsNum,const u8 * stack,u64 stackSize,std::vector<DfxFrame> & callStack,size_t maxStackLevel)77 bool CallStack::UnwindCallStack(const VirtualThread &thread, bool abi32, u64 *regs, u64 regsNum,
78 const u8 *stack, u64 stackSize, std::vector<DfxFrame> &callStack,
79 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,size_t expandLimit)142 size_t CallStack::DoExpandCallStack(std::vector<DfxFrame> &newCallFrames,
143 const std::vector<DfxFrame> &cachedCallFrames,
144 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(pid_t tid,std::vector<DfxFrame> & callFrames,size_t expandLimit)209 size_t CallStack::ExpandCallStack(pid_t tid, std::vector<DfxFrame> &callFrames, 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,size_t maxStackLevel)259 bool CallStack::DoUnwind2(const VirtualThread &thread, std::vector<DfxFrame> &callStack,
260 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 static 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 static 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->SetRegs(regs);
288 unwinder->Unwind(&unwindInfo);
289 callStack = unwinder->GetFrames();
290 HLOGD("callStack size:%zu", callStack.size());
291 for (auto frame: callStack) {
292 HLOGD("pc 0x%" PRIx64 " sp 0x%" PRIx64 "", frame.pc, frame.sp);
293 }
294 auto lastIt = callStack.end() - 1;
295 auto preIt = lastIt - 1;
296 if (lastIt != callStack.end() && preIt != callStack.end() &&
297 callStack.size() > 1 && lastIt->pc == preIt->pc && lastIt->sp == preIt->sp) {
298 callStack.erase(lastIt);
299 HLOGD("remove last callframe");
300 }
301 return true;
302 #endif
303 }
304
DumpTableInfo(UnwindTableInfo & outTableInfo)305 void CallStack::DumpTableInfo(UnwindTableInfo &outTableInfo)
306 {
307 HLOGV("unwind_table info: ");
308 HLOGV(" start_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.startPc);
309 HLOGV(" end_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.endPc);
310 HLOGV(" segbase: 0x%016" UNW_WORD_PFLAG "", outTableInfo.segbase);
311 HLOGV(" table_data: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableData);
312 HLOGV(" table_len: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableLen);
313 }
314
FillUnwindTable(SymbolsFile * symbolsFile,std::shared_ptr<DfxMap> map,UnwindInfo * unwindInfoPtr,uintptr_t pc,UnwindTableInfo & outTableInfo)315 int CallStack::FillUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr,
316 uintptr_t pc, UnwindTableInfo& outTableInfo)
317 {
318 HLOGM("try search debug info at %s", symbolsFile->filePath_.c_str());
319 CHECK_TRUE(unwindInfoPtr == nullptr, -1, 0, "");
320 auto &tableInfoMap = unwindInfoPtr->callStack.unwindTableInfoMap_;
321 // all the thread in same process have same mmap and symbols
322 if (tableInfoMap.find(unwindInfoPtr->thread.pid_) == tableInfoMap.end()) {
323 tableInfoMap.emplace(unwindInfoPtr->thread.pid_, DsoUnwindTableInfoMap {});
324 }
325 DsoUnwindTableInfoMap &unwTabMap = tableInfoMap[unwindInfoPtr->thread.pid_];
326 // find use dso name as key
327 if (unwTabMap.find(symbolsFile->filePath_) == unwTabMap.end()) {
328 UnwindTableInfo uti;
329 auto elf = symbolsFile->GetElfFile();
330 if (elf == nullptr) {
331 return -1;
332 }
333 if (elf->FindUnwindTableInfo(pc, map, uti) == 0) {
334 CHECK_TRUE(uti.format == -1, -1, 1, "parse unwind table failed.");
335 unwTabMap[symbolsFile->filePath_] = uti;
336 outTableInfo = unwTabMap[symbolsFile->filePath_];
337 DumpTableInfo(uti);
338 return 0;
339 } else {
340 HLOGV("FillUnwindTable failed");
341 return -1;
342 }
343 } else {
344 outTableInfo = unwTabMap[symbolsFile->filePath_];
345 return 0;
346 }
347 return -1;
348 }
349
FindUnwindTable(uintptr_t pc,UnwindTableInfo & outTableInfo,void * arg)350 int CallStack::FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg)
351 {
352 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
353 CHECK_TRUE(unwindInfoPtr == nullptr, -1, 0, "");
354 int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
355 if (mapIndex >= 0) {
356 auto map = unwindInfoPtr->thread.GetMaps()[mapIndex];
357 if (map != nullptr) {
358 SymbolsFile *symbolsFile = unwindInfoPtr->thread.FindSymbolsFileByMap(map);
359 if (symbolsFile != nullptr) {
360 return FillUnwindTable(symbolsFile, map, unwindInfoPtr, pc, outTableInfo);
361 } else {
362 HLOGD("no symbols file found for thread %d:%s", unwindInfoPtr->thread.tid_,
363 unwindInfoPtr->thread.name_.c_str());
364 }
365 } else {
366 HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
367 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
368 }
369 } else {
370 HLOGD("map index is -1");
371 }
372 return -1;
373 }
374
AccessMem2(uintptr_t addr,uintptr_t * val,void * arg)375 int CallStack::AccessMem2(uintptr_t addr, uintptr_t *val, void *arg)
376 {
377 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
378 *val = 0;
379
380 /* Check overflow. */
381 CHECK_TRUE(unwindInfoPtr == nullptr || (addr + sizeof(uintptr_t) < addr), -1, 1,
382 "unwindInfoPtr is null or address overflow at 0x%" UNW_WORD_PFLAG " increase 0x%zu",
383 addr, sizeof(uintptr_t));
384
385 if (addr < unwindInfoPtr->callStack.stackPoint_ ||
386 addr + sizeof(uintptr_t) >= unwindInfoPtr->callStack.stackEnd_) {
387 if (ReadVirtualThreadMemory(*unwindInfoPtr, addr, val)) {
388 HLOGM("access_mem addr get val 0x%" UNW_WORD_PFLAG ", from mmap", *val);
389 } else {
390 HLOGW("access_mem mmap 0x%" PRIx64 " failed, STACK RANGE 0x%" PRIx64 "- 0x%" PRIx64 "(0x%" PRIx64 ")",
391 (uint64_t)addr,
392 unwindInfoPtr->callStack.stackPoint_, unwindInfoPtr->callStack.stackEnd_,
393 unwindInfoPtr->callStack.stackEnd_ - unwindInfoPtr->callStack.stackPoint_);
394 return -1;
395 }
396 } else {
397 size_t stackOffset = addr - unwindInfoPtr->callStack.stackPoint_;
398 *val = *(uintptr_t *)&unwindInfoPtr->callStack.stack_[stackOffset];
399 HLOGM("access_mem addr %p val %" UNW_WORD_PFLAG ", from stack offset %zu",
400 reinterpret_cast<void *>(addr), *val, stackOffset);
401 }
402
403 return 0;
404 }
GetMapByPc(uintptr_t pc,std::shared_ptr<DfxMap> & map,void * arg)405 int CallStack::GetMapByPc(uintptr_t pc, std::shared_ptr<DfxMap>& map, void *arg)
406 {
407 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
408 int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
409 if (mapIndex >= 0) {
410 map = unwindInfoPtr->thread.GetMaps()[mapIndex];
411 if (map != nullptr) {
412 return 0;
413 }
414 }
415 HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
416 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
417 return -1;
418 }
419 #endif
420
CallStack()421 CallStack::CallStack()
422 {
423 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
424 accessor_ = std::make_shared<OHOS::HiviewDFX::UnwindAccessors>();
425 accessor_->FindUnwindTable = &CallStack::FindUnwindTable;
426 accessor_->AccessMem = &CallStack::AccessMem2;
427 accessor_->AccessReg = nullptr;
428 accessor_->GetMapByPc = &CallStack::GetMapByPc;
429 #endif
430 }
431
ClearCache()432 void CallStack::ClearCache()
433 {
434 cachedCallFramesMap_.clear();
435 lastPid_ = -1;
436 lastAddr_ = 0;
437 lastData_ = 0;
438 stackPoint_ = 0;
439 stackEnd_ = 0;
440 regs_ = nullptr;
441 regsNum_ = 0;
442 stack_ = nullptr;
443 stackSize_ = 0;
444
445 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
446 pidUnwinder_.clear();
447 unwindTableInfoMap_.clear();
448 #endif
449 }
450 } // namespace HiPerf
451 } // namespace Developtools
452 } // namespace OHOS
453