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 extern "C" {
28 #include <libunwind_i.h>
29 }
30 #endif
31
32 #include "dfx_regs.h"
33 #include "register.h"
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(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(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(unw_addr_space_t as,unw_proc_info_t * pi,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(unw_addr_space_t as,unw_regnum_t num,unw_fpreg_t * val,int writeOperation,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(unw_addr_space_t as,unw_word_t * dil_vaddr,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(unw_addr_space_t as,unw_cursor_t * cu,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(unw_addr_space_t as,unw_word_t addr,char * bufp,size_t buf_len,unw_word_t * offp,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<CallFrame> & callStack,size_t maxStackLevel)379 void CallStack::UnwindStep(unw_cursor_t &c, std::vector<CallFrame> &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().ip_ == 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 if (!RegisterGetSPValue(sp, arch_, regs, regNum)) {
423 HLOGW("unable get sp");
424 return false;
425 }
426 if (!RegisterGetIPValue(ip, arch_, regs, regNum)) {
427 HLOGW("unable get ip");
428 return false;
429 }
430 if (ip != 0) {
431 return true;
432 }
433 } else {
434 HLOGW("reg size is 0");
435 return false;
436 }
437 return false;
438 }
439
440 #if HAVE_LIBUNWIND
DoUnwind(const VirtualThread & thread,std::vector<CallFrame> & callStack,size_t maxStackLevel)441 bool CallStack::DoUnwind(const VirtualThread &thread, std::vector<CallFrame> &callStack,
442 size_t maxStackLevel)
443 {
444 unw_addr_space_t addr_space;
445 UnwindInfo unwindInfo = {
446 .thread = thread,
447 .callStack = *this,
448 };
449 unw_cursor_t c;
450 if (unwindAddrSpaceMap_.count(thread.tid_) == 0) {
451 addr_space = unw_create_addr_space(&accessors_, 0);
452 if (!addr_space) {
453 HLOGE("Can't create unwind vaddress space.");
454 return false;
455 }
456 unwindAddrSpaceMap_.emplace(thread.tid_, addr_space);
457 unw_set_caching_policy(addr_space, UNW_CACHE_GLOBAL);
458 unw_flush_cache(addr_space, 0, 0);
459 } else {
460 addr_space = unwindAddrSpaceMap_.at(thread.tid_);
461 }
462
463 int ret = unw_init_remote(&c, addr_space, &unwindInfo);
464 if (ret) {
465 HLOGE("unwind error %d:%s see unw_error_t.", ret, GetUnwErrorName(ret).c_str());
466 return false;
467 } else {
468 UnwindStep(c, callStack, maxStackLevel);
469 }
470 return true;
471 }
472 #endif
473
UnwindCallStack(const VirtualThread & thread,bool abi32,u64 * regs,u64 regsNum,const u8 * stack,u64 stackSize,std::vector<CallFrame> & callStack,size_t maxStackLevel)474 bool CallStack::UnwindCallStack(const VirtualThread &thread, bool abi32, u64 *regs, u64 regsNum,
475 const u8 *stack, u64 stackSize, std::vector<CallFrame> &callStack,
476 size_t maxStackLevel)
477 {
478 regs_ = regs;
479 regsNum_ = regsNum;
480 stack_ = stack;
481 stackSize_ = stackSize;
482
483 arch_ = GetArchTypeFromABI(abi32);
484 UpdateRegForABI(arch_, regs_);
485 if (!RegisterGetSPValue(stackPoint_, arch_, regs_, regsNum_)) {
486 HLOGE("RegisterGetSPValue failed");
487 return false;
488 } else {
489 stackEnd_ = stackPoint_ + stackSize_;
490 }
491
492 uint64_t ip;
493 uint64_t sp;
494 if (!GetIpSP(ip, sp, regs_, regsNum_)) {
495 HLOGW("unable get sp or sp , unable unwind");
496 return false;
497 } else {
498 if (ip != 0) {
499 HLOGV("unwind:%zu: ip 0x%" PRIx64 " sp 0x%" PRIx64 "", callStack.size(), ip, sp);
500 callStack.emplace_back(ip, sp);
501 }
502 }
503
504 /*
505 * If we need more than one entry, do the DWARF
506 * unwind itself.
507 */
508 if (maxStackLevel - 1 > 0) {
509 #if HAVE_LIBUNWIND
510 return DoUnwind(thread, callStack, maxStackLevel);
511 #else
512 return DoUnwind2(thread, callStack, maxStackLevel);
513 #endif
514 }
515 return true;
516 }
517
LogFrame(const std::string msg,const std::vector<CallFrame> & frames)518 void CallStack::LogFrame(const std::string msg, const std::vector<CallFrame> &frames)
519 {
520 HLOGM("%s", msg.c_str());
521 int level = 0;
522 for (auto& frame : frames) {
523 HLOGM("%d:%s", level++, frame.ToString().c_str());
524 }
525 }
526
527 /*
528 we should have CallStack cache for each thread
529 end begin
530 0. A -> B -> C -> E -> F
531 1. C -> E -> F
532 2. B -> C
533 3. A -> B -> C
534 4. B -> G -> H
535 5. J -> C
536
537 0 is our cache
538 1 2 3... is from record
539
540 use expandLimit to setup how may frame match is needs
541
542 */
DoExpandCallStack(std::vector<CallFrame> & newCallFrames,const std::vector<CallFrame> & cachedCallFrames,size_t expandLimit)543 size_t CallStack::DoExpandCallStack(std::vector<CallFrame> &newCallFrames,
544 const std::vector<CallFrame> &cachedCallFrames,
545 size_t expandLimit)
546 {
547 int maxCycle = 0;
548
549 if (expandLimit == 0 or newCallFrames.size() < expandLimit or
550 cachedCallFrames.size() < expandLimit) {
551 HLOGM("expandLimit %zu not match new %zu cache %zu", expandLimit, newCallFrames.size(),
552 cachedCallFrames.size());
553 return 0; // size not enough
554 }
555
556 // called (Stack Bottom) , this will NOT change when compare
557 // in case1 newIt -> C
558 // in case2 newIt -> B
559 const auto newIt = newCallFrames.end() - expandLimit;
560
561 HLOGM("try find new call chain bottom %s for limit %zu", newIt->ToString().c_str(),
562 expandLimit);
563
564 // first frame search, from called - > caller
565 // for case 2 it should found B
566 size_t distances = expandLimit - 1;
567 auto cachedIt = find(cachedCallFrames.begin(), cachedCallFrames.end(), *newIt);
568 if (cachedIt == cachedCallFrames.end()) {
569 HLOGM("not found in first search");
570 }
571
572 // cache frame found
573 while (std::distance(cachedIt, cachedCallFrames.end()) >= signed(expandLimit)) {
574 HLOG_ASSERT_MESSAGE(maxCycle++ < MAX_CALL_FRAME_EXPAND_CYCLE, "MAX_UNWIND_CYCLE = %d reach",
575 MAX_CALL_FRAME_EXPAND_CYCLE);
576
577 if (std::equal(newIt, newIt + expandLimit, cachedIt)) {
578 HLOGM("match %s + %zu", newIt->ToString().c_str(), expandLimit);
579 cachedIt += expandLimit; // in while we check the boundary safe
580 if (cachedIt == cachedCallFrames.end()) {
581 // same but no more need expand
582 break;
583 }
584
585 // expand the frame and make some log ?
586 LogFrame("newCallStack:", newCallFrames);
587 LogFrame("cachedCallStack:", cachedCallFrames);
588
589 newCallFrames.insert(newCallFrames.end(), cachedIt, cachedCallFrames.end());
590 auto expands = std::distance(cachedIt, cachedCallFrames.end());
591 HLOGV("merge callstack increse to %zu (+%zd) ", newCallFrames.size(), expands);
592 // we done the deal
593 return expands;
594 } else {
595 // quick search next same farme again
596 cachedIt++;
597 if (cachedIt != cachedCallFrames.end()) {
598 HLOGM("search next");
599 cachedIt = find(cachedIt, cachedCallFrames.end(), *newIt);
600 }
601 }
602 }
603 HLOGM("cachedIt distance %zd , need %zd", std::distance(cachedCallFrames.begin(), cachedIt),
604 distances);
605 return 0u; // nothing expand
606 }
607
ExpandCallStack(pid_t tid,std::vector<CallFrame> & callFrames,size_t expandLimit)608 size_t CallStack::ExpandCallStack(pid_t tid, std::vector<CallFrame> &callFrames, size_t expandLimit)
609 {
610 size_t expand = 0u;
611 if (expandLimit == 0) {
612 return expand; // nothing need to do
613 } else if (callFrames.size() < expandLimit) {
614 HLOGM("new callstack is too small, skip it");
615 return expand;
616 }
617 if (!cachedCallFramesMap_.count(tid)) {
618 cachedCallFramesMap_[tid].reserve(MAX_CALL_FRAME_EXPAND_CACHE_SIZE);
619 }
620 if (callFrames.size() >= 1u) {
621 // get top (Earliest caller)
622 HashList<uint64_t, std::vector<CallFrame>> &cachedCallFrames = cachedCallFramesMap_[tid];
623 HLOGV("find call stack frames in cache size %zu", cachedCallFrames.size());
624 // compare
625 using namespace std::rel_ops; // enable complement comparing operators
626 for (auto itr = cachedCallFrames.begin(); itr < cachedCallFrames.end(); ++itr) {
627 // each cached callstack
628 /*
629 stack 2 1 0
630 cache A -> B -> C
631 new B -> C
632 check:
633 1 if new B == cache C
634 2 if new B == cache B
635 3 if new C == new C (if limit > 0)
636 4 insert A after B in new stack
637 */
638 const std::vector<CallFrame> &cachedCallStack = *itr;
639 if (cachedCallStack.size() < expandLimit) {
640 HLOGM("cache callstack is too small, skip it");
641 continue; // check next
642 }
643 expand = DoExpandCallStack(callFrames, cachedCallStack, expandLimit);
644 if (expand > 0) {
645 break;
646 }
647 }
648 // add new one in to cache cachedCallFrames.
649 // further optimization can be done by caching pointer which avoids copying
650 // vector
651 cachedCallFrames[callFrames[0].ip_] = callFrames;
652 }
653 HLOGM("expand %zu", expand);
654 return expand;
655 }
656
657 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
DoUnwind2(const VirtualThread & thread,std::vector<CallFrame> & callStack,size_t maxStackLevel)658 bool CallStack::DoUnwind2(const VirtualThread &thread, std::vector<CallFrame> &callStack,
659 size_t maxStackLevel)
660 {
661 #ifdef target_cpu_x86_64
662 return false;
663 #else
664 UnwindInfo unwindInfo = {
665 .thread = thread,
666 .callStack = *this,
667 };
668
669 if (pidUnwinder_.count(thread.pid_) == 0) {
670 pidUnwinder_.emplace(thread.pid_, std::make_shared<Unwinder>(accessor_));
671 }
672 auto unwinder = pidUnwinder_[thread.pid_];
673
674 #ifdef target_cpu_arm
675 static std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm>();
676 std::vector<uintptr_t> tempRegs;
677 for (auto i = 0; i < regsNum_; ++i) {
678 tempRegs.push_back(static_cast<uintptr_t>(regs_[i]));
679 }
680 regs->SetRegsData(tempRegs);
681 #else
682 static std::shared_ptr<DfxRegs> regs = std::make_shared<DfxRegsArm64>();
683 regs->SetRegsData((uintptr_t*)(regs_), regsNum_);
684 #endif
685 unwinder->SetRegs(regs);
686
687 uintptr_t pc = regs->GetPc();
688 uintptr_t sp = regs->GetSp();
689 while (callStack.size() < maxStackLevel) {
690 if (unwinder->Step(pc, sp, &unwindInfo)) {
691 if (pc == callStack.back().ip_ && sp == callStack.back().sp_) {
692 break;
693 }
694 callStack.emplace_back(pc, sp);
695 HLOGV("unwind:%zu: ip 0x%" UNW_WORD_PFLAG " sp 0x%" UNW_WORD_PFLAG "", callStack.size(), pc, sp);
696 } else {
697 break;
698 }
699 }
700 return true;
701 #endif
702 }
703
DumpTableInfo(UnwindTableInfo & outTableInfo)704 void CallStack::DumpTableInfo(UnwindTableInfo &outTableInfo)
705 {
706 HLOGV("unwind_table info: ");
707 HLOGV(" start_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.startPc);
708 HLOGV(" end_ip: 0x%016" UNW_WORD_PFLAG "", outTableInfo.endPc);
709 HLOGV(" segbase: 0x%016" UNW_WORD_PFLAG "", outTableInfo.segbase);
710 HLOGV(" table_data: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableData);
711 HLOGV(" table_len: 0x%016" UNW_WORD_PFLAG "", outTableInfo.tableLen);
712 }
713
FillUnwindTable(SymbolsFile * symbolsFile,std::shared_ptr<DfxMap> map,UnwindInfo * unwindInfoPtr,uintptr_t pc,UnwindTableInfo & outTableInfo)714 int CallStack::FillUnwindTable(SymbolsFile *symbolsFile, std::shared_ptr<DfxMap> map, UnwindInfo *unwindInfoPtr,
715 uintptr_t pc, UnwindTableInfo& outTableInfo)
716 {
717 HLOGM("try search debug info at %s", symbolsFile->filePath_.c_str());
718 auto &tableInfoMap = unwindInfoPtr->callStack.unwindTableInfoMap_;
719 // all the thread in same process have same mmap and symbols
720 if (tableInfoMap.find(unwindInfoPtr->thread.pid_) == tableInfoMap.end()) {
721 tableInfoMap.emplace(unwindInfoPtr->thread.pid_, DsoUnwindTableInfoMap {});
722 }
723 DsoUnwindTableInfoMap &unwTabMap = tableInfoMap[unwindInfoPtr->thread.pid_];
724 // find use dso name as key
725 if (unwTabMap.find(symbolsFile->filePath_) == unwTabMap.end()) {
726 UnwindTableInfo uti;
727 auto elf = symbolsFile->GetElfFile();
728 if (elf == nullptr) {
729 return -1;
730 }
731 if (elf->FindUnwindTableInfo(pc, map, uti) == 0) {
732 if (uti.format == -1) {
733 HLOGV("parse unwind table failed.");
734 return -1;
735 }
736 unwTabMap[symbolsFile->filePath_] = uti;
737 outTableInfo = unwTabMap[symbolsFile->filePath_];
738 DumpTableInfo(uti);
739 return 0;
740 } else {
741 HLOGV("FillUnwindTable failed");
742 return -1;
743 }
744 } else {
745 outTableInfo = unwTabMap[symbolsFile->filePath_];
746 return 0;
747 }
748 return -1;
749 }
750
FindUnwindTable(uintptr_t pc,UnwindTableInfo & outTableInfo,void * arg)751 int CallStack::FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg)
752 {
753 UnwindInfo *unwindInfoPtr = static_cast<UnwindInfo *>(arg);
754 auto map = unwindInfoPtr->thread.FindMapByAddr(pc);
755 if (map != nullptr) {
756 SymbolsFile *symbolsFile = unwindInfoPtr->thread.FindSymbolsFileByMap(map);
757 if (symbolsFile != nullptr) {
758 return FillUnwindTable(symbolsFile, map, unwindInfoPtr, pc, outTableInfo);
759 } else {
760 HLOGW("no symbols file found for thread %d:%s", unwindInfoPtr->thread.tid_,
761 unwindInfoPtr->thread.name_.c_str());
762 }
763 } else {
764 HLOGE("pc 0x%016" UNW_WORD_PFLAG " not found in thread %d:%s", pc,
765 unwindInfoPtr->thread.tid_, unwindInfoPtr->thread.name_.c_str());
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 if (addr + sizeof(uintptr_t) < addr) {
777 HLOGE("address overflow at 0x%" UNW_WORD_PFLAG " increase 0x%zu", addr, sizeof(uintptr_t));
778 return -1;
779 }
780
781 if (addr < unwindInfoPtr->callStack.stackPoint_ or
782 addr + sizeof(uintptr_t) >= unwindInfoPtr->callStack.stackEnd_) {
783 if (ReadVirtualThreadMemory(*unwindInfoPtr, addr, val)) {
784 HLOGM("access_mem addr get val 0x%" UNW_WORD_PFLAG ", from mmap", *val);
785 } else {
786 HLOGW("access_mem mmap 0x%" PRIx64 " failed, STACK RANGE 0x%" PRIx64 "- 0x%" PRIx64 "(0x%" PRIx64 ")",
787 (uint64_t)addr,
788 unwindInfoPtr->callStack.stackPoint_, unwindInfoPtr->callStack.stackEnd_,
789 unwindInfoPtr->callStack.stackEnd_ - unwindInfoPtr->callStack.stackPoint_);
790 return -1;
791 }
792 } else {
793 size_t stackOffset = addr - unwindInfoPtr->callStack.stackPoint_;
794 *val = *(uintptr_t *)&unwindInfoPtr->callStack.stack_[stackOffset];
795 HLOGM("access_mem addr %p val %" UNW_WORD_PFLAG ", from stack offset %zu",
796 reinterpret_cast<void *>(addr), *val, stackOffset);
797 }
798
799 return 0;
800 }
801 #endif
802
CallStack()803 CallStack::CallStack()
804 {
805 #if defined(HAVE_LIBUNWINDER) && HAVE_LIBUNWINDER
806 accessor_ = std::make_shared<OHOS::HiviewDFX::UnwindAccessors>();
807 accessor_->FindUnwindTable = &CallStack::FindUnwindTable;
808 accessor_->AccessMem = &CallStack::AccessMem2;
809 accessor_->AccessReg = nullptr;
810 #endif
811 }
812
~CallStack()813 CallStack::~CallStack()
814 {
815 #if HAVE_LIBUNWIND
816 for (auto &pair : unwindAddrSpaceMap_) {
817 unw_destroy_addr_space(pair.second);
818 }
819 #endif
820 }
821 } // namespace HiPerf
822 } // namespace Developtools
823 } // namespace OHOS
824