dmp文件分析(四)- 调用栈分析


| 阅读 |,阅读约 4 分钟
| 复制链接:

Overview

dmp文件分析(四)- 调用栈分析

前面的章节我们介绍过,分析dmp的一个调用栈的过程,以及获取上一个栈帧的公式。今天介绍这个公式在breakpad中的源码调用。调用栈分析有三种途径,里面很多细节还不是完全理解,只是大概罗列一下代码的框架

获取调用者的栈帧

前面介绍过Stackwalker方法是基类,每种不同的cpu架构都有自己的实现类,都需要重写几个方法,其中很重要的一个是GetCallerFrame方法,用于获取调用栈的上一个栈帧

caller: 调用者(父函数); callee:被调用者(子函数)

这里以常用的amd64架构cpu介绍源码实现。实现类为 StackwalkerAMD64

 1StackFrame* StackwalkerAMD64::GetCallerFrame(const CallStack* stack,
 2                                             bool stack_scan_allowed) {
 3  ...
 4  // 先获取调用栈所有的栈帧
 5  const vector<StackFrame*> &frames = *stack->frames();
 6  // 取出最后一个栈帧
 7  StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
 8  scoped_ptr<StackFrameAMD64> new_frame;
 9
10  // 如果symbol中有CFI信息,查找栈帧相关的CFI信息
11  scoped_ptr<CFIFrameInfo> cfi_frame_info(
12      frame_symbolizer_->FindCFIFrameInfo(last_frame));
13  if (cfi_frame_info.get())
14    // 通过CFI信息获取上次调用函数的栈帧
15    new_frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get()));
16
17  // If CFI was not available or failed, try using frame pointer recovery.
18  // 如果CFI不可用或者获取失败,尝试用栈帧结构中的指针恢复调用关系
19  if (!new_frame.get()) {
20    new_frame.reset(GetCallerByFramePointerRecovery(frames));
21  }
22
23  // If all else fails, fall back to stack scanning.
24  // 如果上面的方法都获取失败,退回堆栈扫描
25  if (stack_scan_allowed && !new_frame.get()) {
26    new_frame.reset(GetCallerByStackScan(frames));
27  }
28
29  // If nothing worked, tell the caller.
30  if (!new_frame.get())
31    return NULL;
32
33  // 判断是否应该终止栈帧查找(找到了栈尾,或者栈帧被损坏了)
34  // Should we terminate the stack walk? (end-of-stack or broken invariant)
35  if (TerminateWalk(new_frame->context.rip, new_frame->context.rsp,
36                    last_frame->context.rsp, frames.size() == 1)) {
37    return NULL;
38  }
39
40  // new_frame->context.rip是返回地址,即在调用CALL指令之后,被调用者的地址
41  // new_frame->instruction 的值比它小一点,指向CALL指令
42  new_frame->instruction = new_frame->context.rip - 1;
43
44  return new_frame.release();
45}

根据CFI分析上层栈帧

  • CFI 相关知识目前还不太清楚,后续会专门研究一下并整理文档

查找symbol的CFI

 1CFIFrameInfo *SourceLineResolverBase::FindCFIFrameInfo(
 2    const StackFrame *frame) {
 3  if (frame->module) {
 4    // 根据模块的文件名为key,查询模块信息
 5    ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
 6    if (it != modules_->end()) {
 7      return it->second->FindCFIFrameInfo(frame);
 8    }
 9  }
10  return NULL;
11}
12
13// 下面部分为从symbol读取的CFI信息,并且保存在一个RangeMap中
14CFIFrameInfo *BasicSourceLineResolver::Module::FindCFIFrameInfo(
15    const StackFrame *frame) const {
16  MemAddr address = frame->instruction - frame->module->base_address();
17  MemAddr initial_base, initial_size;
18  string initial_rules;
19
20  // Find the initial rule whose range covers this address. That
21  // provides an initial set of register recovery rules. Then, walk
22  // forward from the initial rule's starting address to frame's
23  // instruction address, applying delta rules.
24  if (!cfi_initial_rules_.RetrieveRange(address, &initial_rules, &initial_base,
25                                        NULL /* delta */, &initial_size)) {
26    return NULL;
27  }
28
29  // Create a frame info structure, and populate it with the rules from
30  // the STACK CFI INIT record.
31  scoped_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
32  if (!ParseCFIRuleSet(initial_rules, rules.get()))
33    return NULL;
34
35  // Find the first delta rule that falls within the initial rule's range.
36  map<MemAddr, string>::const_iterator delta =
37    cfi_delta_rules_.lower_bound(initial_base);
38
39  // Apply delta rules up to and including the frame's address.
40  while (delta != cfi_delta_rules_.end() && delta->first <= address) {
41    ParseCFIRuleSet(delta->second, rules.get());
42    delta++;
43  }
44
45  return rules.release();
46}

根据CFI查找堆栈

 1StackFrameAMD64* StackwalkerAMD64::GetCallerByCFIFrameInfo(
 2    const vector<StackFrame*> &frames,
 3    CFIFrameInfo* cfi_frame_info) {
 4  StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
 5
 6  scoped_ptr<StackFrameAMD64> frame(new StackFrameAMD64());
 7  if (!cfi_walker_
 8      .FindCallerRegisters(*memory_, *cfi_frame_info,
 9                           last_frame->context, last_frame->context_validity,
10                           &frame->context, &frame->context_validity))
11    return NULL;
12
13  // Make sure we recovered all the essentials.
14  static const int essentials = (StackFrameAMD64::CONTEXT_VALID_RIP
15                                 | StackFrameAMD64::CONTEXT_VALID_RSP);
16  if ((frame->context_validity & essentials) != essentials)
17    return NULL;
18
19  frame->trust = StackFrame::FRAME_TRUST_CFI;
20  return frame.release();
21}

根据栈帧结构分析上层栈帧

  • 这里利用了上一节分析到的栈帧结构中的几个公式
 1StackFrameAMD64* StackwalkerAMD64::GetCallerByFramePointerRecovery(
 2    const vector<StackFrame*>& frames) {
 3
 4  // 获取上一个栈帧的 rbp 寄存器
 5  StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
 6  uint64_t last_rbp = last_frame->context.rbp;
 7
 8  // 下面利用上一篇文章说到的栈帧结构公式,计算下一个栈帧的寄存器地址
 9
10  // Assume the presence of a frame pointer. This is not mandated by the
11  // AMD64 ABI, c.f. section 3.2.2 footnote 7, though it is typical for
12  // compilers to still preserve the frame pointer and not treat %rbp as a
13  // general purpose register.
14  //
15  // With this assumption, the CALL instruction pushes the return address
16  // onto the stack and sets %rip to the procedure to enter. The procedure
17  // then establishes the stack frame with a prologue that PUSHes the current
18  // %rbp onto the stack, MOVes the current %rsp to %rbp, and then allocates
19  // space for any local variables. Using this procedure linking information,
20  // it is possible to locate frame information for the callee:
21  //
22  // %caller_rsp = *(%callee_rbp + 16)
23  // %caller_rip = *(%callee_rbp + 8)
24  // %caller_rbp = *(%callee_rbp)
25
26  // If rbp is not 8-byte aligned it can't be a frame pointer.
27  if (last_rbp % 8 != 0) {
28    return NULL;
29  }
30
31  uint64_t caller_rip, caller_rbp;
32  if (memory_->GetMemoryAtAddress(last_rbp + 8, &caller_rip) &&
33      memory_->GetMemoryAtAddress(last_rbp, &caller_rbp)) {
34    uint64_t caller_rsp = last_rbp + 16;
35
36    StackFrameAMD64* frame = new StackFrameAMD64();
37    frame->trust = StackFrame::FRAME_TRUST_FP;
38    frame->context = last_frame->context;
39    frame->context.rip = caller_rip;
40    frame->context.rsp = caller_rsp;
41    frame->context.rbp = caller_rbp;
42    frame->context_validity = StackFrameAMD64::CONTEXT_VALID_RIP |
43                              StackFrameAMD64::CONTEXT_VALID_RSP |
44                              StackFrameAMD64::CONTEXT_VALID_RBP;
45    return frame;
46  }
47
48  return NULL;
49}

扫描栈获取下一个栈帧的信息

  • 这个地方也还没有看明白
 1StackFrameAMD64* StackwalkerAMD64::GetCallerByStackScan(
 2    const vector<StackFrame*> &frames) {
 3  StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
 4  uint64_t last_rsp = last_frame->context.rsp;
 5  uint64_t caller_rip_address, caller_rip;
 6
 7  // 查找寄存器中的返回地址
 8  if (!ScanForReturnAddress(last_rsp, &caller_rip_address, &caller_rip,
 9                            frames.size() == 1 /* is_context_frame */)) {
10    // No plausible return address was found.
11    return NULL;
12  }
13
14  // Create a new stack frame (ownership will be transferred to the caller)
15  // and fill it in.
16  StackFrameAMD64* frame = new StackFrameAMD64();
17
18  frame->trust = StackFrame::FRAME_TRUST_SCAN;
19  frame->context = last_frame->context;
20  frame->context.rip = caller_rip;
21  // The caller's %rsp is directly underneath the return address pushed by
22  // the call.
23  // 栈帧的 esp地址 = 调用者的 eip + 8
24  frame->context.rsp = caller_rip_address + 8;
25  frame->context_validity = StackFrameAMD64::CONTEXT_VALID_RIP |
26                            StackFrameAMD64::CONTEXT_VALID_RSP;
27
28  // Other unwinders give up if they don't have an %rbp value, so see if we
29  // can pass some plausible value on.
30  if (last_frame->context_validity & StackFrameAMD64::CONTEXT_VALID_RBP) {
31    // Functions typically push their caller's %rbp immediately upon entry,
32    // and then set %rbp to point to that. So if the callee's %rbp is
33    // pointing to the first word below the alleged return address, presume
34    // that the caller's %rbp is saved there.
35    if (caller_rip_address - 8 == last_frame->context.rbp) {
36      uint64_t caller_rbp = 0;
37      if (memory_->GetMemoryAtAddress(last_frame->context.rbp, &caller_rbp) &&
38          caller_rbp > caller_rip_address) {
39        frame->context.rbp = caller_rbp;
40        frame->context_validity |= StackFrameAMD64::CONTEXT_VALID_RBP;
41      }
42    } else if (last_frame->context.rbp >= caller_rip_address + 8) {
43      // If the callee's %rbp is plausible as a value for the caller's
44      // %rbp, presume that the callee left it unchanged.
45      frame->context.rbp = last_frame->context.rbp;
46      frame->context_validity |= StackFrameAMD64::CONTEXT_VALID_RBP;
47    }
48  }
49  return frame;
50}
 1template<typename InstructionType>
 2  bool ScanForReturnAddress(InstructionType location_start,
 3                            InstructionType* location_found,
 4                            InstructionType* ip_found,
 5                            bool is_context_frame) {
 6    // When searching for the caller of the context frame,
 7    // allow the scanner to look farther down the stack.
 8    const int search_words = is_context_frame ?
 9      kRASearchWords * 4 :
10      kRASearchWords;
11
12    return ScanForReturnAddress(location_start, location_found, ip_found,
13                                search_words);
14  }
15
16template<typename InstructionType>
17  bool ScanForReturnAddress(InstructionType location_start,
18                            InstructionType* location_found,
19                            InstructionType* ip_found,
20                            int searchwords) {
21    for (InstructionType location = location_start;
22         location <= location_start + searchwords * sizeof(InstructionType);
23         location += sizeof(InstructionType)) {
24      InstructionType ip;
25      if (!memory_->GetMemoryAtAddress(location, &ip))
26        break;
27
28      if (modules_ && modules_->GetModuleForAddress(ip) &&
29          InstructionAddressSeemsValid(ip)) {
30        *ip_found = ip;
31        *location_found = location;
32        return true;
33      }
34    }
35    // nothing found
36    return false;
37  }