xref: /developtools/hiperf/src/callstack.cpp (revision 48f512ce)
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
41namespace OHOS {
42namespace Developtools {
43namespace HiPerf {
44using namespace OHOS::HiviewDFX;
45
46bool 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
67const 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};
80const 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
89void 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
99bool 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*/
186int 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
260int 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
283int 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
315int 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
347void CallStack::PutUnwindInfo([[maybe_unused]] unw_addr_space_t as,
348                              [[maybe_unused]] unw_proc_info_t *pi, [[maybe_unused]] void *arg)
349{
350}
351
352int 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
359int 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
366int 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
372int 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
379void 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
419bool 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
435bool 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
468bool 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
512void 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/*
522we should have CallStack cache for each thread
523end                    begin
5240. A -> B -> C -> E -> F
5251.           C -> E -> F
5262.      B -> C
5273. A -> B -> C
5284.      B -> G -> H
5295.      J -> C
530
5310 is our cache
5321 2 3... is from record
533
534use expandLimit to setup how may frame match is needs
535
536*/
537size_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
604size_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
654bool 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
700void 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
710int 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
745int 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
770int 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}
800int 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
816CallStack::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
827CallStack::~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