• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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