From 70563e92f455b0863e1d7816e24be1cd72df5282 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Mon, 8 Jan 2018 17:17:38 -0800 Subject: [PATCH] Add CrashpadInfoReader to read CrashpadInfo via ProcessMemory Bug: crashpad:30 Change-Id: I295d518ee0eef1fd61e5544cd6bad25827d07a02 Reviewed-on: https://chromium-review.googlesource.com/846025 Reviewed-by: Mark Mentovai Commit-Queue: Joshua Peraza --- snapshot/BUILD.gn | 3 + .../crashpad_types/crashpad_info_reader.cc | 187 ++++++++++++++++++ .../crashpad_types/crashpad_info_reader.h | 75 +++++++ .../crashpad_info_reader_test.cc | 132 +++++++++++++ snapshot/snapshot.gyp | 3 + snapshot/snapshot_test.gyp | 2 + util/misc/as_underlying_type.h | 3 +- 7 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 snapshot/crashpad_types/crashpad_info_reader.cc create mode 100644 snapshot/crashpad_types/crashpad_info_reader.h create mode 100644 snapshot/crashpad_types/crashpad_info_reader_test.cc diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index 306610f5..cd6bb3e1 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -100,6 +100,8 @@ static_library("snapshot") { if (crashpad_is_linux || crashpad_is_android) { sources += [ + "crashpad_types/crashpad_info_reader.cc", + "crashpad_types/crashpad_info_reader.h", "elf/elf_dynamic_array_reader.cc", "elf/elf_dynamic_array_reader.h", "elf/elf_image_reader.cc", @@ -285,6 +287,7 @@ source_set("snapshot_test") { if (crashpad_is_linux || crashpad_is_android) { sources += [ + "crashpad_types/crashpad_info_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/crashpad_info_reader.cc b/snapshot/crashpad_types/crashpad_info_reader.cc new file mode 100644 index 00000000..ade9931b --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader.cc @@ -0,0 +1,187 @@ +// 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/crashpad_info_reader.h" + +#include + +#include "build/build_config.h" +#include "client/crashpad_info.h" +#include "util/linux/traits.h" +#include "util/misc/as_underlying_type.h" + +namespace crashpad { + +namespace { + +void UnsetIfNotValidTriState(TriState* value) { + switch (AsUnderlyingType(*value)) { + case AsUnderlyingType(TriState::kUnset): + case AsUnderlyingType(TriState::kEnabled): + case AsUnderlyingType(TriState::kDisabled): + return; + } + LOG(WARNING) << "Unsetting invalid TriState " << AsUnderlyingType(*value); + *value = TriState::kUnset; +} + +} // namespace + +class CrashpadInfoReader::InfoContainer { + public: + virtual ~InfoContainer() = default; + + virtual bool Read(const ProcessMemoryRange* memory, VMAddress address) = 0; + + protected: + InfoContainer() = default; +}; + +template +class CrashpadInfoReader::InfoContainerSpecific : public InfoContainer { + public: + InfoContainerSpecific() : InfoContainer() {} + ~InfoContainerSpecific() override = default; + + bool Read(const ProcessMemoryRange* memory, VMAddress address) override { + if (!memory->Read(address, + offsetof(decltype(info), size) + sizeof(info.size), + &info)) { + return false; + } + + if (info.signature != CrashpadInfo::kSignature) { + LOG(ERROR) << "invalid signature 0x" << std::hex << info.signature; + return false; + } + + if (!memory->Read(address, + std::min(VMSize{info.size}, VMSize{sizeof(info)}), + &info)) { + return false; + } + + if (info.size > sizeof(info)) { + LOG(INFO) << "large crashpad info size " << info.size; + } + + if (info.version != 1) { + LOG(ERROR) << "unexpected version " << info.version; + return false; + } + + memset(reinterpret_cast(&info), 0, sizeof(info) - info.size); + + UnsetIfNotValidTriState(&info.crashpad_handler_behavior); + UnsetIfNotValidTriState(&info.system_crash_reporter_forwarding); + UnsetIfNotValidTriState(&info.gather_indirectly_referenced_memory); + + return true; + } + + struct { + uint32_t signature; + uint32_t size; + uint32_t version; + uint32_t indirectly_referenced_memory_cap; + uint32_t padding_0; + TriState crashpad_handler_behavior; + TriState system_crash_reporter_forwarding; + TriState gather_indirectly_referenced_memory; + uint8_t padding_1; + typename Traits::Address extra_memory_ranges; + typename Traits::Address simple_annotations; + typename Traits::Address user_data_minidump_stream_head; + typename Traits::Address annotations_list; + } info; + +#if defined(ARCH_CPU_64_BITS) +#define NATIVE_TRAITS Traits64 +#else +#define NATIVE_TRAITS Traits32 +#endif + static_assert(!std::is_same::value || + sizeof(info) == sizeof(CrashpadInfo), + "CrashpadInfo size mismtach"); +#undef NATIVE_TRAITS +}; + +CrashpadInfoReader::CrashpadInfoReader() + : container_(), is_64_bit_(false), initialized_() {} + +CrashpadInfoReader::~CrashpadInfoReader() = default; + +bool CrashpadInfoReader::Initialize(const ProcessMemoryRange* memory, + VMAddress address) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + is_64_bit_ = memory->Is64Bit(); + + std::unique_ptr new_container; + if (is_64_bit_) { + new_container = std::make_unique>(); + } else { + new_container = std::make_unique>(); + } + + if (!new_container->Read(memory, address)) { + return false; + } + container_ = std::move(new_container); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +#define GET_MEMBER(name) \ + (is_64_bit_ \ + ? reinterpret_cast*>(container_.get()) \ + ->info.name \ + : reinterpret_cast*>(container_.get()) \ + ->info.name) + +#define DEFINE_GETTER(type, method, member) \ + type CrashpadInfoReader::method() { \ + INITIALIZATION_STATE_DCHECK_VALID(initialized_); \ + return GET_MEMBER(member); \ + } + +DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior); + +DEFINE_GETTER(TriState, + SystemCrashReporterForwarding, + system_crash_reporter_forwarding); + +DEFINE_GETTER(TriState, + GatherIndirectlyReferencedMemory, + gather_indirectly_referenced_memory); + +DEFINE_GETTER(uint32_t, + IndirectlyReferencedMemoryCap, + indirectly_referenced_memory_cap); + +DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges); + +DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations); + +DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list); + +DEFINE_GETTER(VMAddress, + UserDataMinidumpStreamHead, + user_data_minidump_stream_head); + +#undef DEFINE_GETTER +#undef GET_MEMBER + +} // namespace crashpad diff --git a/snapshot/crashpad_types/crashpad_info_reader.h b/snapshot/crashpad_types/crashpad_info_reader.h new file mode 100644 index 00000000..5f2352ef --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader.h @@ -0,0 +1,75 @@ +// 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_CRASHPAD_INFO_READER_H_ +#define CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_CRASHPAD_INFO_READER_H_ + +#include + +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/misc/tri_state.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief Reads CrashpadInfo structs from another process via a +//! ProcessMemoryRange. +class CrashpadInfoReader { + public: + CrashpadInfoReader(); + ~CrashpadInfoReader(); + + //! \brief Initializes this object. + //! + //! This method must be successfully called bfore any other method in this + //! class. + //! + //! \param[in] memory The reader for the remote process. + //! \param[in] address The address in the remote process' address space of a + //! CrashpadInfo struct. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(const ProcessMemoryRange* memory, VMAddress address); + + //! \{ + //! \see CrashpadInfo + TriState CrashpadHandlerBehavior(); + TriState SystemCrashReporterForwarding(); + TriState GatherIndirectlyReferencedMemory(); + uint32_t IndirectlyReferencedMemoryCap(); + VMAddress ExtraMemoryRanges(); + VMAddress SimpleAnnotations(); + VMAddress AnnotationsList(); + VMAddress UserDataMinidumpStreamHead(); + //! \} + + private: + class InfoContainer; + + template + class InfoContainerSpecific; + + std::unique_ptr container_; + bool is_64_bit_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(CrashpadInfoReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_CRASHPAD_INFO_READER_H_ diff --git a/snapshot/crashpad_types/crashpad_info_reader_test.cc b/snapshot/crashpad_types/crashpad_info_reader_test.cc new file mode 100644 index 00000000..23565dab --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader_test.cc @@ -0,0 +1,132 @@ +// 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/crashpad_info_reader.h" + +#include +#include + +#include "build/build_config.h" +#include "client/annotation_list.h" +#include "client/crashpad_info.h" +#include "client/simple_address_range_bag.h" +#include "client/simple_string_dictionary.h" +#include "gtest/gtest.h" +#include "test/multiprocess.h" +#include "util/file/file_io.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_linux.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr TriState kCrashpadHandlerBehavior = TriState::kEnabled; +constexpr TriState kSystemCrashReporterForwarding = TriState::kDisabled; +constexpr TriState kGatherIndirectlyReferencedMemory = TriState::kUnset; + +constexpr uint32_t kIndirectlyReferencedMemoryCap = 42; + +class CrashpadInfoTest { + public: + CrashpadInfoTest() + : extra_memory_(), simple_annotations_(), annotation_list_() { + CrashpadInfo* info = CrashpadInfo::GetCrashpadInfo(); + info->set_extra_memory_ranges(&extra_memory_); + info->set_simple_annotations(&simple_annotations_); + info->set_annotations_list(&annotation_list_); + info->set_crashpad_handler_behavior(kCrashpadHandlerBehavior); + info->set_system_crash_reporter_forwarding(kSystemCrashReporterForwarding); + info->set_gather_indirectly_referenced_memory( + kGatherIndirectlyReferencedMemory, kIndirectlyReferencedMemoryCap); + } + + void ExpectCrashpadInfo(pid_t pid, bool is_64_bit) { + ProcessMemoryLinux memory; + ASSERT_TRUE(memory.Initialize(pid)); + + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + + CrashpadInfo* info = CrashpadInfo::GetCrashpadInfo(); + + CrashpadInfoReader reader; + ASSERT_TRUE(reader.Initialize(&range, FromPointerCast(info))); + EXPECT_EQ(reader.CrashpadHandlerBehavior(), kCrashpadHandlerBehavior); + EXPECT_EQ(reader.SystemCrashReporterForwarding(), + kSystemCrashReporterForwarding); + EXPECT_EQ(reader.GatherIndirectlyReferencedMemory(), + kGatherIndirectlyReferencedMemory); + EXPECT_EQ(reader.IndirectlyReferencedMemoryCap(), + kIndirectlyReferencedMemoryCap); + EXPECT_EQ(reader.ExtraMemoryRanges(), + FromPointerCast(&extra_memory_)); + EXPECT_EQ(reader.SimpleAnnotations(), + FromPointerCast(info->simple_annotations())); + EXPECT_EQ(reader.AnnotationsList(), + FromPointerCast(info->annotations_list())); + } + + private: + SimpleAddressRangeBag extra_memory_; + SimpleStringDictionary simple_annotations_; + AnnotationList annotation_list_; + + DISALLOW_COPY_AND_ASSIGN(CrashpadInfoTest); +}; + +TEST(CrashpadInfoReader, ReadFromSelf) { + CrashpadInfoTest test; + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + test.ExpectCrashpadInfo(getpid(), am_64_bit); +} + +class ReadFromChildTest : public Multiprocess { + public: + ReadFromChildTest() : Multiprocess(), info_test_() {} + + ~ReadFromChildTest() = default; + + private: + void MultiprocessParent() { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + info_test_.ExpectCrashpadInfo(ChildPID(), am_64_bit); + } + + void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); } + + CrashpadInfoTest info_test_; + + DISALLOW_COPY_AND_ASSIGN(ReadFromChildTest); +}; + +TEST(CrashpadInfoReader, ReadFromChild) { + ReadFromChildTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index d2fc91f5..ba14eb3e 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -39,6 +39,8 @@ 'cpu_context.h', 'crashpad_info_client_options.cc', 'crashpad_info_client_options.h', + 'crashpad_types/crashpad_info_reader.cc', + 'crashpad_types/crashpad_info_reader.h', 'elf/elf_dynamic_array_reader.cc', 'elf/elf_dynamic_array_reader.h', 'elf/elf_image_reader.cc', @@ -171,6 +173,7 @@ }, { # else: OS!="linux" and OS!="android" 'sources/': [ ['exclude', '^elf/'], + ['exclude', '^crashpad_types/'], ], }], ['target_arch!="ia32" and target_arch!="x64"', { diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index 1e32b8e1..fc3ad4c8 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -72,6 +72,7 @@ 'api/module_annotations_win_test.cc', 'cpu_context_test.cc', 'crashpad_info_client_options_test.cc', + 'crashpad_types/crashpad_info_reader_test.cc', 'elf/elf_image_reader_test.cc', 'elf/elf_image_reader_test_note.S', 'linux/debug_rendezvous_test.cc', @@ -139,6 +140,7 @@ }, { # else: OS!="linux" and OS!="android" 'sources/': [ ['exclude', '^elf/'], + ['exclude', '^crashpad_types/'], ], }], ], diff --git a/util/misc/as_underlying_type.h b/util/misc/as_underlying_type.h index 8afd0ef3..ba673ae5 100644 --- a/util/misc/as_underlying_type.h +++ b/util/misc/as_underlying_type.h @@ -24,7 +24,8 @@ namespace crashpad { //! \param[in] from The value to be casted. //! \return \a from casted to its underlying type. template -typename std::underlying_type::type AsUnderlyingType(From from) { +constexpr typename std::underlying_type::type AsUnderlyingType( + From from) { return static_cast::type>(from); }