mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2026-01-01 20:24:40 +01:00
Write a window of memory around the instruction pointer from the crashing thread to the minidump on OS X.
R=nealsid at http://breakpad.appspot.com/200001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@699 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
cec12872c4
commit
4621ee0691
14 changed files with 930 additions and 18 deletions
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
// exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
|
@ -36,6 +37,14 @@
|
|||
#include "client/mac/handler/exception_handler.h"
|
||||
#include "client/mac/tests/auto_tempdir.h"
|
||||
#include "common/mac/MachIPC.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
// This acts as the log sink for INFO logging from the processor
|
||||
// logging code. The logging output confuses XCode and makes it think
|
||||
// there are unit test failures. testlogging.h handles the overriding.
|
||||
std::ostringstream info_log;
|
||||
}
|
||||
|
||||
namespace {
|
||||
using std::string;
|
||||
|
|
@ -44,6 +53,11 @@ using google_breakpad::ExceptionHandler;
|
|||
using google_breakpad::MachPortSender;
|
||||
using google_breakpad::MachReceiveMessage;
|
||||
using google_breakpad::MachSendMessage;
|
||||
using google_breakpad::Minidump;
|
||||
using google_breakpad::MinidumpContext;
|
||||
using google_breakpad::MinidumpException;
|
||||
using google_breakpad::MinidumpMemoryList;
|
||||
using google_breakpad::MinidumpMemoryRegion;
|
||||
using google_breakpad::ReceivePort;
|
||||
using testing::Test;
|
||||
|
||||
|
|
@ -80,7 +94,6 @@ static bool MDCallback(const char *dump_dir, const char *file_name,
|
|||
}
|
||||
|
||||
TEST_F(ExceptionHandlerTest, InProcess) {
|
||||
AutoTempDir tempDir;
|
||||
// Give the child process a pipe to report back on.
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, pipe(fds));
|
||||
|
|
@ -167,8 +180,8 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
|
|||
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
|
||||
mach_port_t child_task = child_message.GetTranslatedPort(0);
|
||||
mach_port_t child_thread = child_message.GetTranslatedPort(1);
|
||||
ASSERT_NE(MACH_PORT_NULL, child_task);
|
||||
ASSERT_NE(MACH_PORT_NULL, child_thread);
|
||||
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
|
||||
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread);
|
||||
|
||||
// Write a minidump of the child process.
|
||||
bool result = ExceptionHandler::WriteMinidumpForChild(child_task,
|
||||
|
|
@ -195,4 +208,390 @@ TEST_F(ExceptionHandlerTest, DumpChildProcess) {
|
|||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||
}
|
||||
|
||||
// Test that memory around the instruction pointer is written
|
||||
// to the dump as a MinidumpMemoryRegion.
|
||||
TEST_F(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
// Give the child process a pipe to report back on.
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = kMemorySize / 2;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them in the middle
|
||||
// of the block of memory, because the minidump should contain 128
|
||||
// bytes on either side of the instruction pointer.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
// not reached
|
||||
exit(1);
|
||||
}
|
||||
// In the parent process.
|
||||
ASSERT_NE(-1, pid);
|
||||
close(fds[1]);
|
||||
|
||||
// Wait for the background process to return the minidump file.
|
||||
close(fds[1]);
|
||||
char minidump_file[PATH_MAX];
|
||||
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
|
||||
ASSERT_NE(0, nbytes);
|
||||
// Ensure that minidump file exists and is > 0 bytes.
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(minidump_file, &st));
|
||||
ASSERT_LT(0, st.st_size);
|
||||
|
||||
// Child process should have exited with a zero status.
|
||||
int ret;
|
||||
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
|
||||
EXPECT_NE(0, WIFEXITED(ret));
|
||||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_file);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_NE((unsigned int)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
EXPECT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemorySize, region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t prefix_bytes[kOffset];
|
||||
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
|
||||
suffix_bytes, sizeof(suffix_bytes)) == 0);
|
||||
}
|
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the low end.
|
||||
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
// Give the child process a pipe to report back on.
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = 256; // bytes
|
||||
const int kOffset = 0;
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them at the start
|
||||
// of the block of memory, to ensure that the memory bounding
|
||||
// works properly.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
// not reached
|
||||
exit(1);
|
||||
}
|
||||
// In the parent process.
|
||||
ASSERT_NE(-1, pid);
|
||||
close(fds[1]);
|
||||
|
||||
// Wait for the background process to return the minidump file.
|
||||
close(fds[1]);
|
||||
char minidump_file[PATH_MAX];
|
||||
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
|
||||
ASSERT_NE(0, nbytes);
|
||||
// Ensure that minidump file exists and is > 0 bytes.
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(minidump_file, &st));
|
||||
ASSERT_LT(0, st.st_size);
|
||||
|
||||
// Child process should have exited with a zero status.
|
||||
int ret;
|
||||
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
|
||||
EXPECT_NE(0, WIFEXITED(ret));
|
||||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_file);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_NE((unsigned int)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
EXPECT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemorySize / 2, region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
|
||||
memset(suffix_bytes, 0, sizeof(suffix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
|
||||
suffix_bytes, sizeof(suffix_bytes)) == 0);
|
||||
}
|
||||
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the high end.
|
||||
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
// Give the child process a pipe to report back on.
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
// Use 4k here because the OS will hand out a single page even
|
||||
// if a smaller size is requested, and this test wants to
|
||||
// test the upper bound of the memory range.
|
||||
const u_int32_t kMemorySize = 4096; // bytes
|
||||
// This crashes with SIGILL on x86/x86-64/arm.
|
||||
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
|
||||
const int kOffset = kMemorySize - sizeof(instructions);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
if (!memory)
|
||||
exit(0);
|
||||
|
||||
// Write some instructions that will crash. Put them at the start
|
||||
// of the block of memory, to ensure that the memory bounding
|
||||
// works properly.
|
||||
memcpy(memory + kOffset, instructions, sizeof(instructions));
|
||||
|
||||
// Now execute the instructions, which should crash.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(memory + kOffset);
|
||||
memory_function();
|
||||
// not reached
|
||||
exit(1);
|
||||
}
|
||||
// In the parent process.
|
||||
ASSERT_NE(-1, pid);
|
||||
close(fds[1]);
|
||||
|
||||
// Wait for the background process to return the minidump file.
|
||||
close(fds[1]);
|
||||
char minidump_file[PATH_MAX];
|
||||
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
|
||||
ASSERT_NE(0, nbytes);
|
||||
// Ensure that minidump file exists and is > 0 bytes.
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(minidump_file, &st));
|
||||
ASSERT_LT(0, st.st_size);
|
||||
|
||||
// Child process should have exited with a zero status.
|
||||
int ret;
|
||||
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
|
||||
EXPECT_NE(0, WIFEXITED(ret));
|
||||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is a memory region
|
||||
// in the memory list that covers the instruction pointer from
|
||||
// the exception record.
|
||||
Minidump minidump(minidump_file);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_NE((unsigned int)0, memory_list->region_count());
|
||||
|
||||
MinidumpContext* context = exception->GetContext();
|
||||
ASSERT_TRUE(context);
|
||||
|
||||
u_int64_t instruction_pointer;
|
||||
switch (context->GetContextCPU()) {
|
||||
case MD_CONTEXT_X86:
|
||||
instruction_pointer = context->GetContextX86()->eip;
|
||||
break;
|
||||
case MD_CONTEXT_AMD64:
|
||||
instruction_pointer = context->GetContextAMD64()->rip;
|
||||
break;
|
||||
case MD_CONTEXT_ARM:
|
||||
instruction_pointer = context->GetContextARM()->iregs[15];
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
|
||||
break;
|
||||
}
|
||||
|
||||
MinidumpMemoryRegion* region =
|
||||
memory_list->GetMemoryRegionForAddress(instruction_pointer);
|
||||
EXPECT_TRUE(region);
|
||||
|
||||
const size_t kPrefixSize = 128; // bytes
|
||||
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
|
||||
const u_int8_t* bytes = region->GetMemory();
|
||||
ASSERT_TRUE(bytes);
|
||||
|
||||
u_int8_t prefix_bytes[kPrefixSize];
|
||||
memset(prefix_bytes, 0, sizeof(prefix_bytes));
|
||||
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
|
||||
EXPECT_TRUE(memcmp(bytes + kPrefixSize,
|
||||
instructions, sizeof(instructions)) == 0);
|
||||
}
|
||||
|
||||
// Ensure that an extra memory block doesn't get added when the
|
||||
// instruction pointer is not in mapped memory.
|
||||
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
||||
// Give the child process a pipe to report back on.
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, pipe(fds));
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
|
||||
// Try calling a NULL pointer.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
reinterpret_cast<void_function>(NULL);
|
||||
memory_function();
|
||||
// not reached
|
||||
exit(1);
|
||||
}
|
||||
// In the parent process.
|
||||
ASSERT_NE(-1, pid);
|
||||
close(fds[1]);
|
||||
|
||||
// Wait for the background process to return the minidump file.
|
||||
close(fds[1]);
|
||||
char minidump_file[PATH_MAX];
|
||||
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
|
||||
ASSERT_NE(0, nbytes);
|
||||
// Ensure that minidump file exists and is > 0 bytes.
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(minidump_file, &st));
|
||||
ASSERT_LT(0, st.st_size);
|
||||
|
||||
// Child process should have exited with a zero status.
|
||||
int ret;
|
||||
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
|
||||
EXPECT_NE(0, WIFEXITED(ret));
|
||||
EXPECT_EQ(0, WEXITSTATUS(ret));
|
||||
|
||||
// Read the minidump. Locate the exception record and the
|
||||
// memory list, and then ensure that there is only one memory region
|
||||
// in the memory list (the thread memory from the single thread).
|
||||
Minidump minidump(minidump_file);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpException* exception = minidump.GetException();
|
||||
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(exception);
|
||||
ASSERT_TRUE(memory_list);
|
||||
ASSERT_EQ((unsigned int)1, memory_list->region_count());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue