Merge branch 'upstream/master' into HEAD

Change-Id: I9b2399c8303dd09947308a841b7e5efaaa930c85
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 41989ce..680b958 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,7 +28,7 @@
     "${CMAKE_EXE_LINKER_FLAGS} ${CLANG_CIPD_TOOLCHAIN_DIR}/../lib/libc++.a")
 endif(APPLE)
 
-if(UNIX)
+if(UNIX OR MINGW)
 find_package(PkgConfig)
 find_package(ZLIB)
 if(BLOATY_ENABLE_RE2)
@@ -105,7 +105,7 @@
 set(THREADS_PREFER_PTHREAD_FLAG TRUE)
 find_package(Threads REQUIRED)
 
-if(UNIX)
+if(UNIX OR MINGW)
   if(BLOATY_ENABLE_RE2)
     if(RE2_FOUND)
       include_directories(${RE2_INCLUDE_DIRS})
@@ -355,7 +355,7 @@
 )
 add_dependencies(libbloaty rustc-demangle-lib)
 
-if(UNIX)
+if(UNIX OR MINGW)
   set(LIBBLOATY_LIBS libbloaty)
   if(PROTOBUF_FOUND)
     list(APPEND LIBBLOATY_LIBS ${PROTOBUF_LIBRARIES})
@@ -390,7 +390,7 @@
   list(APPEND LIBBLOATY_LIBS zlibstatic)
 endif()
 
-if(UNIX)
+if(UNIX OR MINGW)
   if(BLOATY_ENABLE_RE2)
     if(RE2_FOUND)
       link_directories(${RE2_LIBRARY_DIRS})
diff --git a/src/bloaty.cc b/src/bloaty.cc
index f178b96..8bc4c08 100644
--- a/src/bloaty.cc
+++ b/src/bloaty.cc
@@ -18,6 +18,12 @@
 // It's very hard to figure out why. For the moment this seems to fix it,
 // but ideally we'd have a better solution here.
 typedef size_t z_size_t;
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <signal.h>
+#include <stdlib.h>
 #include <zlib.h>
 
 #include <atomic>
@@ -35,19 +41,12 @@
 #include <thread>
 #include <unordered_map>
 #include <vector>
-
-#include <assert.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <math.h>
-#include <signal.h>
-#include <stdlib.h>
-#if !defined(_MSC_VER)
+#if !defined(_WIN32)
 #include <sys/mman.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #else
-#include <Windows.h>
+#include <windows.h>
 #endif
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -55,16 +54,15 @@
 #include "absl/debugging/internal/demangle.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/numbers.h"
-#include "absl/strings/string_view.h"
 #include "absl/strings/str_join.h"
+#include "absl/strings/string_view.h"
 #include "absl/strings/substitute.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
-
 #include "bloaty.h"
 #include "bloaty.pb.h"
 #include "demangle.h"
 #include "rustc_demangle.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
 #include "re.h"
 
 using absl::string_view;
@@ -245,8 +243,7 @@
       return std::string(symbol);
     }
   } else if (source == DataSource::kFullSymbols) {
-    char* demangled =
-        __cxa_demangle(demangle_from.data(), NULL, NULL, NULL);
+    char* demangled = __cxa_demangle(demangle_from.data(), NULL, NULL, NULL);
     if (demangled) {
       std::string ret(demangled);
       free(demangled);
@@ -274,10 +271,10 @@
   }
 }
 
-
 // NameMunger //////////////////////////////////////////////////////////////////
 
-void NameMunger::AddRegex(const std::string& regex, const std::string& replacement) {
+void NameMunger::AddRegex(const std::string& regex,
+                          const std::string& replacement) {
   auto reg = absl::make_unique<ReImpl>(regex);
   regexes_.push_back(std::make_pair(std::move(reg), replacement));
 }
@@ -295,7 +292,6 @@
   return name_str;
 }
 
-
 // Rollup //////////////////////////////////////////////////////////////////////
 
 // A Rollup is a hierarchical tally of sizes.  Its graphical representation is
@@ -331,8 +327,8 @@
   Rollup(Rollup&& other) = default;
   Rollup& operator=(Rollup&& other) = default;
 
-  void AddSizes(const std::vector<std::string>& names,
-                uint64_t size, bool is_vmsize) {
+  void AddSizes(const std::vector<std::string>& names, uint64_t size,
+                bool is_vmsize) {
     // We start at 1 to exclude the base map (see base_map_).
     AddInternal(names, 1, size, is_vmsize);
   }
@@ -346,33 +342,17 @@
   void CreateDiffModeRollupOutput(Rollup* base, const Options& options,
                                   RollupOutput* output) const {
     RollupRow* row = &output->toplevel_row_;
-    row->vmsize = vm_total_;
-    row->filesize = file_total_;
-    row->filtered_vmsize = filtered_vm_total_;
-    row->filtered_filesize = filtered_file_total_;
+    row->size.vm = vm_total_;
+    row->size.file = file_total_;
+    row->filtered_size.vm = filtered_vm_total_;
+    row->filtered_size.file = filtered_file_total_;
     row->vmpercent = 100;
     row->filepercent = 100;
     output->diff_mode_ = true;
     CreateRows(row, base, options, true);
   }
 
-  void SetFilterRegex(const ReImpl* regex) {
-    filter_regex_ = regex;
-  }
-
-  // Subtract the values in "other" from this.
-  void Subtract(const Rollup& other) {
-    vm_total_ -= other.vm_total_;
-    file_total_ -= other.file_total_;
-
-    for (const auto& other_child : other.children_) {
-      auto& child = children_[other_child.first];
-      if (child.get() == NULL) {
-        child.reset(new Rollup());
-      }
-      child->Subtract(*other_child.second);
-    }
-  }
+  void SetFilterRegex(const ReImpl* regex) { filter_regex_ = regex; }
 
   // Add the values in "other" from this.
   void Add(const Rollup& other) {
@@ -388,6 +368,17 @@
     }
   }
 
+  // Create entries for all children which exist in "other" but not in this.
+  void AddEntriesFrom(const Rollup& other) {
+    for (const auto& other_child : other.children_) {
+      auto& child = children_[other_child.first];
+      if (child.get() == NULL) {
+        child.reset(new Rollup());
+      }
+      child->AddEntriesFrom(*other_child.second);
+    }
+  }
+
   int64_t file_total() const { return file_total_; }
   int64_t filtered_file_total() const { return filtered_file_total_; }
 
@@ -485,11 +476,33 @@
   }
 
   for (const auto& value : children_) {
-    if (value.second->vm_total_ != 0 || value.second->file_total_ != 0) {
+    int64_t vm_total = value.second->vm_total_;
+    int64_t file_total = value.second->file_total_;
+    Rollup* base_child = nullptr;
+
+    if (base) {
+      // Reassign sizes to base during a diff to compare to target sizes.
+      auto it = base->children_.find(value.first);
+      if (it != base->children_.end()) {
+        base_child = it->second.get();
+        vm_total -= base_child->vm_total_;
+        file_total -= base_child->file_total_;
+      }
+    }
+
+    if (vm_total != 0 || file_total != 0) {
       row->sorted_children.emplace_back(value.first);
       RollupRow& child_row = row->sorted_children.back();
-      child_row.vmsize = value.second->vm_total_;
-      child_row.filesize = value.second->file_total_;
+      child_row.size.vm = vm_total;
+      child_row.size.file = file_total;
+
+      // Preserve the old size for this label in the RollupRow output.
+      // If there is a diff base, the old sizes come from the size of the label
+      // in that base.  Otherwise, the old size stays 0.
+      if (base_child) {
+        child_row.old_size.vm = base_child->vm_total_;
+        child_row.old_size.file = base_child->file_total_;
+      }
     }
   }
 
@@ -524,14 +537,14 @@
   for (auto& child : child_rows) {
     switch (options.sort_by()) {
       case Options::SORTBY_VMSIZE:
-        child.sortkey = std::abs(child.vmsize);
+        child.sortkey = std::abs(child.size.vm);
         break;
       case Options::SORTBY_FILESIZE:
-        child.sortkey = std::abs(child.filesize);
+        child.sortkey = std::abs(child.size.file);
         break;
       case Options::SORTBY_BOTH:
         child.sortkey =
-            std::max(std::abs(child.vmsize), std::abs(child.filesize));
+            std::max(std::abs(child.size.vm), std::abs(child.size.file));
         break;
       default:
         BLOATY_UNREACHABLE();
@@ -550,8 +563,8 @@
   // out to "others_row".
   size_t i = child_rows.size() - 1;
   while (i >= options.max_rows_per_level()) {
-    CheckedAdd(&others_row.vmsize, child_rows[i].vmsize);
-    CheckedAdd(&others_row.filesize, child_rows[i].filesize);
+    CheckedAdd(&others_row.size.vm, child_rows[i].size.vm);
+    CheckedAdd(&others_row.size.file, child_rows[i].size.file);
     if (base) {
       auto it = base->children_.find(child_rows[i].name);
       if (it != base->children_.end()) {
@@ -564,26 +577,26 @@
     i--;
   }
 
-  if (std::abs(others_row.vmsize) > 0 || std::abs(others_row.filesize) > 0) {
+  if (std::abs(others_row.size.vm) > 0 || std::abs(others_row.size.file) > 0) {
     child_rows.push_back(others_row);
-    CheckedAdd(&others_rollup.vm_total_, others_row.vmsize);
-    CheckedAdd(&others_rollup.file_total_, others_row.filesize);
+    CheckedAdd(&others_rollup.vm_total_, others_row.size.vm);
+    CheckedAdd(&others_rollup.file_total_, others_row.size.file);
   }
 
   // Now sort by actual value (positive or negative).
   for (auto& child : child_rows) {
     switch (options.sort_by()) {
       case Options::SORTBY_VMSIZE:
-        child.sortkey = child.vmsize;
+        child.sortkey = child.size.vm;
         break;
       case Options::SORTBY_FILESIZE:
-        child.sortkey = child.filesize;
+        child.sortkey = child.size.file;
         break;
       case Options::SORTBY_BOTH:
-        if (std::abs(child.vmsize) > std::abs(child.filesize)) {
-          child.sortkey = child.vmsize;
+        if (std::abs(child.size.vm) > std::abs(child.size.file)) {
+          child.sortkey = child.size.vm;
         } else {
-          child.sortkey = child.filesize;
+          child.sortkey = child.size.file;
         }
         break;
       default:
@@ -596,8 +609,8 @@
   // For a non-diff, the percentage is compared to the total size of the parent.
   if (!base) {
     for (auto& child_row : child_rows) {
-      child_row.vmpercent = Percent(child_row.vmsize, row->vmsize);
-      child_row.filepercent = Percent(child_row.filesize, row->filesize);
+      child_row.vmpercent = Percent(child_row.size.vm, row->size.vm);
+      child_row.filepercent = Percent(child_row.size.file, row->size.file);
     }
   }
 
@@ -633,7 +646,6 @@
   }
 }
 
-
 // RollupOutput ////////////////////////////////////////////////////////////////
 
 // RollupOutput represents rollup data after we have applied output massaging
@@ -672,14 +684,14 @@
   return ret;
 }
 
-std::string DoubleStringPrintf(const char *fmt, double d) {
+std::string DoubleStringPrintf(const char* fmt, double d) {
   char buf[1024];
   snprintf(buf, sizeof(buf), fmt, d);
   return std::string(buf);
 }
 
 std::string SiPrint(int64_t size, bool force_sign) {
-  const char *prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"};
+  const char* prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"};
   size_t num_prefixes = 5;
   size_t n = 0;
   double size_d = size;
@@ -742,14 +754,39 @@
 
 }  // namespace
 
+void RollupOutput::Print(const OutputOptions& options, std::ostream* out) {
+  if (!source_names_.empty()) {
+    switch (options.output_format) {
+      case bloaty::OutputFormat::kPrettyPrint:
+        PrettyPrint(options, out);
+        break;
+      case bloaty::OutputFormat::kCSV:
+        PrintToCSV(out, /*tabs=*/false, options.showAllSizesCSV);
+        break;
+      case bloaty::OutputFormat::kTSV:
+        PrintToCSV(out, /*tabs=*/true, options.showAllSizesCSV);
+        break;
+      case bloaty::OutputFormat::kProtobuf:
+          PrintToProtobuf(out);
+          break;
+      default:
+        BLOATY_UNREACHABLE();
+    }
+  }
+
+  if (!disassembly_.empty()) {
+    *out << disassembly_;
+  }
+}
+
 void RollupOutput::PrettyPrintRow(const RollupRow& row, size_t indent,
                                   const OutputOptions& options,
                                   std::ostream* out) const {
   if (&row != &toplevel_row_) {
     // Avoid printing this row if it is only zero.
     // This can happen when using --domain if the row is zero for this domain.
-    if ((!ShowFile(options) && row.vmsize == 0) ||
-        (!ShowVM(options) && row.filesize == 0)) {
+    if ((!ShowFile(options) && row.size.vm == 0) ||
+        (!ShowVM(options) && row.size.file == 0)) {
       return;
     }
   }
@@ -758,12 +795,12 @@
 
   if (ShowFile(options)) {
     *out << PercentString(row.filepercent, diff_mode_) << " "
-         << SiPrint(row.filesize, diff_mode_) << " ";
+         << SiPrint(row.size.file, diff_mode_) << " ";
   }
 
   if (ShowVM(options)) {
     *out << PercentString(row.vmpercent, diff_mode_) << " "
-         << SiPrint(row.vmsize, diff_mode_) << " ";
+         << SiPrint(row.size.vm, diff_mode_) << " ";
   }
 
   *out << "   " << row.name << "\n";
@@ -787,7 +824,7 @@
   // Rows are printed before their sub-rows.
   PrettyPrintRow(row, indent, options, out);
 
-  if (!row.vmsize && !row.filesize) {
+  if (!row.size.vm && !row.size.file) {
     return;
   }
 
@@ -833,11 +870,12 @@
 
   uint64_t file_filtered = 0;
   uint64_t vm_filtered = 0;
+  uint64_t filtered = 0;
   if (ShowFile(options)) {
-    file_filtered = toplevel_row_.filtered_filesize;
+    filtered += toplevel_row_.filtered_size.file;
   }
   if (ShowVM(options)) {
-    vm_filtered = toplevel_row_.filtered_vmsize;
+    filtered += toplevel_row_.filtered_size.vm;
   }
 
   if (vm_filtered == 0 && file_filtered == 0) {
@@ -855,19 +893,28 @@
     *out << SiPrint(vm_filtered, /*force_sign=*/false);
   }
 
-   *out << " of entries\n";
+  *out << " of entries\n";
 }
 
 void RollupOutput::PrintRowToCSV(const RollupRow& row,
                                  std::vector<std::string> parent_labels,
-                                 std::ostream* out, bool tabs) const {
+                                 std::ostream* out, bool tabs, bool csvDiff) const {
   while (parent_labels.size() < source_names_.size()) {
     // If this label had no data at this level, append an empty string.
     parent_labels.push_back("");
   }
 
-  parent_labels.push_back(std::to_string(row.vmsize));
-  parent_labels.push_back(std::to_string(row.filesize));
+  parent_labels.push_back(std::to_string(row.size.vm));
+  parent_labels.push_back(std::to_string(row.size.file));
+
+  // If in diff where both old size are 0, get new size by adding diff size to
+  // old size.
+  if (csvDiff) {
+    parent_labels.push_back(std::to_string(row.old_size.vm));
+  	parent_labels.push_back(std::to_string(row.old_size.file));
+    parent_labels.push_back(std::to_string(row.old_size.vm + (row.size.vm)));
+    parent_labels.push_back(
+        std::to_string(row.old_size.file + (row.size.file)));}
 
   std::string sep = tabs ? "\t" : ",";
   *out << absl::StrJoin(parent_labels, sep) << "\n";
@@ -875,7 +922,7 @@
 
 void RollupOutput::PrintTreeToCSV(const RollupRow& row,
                                   std::vector<std::string> parent_labels,
-                                  std::ostream* out, bool tabs) const {
+                                  std::ostream* out, bool tabs, bool csvDiff) const {
   if (tabs) {
     parent_labels.push_back(row.name);
   } else {
@@ -884,21 +931,28 @@
 
   if (row.sorted_children.size() > 0) {
     for (const auto& child_row : row.sorted_children) {
-      PrintTreeToCSV(child_row, parent_labels, out, tabs);
+      PrintTreeToCSV(child_row, parent_labels, out, tabs, csvDiff);
     }
   } else {
-    PrintRowToCSV(row, parent_labels, out, tabs);
+    PrintRowToCSV(row, parent_labels, out, tabs, csvDiff);
   }
 }
 
-void RollupOutput::PrintToCSV(std::ostream* out, bool tabs) const {
+void RollupOutput::PrintToCSV(std::ostream* out, bool tabs,
+                              bool csvDiff) const {
   std::vector<std::string> names(source_names_);
   names.push_back("vmsize");
   names.push_back("filesize");
+  if (csvDiff) {
+    names.push_back("original_vmsize");
+    names.push_back("original_filesize");
+    names.push_back("current_vmsize");
+    names.push_back("current_filesize");
+  }
   std::string sep = tabs ? "\t" : ",";
   *out << absl::StrJoin(names, sep) << "\n";
   for (const auto& child_row : toplevel_row_.sorted_children) {
-    PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs);
+    PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs, csvDiff);
   }
 }
 
@@ -907,18 +961,23 @@
 
 constexpr uint64_t RangeSink::kUnknownSize;
 
-
 // MmapInputFile ///////////////////////////////////////////////////////////////
 
-#if !defined(_MSC_VER)
+#if !defined(_WIN32)
 class MmapInputFile : public InputFile {
  public:
-  MmapInputFile(const std::string& filename);
+  MmapInputFile(string_view filename, string_view data);
   MmapInputFile(const MmapInputFile&) = delete;
   MmapInputFile& operator=(const MmapInputFile&) = delete;
   ~MmapInputFile() override;
-};
 
+  bool TryOpen(absl::string_view filename,
+               std::unique_ptr<InputFile>& file) override {
+    return DoTryOpen(filename, file);
+  }
+  static bool DoTryOpen(absl::string_view filename,
+                        std::unique_ptr<InputFile>& file);
+};
 
 class FileDescriptor {
  public:
@@ -936,28 +995,41 @@
   int fd_;
 };
 
-MmapInputFile::MmapInputFile(const std::string& filename)
-    : InputFile(filename) {
-  FileDescriptor fd(open(filename.c_str(), O_RDONLY));
+bool MmapInputFile::DoTryOpen(absl::string_view filename,
+                              std::unique_ptr<InputFile>& file) {
+  std::string str(filename);
+  FileDescriptor fd(open(str.c_str(), O_RDONLY));
   struct stat buf;
-  const char *map;
+  const char* map;
 
   if (fd.fd() < 0) {
-    THROWF("couldn't open file '$0': $1", filename, strerror(errno));
+    std::cerr << absl::Substitute("couldn't open file '$0': $1\n", filename,
+                                  strerror(errno));
+    return false;
   }
 
   if (fstat(fd.fd(), &buf) < 0) {
-    THROWF("couldn't stat file '$0': $1", filename, strerror(errno));
+    std::cerr << absl::Substitute("couldn't stat file '$0': $1\n", filename,
+                                  strerror(errno));
+    return false;
   }
 
   map = static_cast<char*>(
       mmap(nullptr, buf.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0));
 
   if (map == MAP_FAILED) {
-    THROWF("couldn't mmap file '$0': $1", filename, strerror(errno));
+    std::cerr << absl::Substitute("couldn't mmap file '$0': $1", filename,
+                                  strerror(errno));
+    return false;
   }
 
-  data_ = string_view(map, buf.st_size);
+  file.reset(new MmapInputFile(filename, string_view(map, buf.st_size)));
+  return true;
+}
+
+MmapInputFile::MmapInputFile(string_view filename, string_view data)
+    : InputFile(filename) {
+  data_ = data;
 }
 
 MmapInputFile::~MmapInputFile() {
@@ -969,19 +1041,30 @@
 
 std::unique_ptr<InputFile> MmapInputFileFactory::OpenFile(
     const std::string& filename) const {
-  return absl::make_unique<MmapInputFile>(filename);
+  std::unique_ptr<InputFile> ret;
+  if (!MmapInputFile::DoTryOpen(filename, ret)) {
+    THROW("Failed to open file.");
+  }
+  return ret;
 }
 
-#else // !_MSC_VER
+#else  // !_WIN32
 
 // MmapInputFile ///////////////////////////////////////////////////////////////
 
 class Win32MMapInputFile : public InputFile {
  public:
-  Win32MMapInputFile(const std::string& filename);
+  Win32MMapInputFile(string_view filename, string_view data);
   Win32MMapInputFile(const Win32MMapInputFile&) = delete;
   Win32MMapInputFile& operator=(const Win32MMapInputFile&) = delete;
   ~Win32MMapInputFile() override;
+
+  bool TryOpen(absl::string_view filename,
+               std::unique_ptr<InputFile>& file) override {
+    return DoTryOpen(filename, file);
+  }
+  static bool DoTryOpen(absl::string_view filename,
+                        std::unique_ptr<InputFile>& file);
 };
 
 class Win32Handle {
@@ -1001,34 +1084,49 @@
   HANDLE h_;
 };
 
-Win32MMapInputFile::Win32MMapInputFile(const std::string& filename)
+Win32MMapInputFile::Win32MMapInputFile(string_view filename, string_view data)
     : InputFile(filename) {
-  Win32Handle fd(::CreateFileA(filename.c_str(), FILE_GENERIC_READ,
-                               FILE_SHARE_READ, NULL, OPEN_EXISTING,
-                               FILE_ATTRIBUTE_NORMAL, NULL));
+  data_ = data;
+}
+
+bool Win32MMapInputFile::DoTryOpen(absl::string_view filename,
+                                   std::unique_ptr<InputFile>& file) {
+  std::string str(filename);
+  Win32Handle fd(::CreateFileA(str.c_str(), FILE_GENERIC_READ, FILE_SHARE_READ,
+                               NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+                               NULL));
   LARGE_INTEGER li = {};
   const char* map;
 
   if (fd.h() == INVALID_HANDLE_VALUE) {
-    THROWF("couldn't open file '$0': $1", filename, ::GetLastError());
+    std::cerr << absl::Substitute("couldn't open file '$0': $1", filename,
+                                  ::GetLastError());
+    return false;
   }
 
   if (!::GetFileSizeEx(fd.h(), &li)) {
-    THROWF("couldn't stat file '$0': $1", filename, ::GetLastError());
+    std::cerr << absl::Substitute("couldn't stat file '$0': $1", filename,
+                                  ::GetLastError());
+    return false;
   }
 
   Win32Handle mapfd(
       ::CreateFileMappingA(fd.h(), NULL, PAGE_READONLY, 0, 0, nullptr));
   if (!mapfd.h()) {
-    THROWF("couldn't create file mapping '$0': $1", filename, ::GetLastError());
+    std::cerr << absl::Substitute("couldn't create file mapping '$0': $1",
+                                  filename, ::GetLastError());
+    return false;
   }
 
   map = static_cast<char*>(::MapViewOfFile(mapfd.h(), FILE_MAP_READ, 0, 0, 0));
   if (!map) {
-    THROWF("couldn't MapViewOfFile file '$0': $1", filename, ::GetLastError());
+    std::cerr << absl::Substitute("couldn't MapViewOfFile file '$0': $1",
+                                  filename, ::GetLastError());
+    return false;
   }
 
-  data_ = string_view(map, li.QuadPart);
+  file.reset(new Win32MMapInputFile(filename, string_view(map, li.QuadPart)));
+  return true;
 }
 
 Win32MMapInputFile::~Win32MMapInputFile() {
@@ -1040,18 +1138,25 @@
 
 std::unique_ptr<InputFile> MmapInputFileFactory::OpenFile(
     const std::string& filename) const {
-  return absl::make_unique<Win32MMapInputFile>(filename);
+  std::unique_ptr<InputFile> ret;
+  if (!Win32MMapInputFile::DoTryOpen(filename, ret)) {
+    THROW("Failed to open file.");
+  }
+  return ret;
 }
 
 #endif
 
 // RangeSink ///////////////////////////////////////////////////////////////////
 
-RangeSink::RangeSink(const InputFile *file, const Options &options,
-                     DataSource data_source, const DualMap *translator,
-                     google::protobuf::Arena *arena)
-    : file_(file), options_(options), data_source_(data_source),
-      translator_(translator), arena_(arena) {}
+RangeSink::RangeSink(const InputFile* file, const Options& options,
+                     DataSource data_source, const DualMap* translator,
+                     google::protobuf::Arena* arena)
+    : file_(file),
+      options_(options),
+      data_source_(data_source),
+      translator_(translator),
+      arena_(arena) {}
 
 RangeSink::~RangeSink() {}
 
@@ -1347,21 +1452,23 @@
   }
   uint64_t mb = 1 << 20;
   // Limit for uncompressed size is 30x the compressed size + 128MB.
-  if (uncompressed_size > static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) {
+  if (uncompressed_size >
+      static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) {
     fprintf(stderr,
             "warning: ignoring compressed debug data, implausible uncompressed "
             "size (compressed: %zu, uncompressed: %" PRIu64 ")\n",
             data.size(), uncompressed_size);
     return absl::string_view();
   }
-  unsigned char *dbuf =
+  unsigned char* dbuf =
       arena_->google::protobuf::Arena::CreateArray<unsigned char>(
           arena_, uncompressed_size);
   uLongf zliblen = uncompressed_size;
-  if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) != Z_OK) {
+  if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) !=
+      Z_OK) {
     THROW("Error decompressing debug info");
   }
-  string_view sv(reinterpret_cast<char *>(dbuf), zliblen);
+  string_view sv(reinterpret_cast<char*>(dbuf), zliblen);
   return sv;
 }
 
@@ -1404,7 +1511,6 @@
   const int max_;
 };
 
-
 // Bloaty //////////////////////////////////////////////////////////////////////
 
 // Represents a program execution and associated state.
@@ -1506,8 +1612,9 @@
   std::unique_ptr<google::protobuf::Arena> arena_;
 };
 
-Bloaty::Bloaty(const InputFileFactory &factory, const Options &options)
-    : file_factory_(factory), options_(options),
+Bloaty::Bloaty(const InputFileFactory& factory, const Options& options)
+    : file_factory_(factory),
+      options_(options),
       arena_(std::make_unique<google::protobuf::Arena>()) {
   AddBuiltInSources(data_sources, options);
 }
@@ -1590,8 +1697,10 @@
   auto iter = all_known_sources_.find(source.base_data_source());
 
   if (iter == all_known_sources_.end()) {
-    THROWF("custom data source '$0': no such base source '$1'.\nTry --list-sources to see valid sources.", source.name(),
-           source.base_data_source());
+    THROWF(
+        "custom data source '$0': no such base source '$1'.\nTry "
+        "--list-sources to see valid sources.",
+        source.name(), source.base_data_source());
   } else if (!iter->second->munger->IsEmpty()) {
     THROWF("custom data source '$0' tries to depend on custom data source '$1'",
            source.name(), source.base_data_source());
@@ -1609,7 +1718,8 @@
   source_names_.emplace_back(name);
   auto it = all_known_sources_.find(name);
   if (it == all_known_sources_.end()) {
-    THROWF("no such data source: $0.\nTry --list-sources to see valid sources.", name);
+    THROWF("no such data source: $0.\nTry --list-sources to see valid sources.",
+           name);
   }
 
   sources_.emplace_back(it->second.get());
@@ -1676,7 +1786,8 @@
     return ret;
   }
 
-  void PrintMapRow(string_view str, uint64_t start, uint64_t end, int hex_digits) {
+  void PrintMapRow(string_view str, uint64_t start, uint64_t end,
+                   int hex_digits) {
     printf("%.*" PRIx64 "-%.*" PRIx64 "\t %s\t\t%.*s\n", hex_digits, start,
            hex_digits, end, LeftPad(std::to_string(end - start), 10).c_str(),
            (int)str.size(), str.data());
@@ -1704,7 +1815,7 @@
   std::vector<std::unique_ptr<DualMap>> maps_;
 };
 
-void Bloaty::ScanAndRollupFile(const std::string &filename, Rollup* rollup,
+void Bloaty::ScanAndRollupFile(const std::string& filename, Rollup* rollup,
                                std::vector<std::string>* out_build_ids) const {
   auto file = GetObjectFile(filename);
 
@@ -1721,9 +1832,9 @@
   sink_ptrs.push_back(sinks.back().get());
 
   for (auto source : sources_) {
-    sinks.push_back(absl::make_unique<RangeSink>(&file->file_data(), options_,
-                                                 source->effective_source,
-                                                 maps.base_map(), arena_.get()));
+    sinks.push_back(absl::make_unique<RangeSink>(
+        &file->file_data(), options_, source->effective_source, maps.base_map(),
+        arena_.get()));
     sinks.back()->AddOutput(maps.AppendMap(), source->munger.get());
     // We handle the kInputFiles data source internally, without handing it off
     // to the file format implementation.  This seems slightly simpler, since
@@ -1746,8 +1857,8 @@
     }
   }
 
-  int64_t filesize_before = rollup->file_total() +
-      rollup->filtered_file_total();
+  int64_t filesize_before =
+      rollup->file_total() + rollup->filtered_file_total();
   file->ProcessFile(sink_ptrs);
 
   // kInputFile source: Copy the base map to the filename sink(s).
@@ -1767,8 +1878,8 @@
   maps.ComputeRollup(rollup);
 
   // The ObjectFile implementation must guarantee this.
-  int64_t filesize = rollup->file_total() +
-      rollup->filtered_file_total() - filesize_before;
+  int64_t filesize =
+      rollup->file_total() + rollup->filtered_file_total() - filesize_before;
   (void)filesize;
   assert(filesize == file->file_data().data().size());
 
@@ -1785,10 +1896,9 @@
   }
 }
 
-void Bloaty::ScanAndRollupFiles(
-    const std::vector<std::string>& filenames,
-    std::vector<std::string>* build_ids,
-    Rollup * rollup) const {
+void Bloaty::ScanAndRollupFiles(const std::vector<std::string>& filenames,
+                                std::vector<std::string>* build_ids,
+                                Rollup* rollup) const {
   int num_cpus = std::thread::hardware_concurrency();
   int num_threads = std::min(num_cpus, static_cast<int>(filenames.size()));
 
@@ -1809,16 +1919,18 @@
   for (int i = 0; i < num_threads; i++) {
     thread_data[i].rollup.SetFilterRegex(regex.get());
 
-    threads[i] = std::thread([this, &index, &filenames](PerThreadData* data) {
-      try {
-        int j;
-        while (index.TryGetNext(&j)) {
-          ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids);
-        }
-      } catch (const bloaty::Error& e) {
-        index.Abort(e.what());
-      }
-    }, &thread_data[i]);
+    threads[i] = std::thread(
+        [this, &index, &filenames](PerThreadData* data) {
+          try {
+            int j;
+            while (index.TryGetNext(&j)) {
+              ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids);
+            }
+          } catch (const bloaty::Error& e) {
+            index.Abort(e.what());
+          }
+        },
+        &thread_data[i]);
   }
 
   for (int i = 0; i < num_threads; i++) {
@@ -1830,8 +1942,7 @@
       rollup->Add(data->rollup);
     }
 
-    build_ids->insert(build_ids->end(),
-                      data->build_ids.begin(),
+    build_ids->insert(build_ids->end(), data->build_ids.begin(),
                       data->build_ids.end());
   }
 
@@ -1865,7 +1976,7 @@
       base_filenames.push_back(file_info.filename_);
     }
     ScanAndRollupFiles(base_filenames, &build_ids, &base);
-    rollup.Subtract(base);
+    rollup.AddEntriesFrom(base);
     rollup.CreateDiffModeRollupOutput(&base, options, output);
   } else {
     rollup.CreateRollupOutput(options, output);
@@ -1881,8 +1992,7 @@
     std::string unused_debug;
     for (const auto& pair : debug_files_) {
       unused_debug += absl::Substitute(
-          "$0   $1\n",
-          absl::BytesToHexString(pair.first).c_str(),
+          "$0   $1\n", absl::BytesToHexString(pair.first).c_str(),
           pair.second.c_str());
     }
 
@@ -1896,9 +2006,8 @@
           "$0   $1\n", absl::BytesToHexString(file_info.build_id_).c_str(),
           file_info.filename_.c_str());
     }
-    THROWF(
-        "Debug file(s) did not match any input file:\n$0\nInput Files:\n$1",
-        unused_debug.c_str(), input_files.c_str());
+    THROWF("Debug file(s) did not match any input file:\n$0\nInput Files:\n$1",
+           unused_debug.c_str(), input_files.c_str());
   }
 }
 
@@ -1992,9 +2101,7 @@
     return ret;
   }
 
-  void ConsumeAndSaveArg() {
-    (*out_argv_)[(*out_argc_)++] = argv_[index_++];
-  }
+  void ConsumeAndSaveArg() { (*out_argv_)[(*out_argc_)++] = argv_[index_++]; }
 
   // Singular flag like --csv or -v.
   bool TryParseFlag(string_view flag) {
@@ -2193,6 +2300,7 @@
       }
     } else {
       if (saw_separator) {
+        output_options->showAllSizesCSV = true;
         options->add_base_filename(std::string(args.ConsumeArg()));
       } else {
         options->add_filename(std::string(args.ConsumeArg()));
diff --git a/src/bloaty.h b/src/bloaty.h
index d3e7949..482c9e6 100644
--- a/src/bloaty.h
+++ b/src/bloaty.h
@@ -71,9 +71,11 @@
 
 class InputFile {
  public:
-  InputFile(const std::string& filename) : filename_(filename) {}
+  InputFile(absl::string_view filename) : filename_(filename) {}
   InputFile(const InputFile&) = delete;
   InputFile& operator=(const InputFile&) = delete;
+  virtual bool TryOpen(absl::string_view filename,
+                       std::unique_ptr<InputFile>& file) = 0;
   virtual ~InputFile() {}
 
   const std::string& filename() const { return filename_; }
@@ -303,7 +305,11 @@
 // Provided by dwarf.cc.  To use these, a module should fill in a dwarf::File
 // and then call these functions.
 void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& map,
-                           RangeSink* sink);
+                           const dwarf::CU* skeleton, RangeSink* sink);
+inline void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& map,
+                                  RangeSink* sink) {
+  return ReadDWARFCompileUnits(file, map, nullptr, sink);
+}
 void ReadDWARFInlines(const dwarf::File& file, RangeSink* sink,
                       bool include_line);
 void ReadEhFrame(absl::string_view contents, RangeSink* sink);
@@ -356,18 +362,26 @@
 
 class Rollup;
 
+struct DomainSizes {
+  int64_t vm;
+  int64_t file;
+};
+
 struct RollupRow {
   RollupRow(const std::string& name_) : name(name_) {}
 
   std::string name;
-  int64_t vmsize = 0;
-  int64_t filesize = 0;
-  int64_t filtered_vmsize = 0;
-  int64_t filtered_filesize = 0;
+  DomainSizes size = {0, 0};
+  DomainSizes filtered_size = {0, 0};
+
   int64_t other_count = 0;
   int64_t sortkey;
   double vmpercent;
   double filepercent;
+
+  // The size of the base in a diff mode. Otherwise stay 0.
+  DomainSizes old_size = {0, 0};
+  
   std::vector<RollupRow> sorted_children;
 
   static bool Compare(const RollupRow& a, const RollupRow& b) {
@@ -397,6 +411,7 @@
   OutputFormat output_format = OutputFormat::kPrettyPrint;
   size_t max_label_len = 80;
   ShowDomain show = ShowDomain::kShowBoth;
+  bool showAllSizesCSV = false;
 };
 
 struct RollupOutput {
@@ -411,29 +426,7 @@
 
   const std::vector<std::string>& source_names() const { return source_names_; }
 
-  void Print(const OutputOptions& options, std::ostream* out) {
-    if (!source_names_.empty()) {
-      switch (options.output_format) {
-        case bloaty::OutputFormat::kPrettyPrint:
-          PrettyPrint(options, out);
-          break;
-        case bloaty::OutputFormat::kCSV:
-          PrintToCSV(out, /*tabs=*/false);
-          break;
-        case bloaty::OutputFormat::kTSV:
-          PrintToCSV(out, /*tabs=*/true);
-          break;
-        case bloaty::OutputFormat::kProtobuf:
-          PrintToProtobuf(out);
-          break;
-        default:
-          BLOATY_UNREACHABLE();
-      }
-    }
-    if (!disassembly_.empty()) {
-      *out << disassembly_;
-    }
-  }
+  void Print(const OutputOptions& options, std::ostream* out);
 
   void SetDisassembly(absl::string_view disassembly) {
     disassembly_ = std::string(disassembly);
@@ -457,7 +450,7 @@
 
   static bool IsSame(const std::string& a, const std::string& b);
   void PrettyPrint(const OutputOptions& options, std::ostream* out) const;
-  void PrintToCSV(std::ostream* out, bool tabs) const;
+  void PrintToCSV(std::ostream* out, bool tabs, bool csvDiff) const;
   void PrintToProtobuf(std::ostream* out) const;
   void PrettyPrintRow(const RollupRow& row, size_t indent,
                       const OutputOptions& options, std::ostream* out) const;
@@ -465,10 +458,10 @@
                        const OutputOptions& options, std::ostream* out) const;
   void PrintRowToCSV(const RollupRow& row,
                      std::vector<std::string> parent_labels,
-                     std::ostream* out, bool tabs) const;
+                     std::ostream* out, bool tabs, bool csvDiff) const;
   void PrintTreeToCSV(const RollupRow& row,
                       std::vector<std::string> parent_labels,
-                      std::ostream* out, bool tabs) const;
+                      std::ostream* out, bool tabs, bool csvDiff) const;
 };
 
 // Shim for `std::filesystem::path(filename).stem()`.
diff --git a/src/dwarf.cc b/src/dwarf.cc
index d062c55..5099a31 100644
--- a/src/dwarf.cc
+++ b/src/dwarf.cc
@@ -573,6 +573,11 @@
   sink->AddFileRange("dwarf_stmtlistrange", cu.unit_name(), data);
 }
 
+struct DwoFilePointer {
+  std::string comp_dir;
+  std::string dwo_name;
+};
+
 // The DWARF debug info can help us get compileunits info.  DIEs for compilation
 // units, functions, and global variables often have attributes that will
 // resolve to addresses.
@@ -588,13 +593,33 @@
   while (iter.NextCU(reader, &cu)) {
     dwarf::DIEReader die_reader = cu.GetDIEReader();
     GeneralDIE compileunit_die;
+    DwoFilePointer dwo_info;
     auto* abbrev = die_reader.ReadCode(cu);
     die_reader.ReadAttributes(
         cu, abbrev,
-        [&cu, &compileunit_die](uint16_t tag, dwarf::AttrValue value) {
+        [&](uint16_t tag, dwarf::AttrValue value) {
           ReadGeneralDIEAttr(tag, value, cu, &compileunit_die);
+          switch (tag) {
+            case DW_AT_comp_dir:
+              if (value.IsString()) {
+                dwo_info.comp_dir = value.GetString(cu);
+              }
+              break;
+            case DW_AT_GNU_dwo_name:
+              if (value.IsString()) {
+                dwo_info.dwo_name = value.GetString(cu);
+              }
+              break;
+          }
         });
 
+    if (!dwo_info.comp_dir.empty() && !dwo_info.dwo_name.empty()) {
+      auto file = MmapInputFileFactory().OpenFile(dwo_info.comp_dir + "/" + dwo_info.dwo_name);
+      dwarf::File dwo_dwarf;
+      cu.dwarf().open(*file, &dwo_dwarf, sink);
+      ReadDWARFCompileUnits(dwo_dwarf, symbol_map, &cu, sink);
+    }
+
     if (cu.unit_name().empty()) {
       continue;
     }
@@ -628,7 +653,7 @@
 }
 
 void ReadDWARFCompileUnits(const dwarf::File& file, const DualMap& symbol_map,
-                           RangeSink* sink) {
+                           const dwarf::CU* skeleton, RangeSink* sink) {
   if (!file.debug_info.size()) {
     THROW("missing debug info");
   }
@@ -638,7 +663,7 @@
   }
 
   // Share a reader to avoid re-parsing debug abbreviations.
-  dwarf::InfoReader reader(file);
+  dwarf::InfoReader reader(file, skeleton);
 
   ReadDWARFDebugInfo(reader, dwarf::InfoReader::Section::kDebugInfo, symbol_map,
                      sink);
diff --git a/src/dwarf/attr.cc b/src/dwarf/attr.cc
index b794fd1..a540341 100644
--- a/src/dwarf/attr.cc
+++ b/src/dwarf/attr.cc
@@ -26,6 +26,12 @@
 namespace dwarf {
 
 absl::optional<uint64_t> AttrValue::ToUint(const CU& cu) const {
+  if (form_ == DW_FORM_implicit_const) {
+    // DW_FORM_implicit_const value is stored in AbbrevTable, but
+    // we don't keep it in AttrValue (discarded in AbbrevTable::ReadAbbrevs()).
+    // return absl::nullopt to make sure the value is not available.
+    return absl::nullopt;
+  }
   if (IsUint()) return GetUint(cu);
   string_view str = GetString(cu);
   switch (str.size()) {
@@ -46,6 +52,10 @@
     return ResolveIndirectAddress(cu);
   } else {
     assert(type_ == Type::kUint);
+    // DW_FORM_implicit_const value is stored in AbbrevTable, but
+    // we don't keep it in AttrValue (discarded in AbbrevTable::ReadAbbrevs()).
+    // Assertion makes sure that nobody is trying to read a fake value.
+    assert(form_ != DW_FORM_implicit_const);
     return uint_;
   }
 }
@@ -102,7 +112,7 @@
   return ReadIndirectAddress(cu, uint_);
 }
 
-AttrValue AttrValue::ParseAttr(const CU& cu, uint8_t form, string_view* data) {
+AttrValue AttrValue::ParseAttr(const CU& cu, uint16_t form, string_view* data) {
   switch (form) {
     case DW_FORM_indirect: {
       uint16_t indirect_form = ReadLEB128<uint16_t>(data);
@@ -130,6 +140,7 @@
     case DW_FORM_strx4:
       return AttrValue::UnresolvedString(form, ReadFixed<uint32_t>(data));
     case DW_FORM_strx:
+    case DW_FORM_GNU_str_index:
       return AttrValue::UnresolvedString(form, ReadLEB128<uint64_t>(data));
     case DW_FORM_addrx1:
       return AttrValue::UnresolvedUint(form, ReadFixed<uint8_t>(data));
@@ -140,6 +151,7 @@
     case DW_FORM_addrx4:
       return AttrValue::UnresolvedUint(form, ReadFixed<uint32_t>(data));
     case DW_FORM_addrx:
+    case DW_FORM_GNU_addr_index:
       return AttrValue::UnresolvedUint(form, ReadLEB128<uint64_t>(data));
     case DW_FORM_addr:
     address_size:
@@ -176,6 +188,7 @@
     case DW_FORM_string:
       return AttrValue(form, ReadNullTerminated(data));
     case DW_FORM_strp:
+    case DW_FORM_line_strp:
       if (cu.unit_sizes().dwarf64()) {
         return AttrValue(form, ReadIndirectString<uint64_t>(cu, data));
       } else {
@@ -201,6 +214,8 @@
       return AttrValue(form, ReadFixed<uint8_t>(data));
     case DW_FORM_sdata:
       return AttrValue(form, ReadLEB128<uint64_t>(data));
+    case DW_FORM_implicit_const:
+      return AttrValue(form, 1);
     default:
       THROWF("Don't know how to parse DWARF form: $0", form);
   }
diff --git a/src/dwarf/attr.h b/src/dwarf/attr.h
index 9261d26..a6964d2 100644
--- a/src/dwarf/attr.h
+++ b/src/dwarf/attr.h
@@ -27,7 +27,7 @@
 
 class AttrValue {
  public:
-  static AttrValue ParseAttr(const CU& cu, uint8_t form, absl::string_view* data);
+  static AttrValue ParseAttr(const CU& cu, uint16_t form, absl::string_view* data);
 
   AttrValue(const AttrValue &) = default;
   AttrValue &operator=(const AttrValue &) = default;
@@ -45,7 +45,7 @@
   // Attempts to coerce to uint, returning nullopt if this is not possible.
   absl::optional<uint64_t> ToUint(const CU& cu) const;
 
-  // REQUIRES: IsUint().
+  // REQUIRES: IsUint() && form() != DW_FORM_implicit_const.
   uint64_t GetUint(const CU& cu) const;
 
   // REQUIRES: IsString().
diff --git a/src/dwarf/debug_info.cc b/src/dwarf/debug_info.cc
index e7f488c..cd9ce16 100644
--- a/src/dwarf/debug_info.cc
+++ b/src/dwarf/debug_info.cc
@@ -60,12 +60,15 @@
     while (true) {
       Attribute attr;
       attr.name = ReadLEB128<uint16_t>(&data);
-      attr.form = ReadLEB128<uint8_t>(&data);
+      attr.form = ReadLEB128<uint16_t>(&data);
 
       if (attr.name == 0 && attr.form == 0) {
         break;  // End of this abbrev
       }
-
+      if (attr.form == DW_FORM_implicit_const) {
+        // We don't use the constant value, just discard it.
+        ReadLEB128<int64_t>(&data);
+      }
       abbrev.attr.push_back(attr);
     }
   }
@@ -125,6 +128,7 @@
                     InfoReader::Section section, InfoReader& reader) {
   entire_unit_ = entire_unit;
   dwarf_ = &reader.dwarf_;
+  dwo_id_ = 0;
   unit_sizes_.ReadDWARFVersion(&data);
 
   if (unit_sizes_.dwarf_version() > 5) {
@@ -179,6 +183,12 @@
 
   data_ = data;
   ReadTopLevelDIE(reader);
+
+  if (reader.skeleton_ && reader.skeleton_->dwo_id_ == dwo_id_) {
+    skeleton_ = reader.skeleton_;
+  } else {
+    skeleton_ = this;
+  }
 }
 
 // Read the root-level DIE in order to populate some member variables on which
@@ -203,6 +213,7 @@
             }
             break;
           case DW_AT_addr_base:
+          case DW_AT_GNU_addr_base:
             if (value.form() == DW_FORM_sec_offset) {
               addr_base_ = value.GetUint(*this);
             }
@@ -217,6 +228,10 @@
               range_lists_base_ = value.GetUint(*this);
             }
             break;
+          case DW_AT_GNU_dwo_id:
+            if (value.IsUint()) {
+              dwo_id_ = value.GetUint(*this);
+            }
         }
       });
 
diff --git a/src/dwarf/debug_info.h b/src/dwarf/debug_info.h
index d1b0f79..a18b0b4 100644
--- a/src/dwarf/debug_info.h
+++ b/src/dwarf/debug_info.h
@@ -59,8 +59,16 @@
 #include "util.h"
 
 namespace bloaty {
+
+class InputFile;
+class RangeSink;
+
 namespace dwarf {
 
+struct File;
+
+typedef void OpenDwarf(const InputFile &file, File *dwarf, RangeSink *sink);
+
 struct File {
   absl::string_view debug_abbrev;
   absl::string_view debug_addr;
@@ -75,6 +83,8 @@
   absl::string_view debug_str;
   absl::string_view debug_str_offsets;
   absl::string_view debug_types;
+  const InputFile* file;
+  OpenDwarf* open;
 
   absl::string_view* GetFieldByName(absl::string_view name);
   void SetFieldByName(absl::string_view name, absl::string_view contents) {
@@ -156,7 +166,7 @@
   // In a DWARF abbreviation, each attribute has a name and a form.
   struct Attribute {
     uint16_t name;
-    uint8_t form;
+    uint16_t form;
   };
 
   // The representation of a single abbreviation.
@@ -198,6 +208,8 @@
 class InfoReader {
  public:
   InfoReader(const File& file) : dwarf_(file) {}
+  InfoReader(const File& file, const CU* skeleton)
+      : dwarf_(file), skeleton_(skeleton) {}
   InfoReader(const InfoReader&) = delete;
   InfoReader& operator=(const InfoReader&) = delete;
 
@@ -214,6 +226,7 @@
  private:
   friend class CU;
   const File& dwarf_;
+  const CU* skeleton_ = nullptr;
 
   std::unordered_map<uint64_t, std::string> stmt_list_map_;
 
@@ -243,6 +256,7 @@
   DIEReader GetDIEReader();
 
   const File& dwarf() const { return *dwarf_; }
+  const CU& skeleton() const { return *skeleton_; }
   const CompilationUnitSizes& unit_sizes() const { return unit_sizes_; }
   const std::string& unit_name() const { return unit_name_; }
   absl::string_view entire_unit() const { return entire_unit_; }
@@ -285,6 +299,7 @@
   // Only for skeleton and split CUs.
   uint8_t unit_type_;
   uint64_t dwo_id_;
+  const CU* skeleton_;
 
   // Only for .debug_types
   uint64_t unit_type_signature_;
@@ -324,13 +339,14 @@
 };
 
 inline uint64_t ReadIndirectAddress(const CU& cu, uint64_t val) {
-  absl::string_view addrs = cu.dwarf().debug_addr;
+  absl::string_view addrs = cu.skeleton().dwarf().debug_addr;
+  uint64_t base = cu.skeleton().addr_base();
   switch (cu.unit_sizes().address_size()) {
     case 4:
-      SkipBytes((val * 4) + cu.addr_base(), &addrs);
+      SkipBytes((val * 4) + base, &addrs);
       return ReadFixed<uint32_t>(&addrs);
     case 8:
-      SkipBytes((val * 8) + cu.addr_base(), &addrs);
+      SkipBytes((val * 8) + base, &addrs);
       return ReadFixed<uint64_t>(&addrs);
     default:
       BLOATY_UNREACHABLE();
diff --git a/src/elf.cc b/src/elf.cc
index 67c8893..a8d7f42 100644
--- a/src/elf.cc
+++ b/src/elf.cc
@@ -1192,10 +1192,12 @@
 // reader directly on them.  At the moment we don't attempt to make these
 // work with object files.
 
-static void ReadDWARFSections(const InputFile &file, dwarf::File *dwarf,
-                              RangeSink *sink) {
+void ReadDWARFSections(const InputFile &file, dwarf::File *dwarf,
+                       RangeSink *sink) {
   ElfFile elf(file.data());
   assert(elf.IsOpen());
+  dwarf->file = &file;
+  dwarf->open = &ReadDWARFSections;
   for (Elf64_Xword i = 1; i < elf.section_count(); i++) {
     ElfFile::Section section;
     elf.ReadSection(i, &section);
@@ -1229,6 +1231,12 @@
       uncompressed_size = ReadBigEndian<uint64_t>(&contents);
     }
 
+    static constexpr string_view dwo_str(".dwo");
+    if (name.size() >= dwo_str.size() &&
+        name.rfind(".dwo") == name.size() - dwo_str.size()) {
+      name.remove_suffix(dwo_str.size());
+    }
+
     if (string_view* member = dwarf->GetFieldByName(name)) {
       if (uncompressed_size) {
         *member = sink->ZlibDecompress(contents, uncompressed_size);
diff --git a/src/macho.cc b/src/macho.cc
index f399853..18a9474 100644
--- a/src/macho.cc
+++ b/src/macho.cc
@@ -522,6 +522,8 @@
 
 static void ReadDebugSectionsFromMachO(const InputFile &file,
                                        dwarf::File *dwarf, RangeSink *sink) {
+  dwarf->file = &file;
+  dwarf->open = &ReadDebugSectionsFromMachO;
   ForEachLoadCommand(
       file.data(), nullptr, [dwarf, sink](const LoadCommand &cmd) {
         switch (cmd.cmd) {
diff --git a/src/webassembly.cc b/src/webassembly.cc
index 9d1ccc0..5e2ab65 100644
--- a/src/webassembly.cc
+++ b/src/webassembly.cc
@@ -178,24 +178,31 @@
   });
 }
 
-typedef std::unordered_map<int, std::string> FuncNames;
+typedef std::unordered_map<int, std::string> IndexedNames;
 
-void ReadFunctionNames(const Section& section, FuncNames* names,
-                       RangeSink* sink) {
+void ReadNames(const Section& section, IndexedNames* func_names,
+               IndexedNames* dataseg_names, RangeSink* sink) {
   enum class NameType {
     kModule = 0,
     kFunction = 1,
     kLocal = 2,
+    kLabel = 3,
+    kType = 4,
+    kTable = 5,
+    kMemory = 6,
+    kGlobal = 7,
+    kElemSegment = 8,
+    kDataSegment = 9
   };
 
   string_view data = section.contents;
 
   while (!data.empty()) {
-    char type = ReadVarUInt7(&data);
+    NameType type = static_cast<NameType>(ReadVarUInt7(&data));
     uint32_t size = ReadVarUInt32(&data);
     string_view section = ReadPiece(size, &data);
 
-    if (static_cast<NameType>(type) == NameType::kFunction) {
+    if (type == NameType::kFunction || type == NameType::kDataSegment) {
       uint32_t count = ReadVarUInt32(&section);
       for (uint32_t i = 0; i < count; i++) {
         string_view entry = section;
@@ -204,6 +211,7 @@
         string_view name = ReadPiece(name_len, &section);
         entry = StrictSubstr(entry, 0, name.data() - entry.data() + name.size());
         sink->AddFileRange("wasm_funcname", name, entry);
+        IndexedNames *names = (type == NameType::kFunction ? func_names : dataseg_names);
         (*names)[index] = std::string(name);
       }
     }
@@ -276,7 +284,7 @@
   return func_count;
 }
 
-void ReadCodeSection(const Section& section, const FuncNames& names,
+void ReadCodeSection(const Section& section, const IndexedNames& names,
                      uint32_t num_imports, RangeSink* sink) {
   string_view data = section.contents;
 
@@ -301,25 +309,63 @@
   }
 }
 
+void ReadDataSection(const Section& section, const IndexedNames& names,
+                     RangeSink* sink) {
+  string_view data = section.contents;
+  uint32_t count = ReadVarUInt32(&data);
+
+  for (uint32_t i = 0; i < count; i++) {
+    string_view segment = data;
+    uint8_t mode = ReadFixed<uint8_t>(&data);
+    if (mode > 1) THROW("multi-memory extension isn't supported");
+    if (mode == 0) { // Active segment
+      // We will need to read the init expr.
+      // For the extended const proposal, read instructions until end is reached
+      // Otherwise, just read a constexpr inst (t.const or global.get)
+      // For now, we just need to support passive segments.
+      continue;
+    }
+    // else, a passive segment
+
+    uint32_t segment_size = ReadVarUInt32(&data);
+    uint32_t total_size = segment_size + (data.data() - segment.data());
+
+    segment = StrictSubstr(segment, 0, total_size);
+    data = StrictSubstr(data, segment_size);
+
+    auto iter = names.find(i);
+    if (iter == names.end()) {
+      std::string name = "data[" + std::to_string(i) + "]";
+      sink->AddFileRange("wasm_data", name, segment);
+    } else {
+      sink->AddFileRange("wasm_data", iter->second, segment);
+    }
+  }
+}
+
+
 void ParseSymbols(RangeSink* sink) {
   // First pass: read the custom naming section to get function names.
   std::unordered_map<int, std::string> func_names;
+  std::unordered_map<int, std::string> dataseg_names;
   uint32_t num_imports = 0;
 
   ForEachSection(sink->input_file().data(),
-                 [&func_names, sink](const Section& section) {
+                 [&func_names, &dataseg_names, sink](const Section& section) {
                    if (section.name == "name") {
-                     ReadFunctionNames(section, &func_names, sink);
+                     ReadNames(section, &func_names, &dataseg_names, sink);
                    }
                  });
 
   // Second pass: read the function/code sections.
   ForEachSection(sink->input_file().data(),
-                 [&func_names, &num_imports, sink](const Section& section) {
+                 [&func_names, &dataseg_names, &num_imports, sink](const Section& section) {
                    if (section.id == Section::kImport) {
                      num_imports = GetNumFunctionImports(section);
                    } else if (section.id == Section::kCode) {
                      ReadCodeSection(section, func_names, num_imports, sink);
+                   } else if (section.id == Section::kData) {
+                     ReadDataSection(section, dataseg_names, sink);
                    }
                  });
 }
diff --git a/src/write_bloaty_report.cc b/src/write_bloaty_report.cc
index a22fbad..593eff6 100644
--- a/src/write_bloaty_report.cc
+++ b/src/write_bloaty_report.cc
@@ -30,14 +30,15 @@
 
   google::protobuf::Arena arena;
   Report* report = google::protobuf::Arena::CreateMessage<Report>(&arena);
-  report->set_file_total(toplevel_row_.filesize);
-  report->set_vm_total(toplevel_row_.vmsize);
+  report->set_file_total(toplevel_row_.size.file);
+  report->set_vm_total(toplevel_row_.size.vm);
 
   ([&] {
     // If 2 levels, assume compileunits,symbols.
     // If 3 levels, assume accesspattern,compileunits,symbols; and pick the cold accesses.
     auto* compile_unit_row = &toplevel_row_;
-    if (toplevel_row_.sorted_children.front().sorted_children.front().sorted_children.size() > 0) {
+    if (toplevel_row_.sorted_children.front().sorted_children.size() > 0 &&
+        toplevel_row_.sorted_children.front().sorted_children.front().sorted_children.size() > 0) {
       bool found_hot = false;
       for (const auto& access : toplevel_row_.sorted_children) {
         if (access.name == "Hot") {
@@ -68,8 +69,8 @@
       CompileUnit* compile_unit = report->add_compile_units();
       for (const auto& symbol_row : child_row.sorted_children) {
         SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena);
-        info->set_file_actual(symbol_row.filesize);
-        info->set_vm_actual(symbol_row.vmsize);
+        info->set_file_actual(symbol_row.size.file);
+        info->set_vm_actual(symbol_row.size.vm);
         Symbol* symbol = compile_unit->add_symbols();
         symbol->set_allocated_sizes(info);
         auto decoded_symbol = DecodeSymbolWithCrateId(symbol_row.name);
@@ -80,16 +81,16 @@
         // Add a "fake symbol" that is the same as the compile unit,
         // to make sizes add up.
         SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena);
-        info->set_file_actual(child_row.filesize);
-        info->set_vm_actual(child_row.vmsize);
+        info->set_file_actual(child_row.size.file);
+        info->set_vm_actual(child_row.size.vm);
         Symbol* symbol = compile_unit->add_symbols();
         symbol->set_allocated_sizes(info);
         symbol->set_name(child_row.name);
       }
 
       SizeInfo* info = google::protobuf::Arena::CreateMessage<SizeInfo>(&arena);
-      info->set_file_actual(child_row.filesize);
-      info->set_vm_actual(child_row.vmsize);
+      info->set_file_actual(child_row.size.file);
+      info->set_vm_actual(child_row.size.vm);
       compile_unit->set_allocated_sizes(info);
       compile_unit->set_name(child_row.name);
     }
diff --git a/tests/bloaty_misc_test.cc b/tests/bloaty_misc_test.cc
index 8bee15a..da794d8 100644
--- a/tests/bloaty_misc_test.cc
+++ b/tests/bloaty_misc_test.cc
@@ -27,7 +27,7 @@
       {"bloaty", "-d", "compileunits", "03-small-binary-that-crashed-inlines.bin"});
   RunBloaty(
       {"bloaty", "-d", "inlines", "03-small-binary-that-crashed-inlines.bin"});
-  EXPECT_EQ(top_row_->vmsize, 2340);
+  EXPECT_EQ(top_row_->size.vm, 2340);
 }
 
 TEST_F(BloatyTest, GoBinary) {
@@ -37,9 +37,16 @@
       {"bloaty", "-d", "inlines", "04-go-binary-with-ref-addr.bin"});
 }
 
+TEST_F(BloatyTest, ImplicitConstAndLineStrp) {
+  RunBloaty(
+      {"bloaty", "-d", "compileunits", "05-implicit-const-and-line-strp.bin"});
+  RunBloaty(
+      {"bloaty", "-d", "inlines", "05-implicit-const-and-line-strp.bin"});
+}
+
 TEST_F(BloatyTest, MultiThreaded) {
   RunBloaty({"bloaty", "02-section-count-overflow.o"});
-  size_t file_size = top_row_->filesize;
+  size_t file_size = top_row_->size.file;
 
   // Bloaty doesn't know or care that you are passing the same file multiple
   // times.
@@ -49,7 +56,7 @@
     args.push_back("02-section-count-overflow.o");
   }
   RunBloaty(args);  // Heavily multithreaded test.
-  EXPECT_EQ(top_row_->filesize, file_size * 100);
+  EXPECT_EQ(top_row_->size.file, file_size * 100);
 }
 
 TEST(GetPathStem, Normal) {
diff --git a/tests/bloaty_test.cc b/tests/bloaty_test.cc
index 8187aca..68eb9df 100644
--- a/tests/bloaty_test.cc
+++ b/tests/bloaty_test.cc
@@ -21,20 +21,20 @@
 
   // Empty .c file should result in a .o file with no vmsize.
   RunBloaty({"bloaty", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with segments (we fake segments on .o files).
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with symbols.
   RunBloaty({"bloaty", "-d", "symbols", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // We can't run any of these targets against object files.
@@ -50,16 +50,16 @@
 
   // Test "-n 0" which should return an unlimited number of rows.
   RunBloaty({"bloaty", "-n", "0", file});
-  EXPECT_GT(top_row_->vmsize, 64);
-  EXPECT_LT(top_row_->vmsize, 300);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 64);
+  EXPECT_LT(top_row_->size.vm, 300);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with segments (we fake segments on .o files).
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 64);
-  EXPECT_LT(top_row_->vmsize, 300);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 64);
+  EXPECT_LT(top_row_->size.vm, 300);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // For inputfiles we should get everything attributed to the input file.
@@ -111,15 +111,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  //EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  //EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  //EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  //EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "40", "-s", "vm", file});
   AssertChildren(*top_row_, {
@@ -174,15 +174,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "50", file});
   AssertChildren(*top_row_, {
@@ -199,15 +199,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "50", "-s", "vm", file});
   AssertChildren(*top_row_, {
diff --git a/tests/dwarf/debug_info/gnu-split-dwarf.test b/tests/dwarf/debug_info/gnu-split-dwarf.test
new file mode 100644
index 0000000..bbe38dc
--- /dev/null
+++ b/tests/dwarf/debug_info/gnu-split-dwarf.test
@@ -0,0 +1,166 @@
+# Tests that we can find .dwo files based on the DW_AT_GNU_dwo_name attribute.
+# Output of this sort will be produced by using -gsplit-dwarf with gcc or clang.
+
+# RUN: mkdir -p %/t
+# RUN: cat %s | sed "s|__PWD__|%/t|" | %yaml2obj --docnum=1 -o %t.obj
+# RUN: %yaml2obj %s --docnum=2 -o %/t/foo.dwo
+# RUN: %yaml2obj %s --docnum=3 -o %/t/bar.dwo
+# RUN: %bloaty %t.obj -d compileunits --raw-map --domain=vm | %FileCheck %s
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_X86_64
+  Entry:           0x1040
+ProgramHeaders:
+  - Type:            PT_LOAD
+    Flags:           [ PF_X, PF_R ]
+    FirstSec:        .text
+    LastSec:         .text
+    VAddr:           0x400000
+    Align:           0x1000
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x400000
+    AddressAlign:    0x10
+    Size:            0x1100
+  - Name:            .debug_addr
+    Type:            SHT_PROGBITS
+    AddressAlign:    0x1
+    Content: '00004000000000000001400000000000'
+DWARF:
+  debug_str:
+    # We don't expect DW_AT_comp_dir to contain '.' in real life,
+    # but using it here lets our test data work from any filesystem
+    # location
+    - foo.dwo
+    - bar.dwo
+    - __PWD__
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x1
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_comp_dir
+              Form:            DW_FORM_strp
+            - Attribute:       DW_AT_GNU_dwo_name
+              Form:            DW_FORM_strp
+            - Attribute:       DW_AT_GNU_dwo_id
+              Form:            DW_FORM_data8
+            - Attribute:       DW_AT_GNU_addr_base
+              Form:            DW_FORM_sec_offset
+  debug_info:
+    - Version:         4
+      AbbrevTableID:   0
+      AbbrOffset:      0x0
+      AddrSize:        8
+      Entries:
+        - AbbrCode:        0x1
+          Values:
+            - Value:           0x10
+            - Value:           0x0
+            - Value:           0xdeaddeaddeadbeef
+            - Value:           0x0
+    - Version:         4
+      AbbrevTableID:   0
+      AbbrOffset:      0x0
+      AddrSize:        8
+      Entries:
+        - AbbrCode:        0x1
+          Values:
+            - Value:           0x10
+            - Value:           0x8
+            - Value:           0xbeefbeefbeefbeef
+            - Value:           0x8
+...
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_REL
+  Machine:         EM_X86_64
+DWARF:
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x1
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_strp
+            - Attribute:       DW_AT_GNU_dwo_id
+              Form:            DW_FORM_data8
+            - Attribute:       DW_AT_low_pc
+              Form:            DW_FORM_GNU_addr_index
+            - Attribute:       DW_AT_high_pc
+              Form:            DW_FORM_data4
+  debug_str:
+    - foo.c
+  debug_info:
+    - Version:         4
+      AbbrevTableID:   0
+      AbbrOffset:      0x0
+      AddrSize:        8
+      Entries:
+        - AbbrCode:        0x1
+          Values:
+            - Value:           0x0
+            - Value:           0xdeaddeaddeadbeef
+            - Value:           0x0
+            - Value:           0x100
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_REL
+  Machine:         EM_X86_64
+DWARF:
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x1
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_strp
+            - Attribute:       DW_AT_GNU_dwo_id
+              Form:            DW_FORM_data8
+            - Attribute:       DW_AT_low_pc
+              Form:            DW_FORM_GNU_addr_index
+            - Attribute:       DW_AT_high_pc
+              Form:            DW_FORM_data4
+  debug_str:
+    - bar.c
+  # XXX: In a real file this section would be named debug_info.dwo.
+  # But currently obj2yaml doesn't have any way of specifying a section name
+  # other than debug_info.  This means we don't currently have testing of
+  # the code that strips .dwo from the end of section names.
+  debug_info:
+    - Version:         4
+      AbbrevTableID:   0
+      AbbrOffset:      0x0
+      AddrSize:        8
+      Entries:
+        - AbbrCode:        0x1
+          Values:
+            - Value:           0x0
+            - Value:           0xbeefbeefbeefbeef
+            - Value:           0x0
+            - Value:           0x1000
+
+...
+
+# CHECK: VM MAP:
+# CHECK: 000000-400000       4194304             [-- Nothing mapped --]
+# CHECK: 400000-400100           256             foo.c
+# CHECK: 400100-401100          4096             bar.c
diff --git a/tests/fuzz_target.cc b/tests/fuzz_target.cc
index 037b414..99e13f5 100644
--- a/tests/fuzz_target.cc
+++ b/tests/fuzz_target.cc
@@ -28,6 +28,12 @@
       : InputFile("fake_StringPieceInputFile_file") {
     data_ = data;
   }
+
+  bool TryOpen(absl::string_view /* filename */,
+               std::unique_ptr<InputFile>& file) override {
+    file.reset(new StringPieceInputFile(data_));
+    return true;
+  }
 };
 
 class StringPieceInputFileFactory : public InputFileFactory {
diff --git a/tests/test.h b/tests/test.h
index dc864f0..99517d7 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -81,16 +81,16 @@
       uint64_t vmtotal = 0;
       uint64_t filetotal = 0;
       for (const auto& child : row.sorted_children) {
-        vmtotal += child.vmsize;
-        filetotal += child.filesize;
+        vmtotal += child.size.vm;
+        filetotal += child.size.file;
         CheckConsistencyForRow(child, false, diff_mode, count);
         ASSERT_TRUE(names.insert(child.name).second);
-        ASSERT_FALSE(child.vmsize == 0 && child.filesize == 0);
+        ASSERT_FALSE(child.size.vm == 0 && child.size.file == 0);
       }
 
       if (!diff_mode) {
-        ASSERT_EQ(vmtotal, row.vmsize);
-        ASSERT_EQ(filetotal, row.filesize);
+        ASSERT_EQ(vmtotal, row.size.vm);
+        ASSERT_EQ(filetotal, row.size.file);
       }
     } else {
       // Count leaf rows.
@@ -147,7 +147,7 @@
         ASSERT_TRUE(GetFileSize(filename, &size));
         total_input_size += size;
       }
-      ASSERT_EQ(top_row_->filesize, total_input_size);
+      ASSERT_EQ(top_row_->size.file, total_input_size);
     }
 
     int rows = 0;
@@ -248,21 +248,21 @@
       if (expected_vm == kUnknown) {
         // Always pass.
       } else if (expected_vm > 0) {
-        EXPECT_GE(child.vmsize, expected_vm);
+        EXPECT_GE(child.size.vm, expected_vm);
         // Allow some overhead.
-        EXPECT_LE(child.vmsize, (expected_vm * 1.1) + 100);
+        EXPECT_LE(child.size.vm, (expected_vm * 1.1) + 100);
       } else {
         ASSERT_TRUE(false);
       }
 
       if (expected_file == kSameAsVM) {
-        expected_file = child.vmsize;
+        expected_file = child.size.vm;
       }
 
       if (expected_file != kUnknown) {
-        EXPECT_GE(child.filesize, expected_file);
+        EXPECT_GE(child.size.file, expected_file);
         // Allow some overhead.
-        EXPECT_LE(child.filesize, (expected_file * 1.2) + 180);
+        EXPECT_LE(child.size.file, (expected_file * 1.2) + 180);
       }
 
       if (++i == children.size()) {
diff --git a/tests/testdata/make_all_msvc_test_files.bat b/tests/testdata/make_all_msvc_test_files.bat
old mode 100644
new mode 100755
diff --git a/tests/testdata/misc/05-implicit-const-and-line-strp.bin b/tests/testdata/misc/05-implicit-const-and-line-strp.bin
new file mode 100755
index 0000000..4f84668
--- /dev/null
+++ b/tests/testdata/misc/05-implicit-const-and-line-strp.bin
Binary files differ
diff --git a/tests/wasm/sections.test b/tests/wasm/sections.test
new file mode 100644
index 0000000..eff9979
--- /dev/null
+++ b/tests/wasm/sections.test
@@ -0,0 +1,221 @@
+# RUN: %yaml2obj %s -o %t.obj
+# RUN: %bloaty --raw-map %t.obj | %FileCheck %s
+
+--- !WASM
+FileHeader:
+  Version:         0x1
+Sections:
+  - Type:            TYPE
+    Signatures:
+      - Index:           0
+        ParamTypes:      []
+        ReturnTypes:
+          - I32
+      - Index:           1
+        ParamTypes:
+          - I32
+          - I32
+        ReturnTypes:
+          - I32
+  - Type:            IMPORT
+    Imports:
+      - Module:          env
+        Field:           __linear_memory
+        Kind:            MEMORY
+        Memory:
+          Minimum:         0x1
+      - Module:          env
+        Field:           __stack_pointer
+        Kind:            GLOBAL
+        GlobalType:      I32
+        GlobalMutable:   true
+      - Module:          env
+        Field:           __indirect_function_table
+        Kind:            TABLE
+        Table:
+          Index:           0
+          ElemType:        FUNCREF
+          Limits:
+            Minimum:         0x1
+  - Type:            FUNCTION
+    FunctionTypes:   [ 0, 0, 0, 1 ]
+  - Type:            ELEM
+    Segments:
+      - Offset:
+          Opcode:          I32_CONST
+          Value:           1
+        Functions:       [ 0 ]
+  - Type:            DATACOUNT
+    Count:           4
+  - Type:            CODE
+    Relocations:
+      - Type:            R_WASM_MEMORY_ADDR_LEB
+        Index:           1
+        Offset:          0xD
+      - Type:            R_WASM_MEMORY_ADDR_LEB
+        Index:           2
+        Offset:          0x27
+      - Type:            R_WASM_MEMORY_ADDR_LEB
+        Index:           3
+        Offset:          0x3D
+      - Type:            R_WASM_MEMORY_ADDR_LEB
+        Index:           3
+        Offset:          0x55
+      - Type:            R_WASM_MEMORY_ADDR_SLEB
+        Index:           4
+        Offset:          0x5B
+      - Type:            R_WASM_TABLE_INDEX_SLEB
+        Index:           0
+        Offset:          0x6F
+      - Type:            R_WASM_GLOBAL_INDEX_LEB
+        Index:           7
+        Offset:          0x83
+      - Type:            R_WASM_GLOBAL_INDEX_LEB
+        Index:           7
+        Offset:          0x98
+      - Type:            R_WASM_FUNCTION_INDEX_LEB
+        Index:           0
+        Offset:          0xB4
+      - Type:            R_WASM_GLOBAL_INDEX_LEB
+        Index:           7
+        Offset:          0xCC
+      - Type:            R_WASM_FUNCTION_INDEX_LEB
+        Index:           6
+        Offset:          0xDA
+    Functions:
+      - Index:           0
+        Locals:
+          - Type:            I32
+            Count:           13
+        Body:            41002100200028028880808000210141052102200120026A210341002104200428028C808080002105200320056A2106410021072007280280808080002108200820066A21094100210A200A200936028080808000418480808000210B200B210C200C0F0B
+      - Index:           1
+        Locals:
+          - Type:            I32
+            Count:           2
+        Body:            41818080800021002000210120010F0B
+      - Index:           2
+        Locals:
+          - Type:            I32
+            Count:           8
+        Body:            238080808000210041102101200020016B21022002248080808000410021032002200336020C41022104200220043602081080808080001A4103210541102106200220066A2107200724808080800020050F0B
+      - Index:           3
+        Locals:
+          - Type:            I32
+            Count:           1
+        Body:            108280808000210220020F0B
+  - Type:            DATA
+    Segments:
+      - SectionOffset:   6
+        InitFlags:       0
+        Offset:
+          Opcode:          I32_CONST
+          Value:           0
+        Content:         '00000000'
+      - SectionOffset:   15
+        InitFlags:       0
+        Offset:
+          Opcode:          I32_CONST
+          Value:           4
+        Content:         '03000000'
+      - SectionOffset:   24
+        InitFlags:       0
+        Offset:
+          Opcode:          I32_CONST
+          Value:           8
+        Content:         EFBEADDE
+      - SectionOffset:   33
+        InitFlags:       0
+        Offset:
+          Opcode:          I32_CONST
+          Value:           12
+        Content:         '05000000'
+  - Type:            CUSTOM
+    Name:            linking
+    Version:         2
+    SymbolTable:
+      - Index:           0
+        Kind:            FUNCTION
+        Name:            func1
+        Flags:           [  ]
+        Function:        0
+      - Index:           1
+        Kind:            DATA
+        Name:            global3
+        Flags:           [  ]
+        Segment:         2
+        Size:            4
+      - Index:           2
+        Kind:            DATA
+        Name:            global4
+        Flags:           [  ]
+        Segment:         3
+        Size:            4
+      - Index:           3
+        Kind:            DATA
+        Name:            global1
+        Flags:           [  ]
+        Segment:         0
+        Size:            4
+      - Index:           4
+        Kind:            DATA
+        Name:            global2
+        Flags:           [  ]
+        Segment:         1
+        Size:            4
+      - Index:           5
+        Kind:            FUNCTION
+        Name:            func2
+        Flags:           [  ]
+        Function:        1
+      - Index:           6
+        Kind:            FUNCTION
+        Name:            __original_main
+        Flags:           [  ]
+        Function:        2
+      - Index:           7
+        Kind:            GLOBAL
+        Name:            __stack_pointer
+        Flags:           [ UNDEFINED ]
+        Global:          0
+      - Index:           8
+        Kind:            FUNCTION
+        Name:            main
+        Flags:           [  ]
+        Function:        3
+    SegmentInfo:
+      - Index:           0
+        Name:            .bss.global1
+        Alignment:       2
+        Flags:           [  ]
+      - Index:           1
+        Name:            .data.global2
+        Alignment:       2
+        Flags:           [  ]
+      - Index:           2
+        Name:            .data.global3
+        Alignment:       2
+        Flags:           [  ]
+      - Index:           3
+        Name:            .data.global4
+        Alignment:       2
+        Flags:           [  ]
+  - Type:            CUSTOM
+    Name:            producers
+    Tools:
+      - Name:            clang
+        Version:         '14.0.0 (https://github.com/llvm/llvm-project f71c553a30cc52c0b4a6abbaa82ce97c30c13979)'
+...
+
+# CHECK: FILE MAP:
+# CHECK: 000-008           8             [WASM Header]
+# CHECK: 008-015          13             Type
+# CHECK: 015-068          83             Import
+# CHECK: 068-06f           7             Function
+# CHECK: 06f-078           9             Element
+# CHECK: 078-07b           3             DataCount
+# CHECK: 07b-163         232             Code
+# CHECK: 163-18a          39             Data
+# CHECK: 18a-23f         181             linking
+# CHECK: 23f-2b7         120             producers
+# CHECK: 2b7-2f1          58             reloc.CODE
+
diff --git a/tests/wasm/symbol_test.test b/tests/wasm/symbol_test.test
new file mode 100644
index 0000000..60194f8
--- /dev/null
+++ b/tests/wasm/symbol_test.test
@@ -0,0 +1,209 @@
+# RUN: %yaml2obj %s -o %t.obj
+# RUN: %bloaty -d symbols --raw-map %t.obj | %FileCheck %s
+
+--- !WASM
+FileHeader:
+  Version:         0x1
+Sections:
+  - Type:            TYPE
+    Signatures:
+      - Index:           0
+        ParamTypes:      []
+        ReturnTypes:     []
+      - Index:           1
+        ParamTypes:
+          - I32
+        ReturnTypes:     []
+      - Index:           2
+        ParamTypes:      []
+        ReturnTypes:
+          - I32
+      - Index:           3
+        ParamTypes:
+          - I32
+          - I32
+        ReturnTypes:
+          - I32
+  - Type:            FUNCTION
+    FunctionTypes:   [ 0, 1, 0, 2, 2, 2, 3 ]
+  - Type:            TABLE
+    Tables:
+      - Index:           0
+        ElemType:        FUNCREF
+        Limits:
+          Flags:           [ HAS_MAX ]
+          Minimum:         0x2
+          Maximum:         0x2
+  - Type:            MEMORY
+    Memories:
+      - Flags:           [ HAS_MAX, IS_SHARED ]
+        Minimum:         0x2
+        Maximum:         0x2
+  - Type:            GLOBAL
+    Globals:
+      - Index:           0
+        Type:            I32
+        Mutable:         true
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           66592
+      - Index:           1
+        Type:            I32
+        Mutable:         true
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           0
+      - Index:           2
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           0
+      - Index:           3
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           0
+  - Type:            EXPORT
+    Exports:
+      - Name:            memory
+        Kind:            MEMORY
+        Index:           0
+  - Type:            START
+    StartFunction:   2
+  - Type:            ELEM
+    Segments:
+      - Offset:
+          Opcode:          I32_CONST
+          Value:           1
+        Functions:       [ 3 ]
+  - Type:            DATACOUNT
+    Count:           3
+  - Type:            CODE
+    Functions:
+      - Index:           0
+        Locals:          []
+        Body:            0B
+      - Index:           1
+        Locals:          []
+        Body:            0B
+      - Index:           2
+        Locals:          []
+        Body:            02400240024041900841004101FE4802000E020001020B41800841004104FC08000041840841004104FC08010041880841004104FC080200418C0841004104FC0B004190084102FE170200419008417FFE0002001A0C010B4190084101427FFE0102001A0BFC0900FC0901FC09020B
+      - Index:           3
+        Locals:
+          - Type:            I32
+            Count:           13
+        Body:            41002100200028028488808000210141052102200120026A2103410021042004280288888080002105200320056A210641002107200728028C888080002108200820066A21094100210A200A200936028C88808000418088808000210B200B210C200C0F0B
+      - Index:           4
+        Locals:
+          - Type:            I32
+            Count:           2
+        Body:            41818080800021002000210120010F0B
+      - Index:           5
+        Locals:
+          - Type:            I32
+            Count:           8
+        Body:            238080808000210041102101200020016B21022002248080808000410021032002200336020C41022104200220043602081083808080001A4103210541102106200220066A2107200724808080800020050F0B
+      - Index:           6
+        Locals:
+          - Type:            I32
+            Count:           1
+        Body:            108580808000210220020F0B
+  - Type:            DATA
+    Segments:
+      - SectionOffset:   3
+        InitFlags:       1
+        Content:         '03000000'
+      - SectionOffset:   9
+        InitFlags:       1
+        Content:         EFBEADDE
+      - SectionOffset:   15
+        InitFlags:       1
+        Content:         '05000000'
+  - Type:            CUSTOM
+    Name:            name
+    FunctionNames:
+      - Index:           0
+        Name:            __wasm_call_ctors
+      - Index:           1
+        Name:            __wasm_init_tls
+      - Index:           2
+        Name:            __wasm_init_memory
+      - Index:           3
+        Name:            func1
+      - Index:           4
+        Name:            func2
+      - Index:           5
+        Name:            __original_main
+      - Index:           6
+        Name:            main
+    GlobalNames:
+      - Index:           0
+        Name:            __stack_pointer
+      - Index:           1
+        Name:            __tls_base
+      - Index:           2
+        Name:            __tls_size
+      - Index:           3
+        Name:            __tls_align
+    DataSegmentNames:
+      - Index:           0
+        Name:            .data.global2
+      - Index:           1
+        Name:            .data.global3
+      - Index:           2
+        Name:            .data.global4
+  - Type:            CUSTOM
+    Name:            producers
+    Languages:
+      - Name:            C99
+        Version:         ''
+    Tools:
+      - Name:            clang
+        Version:         '14.0.0 (https://github.com/llvm/llvm-project f71c553a30cc52c0b4a6abbaa82ce97c30c13979)'
+  - Type:            CUSTOM
+    Name:            target_features
+    Features:
+      - Prefix:          USED
+        Name:            atomics
+      - Prefix:          USED
+        Name:            bulk-memory
+...
+
+# Check output for function and passive data segments
+
+# Code section
+# CHECK: 06b-06e           3             __wasm_call_ctors
+# CHECK: 06e-071           3             __wasm_init_tls
+# CHECK: 071-0e2         113             __wasm_init_memory
+# CHECK: 0e2-14b         105             func1
+# CHECK: 14b-15f          20             func2
+# CHECK: 15f-1b6          87             __original_main
+# CHECK: 1b6-1c6          16             main
+
+# Data section
+# FIXME: This is wrong, should be the data section header
+# BUG: 1c6-1c9           3             main
+# CHECK: 1c9-1cf           6             .data.global2
+# CHECK: 1cf-1d5           6             .data.global3
+# CHECK: 1d5-1db           6             .data.global4
+
+# Name section
+# FIXME: Name section header and subsection header is 1db-1e6
+# BUG: 1db-1e6          11             .data.global4
+# Function names
+# CHECK: 1e6-1f9          19             __wasm_call_ctors
+# CHECK: 1f9-20a          17             __wasm_init_tls
+# CHECK: 20a-21e          20             __wasm_init_memory
+# CHECK: 21e-225           7             func1
+# CHECK: 225-22c           7             func2
+# CHECK: 22c-23d          17             __original_main
+# FIXME: Subsection headers and global names
+# BUG: 23d-243           6             main
+# BUG: 243-27f          60             [section name]
+# Data names
+# CHECK: 27f-28e          15             .data.global2
+# CHECK: 28e-29d          15             .data.global3
+# CHECK: 29d-2ac          15             .data.global4
\ No newline at end of file