Index: Makefile.in
===================================================================
--- Makefile.in	(revision 234)
+++ Makefile.in	(working copy)
@@ -72,7 +72,9 @@
 	src/processor/minidump_processor_unittest$(EXEEXT) \
 	src/processor/pathname_stripper_unittest$(EXEEXT) \
 	src/processor/postfix_evaluator_unittest$(EXEEXT) \
-	src/processor/range_map_unittest$(EXEEXT) $(am__EXEEXT_1)
+	src/processor/range_map_unittest$(EXEEXT) \
+	src/processor/module_serialize_unittest$(EXEEXT) \
+	$(am__EXEEXT_1)
 @SELFTEST_TRUE@am__append_1 = \
 @SELFTEST_TRUE@	src/processor/stackwalker_selftest
 
@@ -107,8 +109,9 @@
 am__dirstamp = $(am__leading_dot)dirstamp
 am_src_libbreakpad_la_OBJECTS = src/processor/basic_code_modules.lo \
 	src/processor/basic_source_line_resolver.lo \
-	src/processor/call_stack.lo src/processor/logging.lo \
-	src/processor/minidump.lo src/processor/minidump_processor.lo \
+	src/processor/call_stack.lo src/processor/disk_module_cache.lo \
+	src/processor/logging.lo src/processor/minidump.lo \
+	src/processor/minidump_processor.lo \
 	src/processor/pathname_stripper.lo \
 	src/processor/process_state.lo \
 	src/processor/simple_symbol_supplier.lo \
@@ -169,8 +172,9 @@
 src_processor_minidump_stackwalk_DEPENDENCIES =  \
 	src/processor/basic_code_modules.lo \
 	src/processor/basic_source_line_resolver.lo \
-	src/processor/call_stack.lo src/processor/logging.lo \
-	src/processor/minidump.lo src/processor/minidump_processor.lo \
+	src/processor/call_stack.lo src/processor/disk_module_cache.lo \
+	src/processor/logging.lo src/processor/minidump.lo \
+	src/processor/minidump_processor.lo \
 	src/processor/pathname_stripper.lo \
 	src/processor/process_state.lo \
 	src/processor/simple_symbol_supplier.lo \
@@ -179,6 +183,15 @@
 	src/processor/stackwalker_ppc.lo \
 	src/processor/stackwalker_sparc.lo \
 	src/processor/stackwalker_x86.lo
+am_src_processor_module_serialize_unittest_OBJECTS =  \
+	src/processor/module_serialize_unittest.$(OBJEXT)
+src_processor_module_serialize_unittest_OBJECTS =  \
+	$(am_src_processor_module_serialize_unittest_OBJECTS)
+src_processor_module_serialize_unittest_DEPENDENCIES =  \
+	src/processor/basic_code_modules.lo \
+	src/processor/basic_source_line_resolver.lo \
+	src/processor/call_stack.lo src/processor/logging.lo \
+	src/processor/pathname_stripper.lo
 am_src_processor_pathname_stripper_unittest_OBJECTS =  \
 	src/processor/pathname_stripper_unittest.$(OBJEXT)
 src_processor_pathname_stripper_unittest_OBJECTS =  \
@@ -240,6 +253,7 @@
 	$(src_processor_minidump_dump_SOURCES) \
 	$(src_processor_minidump_processor_unittest_SOURCES) \
 	$(src_processor_minidump_stackwalk_SOURCES) \
+	$(src_processor_module_serialize_unittest_SOURCES) \
 	$(src_processor_pathname_stripper_unittest_SOURCES) \
 	$(src_processor_postfix_evaluator_unittest_SOURCES) \
 	$(src_processor_range_map_unittest_SOURCES) \
@@ -251,6 +265,7 @@
 	$(src_processor_minidump_dump_SOURCES) \
 	$(src_processor_minidump_processor_unittest_SOURCES) \
 	$(src_processor_minidump_stackwalk_SOURCES) \
+	$(src_processor_module_serialize_unittest_SOURCES) \
 	$(src_processor_pathname_stripper_unittest_SOURCES) \
 	$(src_processor_postfix_evaluator_unittest_SOURCES) \
 	$(src_processor_range_map_unittest_SOURCES) \
@@ -401,6 +416,7 @@
 	src/google_breakpad/processor/minidump_processor.h \
 	src/google_breakpad/processor/process_state.h \
 	src/google_breakpad/processor/source_line_resolver_interface.h \
+	src/google_breakpad/processor/source_line_resolver_module_cache_interface.h \
 	src/google_breakpad/processor/stack_frame.h \
 	src/google_breakpad/processor/stack_frame_cpu.h \
 	src/google_breakpad/processor/stackwalker.h \
@@ -415,6 +431,8 @@
 	src/processor/call_stack.cc \
 	src/processor/contained_range_map-inl.h \
 	src/processor/contained_range_map.h \
+	src/processor/disk_module_cache.cc \
+	src/processor/disk_module_cache.h \
 	src/processor/linked_ptr.h \
 	src/processor/logging.h \
 	src/processor/logging.cc \
@@ -508,6 +526,16 @@
 	src/processor/logging.lo \
 	src/processor/pathname_stripper.lo
 
+src_processor_module_serialize_unittest_SOURCES = \
+	src/processor/module_serialize_unittest.cc
+
+src_processor_module_serialize_unittest_LDADD = \
+	src/processor/basic_code_modules.lo \
+	src/processor/basic_source_line_resolver.lo \
+	src/processor/call_stack.lo \
+	src/processor/logging.lo \
+	src/processor/pathname_stripper.lo
+
 src_processor_stackwalker_selftest_SOURCES = \
 	src/processor/stackwalker_selftest.cc
 
@@ -541,6 +569,7 @@
 	src/processor/basic_code_modules.lo \
 	src/processor/basic_source_line_resolver.lo \
 	src/processor/call_stack.lo \
+	src/processor/disk_module_cache.lo \
 	src/processor/logging.lo \
 	src/processor/minidump.lo \
 	src/processor/minidump_processor.lo \
@@ -778,6 +807,8 @@
 	src/processor/$(DEPDIR)/$(am__dirstamp)
 src/processor/call_stack.lo: src/processor/$(am__dirstamp) \
 	src/processor/$(DEPDIR)/$(am__dirstamp)
+src/processor/disk_module_cache.lo: src/processor/$(am__dirstamp) \
+	src/processor/$(DEPDIR)/$(am__dirstamp)
 src/processor/logging.lo: src/processor/$(am__dirstamp) \
 	src/processor/$(DEPDIR)/$(am__dirstamp)
 src/processor/minidump.lo: src/processor/$(am__dirstamp) \
@@ -883,6 +914,12 @@
 src/processor/minidump_stackwalk$(EXEEXT): $(src_processor_minidump_stackwalk_OBJECTS) $(src_processor_minidump_stackwalk_DEPENDENCIES) src/processor/$(am__dirstamp)
 	@rm -f src/processor/minidump_stackwalk$(EXEEXT)
 	$(CXXLINK) $(src_processor_minidump_stackwalk_OBJECTS) $(src_processor_minidump_stackwalk_LDADD) $(LIBS)
+src/processor/module_serialize_unittest.$(OBJEXT):  \
+	src/processor/$(am__dirstamp) \
+	src/processor/$(DEPDIR)/$(am__dirstamp)
+src/processor/module_serialize_unittest$(EXEEXT): $(src_processor_module_serialize_unittest_OBJECTS) $(src_processor_module_serialize_unittest_DEPENDENCIES) src/processor/$(am__dirstamp)
+	@rm -f src/processor/module_serialize_unittest$(EXEEXT)
+	$(CXXLINK) $(src_processor_module_serialize_unittest_OBJECTS) $(src_processor_module_serialize_unittest_LDADD) $(LIBS)
 src/processor/pathname_stripper_unittest.$(OBJEXT):  \
 	src/processor/$(am__dirstamp) \
 	src/processor/$(DEPDIR)/$(am__dirstamp)
@@ -919,6 +956,8 @@
 	-rm -f src/processor/call_stack.$(OBJEXT)
 	-rm -f src/processor/call_stack.lo
 	-rm -f src/processor/contained_range_map_unittest.$(OBJEXT)
+	-rm -f src/processor/disk_module_cache.$(OBJEXT)
+	-rm -f src/processor/disk_module_cache.lo
 	-rm -f src/processor/logging.$(OBJEXT)
 	-rm -f src/processor/logging.lo
 	-rm -f src/processor/minidump.$(OBJEXT)
@@ -928,6 +967,7 @@
 	-rm -f src/processor/minidump_processor.lo
 	-rm -f src/processor/minidump_processor_unittest.$(OBJEXT)
 	-rm -f src/processor/minidump_stackwalk.$(OBJEXT)
+	-rm -f src/processor/module_serialize_unittest.$(OBJEXT)
 	-rm -f src/processor/pathname_stripper.$(OBJEXT)
 	-rm -f src/processor/pathname_stripper.lo
 	-rm -f src/processor/pathname_stripper_unittest.$(OBJEXT)
@@ -958,12 +998,14 @@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/basic_source_line_resolver_unittest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/call_stack.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/contained_range_map_unittest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/disk_module_cache.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/logging.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_dump.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_processor.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_processor_unittest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/minidump_stackwalk.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/module_serialize_unittest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/pathname_stripper.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/pathname_stripper_unittest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@src/processor/$(DEPDIR)/postfix_evaluator_unittest.Po@am__quote@
Index: src/processor/range_map.h
===================================================================
--- src/processor/range_map.h	(revision 234)
+++ src/processor/range_map.h	(working copy)
@@ -43,10 +43,8 @@
 
 #include <map>
 
-
 namespace google_breakpad {
 
-
 template<typename AddressType, typename EntryType>
 class RangeMap {
  public:
@@ -92,6 +90,8 @@
   // initially created.
   void Clear();
 
+  bool Equals(const RangeMap<AddressType, EntryType> &other) const;
+
  private:
   class Range {
    public:
@@ -101,6 +101,10 @@
     AddressType base() const { return base_; }
     EntryType entry() const { return entry_; }
 
+    //XXX: I really wanted this to be Equals, but
+    // I can't seem to get that to work properly.
+    bool operator==(const Range &other) const;
+
    private:
     // The base address of the range.  The high address does not need to
     // be stored, because RangeMap uses it as the key to the map.
@@ -117,6 +121,11 @@
 
   // Maps the high address of each range to a EntryType.
   AddressToRangeMap map_;
+  
+  friend class ModuleSerializer;
+
+  //template<typename T>
+  //friend bool Equals(const T &a, const T &b);
 };
 
 
Index: src/processor/address_map-inl.h
===================================================================
--- src/processor/address_map-inl.h	(revision 234)
+++ src/processor/address_map-inl.h	(working copy)
@@ -39,6 +39,7 @@
 #include <cassert>
 
 #include "processor/address_map.h"
+#include "processor/equals.h"
 #include "processor/logging.h"
 
 namespace google_breakpad {
@@ -87,6 +88,12 @@
   map_.clear();
 }
 
+template<typename AddressType, typename EntryType>
+bool AddressMap<AddressType, EntryType>::Equals(
+  const AddressMap<AddressType, EntryType> &other) const {
+  return google_breakpad::Equals(map_, other.map_);
+}
+
 }  // namespace google_breakpad
 
 #endif  // PROCESSOR_ADDRESS_MAP_INL_H__
Index: src/processor/range_map-inl.h
===================================================================
--- src/processor/range_map-inl.h	(revision 234)
+++ src/processor/range_map-inl.h	(working copy)
@@ -40,12 +40,12 @@
 #include <cassert>
 
 #include "processor/range_map.h"
+#include "processor/equals.h"
 #include "processor/logging.h"
 
 
 namespace google_breakpad {
 
-
 template<typename AddressType, typename EntryType>
 bool RangeMap<AddressType, EntryType>::StoreRange(const AddressType &base,
                                                   const AddressType &size,
@@ -203,7 +203,18 @@
   map_.clear();
 }
 
+template<typename AddressType, typename EntryType>
+bool RangeMap<AddressType, EntryType>::Equals(
+       const RangeMap<AddressType, EntryType> &other) const {
+  return google_breakpad::Equals(map_, other.map_);
+}
 
+template<typename AddressType, typename EntryType>
+bool RangeMap<AddressType, EntryType>::Range::operator==(const Range &other) const {
+  return google_breakpad::Equals(base_, other.base_) &&
+    google_breakpad::Equals(entry_, other.entry_);
+}
+
 }  // namespace google_breakpad
 
 
Index: src/processor/contained_range_map.h
===================================================================
--- src/processor/contained_range_map.h	(revision 234)
+++ src/processor/contained_range_map.h	(working copy)
@@ -102,6 +102,8 @@
   // empty state when called on the root node.
   void Clear();
 
+  bool Equals(const ContainedRangeMap<AddressType, EntryType> &other) const;
+
  private:
   // AddressToRangeMap stores pointers.  This makes reparenting simpler in
   // StoreRange, because it doesn't need to copy entire objects.
@@ -136,6 +138,8 @@
   // address.  This is a pointer to avoid allocating map structures for
   // leaf nodes, where they are not needed.
   AddressToRangeMap *map_;
+
+  friend class ModuleSerializer;
 };
 
 
Index: src/processor/disk_module_cache.h
===================================================================
--- src/processor/disk_module_cache.h	(revision 0)
+++ src/processor/disk_module_cache.h	(revision 0)
@@ -0,0 +1,74 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// 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.
+
+// DiskModuleCache implements SourceLineResolverModuleCacheInterface,
+// storing the cached objects on disk.
+
+#ifndef GOOGLE_BREAKPAD_PROCESSOR_DISK_MODULE_CACHE_H__
+#define GOOGLE_BREAKPAD_PROCESSOR_DISK_MODULE_CACHE_H__
+
+#include "google_breakpad/processor/source_line_resolver_module_cache_interface.h"
+
+namespace google_breakpad {
+
+using std::string;
+using std::istream;
+using std::ostream;
+
+class DiskModuleCache : public SourceLineResolverModuleCacheInterface {
+ public:
+  DiskModuleCache(string cache_directory);
+  virtual ~DiskModuleCache() {}
+
+  // Retrieve the symbol data associated with a module
+  virtual bool GetModuleData(const string &symbol_file,
+                             istream **data_stream);
+
+  // Get a stream to which the data associated with a module
+  // can be stored
+  virtual bool BeginSetModuleData(const string &symbol_file,
+                                  ostream **data_stream);
+  // Finish setting the data associated with this module,
+  // data should already have been written to the stream
+  virtual bool EndSetModuleData(const string &symbol_file,
+                                ostream **data_stream);
+
+ private:
+  string cache_directory_;
+  // Given a symbol file, map to a cache entry on disk
+  string MapToCacheEntry(const string &symbol_file);
+  // Given a file path, ensure that all directories in the path exist.
+  bool EnsurePathExists(const string &cache_entry);
+  // determine if this path is a directory
+  bool IsDir(const string &path);
+};
+
+} // namespace google_breakpad
+
+#endif  // GOOGLE_BREAKPAD_PROCESSOR_DISK_MODULE_CACHE_H__
Index: src/processor/minidump_stackwalk.cc
===================================================================
--- src/processor/minidump_stackwalk.cc	(revision 234)
+++ src/processor/minidump_stackwalk.cc	(working copy)
@@ -45,6 +45,7 @@
 #include "google_breakpad/processor/minidump_processor.h"
 #include "google_breakpad/processor/process_state.h"
 #include "google_breakpad/processor/stack_frame_cpu.h"
+#include "processor/disk_module_cache.h"
 #include "processor/logging.h"
 #include "processor/pathname_stripper.h"
 #include "processor/scoped_ptr.h"
@@ -55,6 +56,7 @@
 using std::string;
 using std::vector;
 using google_breakpad::BasicSourceLineResolver;
+using google_breakpad::DiskModuleCache;
 using google_breakpad::CallStack;
 using google_breakpad::CodeModule;
 using google_breakpad::CodeModules;
@@ -438,7 +440,8 @@
     symbol_supplier.reset(new SimpleSymbolSupplier(symbol_paths));
   }
 
-  BasicSourceLineResolver resolver;
+  DiskModuleCache disk_cache("/tmp/module_cache/");
+  BasicSourceLineResolver resolver(&disk_cache);
   MinidumpProcessor minidump_processor(symbol_supplier.get(), &resolver);
 
   // Process the minidump.
Index: src/processor/basic_source_line_resolver.cc
===================================================================
--- src/processor/basic_source_line_resolver.cc	(revision 234)
+++ src/processor/basic_source_line_resolver.cc	(working copy)
@@ -33,7 +33,9 @@
 #include <map>
 #include <utility>
 #include <vector>
+#include <sstream>
 
+#include "processor/equals.h"
 #include "processor/address_map-inl.h"
 #include "processor/contained_range_map-inl.h"
 #include "processor/range_map-inl.h"
@@ -61,6 +63,13 @@
       , source_file_id(file_id)
       , line(source_line) { }
 
+  bool operator==(const Line &other) const {
+    return address == other.address &&
+      size == other.size &&
+      source_file_id == other.source_file_id &&
+      line == other.line;
+  }
+
   MemAddr address;
   MemAddr size;
   int source_file_id;
@@ -85,6 +94,16 @@
   RangeMap< MemAddr, linked_ptr<Line> > lines;
 };
 
+// doesn't really belong here, but I can't put it anywhere else...
+inline bool Equals(const BasicSourceLineResolver::Function &a,
+                   const BasicSourceLineResolver::Function &b)
+{
+  return a.name == b.name &&
+    a.address == b.address &&
+    a.size == b.size &&
+    Equals(a.lines, b.lines);
+}
+
 struct BasicSourceLineResolver::PublicSymbol {
   PublicSymbol(const string& set_name,
                MemAddr set_address,
@@ -93,11 +112,17 @@
         address(set_address),
         parameter_size(set_parameter_size) {}
 
+  bool operator==(const PublicSymbol &other) const {
+    return name == other.name &&
+      address == other.address &&
+      parameter_size == other.parameter_size;
+  }
+
   string name;
   MemAddr address;
 
   // If the public symbol is used as a function entry point, parameter_size
-  // is set to the size of the parameters passed to the funciton on the
+  // is set to the size of the parameters passed to the function on the
   // stack, if known.
   int parameter_size;
 };
@@ -116,8 +141,11 @@
   // any returned StackFrameInfo object.
   StackFrameInfo* LookupAddress(StackFrame *frame) const;
 
+  bool Equals(const Module &other) const;
+
  private:
   friend class BasicSourceLineResolver;
+
 #ifdef BSLR_NO_HASH_MAP
   typedef map<int, string> FileMap;
 #else  // BSLR_NO_HASH_MAP
@@ -177,11 +205,395 @@
   // as certain types.
   ContainedRangeMap< MemAddr, linked_ptr<StackFrameInfo> >
       stack_info_[STACK_INFO_LAST];
+
+  friend class ModuleSerializer;
 };
 
-BasicSourceLineResolver::BasicSourceLineResolver() : modules_(new ModuleMap) {
+
+class ModuleSerializer {
+ public:
+  ModuleSerializer(std::istream *stream) : instream_(stream),
+                                           outstream_(NULL) {}
+  ModuleSerializer(std::ostream *stream) : instream_(NULL),
+                                           outstream_(stream) {}
+  
+  bool Serialize(const BasicSourceLineResolver::Module &module) {
+    if (!outstream_)
+      return false;
+
+    outstream_->seekp(0, std::ios_base::beg);
+    Write(SERIALIZE_FORMAT);
+    Write(module.files_);
+    Write(module.functions_);
+    Write(module.public_symbols_);
+    for (int i = BasicSourceLineResolver::Module::STACK_INFO_FPO;
+         i < BasicSourceLineResolver::Module::STACK_INFO_LAST; i++)
+      Write(module.stack_info_[i]);
+    return true;
+  }
+
+  bool Deserialize(BasicSourceLineResolver::Module &module) {
+    if (!instream_)
+      return false;
+
+    instream_->seekg(0, std::ios_base::beg);
+    //TODO: implement me
+    int format;
+    Read(format);
+    if (format != SERIALIZE_FORMAT)
+      return false;
+
+    Read(module.files_);
+    Read(module.functions_);
+    Read(module.public_symbols_);
+    for (int i = BasicSourceLineResolver::Module::STACK_INFO_FPO;
+         i < BasicSourceLineResolver::Module::STACK_INFO_LAST; i++)
+      Read(module.stack_info_[i]);
+
+    return true;
+  }
+
+ private:
+  // Increment this if changing the serializing format
+  static const int SERIALIZE_FORMAT = 1;
+
+  /*
+    doesn't really make sense to have this, it just hides cases where
+    there ought to be specific serializers.
+  template<typename T>
+  void Write(const T &data) {
+    outstream_->write(reinterpret_cast<const char*>(&data), sizeof(data));
+  }
+  */
+
+  template<typename T>
+  void Read(T &data) {
+    instream_->read(reinterpret_cast<char*>(&data), sizeof(data));
+  }
+
+  void Write(const size_t data) {
+    outstream_->write(reinterpret_cast<const char*>(&data), sizeof(size_t));
+  }
+
+  void Write(const u_int32_t data) {
+    outstream_->write(reinterpret_cast<const char*>(&data), sizeof(u_int32_t));
+  }
+
+  void Write(const int data) {
+    outstream_->write(reinterpret_cast<const char*>(&data), sizeof(int));
+  }
+
+  // Serialize strings as a length + character array (not null terminated)
+  // pad out to sizeof(int)
+  void Write(const string &data) {
+    if (data.empty()) {
+      Write((size_t)0);
+      return;
+    }
+
+    int extra = sizeof(int) - (data.size() % sizeof(int));
+    Write(data.size() + extra);
+    outstream_->write(data.c_str(), data.size());
+    char c = '\0';
+    for (int i=0; i<extra; i++)
+      outstream_->write(&c, 1);
+  }
+
+  void Read(string &data) {
+    size_t length;
+    Read(length);
+
+    if (length > 0) {
+      vector<char> string_bytes(length);
+      instream_->read(&string_bytes[0], length);
+      data = &string_bytes[0];
+    }
+  }
+
+  template<typename k, typename d>
+  void Write(const map<k, d> &data) {
+    Write(data.size());
+    for (typename map<k,d>::const_iterator it = data.begin();
+         it != data.end(); it++) {
+      Write(it->first);
+      Write(it->second);
+    }
+  }
+
+  template<typename k, typename v>
+  void Read(map<k, v> &data) {
+    size_t length;
+    Read(length);
+    if (length == 0) {
+      BPLOG(INFO) << "Empty map";
+      return;
+    }
+
+    for (int i=0; i<length; i++) {
+      k key;
+      v value;
+      Read(key);
+      Read(value);
+      data.insert(make_pair(key, value));
+    }
+  }
+
+#ifndef BSLR_NO_HASH_MAP
+  template<typename k, typename d>
+  void Write(const hash_map<k, d> &data) {
+    Write(data.size());
+    for (typename hash_map<k,d>::const_iterator it = data.begin();
+         it != data.end(); it++) {
+      Write(it->first);
+      Write(it->second);
+    }
+  }
+
+  template<typename k, typename v>
+  void Read(hash_map<k, v> &data) {
+    size_t length;
+    Read(length);
+    if (length == 0)
+      return;
+
+    data.resize(length);
+
+    for (int i=0; i<length; i++) {
+      k key;
+      v value;
+      Read(key);
+      Read(value);
+      data.insert(make_pair(key, value));
+    }
+  }
+#endif
+  
+  template<typename AddressType, typename EntryType>
+  void Write(const RangeMap<AddressType, EntryType> &data) {
+    Write(data.map_);
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Read(RangeMap<AddressType, EntryType> &data) {
+    // Can't instantiate Range(), so manually read in the
+    // AddressMap here.
+    size_t length;
+    Read(length);
+    if (length == 0)
+      return;
+
+    for (int i=0; i<length; i++) {
+      AddressType address, range_address;
+      EntryType entry;
+      // map key
+      Read(address);
+      // Range values
+      Read(range_address);
+      Read(entry);
+      data.map_.insert(make_pair(address,
+                                 typename RangeMap<AddressType, EntryType>::Range(range_address, entry)));
+    }
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Write(const typename RangeMap<AddressType, EntryType>::Range &data) {
+    Write(data.base_);
+    Write(data.entry_);
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Write(const AddressMap<AddressType, EntryType> &data) {
+    Write(data.map_);
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Read(AddressMap<AddressType, EntryType> &data) {
+    Read(data.map_);
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Write(const ContainedRangeMap<AddressType, EntryType> &data) {
+    Write(data.base_);
+    Write(data.entry_);
+    if (data.map_) {
+      // write this as a marker
+      Write((size_t)1);
+      Write(*data.map_);
+    }
+    else {
+      Write((size_t)0);
+    }
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Read(ContainedRangeMap<AddressType, EntryType> &data) {
+    // sort of evil, but that's ok
+    AddressType *base_ptr = const_cast<AddressType*>(&data.base_);
+    Read(*base_ptr);
+    EntryType *entry_ptr = const_cast<EntryType*>(&data.entry_);
+    Read(*entry_ptr);
+
+    size_t marker;
+    Read(marker);
+    if (marker == 1) {
+      data.map_ = new typename ContainedRangeMap<AddressType, EntryType>::AddressToRangeMap();
+      Read(*data.map_);
+    }
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Write(const ContainedRangeMap<AddressType, EntryType> * const &data) {
+    BPLOG(INFO) << "Write(ContainedRangeMap *)";
+    if (data) {
+      // write this as a marker
+      Write((size_t)1);
+      Write(*data);
+    }
+    else {
+      Write((size_t)0);
+    }
+  }
+
+  template<typename AddressType, typename EntryType>
+  void Read(ContainedRangeMap<AddressType, EntryType> *&data) {
+    size_t marker;
+    Read(marker);
+    if (marker == 1) {
+      data = new ContainedRangeMap<AddressType, EntryType>();
+      Read(*data);
+    }
+    else {
+      data = NULL;
+    }
+  }
+
+  void Write(const BasicSourceLineResolver::Function &data) {
+    Write(data.name);
+    Write(data.address);
+    Write(data.size);
+    Write(data.parameter_size);
+    Write(data.lines);
+  }
+
+  // Read this as a pointer because there's no zero-argument
+  // constructor.
+  void Read(BasicSourceLineResolver::Function *&data) {
+    string name;
+    SourceLineResolverInterface::MemAddr address;
+    SourceLineResolverInterface::MemAddr size;
+    int parameter_size;
+
+    Read(name);
+    Read(address);
+    Read(size);
+    Read(parameter_size);
+    data = new BasicSourceLineResolver::Function(name, address, size,
+                                                 parameter_size);
+    Read(data->lines);
+  }
+
+  void Write(const BasicSourceLineResolver::PublicSymbol &data) {
+    Write(data.name);
+    Write(data.address);
+    Write(data.parameter_size);
+  }
+
+  // Read this as a pointer because there's no zero-argument
+  // constructor.
+  void Read(BasicSourceLineResolver::PublicSymbol *&data) {
+    string name;
+    SourceLineResolverInterface::MemAddr address;
+    int parameter_size;
+    Read(name);
+    Read(address);
+    Read(parameter_size);
+    data = new BasicSourceLineResolver::PublicSymbol(name, address,
+                                                     parameter_size);
+  }
+
+  void Write(const BasicSourceLineResolver::Line &data) {
+    Write(data.address);
+    Write(data.size);
+    Write(data.source_file_id);
+    Write(data.line);
+  }
+
+  // Read this as a pointer because there's no zero-argument
+  // constructor.
+  void Read(BasicSourceLineResolver::Line *&data) {
+    SourceLineResolverInterface::MemAddr address;
+    SourceLineResolverInterface::MemAddr size;
+    int source_file_id;
+    int line;
+
+    data = new BasicSourceLineResolver::Line(address, size, source_file_id,
+                                             line);
+  }
+
+  void Write(const StackFrameInfo &data) {
+    Write(data.valid);
+    Write(data.prolog_size);
+    Write(data.epilog_size);
+    Write(data.parameter_size);
+    Write(data.saved_register_size);
+    Write(data.local_size);
+    Write(data.max_stack_size);
+    Write(data.allocates_base_pointer);
+    Write(data.program_string);
+  }
+
+  // Just for consistency with the above methods.
+  void Read(StackFrameInfo *&data) {
+    data = new StackFrameInfo();
+    Read(data->valid);
+    Read(data->prolog_size);
+    Read(data->epilog_size);
+    Read(data->parameter_size);
+    Read(data->saved_register_size);
+    Read(data->local_size);
+    Read(data->max_stack_size);
+    Read(data->allocates_base_pointer);
+    Read(data->program_string);
+  }
+
+  template<typename T>
+  void Write(const linked_ptr<T> &data) {
+    if (data.get()) {
+      // write this as a marker
+      Write((size_t)1);
+      Write(*data);
+    }
+    else {
+      Write((size_t)0);
+    }
+  }
+
+  template<typename T>
+  void Read(linked_ptr<T> &data) {
+    size_t marker;
+    Read(marker);
+    if (marker == 1) {
+      T *thing;
+      // Read will allocate the pointer
+      Read(thing);
+      data.reset(thing);
+    }
+  }
+
+  std::istream *instream_;
+  std::ostream *outstream_;
+};
+
+BasicSourceLineResolver::BasicSourceLineResolver() :
+  modules_(new ModuleMap),
+  module_cache_(NULL) {
 }
 
+BasicSourceLineResolver::BasicSourceLineResolver(SourceLineResolverModuleCacheInterface *module_cache) :
+  modules_(new ModuleMap),
+  module_cache_(module_cache) {
+}
+
 BasicSourceLineResolver::~BasicSourceLineResolver() {
   ModuleMap::iterator it;
   for (it = modules_->begin(); it != modules_->end(); ++it) {
@@ -202,11 +614,34 @@
                  map_file;
 
   Module *module = new Module(module_name);
-  if (!module->LoadMap(map_file)) {
-    delete module;
-    return false;
+  
+  // First see if we have a cache, and if so,
+  // if the cache contains this module
+  istream *instream;
+  if (module_cache_ &&
+      module_cache_->GetModuleData(map_file, &instream)) {
+    ModuleSerializer serializer(instream);
+    serializer.Deserialize(*module);
+    delete instream;
   }
+  else {
+    // Otherwise load from file
+    if (!module->LoadMap(map_file)) {
+      delete module;
+      return false;
+    }
 
+    // If we have a cache, then store the result
+    ostream *outstream;
+    if (module_cache_ &&
+        module_cache_->BeginSetModuleData(map_file, &outstream)) {
+      ModuleSerializer serializer(outstream);
+      serializer.Serialize(*module);
+      module_cache_->EndSetModuleData(map_file, &outstream);
+      delete outstream;
+    }
+  }
+
   modules_->insert(make_pair(module_name, module));
   return true;
 }
@@ -402,6 +837,23 @@
   return frame_info.release();
 }
 
+bool BasicSourceLineResolver::Module::Equals(const Module &other) const {
+  return
+    google_breakpad::Equals(files_, other.files_) &&
+    google_breakpad::Equals(functions_, other.functions_) &&
+    google_breakpad::Equals(public_symbols_, other.public_symbols_) &&
+    google_breakpad::Equals(stack_info_[STACK_INFO_FPO],
+                            other.stack_info_[STACK_INFO_FPO]) &&
+    google_breakpad::Equals(stack_info_[STACK_INFO_TRAP],
+                            other.stack_info_[STACK_INFO_TRAP]) &&
+    google_breakpad::Equals(stack_info_[STACK_INFO_TSS],
+                            other.stack_info_[STACK_INFO_TSS]) &&
+    google_breakpad::Equals(stack_info_[STACK_INFO_STANDARD],
+           other.stack_info_[STACK_INFO_STANDARD]) &&
+    google_breakpad::Equals(stack_info_[STACK_INFO_FRAME_DATA],
+           other.stack_info_[STACK_INFO_FRAME_DATA]);
+}
+
 // static
 bool BasicSourceLineResolver::Module::Tokenize(char *line, int max_tokens,
                                                vector<char*> *tokens) {
@@ -611,4 +1063,43 @@
 }
 #endif  // BSLR_NO_HASH_MAP
 
+// static
+bool BasicSourceLineResolver::ModuleRoundTripTest(const string &map_file) {
+  Module *module = new Module("test");
+  if (!module->LoadMap(map_file)) {
+    BPLOG(ERROR) << "Failed to load map file!";
+    return false;
+  }
+
+  if (!module->Equals(*module)) { // sanity check
+    BPLOG(ERROR) << "Failed sanity check!";
+    return false;
+  }
+
+  std::stringstream memory_stream(std::ios::in | std::ios::out |
+                                  std::ios::binary);
+  memory_stream.exceptions(std::ios::eofbit | std::ios::failbit |
+                           std::ios::badbit);
+  ModuleSerializer serializer(static_cast<ostream*>(&memory_stream));
+  if (!serializer.Serialize(*module)) {
+    BPLOG(ERROR) << "Failed to serialize Module!";
+    return false;
+  }
+
+  BPLOG(INFO) << "Serialized " << memory_stream.tellp() << " bytes.";
+
+  Module *new_module = new Module("test");
+  ModuleSerializer deserializer(static_cast<istream*>(&memory_stream));
+  if (!deserializer.Deserialize(*new_module)) {
+    BPLOG(ERROR) << "Failed to deserialize Module!";
+    return false;
+  }
+
+  if (!module->Equals(*new_module)) {
+    BPLOG(ERROR) << "Deserialized module not equivalent to original!";
+    return false;
+  }
+  return true;
+}
+
 }  // namespace google_breakpad
Index: src/processor/module_serialize_unittest.cc
===================================================================
--- src/processor/module_serialize_unittest.cc	(revision 0)
+++ src/processor/module_serialize_unittest.cc	(revision 0)
@@ -0,0 +1,60 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// 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.
+
+// module_serialize_unittest.cc: Unit tests for
+// BasicSourceLineResolver::Module serialization.
+//
+// Author: Ted Mielczarek
+
+#include <fstream>
+#include "stdio.h"
+
+#include "processor/logging.h"
+#include "google_breakpad/processor/basic_source_line_resolver.h"
+
+int main(int argc, char **argv) {
+  BPLOG_INIT(&argc, &argv);
+
+  char symbol_test_files[][1024] = {
+    "src/processor/testdata/symbols/kernel32.pdb/BCE8785C57B44245A669896B6A19B9542/kernel32.sym",
+    "src/processor/testdata/symbols/test_app.pdb/5A9832E5287241C1838ED98914E9B7FF1/test_app.sym" };
+
+  for (int i=0; i < sizeof(symbol_test_files) / sizeof(symbol_test_files[0]);
+       i++) {
+    BPLOG(INFO) << "Testing round trip serialize for symbol file "
+                << symbol_test_files[i];
+    if (!google_breakpad::BasicSourceLineResolver::ModuleRoundTripTest(symbol_test_files[i])) {
+      BPLOG(ERROR) << "FAILED: module round trip test for symbol file "
+                   << symbol_test_files[i];
+      return 1;
+    }
+  }
+
+  return 0;
+}
Index: src/processor/stack_frame_info.h
===================================================================
--- src/processor/stack_frame_info.h	(revision 234)
+++ src/processor/stack_frame_info.h	(working copy)
@@ -100,6 +100,18 @@
     program_string.erase();
   }
 
+  bool operator==(const StackFrameInfo &other) const {
+    return valid == other.valid &&
+      prolog_size == other.prolog_size &&
+      epilog_size == other.epilog_size &&
+      parameter_size == other.parameter_size &&
+      saved_register_size == other.saved_register_size &&
+      local_size == other.local_size &&
+      max_stack_size == other.max_stack_size &&
+      allocates_base_pointer == other.allocates_base_pointer &&
+      program_string == other.program_string;
+  }
+
   // Identifies which fields in the structure are valid.  This is of
   // type Validity, but it is defined as an int because it's not
   // possible to OR values into an enumerated type.  Users must check
Index: src/processor/equals.h
===================================================================
--- src/processor/equals.h	(revision 0)
+++ src/processor/equals.h	(revision 0)
@@ -0,0 +1,199 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// 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.
+
+// equals.h: Templatized functions for checking equivalence.
+//
+// Use these instead of operator== so that we can check for equivalence
+// of objects stored as pointers inside linked_ptrs.  Equals knows
+// how to compare certain objects (RangeMap, AddressMap, ContainedRangeMap),
+// and special-cases linked_ptr as well.  It defaults to operator== for
+// everything else.
+//
+// Author: Ted Mielczarek
+
+#ifndef GOOGLE_BREAKPAD_EQUALS_H_
+#define GOOGLE_BREAKPAD_EQUALS_H_
+
+#include <map>
+
+#ifdef __SUNPRO_CC
+#define BSLR_NO_HASH_MAP
+#endif  // __SUNPRO_CC
+
+#ifndef BSLR_NO_HASH_MAP
+#include <ext/hash_map>
+#endif
+
+#include "google_breakpad/processor/basic_source_line_resolver.h"
+#include "processor/linked_ptr.h"
+#include "processor/range_map.h"
+#include "processor/address_map.h"
+#include "processor/contained_range_map.h"
+#include "processor/logging.h"
+
+namespace google_breakpad {
+
+using std::map;
+#ifndef BSLR_NO_HASH_MAP
+using __gnu_cxx::hash_map;
+#endif
+
+// In the general case, just use operator==
+// This should handle base types.
+template<typename T>
+inline bool Equals(const T &a, const T &b)
+{
+  return a == b;
+}
+
+// for linked_ptr, we want to compare the objects
+// pointed at, not the raw pointers
+template<typename U>
+bool Equals(const linked_ptr<U> &a, const linked_ptr<U> &b)
+{
+  if (a.get() && b.get())
+     return Equals(*a, *b);
+
+  // if one is NULL, just compare pointers
+  return a == b;
+}
+
+// for std::map/hash_map, we want to call Equals on each component of
+// each pair
+#ifndef BSLR_NO_HASH_MAP
+template<typename k, typename v>
+bool Equals(const hash_map<k, v> &a, const hash_map<k, v> &b)
+{
+  // simple check for equivalent size first
+  if (a.size() != b.size())
+    return false;
+
+  typename hash_map<k,v>::const_iterator it, found;
+  for (it = a.begin(); it != a.end(); it++) {
+    // if b doesn't contain this entry, then
+    // just get out
+    found = b.find(it->first);
+    if (found == b.end())
+      return false;
+
+    if (!Equals(it->second, found->second))
+      return false;
+  }
+  return true;
+}
+#endif  // BSLR_NO_HASH_MAP
+
+template<typename T>
+void Display(const T &a, const T &b)
+{
+  BPLOG(INFO) << "Display with unknown types";
+  throw a;
+}
+
+template<typename AddressType, typename EntryType>
+void Display(ContainedRangeMap<AddressType, EntryType> * const &a,
+             ContainedRangeMap<AddressType, EntryType> * const &b)
+{
+  BPLOG(INFO) << "Display: " << a << ", " << b;
+}
+
+template<typename k, typename v>
+bool Equals(const map<k, v> &a, const map<k, v> &b)
+{
+  // simple check for equivalent size first
+  if (a.size() != b.size()) {
+    BPLOG(ERROR) << "Equals(map): " << a.size() << " != " << b.size();
+    return false;
+  }
+
+  typename map<k,v>::const_iterator it, found;
+  for (it = a.begin(); it != a.end(); it++) {
+    // if b doesn't contain this entry, then
+    // just get out
+    found = b.find(it->first);
+    if (found == b.end()) {
+      BPLOG(ERROR) << "Equals(map): " << it->first << " not in map b!";
+      return false;
+    }
+
+    if (!Equals(it->second, found->second)) {
+      BPLOG(ERROR) << "Equals(map): it->second != found->second";
+      Display(it->second, found->second);
+      return false;
+    }
+  }
+  return true;
+}
+
+// For certain classes we know about, use their Equals method.
+// Note: a few variations of this are in other places:
+// processor/basic_source_line_resolver.cc
+
+template<typename AddressType, typename EntryType>
+inline bool Equals(const RangeMap<AddressType, EntryType> &a,
+            const RangeMap<AddressType, EntryType> &b)
+{
+  return a.Equals(b);
+}
+
+template<typename AddressType, typename EntryType>
+inline bool Equals(const typename RangeMap<AddressType, EntryType>::Range &a,
+                   const typename RangeMap<AddressType, EntryType>::Range &b)
+{
+  return a.Equals(b);
+}
+
+template<typename AddressType, typename EntryType>
+inline bool Equals(const AddressMap<AddressType, EntryType> &a,
+                   const AddressMap<AddressType, EntryType> &b)
+{
+  return a.Equals(b);
+}
+
+template<typename AddressType, typename EntryType>
+inline bool Equals(const ContainedRangeMap<AddressType, EntryType> &a,
+                   const ContainedRangeMap<AddressType, EntryType> &b)
+{
+  return a.Equals(b);
+}
+
+template<typename AddressType, typename EntryType>
+inline bool Equals(ContainedRangeMap<AddressType, EntryType> * const &a,
+                   ContainedRangeMap<AddressType, EntryType> * const &b)
+{
+  if (a && b)
+    return a->Equals(*b);
+
+  // if one is NULL, just compare pointer values
+  return a == b;
+}
+
+} // namespace google_breakpad
+
+#endif // GOOGLE_BREAKPAD_EQUALS_H_
Index: src/processor/contained_range_map-inl.h
===================================================================
--- src/processor/contained_range_map-inl.h	(revision 234)
+++ src/processor/contained_range_map-inl.h	(working copy)
@@ -40,6 +40,7 @@
 #include <cassert>
 
 #include "processor/contained_range_map.h"
+#include "processor/equals.h"
 #include "processor/logging.h"
 
 
@@ -185,7 +186,24 @@
   }
 }
 
+template<typename AddressType, typename EntryType>
+bool ContainedRangeMap<AddressType, EntryType>::Equals(
+  const ContainedRangeMap<AddressType, EntryType> &other) const
+{
+  bool equal;
+  if (map_ && other.map_) {
+    equal = google_breakpad::Equals(*map_, *other.map_);
+  }
+  else {
+    // otherwise if they're both NULL they're equal
+    equal = map_ == other.map_;
+  }
 
+  return equal &&
+    google_breakpad::Equals(base_, other.base_) &&
+    google_breakpad::Equals(entry_, other.entry_);
+}
+
 }  // namespace google_breakpad
 
 
Index: src/processor/disk_module_cache.cc
===================================================================
--- src/processor/disk_module_cache.cc	(revision 0)
+++ src/processor/disk_module_cache.cc	(revision 0)
@@ -0,0 +1,173 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// 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 <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include <fstream>
+#include <ios>
+
+#include "processor/disk_module_cache.h"
+#include "processor/logging.h"
+
+using std::string;
+using std::ostream;
+using std::ofstream;
+using std::istream;
+using std::ifstream;
+using std::ios;
+
+
+namespace google_breakpad {
+
+DiskModuleCache::DiskModuleCache(string cache_directory) :
+    cache_directory_(cache_directory)
+{
+  // ensure trailing slash in cache directory
+  if (cache_directory_[cache_directory_.length()] != '/')
+    cache_directory.append("/");
+}
+
+bool DiskModuleCache::GetModuleData(const string &symbol_file,
+                                    istream **data_stream)
+{
+  if (!data_stream)
+    return false;
+
+  string cache_file = MapToCacheEntry(symbol_file);
+  if (cache_file.empty())
+    return false;
+
+  BPLOG(INFO) << "Loading cached copy of symbol file " << symbol_file
+              << " from " << cache_file;
+
+  *data_stream = new ifstream(cache_file.c_str(),
+                              ios::in | ios::binary);
+  if (!**data_stream) {
+    delete *data_stream;
+    *data_stream = NULL;
+    return false;
+  }
+  return true;
+}
+
+bool DiskModuleCache::BeginSetModuleData(const string &symbol_file,
+                                         ostream **data_stream)
+{
+  if (!data_stream)
+    return false;
+
+  string cache_file = MapToCacheEntry(symbol_file);
+  if (!EnsurePathExists(cache_file.substr(0, cache_file.rfind('/'))))
+    return false;
+  
+  *data_stream = new ofstream(cache_file.c_str(),
+                              ios::out | ios::binary | ios::trunc);
+  if (!**data_stream) {
+    delete *data_stream;
+    *data_stream = NULL;
+    return false;
+  }
+  return true;
+}
+
+bool DiskModuleCache::EndSetModuleData(const string &symbol_file,
+                                       ostream **data_stream)
+{
+  ofstream *file_stream = dynamic_cast<ofstream*>(*data_stream);
+  if (!file_stream)
+    return false;
+
+  file_stream->close();
+  return true;
+}
+
+// We assume that symbol_file is in the Microsoft Symbol Server
+// format, /path/debug_file/IDENTIFIER/debug_file.sym.  We map
+// this to /cache/path/debug_file/IDENTIFIER/debug_file.symcache.
+// NOTE: this assumes unix style paths!
+string DiskModuleCache::MapToCacheEntry(const string &symbol_file)
+{
+  string::size_type pos = string::npos;
+
+  // we want the last three components in the path
+  for (int i=0; i<3; i++) {
+    pos = symbol_file.rfind('/', pos);
+    if (pos == string::npos)
+      return "";
+    // back up one character so we don't find this slash again
+    pos--;
+  }
+
+  string cache_file(cache_directory_ +
+                    symbol_file.substr(pos + 2));
+  pos = cache_file.length() - 4;
+  if (cache_file.compare(pos, 4, ".sym") == 0)
+    cache_file.replace(pos, 4, ".symcache");
+
+  return cache_file;
+}
+
+bool DiskModuleCache::EnsurePathExists(const string &path)
+{
+  if (IsDir(path))
+    return true;
+
+  if (mkdir(path.c_str(), 0755) == -1) {
+    // we handle ENOENT here
+    if (errno != ENOENT)
+      return false;
+
+    string::size_type pos = path.rfind('/');
+    if (pos == string::npos)
+      // not enough path left?
+      return false;
+
+    if (!EnsurePathExists(path.substr(0, pos)))
+      return false;
+
+    // all previous directories should exist at this point
+    return mkdir(path.c_str(), 0755) != -1;
+  }
+  else {
+    return true;
+  }
+}
+
+bool DiskModuleCache::IsDir(const string &path)
+{
+  struct stat st;
+  if (stat(path.c_str(), &st) != 0)
+    return false;
+
+  return (st.st_mode & S_IFDIR) == S_IFDIR;
+}
+
+}  // namespace google_breakpad
Index: src/processor/address_map.h
===================================================================
--- src/processor/address_map.h	(revision 234)
+++ src/processor/address_map.h	(working copy)
@@ -64,6 +64,8 @@
   // initially created.
   void Clear();
 
+  bool Equals(const AddressMap<AddressType, EntryType> &other) const;
+
  private:
   // Convenience types.
   typedef std::map<AddressType, EntryType> AddressToEntryMap;
@@ -72,6 +74,8 @@
 
   // Maps the address of each entry to an EntryType.
   AddressToEntryMap map_;
+
+  friend class ModuleSerializer;
 };
 
 }  // namespace google_breakpad
Index: src/google_breakpad/processor/basic_source_line_resolver.h
===================================================================
--- src/google_breakpad/processor/basic_source_line_resolver.h	(revision 234)
+++ src/google_breakpad/processor/basic_source_line_resolver.h	(working copy)
@@ -46,6 +46,7 @@
 #endif  // BSLR_NO_HASH_MAP
 
 #include "google_breakpad/processor/source_line_resolver_interface.h"
+#include "google_breakpad/processor/source_line_resolver_module_cache_interface.h"
 
 namespace google_breakpad {
 
@@ -56,9 +57,12 @@
 using __gnu_cxx::hash_map;
 #endif  // BSLR_NO_HASH_MAP
 
+class ModuleSerializer;
+
 class BasicSourceLineResolver : public SourceLineResolverInterface {
  public:
   BasicSourceLineResolver();
+  BasicSourceLineResolver(SourceLineResolverModuleCacheInterface *);
   virtual ~BasicSourceLineResolver();
 
   // SourceLineResolverInterface methods, see source_line_resolver_interface.h
@@ -73,6 +77,8 @@
 
   virtual StackFrameInfo* FillSourceLineInfo(StackFrame *frame) const;
 
+  static bool ModuleRoundTripTest(const string &map_file);
+
  private:
   template<class T> class MemAddrMap;
   struct Line;
@@ -97,10 +103,16 @@
   typedef hash_map<string, Module*, HashString> ModuleMap;
 #endif  // BSLR_NO_HASH_MAP
   ModuleMap *modules_;
+  SourceLineResolverModuleCacheInterface *module_cache_;
 
   // Disallow unwanted copy ctor and assignment operator
   BasicSourceLineResolver(const BasicSourceLineResolver&);
   void operator=(const BasicSourceLineResolver&);
+
+  friend bool Equals(const BasicSourceLineResolver::Function &a,
+                     const BasicSourceLineResolver::Function &b);
+
+  friend class ModuleSerializer;
 };
 
 }  // namespace google_breakpad
Index: src/google_breakpad/processor/source_line_resolver_module_cache_interface.h
===================================================================
--- src/google_breakpad/processor/source_line_resolver_module_cache_interface.h	(revision 0)
+++ src/google_breakpad/processor/source_line_resolver_module_cache_interface.h	(revision 0)
@@ -0,0 +1,71 @@
+// Copyright (c) 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// 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.
+
+// Abstract interface to store and retrieve cached data for module
+// symbols.
+
+#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_MODULE_CACHE_INTERFACE_H__
+#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_MODULE_CACHE_INTERFACE_H__
+
+#include <string>
+#include <istream>
+#include <ostream>
+
+namespace google_breakpad {
+
+using std::string;
+using std::istream;
+using std::ostream;
+
+class SourceLineResolverModuleCacheInterface {
+ public:
+  virtual ~SourceLineResolverModuleCacheInterface() {}
+
+  // Retrieve the symbol data associated with a module
+  virtual bool GetModuleData(const string &symbol_file,
+                             istream **data_stream) = 0;
+
+  // Get a stream to which the data associated with a module
+  // can be stored
+  virtual bool BeginSetModuleData(const string &symbol_file,
+                                  ostream **data_stream) = 0;
+  // Finish setting the data associated with this module,
+  // data should already have been written to the stream
+  virtual bool EndSetModuleData(const string &symbol_file,
+                                ostream **data_stream) = 0;
+
+ protected:
+  // SourceLineResolverModuleCacheInterface cannot be instantiated
+  // except by subclasses
+  SourceLineResolverModuleCacheInterface() {}
+};
+
+} // namespace google_breakpad
+
+#endif  // GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_MODULE_CACHE_INTERFACE_H__
Index: Makefile.am
===================================================================
--- Makefile.am	(revision 234)
+++ Makefile.am	(working copy)
@@ -62,6 +62,7 @@
 	src/google_breakpad/processor/minidump_processor.h \
 	src/google_breakpad/processor/process_state.h \
 	src/google_breakpad/processor/source_line_resolver_interface.h \
+	src/google_breakpad/processor/source_line_resolver_module_cache_interface.h \
 	src/google_breakpad/processor/stack_frame.h \
 	src/google_breakpad/processor/stack_frame_cpu.h \
 	src/google_breakpad/processor/stackwalker.h \
@@ -76,6 +77,8 @@
 	src/processor/call_stack.cc \
 	src/processor/contained_range_map-inl.h \
 	src/processor/contained_range_map.h \
+	src/processor/disk_module_cache.cc \
+	src/processor/disk_module_cache.h \
 	src/processor/linked_ptr.h \
 	src/processor/logging.h \
 	src/processor/logging.cc \
@@ -117,7 +120,8 @@
 	src/processor/minidump_processor_unittest \
 	src/processor/pathname_stripper_unittest \
 	src/processor/postfix_evaluator_unittest \
-	src/processor/range_map_unittest
+	src/processor/range_map_unittest \
+	src/processor/module_serialize_unittest
 
 if SELFTEST
 check_PROGRAMS += \
@@ -185,6 +189,15 @@
 	src/processor/logging.lo \
 	src/processor/pathname_stripper.lo
 
+src_processor_module_serialize_unittest_SOURCES = \
+	src/processor/module_serialize_unittest.cc
+src_processor_module_serialize_unittest_LDADD = \
+	src/processor/basic_code_modules.lo \
+	src/processor/basic_source_line_resolver.lo \
+	src/processor/call_stack.lo \
+	src/processor/logging.lo \
+	src/processor/pathname_stripper.lo
+
 src_processor_stackwalker_selftest_SOURCES = \
 	src/processor/stackwalker_selftest.cc
 src_processor_stackwalker_selftest_LDADD = \
@@ -218,6 +231,7 @@
 	src/processor/basic_code_modules.lo \
 	src/processor/basic_source_line_resolver.lo \
 	src/processor/call_stack.lo \
+	src/processor/disk_module_cache.lo \
 	src/processor/logging.lo \
 	src/processor/minidump.lo \
 	src/processor/minidump_processor.lo \
