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: I658f3181cbec40a79e304b7306466e10c003564f
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>
This commit is contained in:
Justin Cohen 2021-05-07 14:32:20 -04:00 committed by Commit Bot
parent 0c3d6e5fa1
commit d5b3a1d531
6 changed files with 352 additions and 1 deletions

View File

@ -384,6 +384,10 @@ crashpad_static_library("util") {
"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 @@ source_set("util_test") {
}
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",

66
util/ios/raw_logging.cc Normal file
View File

@ -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

37
util/ios/raw_logging.h Normal file
View File

@ -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_

View File

@ -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

96
util/ios/scoped_vm_read.h Normal file
View File

@ -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_

View File

@ -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