blob: 47fd47e471f85011dbaa34498e9c56fd0ed92d9d [file] [log] [blame]
/*
* 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 <errno.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <log/log.h>
// 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<int64_t>(stats.f_bavail) * stats.f_frsize;
}
bool Seek(int fd, off_t offset) {
return lseek(fd, offset, SEEK_SET) == offset;
}
template<typename T>
T GetValueFromNote(const google_breakpad::ElfCoreDump::Note& note,
size_t offset,
T default_value) {
const T* p = note.GetDescription().GetData<T>(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<Phdr> program_headers;
std::vector<char> note_buf;
if (!ReadUntilNote(&reader, &elf_header, &program_headers, &note_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<Phdr> 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<size_t>(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<Phdr>* program_headers,
std::vector<char>* 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<char>& 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<long>(note, 0, kInvalidValue);
const long page_size = GetValueFromNote<long>(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<long>(
note, sizeof(long) * (2 + 3 * i), kInvalidValue);
const long end = GetValueFromNote<long>(
note, sizeof(long) * (2 + 3 * i + 1), kInvalidValue);
const long offset = GetValueFromNote<long>(
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<char>(note, filename_pos++, 0);
if (!c)
break;
info.path.push_back(c);
}
}
return true;
}
void CoredumpWriter::FilterSegments(
const std::vector<Phdr>& program_headers,
const FileMappings& file_mappings,
std::vector<Phdr>* 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<char>& 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<Phdr>& 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;
}