diff --git a/crashpad.doxy.h b/crashpad.doxy.h index f4dc58d6..5bfefedd 100644 --- a/crashpad.doxy.h +++ b/crashpad.doxy.h @@ -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. diff --git a/util/mach/task_memory.cc b/util/mach/task_memory.cc new file mode 100644 index 00000000..9597165f --- /dev/null +++ b/util/mach/task_memory.cc @@ -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 +#include + +#include + +#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(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 diff --git a/util/mach/task_memory.h b/util/mach/task_memory.h new file mode 100644 index 00000000..6354862a --- /dev/null +++ b/util/mach/task_memory.h @@ -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 + +#include + +#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_ diff --git a/util/mach/task_memory_test.cc b/util/mach/task_memory_test.cc new file mode 100644 index 00000000..6438089a --- /dev/null +++ b/util/mach/task_memory_test.cc @@ -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 + +#include +#include + +#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(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(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(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(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(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 diff --git a/util/test/errors.cc b/util/test/errors.cc new file mode 100644 index 00000000..9c1ac2b7 --- /dev/null +++ b/util/test/errors.cc @@ -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 + +#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 diff --git a/util/test/errors.h b/util/test/errors.h new file mode 100644 index 00000000..366acf1f --- /dev/null +++ b/util/test/errors.h @@ -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 + +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_ diff --git a/util/test/mac/mach_errors.cc b/util/test/mac/mach_errors.cc new file mode 100644 index 00000000..54fd6072 --- /dev/null +++ b/util/test/mac/mach_errors.cc @@ -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 + +#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 + // . Otherwise, give it in hexadecimal to make it easier + // to visualize the various bits. See . + 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 . + 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 diff --git a/util/test/mac/mach_errors.h b/util/test/mac/mach_errors.h new file mode 100644 index 00000000..2b1fd853 --- /dev/null +++ b/util/test/mac/mach_errors.h @@ -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 + +#include + +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_ diff --git a/util/util.gyp b/util/util.gyp index b25f1ff4..23acd937 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', ],