module-info-api
author Ted Mielczarek <ted.mielczarek@gmail.com>
Tue, 19 Oct 2010 14:39:14 -0400
changeset 58 68f964cf9f0bdd1dd0854fa5f0beed4d00ccae16
child 60 6c9545a1f6b262b9ccf4bea4b22c063cd211ca4a
permissions -rw-r--r--
add patch

# HG changeset patch
# Parent 1fd9d99005d29f81ec631f128b0bebb39bd71942
allow passing info about known memory mappings to MinidumpWriter and ExceptionHandler

diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc
--- a/src/client/linux/handler/exception_handler.cc
+++ b/src/client/linux/handler/exception_handler.cc
@@ -83,20 +83,22 @@
 #endif
 #include <sys/wait.h>
 #if !defined(__ANDROID__)
 #include <ucontext.h>
 #endif
 #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"
 #include "third_party/lss/linux_syscall_support.h"
 
 #ifndef PR_SET_PTRACER
 #define PR_SET_PTRACER 0x59616d61
 #endif
@@ -444,18 +446,21 @@
     sys_write(2, "\n", 1);
   }
 }
 
 // This function runs in a compromised context: see the top of the file.
 // 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_info_);
 }
 
 // static
 bool ExceptionHandler::WriteMinidump(const std::string &dump_path,
                                      MinidumpCallback callback,
                                      void* callback_context) {
   ExceptionHandler eh(dump_path, NULL, callback, callback_context, false);
   return eh.WriteMinidump();
@@ -477,9 +482,26 @@
   bool success = GenerateDump(&context);
   UpdateNextID();
   return success;
 #else
   return false;
 #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)));
+
+  std::pair<MappingInfo, u_int8_t[sizeof(MDGUID)]> mapping;
+  mapping.first = info;
+  memcpy(mapping.second, identifier, sizeof(MDGUID));
+  mapping_info_.push_back(mapping);
+}
+
 }  // namespace google_breakpad
diff --git a/src/client/linux/handler/exception_handler.h b/src/client/linux/handler/exception_handler.h
--- a/src/client/linux/handler/exception_handler.h
+++ b/src/client/linux/handler/exception_handler.h
@@ -25,27 +25,30 @@
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
 #define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
 
+#include <string>
 #include <vector>
-#include <string>
 
 #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;
 
 namespace google_breakpad {
 
 class ExceptionHandler;
 
@@ -176,16 +179,25 @@
 #endif
   };
 
   // Returns whether out-of-process dump generation is used or not.
   bool IsOutOfProcess() const {
       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);
   bool InstallHandlers();
   void UninstallHandlers();
   void PreresolveSymbols();
   bool GenerateDump(CrashContext *context);
   void SendContinueSignalToChild();
@@ -231,13 +243,17 @@
   std::vector<std::pair<int, struct sigaction *> > old_handlers_;
 
   // We need to explicitly enable ptrace of parent processes on some
   // kernels, but we need to know the PID of the cloned process before we
   // can do this. We create a pipe which we can use to block the
   // 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_info_;
 };
 
 }  // namespace google_breakpad
 
 #endif  // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc
--- a/src/client/linux/handler/exception_handler_unittest.cc
+++ b/src/client/linux/handler/exception_handler_unittest.cc
@@ -37,16 +37,17 @@
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
 
 #include "breakpad_googletest_includes.h"
 #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"
 
 using namespace google_breakpad;
 
 #if !defined(__ANDROID__)
 #define TEMPDIR "/tmp"
@@ -568,16 +569,97 @@
   ASSERT_TRUE(exception);
   ASSERT_TRUE(memory_list);
   ASSERT_EQ((unsigned int)1, memory_list->region_count());
 
   unlink(minidump_filename.c_str());
   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[37];
+  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));
 
 static bool
 CrashHandler(const void* crash_context, size_t crash_context_size,
              void* context) {
   const int fd = (intptr_t) context;
   int fds[2];
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -101,22 +101,22 @@
 }
 
 // Resume a thread by detaching from it.
 static bool ResumeThread(pid_t pid) {
   return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
 }
 
 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;
 }
 
 namespace google_breakpad {
 
 LinuxDumper::LinuxDumper(int pid)
     : pid_(pid),
@@ -200,26 +200,24 @@
   memcpy(path, "/proc/", 6);
   my_itos(path + 6, pid, pid_len);
   memcpy(path + 6 + pid_len, "/", 1);
   memcpy(path + 6 + pid_len + 1, node, node_len);
   memcpy(path + total_length, "\0", 1);
 }
 
 bool
-LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id,
+LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
                                          uint8_t identifier[sizeof(MDGUID)])
 {
-  assert(mapping_id < mappings_.size());
   my_memset(identifier, 0, sizeof(MDGUID));
-  const MappingInfo* mapping = mappings_[mapping_id];
   if (IsMappedFileOpenUnsafe(mapping)) {
     return false;
   }
-  int fd = sys_open(mapping->name, O_RDONLY, 0);
+  int fd = sys_open(mapping.name, O_RDONLY, 0);
   if (fd < 0)
     return false;
   struct kernel_stat st;
   if (sys_fstat(fd, &st) != 0) {
     sys_close(fd);
     return false;
   }
 #if defined(__x86_64)
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -147,17 +147,17 @@
                               size_t length);
 
   // Builds a proc path for a certain pid for a node.  path is a
   // character array that is overwritten, and node is the final node
   // 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,
+  bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
                                    uint8_t identifier[sizeof(MDGUID)]);
 
   // Utility method to find the location of where the kernel has
   // mapped linux-gate.so in memory(shows up in /proc/pid/maps as
   // [vdso], but we can't guarantee that it's the only virtual dynamic
   // shared object.  Parsing the auxilary vector for AT_SYSINFO_EHDR
   // is the safest way to go.)
   void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
--- a/src/client/linux/minidump_writer/linux_dumper_unittest.cc
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
@@ -235,17 +235,17 @@
       found_exe = true;
       break;
     }
   }
   ASSERT_TRUE(found_exe);
 
   uint8_t identifier1[sizeof(MDGUID)];
   uint8_t identifier2[sizeof(MDGUID)];
-  EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(i, identifier1));
+  EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], identifier1));
   FileID fileid(exe_name);
   EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
   char identifier_string1[37];
   char identifier_string2[37];
   FileID::ConvertIdentifierToString(identifier1, identifier_string1,
                                     37);
   FileID::ConvertIdentifierToString(identifier2, identifier_string2,
                                     37);
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
--- a/src/client/linux/minidump_writer/minidump_writer.cc
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -363,29 +363,31 @@
 #endif
 
 namespace google_breakpad {
 
 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),
 #if !defined(__ARM_EABI__)
         float_state_(&context->float_state),
 #else
         //TODO: fix this after fixing ExceptionHandler
         float_state_(NULL),
 #endif
         crashing_tid_(context->tid),
         dumper_(crashing_pid),
-        memory_blocks_(dumper_.allocator()) {
+        memory_blocks_(dumper_.allocator()),
+        mapping_info_(mappings) {
   }
 
   bool Init() {
     return dumper_.Init() && minidump_writer_.Open(filename_) &&
            dumper_.ThreadsSuspend();
   }
 
   ~MinidumpWriter() {
@@ -746,91 +748,133 @@
         mapping.offset || // we only want to include one mapping per shared lib.
         mapping.size < 4096) {  // too small to get a signature for.
       return false;
     }
 
     return true;
   }
 
+  // If there is caller-provided information about this mapping
+  // in the mapping_info_ list, return true. Otherwise, return false.
+  bool HaveMappingInfo(const MappingInfo& mapping) {
+    for (MappingList::const_iterator iter = mapping_info_.begin();
+         iter != mapping_info_.end();
+         ++iter) {
+      if (iter->first.start_addr == mapping.start_addr &&
+          iter->first.size == mapping.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_info_.size();
 
     for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
       const MappingInfo& mapping = *dumper_.mappings()[i];
       if (ShouldIncludeMapping(mapping))
         num_output_mappings++;
     }
 
     TypedMDRVA<uint32_t> list(&minidump_writer_);
     if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE))
       return false;
 
     dirent->stream_type = MD_MODULE_LIST_STREAM;
     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, mod, NULL))
         return false;
-
-      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);
-
-      // 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))
+      list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
+    }
+    // Next write all the mappings provided by the caller
+    for (MappingList::const_iterator iter = mapping_info_.begin();
+         iter != mapping_info_.end();
+         ++iter) {
+      MDRawModule mod;
+      if (!FillRawModule(iter->first, mod, iter->second))
         return false;
-      mod.module_name_rva = ld.rva;
-
       list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
     }
 
     return true;
   }
 
+  // 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.
+  bool FillRawModule(const MappingInfo& mapping,
+                     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++;
+
+    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))
+      return false;
+
+    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, 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;
+  }
+
   bool WriteMemoryListStream(MDRawDirectory* dirent) {
     TypedMDRVA<uint32_t> list(&minidump_writer_);
     if (!list.AllocateObjectAndArray(memory_blocks_.size(),
                                      sizeof(MDMemoryDescriptor)))
       return false;
 
     dirent->stream_type = MD_MEMORY_LIST_STREAM;
     dirent->location = list.location();
@@ -1231,23 +1275,32 @@
   const pid_t crashing_tid_;  // the process which actually crashed
   LinuxDumper dumper_;
   MinidumpFileWriter minidump_writer_;
   MDLocationDescriptor crashing_thread_context_;
   // Blocks of memory written to the dump. These are all currently
   // 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_info_;
 };
 
 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();
 }
 
 }  // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h
--- a/src/client/linux/minidump_writer/minidump_writer.h
+++ b/src/client/linux/minidump_writer/minidump_writer.h
@@ -25,29 +25,43 @@
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
 #define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
 
+#include <list>
+#include <utility>
+
 #include <stdint.h>
 #include <unistd.h>
 
+#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.
 //   filename: the filename to write to. This is opened O_EXCL and fails if
 //     open fails.
 //   crashing_process: the pid of the crashing process. This must be trusted.
 //   blob: a blob of data from the crashing process. See exception_handler.h
 //   blob_size: the length of |blob|, in bytes
 //
 // Returns true iff successful.
 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
--- a/src/client/linux/minidump_writer/minidump_writer_unittest.cc
+++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -25,20 +25,23 @@
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <unistd.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;
 
 #if !defined(__ANDROID__)
 #define TEMPDIR "/tmp"
 #else
 #define TEMPDIR "/data/local/tmp"
 #endif
@@ -69,8 +72,96 @@
   ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
   struct stat st;
   ASSERT_EQ(stat(templ, &st), 0);
   ASSERT_GT(st.st_size, 0u);
   unlink(templ);
 
   close(fds[1]);
 }
+
+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[37];
+  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));
+
+  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;
+  std::pair<MappingInfo, u_int8_t[sizeof(MDGUID)]> 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]);
+}