/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "crash_collector" #include "coredump_writer.h" #include #include #include #include #include #include #include // From external/google-breakpad. #include "common/linux/elf_core_dump.h" namespace { const size_t kMaxCoredumpSize = 256 * 1024 * 1024; int64_t GetFreeDiskSpace(const std::string& path) { struct statvfs stats; if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &stats)) != 0) { ALOGE("statvfs() failed. errno = %d", errno); return -1; } return static_cast(stats.f_bavail) * stats.f_frsize; } bool Seek(int fd, off_t offset) { return lseek(fd, offset, SEEK_SET) == offset; } template T GetValueFromNote(const google_breakpad::ElfCoreDump::Note& note, size_t offset, T default_value) { const T* p = note.GetDescription().GetData(offset); return p ? *p : default_value; } } // namespace class CoredumpWriter::FdReader { public: explicit FdReader(int fd) : fd_(fd), bytes_read_(0) {} // Reads the given number of bytes. bool Read(void* buf, size_t num_bytes) { if (!android::base::ReadFully(fd_, buf, num_bytes)) return false; bytes_read_ += num_bytes; return true; } // Reads the given number of bytes and writes it to fd_dest. bool CopyTo(int fd_dest, size_t num_bytes) { const size_t kBufSize = 32768; char buf[kBufSize]; while (num_bytes > 0) { int rv = TEMP_FAILURE_RETRY( read(fd_, buf, std::min(kBufSize, num_bytes))); if (rv == 0) break; if (rv == -1) return false; if (fd_dest != -1 && !android::base::WriteFully(fd_dest, buf, rv)) return false; num_bytes -= rv; bytes_read_ += rv; } return num_bytes == 0; } // Reads data and discards it to get to the specified position. bool Seek(size_t offset) { if (offset < bytes_read_) // Cannot move backward. return false; return CopyTo(-1, offset - bytes_read_); } private: int fd_; size_t bytes_read_; DISALLOW_COPY_AND_ASSIGN(FdReader); }; CoredumpWriter::CoredumpWriter(int fd_src, const std::string& coredump_filename, const std::string& proc_files_dir) : fd_src_(fd_src), coredump_filename_(coredump_filename), proc_files_dir_(proc_files_dir) { } CoredumpWriter::~CoredumpWriter() { } ssize_t CoredumpWriter::WriteCoredump() { android::base::unique_fd fd_dest( TEMP_FAILURE_RETRY(open(coredump_filename_.c_str(), O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR))); if (fd_dest == -1) { ALOGE("Failed to open: %s, errno = %d", coredump_filename_.c_str(), errno); return -1; } ssize_t result = WriteCoredumpToFD(fd_dest); fd_dest.reset(-1); if (result == -1) unlink(coredump_filename_.c_str()); return result; } ssize_t CoredumpWriter::WriteCoredumpToFD(int fd_dest) { // Input coredump is generated by kernel's fs/binfmt_elf.c and formatted like: // // ELF Header // Program Header 1 // Program Header 2 // ... // Program Header n // Segment 1 (This segment's type should be PT_NOTE) // Segment 2 // ... // Segment n // First, read ELF Header, all program headers, and the first segment whose // type is PT_NOTE. FdReader reader(fd_src_); Ehdr elf_header; std::vector program_headers; std::vector note_buf; if (!ReadUntilNote(&reader, &elf_header, &program_headers, ¬e_buf)) { return -1; } // Get a set of address ranges occupied by mapped files from NOTE. FileMappings file_mappings; if (!GetFileMappings(note_buf, &file_mappings)) { return -1; } // Filter out segments backed by mapped files as they are useless when // generating minidump. std::vector program_headers_filtered; FilterSegments(program_headers, file_mappings, &program_headers_filtered); // Calculate the coredump size limit. const int64_t free_disk_space = GetFreeDiskSpace(coredump_filename_); if (free_disk_space < 0) { return -1; } coredump_size_limit_ = std::min(static_cast(free_disk_space / 20), kMaxCoredumpSize); // Calculate the output file size. expected_coredump_size_ = program_headers_filtered.back().p_offset + program_headers_filtered.back().p_filesz; if (expected_coredump_size_ > coredump_size_limit_) { ALOGE("Coredump too large: %zu", expected_coredump_size_); return -1; } // Write proc files. if (!WriteAuxv(note_buf, proc_files_dir_ + "/auxv") || !WriteMaps(program_headers, file_mappings, proc_files_dir_ + "/maps")) { return -1; } // Write ELF header. if (!android::base::WriteFully(fd_dest, &elf_header, sizeof(elf_header))) { ALOGE("Failed to write ELF header."); return -1; } // Write program headers. for (size_t i = 0; i < program_headers_filtered.size(); ++i) { const Phdr& program_header = program_headers_filtered[i]; const size_t offset = sizeof(elf_header) + i * elf_header.e_phentsize; if (!Seek(fd_dest, offset) || !android::base::WriteFully(fd_dest, &program_header, sizeof(program_header))) { ALOGE("Failed to write program header: i = %zu", i); return -1; } } // Write NOTE segment. if (!Seek(fd_dest, program_headers_filtered[0].p_offset) || !android::base::WriteFully(fd_dest, note_buf.data(), note_buf.size())) { ALOGE("Failed to write NOTE."); return -1; } // Read all remaining segments and write some of them. for (size_t i = 1; i < program_headers_filtered.size(); ++i) { const Phdr& program_header = program_headers_filtered[i]; if (program_header.p_filesz > 0) { const Phdr& program_header_original = program_headers[i]; if (!reader.Seek(program_header_original.p_offset)) { ALOGE("Failed to seek segment: i = %zu", i); return -1; } if (!Seek(fd_dest, program_header.p_offset) || !reader.CopyTo(fd_dest, program_header.p_filesz)) { ALOGE("Failed to write segment: i = %zu", i); return -1; } } } return expected_coredump_size_; } bool CoredumpWriter::ReadUntilNote(FdReader* reader, Ehdr* elf_header, std::vector* program_headers, std::vector* note_buf) { // Read ELF header. if (!reader->Read(elf_header, sizeof(*elf_header)) || memcmp(elf_header->e_ident, ELFMAG, SELFMAG) != 0 || elf_header->e_ident[EI_CLASS] != google_breakpad::ElfCoreDump::kClass || elf_header->e_version != EV_CURRENT || elf_header->e_type != ET_CORE || elf_header->e_ehsize != sizeof(Ehdr) || elf_header->e_phentsize != sizeof(Phdr)) { ALOGE("Failed to read ELF header."); return false; } // Read program headers; program_headers->resize(elf_header->e_phnum); if (!reader->Seek(elf_header->e_phoff) || !reader->Read(program_headers->data(), sizeof(Phdr) * program_headers->size())) { ALOGE("Failed to read program headers."); return false; } // The first segment should be NOTE. if (program_headers->size() < 1 || (*program_headers)[0].p_type != PT_NOTE) { ALOGE("Failed to locate NOTE."); return false; } const Phdr& note_program_header = (*program_headers)[0]; // Read NOTE segment. note_buf->resize(note_program_header.p_filesz); if (!reader->Seek(note_program_header.p_offset) || !reader->Read(note_buf->data(), note_buf->size())) { ALOGE("Failed to read NOTE."); return false; } return true; } bool CoredumpWriter::GetFileMappings(const std::vector& note_buf, FileMappings* file_mappings) { // Locate FILE note. google_breakpad::ElfCoreDump::Note note( google_breakpad::MemoryRange(note_buf.data(), note_buf.size())); while (note.IsValid() && note.GetType() != NT_FILE) { note = note.GetNextNote(); } if (!note.IsValid()) { ALOGE("Failed to locate NT_FILE."); return false; } // NT_FILE note format: (see kernel's fs/binfmt_elf.c for details) // Number of mapped files // Page size // Start address of file 1 // End address of file 1 // Offset of file 1 // Start address of file 2 // ... // Offset of file n // File name 1 (null-terminated) // File name 2 // ... // File name n const long kInvalidValue = -1; const long file_count = GetValueFromNote(note, 0, kInvalidValue); const long page_size = GetValueFromNote(note, sizeof(long), kInvalidValue); if (file_count == kInvalidValue || page_size == kInvalidValue) { ALOGE("Invalid FILE note."); return false; } // Read contents of FILE note. size_t filename_pos = sizeof(long) * (2 + 3 * file_count); for (long i = 0; i < file_count; ++i) { const long start = GetValueFromNote( note, sizeof(long) * (2 + 3 * i), kInvalidValue); const long end = GetValueFromNote( note, sizeof(long) * (2 + 3 * i + 1), kInvalidValue); const long offset = GetValueFromNote( note, sizeof(long) * (2 + 3 * i + 2), kInvalidValue); if (start == kInvalidValue || end == kInvalidValue || offset == kInvalidValue) { ALOGE("Invalid FILE Note."); return false; } // Add a new mapping. FileInfo& info = (*file_mappings)[std::make_pair(start, end)]; info.offset = offset * page_size; // Read file name. while (true) { const char c = GetValueFromNote(note, filename_pos++, 0); if (!c) break; info.path.push_back(c); } } return true; } void CoredumpWriter::FilterSegments( const std::vector& program_headers, const FileMappings& file_mappings, std::vector* program_headers_filtered) { program_headers_filtered->resize(program_headers.size()); // The first segment is NOTE. Use the original data unchanged. (*program_headers_filtered)[0] = program_headers[0]; for (size_t i = 1; i < program_headers.size(); ++i) { Phdr& out = (*program_headers_filtered)[i]; out = program_headers[i]; // If the type is PT_LOAD and the range is found in the set, it means the // segment is backed by a file. So it can be excluded as it doesn't cotnain // stack data useful to generate minidump. const FileRange range(out.p_vaddr, out.p_vaddr + out.p_memsz); if (out.p_type == PT_LOAD && file_mappings.count(range)) { out.p_filesz = 0; } // Calculate offset. const Phdr& prev_program_header = (*program_headers_filtered)[i - 1]; out.p_offset = prev_program_header.p_offset + prev_program_header.p_filesz; // Offset alignment. if (out.p_align != 0 && out.p_offset % out.p_align != 0) { out.p_offset += out.p_align - out.p_offset % out.p_align; } } } bool CoredumpWriter::WriteAuxv(const std::vector& note_buf, const std::string& output_path) { // Locate AUXV note. google_breakpad::ElfCoreDump::Note note( google_breakpad::MemoryRange(note_buf.data(), note_buf.size())); while (note.IsValid() && note.GetType() != NT_AUXV) { note = note.GetNextNote(); } if (!note.IsValid()) { ALOGE("Failed to locate NT_AUXV."); return false; } android::base::unique_fd fd(TEMP_FAILURE_RETRY(open( output_path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_EXCL, S_IRUSR | S_IWUSR))); if (fd == -1) { ALOGE("Failed to open %s", output_path.c_str()); return false; } // The contents of NT_AUXV is in the same format as that of /proc/[pid]/auxv. return android::base::WriteFully( fd, note.GetDescription().data(), note.GetDescription().length()); } bool CoredumpWriter::WriteMaps(const std::vector& program_headers, const FileMappings& file_mappings, const std::string& output_path) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open( output_path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_EXCL, S_IRUSR | S_IWUSR))); if (fd == -1) { ALOGE("Failed to open %s", output_path.c_str()); return false; } for (const auto& program_header : program_headers) { if (program_header.p_type != PT_LOAD) continue; const FileRange range(program_header.p_vaddr, program_header.p_vaddr + program_header.p_memsz); // If a mapping is found for the range, the range is mapped to a file. const auto it = file_mappings.find(range); const long offset = it != file_mappings.end() ? it->second.offset : 0; const std::string path = it != file_mappings.end() ? it->second.path : ""; const int kBufSize = 1024; char buf[kBufSize]; const int len = snprintf( buf, kBufSize, "%08lx-%08lx %c%c%c%c %08lx %02x:%02x %d %s\n", range.first, range.second, program_header.p_flags & PF_R ? 'r' : '-', program_header.p_flags & PF_W ? 'w' : '-', program_header.p_flags & PF_X ? 'x' : '-', 'p', // Fake value: We can't know if the mapping is shared or private. offset, 0, // Fake device (major) value. 0, // Fake device (minor) value. 0, // Fake inode value. path.c_str()); if (len < 0 || len > kBufSize || !android::base::WriteFully(fd, buf, len)) { return false; } } return true; }