Add TaskMemory, which can read another Mach task’s memory, and its test.

This also adds MachErrorMessage(), a test-only function that’s a
dependency of TaskMemory’s test, and related test-only error message
functions.

TEST=util_test TaskMemory.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/438993002
This commit is contained in:
Mark Mentovai 2014-08-03 18:48:40 -04:00
parent 859560f70d
commit 9f6d86742d
9 changed files with 876 additions and 0 deletions

View File

@ -19,3 +19,6 @@
//! \namespace crashpad::internal
//! \brief The internal namespace, not for public use.
//! \namespace crashpad::test
//! \brief The testing namespace, for use in test code only.

112
util/mach/task_memory.cc Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2014 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/mach/task_memory.h"
#include <mach/mach_vm.h>
#include <string.h>
#include <algorithm>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_vm.h"
#include "base/strings/stringprintf.h"
namespace crashpad {
TaskMemory::TaskMemory(mach_port_t task) : task_(task) {
}
bool TaskMemory::Read(mach_vm_address_t address, size_t size, void* buffer) {
if (size == 0) {
return true;
}
mach_vm_address_t region_address = mach_vm_trunc_page(address);
mach_vm_size_t region_size =
mach_vm_round_page(address - region_address + size);
vm_offset_t region;
mach_msg_type_number_t region_count;
kern_return_t kr =
mach_vm_read(task_, region_address, region_size, &region, &region_count);
if (kr != KERN_SUCCESS) {
MACH_LOG(WARNING, kr) << base::StringPrintf(
"mach_vm_read(0x%llx, 0x%llx)", region_address, region_size);
return false;
}
DCHECK_EQ(region_count, region_size);
base::mac::ScopedMachVM region_owner(region, region_count);
const char* region_base = reinterpret_cast<const char*>(region);
memcpy(buffer, &region_base[address - region_address], size);
return true;
}
bool TaskMemory::ReadCString(mach_vm_address_t address, std::string* string) {
return ReadCStringInternal(address, false, 0, string);
}
bool TaskMemory::ReadCStringSizeLimited(mach_vm_address_t address,
mach_vm_size_t size,
std::string* string) {
return ReadCStringInternal(address, true, size, string);
}
bool TaskMemory::ReadCStringInternal(mach_vm_address_t address,
bool has_size,
mach_vm_size_t size,
std::string* string) {
if (has_size) {
if (size == 0) {
string->clear();
return true;
}
} else {
size = PAGE_SIZE;
}
std::string local_string;
mach_vm_address_t read_address = address;
do {
mach_vm_size_t read_length =
std::min(size, PAGE_SIZE - (read_address % PAGE_SIZE));
std::string read_string(read_length, '\0');
if (!Read(read_address, read_length, &read_string[0])) {
return false;
}
size_t terminator = read_string.find_first_of('\0');
if (terminator == std::string::npos) {
local_string.append(read_string);
} else {
local_string.append(read_string, 0, terminator);
string->swap(local_string);
return true;
}
if (has_size) {
size -= read_length;
}
read_address = mach_vm_trunc_page(read_address + read_length);
} while ((!has_size || size > 0) && read_address > address);
LOG(WARNING) << base::StringPrintf("unterminated string at 0x%llx", address);
return false;
}
} // namespace crashpad

96
util/mach/task_memory.h Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2014 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_MACH_TASK_MEMORY_H_
#define CRASHPAD_UTIL_MACH_TASK_MEMORY_H_
#include <mach/mach.h>
#include <string>
#include "base/basictypes.h"
namespace crashpad {
//! \brief Accesses the memory of another Mach task.
class TaskMemory {
public:
//! \param[in] task A send right to the target tasks task port. This object
//! does not take ownership of the send right.
explicit TaskMemory(mach_port_t task);
~TaskMemory() {}
//! \brief Copies memory from the target task into a user-provided buffer in
//! the current task.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! memory region to copy.
//! \param[in] size The size, in bytes, of the memory region to copy. \a
//! buffer must be at least this size.
//! \param[out] buffer The buffer into which the contents of the other tasks
//! memory will be copied.
//!
//! \return `true` on success, with \a buffer filled appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! encountering unmapped or unreadable pages.
bool Read(mach_vm_address_t address, size_t size, void* buffer);
//! \brief Reads a `NUL`-terminated C string from the target task into a
//! string in the current task.
//!
//! The length of the string need not be known ahead of time. This method will
//! read contiguous memory until a `NUL` terminator is found.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! string to copy.
//! \param[out] string The string read from the other task.
//!
//! \return `true` on success, with \a string set appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! encountering unmapped or unreadable pages.
bool ReadCString(mach_vm_address_t address, std::string* string);
//! \brief Reads a `NUL`-terminated C string from the target task into a
//! string in the current task.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! string to copy.
//! \param[in] size The maximum number of bytes to read. The string is
//! required to be `NUL`-terminated within this many bytes.
//! \param[out] string The string read from the other task.
//!
//! \return `true` on success, with \a string set appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! a `NUL` terminator is not found within \a size bytes, or when
//! encountering unmapped or unreadable pages.
bool ReadCStringSizeLimited(mach_vm_address_t address,
mach_vm_size_t size,
std::string* string);
private:
// The common internal implementation shared by the ReadCString*() methods.
bool ReadCStringInternal(mach_vm_address_t address,
bool has_size,
mach_vm_size_t size,
std::string* string);
mach_port_t task_; // weak
DISALLOW_COPY_AND_ASSIGN(TaskMemory);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_MACH_TASK_MEMORY_H_

View File

@ -0,0 +1,386 @@
// Copyright 2014 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/mach/task_memory.h"
#include <mach/mach.h>
#include <algorithm>
#include <string>
#include "base/mac/scoped_mach_vm.h"
#include "gtest/gtest.h"
#include "util/test/mac/mach_errors.h"
namespace {
using namespace crashpad;
using namespace crashpad::test;
TEST(TaskMemory, ReadSelf) {
vm_address_t address = 0;
const vm_size_t kSize = 4 * PAGE_SIZE;
kern_return_t kr =
vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_allocate");
base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
char* region = reinterpret_cast<char*>(address);
for (size_t index = 0; index < kSize; ++index) {
region[index] = (index % 256) ^ ((index >> 8) % 256);
}
TaskMemory memory(mach_task_self());
std::string result(kSize, '\0');
// Ensure that the entire region can be read.
ASSERT_TRUE(memory.Read(address, kSize, &result[0]));
EXPECT_EQ(0, memcmp(region, &result[0], kSize));
// Ensure that a read of length 0 succeeds and doesnt touch the result.
result.assign(kSize, '\0');
std::string zeroes = result;
ASSERT_TRUE(memory.Read(address, 0, &result[0]));
EXPECT_EQ(zeroes, result);
// Ensure that a read starting at an unaligned address works.
ASSERT_TRUE(memory.Read(address + 1, kSize - 1, &result[0]));
EXPECT_EQ(0, memcmp(region + 1, &result[0], kSize - 1));
// Ensure that a read ending at an unaligned address works.
ASSERT_TRUE(memory.Read(address, kSize - 1, &result[0]));
EXPECT_EQ(0, memcmp(region, &result[0], kSize - 1));
// Ensure that a read starting and ending at unaligned addresses works.
ASSERT_TRUE(memory.Read(address + 1, kSize - 2, &result[0]));
EXPECT_EQ(0, memcmp(region + 1, &result[0], kSize - 2));
// Ensure that a read of exactly one page works.
ASSERT_TRUE(memory.Read(address + PAGE_SIZE, PAGE_SIZE, &result[0]));
EXPECT_EQ(0, memcmp(region + PAGE_SIZE, &result[0], PAGE_SIZE));
// Ensure that a read of a single byte works.
ASSERT_TRUE(memory.Read(address + 2, 1, &result[0]));
EXPECT_EQ(region[2], result[0]);
// Ensure that a read of length zero works and doesnt touch the data.
result[0] = 'M';
ASSERT_TRUE(memory.Read(address + 3, 0, &result[0]));
EXPECT_EQ('M', result[0]);
}
TEST(TaskMemory, ReadSelfUnmapped) {
vm_address_t address = 0;
const vm_size_t kSize = 2 * PAGE_SIZE;
kern_return_t kr =
vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_allocate");
base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
char* region = reinterpret_cast<char*>(address);
for (size_t index = 0; index < kSize; ++index) {
// Dont include any NUL bytes, because ReadCString stops when it encounters
// a NUL.
region[index] = (index % 255) + 1;
}
kr = vm_protect(
mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_protect");
TaskMemory memory(mach_task_self());
std::string result(kSize, '\0');
EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
// Repeat the test with an unmapped page instead of an unreadable one. This
// portion of the test may be flaky in the presence of other threads, if
// another thread maps something in the region that is deallocated here.
kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_deallocate");
vm_owner.reset(address, PAGE_SIZE);
EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
}
// This function consolidates the cast from a char* to mach_vm_address_t in one
// location when reading from the current task.
bool ReadCStringSelf(TaskMemory* memory,
const char* pointer,
std::string* result) {
return memory->ReadCString(reinterpret_cast<mach_vm_address_t>(pointer),
result);
}
TEST(TaskMemory, ReadCStringSelf) {
TaskMemory memory(mach_task_self());
std::string result;
const char kConstCharEmpty[] = "";
ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharEmpty, &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kConstCharEmpty, result);
const char kConstCharShort[] = "A short const char[]";
ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharShort, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kConstCharShort, result);
static const char kStaticConstCharEmpty[] = "";
ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharEmpty, &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kStaticConstCharEmpty, result);
static const char kStaticConstCharShort[] = "A short static const char[]";
ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharShort, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStaticConstCharShort, result);
std::string string_short("A short std::string in a function");
ASSERT_TRUE(ReadCStringSelf(&memory, &string_short[0], &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(string_short, result);
std::string string_long;
const size_t kStringLongSize = 4 * PAGE_SIZE;
for (size_t index = 0; index < kStringLongSize; ++index) {
// Dont include any NUL bytes, because ReadCString stops when it encounters
// a NUL.
string_long.append(1, (index % 255) + 1);
}
ASSERT_EQ(kStringLongSize, string_long.size());
ASSERT_TRUE(ReadCStringSelf(&memory, &string_long[0], &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStringLongSize, result.size());
EXPECT_EQ(string_long, result);
}
TEST(TaskMemory, ReadCStringSelfUnmapped) {
vm_address_t address = 0;
const vm_size_t kSize = 2 * PAGE_SIZE;
kern_return_t kr =
vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_allocate");
base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
char* region = reinterpret_cast<char*>(address);
for (size_t index = 0; index < kSize; ++index) {
// Dont include any NUL bytes, because ReadCString stops when it encounters
// a NUL.
region[index] = (index % 255) + 1;
}
kr = vm_protect(
mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_protect");
TaskMemory memory(mach_task_self());
std::string result;
EXPECT_FALSE(memory.ReadCString(address, &result));
// Make sure that if the string is NUL-terminated within the mapped memory
// region, it can be read properly.
char terminator_or_not = '\0';
std::swap(region[PAGE_SIZE - 1], terminator_or_not);
ASSERT_TRUE(memory.ReadCString(address, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(PAGE_SIZE - 1u, result.size());
EXPECT_EQ(region, result);
// Repeat the test with an unmapped page instead of an unreadable one. This
// portion of the test may be flaky in the presence of other threads, if
// another thread maps something in the region that is deallocated here.
std::swap(region[PAGE_SIZE - 1], terminator_or_not);
kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_deallocate");
vm_owner.reset(address, PAGE_SIZE);
EXPECT_FALSE(memory.ReadCString(address, &result));
// Clear the result before testing that the string can be read. This makes
// sure that the result is actually filled in, because it already contains the
// expected value from the tests above.
result.clear();
std::swap(region[PAGE_SIZE - 1], terminator_or_not);
ASSERT_TRUE(memory.ReadCString(address, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(PAGE_SIZE - 1u, result.size());
EXPECT_EQ(region, result);
}
// This function consolidates the cast from a char* to mach_vm_address_t in one
// location when reading from the current task.
bool ReadCStringSizeLimitedSelf(TaskMemory* memory,
const char* pointer,
size_t size,
std::string* result) {
return memory->ReadCStringSizeLimited(
reinterpret_cast<mach_vm_address_t>(pointer), size, result);
}
TEST(TaskMemory, ReadCStringSizeLimited_ConstCharEmpty) {
TaskMemory memory(mach_task_self());
std::string result;
const char kConstCharEmpty[] = "";
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, kConstCharEmpty, arraysize(kConstCharEmpty), &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kConstCharEmpty, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, kConstCharEmpty, arraysize(kConstCharEmpty) + 1, &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kConstCharEmpty, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, kConstCharEmpty, 0, &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kConstCharEmpty, result);
}
TEST(TaskMemory, ReadCStringSizeLimited_ConstCharShort) {
TaskMemory memory(mach_task_self());
std::string result;
const char kConstCharShort[] = "A short const char[]";
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, kConstCharShort, arraysize(kConstCharShort), &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kConstCharShort, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, kConstCharShort, arraysize(kConstCharShort) + 1, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kConstCharShort, result);
ASSERT_FALSE(ReadCStringSizeLimitedSelf(
&memory, kConstCharShort, arraysize(kConstCharShort) - 1, &result));
}
TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharEmpty) {
TaskMemory memory(mach_task_self());
std::string result;
static const char kStaticConstCharEmpty[] = "";
ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
kStaticConstCharEmpty,
arraysize(kStaticConstCharEmpty),
&result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kStaticConstCharEmpty, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
kStaticConstCharEmpty,
arraysize(kStaticConstCharEmpty) + 1,
&result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kStaticConstCharEmpty, result);
result.clear();
ASSERT_TRUE(
ReadCStringSizeLimitedSelf(&memory, kStaticConstCharEmpty, 0, &result));
EXPECT_TRUE(result.empty());
EXPECT_EQ(kStaticConstCharEmpty, result);
}
TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharShort) {
TaskMemory memory(mach_task_self());
std::string result;
static const char kStaticConstCharShort[] = "A short static const char[]";
ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
kStaticConstCharShort,
arraysize(kStaticConstCharShort),
&result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStaticConstCharShort, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
kStaticConstCharShort,
arraysize(kStaticConstCharShort) + 1,
&result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStaticConstCharShort, result);
ASSERT_FALSE(ReadCStringSizeLimitedSelf(&memory,
kStaticConstCharShort,
arraysize(kStaticConstCharShort) - 1,
&result));
}
TEST(TaskMemory, ReadCStringSizeLimited_StringShort) {
TaskMemory memory(mach_task_self());
std::string result;
std::string string_short("A short std::string in a function");
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, &string_short[0], string_short.size() + 1, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(string_short, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, &string_short[0], string_short.size() + 2, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(string_short, result);
ASSERT_FALSE(ReadCStringSizeLimitedSelf(
&memory, &string_short[0], string_short.size(), &result));
}
TEST(TaskMemory, ReadCStringSizeLimited_StringLong) {
TaskMemory memory(mach_task_self());
std::string result;
std::string string_long;
const size_t kStringLongSize = 4 * PAGE_SIZE;
for (size_t index = 0; index < kStringLongSize; ++index) {
// Dont include any NUL bytes, because ReadCString stops when it encounters
// a NUL.
string_long.append(1, (index % 255) + 1);
}
ASSERT_EQ(kStringLongSize, string_long.size());
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, &string_long[0], string_long.size() + 1, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStringLongSize, result.size());
EXPECT_EQ(string_long, result);
result.clear();
ASSERT_TRUE(ReadCStringSizeLimitedSelf(
&memory, &string_long[0], string_long.size() + 2, &result));
EXPECT_FALSE(result.empty());
EXPECT_EQ(kStringLongSize, result.size());
EXPECT_EQ(string_long, result);
ASSERT_FALSE(ReadCStringSizeLimitedSelf(
&memory, &string_long[0], string_long.size(), &result));
}
} // namespace

38
util/test/errors.cc Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2014 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/test/errors.h"
#include <errno.h>
#include "base/safe_strerror_posix.h"
#include "base/strings/stringprintf.h"
namespace crashpad {
namespace test {
std::string ErrnoMessage(int err, const std::string& base) {
return base::StringPrintf("%s%s%s (%d)",
base.c_str(),
base.empty() ? "" : ": ",
safe_strerror(errno).c_str(),
err);
}
std::string ErrnoMessage(const std::string& base) {
return ErrnoMessage(errno, base);
}
} // namespace test
} // namespace crashpad

71
util/test/errors.h Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2014 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_TEST_ERRORS_H_
#define CRASHPAD_UTIL_TEST_ERRORS_H_
#include <string>
namespace crashpad {
namespace test {
// These functions format messages in a similar way to the PLOG and PCHECK
// family of logging macros in base/logging.h. They exist to interoperate with
// gtest assertions, which dont interoperate with logging but can be streamed
// to.
//
// Where non-test code could do:
// PCHECK(rv == 0) << "close";
// gtest-based test code can do:
// EXPECT_EQ(0, rv) << ErrnoMessage("close");
//! \brief Formats an error message using an `errno` value.
//!
//! The returned string will combine the \a base string, if supplied, with a
//! a textual and numeric description of the error.
//!
//! The message is formatted using `strerror()`. \a err may be `0` or outside of
//! the range of known error codes, and the message returned will contain the
//! string that `strerror()` uses in these cases.
//!
//! \param[in] err The error code, usable as an `errno` value.
//! \param[in] base A string to prepend to the error description.
//!
//! \return A string of the format `"Operation not permitted (1)"` if \a err has
//! the value `EPERM` on a system where this is defined to be `1`. If \a
//! base is not empty, it will be prepended to this string, separated by a
//! colon.
std::string ErrnoMessage(int err, const std::string& base = std::string());
//! \brief Formats an error message using `errno`.
//!
//! The returned string will combine the \a base string, if supplied, with a
//! a textual and numeric description of the error.
//!
//! The message is formatted using `strerror()`. `errno` may be `0` or outside
//! of the range of known error codes, and the message returned will contain the
//! string that `strerror()` uses in these cases.
//!
//! \param[in] base A string to prepend to the error description.
//!
//! \return A string of the format `"Operation not permitted (1)"` if `errno`
//! has the value `EPERM` on a system where this is defined to be `1`. If
//! \a base is not empty, it will be prepended to this string, separated by
//! a colon.
std::string ErrnoMessage(const std::string& base = std::string());
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_UTIL_TEST_ERRORS_H_

View File

@ -0,0 +1,78 @@
// Copyright 2014 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/test/mac/mach_errors.h"
#include <servers/bootstrap.h>
#include "base/safe_strerror_posix.h"
#include "base/strings/stringprintf.h"
namespace {
std::string FormatBase(const std::string& base) {
if (base.empty()) {
return std::string();
}
return base::StringPrintf("%s: ", base.c_str());
}
std::string FormatMachErrorNumber(mach_error_t mach_err) {
// For the os/kern subsystem, give the error number in decimal as in
// <mach/kern_return.h>. Otherwise, give it in hexadecimal to make it easier
// to visualize the various bits. See <mach/error.h>.
if (mach_err >= 0 && mach_err < KERN_RETURN_MAX) {
return base::StringPrintf(" (%d)", mach_err);
}
return base::StringPrintf(" (0x%08x)", mach_err);
}
} // namespace
namespace crashpad {
namespace test {
std::string MachErrorMessage(mach_error_t mach_err, const std::string& base) {
return base::StringPrintf("%s%s%s",
FormatBase(base).c_str(),
mach_error_string(mach_err),
FormatMachErrorNumber(mach_err).c_str());
}
std::string BootstrapErrorMessage(kern_return_t bootstrap_err,
const std::string& base) {
switch (bootstrap_err) {
case BOOTSTRAP_SUCCESS:
case BOOTSTRAP_NOT_PRIVILEGED:
case BOOTSTRAP_NAME_IN_USE:
case BOOTSTRAP_UNKNOWN_SERVICE:
case BOOTSTRAP_SERVICE_ACTIVE:
case BOOTSTRAP_BAD_COUNT:
case BOOTSTRAP_NO_MEMORY:
case BOOTSTRAP_NO_CHILDREN:
// Show known bootstrap errors in decimal because that's how they're
// defined in <servers/bootstrap.h>.
return base::StringPrintf("%s%s (%d)",
FormatBase(base).c_str(),
bootstrap_strerror(bootstrap_err),
bootstrap_err);
default:
return MachErrorMessage(bootstrap_err, base);
}
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,70 @@
// Copyright 2014 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_TEST_MAC_MACH_ERRORS_H_
#define CRASHPAD_UTIL_TEST_MAC_MACH_ERRORS_H_
#include <mach/mach.h>
#include <string>
namespace crashpad {
namespace test {
// These functions format messages in a similar way to the logging macros in
// base/mac/mach_logging.h. They exist to interoperate with gtest assertions,
// which dont interoperate with logging but can be streamed to.
//
// Where non-test code could do:
// MACH_CHECK(kr == KERN_SUCCESS, kr) << "vm_deallocate";
// gtest-based test code can do:
// EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "vm_deallocate");
//! \brief Formats a Mach error message.
//!
//! The returned string will combine the \a base string, if supplied, with a
//! a textual and numeric description of the error.
//!
//! \param[in] mach_err The Mach error code, which may be a `kern_return_t` or
//! related type.
//! \param[in] base A string to prepend to the error description.
//!
//! \return A string of the format `"(os/kern) invalid address (1)"` if \a
//! mach_err has the value `KERN_INVALID_ADDRESS` on a system where this is
//! defined to be 1. If \a base is not empty, it will be prepended to this
//! string, separated by a colon.
std::string MachErrorMessage(mach_error_t mach_err,
const std::string& base = std::string());
//! \brief Formats a bootstrap error message.
//!
//! The returned string will combine the \a base string, if supplied, with a
//! a textual and numeric description of the error.
//!
//! \param[in] bootstrap_err The bootstrap error code.
//! \param[in] base A string to prepend to the error description.
//!
//! \return A string of the format `"Permission denied (1100)"` if \a
//! bootstrap_err has the value `BOOTSTRAP_NOT_PRIVILEGED` on a system where
//! this is defined to be 1100. If \a base is not empty, it will be
//! prepended to this string, separated by a colon. If \a bootstrap_err is
//! not a valid bootstrap error code, it will be interpreted as a Mach error
//! code in the manner of MachErrorMessage().
std::string BootstrapErrorMessage(kern_return_t bootstrap_err,
const std::string& base = std::string());
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_UTIL_TEST_MAC_MACH_ERRORS_H_

View File

@ -32,6 +32,8 @@
'file/file_writer.h',
'file/string_file_writer.cc',
'file/string_file_writer.h',
'mach/task_memory.cc',
'mach/task_memory.h',
'misc/uuid.cc',
'misc/uuid.h',
'stdlib/cxx.h',
@ -39,11 +41,30 @@
'stdlib/strlcpy.h',
],
},
{
'target_name': 'util_test_lib',
'type': 'static_library',
'dependencies': [
'../compat/compat.gyp:compat',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
'util',
],
'include_dirs': [
'..',
],
'sources': [
'test/errors.cc',
'test/errors.h',
'test/mac/mach_errors.cc',
'test/mac/mach_errors.h',
],
},
{
'target_name': 'util_test',
'type': 'executable',
'dependencies': [
'util',
'util_test_lib',
'../compat/compat.gyp:compat',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
@ -54,6 +75,7 @@
'sources': [
'../third_party/gtest/gtest/src/gtest_main.cc',
'file/string_file_writer_test.cc',
'mach/task_memory_test.cc',
'misc/uuid_test.cc',
'stdlib/strlcpy_test.cc',
],