From 878af9cbbdb400b2e7025b680c613dcf9df5ed2c Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Tue, 9 Jan 2018 10:22:24 -0800 Subject: [PATCH] Add AnnotationReader to read annotation types via a ProcessMemory Bug: crashpad:30 Change-Id: Icd13d29992b8684ca92916068f12428c25e0e775 Reviewed-on: https://chromium-review.googlesource.com/846519 Commit-Queue: Joshua Peraza Reviewed-by: Mark Mentovai --- snapshot/BUILD.gn | 3 + .../crashpad_types/image_annotation_reader.cc | 153 ++++++++++++++++ .../crashpad_types/image_annotation_reader.h | 76 ++++++++ .../image_annotation_reader_test.cc | 164 ++++++++++++++++++ snapshot/snapshot.gyp | 2 + snapshot/snapshot_test.gyp | 1 + 6 files changed, 399 insertions(+) create mode 100644 snapshot/crashpad_types/image_annotation_reader.cc create mode 100644 snapshot/crashpad_types/image_annotation_reader.h create mode 100644 snapshot/crashpad_types/image_annotation_reader_test.cc diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index cd6bb3e1..b98f9d25 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -102,6 +102,8 @@ static_library("snapshot") { sources += [ "crashpad_types/crashpad_info_reader.cc", "crashpad_types/crashpad_info_reader.h", + "crashpad_types/image_annotation_reader.cc", + "crashpad_types/image_annotation_reader.h", "elf/elf_dynamic_array_reader.cc", "elf/elf_dynamic_array_reader.h", "elf/elf_image_reader.cc", @@ -288,6 +290,7 @@ source_set("snapshot_test") { if (crashpad_is_linux || crashpad_is_android) { sources += [ "crashpad_types/crashpad_info_reader_test.cc", + "crashpad_types/image_annotation_reader_test.cc", "elf/elf_image_reader_test.cc", "elf/elf_image_reader_test_note.S", "elf/test_exported_symbols.sym", diff --git a/snapshot/crashpad_types/image_annotation_reader.cc b/snapshot/crashpad_types/image_annotation_reader.cc new file mode 100644 index 00000000..bd904979 --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader.cc @@ -0,0 +1,153 @@ +// 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. + +#include "snapshot/crashpad_types/image_annotation_reader.h" + +#include +#include + +#include +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "client/annotation.h" +#include "client/annotation_list.h" +#include "client/simple_string_dictionary.h" +#include "snapshot/snapshot_constants.h" +#include "util/linux/traits.h" + +namespace crashpad { + +namespace process_types { + +template +struct Annotation { + typename Traits::Address link_node; + typename Traits::Address name; + typename Traits::Address value; + uint32_t size; + uint16_t type; +}; + +template +struct AnnotationList { + typename Traits::Address tail_pointer; + Annotation head; + Annotation tail; +}; + +} // namespace process_types + +#if defined(ARCH_CPU_64_BITS) +#define NATIVE_TRAITS Traits64 +#else +#define NATIVE_TRAITS Traits32 +#endif // ARCH_CPU_64_BITS + +static_assert(sizeof(process_types::Annotation) == + sizeof(Annotation), + "Annotation size mismatch"); + +static_assert(sizeof(process_types::AnnotationList) == + sizeof(AnnotationList), + "AnnotationList size mismatch"); + +#undef NATIVE_TRAITS + +ImageAnnotationReader::ImageAnnotationReader(const ProcessMemoryRange* memory) + : memory_(memory) {} + +ImageAnnotationReader::~ImageAnnotationReader() = default; + +bool ImageAnnotationReader::SimpleMap( + VMAddress address, + std::map* annotations) const { + std::vector simple_annotations( + SimpleStringDictionary::num_entries); + + if (!memory_->Read(address, + simple_annotations.size() * sizeof(simple_annotations[0]), + &simple_annotations[0])) { + return false; + } + + for (const auto& entry : simple_annotations) { + size_t key_length = strnlen(entry.key, sizeof(entry.key)); + if (key_length) { + std::string key(entry.key, key_length); + std::string value(entry.value, strnlen(entry.value, sizeof(entry.value))); + if (!annotations->insert(std::make_pair(key, value)).second) { + LOG(WARNING) << "duplicate simple annotation " << key << " " << value; + } + } + } + return true; +} + +bool ImageAnnotationReader::AnnotationsList( + VMAddress address, + std::vector* annotations) const { + return memory_->Is64Bit() + ? ReadAnnotationList(address, annotations) + : ReadAnnotationList(address, annotations); +} + +template +bool ImageAnnotationReader::ReadAnnotationList( + VMAddress address, + std::vector* annotations) const { + process_types::AnnotationList annotation_list; + if (!memory_->Read(address, sizeof(annotation_list), &annotation_list)) { + LOG(ERROR) << "could not read annotation list"; + return false; + } + + process_types::Annotation current = annotation_list.head; + for (size_t index = 0; current.link_node != annotation_list.tail_pointer && + index < kMaxNumberOfAnnotations; + ++index) { + if (!memory_->Read(current.link_node, sizeof(current), ¤t)) { + LOG(ERROR) << "could not read annotation at index " << index; + return false; + } + + if (current.size == 0) { + continue; + } + + AnnotationSnapshot snapshot; + snapshot.type = current.type; + + if (!memory_->ReadCStringSizeLimited( + current.name, Annotation::kNameMaxLength, &snapshot.name)) { + LOG(WARNING) << "could not read annotation name at index " << index; + continue; + } + + size_t value_length = + std::min(static_cast(current.size), Annotation::kValueMaxSize); + snapshot.value.resize(value_length); + if (!memory_->Read(current.value, value_length, snapshot.value.data())) { + LOG(WARNING) << "could not read annotation value at index " << index; + continue; + } + + annotations->push_back(std::move(snapshot)); + } + + return true; +} + +} // namespace crashpad diff --git a/snapshot/crashpad_types/image_annotation_reader.h b/snapshot/crashpad_types/image_annotation_reader.h new file mode 100644 index 00000000..e425bef6 --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ +#define CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/annotation_snapshot.h" +#include "util/misc/address_types.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief Reads Annotations from another process via a ProcessMemoryRange. +//! +//! These annotations are stored for the benefit of crash reporters, and provide +//! information thought to be potentially useful for crash analysis. +class ImageAnnotationReader { + public: + //! \brief Constructs the object. + //! + //! \param[in] memory A memory reader for the remote process. + explicit ImageAnnotationReader(const ProcessMemoryRange* memory); + + ~ImageAnnotationReader(); + + //! \brief Reads annotations that are organized as key-value pairs, where all + //! keys and values are strings. + //! + //! \param[in] address The address in the target process' address space of a + //! SimpleStringDictionary containing the annotations to read. + //! \param[out] annotations The annotations read, valid if this method + //! returns `true`. + //! \return `true` on success. `false` on failure with a message logged. + bool SimpleMap(VMAddress address, + std::map* annotations) const; + + //! \brief Reads the module's annotations that are organized as a list of + //! typed annotation objects. + //! + //! \param[in] address The address in the target process' address space of an + //! AnnotationList. + //! \param[out] annotations The annotations read, valid if this method returns + //! `true`. + //! \return `true` on success. `false` on failure with a message logged. + bool AnnotationsList(VMAddress, + std::vector* annotations) const; + + private: + template + bool ReadAnnotationList(VMAddress address, + std::vector* annotations) const; + + const ProcessMemoryRange* memory_; // weak + + DISALLOW_COPY_AND_ASSIGN(ImageAnnotationReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ diff --git a/snapshot/crashpad_types/image_annotation_reader_test.cc b/snapshot/crashpad_types/image_annotation_reader_test.cc new file mode 100644 index 00000000..b0e635ff --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader_test.cc @@ -0,0 +1,164 @@ +// 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. + +#include "snapshot/crashpad_types/image_annotation_reader.h" + +#include +#include +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "client/annotation.h" +#include "client/annotation_list.h" +#include "client/simple_string_dictionary.h" +#include "gtest/gtest.h" +#include "test/multiprocess.h" +#include "util/file/file_io.h" +#include "util/misc/as_underlying_type.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_linux.h" + +namespace crashpad { +namespace test { +namespace { + +void ExpectSimpleMap(const std::map& map, + const SimpleStringDictionary& expected_map) { + EXPECT_EQ(map.size(), expected_map.GetCount()); + for (const auto& pair : map) { + EXPECT_EQ(pair.second, expected_map.GetValueForKey(pair.first)); + } +} + +void ExpectAnnotationList(const std::vector& list, + AnnotationList& expected_list) { + size_t index = 0; + for (const Annotation* expected_annotation : expected_list) { + const AnnotationSnapshot& annotation = list[index++]; + EXPECT_EQ(annotation.name, expected_annotation->name()); + EXPECT_EQ(annotation.type, AsUnderlyingType(expected_annotation->type())); + EXPECT_EQ(annotation.value.size(), expected_annotation->size()); + EXPECT_EQ(memcmp(annotation.value.data(), + expected_annotation->value(), + std::min(VMSize{annotation.value.size()}, + VMSize{expected_annotation->size()})), + 0); + } +} + +class AnnotationTest { + public: + AnnotationTest() + : expected_simple_map_(), + test_annotations_(), + expected_annotation_list_() { + expected_simple_map_.SetKeyValue("key", "value"); + expected_simple_map_.SetKeyValue("key2", "value2"); + + static constexpr char kAnnotationName[] = "test annotation"; + static constexpr char kAnnotationValue[] = "test annotation value"; + test_annotations_.push_back(std::make_unique( + Annotation::Type::kString, + kAnnotationName, + reinterpret_cast(const_cast(kAnnotationValue)))); + test_annotations_.back()->SetSize(sizeof(kAnnotationValue)); + expected_annotation_list_.Add(test_annotations_.back().get()); + + static constexpr char kAnnotationName2[] = "test annotation2"; + static constexpr char kAnnotationValue2[] = "test annotation value2"; + test_annotations_.push_back(std::make_unique( + Annotation::Type::kString, + kAnnotationName2, + reinterpret_cast(const_cast(kAnnotationValue2)))); + test_annotations_.back()->SetSize(sizeof(kAnnotationValue2)); + expected_annotation_list_.Add(test_annotations_.back().get()); + } + + ~AnnotationTest() = default; + + void ExpectAnnotations(pid_t pid, bool is_64_bit) { + ProcessMemoryLinux memory; + ASSERT_TRUE(memory.Initialize(pid)); + + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + + ImageAnnotationReader reader(&range); + + std::map simple_map; + ASSERT_TRUE(reader.SimpleMap( + FromPointerCast(&expected_simple_map_), &simple_map)); + ExpectSimpleMap(simple_map, expected_simple_map_); + + std::vector annotation_list; + ASSERT_TRUE(reader.AnnotationsList( + FromPointerCast(&expected_annotation_list_), + &annotation_list)); + ExpectAnnotationList(annotation_list, expected_annotation_list_); + } + + private: + SimpleStringDictionary expected_simple_map_; + std::vector> test_annotations_; + AnnotationList expected_annotation_list_; + + DISALLOW_COPY_AND_ASSIGN(AnnotationTest); +}; + +TEST(ImageAnnotationReader, ReadFromSelf) { + AnnotationTest test; + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + test.ExpectAnnotations(getpid(), am_64_bit); +} + +class ReadFromChildTest : public Multiprocess { + public: + ReadFromChildTest() : Multiprocess(), annotation_test_() {} + + ~ReadFromChildTest() {} + + private: + void MultiprocessParent() { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + annotation_test_.ExpectAnnotations(ChildPID(), am_64_bit); + } + + void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); } + + AnnotationTest annotation_test_; + + DISALLOW_COPY_AND_ASSIGN(ReadFromChildTest); +}; + +TEST(ImageAnnotationReader, ReadFromChild) { + ReadFromChildTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index ba14eb3e..35fb614e 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -41,6 +41,8 @@ 'crashpad_info_client_options.h', 'crashpad_types/crashpad_info_reader.cc', 'crashpad_types/crashpad_info_reader.h', + 'crashpad_types/image_annotation_reader.cc', + 'crashpad_types/image_annotation_reader.h', 'elf/elf_dynamic_array_reader.cc', 'elf/elf_dynamic_array_reader.h', 'elf/elf_image_reader.cc', diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index fc3ad4c8..ae4b7e9f 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -73,6 +73,7 @@ 'cpu_context_test.cc', 'crashpad_info_client_options_test.cc', 'crashpad_types/crashpad_info_reader_test.cc', + 'crashpad_types/image_annotation_reader_test.cc', 'elf/elf_image_reader_test.cc', 'elf/elf_image_reader_test_note.S', 'linux/debug_rendezvous_test.cc',