Add ProcessSnapshotMinidump::CustomMinidumpStreams()

Add a method to ProcessSnapshotMinidump to expose a similar interface
to ModuleSnapshot::CustomMinidumpStreams(). It's implemented on the
process snapshot here because there is no way to map custom minidump
streams back to a specific module. This allows implementing tests that
inspect custom user streams in minidumps.

Bug: 896019

Change-Id: I1673c342753e13d64bddcc0083ca29fa356deac7
Reviewed-on: https://chromium-review.googlesource.com/c/1271405
Commit-Queue: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h
index f3701dc..bdaaa48 100644
--- a/minidump/minidump_extensions.h
+++ b/minidump/minidump_extensions.h
@@ -92,10 +92,18 @@
   //! \sa MemoryInfoListStream
   kMinidumpStreamTypeMemoryInfoList = MemoryInfoListStream,
 
+  //! \brief The last reserved minidump stream.
+  //!
+  //! \sa MemoryInfoListStream
+  kMinidumpStreamTypeLastReservedStream = LastReservedStream,
+
   // 0x4350 = "CP"
 
   //! \brief The stream type for MinidumpCrashpadInfo.
   kMinidumpStreamTypeCrashpadInfo = 0x43500001,
+
+  //! \brief The last reserved crashpad stream.
+  kMinidumpStreamTypeCrashpadLastReservedStream = 0x4350ffff,
 };
 
 //! \brief A variable-length UTF-8-encoded string carried within a minidump
diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn
index 3e75817..cbc853a 100644
--- a/snapshot/BUILD.gn
+++ b/snapshot/BUILD.gn
@@ -36,16 +36,17 @@
     "memory_snapshot.cc",
     "memory_snapshot.h",
     "memory_snapshot_generic.h",
+    "minidump/memory_snapshot_minidump.cc",
+    "minidump/memory_snapshot_minidump.h",
     "minidump/minidump_annotation_reader.cc",
     "minidump/minidump_annotation_reader.h",
     "minidump/minidump_simple_string_dictionary_reader.cc",
     "minidump/minidump_simple_string_dictionary_reader.h",
+    "minidump/minidump_stream.h",
     "minidump/minidump_string_list_reader.cc",
     "minidump/minidump_string_list_reader.h",
     "minidump/minidump_string_reader.cc",
     "minidump/minidump_string_reader.h",
-    "minidump/memory_snapshot_minidump.cc",
-    "minidump/memory_snapshot_minidump.h",
     "minidump/module_snapshot_minidump.cc",
     "minidump/module_snapshot_minidump.h",
     "minidump/process_snapshot_minidump.cc",
diff --git a/snapshot/minidump/minidump_stream.h b/snapshot/minidump/minidump_stream.h
new file mode 100644
index 0000000..1c75cb6
--- /dev/null
+++ b/snapshot/minidump/minidump_stream.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace crashpad {
+
+//! \brief Stores a minidump stream along with its stream ID.
+class MinidumpStream {
+ public:
+  MinidumpStream(uint32_t stream_type, std::vector<uint8_t> data)
+      : stream_type_(stream_type), data_(data){};
+
+  uint32_t stream_type() const { return stream_type_; }
+  const std::vector<uint8_t>& data() const { return data_; }
+
+ private:
+  uint32_t stream_type_;
+  std::vector<uint8_t> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(MinidumpStream);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_STREAM_H_
diff --git a/snapshot/minidump/process_snapshot_minidump.cc b/snapshot/minidump/process_snapshot_minidump.cc
index 5cc5ad8..620f5de 100644
--- a/snapshot/minidump/process_snapshot_minidump.cc
+++ b/snapshot/minidump/process_snapshot_minidump.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "minidump/minidump_extensions.h"
 #include "snapshot/memory_map_region_snapshot.h"
 #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h"
 #include "util/file/file_io.h"
@@ -45,7 +46,11 @@
       stream_directory_(),
       stream_map_(),
       modules_(),
+      threads_(),
       unloaded_modules_(),
+      mem_regions_(),
+      mem_regions_exposed_(),
+      custom_streams_(),
       crashpad_info_(),
       system_snapshot_(),
       arch_(CPUArchitecture::kCPUArchitectureUnknown),
@@ -109,7 +114,8 @@
       !InitializeModules() ||
       !InitializeSystemSnapshot() ||
       !InitializeMemoryInfo() ||
-      !InitializeThreads()) {
+      !InitializeThreads() ||
+      !InitializeCustomMinidumpStreams()) {
     return false;
   }
 
@@ -234,6 +240,18 @@
   return nullptr;
 }
 
+std::vector<const MinidumpStream*>
+ProcessSnapshotMinidump::CustomMinidumpStreams() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  std::vector<const MinidumpStream*> result;
+  result.reserve(custom_streams_.size());
+  for (const auto& custom_stream : custom_streams_) {
+    result.push_back(custom_stream.get());
+  }
+  return result;
+}
+
 bool ProcessSnapshotMinidump::InitializeCrashpadInfo() {
   const auto& stream_it = stream_map_.find(kMinidumpStreamTypeCrashpadInfo);
   if (stream_it == stream_map_.end()) {
@@ -529,4 +547,33 @@
   return true;
 }
 
+bool ProcessSnapshotMinidump::InitializeCustomMinidumpStreams() {
+  for (size_t i = 0; i < stream_directory_.size(); i++) {
+    const auto& stream = stream_directory_[i];
+
+    // Filter out reserved minidump and crashpad streams.
+    const uint32_t stream_type = stream.StreamType;
+    if (stream_type <=
+            MinidumpStreamType::kMinidumpStreamTypeLastReservedStream ||
+        (stream_type >= MinidumpStreamType::kMinidumpStreamTypeCrashpadInfo &&
+         stream_type <= MinidumpStreamType::
+                            kMinidumpStreamTypeCrashpadLastReservedStream)) {
+      continue;
+    }
+
+    std::vector<uint8_t> data(stream.Location.DataSize);
+    if (!file_reader_->SeekSet(stream.Location.Rva) ||
+        !file_reader_->ReadExactly(data.data(), data.size())) {
+      LOG(ERROR) << "Failed to read stream with ID 0x" << std::hex
+                 << stream_type << std::dec << " at index " << i;
+      return false;
+    }
+
+    custom_streams_.push_back(
+        std::make_unique<MinidumpStream>(stream_type, std::move(data)));
+  }
+
+  return true;
+}
+
 }  // namespace crashpad
diff --git a/snapshot/minidump/process_snapshot_minidump.h b/snapshot/minidump/process_snapshot_minidump.h
index 2a868a1..8653d33 100644
--- a/snapshot/minidump/process_snapshot_minidump.h
+++ b/snapshot/minidump/process_snapshot_minidump.h
@@ -29,6 +29,7 @@
 #include "minidump/minidump_extensions.h"
 #include "snapshot/exception_snapshot.h"
 #include "snapshot/memory_snapshot.h"
+#include "snapshot/minidump/minidump_stream.h"
 #include "snapshot/minidump/module_snapshot_minidump.h"
 #include "snapshot/minidump/system_snapshot_minidump.h"
 #include "snapshot/minidump/thread_snapshot_minidump.h"
@@ -83,6 +84,16 @@
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
   const ProcessMemory* Memory() const override;
 
+  //! \brief Returns a list of custom minidump streams. This routine is the
+  //!     equivalent of ModuleSnapshot::CustomMinidumpStreams(), except that in
+  //!     a minidump it is impossible to associate a custom stream to a specific
+  //!     module.
+  //!
+  //! \return The caller does not take ownership of the returned objects, they
+  //!     are scoped to the lifetime of the ProcessSnapshotMinidump object that
+  //!     they were obtained from.
+  std::vector<const MinidumpStream*> CustomMinidumpStreams() const;
+
  private:
   // Initializes data carried in a MinidumpCrashpadInfo stream on behalf of
   // Initialize().
@@ -115,6 +126,9 @@
   // Initialize().
   bool InitializeMiscInfo();
 
+  // Initializes custom minidump streams.
+  bool InitializeCustomMinidumpStreams();
+
   MINIDUMP_HEADER header_;
   std::vector<MINIDUMP_DIRECTORY> stream_directory_;
   std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
@@ -124,6 +138,7 @@
   std::vector<std::unique_ptr<internal::MemoryMapRegionSnapshotMinidump>>
       mem_regions_;
   std::vector<const MemoryMapRegionSnapshot*> mem_regions_exposed_;
+  std::vector<std::unique_ptr<MinidumpStream>> custom_streams_;
   MinidumpCrashpadInfo crashpad_info_;
   internal::SystemSnapshotMinidump system_snapshot_;
   CPUArchitecture arch_;
diff --git a/snapshot/minidump/process_snapshot_minidump_test.cc b/snapshot/minidump/process_snapshot_minidump_test.cc
index 4d74454..52b0992 100644
--- a/snapshot/minidump/process_snapshot_minidump_test.cc
+++ b/snapshot/minidump/process_snapshot_minidump_test.cc
@@ -1109,6 +1109,68 @@
   EXPECT_EQ(delegate.result, minidump_stack);
 }
 
+TEST(ProcessSnapshotMinidump, CustomMinidumpStreams) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  static const char kStreamReservedData[] = "A string";
+  static const char kStreamUnreservedData[] = "Another string";
+  // In the minidump reserved range
+  constexpr MinidumpStreamType kStreamTypeReserved1 =
+      (MinidumpStreamType)0x1111;
+  // In the crashpad reserved range
+  constexpr MinidumpStreamType kStreamTypeReserved2 =
+      (MinidumpStreamType)0x43501111;
+  constexpr MinidumpStreamType kStreamTypeUnreserved =
+      (MinidumpStreamType)0xffffffff;
+
+  MINIDUMP_DIRECTORY misc_directory = {};
+  RVA reserved1_offset = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(
+      string_file.Write(kStreamReservedData, sizeof(kStreamReservedData)));
+  RVA reserved2_offset = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(
+      string_file.Write(kStreamReservedData, sizeof(kStreamReservedData)));
+  RVA unreserved_offset = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(
+      string_file.Write(kStreamUnreservedData, sizeof(kStreamUnreservedData)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  misc_directory.StreamType = kStreamTypeReserved1;
+  misc_directory.Location.DataSize = sizeof(kStreamReservedData);
+  misc_directory.Location.Rva = reserved1_offset;
+  ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory)));
+
+  misc_directory.StreamType = kStreamTypeReserved2;
+  misc_directory.Location.DataSize = sizeof(kStreamReservedData);
+  misc_directory.Location.Rva = reserved2_offset;
+  ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory)));
+
+  misc_directory.StreamType = kStreamTypeUnreserved;
+  misc_directory.Location.DataSize = sizeof(kStreamUnreservedData);
+  misc_directory.Location.Rva = unreserved_offset;
+  ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 3;
+  ASSERT_TRUE(string_file.SeekSet(0));
+  ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(&string_file));
+
+  auto custom_streams = process_snapshot.CustomMinidumpStreams();
+  ASSERT_EQ(custom_streams.size(), 1U);
+  EXPECT_EQ(custom_streams[0]->stream_type(), (uint32_t)kStreamTypeUnreserved);
+
+  auto stream_data = custom_streams[0]->data();
+  EXPECT_EQ(stream_data.size(), sizeof(kStreamUnreservedData));
+  EXPECT_STREQ((char*)&stream_data.front(), kStreamUnreservedData);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp
index dd60b0c..ac7de29 100644
--- a/snapshot/snapshot.gyp
+++ b/snapshot/snapshot.gyp
@@ -111,6 +111,7 @@
         'minidump/minidump_annotation_reader.h',
         'minidump/minidump_simple_string_dictionary_reader.cc',
         'minidump/minidump_simple_string_dictionary_reader.h',
+        'minidump/minidump_stream.h',
         'minidump/minidump_string_list_reader.cc',
         'minidump/minidump_string_list_reader.h',
         'minidump/minidump_string_reader.cc',