Enable reading notes from ELF images

Bug: crashpad:30
Change-Id: Ie6c594b05c6d39a869ed30b7a7b49e6a6301cc65
Reviewed-on: https://chromium-review.googlesource.com/792539
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-12-05 11:21:14 -08:00 committed by Commit Bot
parent 741a84a298
commit 7a0daa6989
7 changed files with 391 additions and 28 deletions

View File

@ -17,6 +17,8 @@
#include_next <elf.h>
#include <android/api-level.h>
#if !defined(ELF32_ST_VISIBILITY)
#define ELF32_ST_VISIBILITY(other) ((other) & 0x3)
#endif
@ -35,4 +37,22 @@
#define STT_TLS 6
#endif
// ELF note header types are normally provided by <linux/elf.h>. While unified
// headers include <linux/elf.h> in <elf.h>, traditional headers do not, prior
// to API 21. <elf.h> and <linux/elf.h> can't both be included in the same
// translation unit due to collisions, so we provide these types here.
#if __ANDROID_API__ < 21 && !defined(__ANDROID_API_N__)
typedef struct {
Elf32_Word n_namesz;
Elf32_Word n_descsz;
Elf32_Word n_type;
} Elf32_Nhdr;
typedef struct {
Elf64_Word n_namesz;
Elf64_Word n_descsz;
Elf64_Word n_type;
} Elf64_Nhdr;
#endif // __ANDROID_API__ < 21 && !defined(NT_PRSTATUS)
#endif // CRASHPAD_COMPAT_ANDROID_ELF_H_

View File

@ -16,7 +16,9 @@
#include <stddef.h>
#include <algorithm>
#include <limits>
#include <utility>
#include <vector>
#include "base/logging.h"
@ -36,6 +38,13 @@ class ElfImageReader::ProgramHeaderTable {
virtual bool GetPreferredLoadedMemoryRange(VMAddress* address,
VMSize* size) const = 0;
// Locate the next PT_NOTE segment starting at segment index start_index. If a
// PT_NOTE segment is found, start_index is set to the next index after the
// found segment.
virtual bool GetNoteSegment(size_t* start_index,
VMAddress* address,
VMSize* size) const = 0;
protected:
ProgramHeaderTable() {}
};
@ -150,6 +159,21 @@ class ElfImageReader::ProgramHeaderTableSpecific
return false;
}
bool GetNoteSegment(size_t* start_index,
VMAddress* address,
VMSize* size) const override {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
for (size_t index = *start_index; index < table_.size(); ++index) {
if (table_[index].p_type == PT_NOTE) {
*start_index = index + 1;
*address = table_[index].p_vaddr;
*size = table_[index].p_memsz;
return true;
}
}
return false;
}
private:
std::vector<PhdrType> table_;
InitializationStateDcheck initialized_;
@ -157,6 +181,150 @@ class ElfImageReader::ProgramHeaderTableSpecific
DISALLOW_COPY_AND_ASSIGN(ProgramHeaderTableSpecific<PhdrType>);
};
ElfImageReader::NoteReader::~NoteReader() = default;
ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote(
std::string* name,
NoteType* type,
std::string* desc) {
if (!is_valid_) {
LOG(ERROR) << "invalid note reader";
return Result::kError;
}
Result result = Result::kError;
do {
while (current_address_ == segment_end_address_) {
VMSize segment_size;
if (!phdr_table_->GetNoteSegment(
&phdr_index_, &current_address_, &segment_size)) {
return Result::kNoMoreNotes;
}
current_address_ += elf_reader_->GetLoadBias();
segment_end_address_ = current_address_ + segment_size;
segment_range_ = std::make_unique<ProcessMemoryRange>();
if (!segment_range_->Initialize(*range_) ||
!segment_range_->RestrictRange(current_address_, segment_size)) {
return Result::kError;
}
}
retry_ = false;
result = range_->Is64Bit() ? ReadNote<Elf64_Nhdr>(name, type, desc)
: ReadNote<Elf32_Nhdr>(name, type, desc);
} while (retry_);
if (result == Result::kSuccess) {
return Result::kSuccess;
}
is_valid_ = false;
return Result::kError;
}
ElfImageReader::NoteReader::NoteReader(const ElfImageReader* elf_reader,
const ProcessMemoryRange* range,
const ProgramHeaderTable* phdr_table,
ssize_t max_note_size,
const std::string& name_filter,
NoteType type_filter,
bool use_filter)
: current_address_(0),
segment_end_address_(0),
elf_reader_(elf_reader),
range_(range),
phdr_table_(phdr_table),
segment_range_(),
phdr_index_(0),
max_note_size_(max_note_size),
name_filter_(name_filter),
type_filter_(type_filter),
use_filter_(use_filter),
is_valid_(true),
retry_(false) {}
template <typename NhdrType>
ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote(
std::string* name,
NoteType* type,
std::string* desc) {
static_assert(sizeof(*type) >= sizeof(NhdrType::n_namesz),
"Note field size mismatch");
DCHECK_LT(current_address_, segment_end_address_);
NhdrType note_info;
if (!segment_range_->Read(current_address_, sizeof(note_info), &note_info)) {
return Result::kError;
}
current_address_ += sizeof(note_info);
constexpr size_t align = sizeof(note_info.n_namesz);
#define PAD(x) ((x) + align - 1 & ~(align - 1))
size_t padded_namesz = PAD(note_info.n_namesz);
size_t padded_descsz = PAD(note_info.n_descsz);
size_t note_size = padded_namesz + padded_descsz;
// Notes typically have 4-byte alignment. However, .note.android.ident may
// inadvertently use 2-byte alignment.
// https://android-review.googlesource.com/c/platform/bionic/+/554986/
// We can still find .note.android.ident if it appears first in a note segment
// but there may be 4-byte aligned notes following it. If this note was
// aligned at less than 4-bytes, expect that the next note will be aligned at
// 4-bytes and add extra padding, if necessary.
VMAddress end_of_note =
std::min(PAD(current_address_ + note_size), segment_end_address_);
#undef PAD
if (max_note_size_ >= 0 && note_size > static_cast<size_t>(max_note_size_)) {
current_address_ = end_of_note;
retry_ = true;
return Result::kError;
}
if (use_filter_ && note_info.n_type != type_filter_) {
current_address_ = end_of_note;
retry_ = true;
return Result::kError;
}
std::string local_name(note_info.n_namesz, '\0');
if (!segment_range_->Read(
current_address_, note_info.n_namesz, &local_name[0])) {
return Result::kError;
}
if (!local_name.empty()) {
if (local_name.back() != '\0') {
LOG(ERROR) << "unterminated note name";
return Result::kError;
}
local_name.pop_back();
}
if (use_filter_ && local_name != name_filter_) {
current_address_ = end_of_note;
retry_ = true;
return Result::kError;
}
current_address_ += padded_namesz;
std::string local_desc(note_info.n_descsz, '\0');
if (!segment_range_->Read(
current_address_, note_info.n_descsz, &local_desc[0])) {
return Result::kError;
}
current_address_ = end_of_note;
if (name) {
name->swap(local_name);
}
if (type) {
*type = note_info.n_type;
}
desc->swap(local_desc);
return Result::kSuccess;
}
ElfImageReader::ElfImageReader()
: header_64_(),
ehdr_address_(0),
@ -467,4 +635,18 @@ bool ElfImageReader::GetAddressFromDynamicArray(uint64_t tag,
return true;
}
std::unique_ptr<ElfImageReader::NoteReader> ElfImageReader::Notes(
ssize_t max_note_size) {
return std::make_unique<NoteReader>(
this, &memory_, program_headers_.get(), max_note_size);
}
std::unique_ptr<ElfImageReader::NoteReader>
ElfImageReader::NotesWithNameAndType(const std::string& name,
NoteReader::NoteType type,
ssize_t max_note_size) {
return std::make_unique<NoteReader>(
this, &memory_, program_headers_.get(), max_note_size, name, type, true);
}
} // namespace crashpad

View File

@ -17,6 +17,7 @@
#include <elf.h>
#include <stdint.h>
#include <sys/types.h>
#include <memory>
#include <string>
@ -35,7 +36,77 @@ namespace crashpad {
//!
//! This class is capable of reading both 32-bit and 64-bit images.
class ElfImageReader {
private:
class ProgramHeaderTable;
public:
//! \brief This class enables reading note segments from an ELF image.
//!
//! Objects of this class should be created by calling
//! ElfImageReader::Notes() or ElfImageReader::NotesWithNameAndType().
class NoteReader {
public:
~NoteReader();
//! \brief The return value for NextNote().
enum class Result {
//! \brief An error occurred. The NoteReader is invalidated and message is
//! logged.
kError,
//! \brief A note was found.
kSuccess,
//! \brief No more notes were found.
kNoMoreNotes,
};
//! \brief A type large enough to hold a note type, potentially across
//! bitness.
using NoteType = decltype(Elf64_Nhdr::n_type);
//! \brief Searches for the next note in the image.
//!
//! \param[out] name The name of the note owner, if not `nullptr`.
//! \param[out] type A type for the note, if not `nullptr`.
//! \param[out] desc The note descriptor.
//! \return a #Result value. \a name, \a type, and \a desc are only valid if
//! this method returns Result::kSuccess.
Result NextNote(std::string* name, NoteType* type, std::string* desc);
// private
NoteReader(const ElfImageReader* elf_reader_,
const ProcessMemoryRange* range,
const ProgramHeaderTable* phdr_table,
ssize_t max_note_size,
const std::string& name_filter = std::string(),
NoteType type_filter = 0,
bool use_filter = false);
private:
// Reads the next note at the current segment address. Sets retry_ to true
// and returns kError if use_filter_ is true and the note's name and type do
// not match name_filter_ and type_filter_.
template <typename T>
Result ReadNote(std::string* name, NoteType* type, std::string* desc);
VMAddress current_address_;
VMAddress segment_end_address_;
const ElfImageReader* elf_reader_; // weak
const ProcessMemoryRange* range_; // weak
const ProgramHeaderTable* phdr_table_; // weak
std::unique_ptr<ProcessMemoryRange> segment_range_;
size_t phdr_index_;
ssize_t max_note_size_;
std::string name_filter_;
NoteType type_filter_;
bool use_filter_;
bool is_valid_;
bool retry_;
DISALLOW_COPY_AND_ASSIGN(NoteReader);
};
ElfImageReader();
~ElfImageReader();
@ -101,8 +172,37 @@ class ElfImageReader {
//! \return `true` if the debug address was found.
bool GetDebugAddress(VMAddress* debug);
//! \brief Return a NoteReader for this image, which scans all PT_NOTE
//! segments in the image.
//!
//! The returned NoteReader is only valid for the lifetime of the
//! ElfImageReader that created it.
//!
//! \param[in] max_note_size The maximum note size to read. Notes whose
//! combined name, descriptor, and padding size are greater than
//! \a max_note_size will be silently skipped. A \a max_note_size of -1
//! indicates infinite maximum note size.
//! \return A NoteReader object capable of reading notes in this image.
std::unique_ptr<NoteReader> Notes(ssize_t max_note_size);
//! \brief Return a NoteReader for this image, which scans all PT_NOTE
//! segments in the image, filtering by name and type.
//!
//! The returned NoteReader is only valid for the lifetime of the
//! ElfImageReader that created it.
//!
//! \param[in] name The note name to match.
//! \param[in] type The note type to match.
//! \param[in] max_note_size The maximum note size to read. Notes whose
//! combined name, descriptor, and padding size are greater than
//! \a max_note_size will be silently skipped. A \a max_note_size of -1
//! indicates infinite maximum note size.
//! \return A NoteReader object capable of reading notes in this image.
std::unique_ptr<NoteReader> NotesWithNameAndType(const std::string& name,
NoteReader::NoteType type,
ssize_t max_note_size);
private:
class ProgramHeaderTable;
template <typename PhdrType>
class ProgramHeaderTableSpecific;

View File

@ -54,27 +54,17 @@ void LocateExecutable(pid_t pid, bool is_64_bit, VMAddress* elf_address) {
*elf_address = exe_mapping->range.Base();
}
void ExpectElfImageWithSymbol(pid_t pid,
VMAddress address,
bool is_64_bit,
std::string symbol_name,
VMAddress expected_symbol_address) {
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(pid));
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, is_64_bit));
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, address));
void ExpectSymbol(ElfImageReader* reader,
const std::string& symbol_name,
VMAddress expected_symbol_address) {
VMAddress symbol_address;
VMSize symbol_size;
ASSERT_TRUE(
reader.GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size));
reader->GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size));
EXPECT_EQ(symbol_address, expected_symbol_address);
EXPECT_FALSE(
reader.GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size));
reader->GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size));
}
void ReadThisExecutableInTarget(pid_t pid) {
@ -87,12 +77,48 @@ void ReadThisExecutableInTarget(pid_t pid) {
VMAddress elf_address;
LocateExecutable(pid, am_64_bit, &elf_address);
ExpectElfImageWithSymbol(
pid,
elf_address,
am_64_bit,
"ElfImageReaderTestExportedSymbol",
FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol));
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(pid));
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, elf_address));
ExpectSymbol(&reader,
"ElfImageReaderTestExportedSymbol",
FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol));
ElfImageReader::NoteReader::Result result;
std::string note_name;
std::string note_desc;
ElfImageReader::NoteReader::NoteType note_type;
std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(-1);
while ((result = notes->NextNote(&note_name, &note_type, &note_desc)) ==
ElfImageReader::NoteReader::Result::kSuccess) {
}
EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes);
notes = reader.Notes(0);
EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
ElfImageReader::NoteReader::Result::kNoMoreNotes);
// Find the note defined in elf_image_reader_test_note.S.
constexpr char kCrashpadNoteName[] = "Crashpad";
constexpr ElfImageReader::NoteReader::NoteType kCrashpadNoteType = 1;
constexpr uint32_t kCrashpadNoteDesc = 42;
notes = reader.NotesWithNameAndType(kCrashpadNoteName, kCrashpadNoteType, -1);
ASSERT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
ElfImageReader::NoteReader::Result::kSuccess);
EXPECT_EQ(note_name, kCrashpadNoteName);
EXPECT_EQ(note_type, kCrashpadNoteType);
EXPECT_EQ(note_desc.size(), sizeof(kCrashpadNoteDesc));
EXPECT_EQ(*reinterpret_cast<decltype(kCrashpadNoteDesc)*>(&note_desc[0]),
kCrashpadNoteDesc);
EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
ElfImageReader::NoteReader::Result::kNoMoreNotes);
}
// Assumes that libc is loaded at the same address in this process as in the
@ -109,11 +135,15 @@ void ReadLibcInTarget(pid_t pid) {
<< dlerror();
VMAddress elf_address = FromPointerCast<VMAddress>(info.dli_fbase);
ExpectElfImageWithSymbol(pid,
elf_address,
am_64_bit,
"getpid",
FromPointerCast<VMAddress>(getpid));
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(pid));
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, elf_address));
ExpectSymbol(&reader, "getpid", FromPointerCast<VMAddress>(getpid));
}
class ReadExecutableChildTest : public Multiprocess {

View File

@ -0,0 +1,30 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// 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 NOTE_ALIGN 4
.section .note.crashpad.test,"a",%note
.balign NOTE_ALIGN
.type testnote, %object
testnote:
.long name_end - name // namesz
.long desc_end - desc // descsz
.long 1 // type
name:
.ascii "Crashpad\0"
name_end:
.balign NOTE_ALIGN
desc:
.long 42
desc_end:
.size testnote, .-testnote

View File

@ -73,6 +73,7 @@
'cpu_context_test.cc',
'crashpad_info_client_options_test.cc',
'elf/elf_image_reader_test.cc',
'elf/elf_image_reader_test_note.S',
'linux/debug_rendezvous_test.cc',
'linux/exception_snapshot_linux_test.cc',
'linux/process_reader_test.cc',
@ -124,7 +125,7 @@
'copies': [{
'destination': '<(PRODUCT_DIR)',
'files': [
'linux/test_exported_symbols.sym',
'elf/test_exported_symbols.sym',
],
}],
'ldflags': [