aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-12-13 22:10:23 +0000
committerted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-12-13 22:10:23 +0000
commitef7262d4775bf6de750bc2a26dbf98368d7ec0c3 (patch)
treea85d6b8c2b66f2da9fdbef3fd0a936dd7f566ac4
parentReuse code and fix inconsistent array boundaries. (diff)
downloadbreakpad-ef7262d4775bf6de750bc2a26dbf98368d7ec0c3.tar.xz
allow passing info about known memory mappings to MinidumpWriter and ExceptionHandler
r=thestig at http://breakpad.appspot.com/242001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@741 4c0a9323-5329-0410-9bdc-e9ce6186880e
-rw-r--r--src/client/linux/handler/exception_handler.cc26
-rw-r--r--src/client/linux/handler/exception_handler.h18
-rw-r--r--src/client/linux/handler/exception_handler_unittest.cc86
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.cc25
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.h8
-rw-r--r--src/client/linux/minidump_writer/linux_dumper_unittest.cc45
-rw-r--r--src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc12
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc144
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.h14
-rw-r--r--src/client/linux/minidump_writer/minidump_writer_unittest.cc315
10 files changed, 616 insertions, 77 deletions
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
index 592c9e5b..4c7436ea 100644
--- a/src/client/linux/handler/exception_handler.cc
+++ b/src/client/linux/handler/exception_handler.cc
@@ -88,10 +88,12 @@
#include <unistd.h>
#include <algorithm>
+#include <utility>
#include <vector>
#include "common/linux/linux_libc_support.h"
#include "common/memory.h"
+#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/guid_creator.h"
#include "common/linux/eintr_wrapper.h"
@@ -449,8 +451,11 @@ void ExceptionHandler::WaitForContinueSignal() {
// Runs on the cloned process.
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
size_t context_size) {
- return google_breakpad::WriteMinidump(
- next_minidump_path_c_, crashing_process, context, context_size);
+ return google_breakpad::WriteMinidump(next_minidump_path_c_,
+ crashing_process,
+ context,
+ context_size,
+ mapping_list_);
}
// static
@@ -482,4 +487,21 @@ bool ExceptionHandler::WriteMinidump() {
#endif // !defined(__ARM_EABI__)
}
+void ExceptionHandler::AddMappingInfo(const std::string& name,
+ const u_int8_t identifier[sizeof(MDGUID)],
+ uintptr_t start_address,
+ size_t mapping_size,
+ size_t file_offset) {
+ MappingInfo info;
+ info.start_addr = start_address;
+ info.size = mapping_size;
+ info.offset = file_offset;
+ strncpy(info.name, name.c_str(), std::min(name.size(), sizeof(info)));
+
+ MappingEntry mapping;
+ mapping.first = info;
+ memcpy(mapping.second, identifier, sizeof(MDGUID));
+ mapping_list_.push_back(mapping);
+}
+
} // namespace google_breakpad
diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h
index 82f7fb4c..e75517ea 100644
--- a/src/client/linux/handler/exception_handler.h
+++ b/src/client/linux/handler/exception_handler.h
@@ -30,17 +30,20 @@
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
-#include <vector>
#include <string>
+#include <vector>
#include <pthread.h>
#include <signal.h>
+#include <stdint.h>
#include <stdio.h>
#if defined(__ANDROID__)
#include "client/linux/android_ucontext.h"
#endif
#include "client/linux/crash_generation/crash_generation_client.h"
+#include "client/linux/minidump_writer/minidump_writer.h"
+#include "google_breakpad/common/minidump_format.h"
#include "processor/scoped_ptr.h"
struct sigaction;
@@ -181,6 +184,15 @@ class ExceptionHandler {
return crash_generation_client_.get() != NULL;
}
+ // Add information about a memory mapping. This can be used if
+ // a custom library loader is used that maps things in a way
+ // that the linux dumper can't handle by reading the maps file.
+ void AddMappingInfo(const std::string& name,
+ const u_int8_t identifier[sizeof(MDGUID)],
+ uintptr_t start_address,
+ size_t mapping_size,
+ size_t file_offset);
+
private:
void Init(const std::string &dump_path,
const int server_fd);
@@ -236,6 +248,10 @@ class ExceptionHandler {
// cloned process after creating it, until we have explicitly enabled
// ptrace. This is used to store the file descriptors for the pipe
int fdes[2];
+
+ // Callers can add extra info about mappings for cases where the
+ // dumper code cannot extract enough information from /proc/<pid>/maps.
+ MappingList mapping_list_;
};
} // namespace google_breakpad
diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc
index 988de063..67c05f6b 100644
--- a/src/client/linux/handler/exception_handler_unittest.cc
+++ b/src/client/linux/handler/exception_handler_unittest.cc
@@ -42,6 +42,7 @@
#include "client/linux/handler/exception_handler.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/eintr_wrapper.h"
+#include "common/linux/file_id.h"
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
#include "google_breakpad/processor/minidump.h"
@@ -54,6 +55,10 @@ using namespace google_breakpad;
#define TEMPDIR "/data/local/tmp"
#endif
+// Length of a formatted GUID string =
+// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
+const int kGUIDStringSize = 37;
+
static void sigchld_handler(int signo) { }
class ExceptionHandlerTest : public ::testing::Test {
@@ -573,6 +578,87 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
free(filename);
}
+static bool SimpleCallback(const char* dump_path,
+ const char* minidump_id,
+ void* context,
+ bool succeeded) {
+ if (!succeeded)
+ return succeeded;
+
+ string* minidump_file = reinterpret_cast<string*>(context);
+ minidump_file->append(dump_path);
+ minidump_file->append("/");
+ minidump_file->append(minidump_id);
+ minidump_file->append(".dmp");
+ return true;
+}
+
+// Test that anonymous memory maps can be annotated with names and IDs.
+TEST(ExceptionHandlerTest, ModuleInfo) {
+ // 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);
+ const char* kMemoryName = "a fake module";
+ const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ char module_identifier_buffer[kGUIDStringSize];
+ FileID::ConvertIdentifierToString(kModuleGUID,
+ module_identifier_buffer,
+ sizeof(module_identifier_buffer));
+ string module_identifier(module_identifier_buffer);
+ // Strip out dashes
+ size_t pos;
+ while ((pos = module_identifier.find('-')) != string::npos) {
+ module_identifier.erase(pos, 1);
+ }
+ // And append a zero, because module IDs include an "age" field
+ // which is always zero on Linux.
+ module_identifier += "0";
+
+ // Get some memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
+ ASSERT_TRUE(memory);
+
+ string minidump_filename;
+ ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback,
+ (void*)&minidump_filename, true);
+ // Add info about the anonymous memory mapping.
+ handler.AddMappingInfo(kMemoryName,
+ kModuleGUID,
+ kMemoryAddress,
+ kMemorySize,
+ 0);
+ handler.WriteMinidump();
+
+ // Read the minidump. Load the module list, and ensure that
+ // the mmap'ed |memory| is listed with the given module name
+ // and debug ID.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(kMemoryAddress, module->base_address());
+ EXPECT_EQ(kMemorySize, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ unlink(minidump_filename.c_str());
+}
+
static const unsigned kControlMsgSize =
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
index 6e6f4cfa..5187120f 100644
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -104,12 +104,12 @@ static bool ResumeThread(pid_t pid) {
}
inline static bool IsMappedFileOpenUnsafe(
- const google_breakpad::MappingInfo* mapping) {
+ const google_breakpad::MappingInfo& mapping) {
// It is unsafe to attempt to open a mapped file that lives under /dev,
// because the semantics of the open may be driver-specific so we'd risk
// hanging the crash dumper. And a file in /dev/ almost certainly has no
// ELF file identifier anyways.
- return my_strncmp(mapping->name,
+ return my_strncmp(mapping.name,
kMappedFileUnsafePrefix,
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
}
@@ -203,21 +203,21 @@ LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
}
bool
-LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id,
+LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
+ unsigned int mapping_id,
uint8_t identifier[sizeof(MDGUID)])
{
- assert(mapping_id < mappings_.size());
+ assert(mapping_id == -1 || mapping_id < mappings_.size());
my_memset(identifier, 0, sizeof(MDGUID));
- MappingInfo* mapping = mappings_[mapping_id];
if (IsMappedFileOpenUnsafe(mapping))
return false;
char filename[NAME_MAX];
- size_t filename_len = my_strlen(mapping->name);
+ size_t filename_len = my_strlen(mapping.name);
assert(filename_len < NAME_MAX);
if (filename_len >= NAME_MAX)
return false;
- memcpy(filename, mapping->name, filename_len);
+ memcpy(filename, mapping.name, filename_len);
filename[filename_len] = '\0';
bool filename_modified = HandleDeletedFileInMapping(filename);
@@ -239,8 +239,11 @@ LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id,
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
sys_munmap(base, st.st_size);
- if (success && filename_modified)
- mapping->name[filename_len - sizeof(kDeletedSuffix) + 1] = '\0';
+ if (success && mapping_id != -1 && filename_modified) {
+ mappings_[mapping_id]->name[filename_len -
+ sizeof(kDeletedSuffix) + 1] = '\0';
+ }
+
return success;
}
@@ -310,7 +313,7 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
module->offset = offset;
const char* name = NULL;
// Only copy name if the name is a valid path name, or if
- // we've found the VDSO image
+ // it's the VDSO image.
if ((name = my_strchr(line, '/')) != NULL) {
const unsigned l = my_strlen(name);
if (l < sizeof(module->name))
@@ -500,7 +503,7 @@ const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
return NULL;
}
-bool LinuxDumper::HandleDeletedFileInMapping(char* path) {
+bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
// Check for ' (deleted)' in |path|.
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
index 16b15f3c..2233ec05 100644
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -151,8 +151,10 @@ class LinuxDumper {
// without any slashes.
void BuildProcPath(char* path, pid_t pid, const char* node) const;
- // Generate a File ID from the .text section of a mapped entry
- bool ElfFileIdentifierForMapping(unsigned int mapping_id,
+ // Generate a File ID from the .text section of a mapped entry.
+ // mapping_id may be -1 if this is not a member of mappings_.
+ bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
+ unsigned int mapping_id,
uint8_t identifier[sizeof(MDGUID)]);
// Utility method to find the location of where the kernel has
@@ -174,7 +176,7 @@ class LinuxDumper {
// For programs that don't end with ' (deleted)', this is a no-op.
// This assumes |path| is a buffer with length NAME_MAX.
// Returns true if |path| is modified.
- bool HandleDeletedFileInMapping(char* path);
+ bool HandleDeletedFileInMapping(char* path) const;
const pid_t pid_;
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
index b81291b6..a3e84dbc 100644
--- a/src/client/linux/minidump_writer/linux_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
@@ -32,26 +32,19 @@
#include <limits.h>
#include <unistd.h>
#include <signal.h>
+#include <stdint.h>
+#include <sys/poll.h>
#include <sys/types.h>
#include "breakpad_googletest_includes.h"
#include "client/linux/minidump_writer/linux_dumper.h"
+#include "common/linux/eintr_wrapper.h"
#include "common/linux/file_id.h"
#include "common/memory.h"
using std::string;
using namespace google_breakpad;
-// This provides a wrapper around system calls which may be
-// interrupted by a signal and return EINTR. See man 7 signal.
-#define HANDLE_EINTR(x) ({ \
- typeof(x) __eintr_result__; \
- do { \
- __eintr_result__ = x; \
- } while (__eintr_result__ == -1 && errno == EINTR); \
- __eintr_result__;\
-})
-
namespace {
typedef testing::Test LinuxDumperTest;
}
@@ -88,8 +81,14 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
pid_t child_pid = fork();
if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
// Locate helper binary next to the current binary.
char self_path[PATH_MAX];
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
@@ -105,9 +104,12 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
helper_path.erase(pos + 1);
helper_path += "linux_dumper_unittest_helper";
- // Set the number of threads
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
execl(helper_path.c_str(),
"linux_dumper_unittest_helper",
+ pipe_fd_string,
kNumberOfThreadsArgument,
NULL);
// Kill if we get here.
@@ -115,9 +117,21 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
exit(0);
}
- // The sleep is flaky, but prevents us from reading
- // the child process before all threads have been created.
- sleep(1);
+ close(fds[1]);
+ // Wait for the child process to signal that it's ready.
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ read(fds[0], &junk, sizeof(junk));
+ close(fds[0]);
+
+ // Child is ready now.
LinuxDumper dumper(child_pid);
ASSERT_TRUE(dumper.Init());
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
@@ -240,7 +254,8 @@ TEST(LinuxDumperTest, FileIDsMatch) {
uint8_t identifier1[sizeof(MDGUID)];
uint8_t identifier2[sizeof(MDGUID)];
- EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(i, identifier1));
+ EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], i,
+ identifier1));
FileID fileid(exe_name);
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
char identifier_string1[37];
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc b/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
index 6b7ad5a2..27c2e994 100644
--- a/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
@@ -32,6 +32,7 @@
// id.
#include <pthread.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
@@ -58,7 +59,13 @@ void *thread_function(void *data) {
}
int main(int argc, char *argv[]) {
- int num_threads = atoi(argv[1]);
+ if (argc < 2) {
+ fprintf(stderr,
+ "usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n");
+ return 1;
+ }
+ int pipefd = atoi(argv[1]);
+ int num_threads = atoi(argv[2]);
if (num_threads < 1) {
fprintf(stderr, "ERROR: number of threads is 0");
return 1;
@@ -70,6 +77,9 @@ int main(int argc, char *argv[]) {
for (int i = 1; i < num_threads; i++) {
pthread_create(&threads[i], &thread_attributes, &thread_function, NULL);
}
+ // Signal parent that this process has started all threads.
+ uint8_t byte = 1;
+ write(pipefd, &byte, sizeof(byte));
thread_function(NULL);
return 0;
}
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
index 405ec4e8..3eb90953 100644
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -368,7 +368,8 @@ class MinidumpWriter {
public:
MinidumpWriter(const char* filename,
pid_t crashing_pid,
- const ExceptionHandler::CrashContext* context)
+ const ExceptionHandler::CrashContext* context,
+ const MappingList& mappings)
: filename_(filename),
siginfo_(&context->siginfo),
ucontext_(&context->context),
@@ -380,7 +381,8 @@ class MinidumpWriter {
#endif
crashing_tid_(context->tid),
dumper_(crashing_pid),
- memory_blocks_(dumper_.allocator()) {
+ memory_blocks_(dumper_.allocator()),
+ mapping_list_(mappings) {
}
bool Init() {
@@ -752,17 +754,34 @@ class MinidumpWriter {
return true;
}
+ // If there is caller-provided information about this mapping
+ // in the mapping_list_ list, return true. Otherwise, return false.
+ bool HaveMappingInfo(const MappingInfo& mapping) {
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ // Ignore any mappings that are wholly contained within
+ // mappings in the mapping_info_ list.
+ if (mapping.start_addr >= iter->first.start_addr &&
+ (mapping.start_addr + mapping.size) <=
+ (iter->first.start_addr + iter->first.size)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Write information about the mappings in effect. Because we are using the
// minidump format, the information about the mappings is pretty limited.
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
// another stream in the file.
bool WriteMappings(MDRawDirectory* dirent) {
const unsigned num_mappings = dumper_.mappings().size();
- unsigned num_output_mappings = 0;
+ unsigned num_output_mappings = mapping_list_.size();
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
const MappingInfo& mapping = *dumper_.mappings()[i];
- if (ShouldIncludeMapping(mapping))
+ if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
num_output_mappings++;
}
@@ -774,56 +793,86 @@ class MinidumpWriter {
dirent->location = list.location();
*list.get() = num_output_mappings;
- for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
+ // First write all the mappings from the dumper
+ unsigned int j = 0;
+ for (unsigned i = 0; i < num_mappings; ++i) {
const MappingInfo& mapping = *dumper_.mappings()[i];
- if (!ShouldIncludeMapping(mapping))
+ if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping))
continue;
MDRawModule mod;
- my_memset(&mod, 0, MD_MODULE_SIZE);
- mod.base_of_image = mapping.start_addr;
- mod.size_of_image = mapping.size;
- const size_t filepath_len = my_strlen(mapping.name);
-
- // Figure out file name from path
- const char* filename_ptr = mapping.name + filepath_len - 1;
- while (filename_ptr >= mapping.name) {
- if (*filename_ptr == '/')
- break;
- filename_ptr--;
- }
- filename_ptr++;
- const size_t filename_len = mapping.name + filepath_len - filename_ptr;
-
- uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
- uint8_t* cv_ptr = cv_buf;
- UntypedMDRVA cv(&minidump_writer_);
- if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
+ if (!FillRawModule(mapping, i, mod, NULL))
return false;
+ list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+ }
+ // Next write all the mappings provided by the caller
+ for (MappingList::const_iterator iter = mapping_list_.begin();
+ iter != mapping_list_.end();
+ ++iter) {
+ MDRawModule mod;
+ if (!FillRawModule(iter->first, -1, mod, iter->second))
+ return false;
+ list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+ }
- const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
- memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
- cv_ptr += sizeof(cv_signature);
- uint8_t* signature = cv_ptr;
- cv_ptr += sizeof(MDGUID);
- dumper_.ElfFileIdentifierForMapping(i, signature);
- my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
- cv_ptr += sizeof(uint32_t);
+ return true;
+ }
- // Write pdb_file_name
- memcpy(cv_ptr, filename_ptr, filename_len + 1);
- cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
+ // Fill the MDRawModule |mod| with information about the provided
+ // |mapping|. If |identifier| is non-NULL, use it instead of calculating
+ // a file ID from the mapping. |mapping_id| can be -1 if this mapping
+ // is not from the LinuxDumper.
+ bool FillRawModule(const MappingInfo& mapping,
+ unsigned int mapping_id,
+ MDRawModule& mod,
+ const u_int8_t* identifier) {
+ my_memset(&mod, 0, MD_MODULE_SIZE);
+
+ mod.base_of_image = mapping.start_addr;
+ mod.size_of_image = mapping.size;
+ const size_t filepath_len = my_strlen(mapping.name);
+
+ // Figure out file name from path
+ const char* filename_ptr = mapping.name + filepath_len - 1;
+ while (filename_ptr >= mapping.name) {
+ if (*filename_ptr == '/')
+ break;
+ filename_ptr--;
+ }
+ filename_ptr++;
- mod.cv_record = cv.location();
+ const size_t filename_len = mapping.name + filepath_len - filename_ptr;
- MDLocationDescriptor ld;
- if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
- return false;
- mod.module_name_rva = ld.rva;
+ uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
+ uint8_t* cv_ptr = cv_buf;
+ UntypedMDRVA cv(&minidump_writer_);
+ if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
+ return false;
- list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+ const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
+ memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
+ cv_ptr += sizeof(cv_signature);
+ uint8_t* signature = cv_ptr;
+ cv_ptr += sizeof(MDGUID);
+ if (identifier) {
+ // GUID was provided by caller.
+ memcpy(signature, identifier, sizeof(MDGUID));
+ } else {
+ dumper_.ElfFileIdentifierForMapping(mapping, mapping_id, signature);
}
+ my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
+ cv_ptr += sizeof(uint32_t);
+ // Write pdb_file_name
+ memcpy(cv_ptr, filename_ptr, filename_len + 1);
+ cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
+
+ mod.cv_record = cv.location();
+
+ MDLocationDescriptor ld;
+ if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
+ return false;
+ mod.module_name_rva = ld.rva;
return true;
}
@@ -1233,15 +1282,24 @@ class MinidumpWriter {
// written while writing the thread list stream, but saved here
// so a memory list stream can be written afterwards.
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
+ // Additional information about some mappings provided by the caller.
+ const MappingList& mapping_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);
+}
+
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings) {
if (blob_size != sizeof(ExceptionHandler::CrashContext))
return false;
const ExceptionHandler::CrashContext* context =
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
- MinidumpWriter writer(filename, crashing_process, context);
+ MinidumpWriter writer(filename, crashing_process, context, mappings);
if (!writer.Init())
return false;
return writer.Dump();
diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h
index 579f68cd..156ca35f 100644
--- a/src/client/linux/minidump_writer/minidump_writer.h
+++ b/src/client/linux/minidump_writer/minidump_writer.h
@@ -33,8 +33,17 @@
#include <stdint.h>
#include <unistd.h>
+#include <list>
+#include <utility>
+
+#include "google_breakpad/common/minidump_format.h"
+
namespace google_breakpad {
+// A list of <MappingInfo, GUID>
+typedef std::pair<struct MappingInfo, u_int8_t[sizeof(MDGUID)]> MappingEntry;
+typedef std::list<MappingEntry> MappingList;
+
// 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.
@@ -48,6 +57,11 @@ namespace google_breakpad {
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.
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size,
+ const MappingList& mappings);
+
} // namespace google_breakpad
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
diff --git a/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
index 0a3b46f2..5377aaca 100644
--- a/src/client/linux/minidump_writer/minidump_writer_unittest.cc
+++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -28,12 +28,17 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
#include <sys/syscall.h>
+#include "breakpad_googletest_includes.h"
#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/linux_dumper.h"
#include "client/linux/minidump_writer/minidump_writer.h"
#include "common/linux/eintr_wrapper.h"
-#include "breakpad_googletest_includes.h"
+#include "common/linux/file_id.h"
+#include "google_breakpad/processor/minidump.h"
using namespace google_breakpad;
@@ -43,6 +48,10 @@ using namespace google_breakpad;
#define TEMPDIR "/data/local/tmp"
#endif
+// Length of a formatted GUID string =
+// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
+const int kGUIDStringSize = 37;
+
namespace {
typedef testing::Test MinidumpWriterTest;
}
@@ -76,3 +85,307 @@ TEST(MinidumpWriterTest, Setup) {
close(fds[1]);
}
+
+// Test that mapping info can be specified when writing a minidump,
+// and that it ends up in the module list of the minidump.
+TEST(MinidumpWriterTest, MappingInfo) {
+ 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);
+ const char* kMemoryName = "a fake module";
+ const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ char module_identifier_buffer[kGUIDStringSize];
+ FileID::ConvertIdentifierToString(kModuleGUID,
+ module_identifier_buffer,
+ sizeof(module_identifier_buffer));
+ string module_identifier(module_identifier_buffer);
+ // Strip out dashes
+ size_t pos;
+ while ((pos = module_identifier.find('-')) != string::npos) {
+ module_identifier.erase(pos, 1);
+ }
+ // And append a zero, because module IDs include an "age" field
+ // which is always zero on Linux.
+ module_identifier += "0";
+
+ // Get some memory.
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0));
+ const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
+ ASSERT_TRUE(memory);
+
+ 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;
+ memset(&context, 0, sizeof(context));
+ context.tid = 1;
+
+ char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+ mktemp(templ);
+
+ // Add information about the mapped memory.
+ MappingInfo info;
+ info.start_addr = kMemoryAddress;
+ info.size = kMemorySize;
+ info.offset = 0;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ MappingEntry mapping;
+ mapping.first = info;
+ memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
+ mappings.push_back(mapping);
+ ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
+
+ // Read the minidump. Load the module list, and ensure that
+ // the mmap'ed |memory| is listed with the given module name
+ // and debug ID.
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(kMemoryAddress, module->base_address());
+ EXPECT_EQ(kMemorySize, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ unlink(templ);
+ close(fds[1]);
+}
+
+// Test that mapping info can be specified, and that it overrides
+// existing mappings that are wholly contained within the specified
+// range.
+TEST(MinidumpWriterTest, MappingInfoContained) {
+ 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);
+ const char* kMemoryName = "a fake module";
+ const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+ };
+ char module_identifier_buffer[kGUIDStringSize];
+ FileID::ConvertIdentifierToString(kModuleGUID,
+ module_identifier_buffer,
+ sizeof(module_identifier_buffer));
+ string module_identifier(module_identifier_buffer);
+ // Strip out dashes
+ size_t pos;
+ while ((pos = module_identifier.find('-')) != string::npos) {
+ module_identifier.erase(pos, 1);
+ }
+ // And append a zero, because module IDs include an "age" field
+ // which is always zero on Linux.
+ module_identifier += "0";
+
+ // mmap a file
+ char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
+ mktemp(tempfile);
+ int fd = open(tempfile, O_RDWR | O_CREAT, 0);
+ ASSERT_NE(-1, fd);
+ unlink(tempfile);
+ // fill with zeros
+ char buffer[kMemorySize];
+ memset(buffer, 0, kMemorySize);
+ ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
+ lseek(fd, 0, SEEK_SET);
+
+ char* memory =
+ reinterpret_cast<char*>(mmap(NULL,
+ kMemorySize,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ fd,
+ 0));
+ const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
+ ASSERT_TRUE(memory);
+ close(fd);
+
+ 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;
+ memset(&context, 0, sizeof(context));
+ context.tid = 1;
+
+ char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+ mktemp(dumpfile);
+
+ // Add information about the mapped memory. Report it as being larger than
+ // it actually is.
+ MappingInfo info;
+ info.start_addr = kMemoryAddress - kMemorySize;
+ info.size = kMemorySize * 3;
+ info.offset = 0;
+ strcpy(info.name, kMemoryName);
+
+ MappingList mappings;
+ MappingEntry mapping;
+ mapping.first = info;
+ memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
+ mappings.push_back(mapping);
+ ASSERT_TRUE(WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
+
+ // Read the minidump. Load the module list, and ensure that
+ // the mmap'ed |memory| is listed with the given module name
+ // and debug ID.
+ Minidump minidump(dumpfile);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module =
+ module_list->GetModuleForAddress(kMemoryAddress);
+ ASSERT_TRUE(module);
+
+ EXPECT_EQ(info.start_addr, module->base_address());
+ EXPECT_EQ(info.size, module->size());
+ EXPECT_EQ(kMemoryName, module->code_file());
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ unlink(dumpfile);
+ close(fds[1]);
+}
+
+TEST(MinidumpWriterTest, DeletedBinary) {
+ static const int kNumberOfThreadsInHelperProgram = 1;
+ char kNumberOfThreadsArgument[2];
+ sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
+
+ // Locate helper binary next to the current binary.
+ char self_path[PATH_MAX];
+ if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
+ FAIL() << "readlink failed: " << strerror(errno);
+ exit(1);
+ }
+ string helper_path(self_path);
+ size_t pos = helper_path.rfind('/');
+ if (pos == string::npos) {
+ FAIL() << "no trailing slash in path: " << helper_path;
+ exit(1);
+ }
+ helper_path.erase(pos + 1);
+ helper_path += "linux_dumper_unittest_helper";
+
+ // Copy binary to a temp file.
+ char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
+ mktemp(binpath);
+ char cmdline[2 * PATH_MAX];
+ sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
+ ASSERT_EQ(0, system(cmdline));
+ ASSERT_EQ(0, chmod(binpath, 0755));
+
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ close(fds[0]);
+
+ // Pass the pipe fd and the number of threads as arguments.
+ char pipe_fd_string[8];
+ sprintf(pipe_fd_string, "%d", fds[1]);
+ execl(binpath,
+ binpath,
+ pipe_fd_string,
+ kNumberOfThreadsArgument,
+ NULL);
+ }
+ close(fds[1]);
+ // Wait for the child process to signal that it's ready.
+ struct pollfd pfd;
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fds[0];
+ pfd.events = POLLIN | POLLERR;
+
+ const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+ ASSERT_EQ(1, r);
+ ASSERT_TRUE(pfd.revents & POLLIN);
+ uint8_t junk;
+ read(fds[0], &junk, sizeof(junk));
+ close(fds[0]);
+
+ // Child is ready now.
+ // Unlink the test binary.
+ unlink(binpath);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+ mktemp(templ);
+ // Set a non-zero tid to avoid tripping asserts.
+ context.tid = 1;
+ ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
+ kill(child_pid, SIGKILL);
+
+ struct stat st;
+ ASSERT_EQ(stat(templ, &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+
+
+
+ Minidump minidump(templ);
+ ASSERT_TRUE(minidump.Read());
+
+ // Check that the main module filename is correct.
+ MinidumpModuleList* module_list = minidump.GetModuleList();
+ ASSERT_TRUE(module_list);
+ const MinidumpModule* module = module_list->GetMainModule();
+ EXPECT_STREQ(binpath, module->code_file().c_str());
+ // Check that the file ID is correct.
+ FileID fileid(helper_path.c_str());
+ uint8_t identifier[sizeof(MDGUID)];
+ EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
+ char identifier_string[kGUIDStringSize];
+ FileID::ConvertIdentifierToString(identifier,
+ identifier_string,
+ kGUIDStringSize);
+ string module_identifier(identifier_string);
+ // Strip out dashes
+ while ((pos = module_identifier.find('-')) != string::npos) {
+ module_identifier.erase(pos, 1);
+ }
+ // And append a zero, because module IDs include an "age" field
+ // which is always zero on Linux.
+ module_identifier += "0";
+ EXPECT_EQ(module_identifier, module->debug_identifier());
+
+ unlink(templ);
+}