ios: Add support for ScopedVMRead and RAWLOG
Adds wrapper to vm_read and vm_deallocate memory to allow for safe
in-process memory reads during crashes.
Also adds a logging utility safe for in-process exception handling.
Bug: crashpad: 31
Change-Id: I9446fb3df71a2734e125b74312dc161a0bb4449d
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2875349
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Justin Cohen <justincohen@chromium.org>
GitOrigin-RevId: d5b3a1d531255ef240e8c1caa6fbaa808de9743c
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 48bf8e2..71bf1ce 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -384,6 +384,10 @@
"ios/exception_processor.mm",
"ios/ios_system_data_collector.h",
"ios/ios_system_data_collector.mm",
+ "ios/raw_logging.cc",
+ "ios/raw_logging.h",
+ "ios/scoped_vm_read.cc",
+ "ios/scoped_vm_read.h",
]
}
@@ -787,7 +791,10 @@
}
if (crashpad_is_ios) {
- sources += [ "ios/exception_processor_test.mm" ]
+ sources += [
+ "ios/exception_processor_test.mm",
+ "ios/scoped_vm_read_test.cc",
+ ]
sources -= [
"process/process_memory_range_test.cc",
diff --git a/util/ios/raw_logging.cc b/util/ios/raw_logging.cc
new file mode 100644
index 0000000..f037c45
--- /dev/null
+++ b/util/ios/raw_logging.cc
@@ -0,0 +1,66 @@
+// 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 "util/ios/raw_logging.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+
+namespace crashpad {
+namespace internal {
+
+void RawLogString(const char* message) {
+ const size_t message_len = strlen(message);
+ size_t bytes_written = 0;
+ while (bytes_written < message_len) {
+ int rv = HANDLE_EINTR(write(
+ STDERR_FILENO, message + bytes_written, message_len - bytes_written));
+ if (rv < 0) {
+ // Give up, nothing we can do now.
+ break;
+ }
+ bytes_written += rv;
+ }
+}
+
+void RawLogInt(unsigned int number) {
+ char buffer[20];
+ char* digit = &buffer[sizeof(buffer) - 1];
+ *digit = '\0';
+ do {
+ *(--digit) = (number % 10) + '0';
+ number /= 10;
+ } while (number != 0);
+ RawLogString(digit);
+}
+
+// Prints `path:linenum message:error` (with optional `:error`).
+void RawLog(const char* file, int line, const char* message, int error) {
+ RawLogString(file);
+ HANDLE_EINTR(write(STDERR_FILENO, ":", 1));
+ RawLogInt(line);
+ HANDLE_EINTR(write(STDERR_FILENO, " ", 1));
+ RawLogString(message);
+ if (error) {
+ RawLogString(": ");
+ RawLogInt(error);
+ }
+ HANDLE_EINTR(write(STDERR_FILENO, "\n", 1));
+}
+
+} // namespace internal
+} // namespace crashpad
diff --git a/util/ios/raw_logging.h b/util/ios/raw_logging.h
new file mode 100644
index 0000000..505aa67
--- /dev/null
+++ b/util/ios/raw_logging.h
@@ -0,0 +1,37 @@
+// 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_UTIL_IOS_EXCEPTION_LOGGING_H_
+#define CRASHPAD_UTIL_IOS_EXCEPTION_LOGGING_H_
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Log \a message to stderr in a way that is safe to run during an
+//! in-process crash. Also prints the given file, line number and an
+//! optional error code.
+//!
+//! Note: RUNS-DURING-CRASH.
+void RawLog(const char* file, int line, const char* message, int error);
+
+} // namespace internal
+} // namespace crashpad
+
+#define RAW_LOG(message) \
+ ::crashpad::internal::RawLog(__FILE__, __LINE__, message, 0)
+
+#define RAW_LOG_ERROR(error, message) \
+ ::crashpad::internal::RawLog(__FILE__, __LINE__, message, error)
+
+#endif // CRASHPAD_UTIL_IOS_EXCEPTION_LOGGING_H_
diff --git a/util/ios/scoped_vm_read.cc b/util/ios/scoped_vm_read.cc
new file mode 100644
index 0000000..33c2ad0
--- /dev/null
+++ b/util/ios/scoped_vm_read.cc
@@ -0,0 +1,62 @@
+// 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 "util/ios/scoped_vm_read.h"
+
+#include "util/ios/raw_logging.h"
+
+namespace crashpad {
+namespace internal {
+
+ScopedVMReadInternal::ScopedVMReadInternal()
+ : data_(0), vm_read_data_(0), vm_read_data_count_(0) {}
+
+ScopedVMReadInternal::~ScopedVMReadInternal() {
+ if (data_) {
+ kern_return_t kr =
+ vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_);
+ if (kr != KERN_SUCCESS)
+ RAW_LOG_ERROR(kr, "vm_deallocate");
+ }
+}
+
+bool ScopedVMReadInternal::Read(const void* data, const size_t data_length) {
+ if (data_) {
+ kern_return_t kr =
+ vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_);
+ if (kr != KERN_SUCCESS)
+ RAW_LOG_ERROR(kr, "vm_deallocate");
+ data_ = 0;
+ }
+ vm_address_t data_address = reinterpret_cast<vm_address_t>(data);
+ vm_address_t page_region_address = trunc_page(data_address);
+ vm_size_t page_region_size =
+ round_page(data_address - page_region_address + data_length);
+ kern_return_t kr = vm_read(mach_task_self(),
+ page_region_address,
+ page_region_size,
+ &vm_read_data_,
+ &vm_read_data_count_);
+
+ if (kr == KERN_SUCCESS) {
+ data_ = vm_read_data_ + (data_address - page_region_address);
+ return true;
+ } else {
+ RAW_LOG_ERROR(kr, "vm_read");
+ return false;
+ }
+}
+
+} // namespace internal
+} // namespace crashpad
diff --git a/util/ios/scoped_vm_read.h b/util/ios/scoped_vm_read.h
new file mode 100644
index 0000000..86d8039
--- /dev/null
+++ b/util/ios/scoped_vm_read.h
@@ -0,0 +1,96 @@
+// 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_UTIL_IOS_SCOPED_VM_READ_H_
+#define CRASHPAD_UTIL_IOS_SCOPED_VM_READ_H_
+
+#include <mach/mach.h>
+
+#include "base/macros.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief Non-templated internal class to be used by ScopedVMRead.
+//!
+//! Note: RUNS-DURING-CRASH.
+class ScopedVMReadInternal {
+ public:
+ ScopedVMReadInternal();
+ ~ScopedVMReadInternal();
+
+ //! \brief Releases any previously read data and vm_reads \a data. Logs an
+ //! error on failure.
+ //!
+ //! \param[in] data Memory to be read by vm_read.
+ //! \param[in] data_length Length of \a data.
+ //!
+ //! \return `true` if all the data was read. Logs an error and returns false
+ //! on failure
+ bool Read(const void* data, size_t data_length);
+
+ vm_address_t data() const { return data_; }
+
+ private:
+ // The address of the requested data.
+ vm_address_t data_;
+
+ // The rounded down page boundary of the requested data.
+ vm_address_t vm_read_data_;
+
+ // The size of the pages that were actually read.
+ mach_msg_type_number_t vm_read_data_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedVMReadInternal);
+};
+
+//! \brief A scoped wrapper for calls to `vm_read` and `vm_deallocate`. Allows
+//! in-process handler to safely read memory for the intermediate dump.
+//!
+//! Note: RUNS-DURING-CRASH.
+template <typename T>
+class ScopedVMRead {
+ public:
+ ScopedVMRead() : internal_() {}
+ ~ScopedVMRead() {}
+
+ //! \brief Releases any previously read data and vm_reads data.
+ //!
+ //! \param[in] data Memory to be read by vm_read.
+ //! \param[in] count Length of \a data.
+ //!
+ //! \return `true` if all the data was read. Logs an error and returns false
+ //! on failure
+ bool Read(const void* data, size_t count = 1) {
+ size_t data_length = count * sizeof(T);
+ return internal_.Read(data, data_length);
+ }
+
+ //! \brief Returns the pointer to memory safe to read during the in-process
+ //! crash handler.
+ T* operator->() const { return get(); }
+
+ //! \brief Returns the pointer to memory safe to read during the in-process
+ //! crash handler.
+ T* get() const { return reinterpret_cast<T*>(internal_.data()); }
+
+ private:
+ ScopedVMReadInternal internal_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedVMRead);
+};
+
+} // namespace internal
+} // namespace crashpad
+
+#endif // CRASHPAD_UTIL_IOS_SCOPED_VM_READ_H_
diff --git a/util/ios/scoped_vm_read_test.cc b/util/ios/scoped_vm_read_test.cc
new file mode 100644
index 0000000..80edf04
--- /dev/null
+++ b/util/ios/scoped_vm_read_test.cc
@@ -0,0 +1,83 @@
+// 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 "util/ios/scoped_vm_read.h"
+
+#include <sys/time.h>
+
+#include "base/mac/scoped_mach_vm.h"
+#include "gtest/gtest.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(ScopedVMReadTest, BasicFunctionality) {
+ // bad data or count.
+ internal::ScopedVMRead<vm_address_t> vmread_bad;
+ ASSERT_FALSE(vmread_bad.Read(nullptr, 100));
+ ASSERT_FALSE(vmread_bad.Read(reinterpret_cast<void*>(0x1000), 100));
+ vm_address_t address = 1;
+ ASSERT_FALSE(vmread_bad.Read(&address, 1000000000));
+
+ // array
+ constexpr char read_me[] = "read me";
+ internal::ScopedVMRead<char> vmread_string;
+ ASSERT_TRUE(vmread_string.Read(read_me, strlen(read_me)));
+ EXPECT_STREQ(read_me, vmread_string.get());
+
+ // struct
+ timeval time_of_day;
+ EXPECT_TRUE(gettimeofday(&time_of_day, nullptr) == 0);
+ internal::ScopedVMRead<timeval> vmread_time;
+ ASSERT_TRUE(vmread_time.Read(&time_of_day));
+ EXPECT_EQ(time_of_day.tv_sec, vmread_time->tv_sec);
+ EXPECT_EQ(time_of_day.tv_usec, vmread_time->tv_usec);
+
+ // reset.
+ timeval time_of_day2;
+ EXPECT_TRUE(gettimeofday(&time_of_day2, nullptr) == 0);
+ ASSERT_TRUE(vmread_time.Read(&time_of_day2));
+ EXPECT_EQ(time_of_day2.tv_sec, vmread_time->tv_sec);
+ EXPECT_EQ(time_of_day2.tv_usec, vmread_time->tv_usec);
+}
+
+TEST(ScopedVMReadTest, MissingMiddleVM) {
+ char* region;
+ vm_size_t page_size = getpagesize();
+ vm_size_t region_size = page_size * 3;
+ ASSERT_EQ(vm_allocate(mach_task_self(),
+ reinterpret_cast<vm_address_t*>(®ion),
+ region_size,
+ VM_FLAGS_ANYWHERE),
+ 0);
+ base::mac::ScopedMachVM vm_owner(reinterpret_cast<vm_address_t>(region),
+ region_size);
+
+ internal::ScopedVMRead<char> vmread_missing_middle;
+ ASSERT_TRUE(vmread_missing_middle.Read(region, region_size));
+
+ // Dealloc middle page.
+ ASSERT_EQ(vm_deallocate(mach_task_self(),
+ reinterpret_cast<vm_address_t>(region + page_size),
+ page_size),
+ 0);
+
+ ASSERT_FALSE(vmread_missing_middle.Read(region, region_size));
+ ASSERT_TRUE(vmread_missing_middle.Read(region, page_size));
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad