mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-08 21:26:04 +00:00
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:
parent
859560f70d
commit
9f6d86742d
@ -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
112
util/mach/task_memory.cc
Normal 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, ®ion, ®ion_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, ®ion_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
96
util/mach/task_memory.h
Normal 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 task’s 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 task’s 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 task’s
|
||||
//! 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 task’s 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 task’s 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_
|
386
util/mach/task_memory_test.cc
Normal file
386
util/mach/task_memory_test.cc
Normal 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 doesn’t 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 doesn’t 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) {
|
||||
// Don’t 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) {
|
||||
// Don’t 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) {
|
||||
// Don’t 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) {
|
||||
// Don’t 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
38
util/test/errors.cc
Normal 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
71
util/test/errors.h
Normal 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 don’t 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_
|
78
util/test/mac/mach_errors.cc
Normal file
78
util/test/mac/mach_errors.cc
Normal 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
|
70
util/test/mac/mach_errors.h
Normal file
70
util/test/mac/mach_errors.h
Normal 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 don’t 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_
|
@ -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',
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user