1 | // Copyright (c) 2014 Google Inc. |
2 | // All rights reserved. |
3 | // |
4 | // Redistribution and use in source and binary forms, with or without |
5 | // modification, are permitted provided that the following conditions are |
6 | // met: |
7 | // |
8 | // * Redistributions of source code must retain the above copyright |
9 | // notice, this list of conditions and the following disclaimer. |
10 | // * Redistributions in binary form must reproduce the above |
11 | // copyright notice, this list of conditions and the following disclaimer |
12 | // in the documentation and/or other materials provided with the |
13 | // distribution. |
14 | // * Neither the name of Google Inc. nor the names of its |
15 | // contributors may be used to endorse or promote products derived from |
16 | // this software without specific prior written permission. |
17 | // |
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | |
30 | // microdump.cc: A microdump reader. |
31 | // |
32 | // See microdump.h for documentation. |
33 | |
34 | #include "google_breakpad/processor/microdump.h" |
35 | |
36 | #include <stdio.h> |
37 | #include <string.h> |
38 | |
39 | #include <memory> |
40 | #include <sstream> |
41 | #include <string> |
42 | #include <vector> |
43 | |
44 | #include "google_breakpad/common/minidump_cpu_arm.h" |
45 | #include "google_breakpad/processor/code_module.h" |
46 | #include "processor/basic_code_module.h" |
47 | #include "processor/convert_old_arm64_context.h" |
48 | #include "processor/linked_ptr.h" |
49 | #include "processor/logging.h" |
50 | #include "processor/range_map-inl.h" |
51 | |
52 | namespace { |
53 | static const char kGoogleBreakpadKey[] = "google-breakpad" ; |
54 | static const char kMicrodumpBegin[] = "-----BEGIN BREAKPAD MICRODUMP-----" ; |
55 | static const char kMicrodumpEnd[] = "-----END BREAKPAD MICRODUMP-----" ; |
56 | static const char kOsKey[] = ": O " ; |
57 | static const char kCpuKey[] = ": C " ; |
58 | static const char kCrashReasonKey[] = ": R " ; |
59 | static const char kGpuKey[] = ": G " ; |
60 | static const char kMmapKey[] = ": M " ; |
61 | static const char kStackKey[] = ": S " ; |
62 | static const char kStackFirstLineKey[] = ": S 0 " ; |
63 | static const char kArmArchitecture[] = "arm" ; |
64 | static const char kArm64Architecture[] = "arm64" ; |
65 | static const char kX86Architecture[] = "x86" ; |
66 | static const char kMipsArchitecture[] = "mips" ; |
67 | static const char kMips64Architecture[] = "mips64" ; |
68 | static const char kGpuUnknown[] = "UNKNOWN" ; |
69 | |
70 | template<typename T> |
71 | T HexStrToL(const string& str) { |
72 | uint64_t res = 0; |
73 | std::istringstream ss(str); |
74 | ss >> std::hex >> res; |
75 | return static_cast<T>(res); |
76 | } |
77 | |
78 | std::vector<uint8_t> ParseHexBuf(const string& str) { |
79 | std::vector<uint8_t> buf; |
80 | for (size_t i = 0; i < str.length(); i += 2) { |
81 | buf.push_back(HexStrToL<uint8_t>(str.substr(i, 2))); |
82 | } |
83 | return buf; |
84 | } |
85 | |
86 | bool GetLine(std::istringstream* istream, string* str) { |
87 | if (std::getline(*istream, *str)) { |
88 | // Trim any trailing newline from the end of the line. Allows us |
89 | // to seamlessly handle both Windows/DOS and Unix formatted input. The |
90 | // adb tool generally writes logcat dumps in Windows/DOS format. |
91 | if (!str->empty() && str->at(str->size() - 1) == '\r') { |
92 | str->erase(str->size() - 1); |
93 | } |
94 | return true; |
95 | } |
96 | return false; |
97 | } |
98 | |
99 | } // namespace |
100 | |
101 | namespace google_breakpad { |
102 | |
103 | // |
104 | // MicrodumpModules |
105 | // |
106 | |
107 | void MicrodumpModules::Add(const CodeModule* module) { |
108 | linked_ptr<const CodeModule> module_ptr(module); |
109 | if (!map_.StoreRange(module->base_address(), module->size(), module_ptr)) { |
110 | BPLOG(ERROR) << "Module " << module->code_file() << |
111 | " could not be stored" ; |
112 | } |
113 | } |
114 | |
115 | void MicrodumpModules::SetEnableModuleShrink(bool is_enabled) { |
116 | map_.SetMergeStrategy(is_enabled ? MergeRangeStrategy::kTruncateUpper |
117 | : MergeRangeStrategy::kExclusiveRanges); |
118 | } |
119 | |
120 | // |
121 | // MicrodumpContext |
122 | // |
123 | |
124 | void MicrodumpContext::SetContextARM(MDRawContextARM* arm) { |
125 | DumpContext::SetContextFlags(MD_CONTEXT_ARM); |
126 | DumpContext::SetContextARM(arm); |
127 | valid_ = true; |
128 | } |
129 | |
130 | void MicrodumpContext::SetContextARM64(MDRawContextARM64* arm64) { |
131 | DumpContext::SetContextFlags(MD_CONTEXT_ARM64); |
132 | DumpContext::SetContextARM64(arm64); |
133 | valid_ = true; |
134 | } |
135 | |
136 | void MicrodumpContext::SetContextX86(MDRawContextX86* x86) { |
137 | DumpContext::SetContextFlags(MD_CONTEXT_X86); |
138 | DumpContext::SetContextX86(x86); |
139 | valid_ = true; |
140 | } |
141 | |
142 | void MicrodumpContext::SetContextMIPS(MDRawContextMIPS* mips32) { |
143 | DumpContext::SetContextFlags(MD_CONTEXT_MIPS); |
144 | DumpContext::SetContextMIPS(mips32); |
145 | valid_ = true; |
146 | } |
147 | |
148 | void MicrodumpContext::SetContextMIPS64(MDRawContextMIPS* mips64) { |
149 | DumpContext::SetContextFlags(MD_CONTEXT_MIPS64); |
150 | DumpContext::SetContextMIPS(mips64); |
151 | valid_ = true; |
152 | } |
153 | |
154 | |
155 | // |
156 | // MicrodumpMemoryRegion |
157 | // |
158 | |
159 | MicrodumpMemoryRegion::MicrodumpMemoryRegion() : base_address_(0) { } |
160 | |
161 | void MicrodumpMemoryRegion::Init(uint64_t base_address, |
162 | const std::vector<uint8_t>& contents) { |
163 | base_address_ = base_address; |
164 | contents_ = contents; |
165 | } |
166 | |
167 | uint64_t MicrodumpMemoryRegion::GetBase() const { return base_address_; } |
168 | |
169 | uint32_t MicrodumpMemoryRegion::GetSize() const { return contents_.size(); } |
170 | |
171 | bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
172 | uint8_t* value) const { |
173 | return GetMemoryLittleEndian(address, value); |
174 | } |
175 | |
176 | bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
177 | uint16_t* value) const { |
178 | return GetMemoryLittleEndian(address, value); |
179 | } |
180 | |
181 | bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
182 | uint32_t* value) const { |
183 | return GetMemoryLittleEndian(address, value); |
184 | } |
185 | |
186 | bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
187 | uint64_t* value) const { |
188 | return GetMemoryLittleEndian(address, value); |
189 | } |
190 | |
191 | template<typename ValueType> |
192 | bool MicrodumpMemoryRegion::GetMemoryLittleEndian(uint64_t address, |
193 | ValueType* value) const { |
194 | if (address < base_address_ || |
195 | address - base_address_ + sizeof(ValueType) > contents_.size()) |
196 | return false; |
197 | ValueType v = 0; |
198 | uint64_t start = address - base_address_; |
199 | // The loop condition is odd, but it's correct for size_t. |
200 | for (size_t i = sizeof(ValueType) - 1; i < sizeof(ValueType); i--) |
201 | v = (v << 8) | static_cast<uint8_t>(contents_[start + i]); |
202 | *value = v; |
203 | return true; |
204 | } |
205 | |
206 | void MicrodumpMemoryRegion::Print() const { |
207 | // Not reached, just needed to honor the base class contract. |
208 | assert(false); |
209 | } |
210 | |
211 | // |
212 | // Microdump |
213 | // |
214 | Microdump::Microdump(const string& contents) |
215 | : context_(new MicrodumpContext()), |
216 | stack_region_(new MicrodumpMemoryRegion()), |
217 | modules_(new MicrodumpModules()), |
218 | system_info_(new SystemInfo()), |
219 | crash_reason_(), |
220 | crash_address_(0u) { |
221 | assert(!contents.empty()); |
222 | |
223 | bool in_microdump = false; |
224 | string line; |
225 | uint64_t stack_start = 0; |
226 | std::vector<uint8_t> stack_content; |
227 | string arch; |
228 | |
229 | std::istringstream stream(contents); |
230 | while (GetLine(&stream, &line)) { |
231 | if (line.find(kGoogleBreakpadKey) == string::npos) { |
232 | continue; |
233 | } |
234 | if (line.find(kMicrodumpBegin) != string::npos) { |
235 | in_microdump = true; |
236 | continue; |
237 | } |
238 | if (!in_microdump) { |
239 | continue; |
240 | } |
241 | if (line.find(kMicrodumpEnd) != string::npos) { |
242 | break; |
243 | } |
244 | |
245 | size_t pos; |
246 | if ((pos = line.find(kOsKey)) != string::npos) { |
247 | string os_str(line, pos + strlen(kOsKey)); |
248 | std::istringstream os_tokens(os_str); |
249 | string os_id; |
250 | string num_cpus; |
251 | string os_version; |
252 | // This reflect the actual HW arch and might not match the arch emulated |
253 | // for the execution (e.g., running a 32-bit binary on a 64-bit cpu). |
254 | string hw_arch; |
255 | |
256 | os_tokens >> os_id; |
257 | os_tokens >> arch; |
258 | os_tokens >> num_cpus; |
259 | os_tokens >> hw_arch; |
260 | GetLine(&os_tokens, &os_version); |
261 | os_version.erase(0, 1); // remove leading space. |
262 | |
263 | system_info_->cpu = arch; |
264 | system_info_->cpu_count = HexStrToL<uint8_t>(num_cpus); |
265 | system_info_->os_version = os_version; |
266 | |
267 | if (os_id == "L" ) { |
268 | system_info_->os = "Linux" ; |
269 | system_info_->os_short = "linux" ; |
270 | } else if (os_id == "A" ) { |
271 | system_info_->os = "Android" ; |
272 | system_info_->os_short = "android" ; |
273 | modules_->SetEnableModuleShrink(true); |
274 | } |
275 | |
276 | // OS line also contains release and version for future use. |
277 | } else if ((pos = line.find(kStackKey)) != string::npos) { |
278 | if (line.find(kStackFirstLineKey) != string::npos) { |
279 | // The first line of the stack (S 0 stack header) provides the value of |
280 | // the stack pointer, the start address of the stack being dumped and |
281 | // the length of the stack. We could use it in future to double check |
282 | // that we received all the stack as expected. |
283 | continue; |
284 | } |
285 | string stack_str(line, pos + strlen(kStackKey)); |
286 | std::istringstream stack_tokens(stack_str); |
287 | string start_addr_str; |
288 | string raw_content; |
289 | stack_tokens >> start_addr_str; |
290 | stack_tokens >> raw_content; |
291 | uint64_t start_addr = HexStrToL<uint64_t>(start_addr_str); |
292 | |
293 | if (stack_start != 0) { |
294 | // Verify that the stack chunks in the microdump are contiguous. |
295 | assert(start_addr == stack_start + stack_content.size()); |
296 | } else { |
297 | stack_start = start_addr; |
298 | } |
299 | std::vector<uint8_t> chunk = ParseHexBuf(raw_content); |
300 | stack_content.insert(stack_content.end(), chunk.begin(), chunk.end()); |
301 | |
302 | } else if ((pos = line.find(kCpuKey)) != string::npos) { |
303 | string cpu_state_str(line, pos + strlen(kCpuKey)); |
304 | std::vector<uint8_t> cpu_state_raw = ParseHexBuf(cpu_state_str); |
305 | if (strcmp(arch.c_str(), kArmArchitecture) == 0) { |
306 | if (cpu_state_raw.size() != sizeof(MDRawContextARM)) { |
307 | std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
308 | << " bytes instead of " << sizeof(MDRawContextARM) |
309 | << std::endl; |
310 | continue; |
311 | } |
312 | MDRawContextARM* arm = new MDRawContextARM(); |
313 | memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size()); |
314 | context_->SetContextARM(arm); |
315 | } else if (strcmp(arch.c_str(), kArm64Architecture) == 0) { |
316 | if (cpu_state_raw.size() == sizeof(MDRawContextARM64)) { |
317 | MDRawContextARM64* arm = new MDRawContextARM64(); |
318 | memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size()); |
319 | context_->SetContextARM64(arm); |
320 | } else if (cpu_state_raw.size() == sizeof(MDRawContextARM64_Old)) { |
321 | MDRawContextARM64_Old old_arm; |
322 | memcpy(&old_arm, &cpu_state_raw[0], cpu_state_raw.size()); |
323 | MDRawContextARM64* new_arm = new MDRawContextARM64(); |
324 | ConvertOldARM64Context(old_arm, new_arm); |
325 | context_->SetContextARM64(new_arm); |
326 | } else { |
327 | std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
328 | << " bytes instead of " << sizeof(MDRawContextARM64) |
329 | << std::endl; |
330 | continue; |
331 | } |
332 | } else if (strcmp(arch.c_str(), kX86Architecture) == 0) { |
333 | if (cpu_state_raw.size() != sizeof(MDRawContextX86)) { |
334 | std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
335 | << " bytes instead of " << sizeof(MDRawContextX86) |
336 | << std::endl; |
337 | continue; |
338 | } |
339 | MDRawContextX86* x86 = new MDRawContextX86(); |
340 | memcpy(x86, &cpu_state_raw[0], cpu_state_raw.size()); |
341 | context_->SetContextX86(x86); |
342 | } else if (strcmp(arch.c_str(), kMipsArchitecture) == 0) { |
343 | if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) { |
344 | std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
345 | << " bytes instead of " << sizeof(MDRawContextMIPS) |
346 | << std::endl; |
347 | continue; |
348 | } |
349 | MDRawContextMIPS* mips32 = new MDRawContextMIPS(); |
350 | memcpy(mips32, &cpu_state_raw[0], cpu_state_raw.size()); |
351 | context_->SetContextMIPS(mips32); |
352 | } else if (strcmp(arch.c_str(), kMips64Architecture) == 0) { |
353 | if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) { |
354 | std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
355 | << " bytes instead of " << sizeof(MDRawContextMIPS) |
356 | << std::endl; |
357 | continue; |
358 | } |
359 | MDRawContextMIPS* mips64 = new MDRawContextMIPS(); |
360 | memcpy(mips64, &cpu_state_raw[0], cpu_state_raw.size()); |
361 | context_->SetContextMIPS64(mips64); |
362 | } else { |
363 | std::cerr << "Unsupported architecture: " << arch << std::endl; |
364 | } |
365 | } else if ((pos = line.find(kCrashReasonKey)) != string::npos) { |
366 | string crash_reason_str(line, pos + strlen(kCrashReasonKey)); |
367 | std::istringstream crash_reason_tokens(crash_reason_str); |
368 | string signal; |
369 | string address; |
370 | crash_reason_tokens >> signal; |
371 | crash_reason_tokens >> crash_reason_; |
372 | crash_reason_tokens >> address; |
373 | crash_address_ = HexStrToL<uint64_t>(address); |
374 | } else if ((pos = line.find(kGpuKey)) != string::npos) { |
375 | string gpu_str(line, pos + strlen(kGpuKey)); |
376 | if (strcmp(gpu_str.c_str(), kGpuUnknown) != 0) { |
377 | std::istringstream gpu_tokens(gpu_str); |
378 | std::getline(gpu_tokens, system_info_->gl_version, '|'); |
379 | std::getline(gpu_tokens, system_info_->gl_vendor, '|'); |
380 | std::getline(gpu_tokens, system_info_->gl_renderer, '|'); |
381 | } |
382 | } else if ((pos = line.find(kMmapKey)) != string::npos) { |
383 | string mmap_line(line, pos + strlen(kMmapKey)); |
384 | std::istringstream mmap_tokens(mmap_line); |
385 | string addr, offset, size, identifier, filename; |
386 | mmap_tokens >> addr; |
387 | mmap_tokens >> offset; |
388 | mmap_tokens >> size; |
389 | mmap_tokens >> identifier; |
390 | mmap_tokens >> filename; |
391 | |
392 | modules_->Add(new BasicCodeModule( |
393 | HexStrToL<uint64_t>(addr), // base_address |
394 | HexStrToL<uint64_t>(size), // size |
395 | filename, // code_file |
396 | identifier, // code_identifier |
397 | filename, // debug_file |
398 | identifier, // debug_identifier |
399 | "" )); // version |
400 | } |
401 | } |
402 | stack_region_->Init(stack_start, stack_content); |
403 | } |
404 | |
405 | } // namespace google_breakpad |
406 | |