dmp文件分析(一)- 概述
| 阅读 | 共 3082 字,阅读约
Overview
dmp文件分析(一)
前面的章节已经将dmp文件读取分析完成,读取过程将dmp中的信息加载到对象中,用于后续分析,这篇文章开始介绍分析流程。
dmp读取流程回顾
如下源码已经做了详细注释,总结如下:
- 读取头文件中的文件创建时间
- 读取进程运行时长
- 读取cpu信息
- 读取操作系统信息
- 读取崩溃的线程id
- 读取崩溃原因
- 读取断言信息
- 读取模块信息,并拷贝一份,用于后续分析(分析时会修改原始数据)
- 读取内存信息
- 读取线程信息
- for循环,依次分析每个线程的调用栈
分析调用栈需要的数据
- 操作系统信息
- 上下文信息
- 线程内存信息
- 模块列表
- 符号相关参数信息
1ProcessResult MinidumpProcessor::Process(
2 Minidump *dump, ProcessState *process_state) {
3
4 // 读取dmp文件头
5 const MDRawHeader *header = dump->header();
6 // 取出创建的时间戳
7 process_state->time_date_stamp_ = header->time_date_stamp;
8
9 // 读取misc stream中的进程运行时长
10 bool has_process_create_time =
11 GetProcessCreateTime(dump, &process_state->process_create_time_);
12
13 // 读取system stream中的cpu、处理器架构信息
14 bool has_cpu_info = GetCPUInfo(dump, &process_state->system_info_);
15 // 读取system stream中的操作系统信息
16 bool has_os_info = GetOSInfo(dump, &process_state->system_info_);
17
18 // 读取breakpad stream中的自定义崩溃信息
19 MinidumpBreakpadInfo *breakpad_info = dump->GetBreakpadInfo();
20 if (breakpad_info) {
21 // 如果breakpad中有崩溃线程id,以这个线程id为准
22 has_dump_thread = breakpad_info->GetDumpThreadID(&dump_thread_id);
23 has_requesting_thread =
24 breakpad_info->GetRequestingThreadID(&requesting_thread_id);
25 }
26 // 读取exception中的异常描述信息
27 MinidumpException *exception = dump->GetException();
28 if (exception) {
29 process_state->crashed_ = true;
30 has_requesting_thread = exception->GetThreadID(&requesting_thread_id);
31 // 获取进程崩溃原因
32 process_state->crash_reason_ = GetCrashReason(
33 dump, &process_state->crash_address_);
34 }
35
36 // 获取assert stream中的断言信息
37 process_state->assertion_ = GetAssertion(dump);
38 // 获取module stream中的模块列表信息
39 MinidumpModuleList *module_list = dump->GetModuleList();
40 if (module_list) {
41 // 将模块信息拷贝一份,后面分析时使用拷贝的副本进行分析
42 // 原因是:后续在分析调用栈时,会动态的修改这个字段,不能在原有的字段上操作
43 process_state->modules_ = module_list->Copy();
44 }
45 // 获取未加载的模块列表信息(这个模块跟module stream类似,没有专门介绍)
46 MinidumpUnloadedModuleList *unloaded_module_list =
47 dump->GetUnloadedModuleList();
48 // 获取 memory stream中的内存信息
49 MinidumpMemoryList *memory_list = dump->GetMemoryList();
50 // 获取 thread stream中的线程列表信息
51 MinidumpThreadList *threads = dump->GetThreadList();
52
53 // 遍历所有的线程
54 for (unsigned int thread_index = 0;
55 thread_index < thread_count;
56 ++thread_index) {
57
58 MinidumpThread *thread = threads->GetThreadAtIndex(thread_index);
59
60 // breakpad中记录的线程,表示是崩溃线程,这个线程跳过分析
61 // 因为这个崩溃的线程的堆栈和上下文信息有可能被损坏了,分析它并不会给我们提供有用信息
62 if (has_dump_thread && thread_id == dump_thread_id) {
63 continue;
64 }
65
66 // 获取线程的上下文信息
67 MinidumpContext *context = thread->GetContext();
68
69 if (has_requesting_thread && thread_id == requesting_thread_id) {
70 // 如果找到两个崩溃线程,出错
71 if (found_requesting_thread) {
72 BPLOG(ERROR) << "Duplicate requesting thread: " << thread_string;
73 return PROCESS_ERROR_DUPLICATE_REQUESTING_THREADS;
74 }
75 // 如果exception stream中有上下文信息,就用这个上下文信息代替线程本身的上下文信息
76 if (process_state->crashed_) {
77 MinidumpContext *ctx = exception->GetContext();
78 context = ctx ? ctx : thread->GetContext();
79 }
80 }
81
82 // 读取线程运行时的内存信息
83 MinidumpMemoryRegion *thread_memory = thread->GetMemory();
84 // 如果读取不到线程中存储的内存信息
85 if (!thread_memory && memory_list) {
86 // 尝试获取线程堆栈的起始内存地址
87 uint64_t start_stack_memory_range = thread->GetStartOfStackMemoryRange();
88 if (start_stack_memory_range) {
89 // 根据线程栈中的起始内存地址,在进程内存列表中找到一个比较符合的内存信息
90 thread_memory = memory_list->GetMemoryRegionForAddress(
91 start_stack_memory_range);
92 }
93 }
94
95 /**
96 * 这里是分析的入口函数,前面都是读取dmp文件的各种数据初始化操作
97 * 外面套了一个for循环,依次对dmp中的每个线程做堆栈的提取和分析
98 * 从这个函数可以看出,分析每个线程的堆栈需要的参数有:
99 * 1. 操作系统信息
100 1. 上下文信息
101 * 3. 线程内存信息
102 * 4. 模块列表
103 * 5. 未加载的模块列表
104 * 6. 符号相关参数信息(最后一个参数frame_symbolizer_)
105 */
106 scoped_ptr<Stackwalker> stackwalker(
107 Stackwalker::StackwalkerForCPU(process_state->system_info(),
108 context,
109 thread_memory,
110 process_state->modules_,
111 process_state->unloaded_modules_,
112 frame_symbolizer_));
113
114 scoped_ptr<CallStack> stack(new CallStack());
115 if (stackwalker.get()) {
116 if (!stackwalker->Walk(stack.get(),
117 &process_state->modules_without_symbols_,
118 &process_state->modules_with_corrupt_symbols_)) {
119 BPLOG(INFO) << "Stackwalker interrupt (missing symbols?) at "
120 << thread_string;
121 interrupted = true;
122 }
123 } else {
124 // 线程如果找不到cpu上下文信息,将会走到这一步
125 // 但是并不会终止分析,只是认为只是一个被损坏的线程,会继续进行下一个线程的分析,
126 BPLOG(ERROR) << "No stackwalker for " << thread_string;
127 }
128 // 重置栈的线程id
129 stack->set_tid(thread_id);
130 // 保存栈帧信息到堆栈列表
131 process_state->threads_.push_back(stack.release());
132 // 保存线程内存信息到内存列表
133 process_state->thread_memory_regions_.push_back(thread_memory);
134 }
135 ...
136}
线程堆栈分析核心流程
- Stackwalker::StackwalkerForCPU函数根据不同的cpu类型实例化不同的类
- 构造stackwalker对象
- 调用stackwalker的Walk方法开始分析
1ProcessResult MinidumpProcessor::Process(
2 Minidump *dump, ProcessState *process_state) {
3
4 ...
5 for (unsigned int thread_index = 0;
6 thread_index < thread_count;
7 ++thread_index) {
8 ...
9 /**
10 * 这里是分析的入口函数,前面都是读取dmp文件的各种数据初始化操作
11 * 外面套了一个for循环,依次对dmp中的每个线程做堆栈的提取和分析
12 * 从这个函数可以看出,分析每个线程的堆栈需要的参数有:
13 * 1. 操作系统信息
14 1. 上下文信息
15 * 3. 线程内存信息
16 * 4. 模块列表
17 * 5. 未加载的模块列表
18 * 6. 符号相关参数信息(最后一个参数frame_symbolizer_)
19 */
20 scoped_ptr<Stackwalker> stackwalker(
21 // 根据CPU信息,实例化不同的类
22 Stackwalker::StackwalkerForCPU(process_state->system_info(),
23 context,
24 thread_memory,
25 process_state->modules_,
26 process_state->unloaded_modules_,
27 frame_symbolizer_));
28
29 scoped_ptr<CallStack> stack(new CallStack());
30 if (stackwalker.get()) {
31 // 分析的核心方法
32 if (!stackwalker->Walk(stack.get(),
33 &process_state->modules_without_symbols_,
34 &process_state->modules_with_corrupt_symbols_)) {
35 BPLOG(INFO) << "Stackwalker interrupt (missing symbols?) at "
36 << thread_string;
37 interrupted = true;
38 }
39 }
40 ...
41}
构造Stackwalker
- Stackwalker是一个基类,不同类型的cpu,都继承这个类,实现自己的堆栈分析方法
- 调用context中的方法,得到cpu信息,初始化不同的子类
- 不同的子类,重写基类的Walk方法
1Stackwalker* Stackwalker::StackwalkerForCPU(
2 const SystemInfo* system_info,
3 DumpContext* context,
4 MemoryRegion* memory,
5 const CodeModules* modules,
6 const CodeModules* unloaded_modules,
7 StackFrameSymbolizer* frame_symbolizer) {
8 // 获取cpu信息
9 uint32_t cpu = context->GetContextCPU();
10 // 根据不同的cpu类型,初始化不同的子类
11 // 可以看到,不同的cpu类型,获取context(上下文)的方法也不同
12 switch (cpu) {
13 case MD_CONTEXT_X86:
14 cpu_stackwalker = new StackwalkerX86(system_info,
15 context->GetContextX86(),
16 memory, modules, frame_symbolizer);
17 break;
18
19 case MD_CONTEXT_PPC:
20 cpu_stackwalker = new StackwalkerPPC(system_info,
21 context->GetContextPPC(),
22 memory, modules, frame_symbolizer);
23 break;
24
25 ...
26 }
27
28 return cpu_stackwalker;
29}
不同cpu的context
- context记录的线程堆栈分析的核心数据
- context使用union(联合体)记录这些数据
- 写入时,根据cpu类型,只会给cpu相匹配的字段赋值
- 读取时,根据cpu类型,只会获取指定字段的值
1union {
2 MDRawContextBase* base;
3 MDRawContextX86* x86;
4 MDRawContextPPC* ppc;
5 MDRawContextPPC64* ppc64;
6 MDRawContextAMD64* amd64;
7 // on Solaris SPARC, sparc is defined as a numeric constant,
8 // so variables can NOT be named as sparc
9 MDRawContextSPARC* ctx_sparc;
10 MDRawContextARM* arm;
11 MDRawContextARM64* arm64;
12 MDRawContextMIPS* ctx_mips;
13 } context_;
Walk方法概述
- 根据不同cpu构造好Stackwalker的子类后,开始调用Walk方法进行堆栈分析
- 分析每一个栈帧时,会先读取匹配的符号信息,协助分析
- 每一个栈帧中保存着寄存器的指令地址:eip
- 分析完一个栈帧,会查找它的调用者(上一级)栈帧,直到分析到栈结束
- 或者调用者的栈帧方法为:GetCallerFrame,下一章专门介绍
1bool Stackwalker::Walk(
2 CallStack* stack,
3 vector<const CodeModule*>* modules_without_symbols,
4 vector<const CodeModule*>* modules_with_corrupt_symbols) {
5
6 // 先清空堆栈信息
7 stack->Clear();
8
9 // 开始根据栈帧的上下文信息,找到上层调用者,直到调用结束
10 // 记录调用栈的层数,防止递归调用死循环的存在
11 uint32_t scanned_frames = 0;
12
13 // 构造一个栈帧,并取出context中的寄存器的eip,并缓存下来
14 scoped_ptr<StackFrame> frame(GetContextFrame());
15
16 // 这里是一个死循环,一直分析并提取堆栈,直到下一个栈帧为空
17 while (frame.get()) {
18 // 栈帧始终包含一个有效的调用栈信息,并设置指令地址
19 // 指令地址来自context中的eip,或者被调用者
20
21 // 提取symbol信息,协助分析
22 StackFrameSymbolizer::SymbolizerResult symbolizer_result =
23 frame_symbolizer_->FillSourceLineInfo(modules_, unloaded_modules_,
24 system_info_,
25 frame.get());
26 // 匹配的符号文件的结果
27 switch (symbolizer_result) {
28 case StackFrameSymbolizer::kInterrupt:
29 BPLOG(INFO) << "Stack walk is interrupted.";
30 return false;
31 break;
32 case StackFrameSymbolizer::kError:
33 InsertSpecialAttentionModule(symbolizer_result, frame->module,
34 modules_without_symbols);
35 break;
36 case StackFrameSymbolizer::kWarningCorruptSymbols:
37 InsertSpecialAttentionModule(symbolizer_result, frame->module,
38 modules_with_corrupt_symbols);
39 break;
40 case StackFrameSymbolizer::kNoError:
41 break;
42 default:
43 assert(false);
44 break;
45 }
46
47 // 调用层数 +1
48 switch (frame.get()->trust) {
49 case StackFrame::FRAME_TRUST_NONE:
50 case StackFrame::FRAME_TRUST_SCAN:
51 case StackFrame::FRAME_TRUST_CFI_SCAN:
52 scanned_frames++;
53 break;
54 default:
55 break;
56 }
57
58 // 添加分析完毕的栈帧信息到堆栈列表中
59 stack->frames_.push_back(frame.release());
60
61 // 获取下一个栈帧(调用者的栈帧)
62 bool stack_scan_allowed = scanned_frames < max_frames_scanned_;
63
64 // 栈帧回溯的核心方法:GetCallerFrame
65 frame.reset(GetCallerFrame(stack, stack_scan_allowed));
66 }
67
68 return true;
69}
GetContextFrame
- GetContextFrame是基类的一个虚方法,子类必须重新
- 这里以x86的cpu架构为例,分析实现逻辑
- 内部实现:
- 构造一个StackFrame对象
- 取出寄存器中的eip,放入StackFrame的成员变量中
1StackFrame* StackwalkerX86::GetContextFrame() {
2
3 StackFrameX86* frame = new StackFrameX86();
4
5 // context中记录了寄存器的信息,
6 // 其中最核心的是指令指针:eip
7 // eip寄存器,用来存储CPU要读取指令的地址
8 // 崩溃的时候,eip存放的地址,就是崩溃时的指令地址
9 // 将eip传给栈帧的instruction字段,用于后续分析
10 frame->context = *context_;
11 frame->context_validity = StackFrameX86::CONTEXT_VALID_ALL;
12 frame->trust = StackFrame::FRAME_TRUST_CONTEXT;
13 frame->instruction = frame->context.eip;
14
15 return frame;
16}