• 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,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