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*>(&region),
+                        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