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 #if HAVE_LIBUNWIND
26 #include <libunwind.h>
27 #include <libunwind_i.h>
28 #endif
29 
30 #include "dfx_regs.h"
31 #include "hiperf_hilog.h"
32 #include "register.h"
33 
34 #ifdef target_cpu_arm
35 // reg size is int (unw_word_t)
36 #define UNW_WORD_PFLAG "x"
37 #else
38 // reg size is long (unw_word_t)
39 #define UNW_WORD_PFLAG "zx"
40 #endif
41 namespace OHOS {
42 namespace Developtools {
43 namespace HiPerf {
44 using namespace OHOS::HiviewDFX;
45 
ReadVirtualThreadMemory(UnwindInfo &unwindInfoPtr, ADDR_TYPE vaddr, ADDR_TYPE *data)46 bool CallStack::ReadVirtualThreadMemory(UnwindInfo &unwindInfoPtr, ADDR_TYPE vaddr, ADDR_TYPE *data)
47 {
48     if (__builtin_expect(unwindInfoPtr.thread.pid_ == unwindInfoPtr.callStack.lastPid_ &&
49         vaddr == unwindInfoPtr.callStack.lastAddr_, true)) {
50         *data = unwindInfoPtr.callStack.lastData_;
51         return true;
52     }
53 
54     if (unwindInfoPtr.thread.ReadRoMemory(vaddr, reinterpret_cast<uint8_t*>(data), sizeof(ADDR_TYPE))) {
55         unwindInfoPtr.callStack.lastPid_ = unwindInfoPtr.thread.pid_;
56         unwindInfoPtr.callStack.lastAddr_ = vaddr;
57         unwindInfoPtr.callStack.lastData_ = *data;
58         return true;
59     } else {
60         unwindInfoPtr.callStack.lastPid_ = -1;
61         unwindInfoPtr.callStack.lastAddr_ = 0;
62         return false;
63     }
64 }
65 
66 #if HAVE_LIBUNWIND
67 const std::map<unw_error_t, const std::string> UNW_ERROR_MAP = {
68     {UNW_ESUCCESS, std::to_string(UNW_ESUCCESS)},
69     {UNW_EUNSPEC, std::to_string(UNW_EUNSPEC)},
70     {UNW_ENOMEM, std::to_string(UNW_ENOMEM)},
71     {UNW_EBADREG, std::to_string(UNW_EBADREG)},
72     {UNW_EREADONLYREG, std::to_string(UNW_EREADONLYREG)},
73     {UNW_ESTOPUNWIND, std::to_string(UNW_ESTOPUNWIND)},
74     {UNW_EINVALIDIP, std::to_string(UNW_EINVALIDIP)},
75     {UNW_EBADFRAME, std::to_string(UNW_EBADFRAME)},
76     {UNW_EINVAL, std::to_string(UNW_EINVAL)},
77     {UNW_EBADVERSION, std::to_string(UNW_EBADVERSION)},
78     {UNW_ENOINFO, std::to_string(UNW_ENOINFO)},
79 };
GetUnwErrorName(int error)80 const std::string CallStack::GetUnwErrorName(int error)
81 {
82     if (UNW_ERROR_MAP.count(static_cast<unw_error_t>(-error)) > 0) {
83         return UNW_ERROR_MAP.at(static_cast<unw_error_t>(-error));
84     } else {
85         return "UNKNOW_UNW_ERROR";
86     }
87 }
88 
dumpUDI(unw_dyn_info_t &di)89 void CallStack::dumpUDI(unw_dyn_info_t &di)
90 {
91     HLOGV("unwind_table info: ");
92     HLOGV(" di.start_ip:            0x%016" UNW_WORD_PFLAG "", di.start_ip);
93     HLOGV(" di.end_ip:              0x%016" UNW_WORD_PFLAG "", di.end_ip);
94     HLOGV(" di.u.rti.segbase:       0x%016" UNW_WORD_PFLAG "", di.u.rti.segbase);
95     HLOGV(" di.u.rti.table_data:    0x%016" UNW_WORD_PFLAG "", di.u.rti.table_data);
96     HLOGV(" di.u.rti.table_len:     0x%016" UNW_WORD_PFLAG "", di.u.rti.table_len);
97 }
98 
fillUDI(unw_dyn_info_t &di, SymbolsFile &symbolsFile, std::shared_ptr<DfxMap> map, const VirtualThread &thread)99 bool CallStack::fillUDI(unw_dyn_info_t &di, SymbolsFile &symbolsFile, std::shared_ptr<DfxMap> map,
100                         const VirtualThread &thread)
101 {
102     di.start_ip = map->begin;
103     di.end_ip = map->end;
104 #ifndef target_cpu_arm
105     uint64_t fdeTableElfOffset;
106     uint64_t fdeTableSize;
107     uint64_t ehFrameHdrElfOffset;
108     if ((UNW_INFO_FORMAT_REMOTE_TABLE == di.format) &&
109         symbolsFile.GetHDRSectionInfo(ehFrameHdrElfOffset, fdeTableElfOffset, fdeTableSize)) {
110         /*
111             unw_word_t name_ptr;        // addr. of table name (e.g., library name)
112             unw_word_t segbase;         // segment base
113             unw_word_t table_len;       // must be a multiple of sizeof(unw_word_t)!
114             unw_word_t table_data;
115         */
116         /*
117             all the rti addr is offset of the elf file
118             begin - page offset = elf file base addr in vaddr user space
119             begin - page offset + elf offset = vaddr in real word.(for this thread)
120         */
121 
122         // segbase is file offset .
123         /*
124             00200000-00344000 r--p 00000000 08:02 46404365
125             00344000-005c4000 r-xp 00143000 08:02 46404365
126 
127             LOAD           0x00000000001439c0 0x00000000003449c0 0x00000000003449c0
128                             0x000000000027f3c0 0x000000000027f3c0  R E    0x1000
129 
130             GNU_EH_FRAME   0x00000000000f3248 0x00000000002f3248 0x00000000002f3248
131                             0x000000000000bb04 0x000000000000bb04  R      0x4
132 
133         */
134         auto ehFrameMap = thread.FindMapByFileInfo(map->name, ehFrameHdrElfOffset);
135         if (ehFrameMap == nullptr) {
136             HLOGE("no ehframe map found.");
137             return false;
138         }
139 
140         di.u.rti.segbase = ehFrameMap->begin + ehFrameHdrElfOffset - ehFrameMap->offset;
141         di.u.rti.table_data = ehFrameMap->begin + fdeTableElfOffset - ehFrameMap->offset;
142         di.u.rti.table_len = fdeTableSize / sizeof(uintptr_t);
143 
144         HLOGV(" map pageoffset:         0x%016" PRIx64 "", map->offset);
145         HLOGV(" ehFrameHdrElfOffset:    0x%016" PRIx64 "", ehFrameHdrElfOffset);
146         HLOGV(" fdeTableElfOffset:      0x%016" PRIx64 "", fdeTableElfOffset);
147         HLOGV(" fdeTableSize:           0x%016" PRIx64 "", fdeTableSize);
148         return true;
149     } else {
150         HLOGD("SymbolsFile::GetHDRSectionInfo() failed");
151     }
152 #else
153     uint64_t SectionVaddr;
154     uint64_t SectionSize;
155     uint64_t SectionFileOffset;
156     if ((UNW_INFO_FORMAT_ARM_EXIDX == di.format) &&
157         symbolsFile.GetSectionInfo(ARM_EXIDX, SectionVaddr, SectionSize, SectionFileOffset)) {
158         auto targetMap = thread.FindMapByFileInfo(map->name, SectionFileOffset);
159         if (targetMap == nullptr) {
160             HLOGE("no debug map found.");
161             return false;
162         }
163         HLOGV(" begin: %" PRIx64 " offset:%" PRIx64 "", targetMap->begin,
164               targetMap->offset);
165 
166         di.u.rti.table_data = targetMap->begin + SectionFileOffset - targetMap->offset;
167         di.u.rti.table_len = SectionSize;
168         HLOGV(" SectionName:           %s", std::string(ARM_EXIDX).c_str());
169         HLOGV(" SectionVaddrt:         0x%016" PRIx64 "", SectionVaddr);
170         HLOGV(" SectionFileOffset      0x%016" PRIx64 "", SectionFileOffset);
171         HLOGV(" SectionSize:           0x%016" PRIx64 "", SectionSize);
172 
173         // GetSectionInfo return true, but SectionVaddr || SectionSize is 0 ???
174         HLOG_ASSERT(SectionVaddr != 0 && SectionSize != 0);
175         return true;
176     } else {
177         HLOGD("SymbolsFile::GetSectionInfo() failed");
178     }
179 #endif
180     return false;
181 }
182 
183 /*
184     https://www.nongnu.org/libunwind/man/libunwind-dynamic(3).html
185 */
FindUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr, unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi, int need_unwind_info, void *arg)186 int CallStack::FindUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map,
187                                UnwindInfo *unwindInfoPtr, unw_addr_space_t as, unw_word_t ip,
188                                unw_proc_info_t *pi, int need_unwind_info, void *arg)
189 {
190     HLOGM("try search debug info at %s", symbolsFile->filePath_.c_str());
191     auto &dynInfoProcessMap = unwindInfoPtr->callStack.unwindTableInfoMap_;
192     // all the thread in same process have same map and symbols
193     if (dynInfoProcessMap.find(unwindInfoPtr->thread.pid_) == dynInfoProcessMap.end()) {
194         dynInfoProcessMap.emplace(unwindInfoPtr->thread.pid_, dsoUnwDynInfoMap {});
195     }
196     dsoUnwDynInfoMap &dynFileMap = dynInfoProcessMap[unwindInfoPtr->thread.pid_];
197     // find use dso name as key
198     if (dynFileMap.find(symbolsFile->filePath_) == dynFileMap.end()) {
199         unw_dyn_info_t newdi;
200         if (memset_s(&newdi, sizeof(unw_dyn_info_t), 0, sizeof(unw_dyn_info_t)) != EOK) {
201             HLOGE("memset_s() failed");
202             return -UNW_EUNSPEC;
203         }
204 #ifdef target_cpu_arm
205         // arm use .ARM.exidx , not use ehframe
206         newdi.format = UNW_INFO_FORMAT_ARM_EXIDX;
207 #else
208         // otherwise we use EH FRAME
209         newdi.format = UNW_INFO_FORMAT_REMOTE_TABLE;
210 #endif
211         if (fillUDI(newdi, *symbolsFile, map, unwindInfoPtr->thread)) {
212             dumpUDI(newdi);
213             // we make a option empty value first
214             std::optional<unw_dyn_info_t> &odi = dynFileMap[symbolsFile->filePath_];
215             odi = newdi;
216         } else {
217             HLOGV("fillUDI failed()");
218             return -UNW_EUNSPEC;
219         }
220     }
221 
222     HLOG_ASSERT(dynInfoProcessMap.find(unwindInfoPtr->thread.pid_) != dynInfoProcessMap.end());
223     HLOG_ASSERT_MESSAGE(dynFileMap.find(symbolsFile->filePath_) != dynFileMap.end(), "%s",
224                         symbolsFile->filePath_.c_str());
225     std::optional<unw_dyn_info_t> &odi =
226         dynInfoProcessMap.at(unwindInfoPtr->thread.pid_).at(symbolsFile->filePath_);
227 
228     if (odi.has_value()) {
229         unw_dyn_info_t &di = odi.value();
230         /*
231             we don't use dwarf_search_unwind_table
232             because in arm it will search two function:
233             1 arm_search_unwind_table first
234             2 dwarf_search_unwind_table
235 
236             see libunwind_i.h for arm
237             define tdep_search_unwind_table UNW_OBJ(search_unwind_table)
238 
239         */
240         int ret = static_cast<unw_error_t>(
241             tdep_search_unwind_table(as, ip, &di, pi, need_unwind_info, arg));
242 
243         HLOGM("search_unwind_table ret %d:%s", ret, GetUnwErrorName(ret).c_str());
244 
245         if (UNW_ESUCCESS != ret) {
246             if (UNW_ENOINFO != ret) {
247                 HLOGW("search_unwind_table ret error %d:%s", ret, GetUnwErrorName(ret).c_str());
248             }
249             return -UNW_EUNSPEC;
250         } else {
251             return UNW_ESUCCESS;
252         }
253     } else {
254         HLOGW("no debug info found for thread %d:%s", unwindInfoPtr->thread.tid_,
255               unwindInfoPtr->thread.name_.c_str());
256         return -UNW_EUNSPEC;
257     }
258 }
259 
FindProcInfo(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi, int need_unwind_info, void *arg)260 int CallStack::FindProcInfo(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
261                             int need_unwind_info, void *arg)
262 {
263     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
264 
265     HLOGM("need_unwind_info ret %d ip %" UNW_WORD_PFLAG "", need_unwind_info, ip);
266     auto map = unwindInfoPtr->thread.FindMapByAddr(ip);
267     if (map != nullptr) {
268         SymbolsFile *symbolsFile = unwindInfoPtr->thread.FindSymbolsFileByMap(map);
269         if (symbolsFile != nullptr) {
270             return FindUnwindTable(symbolsFile, map, unwindInfoPtr, as, ip, pi, need_unwind_info, arg);
271         } else {
272             HLOGW("no symbols file found for thread %d:%s", unwindInfoPtr->thread.tid_,
273                   unwindInfoPtr->thread.name_.c_str());
274         }
275     } else {
276         HLOGE("ip 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", ip,
277               unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
278     }
279 
280     return -UNW_EUNSPEC;
281 }
282 
AccessMem([[maybe_unused]] unw_addr_space_t as, unw_word_t addr, unw_word_t *valuePoint, int writeOperation, void *arg)283 int CallStack::AccessMem([[maybe_unused]] unw_addr_space_t as, unw_word_t addr,
284                          unw_word_t *valuePoint, int writeOperation, void *arg)
285 {
286     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
287     *valuePoint = 0;
288 
289     /* Check overflow. */
290     if (addr + sizeof(unw_word_t) < addr) {
291         HLOGE("address overfolw at 0x%" UNW_WORD_PFLAG " increase 0x%zu", addr, sizeof(unw_word_t));
292         return -UNW_EUNSPEC;
293     }
294 
295     if (addr < unwindInfoPtr->callStack.stackPoint_ or
296         addr + sizeof(unw_word_t) >= unwindInfoPtr->callStack.stackEnd_) {
297         if (ReadVirtualThreadMemory(*unwindInfoPtr, addr, valuePoint)) {
298             HLOGM("access_mem addr get val 0x%" UNW_WORD_PFLAG ", from mmap", *valuePoint);
299         } else {
300             HLOGW("access_mem addr failed, from mmap, STACK RANGE 0x%" PRIx64 "- 0x%" PRIx64 "(0x%" PRIx64 ")",
301                   unwindInfoPtr->callStack.stackPoint_, unwindInfoPtr->callStack.stackEnd_,
302                   unwindInfoPtr->callStack.stackEnd_ - unwindInfoPtr->callStack.stackPoint_);
303             return -UNW_EUNSPEC;
304         }
305     } else {
306         size_t stackOffset = addr - unwindInfoPtr->callStack.stackPoint_;
307         *valuePoint = *(unw_word_t *)&unwindInfoPtr->callStack.stack_[stackOffset];
308         HLOGM("access_mem addr %p val %" UNW_WORD_PFLAG ", from stack offset %zu",
309               reinterpret_cast<void *>(addr), *valuePoint, stackOffset);
310     }
311 
312     return UNW_ESUCCESS;
313 }
314 
AccessReg([[maybe_unused]] unw_addr_space_t as, unw_regnum_t regnum, unw_word_t *valuePoint, int writeOperation, void *arg)315 int CallStack::AccessReg([[maybe_unused]] unw_addr_space_t as, unw_regnum_t regnum,
316                          unw_word_t *valuePoint, int writeOperation, void *arg)
317 {
318     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
319     uint64_t val;
320     int perfRegIndex = LibunwindRegIdToPerfReg(regnum);
321     if (perfRegIndex < 0) {
322         HLOGE("can't read reg %d", perfRegIndex);
323         return perfRegIndex;
324     }
325     /* Don't support write, I suspect we don't need it. */
326     if (writeOperation) {
327         HLOGE("access_reg %d", regnum);
328         return -UNW_EINVAL;
329     }
330 
331     if (unwindInfoPtr->callStack.regsNum_ == 0) {
332         return -UNW_EUNSPEC;
333     }
334 
335     if (!RegisterGetValue(val, unwindInfoPtr->callStack.regs_, static_cast<size_t>(perfRegIndex),
336                           unwindInfoPtr->callStack.regsNum_)) {
337         HLOGE("can't read reg %d", perfRegIndex);
338         return -UNW_EUNSPEC;
339     }
340 
341     *valuePoint = (unw_word_t)val;
342     HLOGM("reg %d:%s, val 0x%" UNW_WORD_PFLAG "", regnum, RegisterGetName(static_cast<size_t>(perfRegIndex)).c_str(),
343           *valuePoint);
344     return UNW_ESUCCESS;
345 }
346 
PutUnwindInfo([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_proc_info_t *pi, [[maybe_unused]] void *arg)347 void CallStack::PutUnwindInfo([[maybe_unused]] unw_addr_space_t as,
348                               [[maybe_unused]] unw_proc_info_t *pi, [[maybe_unused]] void *arg)
349 {
350 }
351 
AccessFpreg([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_regnum_t num, [[maybe_unused]] unw_fpreg_t *val, [[maybe_unused]] int writeOperation, [[maybe_unused]] void *arg)352 int CallStack::AccessFpreg([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_regnum_t num,
353                            [[maybe_unused]] unw_fpreg_t *val, [[maybe_unused]] int writeOperation,
354                            [[maybe_unused]] void *arg)
355 {
356     return -UNW_EINVAL;
357 }
358 
GetDynInfoListAaddr([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_word_t *dil_vaddr, [[maybe_unused]] void *arg)359 int CallStack::GetDynInfoListAaddr([[maybe_unused]] unw_addr_space_t as,
360                                    [[maybe_unused]] unw_word_t *dil_vaddr,
361                                    [[maybe_unused]] void *arg)
362 {
363     return -UNW_ENOINFO;
364 }
365 
Resume([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_cursor_t *cu, [[maybe_unused]] void *arg)366 int CallStack::Resume([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_cursor_t *cu,
367                       [[maybe_unused]] void *arg)
368 {
369     return -UNW_EINVAL;
370 }
371 
getProcName([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_word_t addr, [[maybe_unused]] char *bufp, [[maybe_unused]] size_t buf_len, [[maybe_unused]] unw_word_t *offp, [[maybe_unused]] void *arg)372 int CallStack::getProcName([[maybe_unused]] unw_addr_space_t as, [[maybe_unused]] unw_word_t addr,
373                            [[maybe_unused]] char *bufp, [[maybe_unused]] size_t buf_len,
374                            [[maybe_unused]] unw_word_t *offp, [[maybe_unused]] void *arg)
375 {
376     return -UNW_EINVAL;
377 }
378 
UnwindStep(unw_cursor_t &c, std::vector<DfxFrame> &callStack, size_t maxStackLevel)379 void CallStack::UnwindStep(unw_cursor_t &c, std::vector<DfxFrame> &callStack, size_t maxStackLevel)
380 {
381     while (callStack.size() < maxStackLevel) {
382         int ret = unw_step(&c);
383         if (ret > 0) {
384             unw_word_t ip;
385             unw_word_t sp;
386             unw_get_reg(&c, UNW_REG_IP, &ip);
387             unw_get_reg(&c, UNW_REG_SP, &sp);
388 
389             if (ip == 0) {
390                 HLOGD("ip == 0 something is wrong. break");
391                 break;
392             }
393 
394             /*
395              * Decrement the IP for any non-activation frames.
396              * this is required to properly find the srcline
397              * for caller frames.
398              * See also the documentation for dwfl_frame_pc(),
399              * which this code tries to replicate.
400              */
401             if (unw_is_signal_frame(&c) <= 0) {
402                 --ip;
403             }
404             HLOGV("unwind:%zu: ip 0x%" UNW_WORD_PFLAG " sp 0x%" UNW_WORD_PFLAG "", callStack.size(),
405                   ip, sp);
406             if (callStack.back().pc == ip && callStack.back().sp == sp) {
407                 HLOGW("we found a same frame, stop here");
408                 break;
409             }
410             callStack.emplace_back(ip, sp);
411         } else {
412             HLOGV("no more frame step found. ret %d:%s", ret, GetUnwErrorName(ret).c_str());
413             break;
414         }
415     }
416 }
417 #endif
418 
GetIpSP(uint64_t &ip, uint64_t &sp, const u64 *regs, size_t regNum) const419 bool CallStack::GetIpSP(uint64_t &ip, uint64_t &sp, const u64 *regs, size_t regNum) const
420 {
421     if (regNum > 0) {
422         CHECK_TRUE(!RegisterGetSPValue(sp, arch_, regs, regNum), false, 1, "unable get sp");
423         CHECK_TRUE(!RegisterGetIPValue(ip, arch_, regs, regNum), false, 1, "unable get ip");
424         if (ip != 0) {
425             return true;
426         }
427     } else {
428         HLOGW("reg size is 0");
429         return false;
430     }
431     return false;
432 }
433 
434 #if HAVE_LIBUNWIND
DoUnwind(const VirtualThread &thread, std::vector<DfxFrame> &callStack, size_t maxStackLevel)435 bool CallStack::DoUnwind(const VirtualThread &thread, std::vector<DfxFrame> &callStack,
436                          size_t maxStackLevel)
437 {
438     unw_addr_space_t addr_space;
439     UnwindInfo unwindInfo = {
440         .thread = thread,
441         .callStack = *this,
442     };
443     unw_cursor_t c;
444     if (unwindAddrSpaceMap_.count(thread.tid_) == 0) {
445         addr_space = unw_create_addr_space(&accessors_, 0);
446         if (!addr_space) {
447             HLOGE("Can't create unwind vaddress space.");
448             return false;
449         }
450         unwindAddrSpaceMap_.emplace(thread.tid_, addr_space);
451         unw_set_caching_policy(addr_space, UNW_CACHE_GLOBAL);
452         unw_flush_cache(addr_space, 0, 0);
453     } else {
454         addr_space = unwindAddrSpaceMap_.at(thread.tid_);
455     }
456 
457     int ret = unw_init_remote(&c, addr_space, &unwindInfo);
458     if (ret) {
459         HLOGE("unwind error %d:%s see unw_error_t.", ret, GetUnwErrorName(ret).c_str());
460         return false;
461     } else {
462         UnwindStep(c, callStack, maxStackLevel);
463     }
464     return true;
465 }
466 #endif
467 
UnwindCallStack(const VirtualThread &thread, bool abi32, u64 *regs, u64 regsNum, const u8 *stack, u64 stackSize, std::vector<DfxFrame> &callStack, size_t maxStackLevel)468 bool CallStack::UnwindCallStack(const VirtualThread &thread, bool abi32, u64 *regs, u64 regsNum,
469                                 const u8 *stack, u64 stackSize, std::vector<DfxFrame> &callStack,
470                                 size_t maxStackLevel)
471 {
472     regs_ = regs;
473     regsNum_ = regsNum;
474     stack_ = stack;
475     stackSize_ = stackSize;
476 
477     arch_ = GetArchTypeFromABI(abi32);
478     UpdateRegForABI(arch_, regs_);
479     if (!RegisterGetSPValue(stackPoint_, arch_, regs_, regsNum_)) {
480         HLOGE("RegisterGetSPValue failed");
481         return false;
482     } else {
483         stackEnd_ = stackPoint_ + stackSize_;
484     }
485 
486     uint64_t ip;
487     uint64_t sp;
488     if (!GetIpSP(ip, sp, regs_, regsNum_)) {
489         HLOGW("unable get sp or sp , unable unwind");
490         return false;
491     } else {
492         if (ip != 0) {
493             HLOGV("unwind:%zu: ip 0x%" PRIx64 " sp 0x%" PRIx64 "", callStack.size(), ip, sp);
494             callStack.emplace_back(ip, sp);
495         }
496     }
497 
498     /*
499      * If we need more than one entry, do the DWARF
500      * unwind itself.
501      */
502     if (maxStackLevel - 1 > 0) {
503 #if HAVE_LIBUNWIND
504         return DoUnwind(thread, callStack, maxStackLevel);
505 #else
506         return DoUnwind2(thread, callStack, maxStackLevel);
507 #endif
508     }
509     return true;
510 }
511 
LogFrame(const std::string msg, const std::vector<DfxFrame> &frames)512 void CallStack::LogFrame(const std::string msg, const std::vector<DfxFrame> &frames)
513 {
514     HLOGM("%s", msg.c_str());
515     int level = 0;
516     for (auto& frame : frames) {
517         HLOGM("%d:%s", level++, frame.ToString().c_str());
518     }
519 }
520 
521 /*
522 we should have CallStack cache for each thread
523 end                    begin
524 0. A -> B -> C -> E -> F
525 1.           C -> E -> F
526 2.      B -> C
527 3. A -> B -> C
528 4.      B -> G -> H
529 5.      J -> C
530 
531 0 is our cache
532 1 2 3... is from record
533 
534 use expandLimit to setup how may frame match is needs
535 
536 */
DoExpandCallStack(std::vector<DfxFrame> &newCallFrames, const std::vector<DfxFrame> &cachedCallFrames, size_t expandLimit)537 size_t CallStack::DoExpandCallStack(std::vector<DfxFrame> &newCallFrames,
538                                     const std::vector<DfxFrame> &cachedCallFrames,
539                                     size_t expandLimit)
540 {
541     int maxCycle = 0;
542 
543     if (expandLimit == 0 or newCallFrames.size() < expandLimit or
544         cachedCallFrames.size() < expandLimit or
545         cachedCallFrames.size() >= MAX_CALL_FRAME_UNWIND_SIZE) {
546         HLOGM("expandLimit %zu not match new %zu cache %zu", expandLimit, newCallFrames.size(),
547               cachedCallFrames.size());
548         return 0; // size not enough
549     }
550 
551     // called (Stack Bottom) , this will NOT change when compare
552     // in case1 newIt -> C
553     // in case2 newIt -> B
554     const auto newIt = newCallFrames.end() - expandLimit;
555     if (newIt != newCallFrames.end()) {
556         HLOGM("try find new call chain bottom %s for limit %zu", newIt->ToString().c_str(),
557             expandLimit);
558     }
559 
560     // first frame search, from called - > caller
561     // for case 2 it should found B
562     size_t distances = expandLimit - 1;
563     auto cachedIt = find(cachedCallFrames.begin(), cachedCallFrames.end(), *newIt);
564     if (cachedIt == cachedCallFrames.end()) {
565         HLOGM("not found in first search");
566     }
567 
568     // cache frame found
569     while (std::distance(cachedIt, cachedCallFrames.end()) >= signed(expandLimit)) {
570         HLOG_ASSERT_MESSAGE(maxCycle++ < MAX_CALL_FRAME_EXPAND_CYCLE, "MAX_UNWIND_CYCLE = %d reach",
571                             MAX_CALL_FRAME_EXPAND_CYCLE);
572 
573         if (std::equal(newIt, newIt + expandLimit, cachedIt)) {
574             HLOGM("match %s + %zu", newIt->ToString().c_str(), expandLimit);
575             cachedIt += expandLimit; // in while we check the boundary safe
576             if (cachedIt == cachedCallFrames.end()) {
577                 // same but no more need expand
578                 break;
579             }
580 
581             // expand the frame and make some log ?
582             LogFrame("newCallStack:", newCallFrames);
583             LogFrame("cachedCallStack:", cachedCallFrames);
584 
585             newCallFrames.insert(newCallFrames.end(), cachedIt, cachedCallFrames.end());
586             auto expands = std::distance(cachedIt, cachedCallFrames.end());
587             HLOGV("merge callstack increse to %zu (+%zd) ", newCallFrames.size(), expands);
588             // we done the deal
589             return expands;
590         } else {
591             // quick search next same farme again
592             cachedIt++;
593             if (cachedIt != cachedCallFrames.end()) {
594                 HLOGM("search next");
595                 cachedIt = find(cachedIt, cachedCallFrames.end(), *newIt);
596             }
597         }
598     }
599     HLOGM("cachedIt distance %zd , need %zd", std::distance(cachedCallFrames.begin(), cachedIt),
600           distances);
601     return 0u; // nothing expand
602 }
603 
ExpandCallStack(pid_t tid, std::vector<DfxFrame> &callFrames, size_t expandLimit)604 size_t CallStack::ExpandCallStack(pid_t tid, std::vector<DfxFrame> &callFrames, size_t expandLimit)
605 {
606     size_t expand = 0u;
607     if (expandLimit == 0) {
608         return expand; // nothing need to do
609     } else if (callFrames.size() < expandLimit) {
610         HLOGM("new callstack is too small, skip it");
611         return expand;
612     }
613     if (!cachedCallFramesMap_.count(tid)) {
614         cachedCallFramesMap_[tid].reserve(MAX_CALL_FRAME_EXPAND_CACHE_SIZE);
615     }
616     if (callFrames.size() >= 1u) {
617         // get top  (Earliest caller)
618         HashList<uint64_t, std::vector<DfxFrame>> &cachedCallFrames = cachedCallFramesMap_[tid];
619         HLOGV("find call stack frames in cache size %zu", cachedCallFrames.size());
620         // compare
621         using namespace std::rel_ops; // enable complement comparing operators
622         for (auto itr = cachedCallFrames.begin(); itr < cachedCallFrames.end(); ++itr) {
623             // each cached callstack
624             /*
625                 stack 2    1    0
626                 cache A -> B -> C
627                 new        B -> C
628                 check:
629                 1 if new B == cache C
630                 2 if new B == cache B
631                 3 if new C == new C (if limit > 0)
632                 4 insert A after B in new stack
633             */
634             const std::vector<DfxFrame> &cachedCallStack = *itr;
635             if (cachedCallStack.size() < expandLimit) {
636                 HLOGM("cache callstack is too small, skip it");
637                 continue; // check next
638             }
639             expand = DoExpandCallStack(callFrames, cachedCallStack, expandLimit);
640             if (expand > 0) {
641                 break;
642             }
643         }
644         // add new one in to cache cachedCallFrames.
645         // further optimization can be done by caching pointer which avoids copying
646         // vector
647         cachedCallFrames[callFrames[0].pc] = callFrames;
648     }
649     HLOGM("expand %zu", expand);
650     return expand;
651 }
652 
653 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
DoUnwind2(const VirtualThread &thread, std::vector<DfxFrame> &callStack, size_t maxStackLevel)654 bool CallStack::DoUnwind2(const VirtualThread &thread, std::vector<DfxFrame> &callStack,
655                           size_t maxStackLevel)
656 {
657 #ifdef target_cpu_x86_64
658     return false;
659 #else
660     UnwindInfo unwindInfo = {
661         .thread = thread,
662         .callStack = *this,
663     };
664 
665     if (pidUnwinder_.count(thread.pid_) == 0) {
666         pidUnwinder_.emplace(thread.pid_, std::make_shared<Unwinder>(accessor_));
667     }
668     auto unwinder = pidUnwinder_[thread.pid_];
669 
670 #ifdef target_cpu_arm
671     static std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm>();
672     std::vector<uintptr_t> tempRegs;
673     for (auto i = 0; i < regsNum_; ++i) {
674         tempRegs.push_back(static_cast<uintptr_t>(regs_[i]));
675     }
676     regs->SetRegsData(tempRegs);
677 #else
678     static std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm64>();
679     regs->SetRegsData(reinterpret_cast<uintptr_t*>(regs_), regsNum_);
680 #endif
681     CHECK_TRUE(unwinder == nullptr, false, 0, "");
682     unwinder->SetRegs(regs);
683     unwinder->Unwind(&unwindInfo);
684     callStack = unwinder->GetFrames();
685     HLOGD("callStack size:%zu", callStack.size());
686     for (auto frame: callStack) {
687         HLOGD("pc 0x%" PRIx64 " sp 0x%" PRIx64 "", frame.pc, frame.sp);
688     }
689     auto lastIt = callStack.end() - 1;
690     auto preIt = lastIt - 1;
691     if (lastIt != callStack.end() && preIt != callStack.end() &&
692         callStack.size() > 1 && lastIt->pc == preIt->pc && lastIt->sp == preIt->sp) {
693         callStack.erase(lastIt);
694         HLOGD("remove last callframe");
695     }
696     return true;
697 #endif
698 }
699 
DumpTableInfo(UnwindTableInfo &outTableInfo)700 void CallStack::DumpTableInfo(UnwindTableInfo &outTableInfo)
701 {
702     HLOGV("unwind_table info: ");
703     HLOGV(" start_ip:            0x%016" UNW_WORD_PFLAG "", outTableInfo.startPc);
704     HLOGV(" end_ip:              0x%016" UNW_WORD_PFLAG "", outTableInfo.endPc);
705     HLOGV(" segbase:             0x%016" UNW_WORD_PFLAG "", outTableInfo.segbase);
706     HLOGV(" table_data:          0x%016" UNW_WORD_PFLAG "", outTableInfo.tableData);
707     HLOGV(" table_len:           0x%016" UNW_WORD_PFLAG "", outTableInfo.tableLen);
708 }
709 
FillUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr, uintptr_t pc, UnwindTableInfo& outTableInfo)710 int CallStack::FillUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr,
711                                uintptr_t pc, UnwindTableInfo& outTableInfo)
712 {
713     HLOGM("try search debug info at %s", symbolsFile->filePath_.c_str());
714     CHECK_TRUE(unwindInfoPtr == nullptr, -1, 0, "");
715     auto &tableInfoMap = unwindInfoPtr->callStack.unwindTableInfoMap_;
716     // all the thread in same process have same mmap and symbols
717     if (tableInfoMap.find(unwindInfoPtr->thread.pid_) == tableInfoMap.end()) {
718         tableInfoMap.emplace(unwindInfoPtr->thread.pid_, DsoUnwindTableInfoMap {});
719     }
720     DsoUnwindTableInfoMap &unwTabMap = tableInfoMap[unwindInfoPtr->thread.pid_];
721     // find use dso name as key
722     if (unwTabMap.find(symbolsFile->filePath_) == unwTabMap.end()) {
723         UnwindTableInfo uti;
724         auto elf = symbolsFile->GetElfFile();
725         if (elf == nullptr) {
726             return -1;
727         }
728         if (elf->FindUnwindTableInfo(pc, map, uti) == 0) {
729             CHECK_TRUE(uti.format == -1, -1, 1, "parse unwind table failed.");
730             unwTabMap[symbolsFile->filePath_] = uti;
731             outTableInfo = unwTabMap[symbolsFile->filePath_];
732             DumpTableInfo(uti);
733             return 0;
734         } else {
735             HLOGV("FillUnwindTable failed");
736             return -1;
737         }
738     } else {
739         outTableInfo = unwTabMap[symbolsFile->filePath_];
740         return 0;
741     }
742     return -1;
743 }
744 
FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg)745 int CallStack::FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg)
746 {
747     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
748     CHECK_TRUE(unwindInfoPtr == nullptr, -1, 0, "");
749     int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
750     if (mapIndex >= 0) {
751         auto map = unwindInfoPtr->thread.GetMaps()[mapIndex];
752         if (map != nullptr) {
753             SymbolsFile *symbolsFile = unwindInfoPtr->thread.FindSymbolsFileByMap(map);
754             if (symbolsFile != nullptr) {
755                 return FillUnwindTable(symbolsFile, map, unwindInfoPtr, pc, outTableInfo);
756             } else {
757                 HLOGD("no symbols file found for thread %d:%s", unwindInfoPtr->thread.tid_,
758                     unwindInfoPtr->thread.name_.c_str());
759             }
760         } else {
761             HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
762                 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
763         }
764     } else {
765         HLOGD("map index is -1");
766     }
767     return -1;
768 }
769 
AccessMem2(uintptr_t addr, uintptr_t *val, void *arg)770 int CallStack::AccessMem2(uintptr_t addr, uintptr_t *val, void *arg)
771 {
772     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
773     *val = 0;
774 
775     /* Check overflow. */
776     CHECK_TRUE(unwindInfoPtr == nullptr || (addr + sizeof(uintptr_t) < addr), -1, 1,
777                "unwindInfoPtr is null or address overflow at 0x%" UNW_WORD_PFLAG " increase 0x%zu",
778                addr, sizeof(uintptr_t));
779 
780     if (addr < unwindInfoPtr->callStack.stackPoint_ or
781         addr + sizeof(uintptr_t) >= unwindInfoPtr->callStack.stackEnd_) {
782         if (ReadVirtualThreadMemory(*unwindInfoPtr, addr, val)) {
783             HLOGM("access_mem addr get val 0x%" UNW_WORD_PFLAG ", from mmap", *val);
784         } else {
785             HLOGW("access_mem mmap 0x%" PRIx64 " failed, STACK RANGE 0x%" PRIx64 "- 0x%" PRIx64 "(0x%" PRIx64 ")",
786                   (uint64_t)addr,
787                   unwindInfoPtr->callStack.stackPoint_, unwindInfoPtr->callStack.stackEnd_,
788                   unwindInfoPtr->callStack.stackEnd_ - unwindInfoPtr->callStack.stackPoint_);
789             return -1;
790         }
791     } else {
792         size_t stackOffset = addr - unwindInfoPtr->callStack.stackPoint_;
793         *val = *(uintptr_t *)&unwindInfoPtr->callStack.stack_[stackOffset];
794         HLOGM("access_mem addr %p val %" UNW_WORD_PFLAG ", from stack offset %zu",
795               reinterpret_cast<void *>(addr), *val, stackOffset);
796     }
797 
798     return 0;
799 }
GetMapByPc(uintptr_t pc, std::shared_ptr<DfxMap>& map, void *arg)800 int CallStack::GetMapByPc(uintptr_t pc, std::shared_ptr<DfxMap>& map, void *arg)
801 {
802     UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
803     int64_t mapIndex = unwindInfoPtr->thread.FindMapIndexByAddr(pc);
804     if (mapIndex >= 0) {
805         map = unwindInfoPtr->thread.GetMaps()[mapIndex];
806         if (map != nullptr) {
807             return 0;
808         }
809     }
810     HLOGD("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
811           unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
812     return -1;
813 }
814 #endif
815 
CallStack()816 CallStack::CallStack()
817 {
818 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
819     accessor_ = std::make_shared<OHOS::HiviewDFX::UnwindAccessors>();
820     accessor_->FindUnwindTable = &CallStack::FindUnwindTable;
821     accessor_->AccessMem = &CallStack::AccessMem2;
822     accessor_->AccessReg = nullptr;
823     accessor_->GetMapByPc = &CallStack::GetMapByPc;
824 #endif
825 }
826 
~CallStack()827 CallStack::~CallStack()
828 {
829 #if HAVE_LIBUNWIND
830     for (auto &pair : unwindAddrSpaceMap_) {
831         unw_destroy_addr_space(pair.second);
832     }
833 #endif
834 }
835 } // namespace HiPerf
836 } // namespace Developtools
837 } // namespace OHOS
838