Allow adding extra memory regions to minidump on linux/windows

A=Bill McCloskey <wmccloskey@mozilla.com>, ted, original patch from https://bugzilla.mozilla.org/show_bug.cgi?id=662646
R=mark at https://breakpad.appspot.com/450002/

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1041 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
ted.mielczarek@gmail.com 2012-09-18 13:54:58 +00:00
parent 3682b31cbe
commit 61d9b9ff96
10 changed files with 447 additions and 37 deletions

View file

@ -495,13 +495,15 @@ bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
crashing_process, crashing_process,
context, context,
context_size, context_size,
mapping_list_); mapping_list_,
app_memory_list_);
} }
return google_breakpad::WriteMinidump(minidump_descriptor_.path(), return google_breakpad::WriteMinidump(minidump_descriptor_.path(),
crashing_process, crashing_process,
context, context,
context_size, context_size,
mapping_list_); mapping_list_,
app_memory_list_);
} }
// static // static
@ -562,4 +564,26 @@ void ExceptionHandler::AddMappingInfo(const string& name,
mapping_list_.push_back(mapping); mapping_list_.push_back(mapping);
} }
void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) {
AppMemoryList::iterator iter =
std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
if (iter != app_memory_list_.end()) {
// Don't allow registering the same pointer twice.
return;
}
AppMemory app_memory;
app_memory.ptr = ptr;
app_memory.length = length;
app_memory_list_.push_back(app_memory);
}
void ExceptionHandler::UnregisterAppMemory(void* ptr) {
AppMemoryList::iterator iter =
std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr);
if (iter != app_memory_list_.end()) {
app_memory_list_.erase(iter);
}
}
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -193,6 +193,13 @@ class ExceptionHandler {
size_t mapping_size, size_t mapping_size,
size_t file_offset); size_t file_offset);
// Register a block of memory of length bytes starting at address ptr
// to be copied to the minidump when a crash happens.
void RegisterAppMemory(void* ptr, size_t length);
// Unregister a block of memory that was registered with RegisterAppMemory.
void UnregisterAppMemory(void* ptr);
// Force signal handling for the specified signal. // Force signal handling for the specified signal.
bool SimulateSignalDelivery(int sig); bool SimulateSignalDelivery(int sig);
private: private:
@ -238,6 +245,10 @@ class ExceptionHandler {
// Callers can add extra info about mappings for cases where the // Callers can add extra info about mappings for cases where the
// dumper code cannot extract enough information from /proc/<pid>/maps. // dumper code cannot extract enough information from /proc/<pid>/maps.
MappingList mapping_list_; MappingList mapping_list_;
// Callers can request additional memory regions to be included in
// the dump.
AppMemoryList app_memory_list_;
}; };
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -939,6 +939,85 @@ TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithPath) {
ASSERT_TRUE(minidump2.Read()); ASSERT_TRUE(minidump2.Read());
unlink(minidump_2.path()); unlink(minidump_2.path());
// We expect 2 distinct files. // 2 distinct files should be produced.
ASSERT_STRNE(minidump_1_path.c_str(), minidump_2_path.c_str()); ASSERT_STRNE(minidump_1_path.c_str(), minidump_2_path.c_str());
} }
// Test that an additional memory region can be added to the minidump.
TEST(ExceptionHandlerTest, AdditionalMemory) {
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
// Get some heap memory.
u_int8_t* memory = new u_int8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
// Stick some data into the memory so the contents can be verified.
for (u_int32_t i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
AutoTempDir temp_dir;
ExceptionHandler handler(
MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
// Add the memory region to the list of memory to be included.
handler.RegisterAppMemory(memory, kMemorySize);
handler.WriteMinidump();
const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor();
// Read the minidump. Ensure that the memory region is present
Minidump minidump(minidump_desc.path());
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
ASSERT_TRUE(region);
EXPECT_EQ(kMemoryAddress, region->GetBase());
EXPECT_EQ(kMemorySize, region->GetSize());
// Verify memory contents.
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
delete[] memory;
}
// Test that a memory region that was previously registered
// can be unregistered.
TEST(ExceptionHandlerTest, AdditionalMemoryRemove) {
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
// Get some heap memory.
u_int8_t* memory = new u_int8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
AutoTempDir temp_dir;
ExceptionHandler handler(
MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1);
// Add the memory region to the list of memory to be included.
handler.RegisterAppMemory(memory, kMemorySize);
// ...and then remove it
handler.UnregisterAppMemory(memory);
handler.WriteMinidump();
const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor();
// Read the minidump. Ensure that the memory region is not present.
Minidump minidump(minidump_desc.path());
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
EXPECT_FALSE(region);
delete[] memory;
}

View file

@ -76,6 +76,7 @@
namespace { namespace {
using google_breakpad::AppMemoryList;
using google_breakpad::ExceptionHandler; using google_breakpad::ExceptionHandler;
using google_breakpad::LineReader; using google_breakpad::LineReader;
using google_breakpad::LinuxDumper; using google_breakpad::LinuxDumper;
@ -381,6 +382,7 @@ class MinidumpWriter {
int minidump_fd, int minidump_fd,
const ExceptionHandler::CrashContext* context, const ExceptionHandler::CrashContext* context,
const MappingList& mappings, const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper) LinuxDumper* dumper)
: fd_(minidump_fd), : fd_(minidump_fd),
path_(minidump_path), path_(minidump_path),
@ -393,7 +395,8 @@ class MinidumpWriter {
#endif #endif
dumper_(dumper), dumper_(dumper),
memory_blocks_(dumper_->allocator()), memory_blocks_(dumper_->allocator()),
mapping_list_(mappings) { mapping_list_(mappings),
app_memory_list_(appmem) {
// Assert there should be either a valid fd or a valid path, not both. // Assert there should be either a valid fd or a valid path, not both.
assert(fd_ != -1 || minidump_path); assert(fd_ != -1 || minidump_path);
assert(fd_ == -1 || !minidump_path); assert(fd_ == -1 || !minidump_path);
@ -480,6 +483,9 @@ class MinidumpWriter {
return false; return false;
dir.CopyIndex(dir_index++, &dirent); dir.CopyIndex(dir_index++, &dirent);
if (!WriteAppMemory())
return false;
if (!WriteMemoryListStream(&dirent)) if (!WriteMemoryListStream(&dirent))
return false; return false;
dir.CopyIndex(dir_index++, &dirent); dir.CopyIndex(dir_index++, &dirent);
@ -781,6 +787,30 @@ class MinidumpWriter {
return true; return true;
} }
// Write application-provided memory regions.
bool WriteAppMemory() {
for (AppMemoryList::const_iterator iter = app_memory_list_.begin();
iter != app_memory_list_.end();
++iter) {
uint8_t* data_copy =
reinterpret_cast<uint8_t*>(dumper_->allocator()->Alloc(iter->length));
dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr,
iter->length);
UntypedMDRVA memory(&minidump_writer_);
if (!memory.Allocate(iter->length)) {
return false;
}
memory.Copy(data_copy, iter->length);
MDMemoryDescriptor desc;
desc.start_of_memory_range = reinterpret_cast<uintptr_t>(iter->ptr);
desc.memory = memory.location();
memory_blocks_.push_back(desc);
}
return true;
}
static bool ShouldIncludeMapping(const MappingInfo& mapping) { static bool ShouldIncludeMapping(const MappingInfo& mapping) {
if (mapping.name[0] == 0 || // only want modules with filenames. if (mapping.name[0] == 0 || // only want modules with filenames.
mapping.offset || // only want to include one mapping per shared lib. mapping.offset || // only want to include one mapping per shared lib.
@ -1361,6 +1391,9 @@ class MinidumpWriter {
wasteful_vector<MDMemoryDescriptor> memory_blocks_; wasteful_vector<MDMemoryDescriptor> memory_blocks_;
// Additional information about some mappings provided by the caller. // Additional information about some mappings provided by the caller.
const MappingList& mapping_list_; const MappingList& mapping_list_;
// Additional memory regions to be included in the dump,
// provided by the caller.
const AppMemoryList& app_memory_list_;
}; };
@ -1368,7 +1401,8 @@ bool WriteMinidumpImpl(const char* minidump_path,
int minidump_fd, int minidump_fd,
pid_t crashing_process, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings) { const MappingList& mappings,
const AppMemoryList& appmem) {
if (blob_size != sizeof(ExceptionHandler::CrashContext)) if (blob_size != sizeof(ExceptionHandler::CrashContext))
return false; return false;
const ExceptionHandler::CrashContext* context = const ExceptionHandler::CrashContext* context =
@ -1378,7 +1412,8 @@ bool WriteMinidumpImpl(const char* minidump_path,
reinterpret_cast<uintptr_t>(context->siginfo.si_addr)); reinterpret_cast<uintptr_t>(context->siginfo.si_addr));
dumper.set_crash_signal(context->siginfo.si_signo); dumper.set_crash_signal(context->siginfo.si_signo);
dumper.set_crash_thread(context->tid); dumper.set_crash_thread(context->tid);
MinidumpWriter writer(minidump_path, minidump_fd, context, mappings, &dumper); MinidumpWriter writer(minidump_path, minidump_fd, context, mappings,
appmem, &dumper);
if (!writer.Init()) if (!writer.Init())
return false; return false;
return writer.Dump(); return writer.Dump();
@ -1391,33 +1426,36 @@ namespace google_breakpad {
bool WriteMinidump(const char* minidump_path, pid_t crashing_process, bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size) { const void* blob, size_t blob_size) {
return WriteMinidumpImpl(minidump_path, -1, crashing_process, blob, blob_size, return WriteMinidumpImpl(minidump_path, -1, crashing_process, blob, blob_size,
MappingList()); MappingList(), AppMemoryList());
} }
bool WriteMinidump(int minidump_fd, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size) { const void* blob, size_t blob_size) {
return WriteMinidumpImpl(NULL, minidump_fd, crashing_process, blob, blob_size, return WriteMinidumpImpl(NULL, minidump_fd, crashing_process, blob, blob_size,
MappingList()); MappingList(), AppMemoryList());
} }
bool WriteMinidump(const char* minidump_path, pid_t crashing_process, bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings) { const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(minidump_path, -1, crashing_process, blob, blob_size, return WriteMinidumpImpl(minidump_path, -1, crashing_process, blob, blob_size,
mappings); mappings, appmem);
} }
bool WriteMinidump(int minidump_fd, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings) { const MappingList& mappings,
const AppMemoryList& appmem) {
return WriteMinidumpImpl(NULL, minidump_fd, crashing_process, blob, blob_size, return WriteMinidumpImpl(NULL, minidump_fd, crashing_process, blob, blob_size,
mappings); mappings, appmem);
} }
bool WriteMinidump(const char* filename, bool WriteMinidump(const char* filename,
const MappingList& mappings, const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper) { LinuxDumper* dumper) {
MinidumpWriter writer(filename, -1, NULL, mappings, dumper); MinidumpWriter writer(filename, -1, NULL, mappings, appmem, dumper);
if (!writer.Init()) if (!writer.Init())
return false; return false;
return writer.Dump(); return writer.Dump();

View file

@ -51,6 +51,22 @@ struct MappingEntry {
// A list of <MappingInfo, GUID> // A list of <MappingInfo, GUID>
typedef std::list<MappingEntry> MappingList; typedef std::list<MappingEntry> MappingList;
// These entries store a list of memory regions that the client wants included
// in the minidump.
struct AppMemory {
void* ptr;
size_t length;
bool operator==(const struct AppMemory& other) const {
return ptr == other.ptr;
}
bool operator==(const void* other) const {
return ptr == other;
}
};
typedef std::list<AppMemory> AppMemoryList;
// Writes a minidump to the filesystem. These functions do not malloc nor use // Writes a minidump to the filesystem. These functions do not malloc nor use
// libc functions which may. Thus, it can be used in contexts where the state // libc functions which may. Thus, it can be used in contexts where the state
// of the heap may be corrupt. // of the heap may be corrupt.
@ -67,16 +83,20 @@ bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
bool WriteMinidump(int minidump_fd, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size); const void* blob, size_t blob_size);
// These overloads also allow passing a list of known mappings. // These overloads also allow passing a list of known mappings and
// a list of additional memory regions to be included in the minidump.
bool WriteMinidump(const char* minidump_path, pid_t crashing_process, bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings); const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(int minidump_fd, pid_t crashing_process, bool WriteMinidump(int minidump_fd, pid_t crashing_process,
const void* blob, size_t blob_size, const void* blob, size_t blob_size,
const MappingList& mappings); const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(const char* filename, bool WriteMinidump(const char* filename,
const MappingList& mappings, const MappingList& mappings,
const AppMemoryList& appdata,
LinuxDumper* dumper); LinuxDumper* dumper);
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -32,6 +32,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <ucontext.h>
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
@ -184,12 +185,13 @@ TEST(MinidumpWriterTest, MappingInfo) {
strcpy(info.name, kMemoryName); strcpy(info.name, kMemoryName);
MappingList mappings; MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping; MappingEntry mapping;
mapping.first = info; mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping); mappings.push_back(mapping);
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
mappings)); mappings, memory_list));
// Read the minidump. Load the module list, and ensure that // Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name // the mmap'ed |memory| is listed with the given module name
@ -288,12 +290,13 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
strcpy(info.name, kMemoryName); strcpy(info.name, kMemoryName);
MappingList mappings; MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping; MappingEntry mapping;
mapping.first = info; mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping); mappings.push_back(mapping);
ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context), ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
mappings)); mappings, memory_list));
// Read the minidump. Load the module list, and ensure that // Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name // the mmap'ed |memory| is listed with the given module name
@ -410,3 +413,75 @@ TEST(MinidumpWriterTest, DeletedBinary) {
module_identifier += "0"; module_identifier += "0";
EXPECT_EQ(module_identifier, module->debug_identifier()); EXPECT_EQ(module_identifier, module->debug_identifier());
} }
// Test that an additional memory region can be added to the minidump.
TEST(MinidumpWriterTest, AdditionalMemory) {
int fds[2];
ASSERT_NE(-1, 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 = sysconf(_SC_PAGESIZE);
// Get some heap memory.
u_int8_t* memory = new u_int8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
// Stick some data into the memory so the contents can be verified.
for (u_int32_t i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
// This needs a valid context for minidump writing to work, but getting
// a useful one from the child is too much work, so just use one from
// the parent since the child is just a forked copy anyway.
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = temp_dir.path() + "/minidump-writer-unittest";
unlink(templ.c_str());
MappingList mappings;
AppMemoryList memory_list;
// Add the memory region to the list of memory to be included.
AppMemory app_memory;
app_memory.ptr = memory;
app_memory.length = kMemorySize;
memory_list.push_back(app_memory);
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
mappings, memory_list));
// Read the minidump. Ensure that the memory region is present
Minidump minidump(templ.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
ASSERT_TRUE(region);
EXPECT_EQ(kMemoryAddress, region->GetBase());
EXPECT_EQ(kMemorySize, region->GetSize());
// Verify memory contents.
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
delete[] memory;
close(fds[1]);
}

View file

@ -46,9 +46,8 @@ static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024;
// This is passed as the context to the MinidumpWriteDump callback. // This is passed as the context to the MinidumpWriteDump callback.
typedef struct { typedef struct {
ULONG64 memory_base; AppMemoryList::const_iterator iter;
ULONG memory_size; AppMemoryList::const_iterator end;
bool finished;
} MinidumpCallbackContext; } MinidumpCallbackContext;
vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
@ -220,6 +219,12 @@ void ExceptionHandler::Initialize(const wstring& dump_path,
set_dump_path(dump_path); set_dump_path(dump_path);
} }
// Reserve one element for the instruction memory
AppMemory instruction_memory;
instruction_memory.ptr = NULL;
instruction_memory.length = 0;
app_memory_info_.push_back(instruction_memory);
// There is a race condition here. If the first instance has not yet // There is a race condition here. If the first instance has not yet
// initialized the critical section, the second (and later) instances may // initialized the critical section, the second (and later) instances may
// try to use uninitialized critical section object. The feature of multiple // try to use uninitialized critical section object. The feature of multiple
@ -795,9 +800,6 @@ bool ExceptionHandler::WriteMinidumpWithException(
++user_streams.UserStreamCount; ++user_streams.UserStreamCount;
} }
MINIDUMP_CALLBACK_INFORMATION callback;
MinidumpCallbackContext context;
MINIDUMP_CALLBACK_INFORMATION* callback_pointer = NULL;
// Older versions of DbgHelp.dll don't correctly put the memory around // Older versions of DbgHelp.dll don't correctly put the memory around
// the faulting instruction pointer into the minidump. This // the faulting instruction pointer into the minidump. This
// callback will ensure that it gets included. // callback will ensure that it gets included.
@ -822,23 +824,34 @@ bool ExceptionHandler::WriteMinidumpWithException(
// pointer, but settle for whatever's available up to the // pointer, but settle for whatever's available up to the
// boundaries of the memory region. // boundaries of the memory region.
const ULONG64 kIPMemorySize = 256; const ULONG64 kIPMemorySize = 256;
context.memory_base = ULONG64 base =
(std::max)(reinterpret_cast<ULONG64>(info.BaseAddress), (std::max)(reinterpret_cast<ULONG64>(info.BaseAddress),
instruction_pointer - (kIPMemorySize / 2)); instruction_pointer - (kIPMemorySize / 2));
ULONG64 end_of_range = ULONG64 end_of_range =
(std::min)(instruction_pointer + (kIPMemorySize / 2), (std::min)(instruction_pointer + (kIPMemorySize / 2),
reinterpret_cast<ULONG64>(info.BaseAddress) reinterpret_cast<ULONG64>(info.BaseAddress)
+ info.RegionSize); + info.RegionSize);
context.memory_size = ULONG size = static_cast<ULONG>(end_of_range - base);
static_cast<ULONG>(end_of_range - context.memory_base);
context.finished = false; AppMemory& elt = app_memory_info_.front();
callback.CallbackRoutine = MinidumpWriteDumpCallback; elt.ptr = base;
callback.CallbackParam = reinterpret_cast<void*>(&context); elt.length = size;
callback_pointer = &callback;
} }
} }
MinidumpCallbackContext context;
context.iter = app_memory_info_.begin();
context.end = app_memory_info_.end();
// Skip the reserved element if there was no instruction memory
if (context.iter->ptr == 0) {
context.iter++;
}
MINIDUMP_CALLBACK_INFORMATION callback;
callback.CallbackRoutine = MinidumpWriteDumpCallback;
callback.CallbackParam = reinterpret_cast<void*>(&context);
// The explicit comparison to TRUE avoids a warning (C4800). // The explicit comparison to TRUE avoids a warning (C4800).
success = (minidump_write_dump_(GetCurrentProcess(), success = (minidump_write_dump_(GetCurrentProcess(),
GetCurrentProcessId(), GetCurrentProcessId(),
@ -846,7 +859,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
dump_type_, dump_type_,
exinfo ? &except_info : NULL, exinfo ? &except_info : NULL,
&user_streams, &user_streams,
callback_pointer) == TRUE); &callback) == TRUE);
CloseHandle(dump_file); CloseHandle(dump_file);
} }
@ -874,13 +887,13 @@ BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback(
case MemoryCallback: { case MemoryCallback: {
MinidumpCallbackContext* callback_context = MinidumpCallbackContext* callback_context =
reinterpret_cast<MinidumpCallbackContext*>(context); reinterpret_cast<MinidumpCallbackContext*>(context);
if (callback_context->finished) if (callback_context->iter == callback_context->end)
return FALSE; return FALSE;
// Include the specified memory region. // Include the specified memory region.
callback_output->MemoryBase = callback_context->memory_base; callback_output->MemoryBase = callback_context->iter->ptr;
callback_output->MemorySize = callback_context->memory_size; callback_output->MemorySize = callback_context->iter->length;
callback_context->finished = true; callback_context->iter++;
return TRUE; return TRUE;
} }
@ -924,4 +937,26 @@ void ExceptionHandler::UpdateNextID() {
next_minidump_path_c_ = next_minidump_path_.c_str(); next_minidump_path_c_ = next_minidump_path_.c_str();
} }
void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) {
AppMemoryList::iterator iter =
std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr);
if (iter != app_memory_info_.end()) {
// Don't allow registering the same pointer twice.
return;
}
AppMemory app_memory;
app_memory.ptr = reinterpret_cast<ULONG64>(ptr);
app_memory.length = static_cast<ULONG>(length);
app_memory_info_.push_back(app_memory);
}
void ExceptionHandler::UnregisterAppMemory(void* ptr) {
AppMemoryList::iterator iter =
std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr);
if (iter != app_memory_info_.end()) {
app_memory_info_.erase(iter);
}
}
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -65,6 +65,7 @@
// Disable exception handler warnings. // Disable exception handler warnings.
#pragma warning( disable : 4530 ) #pragma warning( disable : 4530 )
#include <list>
#include <string> #include <string>
#include <vector> #include <vector>
@ -78,6 +79,22 @@ namespace google_breakpad {
using std::vector; using std::vector;
using std::wstring; using std::wstring;
// These entries store a list of memory regions that the client wants included
// in the minidump.
struct AppMemory {
ULONG64 ptr;
ULONG length;
bool operator==(const struct AppMemory& other) const {
return ptr == other.ptr;
}
bool operator==(const void* other) const {
return ptr == reinterpret_cast<ULONG64>(other);
}
};
typedef std::list<AppMemory> AppMemoryList;
class ExceptionHandler { class ExceptionHandler {
public: public:
// A callback function to run before Breakpad performs any substantial // A callback function to run before Breakpad performs any substantial
@ -219,6 +236,11 @@ class ExceptionHandler {
// Returns whether out-of-process dump generation is used or not. // Returns whether out-of-process dump generation is used or not.
bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; } bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; }
// Calling RegisterAppMemory(p, len) causes len bytes starting
// at address p to be copied to the minidump when a crash happens.
void RegisterAppMemory(void* ptr, size_t length);
void UnregisterAppMemory(void* ptr);
private: private:
friend class AutoExceptionHandler; friend class AutoExceptionHandler;
@ -404,6 +426,10 @@ class ExceptionHandler {
// to not interfere with debuggers. // to not interfere with debuggers.
bool handle_debug_exceptions_; bool handle_debug_exceptions_;
// Callers can request additional memory regions to be included in
// the dump.
AppMemoryList app_memory_info_;
// A stack of ExceptionHandler objects that have installed unhandled // A stack of ExceptionHandler objects that have installed unhandled
// exception filters. This vector is used by HandleException to determine // exception filters. This vector is used by HandleException to determine
// which ExceptionHandler object to route an exception to. When an // which ExceptionHandler object to route an exception to. When an

View file

@ -374,4 +374,103 @@ TEST_F(ExceptionHandlerTest, WriteMinidumpTest) {
//TODO(ted): more comprehensive tests... //TODO(ted): more comprehensive tests...
} }
// Test that an additional memory region can be included in the minidump.
TEST_F(ExceptionHandlerTest, AdditionalMemory) {
SYSTEM_INFO si;
GetSystemInfo(&si);
const u_int32_t kMemorySize = si.dwPageSize;
// Get some heap memory.
u_int8_t* memory = new u_int8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
// Stick some data into the memory so the contents can be verified.
for (u_int32_t i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
ExceptionHandler handler(temp_path_,
NULL,
DumpCallback,
NULL,
ExceptionHandler::HANDLER_ALL);
// Add the memory region to the list of memory to be included.
handler.RegisterAppMemory(memory, kMemorySize);
ASSERT_TRUE(handler.WriteMinidump());
ASSERT_FALSE(dump_file.empty());
string minidump_filename;
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
&minidump_filename));
// Read the minidump. Ensure that the memory region is present
Minidump minidump(minidump_filename);
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
ASSERT_TRUE(region);
EXPECT_EQ(kMemoryAddress, region->GetBase());
EXPECT_EQ(kMemorySize, region->GetSize());
// Verify memory contents.
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
delete[] memory;
}
// Test that a memory region that was previously registered
// can be unregistered.
TEST_F(ExceptionHandlerTest, AdditionalMemoryRemove) {
SYSTEM_INFO si;
GetSystemInfo(&si);
const u_int32_t kMemorySize = si.dwPageSize;
// Get some heap memory.
u_int8_t* memory = new u_int8_t[kMemorySize];
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
ASSERT_TRUE(memory);
// Stick some data into the memory so the contents can be verified.
for (u_int32_t i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
ExceptionHandler handler(temp_path_,
NULL,
DumpCallback,
NULL,
ExceptionHandler::HANDLER_ALL);
// Add the memory region to the list of memory to be included.
handler.RegisterAppMemory(memory, kMemorySize);
// ...and then remove it
handler.UnregisterAppMemory(memory);
ASSERT_TRUE(handler.WriteMinidump());
ASSERT_FALSE(dump_file.empty());
string minidump_filename;
ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
&minidump_filename));
// Read the minidump. Ensure that the memory region is not present.
Minidump minidump(minidump_filename);
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
ASSERT_TRUE(dump_memory_list);
const MinidumpMemoryRegion* region =
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
EXPECT_FALSE(region);
delete[] memory;
}
} // namespace } // namespace

View file

@ -34,6 +34,7 @@
#include "client/linux/minidump_writer/minidump_writer.h" #include "client/linux/minidump_writer/minidump_writer.h"
#include "client/linux/minidump_writer/linux_core_dumper.h" #include "client/linux/minidump_writer/linux_core_dumper.h"
using google_breakpad::AppMemoryList;
using google_breakpad::MappingList; using google_breakpad::MappingList;
using google_breakpad::LinuxCoreDumper; using google_breakpad::LinuxCoreDumper;
@ -46,8 +47,10 @@ bool WriteMinidumpFromCore(const char* filename,
const char* core_path, const char* core_path,
const char* procfs_override) { const char* procfs_override) {
MappingList mappings; MappingList mappings;
AppMemoryList memory_list;
LinuxCoreDumper dumper(0, core_path, procfs_override); LinuxCoreDumper dumper(0, core_path, procfs_override);
return google_breakpad::WriteMinidump(filename, mappings, &dumper); return google_breakpad::WriteMinidump(filename, mappings, memory_list,
&dumper);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {