From d62cc6fdbdfe1813a59aa7bd7f9f1ed8ba15db2c Mon Sep 17 00:00:00 2001 From: Jakob Kummerow Date: Fri, 12 Nov 2021 18:55:21 +0100 Subject: [PATCH] Linux: capture memory pointed to by context This adds support for capturing memory snippets for addresses currently stored in registers to Linux/Android/CrOS. Modeled after the existing support on Windows. Bug: crashpad:30 Change-Id: Ib7cb523555a6e8e4d70145c205d67dcfbc9c7fcc Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3273712 Commit-Queue: Jakob Kummerow Reviewed-by: Joshua Peraza --- .gitignore | 1 + snapshot/BUILD.gn | 2 + .../linux/capture_memory_delegate_linux.cc | 77 +++++++++++++++++++ .../linux/capture_memory_delegate_linux.h | 70 +++++++++++++++++ snapshot/linux/exception_snapshot_linux.cc | 24 +++++- snapshot/linux/exception_snapshot_linux.h | 2 + snapshot/linux/process_snapshot_linux.cc | 17 +++- snapshot/linux/process_snapshot_linux.h | 4 + snapshot/linux/system_snapshot_linux_test.cc | 4 +- snapshot/linux/thread_snapshot_linux.cc | 22 +++++- snapshot/linux/thread_snapshot_linux.h | 7 +- tools/generate_dump.cc | 7 +- util/linux/memory_map.cc | 57 ++++++++++++++ util/linux/memory_map.h | 10 +++ 14 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 snapshot/linux/capture_memory_delegate_linux.cc create mode 100644 snapshot/linux/capture_memory_delegate_linux.h diff --git a/.gitignore b/.gitignore index 27c9f8d8..b739477a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ *.xcodeproj *~ .*.sw? +.cache .DS_Store .gdb_history .gdbinit diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index fc2246a5..225d3446 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -128,6 +128,8 @@ crashpad_static_library("snapshot") { if (crashpad_is_linux || crashpad_is_android) { sources += [ + "linux/capture_memory_delegate_linux.cc", + "linux/capture_memory_delegate_linux.h", "linux/cpu_context_linux.cc", "linux/cpu_context_linux.h", "linux/debug_rendezvous.cc", diff --git a/snapshot/linux/capture_memory_delegate_linux.cc b/snapshot/linux/capture_memory_delegate_linux.cc new file mode 100644 index 00000000..4f158740 --- /dev/null +++ b/snapshot/linux/capture_memory_delegate_linux.cc @@ -0,0 +1,77 @@ +// Copyright 2021 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/linux/capture_memory_delegate_linux.h" + +#include + +#include "base/numerics/safe_conversions.h" +#include "snapshot/memory_snapshot_generic.h" + +namespace crashpad { +namespace internal { + +CaptureMemoryDelegateLinux::CaptureMemoryDelegateLinux( + ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread* thread_opt, + std::vector>* snapshots, + uint32_t* budget_remaining) + : stack_(thread_opt ? thread_opt->stack_region_address : 0, + thread_opt ? thread_opt->stack_region_size : 0), + process_reader_(process_reader), + snapshots_(snapshots), + budget_remaining_(budget_remaining) {} + +bool CaptureMemoryDelegateLinux::Is64Bit() const { + return process_reader_->Is64Bit(); +} + +bool CaptureMemoryDelegateLinux::ReadMemory(uint64_t at, + uint64_t num_bytes, + void* into) const { + return process_reader_->Memory()->Read( + at, base::checked_cast(num_bytes), into); +} + +std::vector> +CaptureMemoryDelegateLinux::GetReadableRanges( + const CheckedRange& range) const { + return process_reader_->GetMemoryMap()->GetReadableRanges(range); +} + +void CaptureMemoryDelegateLinux::AddNewMemorySnapshot( + const CheckedRange& range) { + // Don't bother storing this memory if it points back into the stack. + if (stack_.ContainsRange(range)) + return; + if (range.size() == 0) + return; + if (budget_remaining_ && *budget_remaining_ == 0) + return; + snapshots_->push_back(std::make_unique()); + internal::MemorySnapshotGeneric* snapshot = snapshots_->back().get(); + snapshot->Initialize(process_reader_->Memory(), range.base(), range.size()); + if (budget_remaining_) { + if (!base::IsValueInRangeForNumericType(range.size())) { + *budget_remaining_ = 0; + } else { + int64_t temp = *budget_remaining_; + temp -= range.size(); + *budget_remaining_ = base::saturated_cast(temp); + } + } +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/linux/capture_memory_delegate_linux.h b/snapshot/linux/capture_memory_delegate_linux.h new file mode 100644 index 00000000..c0250b55 --- /dev/null +++ b/snapshot/linux/capture_memory_delegate_linux.h @@ -0,0 +1,70 @@ +// Copyright 2021 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_LINUX_CAPTURE_MEMORY_DELEGATE_LINUX_H_ +#define CRASHPAD_SNAPSHOT_LINUX_CAPTURE_MEMORY_DELEGATE_LINUX_H_ + +#include "snapshot/capture_memory.h" + +#include + +#include +#include + +#include "snapshot/linux/process_reader_linux.h" +#include "util/numeric/checked_range.h" + +namespace crashpad { +namespace internal { + +class MemorySnapshotGeneric; + +class CaptureMemoryDelegateLinux : public CaptureMemory::Delegate { + public: + //! \brief A MemoryCaptureDelegate for Linux. + //! + //! \param[in] process_reader A ProcessReaderLinux for the target process. + //! \param[in] thread_opt The thread being inspected. Memory ranges + //! overlapping this thread's stack will be ignored on the assumption + //! that they're already captured elsewhere. May be nullptr. + //! \param[in] snapshots A vector of MemorySnapshotGeneric to which the + //! captured memory will be added. + //! \param[in] budget_remaining If non-null, a pointer to the remaining number + //! of bytes to capture. If this is `0`, no further memory will be + //! captured. + CaptureMemoryDelegateLinux( + ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread* thread_opt, + std::vector>* snapshots, + uint32_t* budget_remaining); + + // MemoryCaptureDelegate: + bool Is64Bit() const override; + bool ReadMemory(uint64_t at, uint64_t num_bytes, void* into) const override; + std::vector> GetReadableRanges( + const CheckedRange& range) const override; + void AddNewMemorySnapshot( + const CheckedRange& range) override; + + private: + CheckedRange stack_; + ProcessReaderLinux* process_reader_; // weak + std::vector>* snapshots_; // weak + uint32_t* budget_remaining_; +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_LINUX_CAPTURE_MEMORY_DELEGATE_LINUX_H_ diff --git a/snapshot/linux/exception_snapshot_linux.cc b/snapshot/linux/exception_snapshot_linux.cc index cd40b3b1..42d0eb0b 100644 --- a/snapshot/linux/exception_snapshot_linux.cc +++ b/snapshot/linux/exception_snapshot_linux.cc @@ -17,6 +17,7 @@ #include #include "base/logging.h" +#include "snapshot/linux/capture_memory_delegate_linux.h" #include "snapshot/linux/cpu_context_linux.h" #include "snapshot/linux/process_reader_linux.h" #include "snapshot/linux/signal_context.h" @@ -332,6 +333,18 @@ bool ExceptionSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, INITIALIZATION_STATE_SET_INITIALIZING(initialized_); thread_id_ = thread_id; + const ProcessReaderLinux::Thread* thread = nullptr; + for (const auto& loop_thread : process_reader->Threads()) { + if (thread_id == loop_thread.tid) { + thread = &loop_thread; + break; + } + } + if (!thread) { + // This is allowed until {ProcessReaderLinux::InitializeThreads()} is + // improved to support target threads in the same thread group. + LOG(WARNING) << "thread ID " << thread_id << " not found in process"; + } if (process_reader->Is64Bit()) { if (!ReadContext(process_reader, context_address) || @@ -345,6 +358,10 @@ bool ExceptionSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, } } + CaptureMemoryDelegateLinux capture_memory_delegate( + process_reader, thread, &extra_memory_, nullptr); + CaptureMemory::PointedToByContext(context_, &capture_memory_delegate); + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -462,7 +479,12 @@ const std::vector& ExceptionSnapshotLinux::Codes() const { std::vector ExceptionSnapshotLinux::ExtraMemory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return std::vector(); + std::vector result; + result.reserve(extra_memory_.size()); + for (const auto& em : extra_memory_) { + result.push_back(em.get()); + } + return result; } } // namespace internal diff --git a/snapshot/linux/exception_snapshot_linux.h b/snapshot/linux/exception_snapshot_linux.h index c722ac77..1719f0be 100644 --- a/snapshot/linux/exception_snapshot_linux.h +++ b/snapshot/linux/exception_snapshot_linux.h @@ -25,6 +25,7 @@ #include "snapshot/exception_snapshot.h" #include "snapshot/linux/process_reader_linux.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" #include "util/linux/address_types.h" #include "util/misc/initialization_state_dcheck.h" @@ -91,6 +92,7 @@ class ExceptionSnapshotLinux final : public ExceptionSnapshot { } context_union_; CPUContext context_; std::vector codes_; + std::vector> extra_memory_; uint64_t thread_id_; uint64_t exception_address_; uint32_t signal_number_; diff --git a/snapshot/linux/process_snapshot_linux.cc b/snapshot/linux/process_snapshot_linux.cc index 35f870ec..53499b36 100644 --- a/snapshot/linux/process_snapshot_linux.cc +++ b/snapshot/linux/process_snapshot_linux.cc @@ -41,6 +41,8 @@ bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection) { system_.Initialize(&process_reader_, &snapshot_time_); + GetCrashpadOptionsInternal((&options_)); + InitializeThreads(); InitializeModules(); InitializeAnnotations(); @@ -100,7 +102,7 @@ bool ProcessSnapshotLinux::InitializeException( auto exc_thread_snapshot = std::make_unique(); - if (!exc_thread_snapshot->Initialize(&process_reader_, thread)) { + if (!exc_thread_snapshot->Initialize(&process_reader_, thread, nullptr)) { return false; } @@ -124,7 +126,11 @@ bool ProcessSnapshotLinux::InitializeException( void ProcessSnapshotLinux::GetCrashpadOptions( CrashpadInfoClientOptions* options) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *options = options_; +} +void ProcessSnapshotLinux::GetCrashpadOptionsInternal( + CrashpadInfoClientOptions* options) { CrashpadInfoClientOptions local_options; for (const auto& module : modules_) { @@ -262,10 +268,17 @@ const ProcessMemory* ProcessSnapshotLinux::Memory() const { void ProcessSnapshotLinux::InitializeThreads() { const std::vector& process_reader_threads = process_reader_.Threads(); + uint32_t* budget_remaining_pointer = nullptr; + uint32_t budget_remaining = options_.indirectly_referenced_memory_cap; + if (options_.gather_indirectly_referenced_memory == TriState::kEnabled) { + budget_remaining_pointer = &budget_remaining; + } for (const ProcessReaderLinux::Thread& process_reader_thread : process_reader_threads) { auto thread = std::make_unique(); - if (thread->Initialize(&process_reader_, process_reader_thread)) { + if (thread->Initialize(&process_reader_, + process_reader_thread, + budget_remaining_pointer)) { threads_.push_back(std::move(thread)); } } diff --git a/snapshot/linux/process_snapshot_linux.h b/snapshot/linux/process_snapshot_linux.h index 62517bbf..285ee5d0 100644 --- a/snapshot/linux/process_snapshot_linux.h +++ b/snapshot/linux/process_snapshot_linux.h @@ -139,6 +139,9 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { void InitializeModules(); void InitializeAnnotations(); + // Initializes options_ on behalf of Initialize(). + void GetCrashpadOptionsInternal(CrashpadInfoClientOptions* options); + std::map annotations_simple_map_; timeval snapshot_time_; UUID report_id_; @@ -149,6 +152,7 @@ class ProcessSnapshotLinux final : public ProcessSnapshot { internal::SystemSnapshotLinux system_; ProcessReaderLinux process_reader_; ProcessMemoryRange memory_range_; + CrashpadInfoClientOptions options_; InitializationStateDcheck initialized_; }; diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc index f3013b54..f5d26a1e 100644 --- a/snapshot/linux/system_snapshot_linux_test.cc +++ b/snapshot/linux/system_snapshot_linux_test.cc @@ -47,7 +47,9 @@ TEST(SystemSnapshotLinux, Basic) { uint64_t current_hz, max_hz; system.CPUFrequency(¤t_hz, &max_hz); - EXPECT_GE(max_hz, current_hz); + // For short-term loads, modern CPUs can boost single-core frequency beyond + // the advertised base clock. Let's assume this is no more than a factor 2. + EXPECT_GE(max_hz * 2, current_hz); int major, minor, bugfix; std::string build; diff --git a/snapshot/linux/thread_snapshot_linux.cc b/snapshot/linux/thread_snapshot_linux.cc index e3e2bebd..f279e0ad 100644 --- a/snapshot/linux/thread_snapshot_linux.cc +++ b/snapshot/linux/thread_snapshot_linux.cc @@ -17,6 +17,7 @@ #include #include "base/logging.h" +#include "snapshot/linux/capture_memory_delegate_linux.h" #include "snapshot/linux/cpu_context_linux.h" #include "util/misc/reinterpret_bytes.h" @@ -138,8 +139,10 @@ ThreadSnapshotLinux::ThreadSnapshotLinux() ThreadSnapshotLinux::~ThreadSnapshotLinux() {} -bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, - const ProcessReaderLinux::Thread& thread) { +bool ThreadSnapshotLinux::Initialize( + ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread& thread, + uint32_t* gather_indirectly_referenced_memory_bytes_remaining) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); #if defined(ARCH_CPU_X86_FAMILY) @@ -205,6 +208,13 @@ bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, thread.static_priority, thread.sched_policy, thread.nice_value) : -1; + CaptureMemoryDelegateLinux capture_memory_delegate( + process_reader, + &thread, + &pointed_to_memory_, + gather_indirectly_referenced_memory_bytes_remaining); + CaptureMemory::PointedToByContext(context_, &capture_memory_delegate); + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -240,7 +250,13 @@ uint64_t ThreadSnapshotLinux::ThreadSpecificDataAddress() const { } std::vector ThreadSnapshotLinux::ExtraMemory() const { - return std::vector(); + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector result; + result.reserve(pointed_to_memory_.size()); + for (const auto& pointed_to_memory : pointed_to_memory_) { + result.push_back(pointed_to_memory.get()); + } + return result; } } // namespace internal diff --git a/snapshot/linux/thread_snapshot_linux.h b/snapshot/linux/thread_snapshot_linux.h index 17a6668d..40cd7e7f 100644 --- a/snapshot/linux/thread_snapshot_linux.h +++ b/snapshot/linux/thread_snapshot_linux.h @@ -47,8 +47,10 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { //! //! \return `true` if the snapshot could be created, `false` otherwise with //! a message logged. - bool Initialize(ProcessReaderLinux* process_reader, - const ProcessReaderLinux::Thread& thread); + bool Initialize( + ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread& thread, + uint32_t* gather_indirectly_referenced_memory_bytes_remaining); // ThreadSnapshot: @@ -81,6 +83,7 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { pid_t thread_id_; int priority_; InitializationStateDcheck initialized_; + std::vector> pointed_to_memory_; }; } // namespace internal diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index e22f92a5..ab027b87 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -49,6 +49,7 @@ #include "util/win/xp_compat.h" #elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) #include "snapshot/linux/process_snapshot_linux.h" +#include "util/linux/direct_ptrace_connection.h" #endif // OS_APPLE namespace crashpad { @@ -198,8 +199,12 @@ int GenerateDumpMain(int argc, char* argv[]) { } #elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) // TODO(jperaza): https://crashpad.chromium.org/bug/30. + DirectPtraceConnection task; + if (!task.Initialize(options.pid)) { + return EXIT_FAILURE; + } ProcessSnapshotLinux process_snapshot; - if (!process_snapshot.Initialize(nullptr)) { + if (!process_snapshot.Initialize(&task)) { return EXIT_FAILURE; } #endif // OS_APPLE diff --git a/util/linux/memory_map.cc b/util/linux/memory_map.cc index e15a1c5e..47eec973 100644 --- a/util/linux/memory_map.cc +++ b/util/linux/memory_map.cc @@ -227,6 +227,12 @@ class FullReverseIterator : public MemoryMap::Iterator { std::vector::const_reverse_iterator rend_; }; +// Faster than a CheckedRange, for temporary values. +struct FastRange { + VMAddress base; + VMSize size; +}; + } // namespace MemoryMap::Mapping::Mapping() @@ -321,6 +327,57 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName( return nullptr; } +std::vector> MemoryMap::GetReadableRanges( + const CheckedRange& range) const { + using Range = CheckedRange; + + VMAddress range_base = range.base(); + VMAddress range_end = range.end(); + std::vector overlapping; + + // Find all readable ranges overlapping the target range, maintaining order. + for (const auto& mapping : mappings_) { + if (!mapping.readable) + continue; + if (mapping.range.End() < range_base) + continue; + if (mapping.range.Base() >= range_end) + continue; + // Special case: the "[vvar]" region is marked readable, but we can't + // access it. + if (mapping.inode == 0 && mapping.name == "[vvar]") + continue; + overlapping.push_back({mapping.range.Base(), mapping.range.Size()}); + } + if (overlapping.empty()) + return std::vector(); + + // For the first and last, trim to the boundary of the incoming range. + FastRange& front = overlapping.front(); + VMAddress original_front_base = front.base; + front.base = std::max(front.base, range_base); + front.size = (original_front_base + front.size) - front.base; + FastRange& back = overlapping.back(); + VMAddress back_end = back.base + back.size; + back.size = std::min(range_end, back_end) - back.base; + + // Coalesce, and convert to return type. + std::vector result; + result.push_back({overlapping[0].base, overlapping[0].size}); + DCHECK(result.back().IsValid()); + for (size_t i = 1; i < overlapping.size(); ++i) { + if (result.back().end() == overlapping[i].base) { + result.back().SetRange(result.back().base(), + result.back().size() + overlapping[i].size); + } else { + result.push_back({overlapping[i].base, overlapping[i].size}); + } + DCHECK(result.back().IsValid()); + } + + return result; +} + std::unique_ptr MemoryMap::FindFilePossibleMmapStarts( const Mapping& mapping) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); diff --git a/util/linux/memory_map.h b/util/linux/memory_map.h index f6f7cc84..2a542ebb 100644 --- a/util/linux/memory_map.h +++ b/util/linux/memory_map.h @@ -77,6 +77,16 @@ class MemoryMap { //! it was obtained from. const Mapping* FindMappingWithName(const std::string& name) const; + //! \brief Given a range to be read from the target process, returns a vector + //! of ranges, representing the readable portions of the original range. + //! + //! \param[in] range The range being identified. + //! + //! \return A vector of ranges corresponding to the portion of \a range that + //! is readable based on the memory map. + std::vector> GetReadableRanges( + const CheckedRange& range) const; + //! \brief An abstract base class for iterating over ordered sets of mappings //! in a MemoryMap. class Iterator {