Allow adding extra memory regions to minidump on linux/windows

A=Bill McCloskey <wmccloskey@mozilla.com> R=ted at https://bugzilla.mozilla.org/show_bug.cgi?id=662646

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@989 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
ted.mielczarek 2012-07-19 22:03:39 +00:00
parent 72542b052d
commit 2f56276fbf
10 changed files with 305 additions and 29 deletions

View file

@ -465,7 +465,8 @@ bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
crashing_process,
context,
context_size,
mapping_list_);
mapping_list_,
app_memory_list_);
}
// static
@ -515,4 +516,19 @@ void ExceptionHandler::AddMappingInfo(const string& name,
mapping_list_.push_back(mapping);
}
void ExceptionHandler::RegisterAppMemory(void *ptr, size_t length) {
app_memory_list_.push_back(AppMemory(ptr, length));
}
void ExceptionHandler::UnregisterAppMemory(void *ptr) {
for (AppMemoryList::iterator iter = app_memory_list_.begin();
iter != app_memory_list_.end();
++iter) {
if (iter->ptr == ptr) {
app_memory_list_.erase(iter);
return;
}
}
}
} // namespace google_breakpad

View file

@ -194,6 +194,13 @@ class ExceptionHandler {
size_t mapping_size,
size_t file_offset);
// Register a block of memory of len bytes starting at address p
// 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);
private:
void Init(const string &dump_path,
const int server_fd);
@ -252,6 +259,10 @@ class ExceptionHandler {
// Callers can add extra info about mappings for cases where the
// dumper code cannot extract enough information from /proc/<pid>/maps.
MappingList mapping_list_;
// Callers can request additional memory regions to be included in
// the dump.
AppMemoryList app_memory_list_;
};
} // namespace google_breakpad

View file

@ -785,3 +785,42 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
ASSERT_GT(st.st_size, 0u);
unlink(templ.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 (unsigned int i = 0; i < kMemorySize; ++i) {
memory[i] = i % 255;
}
string minidump_filename;
AutoTempDir temp_dir;
ExceptionHandler handler(temp_dir.path(), NULL, SimpleCallback,
(void*)&minidump_filename, true);
// Add the memory region to the list of memory to be included.
handler.RegisterAppMemory(memory, kMemorySize);
handler.WriteMinidump();
// 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;
}

View file

@ -374,6 +374,7 @@ class MinidumpWriter {
MinidumpWriter(const char* filename,
const ExceptionHandler::CrashContext* context,
const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper)
: filename_(filename),
ucontext_(context ? &context->context : NULL),
@ -385,7 +386,8 @@ class MinidumpWriter {
#endif
dumper_(dumper),
memory_blocks_(dumper_->allocator()),
mapping_list_(mappings) {
mapping_list_(mappings),
app_memory_list_(appmem) {
}
bool Init() {
@ -459,6 +461,9 @@ class MinidumpWriter {
return false;
dir.CopyIndex(dir_index++, &dirent);
if (!WriteAppMemory())
return false;
if (!WriteMemoryListStream(&dirent))
return false;
dir.CopyIndex(dir_index++, &dirent);
@ -760,6 +765,30 @@ class MinidumpWriter {
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 =
(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 = (uintptr_t)iter->ptr;
desc.memory = memory.location();
memory_blocks_.push_back(desc);
}
return true;
}
static bool ShouldIncludeMapping(const MappingInfo& mapping) {
if (mapping.name[0] == 0 || // only want modules with filenames.
mapping.offset || // only want to include one mapping per shared lib.
@ -1335,17 +1364,22 @@ class MinidumpWriter {
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
// Additional information about some mappings provided by the caller.
const MappingList& mapping_list_;
// Additional memory regions to be included in the dump,
// provided by the caller.
const AppMemoryList& app_memory_list_;
};
bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size) {
MappingList m;
return WriteMinidump(filename, crashing_process, blob, blob_size, m);
AppMemoryList a;
return WriteMinidump(filename, crashing_process, blob, blob_size, m, a);
}
bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings) {
const MappingList& mappings,
const AppMemoryList& appmem) {
if (blob_size != sizeof(ExceptionHandler::CrashContext))
return false;
const ExceptionHandler::CrashContext* context =
@ -1355,7 +1389,7 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
reinterpret_cast<uintptr_t>(context->siginfo.si_addr));
dumper.set_crash_signal(context->siginfo.si_signo);
dumper.set_crash_thread(context->tid);
MinidumpWriter writer(filename, context, mappings, &dumper);
MinidumpWriter writer(filename, context, mappings, appmem, &dumper);
if (!writer.Init())
return false;
return writer.Dump();
@ -1363,8 +1397,9 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
bool WriteMinidump(const char* filename,
const MappingList& mappings,
const AppMemoryList& appmem,
LinuxDumper* dumper) {
MinidumpWriter writer(filename, NULL, mappings, dumper);
MinidumpWriter writer(filename, NULL, mappings, appmem, dumper);
if (!writer.Init())
return false;
return writer.Dump();

View file

@ -51,6 +51,16 @@ struct MappingEntry {
// A list of <MappingInfo, GUID>
typedef std::list<MappingEntry> MappingList;
// These entries store a list of memory regions that the client wants included
// in the minidump.
struct AppMemory {
AppMemory(void *ptr, size_t length) : ptr(ptr), length(length) {}
void *ptr;
size_t length;
};
typedef std::list<AppMemory> AppMemoryList;
// Write a minidump to the filesystem. This function does not malloc nor use
// libc functions which may. Thus, it can be used in contexts where the state
// of the heap may be corrupt.
@ -64,13 +74,16 @@ typedef std::list<MappingEntry> MappingList;
bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size);
// This overload also allows passing a list of known mappings.
// This overload also allows passing a list of known mappings and
// a list of additional memory regions to be included in the minidump.
bool WriteMinidump(const char* filename, pid_t crashing_process,
const void* blob, size_t blob_size,
const MappingList& mappings);
const MappingList& mappings,
const AppMemoryList& appdata);
bool WriteMinidump(const char* filename,
const MappingList& mappings,
const AppMemoryList& appdata,
LinuxDumper* dumper);
} // namespace google_breakpad

View file

@ -32,6 +32,7 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <ucontext.h>
#include <unistd.h>
#include <string>
@ -152,12 +153,13 @@ TEST(MinidumpWriterTest, MappingInfo) {
strcpy(info.name, kMemoryName);
MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
mappings));
mappings, memory_list));
// Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name
@ -256,13 +258,14 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
strcpy(info.name, kMemoryName);
MappingList mappings;
AppMemoryList memory_list;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
ASSERT_TRUE(
WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
mappings));
mappings, memory_list));
// Read the minidump. Load the module list, and ensure that
// the mmap'ed |memory| is listed with the given module name
@ -383,3 +386,69 @@ TEST(MinidumpWriterTest, DeletedBinary) {
module_identifier += "0";
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 (int 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.
//TODO(ted): this won't work for Android if unit tests ever get run there.
ASSERT_EQ(0, getcontext(&context.context));
context.tid = child;
AutoTempDir temp_dir;
string templ = "/tmp/minidump-memory.dmp"; //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.
memory_list.push_back(AppMemory(memory, kMemorySize));
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]);
}