| // Copyright (c) 2014 Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // microdump.cc: A microdump reader. |
| // |
| // See microdump.h for documentation. |
| |
| #include "google_breakpad/processor/microdump.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "google_breakpad/common/minidump_cpu_arm.h" |
| #include "google_breakpad/processor/code_module.h" |
| #include "processor/basic_code_module.h" |
| #include "processor/convert_old_arm64_context.h" |
| #include "processor/linked_ptr.h" |
| #include "processor/logging.h" |
| #include "processor/range_map-inl.h" |
| |
| namespace { |
| static const char kGoogleBreakpadKey[] = "google-breakpad"; |
| static const char kMicrodumpBegin[] = "-----BEGIN BREAKPAD MICRODUMP-----"; |
| static const char kMicrodumpEnd[] = "-----END BREAKPAD MICRODUMP-----"; |
| static const char kOsKey[] = ": O "; |
| static const char kCpuKey[] = ": C "; |
| static const char kCrashReasonKey[] = ": R "; |
| static const char kGpuKey[] = ": G "; |
| static const char kMmapKey[] = ": M "; |
| static const char kStackKey[] = ": S "; |
| static const char kStackFirstLineKey[] = ": S 0 "; |
| static const char kArmArchitecture[] = "arm"; |
| static const char kArm64Architecture[] = "arm64"; |
| static const char kX86Architecture[] = "x86"; |
| static const char kMipsArchitecture[] = "mips"; |
| static const char kMips64Architecture[] = "mips64"; |
| static const char kGpuUnknown[] = "UNKNOWN"; |
| |
| template<typename T> |
| T HexStrToL(const string& str) { |
| uint64_t res = 0; |
| std::istringstream ss(str); |
| ss >> std::hex >> res; |
| return static_cast<T>(res); |
| } |
| |
| std::vector<uint8_t> ParseHexBuf(const string& str) { |
| std::vector<uint8_t> buf; |
| for (size_t i = 0; i < str.length(); i += 2) { |
| buf.push_back(HexStrToL<uint8_t>(str.substr(i, 2))); |
| } |
| return buf; |
| } |
| |
| bool GetLine(std::istringstream* istream, string* str) { |
| if (std::getline(*istream, *str)) { |
| // Trim any trailing newline from the end of the line. Allows us |
| // to seamlessly handle both Windows/DOS and Unix formatted input. The |
| // adb tool generally writes logcat dumps in Windows/DOS format. |
| if (!str->empty() && str->at(str->size() - 1) == '\r') { |
| str->erase(str->size() - 1); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace google_breakpad { |
| |
| // |
| // MicrodumpModules |
| // |
| |
| void MicrodumpModules::Add(const CodeModule* module) { |
| linked_ptr<const CodeModule> module_ptr(module); |
| if (!map_.StoreRange(module->base_address(), module->size(), module_ptr)) { |
| BPLOG(ERROR) << "Module " << module->code_file() << |
| " could not be stored"; |
| } |
| } |
| |
| void MicrodumpModules::SetEnableModuleShrink(bool is_enabled) { |
| map_.SetMergeStrategy(is_enabled ? MergeRangeStrategy::kTruncateUpper |
| : MergeRangeStrategy::kExclusiveRanges); |
| } |
| |
| // |
| // MicrodumpContext |
| // |
| |
| void MicrodumpContext::SetContextARM(MDRawContextARM* arm) { |
| DumpContext::SetContextFlags(MD_CONTEXT_ARM); |
| DumpContext::SetContextARM(arm); |
| valid_ = true; |
| } |
| |
| void MicrodumpContext::SetContextARM64(MDRawContextARM64* arm64) { |
| DumpContext::SetContextFlags(MD_CONTEXT_ARM64); |
| DumpContext::SetContextARM64(arm64); |
| valid_ = true; |
| } |
| |
| void MicrodumpContext::SetContextX86(MDRawContextX86* x86) { |
| DumpContext::SetContextFlags(MD_CONTEXT_X86); |
| DumpContext::SetContextX86(x86); |
| valid_ = true; |
| } |
| |
| void MicrodumpContext::SetContextMIPS(MDRawContextMIPS* mips32) { |
| DumpContext::SetContextFlags(MD_CONTEXT_MIPS); |
| DumpContext::SetContextMIPS(mips32); |
| valid_ = true; |
| } |
| |
| void MicrodumpContext::SetContextMIPS64(MDRawContextMIPS* mips64) { |
| DumpContext::SetContextFlags(MD_CONTEXT_MIPS64); |
| DumpContext::SetContextMIPS(mips64); |
| valid_ = true; |
| } |
| |
| |
| // |
| // MicrodumpMemoryRegion |
| // |
| |
| MicrodumpMemoryRegion::MicrodumpMemoryRegion() : base_address_(0) { } |
| |
| void MicrodumpMemoryRegion::Init(uint64_t base_address, |
| const std::vector<uint8_t>& contents) { |
| base_address_ = base_address; |
| contents_ = contents; |
| } |
| |
| uint64_t MicrodumpMemoryRegion::GetBase() const { return base_address_; } |
| |
| uint32_t MicrodumpMemoryRegion::GetSize() const { return contents_.size(); } |
| |
| bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
| uint8_t* value) const { |
| return GetMemoryLittleEndian(address, value); |
| } |
| |
| bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
| uint16_t* value) const { |
| return GetMemoryLittleEndian(address, value); |
| } |
| |
| bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
| uint32_t* value) const { |
| return GetMemoryLittleEndian(address, value); |
| } |
| |
| bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address, |
| uint64_t* value) const { |
| return GetMemoryLittleEndian(address, value); |
| } |
| |
| template<typename ValueType> |
| bool MicrodumpMemoryRegion::GetMemoryLittleEndian(uint64_t address, |
| ValueType* value) const { |
| if (address < base_address_ || |
| address - base_address_ + sizeof(ValueType) > contents_.size()) |
| return false; |
| ValueType v = 0; |
| uint64_t start = address - base_address_; |
| // The loop condition is odd, but it's correct for size_t. |
| for (size_t i = sizeof(ValueType) - 1; i < sizeof(ValueType); i--) |
| v = (v << 8) | static_cast<uint8_t>(contents_[start + i]); |
| *value = v; |
| return true; |
| } |
| |
| void MicrodumpMemoryRegion::Print() const { |
| // Not reached, just needed to honor the base class contract. |
| assert(false); |
| } |
| |
| // |
| // Microdump |
| // |
| Microdump::Microdump(const string& contents) |
| : context_(new MicrodumpContext()), |
| stack_region_(new MicrodumpMemoryRegion()), |
| modules_(new MicrodumpModules()), |
| system_info_(new SystemInfo()), |
| crash_reason_(), |
| crash_address_(0u) { |
| assert(!contents.empty()); |
| |
| bool in_microdump = false; |
| string line; |
| uint64_t stack_start = 0; |
| std::vector<uint8_t> stack_content; |
| string arch; |
| |
| std::istringstream stream(contents); |
| while (GetLine(&stream, &line)) { |
| if (line.find(kGoogleBreakpadKey) == string::npos) { |
| continue; |
| } |
| if (line.find(kMicrodumpBegin) != string::npos) { |
| in_microdump = true; |
| continue; |
| } |
| if (!in_microdump) { |
| continue; |
| } |
| if (line.find(kMicrodumpEnd) != string::npos) { |
| break; |
| } |
| |
| size_t pos; |
| if ((pos = line.find(kOsKey)) != string::npos) { |
| string os_str(line, pos + strlen(kOsKey)); |
| std::istringstream os_tokens(os_str); |
| string os_id; |
| string num_cpus; |
| string os_version; |
| // This reflect the actual HW arch and might not match the arch emulated |
| // for the execution (e.g., running a 32-bit binary on a 64-bit cpu). |
| string hw_arch; |
| |
| os_tokens >> os_id; |
| os_tokens >> arch; |
| os_tokens >> num_cpus; |
| os_tokens >> hw_arch; |
| GetLine(&os_tokens, &os_version); |
| os_version.erase(0, 1); // remove leading space. |
| |
| system_info_->cpu = arch; |
| system_info_->cpu_count = HexStrToL<uint8_t>(num_cpus); |
| system_info_->os_version = os_version; |
| |
| if (os_id == "L") { |
| system_info_->os = "Linux"; |
| system_info_->os_short = "linux"; |
| } else if (os_id == "A") { |
| system_info_->os = "Android"; |
| system_info_->os_short = "android"; |
| modules_->SetEnableModuleShrink(true); |
| } |
| |
| // OS line also contains release and version for future use. |
| } else if ((pos = line.find(kStackKey)) != string::npos) { |
| if (line.find(kStackFirstLineKey) != string::npos) { |
| // The first line of the stack (S 0 stack header) provides the value of |
| // the stack pointer, the start address of the stack being dumped and |
| // the length of the stack. We could use it in future to double check |
| // that we received all the stack as expected. |
| continue; |
| } |
| string stack_str(line, pos + strlen(kStackKey)); |
| std::istringstream stack_tokens(stack_str); |
| string start_addr_str; |
| string raw_content; |
| stack_tokens >> start_addr_str; |
| stack_tokens >> raw_content; |
| uint64_t start_addr = HexStrToL<uint64_t>(start_addr_str); |
| |
| if (stack_start != 0) { |
| // Verify that the stack chunks in the microdump are contiguous. |
| assert(start_addr == stack_start + stack_content.size()); |
| } else { |
| stack_start = start_addr; |
| } |
| std::vector<uint8_t> chunk = ParseHexBuf(raw_content); |
| stack_content.insert(stack_content.end(), chunk.begin(), chunk.end()); |
| |
| } else if ((pos = line.find(kCpuKey)) != string::npos) { |
| string cpu_state_str(line, pos + strlen(kCpuKey)); |
| std::vector<uint8_t> cpu_state_raw = ParseHexBuf(cpu_state_str); |
| if (strcmp(arch.c_str(), kArmArchitecture) == 0) { |
| if (cpu_state_raw.size() != sizeof(MDRawContextARM)) { |
| std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
| << " bytes instead of " << sizeof(MDRawContextARM) |
| << std::endl; |
| continue; |
| } |
| MDRawContextARM* arm = new MDRawContextARM(); |
| memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size()); |
| context_->SetContextARM(arm); |
| } else if (strcmp(arch.c_str(), kArm64Architecture) == 0) { |
| if (cpu_state_raw.size() == sizeof(MDRawContextARM64)) { |
| MDRawContextARM64* arm = new MDRawContextARM64(); |
| memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size()); |
| context_->SetContextARM64(arm); |
| } else if (cpu_state_raw.size() == sizeof(MDRawContextARM64_Old)) { |
| MDRawContextARM64_Old old_arm; |
| memcpy(&old_arm, &cpu_state_raw[0], cpu_state_raw.size()); |
| MDRawContextARM64* new_arm = new MDRawContextARM64(); |
| ConvertOldARM64Context(old_arm, new_arm); |
| context_->SetContextARM64(new_arm); |
| } else { |
| std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
| << " bytes instead of " << sizeof(MDRawContextARM64) |
| << std::endl; |
| continue; |
| } |
| } else if (strcmp(arch.c_str(), kX86Architecture) == 0) { |
| if (cpu_state_raw.size() != sizeof(MDRawContextX86)) { |
| std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
| << " bytes instead of " << sizeof(MDRawContextX86) |
| << std::endl; |
| continue; |
| } |
| MDRawContextX86* x86 = new MDRawContextX86(); |
| memcpy(x86, &cpu_state_raw[0], cpu_state_raw.size()); |
| context_->SetContextX86(x86); |
| } else if (strcmp(arch.c_str(), kMipsArchitecture) == 0) { |
| if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) { |
| std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
| << " bytes instead of " << sizeof(MDRawContextMIPS) |
| << std::endl; |
| continue; |
| } |
| MDRawContextMIPS* mips32 = new MDRawContextMIPS(); |
| memcpy(mips32, &cpu_state_raw[0], cpu_state_raw.size()); |
| context_->SetContextMIPS(mips32); |
| } else if (strcmp(arch.c_str(), kMips64Architecture) == 0) { |
| if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) { |
| std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size() |
| << " bytes instead of " << sizeof(MDRawContextMIPS) |
| << std::endl; |
| continue; |
| } |
| MDRawContextMIPS* mips64 = new MDRawContextMIPS(); |
| memcpy(mips64, &cpu_state_raw[0], cpu_state_raw.size()); |
| context_->SetContextMIPS64(mips64); |
| } else { |
| std::cerr << "Unsupported architecture: " << arch << std::endl; |
| } |
| } else if ((pos = line.find(kCrashReasonKey)) != string::npos) { |
| string crash_reason_str(line, pos + strlen(kCrashReasonKey)); |
| std::istringstream crash_reason_tokens(crash_reason_str); |
| string signal; |
| string address; |
| crash_reason_tokens >> signal; |
| crash_reason_tokens >> crash_reason_; |
| crash_reason_tokens >> address; |
| crash_address_ = HexStrToL<uint64_t>(address); |
| } else if ((pos = line.find(kGpuKey)) != string::npos) { |
| string gpu_str(line, pos + strlen(kGpuKey)); |
| if (strcmp(gpu_str.c_str(), kGpuUnknown) != 0) { |
| std::istringstream gpu_tokens(gpu_str); |
| std::getline(gpu_tokens, system_info_->gl_version, '|'); |
| std::getline(gpu_tokens, system_info_->gl_vendor, '|'); |
| std::getline(gpu_tokens, system_info_->gl_renderer, '|'); |
| } |
| } else if ((pos = line.find(kMmapKey)) != string::npos) { |
| string mmap_line(line, pos + strlen(kMmapKey)); |
| std::istringstream mmap_tokens(mmap_line); |
| string addr, offset, size, identifier, filename; |
| mmap_tokens >> addr; |
| mmap_tokens >> offset; |
| mmap_tokens >> size; |
| mmap_tokens >> identifier; |
| mmap_tokens >> filename; |
| |
| modules_->Add(new BasicCodeModule( |
| HexStrToL<uint64_t>(addr), // base_address |
| HexStrToL<uint64_t>(size), // size |
| filename, // code_file |
| identifier, // code_identifier |
| filename, // debug_file |
| identifier, // debug_identifier |
| "")); // version |
| } |
| } |
| stack_region_->Init(stack_start, stack_content); |
| } |
| |
| } // namespace google_breakpad |