Merge master bac601e785fc into doc
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9bfc20d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,59 @@
+# Copyright 2019 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.
+
+*.S text eol=lf
+*.asm text eol=lf
+*.c text eol=lf
+*.cc text eol=lf
+*.cmx text eol=lf
+*.css text eol=lf
+*.defs text eol=lf
+*.doxy text eol=lf
+*.gn text eol=lf
+*.gni text eol=lf
+*.go text eol=lf
+*.gyp text eol=lf
+*.gypi text eol=lf
+*.h text eol=lf
+*.m text eol=lf
+*.md text eol=lf
+*.mm text eol=lf
+*.pem text eol=lf
+*.plist text eol=lf
+*.proctype text eol=lf
+*.py text eol=lf
+*.sh text eol=lf
+*.sym text eol=lf
+*.txt text eol=lf
+*.yaml text eol=lf
+.clang-format text eol=lf
+.gitattributes text eol=lf
+.gitignore text eol=lf
+.vpython text eol=lf
+/AUTHORS text eol=lf
+/CONTRIBUTORS text eol=lf
+/LICENSE text eol=lf
+/codereview.settings text eol=lf
+APPLE_LICENSE text eol=lf
+COPYING.LIB text eol=lf
+DEPS text eol=lf
+README text eol=lf
+README.crashpad text eol=lf
+
+*.dat binary
+*.dll binary
+*.ico binary
+*.obj binary
+*.png binary
+*.so binary
diff --git a/.gitignore b/.gitignore
index 982a95e..fe8a94c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,17 @@
+# Copyright 2014 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.
+
 *.Makefile
 *.ninja
 *.pyc
@@ -10,6 +24,7 @@
 .gdbinit
 /Makefile
 /out
+/third_party/edo/edo
 /third_party/fuchsia/.cipd
 /third_party/fuchsia/clang
 /third_party/fuchsia/qemu
@@ -19,6 +34,7 @@
 /third_party/linux/.cipd
 /third_party/linux/clang
 /third_party/linux/sysroot
+/third_party/lss/lss
 /third_party/gyp/gyp
 /third_party/mini_chromium/mini_chromium
 /third_party/zlib/zlib
diff --git a/.style.yapf b/.style.yapf
new file mode 100644
index 0000000..8aaf9b6
--- /dev/null
+++ b/.style.yapf
@@ -0,0 +1,16 @@
+# Copyright 2020 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.
+
+[style]
+based_on_style = google
diff --git a/.vpython b/.vpython
new file mode 100644
index 0000000..c2001fc
--- /dev/null
+++ b/.vpython
@@ -0,0 +1,32 @@
+# 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.
+
+# This is a vpython "spec" file.
+#
+# It describes patterns for python wheel dependencies of the python scripts.
+#
+# Read more about `vpython` and how to modify this file here:
+#   https://chromium.googlesource.com/infra/infra/+/master/doc/users/vpython.md
+
+# This is needed for snapshot/win/end_to_end_test.py.
+wheel: <
+  name: "infra/python/wheels/pypiwin32/${vpython_platform}"
+  version: "version:219"
+  match_tag: <
+    platform: "win32"
+  >
+  match_tag: <
+    platform: "win_amd64"
+  >
+>
diff --git a/BUILD.gn b/BUILD.gn
index 065a5e1..98218af 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -14,29 +14,71 @@
 
 import("build/crashpad_buildconfig.gni")
 import("build/test.gni")
+import("util/net/tls.gni")
 
 config("crashpad_config") {
   include_dirs = [ "." ]
 }
 
+# TODO(fuchsia:46805): Remove this once instances of UB have been cleaned up.
+config("disable_ubsan") {
+  if (crashpad_is_in_fuchsia) {
+    cflags = [ "-fno-sanitize=undefined" ]
+  }
+  visibility = [
+    "snapshot:snapshot",
+    "minidump:minidump_test",
+    "third_party/gtest:gtest",
+    "util:util",
+  ]
+}
+
 if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
   test("crashpad_tests") {
     deps = [
       "client:client_test",
-      "handler:handler_test",
       "minidump:minidump_test",
-      "snapshot:snapshot_test",
       "test:gmock_main",
       "test:test_test",
-      "util:util_test",
     ]
+    if (!crashpad_is_ios) {
+      deps += [ "snapshot:snapshot_test" ]
+    }
+    if (!crashpad_is_ios && !crashpad_is_fuchsia) {
+      deps += [ "handler:handler_test" ]
+    }
+    if (crashpad_is_in_fuchsia) {
+      # TODO(fuchsia:46559): Fix the leaks and remove this.
+      deps += [ "//build/config/sanitizers:suppress-lsan.DO-NOT-USE-THIS" ]
+    }
+    if (crashpad_is_android) {
+      use_raw_android_executable = true
+
+      copy("crashpad_test_data") {
+        testonly = true
+        sources = [
+          "test/test_paths_test_data_root.txt",
+          "util/net/testdata/ascii_http_body.txt",
+          "util/net/testdata/binary_http_body.dat",
+        ]
+
+        outputs = [ "$root_out_dir/crashpad_test_data/{{source}}" ]
+      }
+
+      deps += [ ":crashpad_test_data" ]
+
+      extra_dist_files = [
+        "$root_out_dir/crashpad_handler",
+        "$root_out_dir/crashpad_test_test_multiprocess_exec_test_child",
+        "$root_out_dir/crashpad_test_data",
+      ]
+    }
   }
 
   if (crashpad_is_in_fuchsia) {
     import("//build/package.gni")
     package("crashpad_test") {
       testonly = true
-      deprecated_system_image = true
 
       deps = [
         ":crashpad_tests",
@@ -45,7 +87,6 @@
         "snapshot:crashpad_snapshot_test_module_large",
         "snapshot:crashpad_snapshot_test_module_small",
         "test:crashpad_test_test_multiprocess_exec_test_child",
-        "util:generate_test_server_key",
         "util:http_transport_test_server",
       ]
 
@@ -53,24 +94,23 @@
         {
           name = "crashpad_tests"
         },
+      ]
+
+      meta = [
+        {
+          path = "test/fuchsia_crashpad_tests.cmx"
+          dest = "crashpad_tests.cmx"
+        },
+      ]
+
+      binaries = [
         {
           name = "crashpad_test_test_multiprocess_exec_test_child"
-          dest = "crashpad_test_data/crashpad_test_test_multiprocess_exec_test_child"
+          dest = "crashpad_test_test_multiprocess_exec_test_child"
         },
         {
           name = "http_transport_test_server"
-          dest = "crashpad_test_data/http_transport_test_server"
-        },
-
-        # These aren't actually tests, but that seems to be the only way to
-        # convince package() to get them from the output directory.
-        {
-          name = "crashpad_util_test_cert.pem"
-          dest = "crashpad_test_data/crashpad_util_test_cert.pem"
-        },
-        {
-          name = "crashpad_util_test_key.pem"
-          dest = "crashpad_test_data/crashpad_util_test_key.pem"
+          dest = "http_transport_test_server"
         },
       ]
 
@@ -92,43 +132,39 @@
       resources = [
         {
           path = "util/net/testdata/ascii_http_body.txt"
-          dest = "crashpad_test_data/util/net/testdata/ascii_http_body.txt"
+          dest = "util/net/testdata/ascii_http_body.txt"
         },
         {
           path = "util/net/testdata/binary_http_body.dat"
-          dest = "crashpad_test_data/util/net/testdata/binary_http_body.dat"
+          dest = "util/net/testdata/binary_http_body.dat"
         },
         {
           path = "test/test_paths_test_data_root.txt"
-          dest = "crashpad_test_data/test/test_paths_test_data_root.txt"
+          dest = "test/test_paths_test_data_root.txt"
         },
       ]
-    }
 
-    package("crashpad_handler") {
-      deprecated_system_image = true
-
-      deps = [
-        "handler:crashpad_handler",
-      ]
-
-      binaries = [
-        {
-          name = "crashpad_handler"
-        },
-      ]
+      if (crashpad_use_boringssl_for_http_transport_socket) {
+        resources += [
+          {
+            path = "util/net/testdata/crashpad_util_test_cert.pem"
+            dest = "util/net/testdata/crashpad_util_test_cert.pem"
+          },
+          {
+            path = "util/net/testdata/crashpad_util_test_key.pem"
+            dest = "util/net/testdata/crashpad_util_test_key.pem"
+          },
+        ]
+      }
     }
 
     package("crashpad_database_util") {
-      deprecated_system_image = true
-
-      deps = [
-        "tools:crashpad_database_util",
-      ]
+      deps = [ "tools:crashpad_database_util" ]
 
       binaries = [
         {
           name = "crashpad_database_util"
+          shell = true
         },
       ]
     }
@@ -146,6 +182,9 @@
       "handler:handler_test",
       "test:gtest_main",
     ]
+    if (crashpad_is_ios || crashpad_is_fuchsia) {
+      deps -= [ "handler:handler_test" ]
+    }
   }
 
   test("crashpad_minidump_test") {
@@ -160,6 +199,9 @@
       "snapshot:snapshot_test",
       "test:gtest_main",
     ]
+    if (crashpad_is_ios) {
+      deps -= [ "snapshot:snapshot_test" ]
+    }
   }
 
   test("crashpad_test_test") {
@@ -176,3 +218,10 @@
     ]
   }
 }
+
+if (crashpad_is_ios) {
+  group("ios_xcuitests") {
+    testonly = true
+    deps = [ "test/ios:all_tests" ]
+  }
+}
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 2080cc4..8724b7f 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -12,3 +12,4 @@
 Mark Mentovai <mark@chromium.org>
 Robert Sesek <rsesek@chromium.org>
 Scott Graham <scottmg@chromium.org>
+Joshua Peraza <jperaza@chromium.org>
diff --git a/DEPS b/DEPS
index 34f1ef6..81b9583 100644
--- a/DEPS
+++ b/DEPS
@@ -15,22 +15,34 @@
 vars = {
   'chromium_git': 'https://chromium.googlesource.com',
   'pull_linux_clang': False,
-  'pull_win_toolchain': False
+  'pull_win_toolchain': False,
+  # Controls whether crashpad/build/ios/setup-ios-gn.py is run as part of
+  # gclient hooks. It is enabled by default for developer's convenience. It can
+  # be disabled with custom_vars (done automatically on the bots).
+  'run_setup_ios_gn': True,
 }
 
 deps = {
   'buildtools':
-      Var('chromium_git') + '/chromium/buildtools.git@' +
-      '6fe4a3251488f7af86d64fc25cf442e817cf6133',
+      Var('chromium_git') + '/chromium/src/buildtools.git@' +
+      '4164a305626786b1912d467003acf4c4995bec7d',
+  'crashpad/third_party/edo/edo': {
+      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' +
+      '243fc89ae95b24717d41f3786f6a9abeeef87c92',
+      'condition': 'checkout_ios',
+  },
   'crashpad/third_party/gtest/gtest':
       Var('chromium_git') + '/external/github.com/google/googletest@' +
-      'c091b0469ab4c04ee9411ef770f32360945f4c53',
+      'e3f0319d89f4cbf32993de595d984183b1a9fc57',
   'crashpad/third_party/gyp/gyp':
       Var('chromium_git') + '/external/gyp@' +
-      '5e2b3ddde7cda5eb6bc09a5546a76b00e49d888f',
+      '8bee09f4a57807136593ddc906b0b213c21f9014',
+  'crashpad/third_party/lss/lss':
+      Var('chromium_git') + '/linux-syscall-support.git@' +
+      '7bde79cc274d06451bf65ae82c012a5d3e476b5a',
   'crashpad/third_party/mini_chromium/mini_chromium':
       Var('chromium_git') + '/chromium/mini_chromium@' +
-      '793e94e2c652831af2d25bb5288b04e59048c62d',
+      '8ca5ea356cdb97913d62d379d503567a80d90726',
   'crashpad/third_party/libfuzzer/src':
       Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
       'fda403cf93ecb8792cb1d061564d89a6553ca020',
@@ -49,7 +61,7 @@
     'condition': 'checkout_linux and pull_linux_clang',
     'dep_type': 'cipd'
   },
-  'crashpad/third_party/linux/clang/mac-amd64': {
+  'crashpad/third_party/fuchsia/clang/mac-amd64': {
     'packages': [
       {
         'package': 'fuchsia/clang/mac-amd64',
@@ -89,22 +101,24 @@
     'condition': 'checkout_fuchsia and host_os == "linux"',
     'dep_type': 'cipd'
   },
-  'crashpad/third_party/fuchsia/sdk/linux-amd64': {
-    # The SDK is keyed to the host system because it contains build tools.
-    # Currently, linux-amd64 is the only SDK published (see
-    # https://chrome-infra-packages.appspot.com/#/?path=fuchsia/sdk).
-    # As long as this is the case, use that SDK package
-    # even on other build hosts.
-    # The sysroot (containing headers and libraries) and other components are
-    # related to the target and should be functional with an appropriate
-    # toolchain that runs on the build host (fuchsia_clang, above).
+  'crashpad/third_party/fuchsia/sdk/mac-amd64': {
     'packages': [
       {
-        'package': 'fuchsia/sdk/linux-amd64',
+        'package': 'fuchsia/sdk/gn/mac-amd64',
         'version': 'latest'
       },
     ],
-    'condition': 'checkout_fuchsia',
+    'condition': 'checkout_fuchsia and host_os == "mac"',
+    'dep_type': 'cipd'
+  },
+  'crashpad/third_party/fuchsia/sdk/linux-amd64': {
+    'packages': [
+      {
+        'package': 'fuchsia/sdk/gn/linux-amd64',
+        'version': 'latest'
+      },
+    ],
+    'condition': 'checkout_fuchsia and host_os == "linux"',
     'dep_type': 'cipd'
   },
   'crashpad/third_party/win/toolchain': {
@@ -164,45 +178,6 @@
     ],
   },
   {
-    'name': 'gn_mac',
-    'pattern': '.',
-    'condition': 'host_os == "mac"',
-    'action': [
-      'download_from_google_storage',
-      '--no_resume',
-      '--no_auth',
-      '--bucket=chromium-gn',
-      '--sha1_file',
-      'buildtools/mac/gn.sha1',
-    ],
-  },
-  {
-    'name': 'gn_linux',
-    'pattern': '.',
-    'condition': 'host_os == "linux"',
-    'action': [
-      'download_from_google_storage',
-      '--no_resume',
-      '--no_auth',
-      '--bucket=chromium-gn',
-      '--sha1_file',
-      'buildtools/linux64/gn.sha1',
-    ],
-  },
-  {
-    'name': 'gn_win',
-    'pattern': '.',
-    'condition': 'host_os == "win"',
-    'action': [
-      'download_from_google_storage',
-      '--no_resume',
-      '--no_auth',
-      '--bucket=chromium-gn',
-      '--sha1_file',
-      'buildtools/win/gn.exe.sha1',
-    ],
-  },
-  {
     # If using a local clang ("pull_linux_clang" above), also pull down a
     # sysroot.
     'name': 'sysroot_linux',
@@ -213,9 +188,13 @@
     ],
   },
   {
-    'name': 'gyp',
-    'pattern': '\.gypi?$',
-    'action': ['python', 'crashpad/build/gyp_crashpad.py'],
+    'name': 'setup_gn_ios',
+    'pattern': '.',
+    'condition': 'run_setup_ios_gn and checkout_ios',
+    'action': [
+        'python',
+        'crashpad/build/ios/setup_ios_gn.py'
+    ],
   },
 ]
 
diff --git a/README.md b/README.md
index d74c1bd..b20777b 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@
 
  * Bugs can be reported at the [Crashpad issue
    tracker](https://crashpad.chromium.org/bug/).
- * The [Crashpad Buildbots](https://build.chromium.org/p/client.crashpad)
+ * The [Crashpad bots](https://ci.chromium.org/p/crashpad/g/main/console)
    perform automated builds and tests.
  * [crashpad-dev](https://groups.google.com/a/chromium.org/group/crashpad-dev)
    is the Crashpad developers’ mailing list.
diff --git a/build/BUILD.gn b/build/BUILD.gn
index 0ef1127..6e6ccd6 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -32,9 +32,7 @@
 
 group("default_exe_manifest_win") {
   if (crashpad_is_in_chromium) {
-    deps = [
-      "//build/win:default_exe_manifest",
-    ]
+    deps = [ "//build/win:default_exe_manifest" ]
   }
 }
 
@@ -45,7 +43,26 @@
     "-fsanitize=fuzzer",
   ]
 
-  ldflags = [
-    "-fsanitize=address",
-  ]
+  ldflags = [ "-fsanitize=address" ]
+}
+
+if (crashpad_is_ios) {
+  group("ios_enable_arc") {
+    if (crashpad_is_in_chromium) {
+      public_configs = [ "//build/config/compiler:enable_arc" ]
+    } else if (crashpad_is_standalone) {
+      public_configs =
+          [ "//third_party/mini_chromium/mini_chromium/build:ios_enable_arc" ]
+    }
+  }
+
+  group("ios_xctest") {
+    if (crashpad_is_in_chromium) {
+      public_configs = [ "//build/config/ios:xctest_config" ]
+    } else if (crashpad_is_standalone) {
+      public_configs = [
+        "//third_party/mini_chromium/mini_chromium/build/ios:xctest_config",
+      ]
+    }
+  }
 }
diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn
index 95dc277..bc53108 100644
--- a/build/BUILDCONFIG.gn
+++ b/build/BUILDCONFIG.gn
@@ -55,6 +55,7 @@
 _default_configs = [
   "//third_party/mini_chromium/mini_chromium/build:default",
   "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors",
+  "//third_party/mini_chromium/mini_chromium/build:Wimplicit_fallthrough",
 ]
 
 if (crashpad_use_libfuzzer) {
diff --git a/build/crashpad.gypi b/build/crashpad.gypi
index 166e768..2accb5a 100644
--- a/build/crashpad.gypi
+++ b/build/crashpad.gypi
@@ -35,6 +35,11 @@
           }],
         ],
       }],
+      ['OS=="android"', {
+        'ldflags': [
+          '-static-libstdc++',
+        ],
+      }],
     ],
   },
 }
diff --git a/build/crashpad_buildconfig.gni b/build/crashpad_buildconfig.gni
index f767027..7540fb2 100644
--- a/build/crashpad_buildconfig.gni
+++ b/build/crashpad_buildconfig.gni
@@ -15,28 +15,28 @@
 declare_args() {
   # Determines various flavors of build configuration, and which concrete
   # targets to use for dependencies. Valid values are "standalone", "chromium",
-  # and "fuchsia".
+  # "fuchsia", "dart" or "external".
   crashpad_dependencies = "standalone"
 
   if (defined(is_fuchsia_tree) && is_fuchsia_tree) {
-    # Determines various flavors of build configuration, and which concrete
-    # targets to use for dependencies. Valid values are "standalone",
-    # "chromium", and "fuchsia". Defaulted to "fuchsia" because
-    # "is_fuchsia_tree" is set.
     crashpad_dependencies = "fuchsia"
   }
 }
 
 assert(
     crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" ||
-    crashpad_dependencies == "standalone")
+    crashpad_dependencies == "standalone" ||
+    crashpad_dependencies == "external" || crashpad_dependencies == "dart")
 
 crashpad_is_in_chromium = crashpad_dependencies == "chromium"
 crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia"
+crashpad_is_in_dart = crashpad_dependencies == "dart"
+crashpad_is_external = crashpad_dependencies == "external"
 crashpad_is_standalone = crashpad_dependencies == "standalone"
 
 if (crashpad_is_in_chromium) {
   crashpad_is_mac = is_mac
+  crashpad_is_ios = is_ios
   crashpad_is_win = is_win
   crashpad_is_linux = is_linux
   crashpad_is_android = is_android
@@ -46,11 +46,18 @@
 
   crashpad_is_clang = is_clang
 } else {
-  # Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into
-  # the same location in both cases.
-  import("../third_party/mini_chromium/mini_chromium/build/compiler.gni")
-  import("../third_party/mini_chromium/mini_chromium/build/platform.gni")
+  # External and Dart SDK builds assume crashpad and mini_chromium are peers.
+  if (crashpad_is_external || crashpad_is_in_dart) {
+    import("../../../mini_chromium/mini_chromium/build/compiler.gni")
+    import("../../../mini_chromium/mini_chromium/build/platform.gni")
+  } else {
+    # Both standalone and in Fuchsia tree use mini_chromium, and it's mapped into
+    # the same location in both cases.
+    import("../third_party/mini_chromium/mini_chromium/build/compiler.gni")
+    import("../third_party/mini_chromium/mini_chromium/build/platform.gni")
+  }
   crashpad_is_mac = mini_chromium_is_mac
+  crashpad_is_ios = mini_chromium_is_ios
   crashpad_is_win = mini_chromium_is_win
   crashpad_is_linux = mini_chromium_is_linux
   crashpad_is_android = mini_chromium_is_android
@@ -63,7 +70,12 @@
 
 template("crashpad_executable") {
   executable(target_name) {
-    forward_variables_from(invoker, "*", [ "configs", "remove_configs" ])
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "configs",
+                             "remove_configs",
+                           ])
     if (defined(invoker.remove_configs)) {
       configs -= invoker.remove_configs
     }
@@ -72,21 +84,23 @@
       configs += invoker.configs
     }
 
-    if (!defined(deps)) {
-      deps = []
-    }
-
-    if (crashpad_is_in_chromium) {
-      deps += [ "//build/config:exe_and_shlib_deps" ]
-    } else if (crashpad_is_in_fuchsia) {
-      configs += [ "//build/config/fuchsia:fdio_config" ]
+    if (crashpad_is_in_fuchsia) {
+      fdio_config = [ "//build/config/fuchsia:fdio_config" ]
+      if (configs + fdio_config - fdio_config == configs) {
+        configs += fdio_config
+      }
     }
   }
 }
 
 template("crashpad_loadable_module") {
   loadable_module(target_name) {
-    forward_variables_from(invoker, "*", [ "configs", "remove_configs" ])
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "configs",
+                             "remove_configs",
+                           ])
     if (defined(invoker.remove_configs)) {
       configs -= invoker.remove_configs
     }
@@ -95,13 +109,7 @@
       configs += invoker.configs
     }
 
-    if (!defined(deps)) {
-      deps = []
-    }
-
-    if (crashpad_is_in_chromium) {
-      deps += [ "//build/config:exe_and_shlib_deps" ]
-    } else if (crashpad_is_in_fuchsia) {
+    if (crashpad_is_in_fuchsia) {
       configs += [ "//build/config/fuchsia:fdio_config" ]
     }
   }
diff --git a/build/crashpad_dependencies.gni b/build/crashpad_dependencies.gni
deleted file mode 100644
index 1964fac..0000000
--- a/build/crashpad_dependencies.gni
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2017 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.
-
-declare_args() {
-  # Determines various flavors of build configuration, and which concrete
-  # targets to use for dependencies. Valid values are "standalone", "chromium",
-  # and "fuchsia".
-  crashpad_dependencies = "standalone"
-}
-
-assert(
-    crashpad_dependencies == "chromium" || crashpad_dependencies == "fuchsia" ||
-    crashpad_dependencies == "standalone")
-
-crashpad_is_in_chromium = crashpad_dependencies == "chromium"
-crashpad_is_in_fuchsia = crashpad_dependencies == "fuchsia"
-crashpad_is_standalone = crashpad_dependencies == "standalone"
-
-if (crashpad_is_in_chromium) {
-  crashpad_is_posix = is_posix
-} else if (crashpad_is_in_fuchsia) {
-  import("//third_party/mini_chromium/build/is_posix.gni")
-  crashpad_is_posix = mini_chromium_is_posix
-} else if (crashpad_is_standalone) {
-  import("../third_party/mini_chromium/mini_chromium/build/is_posix.gni")
-  crashpad_is_posix = mini_chromium_is_posix
-}
-
-if (crashpad_is_in_chromium) {
-  import("//testing/test.gni")
-} else {
-  template("test") {
-    executable(target_name) {
-      testonly = true
-      forward_variables_from(invoker, "*")
-    }
-  }
-
-  set_defaults("test") {
-    configs = default_executable_configs
-  }
-}
diff --git a/build/crashpad_fuzzer_test.gni b/build/crashpad_fuzzer_test.gni
index 8e0eaa9..cc709b0 100644
--- a/build/crashpad_fuzzer_test.gni
+++ b/build/crashpad_fuzzer_test.gni
@@ -14,8 +14,11 @@
 
 import("crashpad_buildconfig.gni")
 import("test.gni")
+if (crashpad_is_in_chromium) {
+  import("//testing/libfuzzer/fuzzer_test.gni")
+}
 
-template("fuzzer_test") {
+template("crashpad_fuzzer_test") {
   if (crashpad_is_standalone && crashpad_use_libfuzzer) {
     test(target_name) {
       forward_variables_from(invoker,
@@ -38,6 +41,15 @@
       }
       cflags += [ "-fsanitize=fuzzer" ]
     }
+    if (defined(invoker.seed_corpus)) {
+      not_needed(invoker, [ "seed_corpus" ])
+    }
+  } else if (crashpad_is_in_chromium && use_fuzzing_engine) {
+    # Append "crashpad_" to the beginning of the fuzzer's name to make it easier
+    # in Chromium to recognize where fuzzer came from.
+    forward_variables_from(invoker, "*")
+    fuzzer_test("crashpad_" + target_name) {
+    }
   } else {
     not_needed(invoker, "*")
     group(target_name) {
diff --git a/build/gyp_crashpad.py b/build/gyp_crashpad.py
index 8485660..41ab4b9 100755
--- a/build/gyp_crashpad.py
+++ b/build/gyp_crashpad.py
@@ -19,83 +19,84 @@
 
 
 def ChooseDependencyPath(local_path, external_path):
-  """Chooses between a dependency located at local path and an external path.
+    """Chooses between a dependency located at local path and an external path.
 
-  The local path, used in standalone builds, is preferred. If it is not present
-  but the external path is, the external path will be used. If neither path is
-  present, the local path will be used, so that error messages uniformly refer
-  to the local path.
+    The local path, used in standalone builds, is preferred. If it is not
+    present but the external path is, the external path will be used. If neither
+    path is present, the local path will be used, so that error messages
+    uniformly refer to the local path.
 
-  Args:
-    local_path: The preferred local path to use for a standalone build.
-    external_path: The external path to fall back to.
+    Args:
+        local_path: The preferred local path to use for a standalone build.
+        external_path: The external path to fall back to.
 
-  Returns:
-    A 2-tuple. The first element is None or 'external', depending on whether
-    local_path or external_path was chosen. The second element is the chosen
-    path.
-  """
-  if os.path.exists(local_path) or not os.path.exists(external_path):
-    return (None, local_path)
-  return ('external', external_path)
+    Returns:
+        A 2-tuple. The first element is None or 'external', depending on whether
+        local_path or external_path was chosen. The second element is the chosen
+        path.
+    """
+    if os.path.exists(local_path) or not os.path.exists(external_path):
+        return (None, local_path)
+    return ('external', external_path)
 
 
 script_dir = os.path.dirname(__file__)
-crashpad_dir = (os.path.dirname(script_dir) if script_dir not in ('', os.curdir)
-                else os.pardir)
+crashpad_dir = (os.path.dirname(script_dir)
+                if script_dir not in ('', os.curdir) else os.pardir)
 
-sys.path.insert(0,
-    ChooseDependencyPath(os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp',
-                                      'pylib'),
-                         os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp',
-                                      'pylib'))[1])
+sys.path.insert(
+    0,
+    ChooseDependencyPath(
+        os.path.join(crashpad_dir, 'third_party', 'gyp', 'gyp', 'pylib'),
+        os.path.join(crashpad_dir, os.pardir, os.pardir, 'gyp', 'pylib'))[1])
 
 import gyp
 
 
 def main(args):
-  if 'GYP_GENERATORS' not in os.environ:
-    os.environ['GYP_GENERATORS'] = 'ninja'
+    if 'GYP_GENERATORS' not in os.environ:
+        os.environ['GYP_GENERATORS'] = 'ninja'
 
-  crashpad_dir_or_dot = crashpad_dir if crashpad_dir is not '' else os.curdir
+    crashpad_dir_or_dot = crashpad_dir if crashpad_dir is not '' else os.curdir
 
-  (dependencies, mini_chromium_common_gypi) = (ChooseDependencyPath(
-      os.path.join(crashpad_dir, 'third_party', 'mini_chromium',
-                   'mini_chromium', 'build', 'common.gypi'),
-      os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium',
-                   'mini_chromium', 'build', 'common.gypi')))
-  if dependencies is not None:
-    args.extend(['-D', 'crashpad_dependencies=%s' % dependencies])
-  args.extend(['--include', mini_chromium_common_gypi])
-  args.extend(['--depth', crashpad_dir_or_dot])
-  args.append(os.path.join(crashpad_dir, 'crashpad.gyp'))
+    (dependencies, mini_chromium_common_gypi) = (ChooseDependencyPath(
+        os.path.join(crashpad_dir, 'third_party', 'mini_chromium',
+                     'mini_chromium', 'build', 'common.gypi'),
+        os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium',
+                     'mini_chromium', 'build', 'common.gypi')))
+    if dependencies is not None:
+        args.extend(['-D', 'crashpad_dependencies=%s' % dependencies])
+    args.extend(['--include', mini_chromium_common_gypi])
+    args.extend(['--depth', crashpad_dir_or_dot])
+    args.append(os.path.join(crashpad_dir, 'crashpad.gyp'))
 
-  result = gyp.main(args)
-  if result != 0:
-    return result
-
-  if sys.platform == 'win32':
-    # Check to make sure that no target_arch was specified. target_arch may be
-    # set during a cross build, such as a cross build for Android.
-    has_target_arch = False
-    for arg_index in range(0, len(args)):
-      arg = args[arg_index]
-      if (arg.startswith('-Dtarget_arch=') or
-          (arg == '-D' and arg_index + 1 < len(args) and
-           args[arg_index + 1].startswith('target_arch='))):
-        has_target_arch = True
-        break
-
-    if not has_target_arch:
-      # Also generate the x86 build.
-      result = gyp.main(args + ['-D', 'target_arch=ia32', '-G', 'config=Debug'])
-      if result != 0:
+    result = gyp.main(args)
+    if result != 0:
         return result
-      result = gyp.main(
-          args + ['-D', 'target_arch=ia32', '-G', 'config=Release'])
 
-  return result
+    if sys.platform == 'win32':
+        # Check to make sure that no target_arch was specified. target_arch may
+        # be set during a cross build, such as a cross build for Android.
+        has_target_arch = False
+        for arg_index in range(0, len(args)):
+            arg = args[arg_index]
+            if (arg.startswith('-Dtarget_arch=') or
+                (arg == '-D' and arg_index + 1 < len(args) and
+                 args[arg_index + 1].startswith('target_arch='))):
+                has_target_arch = True
+                break
+
+        if not has_target_arch:
+            # Also generate the x86 build.
+            result = gyp.main(args +
+                              ['-D', 'target_arch=ia32', '-G', 'config=Debug'])
+            if result != 0:
+                return result
+            result = gyp.main(
+                args + ['-D', 'target_arch=ia32', '-G', 'config=Release'])
+
+    return result
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/build/gyp_crashpad_android.py b/build/gyp_crashpad_android.py
index f78c05b..327fa21 100755
--- a/build/gyp_crashpad_android.py
+++ b/build/gyp_crashpad_android.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# coding: utf-8
 
 # Copyright 2017 The Crashpad Authors. All rights reserved.
 #
@@ -25,89 +24,46 @@
 
 
 def main(args):
-  parser = argparse.ArgumentParser(
-      description='Set up an Android cross build',
-      epilog='Additional arguments will be passed to gyp_crashpad.py.')
-  parser.add_argument('--ndk', required=True, help='Standalone NDK toolchain')
-  parser.add_argument('--compiler',
-                      default='clang',
-                      choices=('clang', 'gcc'),
-                      help='The compiler to use, clang by default')
-  (parsed, extra_command_line_args) = parser.parse_known_args(args)
+    parser = argparse.ArgumentParser(
+        description='Set up an Android cross build',
+        epilog='Additional arguments will be passed to gyp_crashpad.py.')
+    parser.add_argument('--arch', required=True, help='Target architecture')
+    parser.add_argument('--api-level', required=True, help='Target API level')
+    parser.add_argument('--ndk', required=True, help='Standalone NDK toolchain')
+    (parsed, extra_command_line_args) = parser.parse_known_args(args)
 
-  NDK_ERROR=(
-      'NDK must be a valid standalone NDK toolchain.\n' +
-      'See https://developer.android.com/ndk/guides/standalone_toolchain.html')
-  arch_dirs = glob.glob(os.path.join(parsed.ndk, '*-linux-android*'))
-  if len(arch_dirs) != 1:
-    parser.error(NDK_ERROR)
+    ndk_bin_dir = os.path.join(parsed.ndk, 'toolchains', 'llvm', 'prebuilt',
+                               'linux-x86_64', 'bin')
+    if not os.path.exists(ndk_bin_dir):
+        parser.error("missing toolchain")
 
-  arch_triplet = os.path.basename(arch_dirs[0])
-  ARCH_TRIPLET_TO_ARCH = {
-    'arm-linux-androideabi': 'arm',
-    'aarch64-linux-android': 'arm64',
-    'i686-linux-android': 'ia32',
-    'x86_64-linux-android': 'x64',
-    'mipsel-linux-android': 'mips',
-    'mips64el-linux-android': 'mips64',
-  }
-  if arch_triplet not in ARCH_TRIPLET_TO_ARCH:
-    parser.error(NDK_ERROR)
-  arch = ARCH_TRIPLET_TO_ARCH[arch_triplet]
+    ARCH_TO_ARCH_TRIPLET = {
+        'arm': 'armv7a-linux-androideabi',
+        'arm64': 'aarch64-linux-android',
+        'ia32': 'i686-linux-android',
+        'x64': 'x86_64-linux-android',
+    }
 
-  ndk_bin_dir = os.path.join(parsed.ndk, 'bin')
-
-  clang_path = os.path.join(ndk_bin_dir, 'clang')
-  extra_args = []
-
-  if parsed.compiler == 'clang':
-    os.environ['CC_target'] = clang_path
-    os.environ['CXX_target'] = os.path.join(ndk_bin_dir, 'clang++')
-  elif parsed.compiler == 'gcc':
-    os.environ['CC_target'] = os.path.join(ndk_bin_dir,
-                                           '%s-gcc' % arch_triplet)
+    clang_prefix = ARCH_TO_ARCH_TRIPLET[parsed.arch] + parsed.api_level
+    os.environ['CC_target'] = os.path.join(ndk_bin_dir, clang_prefix + '-clang')
     os.environ['CXX_target'] = os.path.join(ndk_bin_dir,
-                                            '%s-g++' % arch_triplet)
+                                            clang_prefix + '-clang++')
 
-  # Unlike the Clang build, when using GCC with unified headers, __ANDROID_API__
-  # isn’t set automatically and must be pushed in to the build. Fish the correct
-  # value out of the Clang wrapper script. If deprecated headers are in use, the
-  # Clang wrapper won’t mention __ANDROID_API__, but the standalone toolchain’s
-  # <android/api-level.h> will #define it for both Clang and GCC.
-  #
-  # android_api_level is extracted in this manner even when compiling with Clang
-  # so that it’s available for use in GYP conditions that need to test the API
-  # level, but beware that it’ll only be available when unified headers are in
-  # use.
-  #
-  # Unified headers are the way of the future, according to
-  # https://android.googlesource.com/platform/ndk/+/ndk-r14/CHANGELOG.md and
-  # https://android.googlesource.com/platform/ndk/+/master/docs/UnifiedHeaders.md.
-  # Traditional (deprecated) headers have been removed entirely as of NDK r16.
-  # https://android.googlesource.com/platform/ndk/+/ndk-release-r16/CHANGELOG.md.
-  with open(clang_path, 'r') as file:
-    clang_script_contents = file.read()
-  matches = re.finditer(r'\s-D__ANDROID_API__=([\d]+)\s',
-                        clang_script_contents)
-  match = next(matches, None)
-  if match:
-    android_api = int(match.group(1))
-    extra_args.extend(['-D', 'android_api_level=%d' % android_api])
-    if next(matches, None):
-      raise AssertionError('__ANDROID_API__ defined too many times')
+    extra_args = ['-D', 'android_api_level=' + parsed.api_level]
 
-  for tool in ('ar', 'nm', 'readelf'):
-    os.environ['%s_target' % tool.upper()] = (
-        os.path.join(ndk_bin_dir, '%s-%s' % (arch_triplet, tool)))
+    # ARM only includes 'v7a' in the tool prefix for clang
+    tool_prefix = ('arm-linux-androideabi' if parsed.arch == 'arm' else
+                   ARCH_TO_ARCH_TRIPLET[parsed.arch])
 
-  return gyp_crashpad.main(
-      ['-D', 'OS=android',
-       '-D', 'target_arch=%s' % arch,
-       '-D', 'clang=%d' % (1 if parsed.compiler == 'clang' else 0),
-       '-f', 'ninja-android'] +
-      extra_args +
-      extra_command_line_args)
+    for tool in ('ar', 'nm', 'readelf'):
+        os.environ['%s_target' % tool.upper()] = (os.path.join(
+            ndk_bin_dir, '%s-%s' % (tool_prefix, tool)))
+
+    return gyp_crashpad.main([
+        '-D', 'OS=android', '-D',
+        'target_arch=%s' % parsed.arch, '-D', 'clang=1', '-f', 'ninja-android'
+    ] + extra_args + extra_command_line_args)
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/build/install_linux_sysroot.py b/build/install_linux_sysroot.py
index afa8815..97f2c14 100755
--- a/build/install_linux_sysroot.py
+++ b/build/install_linux_sysroot.py
@@ -23,7 +23,6 @@
 import sys
 import urllib2
 
-
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 
 # Sysroot revision from:
@@ -33,42 +32,43 @@
 REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66'
 FILENAME = 'debian_stretch_amd64_sysroot.tar.xz'
 
+
 def main():
-  url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME)
+    url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME)
 
-  sysroot = os.path.join(SCRIPT_DIR, os.pardir,
-                         'third_party', 'linux', 'sysroot')
+    sysroot = os.path.join(SCRIPT_DIR, os.pardir, 'third_party', 'linux',
+                           'sysroot')
 
-  stamp = os.path.join(sysroot, '.stamp')
-  if os.path.exists(stamp):
-    with open(stamp) as s:
-      if s.read() == url:
-        return
+    stamp = os.path.join(sysroot, '.stamp')
+    if os.path.exists(stamp):
+        with open(stamp) as s:
+            if s.read() == url:
+                return
 
-  print 'Installing Debian root image from %s' % url
+    print 'Installing Debian root image from %s' % url
 
-  if os.path.isdir(sysroot):
-    shutil.rmtree(sysroot)
-  os.mkdir(sysroot)
-  tarball = os.path.join(sysroot, FILENAME)
-  print 'Downloading %s' % url
+    if os.path.isdir(sysroot):
+        shutil.rmtree(sysroot)
+    os.mkdir(sysroot)
+    tarball = os.path.join(sysroot, FILENAME)
+    print 'Downloading %s' % url
 
-  for _ in range(3):
-    response = urllib2.urlopen(url)
-    with open(tarball, 'wb') as f:
-      f.write(response.read())
-    break
-  else:
-    raise Exception('Failed to download %s' % url)
+    for _ in range(3):
+        response = urllib2.urlopen(url)
+        with open(tarball, 'wb') as f:
+            f.write(response.read())
+        break
+    else:
+        raise Exception('Failed to download %s' % url)
 
-  subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot])
+    subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot])
 
-  os.remove(tarball)
+    os.remove(tarball)
 
-  with open(stamp, 'w') as s:
-    s.write(url)
+    with open(stamp, 'w') as s:
+        s.write(url)
 
 
 if __name__ == '__main__':
-  main()
-  sys.exit(0)
+    main()
+    sys.exit(0)
diff --git a/build/ios/Default.png b/build/ios/Default.png
new file mode 100644
index 0000000..8c9089d
--- /dev/null
+++ b/build/ios/Default.png
Binary files differ
diff --git a/build/ios/Unittest-Info.plist b/build/ios/Unittest-Info.plist
new file mode 100644
index 0000000..9256fb4
--- /dev/null
+++ b/build/ios/Unittest-Info.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleIdentifier</key>
+	<string>${IOS_BUNDLE_ID_PREFIX}.gtest.${GTEST_BUNDLE_ID_SUFFIX:rfc1034identifier}</string>
+	<key>UIApplicationDelegate</key>
+	<string>CrashpadUnitTestDelegate</string>
+</dict>
+</plist>
diff --git a/build/ios/convert_gn_xcodeproj.py b/build/ios/convert_gn_xcodeproj.py
new file mode 100755
index 0000000..c00d331
--- /dev/null
+++ b/build/ios/convert_gn_xcodeproj.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+
+# Copyright 2020 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.
+"""Convert GN Xcode projects to platform and configuration independent targets.
+
+GN generates Xcode projects that build one configuration only. However, typical
+iOS development involves using the Xcode IDE to toggle the platform and
+configuration. This script replaces the 'gn' configuration with 'Debug',
+'Release' and 'Profile', and changes the ninja invocation to honor these
+configurations.
+"""
+
+import argparse
+import collections
+import copy
+import filecmp
+import json
+import hashlib
+import os
+import plistlib
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+class XcodeProject(object):
+
+    def __init__(self, objects, counter=0):
+        self.objects = objects
+        self.counter = 0
+
+    def AddObject(self, parent_name, obj):
+        while True:
+            self.counter += 1
+            str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
+            new_id = hashlib.sha1(str_id).hexdigest()[:24].upper()
+
+            # Make sure ID is unique. It's possible there could be an id
+            # conflict since this is run after GN runs.
+            if new_id not in self.objects:
+                self.objects[new_id] = obj
+                return new_id
+
+
+def CopyFileIfChanged(source_path, target_path):
+    """Copy |source_path| to |target_path| is different."""
+    target_dir = os.path.dirname(target_path)
+    if not os.path.isdir(target_dir):
+        os.makedirs(target_dir)
+    if (not os.path.exists(target_path) or
+            not filecmp.cmp(source_path, target_path)):
+        shutil.copyfile(source_path, target_path)
+
+
+def LoadXcodeProjectAsJSON(path):
+    """Return Xcode project at |path| as a JSON string."""
+    return subprocess.check_output(
+        ['plutil', '-convert', 'json', '-o', '-', path])
+
+
+def WriteXcodeProject(output_path, json_string):
+    """Save Xcode project to |output_path| as XML."""
+    with tempfile.NamedTemporaryFile() as temp_file:
+        temp_file.write(json_string)
+        temp_file.flush()
+        subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name])
+        CopyFileIfChanged(temp_file.name, output_path)
+
+
+def UpdateProductsProject(file_input, file_output, configurations, root_dir):
+    """Update Xcode project to support multiple configurations.
+
+    Args:
+        file_input: path to the input Xcode project
+        file_output: path to the output file
+        configurations: list of string corresponding to the configurations that
+        need to be supported by the tweaked Xcode projects, must contains at
+        least one value.
+    """
+    json_data = json.loads(LoadXcodeProjectAsJSON(file_input))
+    project = XcodeProject(json_data['objects'])
+
+    objects_to_remove = []
+    for value in project.objects.values():
+        isa = value['isa']
+
+        # Teach build shell script to look for the configuration and platform.
+        if isa == 'PBXShellScriptBuildPhase':
+            value['shellScript'] = value['shellScript'].replace(
+                'ninja -C .',
+                'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
+
+        # Add new configuration, using the first one as default.
+        if isa == 'XCConfigurationList':
+            value['defaultConfigurationName'] = configurations[0]
+            objects_to_remove.extend(value['buildConfigurations'])
+
+            build_config_template = project.objects[value['buildConfigurations']
+                                                    [0]]
+            build_settings = build_config_template['buildSettings']
+            build_settings['CONFIGURATION_BUILD_DIR'] = (
+                '$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)')
+            build_settings['CODE_SIGN_IDENTITY'] = ''
+
+            value['buildConfigurations'] = []
+            for configuration in configurations:
+                new_build_config = copy.copy(build_config_template)
+                new_build_config['name'] = configuration
+                value['buildConfigurations'].append(
+                    project.AddObject('products', new_build_config))
+
+    for object_id in objects_to_remove:
+        del project.objects[object_id]
+
+    AddMarkdownToProject(project, root_dir, json_data['rootObject'])
+
+    objects = collections.OrderedDict(sorted(project.objects.iteritems()))
+    WriteXcodeProject(file_output, json.dumps(json_data))
+
+
+def AddMarkdownToProject(project, root_dir, root_object):
+    list_files_cmd = ['git', '-C', root_dir, 'ls-files', '*.md']
+    paths = subprocess.check_output(list_files_cmd).splitlines()
+    ios_internal_dir = os.path.join(root_dir, 'ios_internal')
+    if os.path.exists(ios_internal_dir):
+        list_files_cmd = ['git', '-C', ios_internal_dir, 'ls-files', '*.md']
+        ios_paths = subprocess.check_output(list_files_cmd).splitlines()
+        paths.extend(["ios_internal/" + path for path in ios_paths])
+    for path in paths:
+        new_markdown_entry = {
+            "fileEncoding": "4",
+            "isa": "PBXFileReference",
+            "lastKnownFileType": "net.daringfireball.markdown",
+            "name": os.path.basename(path),
+            "path": path,
+            "sourceTree": "<group>"
+        }
+        new_markdown_entry_id = project.AddObject('sources', new_markdown_entry)
+        folder = GetFolderForPath(project, root_object, os.path.dirname(path))
+        folder['children'].append(new_markdown_entry_id)
+
+
+def GetFolderForPath(project, rootObject, path):
+    objects = project.objects
+    # 'Sources' is always the first child of
+    # project->rootObject->mainGroup->children.
+    root = objects[objects[objects[rootObject]['mainGroup']]['children'][0]]
+    if not path:
+        return root
+    for folder in path.split('/'):
+        children = root['children']
+        new_root = None
+        for child in children:
+            if (objects[child]['isa'] == 'PBXGroup' and
+                    objects[child]['name'] == folder):
+                new_root = objects[child]
+                break
+        if not new_root:
+            # If the folder isn't found we could just cram it into the leaf
+            # existing folder, but that leads to folders with tons of README.md
+            # inside.
+            new_group = {
+                "children": [],
+                "isa": "PBXGroup",
+                "name": folder,
+                "sourceTree": "<group>"
+            }
+            new_group_id = project.AddObject('sources', new_group)
+            children.append(new_group_id)
+            new_root = objects[new_group_id]
+        root = new_root
+    return root
+
+
+def DisableNewBuildSystem(output_dir):
+    """Disables the new build system due to crbug.com/852522 """
+    xcwspacesharedsettings = os.path.join(output_dir, 'all.xcworkspace',
+                                          'xcshareddata',
+                                          'WorkspaceSettings.xcsettings')
+    if os.path.isfile(xcwspacesharedsettings):
+        json_data = json.loads(LoadXcodeProjectAsJSON(xcwspacesharedsettings))
+    else:
+        json_data = {}
+    json_data['BuildSystemType'] = 'Original'
+    WriteXcodeProject(xcwspacesharedsettings, json.dumps(json_data))
+
+
+def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
+    '''Tweak the Xcode project generated by gn to support multiple
+    configurations.
+
+    The Xcode projects generated by "gn gen --ide" only supports a single
+    platform and configuration (as the platform and configuration are set per
+    output directory). This method takes as input such projects and add support
+    for multiple configurations and platforms (to allow devs to select them in
+    Xcode).
+
+    Args:
+        input_dir: directory containing the XCode projects created by "gn gen
+            --ide"
+        output_dir: directory where the tweaked Xcode projects will be saved
+        configurations: list of string corresponding to the configurations that
+            need to be supported by the tweaked Xcode projects, must contains at
+            least one value.
+    '''
+    # Update products project.
+    products = os.path.join('products.xcodeproj', 'project.pbxproj')
+    product_input = os.path.join(input_dir, products)
+    product_output = os.path.join(output_dir, products)
+    UpdateProductsProject(product_input, product_output, configurations,
+                          root_dir)
+
+    # Copy all workspace.
+    xcwspace = os.path.join('all.xcworkspace', 'contents.xcworkspacedata')
+    CopyFileIfChanged(os.path.join(input_dir, xcwspace),
+                      os.path.join(output_dir, xcwspace))
+
+    # TODO(crbug.com/852522): Disable new BuildSystemType.
+    DisableNewBuildSystem(output_dir)
+
+    # TODO(crbug.com/679110): gn has been modified to remove 'sources.xcodeproj'
+    # and keep 'all.xcworkspace' and 'products.xcodeproj'. The following code is
+    # here to support both old and new projects setup and will be removed once
+    # gn has rolled past it.
+    sources = os.path.join('sources.xcodeproj', 'project.pbxproj')
+    if os.path.isfile(os.path.join(input_dir, sources)):
+        CopyFileIfChanged(os.path.join(input_dir, sources),
+                          os.path.join(output_dir, sources))
+
+
+def Main(args):
+    parser = argparse.ArgumentParser(
+        description='Convert GN Xcode projects for iOS.')
+    parser.add_argument(
+        'input', help='directory containing [product|all] Xcode projects.')
+    parser.add_argument(
+        'output', help='directory where to generate the iOS configuration.')
+    parser.add_argument('--add-config',
+                        dest='configurations',
+                        default=[],
+                        action='append',
+                        help='configuration to add to the Xcode project')
+    parser.add_argument('--root',
+                        type=os.path.abspath,
+                        required=True,
+                        help='root directory of the project')
+    args = parser.parse_args(args)
+
+    if not os.path.isdir(args.input):
+        sys.stderr.write('Input directory does not exists.\n')
+        return 1
+
+    required = set(['products.xcodeproj', 'all.xcworkspace'])
+    if not required.issubset(os.listdir(args.input)):
+        sys.stderr.write(
+            'Input directory does not contain all necessary Xcode projects.\n')
+        return 1
+
+    if not args.configurations:
+        sys.stderr.write(
+            'At least one configuration required, see --add-config.\n')
+        return 1
+
+    ConvertGnXcodeProject(args.root, args.input, args.output,
+                          args.configurations)
+
+
+if __name__ == '__main__':
+    sys.exit(Main(sys.argv[1:]))
diff --git a/build/ios/setup_ios_gn.config b/build/ios/setup_ios_gn.config
new file mode 100644
index 0000000..30c3111
--- /dev/null
+++ b/build/ios/setup_ios_gn.config
@@ -0,0 +1,39 @@
+# Copyright 2020 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.
+
+[goma]
+# Controls whether goma is enabled or not. If you generally use goma but
+# want to disable goma for a single build, consider using the environment
+# variable GOMA_DISABLED.
+enabled = False
+install = "$GOMA_DIR"
+
+[xcode]
+# Controls settings for the generated Xcode project. If jobs is non-zero
+# it will be passed to the ninja invocation in Xcode project.
+jobs = 0
+
+[build]
+# Controls the build output. The only supported values are "64-bit", "32-bit"
+# and "fat" (for a fat binary supporting both "32-bit" and "64-bit" cpus).
+arch = "64-bit"
+
+[gn_args]
+# Values in that section will be copied verbatim in the generated args.gn file.
+target_os = "ios"
+
+[filters]
+# List of target files to pass to --filters argument of gn gen when generating
+# the Xcode project. By default, list all targets from ios/ and ios_internal/
+# and the targets corresponding to the unit tests run on the bots.
diff --git a/build/ios/setup_ios_gn.py b/build/ios/setup_ios_gn.py
new file mode 100755
index 0000000..934b67c
--- /dev/null
+++ b/build/ios/setup_ios_gn.py
@@ -0,0 +1,350 @@
+#!/usr/bin/env python
+
+# Copyright 2020 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.
+
+import argparse
+import convert_gn_xcodeproj
+import errno
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import ConfigParser
+
+try:
+    import cStringIO as StringIO
+except ImportError:
+    import StringIO
+
+SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator')
+SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official', 'Coverage')
+
+
+class ConfigParserWithStringInterpolation(ConfigParser.SafeConfigParser):
+    '''A .ini file parser that supports strings and environment variables.'''
+
+    ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)')
+
+    def values(self, section):
+        return map(lambda (k, v): self._UnquoteString(self._ExpandEnvVar(v)),
+                   ConfigParser.SafeConfigParser.items(self, section))
+
+    def getstring(self, section, option):
+        return self._UnquoteString(self._ExpandEnvVar(self.get(section,
+                                                               option)))
+
+    def _UnquoteString(self, string):
+        if not string or string[0] != '"' or string[-1] != '"':
+            return string
+        return string[1:-1]
+
+    def _ExpandEnvVar(self, value):
+        match = self.ENV_VAR_PATTERN.search(value)
+        if not match:
+            return value
+        name, (begin, end) = match.group(1), match.span(0)
+        prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
+        return prefix + os.environ.get(name, '') + suffix
+
+
+class GnGenerator(object):
+    '''Holds configuration for a build and method to generate gn default
+    files.'''
+
+    FAT_BUILD_DEFAULT_ARCH = '64-bit'
+
+    TARGET_CPU_VALUES = {
+        'iphoneos': {
+            '32-bit': '"arm"',
+            '64-bit': '"arm64"',
+        },
+        'iphonesimulator': {
+            '32-bit': '"x86"',
+            '64-bit': '"x64"',
+        }
+    }
+
+    def __init__(self, settings, config, target):
+        assert target in SUPPORTED_TARGETS
+        assert config in SUPPORTED_CONFIGS
+        self._settings = settings
+        self._config = config
+        self._target = target
+
+    def _GetGnArgs(self):
+        """Build the list of arguments to pass to gn.
+
+        Returns:
+            A list of tuple containing gn variable names and variable values (it
+            is not a dictionary as the order needs to be preserved).
+        """
+        args = []
+
+        args.append(('is_debug', self._config in ('Debug', 'Coverage')))
+
+        if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
+            args.append(('use_system_xcode', False))
+
+        cpu_values = self.TARGET_CPU_VALUES[self._target]
+        build_arch = self._settings.getstring('build', 'arch')
+        if build_arch == 'fat':
+            target_cpu = cpu_values[self.FAT_BUILD_DEFAULT_ARCH]
+            args.append(('target_cpu', target_cpu))
+            args.append(
+                ('additional_target_cpus',
+                 [cpu for cpu in cpu_values.itervalues() if cpu != target_cpu]))
+        else:
+            args.append(('target_cpu', cpu_values[build_arch]))
+
+        # Add user overrides after the other configurations so that they can
+        # refer to them and override them.
+        args.extend(self._settings.items('gn_args'))
+        return args
+
+    def Generate(self, gn_path, root_path, out_path):
+        buf = StringIO.StringIO()
+        self.WriteArgsGn(buf)
+        WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
+                             buf.getvalue(),
+                             overwrite=True)
+
+        subprocess.check_call(
+            self.GetGnCommand(gn_path, root_path, out_path, True))
+
+    def CreateGnRules(self, gn_path, root_path, out_path):
+        buf = StringIO.StringIO()
+        self.WriteArgsGn(buf)
+        WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
+                             buf.getvalue(),
+                             overwrite=True)
+
+        buf = StringIO.StringIO()
+        gn_command = self.GetGnCommand(gn_path, root_path, out_path, False)
+        self.WriteBuildNinja(buf, gn_command)
+        WriteToFileIfChanged(os.path.join(out_path, 'build.ninja'),
+                             buf.getvalue(),
+                             overwrite=False)
+
+        buf = StringIO.StringIO()
+        self.WriteBuildNinjaDeps(buf)
+        WriteToFileIfChanged(os.path.join(out_path, 'build.ninja.d'),
+                             buf.getvalue(),
+                             overwrite=False)
+
+    def WriteArgsGn(self, stream):
+        stream.write('# This file was generated by setup-gn.py. Do not edit\n')
+        stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
+        stream.write('# to configure settings.\n')
+        stream.write('\n')
+
+        if self._settings.has_section('$imports$'):
+            for import_rule in self._settings.values('$imports$'):
+                stream.write('import("%s")\n' % import_rule)
+            stream.write('\n')
+
+        gn_args = self._GetGnArgs()
+        for name, value in gn_args:
+            if isinstance(value, bool):
+                stream.write('%s = %s\n' % (name, str(value).lower()))
+            elif isinstance(value, list):
+                stream.write('%s = [%s' %
+                             (name, '\n' if len(value) > 1 else ''))
+                if len(value) == 1:
+                    prefix = ' '
+                    suffix = ' '
+                else:
+                    prefix = '  '
+                    suffix = ',\n'
+                for item in value:
+                    if isinstance(item, bool):
+                        stream.write('%s%s%s' %
+                                     (prefix, str(item).lower(), suffix))
+                    else:
+                        stream.write('%s%s%s' % (prefix, item, suffix))
+                stream.write(']\n')
+            else:
+                stream.write('%s = %s\n' % (name, value))
+
+    def WriteBuildNinja(self, stream, gn_command):
+        stream.write('rule gn\n')
+        stream.write('  command = %s\n' % NinjaEscapeCommand(gn_command))
+        stream.write('  description = Regenerating ninja files\n')
+        stream.write('\n')
+        stream.write('build build.ninja: gn\n')
+        stream.write('  generator = 1\n')
+        stream.write('  depfile = build.ninja.d\n')
+
+    def WriteBuildNinjaDeps(self, stream):
+        stream.write('build.ninja: nonexistant_file.gn\n')
+
+    def GetGnCommand(self, gn_path, src_path, out_path, generate_xcode_project):
+        gn_command = [gn_path, '--root=%s' % os.path.realpath(src_path), '-q']
+        if generate_xcode_project:
+            gn_command.append('--ide=xcode')
+            gn_command.append('--root-target=gn_all')
+            if self._settings.getboolean('goma', 'enabled'):
+                ninja_jobs = self._settings.getint('xcode', 'jobs') or 200
+                gn_command.append('--ninja-extra-args=-j%s' % ninja_jobs)
+            if self._settings.has_section('filters'):
+                target_filters = self._settings.values('filters')
+                if target_filters:
+                    gn_command.append('--filters=%s' % ';'.join(target_filters))
+        # TODO(justincohen): --check is currently failing in crashpad.
+        # else:
+        # gn_command.append('--check')
+        gn_command.append('gen')
+        gn_command.append('//%s' % os.path.relpath(os.path.abspath(out_path),
+                                                   os.path.abspath(src_path)))
+        return gn_command
+
+
+def WriteToFileIfChanged(filename, content, overwrite):
+    '''Write |content| to |filename| if different. If |overwrite| is False
+    and the file already exists it is left untouched.'''
+    if os.path.exists(filename):
+        if not overwrite:
+            return
+        with open(filename) as file:
+            if file.read() == content:
+                return
+    if not os.path.isdir(os.path.dirname(filename)):
+        os.makedirs(os.path.dirname(filename))
+    with open(filename, 'w') as file:
+        file.write(content)
+
+
+def NinjaNeedEscape(arg):
+    '''Returns True if |arg| needs to be escaped when written to .ninja file.'''
+    return ':' in arg or '*' in arg or ';' in arg
+
+
+def NinjaEscapeCommand(command):
+    '''Escapes |command| in order to write it to .ninja file.'''
+    result = []
+    for arg in command:
+        if NinjaNeedEscape(arg):
+            arg = arg.replace(':', '$:')
+            arg = arg.replace(';', '\\;')
+            arg = arg.replace('*', '\\*')
+        else:
+            result.append(arg)
+    return ' '.join(result)
+
+
+def FindGn():
+    '''Returns absolute path to gn binary looking at the PATH env variable.'''
+    for path in os.environ['PATH'].split(os.path.pathsep):
+        gn_path = os.path.join(path, 'gn')
+        if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
+            return gn_path
+    return None
+
+
+def GenerateXcodeProject(gn_path, root_dir, out_dir, settings):
+    '''Convert GN generated Xcode project into multi-configuration Xcode
+    project.'''
+
+    temp_path = tempfile.mkdtemp(
+        prefix=os.path.abspath(os.path.join(out_dir, '_temp')))
+    try:
+        generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
+        generator.Generate(gn_path, root_dir, temp_path)
+        convert_gn_xcodeproj.ConvertGnXcodeProject(
+            root_dir, os.path.join(temp_path), os.path.join(out_dir, 'build'),
+            SUPPORTED_CONFIGS)
+    finally:
+        if os.path.exists(temp_path):
+            shutil.rmtree(temp_path)
+
+
+def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings):
+    '''Generates all template configurations for gn.'''
+    for config in SUPPORTED_CONFIGS:
+        for target in SUPPORTED_TARGETS:
+            build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
+            generator = GnGenerator(settings, config, target)
+            generator.CreateGnRules(gn_path, root_dir, build_dir)
+
+
+def Main(args):
+    default_root = os.path.normpath(
+        os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+
+    parser = argparse.ArgumentParser(
+        description='Generate build directories for use with gn.')
+    parser.add_argument(
+        'root',
+        default=default_root,
+        nargs='?',
+        help='root directory where to generate multiple out configurations')
+    parser.add_argument('--import',
+                        action='append',
+                        dest='import_rules',
+                        default=[],
+                        help='path to file defining default gn variables')
+    args = parser.parse_args(args)
+
+    # Load configuration (first global and then any user overrides).
+    settings = ConfigParserWithStringInterpolation()
+    settings.read([
+        os.path.splitext(__file__)[0] + '.config',
+        os.path.expanduser('~/.setup-gn'),
+    ])
+
+    # Add private sections corresponding to --import argument.
+    if args.import_rules:
+        settings.add_section('$imports$')
+        for i, import_rule in enumerate(args.import_rules):
+            if not import_rule.startswith('//'):
+                import_rule = '//%s' % os.path.relpath(
+                    os.path.abspath(import_rule), os.path.abspath(args.root))
+            settings.set('$imports$', '$rule%d$' % i, import_rule)
+
+    # Validate settings.
+    if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
+        sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
+                         settings.getstring('build', 'arch'))
+        sys.exit(1)
+
+    if settings.getboolean('goma', 'enabled'):
+        if settings.getint('xcode', 'jobs') < 0:
+            sys.stderr.write('ERROR: invalid value for xcode.jobs: %s\n' %
+                             settings.get('xcode', 'jobs'))
+            sys.exit(1)
+        goma_install = os.path.expanduser(settings.getstring('goma', 'install'))
+        if not os.path.isdir(goma_install):
+            sys.stderr.write('WARNING: goma.install directory not found: %s\n' %
+                             settings.get('goma', 'install'))
+            sys.stderr.write('WARNING: disabling goma\n')
+            settings.set('goma', 'enabled', 'false')
+
+    # Find gn binary in PATH.
+    gn_path = FindGn()
+    if gn_path is None:
+        sys.stderr.write('ERROR: cannot find gn in PATH\n')
+        sys.exit(1)
+
+    out_dir = os.path.join(args.root, 'out')
+    if not os.path.isdir(out_dir):
+        os.makedirs(out_dir)
+
+    GenerateXcodeProject(gn_path, args.root, out_dir, settings)
+    GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
+
+
+if __name__ == '__main__':
+    sys.exit(Main(sys.argv[1:]))
diff --git a/build/run_fuchsia_qemu.py b/build/run_fuchsia_qemu.py
index 135b314..aff0efd 100755
--- a/build/run_fuchsia_qemu.py
+++ b/build/run_fuchsia_qemu.py
@@ -13,7 +13,6 @@
 # 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.
-
 """Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used
 for running tests without a device.
 """
@@ -30,105 +29,117 @@
 import time
 
 try:
-  from subprocess import DEVNULL
+    from subprocess import DEVNULL
 except ImportError:
-  DEVNULL = open(os.devnull, 'r+b')
+    DEVNULL = open(os.devnull, 'r+b')
 
 CRASHPAD_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                              os.pardir)
 
 
 def _Stop(pid_file):
-  if os.path.isfile(pid_file):
-    with open(pid_file, 'rb') as f:
-      pid = int(f.read().strip())
-    try:
-      os.kill(pid, signal.SIGTERM)
-    except:
-      print('Unable to kill pid %d, continuing' % pid, file=sys.stderr)
-    os.unlink(pid_file)
+    if os.path.isfile(pid_file):
+        with open(pid_file, 'rb') as f:
+            pid = int(f.read().strip())
+        try:
+            os.kill(pid, signal.SIGTERM)
+        except:
+            print('Unable to kill pid %d, continuing' % pid, file=sys.stderr)
+        os.unlink(pid_file)
 
 
 def _CheckForTun():
-  """Check for networking. TODO(scottmg): Currently, this is Linux-specific.
-  """
-  returncode = subprocess.call(
-      ['tunctl', '-b', '-u', getpass.getuser(), '-t', 'qemu'],
-      stdout=DEVNULL, stderr=DEVNULL)
-  if returncode != 0:
-    print('To use QEMU with networking on Linux, configure TUN/TAP. See:',
-          file=sys.stderr)
-    print('  https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only',
-          file=sys.stderr)
-    return 2
-  return 0
+    """Check for networking. TODO(scottmg): Currently, this is Linux-specific.
+    """
+    returncode = subprocess.call(
+        ['tunctl', '-b', '-u',
+         getpass.getuser(), '-t', 'qemu'],
+        stdout=DEVNULL,
+        stderr=DEVNULL)
+    if returncode != 0:
+        print('To use QEMU with networking on Linux, configure TUN/TAP. See:',
+              file=sys.stderr)
+        print(
+            '  https://fuchsia.googlesource.com/zircon/+/HEAD/docs/qemu.md#enabling-networking-under-qemu-x86_64-only',
+            file=sys.stderr)
+        return 2
+    return 0
 
 
 def _Start(pid_file):
-  tun_result = _CheckForTun()
-  if tun_result != 0:
-    return tun_result
+    tun_result = _CheckForTun()
+    if tun_result != 0:
+        return tun_result
 
-  arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
-  fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia')
-  qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin',
-                           'qemu-system-x86_64')
-  kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64')
-  kernel_path = os.path.join(kernel_data_dir, 'zircon.bin')
-  initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin')
+    arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
+    fuchsia_dir = os.path.join(CRASHPAD_ROOT, 'third_party', 'fuchsia')
+    qemu_path = os.path.join(fuchsia_dir, 'qemu', arch, 'bin',
+                             'qemu-system-x86_64')
+    kernel_data_dir = os.path.join(fuchsia_dir, 'sdk', arch, 'target', 'x86_64')
+    kernel_path = os.path.join(kernel_data_dir, 'zircon.bin')
+    initrd_path = os.path.join(kernel_data_dir, 'bootdata.bin')
 
-  mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3))
-  instance_name = 'crashpad_qemu_' + \
-      ''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8))
+    mac_tail = ':'.join('%02x' % random.randint(0, 255) for x in range(3))
+    instance_name = (
+        'crashpad_qemu_' +
+        ''.join(chr(random.randint(ord('A'), ord('Z'))) for x in range(8)))
 
-  # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon.
-  popen = subprocess.Popen([
-    qemu_path,
-    '-m', '2048',
-    '-nographic',
-    '-kernel', kernel_path,
-    '-initrd', initrd_path,
-    '-smp', '4',
-    '-serial', 'stdio',
-    '-monitor', 'none',
-    '-machine', 'q35',
-    '-cpu', 'host,migratable=no',
-    '-enable-kvm',
-    '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0',
-    '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail,
-    '-append', 'TERM=dumb zircon.nodename=' + instance_name,
-  ], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL)
+    # These arguments are from the Fuchsia repo in zircon/scripts/run-zircon.
 
-  with open(pid_file, 'wb') as f:
-    f.write('%d\n' % popen.pid)
+    # yapf: disable
+    popen = subprocess.Popen([
+        qemu_path,
+        '-m', '2048',
+        '-nographic',
+        '-kernel', kernel_path,
+        '-initrd', initrd_path,
+        '-smp', '4',
+        '-serial', 'stdio',
+        '-monitor', 'none',
+        '-machine', 'q35',
+        '-cpu', 'host,migratable=no',
+        '-enable-kvm',
+        '-netdev', 'type=tap,ifname=qemu,script=no,downscript=no,id=net0',
+        '-device', 'e1000,netdev=net0,mac=52:54:00:' + mac_tail,
+        '-append', 'TERM=dumb zircon.nodename=' + instance_name,
+    ],
+                             stdin=DEVNULL,
+                             stdout=DEVNULL,
+                             stderr=DEVNULL)
+    # yapf: enable
 
-  for i in range(10):
-    netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools', 'netaddr')
-    if subprocess.call([netaddr_path, '--nowait', instance_name],
-                       stdout=open(os.devnull), stderr=open(os.devnull)) == 0:
-      break
-    time.sleep(.5)
-  else:
-    print('instance did not respond after start', file=sys.stderr)
-    return 1
+    with open(pid_file, 'wb') as f:
+        f.write('%d\n' % popen.pid)
 
-  return 0
+    for i in range(10):
+        netaddr_path = os.path.join(fuchsia_dir, 'sdk', arch, 'tools',
+                                    'netaddr')
+        if subprocess.call([netaddr_path, '--nowait', instance_name],
+                           stdout=open(os.devnull),
+                           stderr=open(os.devnull)) == 0:
+            break
+        time.sleep(.5)
+    else:
+        print('instance did not respond after start', file=sys.stderr)
+        return 1
+
+    return 0
 
 
 def main(args):
-  if len(args) != 1 or args[0] not in ('start', 'stop'):
-    print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr)
-    return 1
+    if len(args) != 1 or args[0] not in ('start', 'stop'):
+        print('usage: run_fuchsia_qemu.py start|stop', file=sys.stderr)
+        return 1
 
-  command = args[0]
+    command = args[0]
 
-  pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid')
-  _Stop(pid_file)
-  if command == 'start':
-    return _Start(pid_file)
+    pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid')
+    _Stop(pid_file)
+    if command == 'start':
+        return _Start(pid_file)
 
-  return 0
+    return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/build/run_tests.py b/build/run_tests.py
index df7d8af..2f1919b 100755
--- a/build/run_tests.py
+++ b/build/run_tests.py
@@ -24,6 +24,7 @@
 import re
 import subprocess
 import sys
+import tempfile
 import uuid
 
 CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
@@ -32,511 +33,603 @@
 
 
 def _FindGNFromBinaryDir(binary_dir):
-  """Attempts to determine the path to a GN binary used to generate the build
-  files in the given binary_dir. This is necessary because `gn` might not be in
-  the path or might be in a non-standard location, particularly on build
-  machines."""
+    """Attempts to determine the path to a GN binary used to generate the build
+    files in the given binary_dir. This is necessary because `gn` might not be
+    in the path or might be in a non-standard location, particularly on build
+    machines."""
 
-  build_ninja = os.path.join(binary_dir, 'build.ninja')
-  if os.path.isfile(build_ninja):
-    with open(build_ninja, 'rb') as f:
-      # Look for the always-generated regeneration rule of the form:
-      #
-      # rule gn
-      #   command = <gn binary> ... arguments ...
-      #
-      # to extract the gn binary's full path.
-      found_rule_gn = False
-      for line in f:
-        if line.strip() == 'rule gn':
-          found_rule_gn = True
-          continue
-        if found_rule_gn:
-          if len(line) == 0 or line[0] != ' ':
-            return None
-          if line.startswith('  command = '):
-            gn_command_line_parts = line.strip().split(' ')
-            if len(gn_command_line_parts) > 2:
-              return os.path.join(binary_dir, gn_command_line_parts[2])
+    build_ninja = os.path.join(binary_dir, 'build.ninja')
+    if os.path.isfile(build_ninja):
+        with open(build_ninja, 'rb') as f:
+            # Look for the always-generated regeneration rule of the form:
+            #
+            # rule gn
+            #   command = <gn binary> ... arguments ...
+            #
+            # to extract the gn binary's full path.
+            found_rule_gn = False
+            for line in f:
+                if line.strip() == 'rule gn':
+                    found_rule_gn = True
+                    continue
+                if found_rule_gn:
+                    if len(line) == 0 or line[0] != ' ':
+                        return None
+                    if line.startswith('  command = '):
+                        gn_command_line_parts = line.strip().split(' ')
+                        if len(gn_command_line_parts) > 2:
+                            return os.path.join(binary_dir,
+                                                gn_command_line_parts[2])
 
-  return None
+    return None
 
 
 def _BinaryDirTargetOS(binary_dir):
-  """Returns the apparent target OS of binary_dir, or None if none appear to be
-  explicitly specified."""
+    """Returns the apparent target OS of binary_dir, or None if none appear to
+    be explicitly specified."""
 
-  gn_path = _FindGNFromBinaryDir(binary_dir)
+    gn_path = _FindGNFromBinaryDir(binary_dir)
 
-  if gn_path:
-    # Look for a GN “target_os”.
-    popen = subprocess.Popen([gn_path, '--root=' + CRASHPAD_DIR,
-                              'args', binary_dir,
-                              '--list=target_os', '--short'],
-                              shell=IS_WINDOWS_HOST,
-                              stdout=subprocess.PIPE, stderr=open(os.devnull))
-    value = popen.communicate()[0]
-    if popen.returncode == 0:
-      match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
-      if match:
-        return match.group(1)
+    if gn_path:
+        # Look for a GN “target_os”.
+        popen = subprocess.Popen([
+            gn_path, '--root=' + CRASHPAD_DIR, 'args', binary_dir,
+            '--list=target_os', '--short'
+        ],
+                                 shell=IS_WINDOWS_HOST,
+                                 stdout=subprocess.PIPE,
+                                 stderr=open(os.devnull))
+        value = popen.communicate()[0]
+        if popen.returncode == 0:
+            match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
+            if match:
+                return match.group(1)
 
-  # For GYP with Ninja, look for the appearance of “linux-android” in the path
-  # to ar. This path is configured by gyp_crashpad_android.py.
-  build_ninja_path = os.path.join(binary_dir, 'build.ninja')
-  if os.path.exists(build_ninja_path):
-    with open(build_ninja_path) as build_ninja_file:
-      build_ninja_content = build_ninja_file.read()
-      match = re.search('^ar = .+-linux-android(eabi)?-ar$',
-                        build_ninja_content,
-                        re.MULTILINE)
-      if match:
-        return 'android'
+    # For GYP with Ninja, look for the appearance of “linux-android” in the path
+    # to ar. This path is configured by gyp_crashpad_android.py.
+    build_ninja_path = os.path.join(binary_dir, 'build.ninja')
+    if os.path.exists(build_ninja_path):
+        with open(build_ninja_path) as build_ninja_file:
+            build_ninja_content = build_ninja_file.read()
+            match = re.search('-linux-android(eabi)?-ar$', build_ninja_content,
+                              re.MULTILINE)
+            if match:
+                return 'android'
 
-  return None
+    return None
 
 
 def _EnableVTProcessingOnWindowsConsole():
-  """Enables virtual terminal processing for ANSI/VT100-style escape sequences
-  on a Windows console attached to standard output. Returns True on success.
-  Returns False if standard output is not a console or if virtual terminal
-  processing is not supported. The feature was introduced in Windows 10.
-  """
+    """Enables virtual terminal processing for ANSI/VT100-style escape sequences
+    on a Windows console attached to standard output. Returns True on success.
+    Returns False if standard output is not a console or if virtual terminal
+    processing is not supported. The feature was introduced in Windows 10.
+    """
 
-  import pywintypes
-  import win32console
-  import winerror
+    import pywintypes
+    import win32console
+    import winerror
 
-  stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE)
-  try:
-    console_mode = stdout_console.GetConsoleMode()
-  except pywintypes.error as e:
-    if e.winerror == winerror.ERROR_INVALID_HANDLE:
-      # Standard output is not a console.
-      return False
-    raise
+    stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE)
+    try:
+        console_mode = stdout_console.GetConsoleMode()
+    except pywintypes.error as e:
+        if e.winerror == winerror.ERROR_INVALID_HANDLE:
+            # Standard output is not a console.
+            return False
+        raise
 
-  try:
-    # From <wincon.h>. This would be
-    # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to be
-    # defined there.
-    ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+    try:
+        # From <wincon.h>. This would be
+        # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to
+        # be defined there.
+        ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
 
-    stdout_console.SetConsoleMode(console_mode |
-                                  ENABLE_VIRTUAL_TERMINAL_PROCESSING)
-  except pywintypes.error as e:
-    if e.winerror == winerror.ERROR_INVALID_PARAMETER:
-      # ANSI/VT100-style escape sequence processing isn’t supported before
-      # Windows 10.
-      return False
-    raise
+        stdout_console.SetConsoleMode(console_mode |
+                                      ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+    except pywintypes.error as e:
+        if e.winerror == winerror.ERROR_INVALID_PARAMETER:
+            # ANSI/VT100-style escape sequence processing isn’t supported before
+            # Windows 10.
+            return False
+        raise
 
-  return True
+    return True
 
 
 def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line):
-  local_test_path = os.path.join(binary_dir, test)
-  MAYBE_UNSUPPORTED_TESTS = (
-      'crashpad_client_test',
-      'crashpad_handler_test',
-      'crashpad_minidump_test',
-      'crashpad_snapshot_test',
-  )
-  if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS:
-    print('This test is not present and may not be supported, skipping')
-    return
+    local_test_path = os.path.join(binary_dir, test)
+    MAYBE_UNSUPPORTED_TESTS = (
+        'crashpad_client_test',
+        'crashpad_handler_test',
+        'crashpad_minidump_test',
+        'crashpad_snapshot_test',
+    )
+    if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS:
+        print('This test is not present and may not be supported, skipping')
+        return
 
-  def _adb(*args):
-    # Flush all of this script’s own buffered stdout output before running adb,
-    # which will likely produce its own output on stdout.
-    sys.stdout.flush()
+    def _adb(*args):
+        # Flush all of this script’s own buffered stdout output before running
+        # adb, which will likely produce its own output on stdout.
+        sys.stdout.flush()
 
-    adb_command = ['adb', '-s', android_device]
-    adb_command.extend(args)
-    subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST)
+        adb_command = ['adb', '-s', android_device]
+        adb_command.extend(args)
+        subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST)
 
-  def _adb_push(sources, destination):
-    args = list(sources)
-    args.append(destination)
-    _adb('push', *args)
+    def _adb_push(sources, destination):
+        args = list(sources)
+        args.append(destination)
+        _adb('push', *args)
 
-  def _adb_shell(command_args, env={}):
-    # Build a command to execute via “sh -c” instead of invoking it directly.
-    # Here’s why:
-    #
-    # /system/bin/env isn’t normally present prior to Android 6.0 (M), where
-    # toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead,
-    # set environment variables by using the shell’s internal “export” command.
-    #
-    # adbd prior to Android 7.0 (N), and the adb client prior to SDK
-    # platform-tools version 24, don’t know how to communicate a shell command’s
-    # exit status. This was added in Android platform/system/core 606835ae5c4b).
-    # With older adb servers and clients, adb will “exit 0” indicating success
-    # even if the command failed on the device. This makes
-    # subprocess.check_call() semantics difficult to implement directly. As a
-    # workaround, have the device send the command’s exit status over stdout and
-    # pick it back up in this function.
-    #
-    # Both workarounds are implemented by giving the device a simple script,
-    # which adbd will run as an “sh -c” argument.
-    adb_command = ['adb', '-s', android_device, 'shell']
-    script_commands = []
-    for k, v in env.items():
-      script_commands.append('export %s=%s' % (pipes.quote(k), pipes.quote(v)))
-    script_commands.extend([
-        ' '.join(pipes.quote(x) for x in command_args),
-        'status=${?}',
-        'echo "status=${status}"',
-        'exit ${status}'])
-    adb_command.append('; '.join(script_commands))
-    child = subprocess.Popen(adb_command,
-                             shell=IS_WINDOWS_HOST,
-                             stdin=open(os.devnull),
-                             stdout=subprocess.PIPE)
+    def _adb_shell(command_args, env={}):
+        # Build a command to execute via “sh -c” instead of invoking it
+        # directly. Here’s why:
+        #
+        # /system/bin/env isn’t normally present prior to Android 6.0 (M), where
+        # toybox was introduced (Android platform/manifest 9a2c01e8450b).
+        # Instead, set environment variables by using the shell’s internal
+        # “export” command.
+        #
+        # adbd prior to Android 7.0 (N), and the adb client prior to SDK
+        # platform-tools version 24, don’t know how to communicate a shell
+        # command’s exit status. This was added in Android platform/system/core
+        # 606835ae5c4b). With older adb servers and clients, adb will “exit 0”
+        # indicating success even if the command failed on the device. This
+        # makes subprocess.check_call() semantics difficult to implement
+        # directly. As a workaround, have the device send the command’s exit
+        # status over stdout and pick it back up in this function.
+        #
+        # Both workarounds are implemented by giving the device a simple script,
+        # which adbd will run as an “sh -c” argument.
+        adb_command = ['adb', '-s', android_device, 'shell']
+        script_commands = []
+        for k, v in env.items():
+            script_commands.append('export %s=%s' %
+                                   (pipes.quote(k), pipes.quote(v)))
+        script_commands.extend([
+            ' '.join(pipes.quote(x) for x in command_args), 'status=${?}',
+            'echo "status=${status}"', 'exit ${status}'
+        ])
+        adb_command.append('; '.join(script_commands))
+        child = subprocess.Popen(adb_command,
+                                 shell=IS_WINDOWS_HOST,
+                                 stdin=open(os.devnull),
+                                 stdout=subprocess.PIPE)
 
-    FINAL_LINE_RE = re.compile('status=(\d+)$')
-    final_line = None
-    while True:
-      # Use readline so that the test output appears “live” when running.
-      data = child.stdout.readline().decode('utf-8')
-      if data == '':
-        break
-      if final_line is not None:
-        # It wasn’t really the final line.
-        print(final_line, end='')
+        FINAL_LINE_RE = re.compile('status=(\d+)$')
         final_line = None
-      if FINAL_LINE_RE.match(data.rstrip()):
-        final_line = data
-      else:
-        print(data, end='')
+        while True:
+            # Use readline so that the test output appears “live” when running.
+            data = child.stdout.readline().decode('utf-8')
+            if data == '':
+                break
+            if final_line is not None:
+                # It wasn’t really the final line.
+                print(final_line, end='')
+                final_line = None
+            if FINAL_LINE_RE.match(data.rstrip()):
+                final_line = data
+            else:
+                print(data, end='')
 
-    if final_line is None:
-      # Maybe there was some stderr output after the end of stdout. Old versions
-      # of adb, prior to when the exit status could be communicated, smush the
-      # two together.
-      raise subprocess.CalledProcessError(-1, adb_command)
-    status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1))
-    if status != 0:
-      raise subprocess.CalledProcessError(status, adb_command)
+        if final_line is None:
+            # Maybe there was some stderr output after the end of stdout. Old
+            # versions of adb, prior to when the exit status could be
+            # communicated, smush the two together.
+            raise subprocess.CalledProcessError(-1, adb_command)
+        status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1))
+        if status != 0:
+            raise subprocess.CalledProcessError(status, adb_command)
 
-    child.wait()
-    if child.returncode != 0:
-      raise subprocess.CalledProcessError(subprocess.returncode, adb_command)
+        child.wait()
+        if child.returncode != 0:
+            raise subprocess.CalledProcessError(subprocess.returncode,
+                                                adb_command)
 
-  # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where
-  # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with
-  # a host-generated name. This won’t retry if the name is in use, but with 122
-  # bits of randomness, it should be OK. This uses “mkdir” instead of “mkdir -p”
-  # because the latter will not indicate failure if the directory already
-  # exists.
-  device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex)
-  _adb_shell(['mkdir', device_temp_dir])
+    # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where
+    # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it
+    # with a host-generated name. This won’t retry if the name is in use, but
+    # with 122 bits of randomness, it should be OK. This uses “mkdir” instead of
+    # “mkdir -p”because the latter will not indicate failure if the directory
+    # already exists.
+    device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex)
+    _adb_shell(['mkdir', device_temp_dir])
 
-  try:
-    # Specify test dependencies that must be pushed to the device. This could be
-    # determined automatically in a GN build, following the example used for
-    # Fuchsia. Since nothing like that exists for GYP, hard-code it for
-    # supported tests.
-    test_build_artifacts = [test]
-    test_data = ['test/test_paths_test_data_root.txt']
+    try:
+        # Specify test dependencies that must be pushed to the device. This
+        # could be determined automatically in a GN build, following the example
+        # used for Fuchsia. Since nothing like that exists for GYP, hard-code it
+        # for supported tests.
+        test_build_artifacts = [test, 'crashpad_handler']
+        test_data = ['test/test_paths_test_data_root.txt']
 
-    if test == 'crashpad_test_test':
-      test_build_artifacts.append(
-          'crashpad_test_test_multiprocess_exec_test_child')
-    elif test == 'crashpad_util_test':
-      test_data.append('util/net/testdata/')
+        if test == 'crashpad_test_test':
+            test_build_artifacts.append(
+                'crashpad_test_test_multiprocess_exec_test_child')
+        elif test == 'crashpad_util_test':
+            test_data.append('util/net/testdata/')
 
-    # Establish the directory structure on the device.
-    device_out_dir = posixpath.join(device_temp_dir, 'out')
-    device_mkdirs = [device_out_dir]
-    for source_path in test_data:
-      # A trailing slash could reasonably mean to copy an entire directory, but
-      # will interfere with what’s needed from the path split. All parent
-      # directories of any source_path need to be be represented in
-      # device_mkdirs, but it’s important that no source_path itself wind up in
-      # device_mkdirs, even if source_path names a directory, because that would
-      # cause the “adb push” of the directory below to behave incorrectly.
-      if source_path.endswith(posixpath.sep):
-        source_path = source_path[:-1]
+        # Establish the directory structure on the device.
+        device_out_dir = posixpath.join(device_temp_dir, 'out')
+        device_mkdirs = [device_out_dir]
+        for source_path in test_data:
+            # A trailing slash could reasonably mean to copy an entire
+            # directory, but will interfere with what’s needed from the path
+            # split. All parent directories of any source_path need to be be
+            # represented in device_mkdirs, but it’s important that no
+            # source_path itself wind up in device_mkdirs, even if source_path
+            # names a directory, because that would cause the “adb push” of the
+            # directory below to behave incorrectly.
+            if source_path.endswith(posixpath.sep):
+                source_path = source_path[:-1]
 
-      device_source_path = posixpath.join(device_temp_dir, source_path)
-      device_mkdir = posixpath.split(device_source_path)[0]
-      if device_mkdir not in device_mkdirs:
-        device_mkdirs.append(device_mkdir)
-    adb_mkdir_command = ['mkdir', '-p']
-    adb_mkdir_command.extend(device_mkdirs)
-    _adb_shell(adb_mkdir_command)
+            device_source_path = posixpath.join(device_temp_dir, source_path)
+            device_mkdir = posixpath.split(device_source_path)[0]
+            if device_mkdir not in device_mkdirs:
+                device_mkdirs.append(device_mkdir)
+        adb_mkdir_command = ['mkdir', '-p']
+        adb_mkdir_command.extend(device_mkdirs)
+        _adb_shell(adb_mkdir_command)
 
-    # Push the test binary and any other build output to the device.
-    local_test_build_artifacts = []
-    for artifact in test_build_artifacts:
-      local_test_build_artifacts.append(os.path.join(binary_dir, artifact))
-    _adb_push(local_test_build_artifacts, device_out_dir)
+        # Push the test binary and any other build output to the device.
+        local_test_build_artifacts = []
+        for artifact in test_build_artifacts:
+            local_test_build_artifacts.append(os.path.join(
+                binary_dir, artifact))
+        _adb_push(local_test_build_artifacts, device_out_dir)
 
-    # Push test data to the device.
-    for source_path in test_data:
-      _adb_push([os.path.join(CRASHPAD_DIR, source_path)],
-                posixpath.join(device_temp_dir, source_path))
+        # Push test data to the device.
+        for source_path in test_data:
+            _adb_push([os.path.join(CRASHPAD_DIR, source_path)],
+                      posixpath.join(device_temp_dir, source_path))
 
-    # Run the test on the device. Pass the test data root in the environment.
-    #
-    # Because the test will not run with its standard output attached to a
-    # pseudo-terminal device, gtest will not normally enable colored output, so
-    # mimic gtest’s own logic for deciding whether to enable color by checking
-    # this script’s own standard output connection. The whitelist of TERM values
-    # comes from gtest googletest/src/gtest.cc
-    # testing::internal::ShouldUseColor().
-    env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir}
-    gtest_color = os.environ.get('GTEST_COLOR')
-    if gtest_color in ('auto', None):
-      if (sys.stdout.isatty() and
-          (os.environ.get('TERM') in
-               ('xterm', 'xterm-color', 'xterm-256color', 'screen',
-                'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode',
-                'rxvt-unicode-256color', 'linux', 'cygwin') or
-           (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))):
-        gtest_color = 'yes'
-      else:
-        gtest_color = 'no'
-    env['GTEST_COLOR'] = gtest_color
-    _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env)
-  finally:
-    _adb_shell(['rm', '-rf', device_temp_dir])
+        # Run the test on the device. Pass the test data root in the
+        # environment.
+        #
+        # Because the test will not run with its standard output attached to a
+        # pseudo-terminal device, gtest will not normally enable colored output,
+        # so mimic gtest’s own logic for deciding whether to enable color by
+        # checking this script’s own standard output connection. The whitelist
+        # of TERM values comes from gtest googletest/src/gtest.cc
+        # testing::internal::ShouldUseColor().
+        env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir}
+        gtest_color = os.environ.get('GTEST_COLOR')
+        if gtest_color in ('auto', None):
+            if (sys.stdout.isatty() and
+                (os.environ.get('TERM')
+                 in ('xterm', 'xterm-color', 'xterm-256color', 'screen',
+                     'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode',
+                     'rxvt-unicode-256color', 'linux', 'cygwin') or
+                 (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))):
+                gtest_color = 'yes'
+            else:
+                gtest_color = 'no'
+        env['GTEST_COLOR'] = gtest_color
+        _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line,
+                   env)
+    finally:
+        _adb_shell(['rm', '-rf', device_temp_dir])
 
 
 def _GetFuchsiaSDKRoot():
-  arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
-  return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch)
+    arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
+    return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch)
 
 
 def _GenerateFuchsiaRuntimeDepsFiles(binary_dir, tests):
-  """Ensures a <binary_dir>/<test>.runtime_deps file exists for each test."""
-  targets_file = os.path.join(binary_dir, 'targets.txt')
-  with open(targets_file, 'wb') as f:
-    f.write('//:' + '\n//:'.join(tests) + '\n')
-  gn_path = _FindGNFromBinaryDir(binary_dir)
-  subprocess.check_call(
-      [gn_path,  '--root=' + CRASHPAD_DIR, 'gen', binary_dir,
-       '--runtime-deps-list-file=' + targets_file])
+    """Ensures a <binary_dir>/<test>.runtime_deps file exists for each test."""
+    targets_file = os.path.join(binary_dir, 'targets.txt')
+    with open(targets_file, 'wb') as f:
+        f.write('//:' + '\n//:'.join(tests) + '\n')
+    gn_path = _FindGNFromBinaryDir(binary_dir)
+    subprocess.check_call([
+        gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir,
+        '--runtime-deps-list-file=' + targets_file
+    ])
 
-  # Run again so that --runtime-deps-list-file isn't in the regen rule. See
-  # https://crbug.com/814816.
-  subprocess.check_call(
-      [gn_path,  '--root=' + CRASHPAD_DIR, 'gen', binary_dir])
+    # Run again so that --runtime-deps-list-file isn't in the regen rule. See
+    # https://crbug.com/814816.
+    subprocess.check_call(
+        [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir])
 
 
 def _HandleOutputFromFuchsiaLogListener(process, done_message):
-  """Pass through the output from |process| (which should be an instance of
-  Fuchsia's loglistener) until a special termination |done_message| is
-  encountered.
+    """Pass through the output from |process| (which should be an instance of
+    Fuchsia's loglistener) until a special termination |done_message| is
+    encountered.
 
-  Also attempts to determine if any tests failed by inspecting the log output,
-  and returns False if there were failures.
-  """
-  success = True
-  while True:
-    line = process.stdout.readline().rstrip()
-    if 'FAILED TEST' in line:
-      success = False
-    elif done_message in line and 'echo ' not in line:
-      break
-    print(line)
-  return success
+    Also attempts to determine if any tests failed by inspecting the log output,
+    and returns False if there were failures.
+    """
+    success = True
+    while True:
+        line = process.stdout.readline().rstrip()
+        if 'FAILED TEST' in line:
+            success = False
+        elif done_message in line and 'echo ' not in line:
+            break
+        print(line)
+    return success
 
 
 def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line):
-  """Runs the given Fuchsia |test| executable on the given |device_name|. The
-  device must already be booted.
+    """Runs the given Fuchsia |test| executable on the given |device_name|. The
+    device must already be booted.
 
-  Copies the executable and its runtime dependencies as specified by GN to the
-  target in /tmp using `netcp`, runs the binary on the target, and logs output
-  back to stdout on this machine via `loglistener`.
-  """
-  sdk_root = _GetFuchsiaSDKRoot()
-
-  # Run loglistener and filter the output to know when the test is done.
-  loglistener_process = subprocess.Popen(
-      [os.path.join(sdk_root, 'tools', 'loglistener'), device_name],
-      stdout=subprocess.PIPE, stdin=open(os.devnull), stderr=open(os.devnull))
-
-  runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps')
-  with open(runtime_deps_file, 'rb') as f:
-    runtime_deps = f.read().splitlines()
-
-  def netruncmd(*args):
-    """Runs a list of commands on the target device. Each command is escaped
-    by using pipes.quote(), and then each command is chained by shell ';'.
+    Copies the executable and its runtime dependencies as specified by GN to the
+    target in /tmp using `netcp`, runs the binary on the target, and logs output
+    back to stdout on this machine via `loglistener`.
     """
-    netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd')
-    final_args = ' ; '.join(' '.join(pipes.quote(x) for x in command)
-                            for command in args)
-    subprocess.check_call([netruncmd_path, device_name, final_args])
+    sdk_root = _GetFuchsiaSDKRoot()
 
-  try:
-    unique_id = uuid.uuid4().hex
-    test_root = '/tmp/%s_%s' % (test, unique_id)
-    tmp_root = test_root + '/tmp'
-    staging_root = test_root + '/pkg'
+    # Run loglistener and filter the output to know when the test is done.
+    loglistener_process = subprocess.Popen(
+        [os.path.join(sdk_root, 'tools', 'loglistener'), device_name],
+        stdout=subprocess.PIPE,
+        stdin=open(os.devnull),
+        stderr=open(os.devnull))
 
-    # Make a staging directory tree on the target.
-    directories_to_create = [tmp_root,
-                             '%s/bin' % staging_root,
-                             '%s/assets' % staging_root]
-    netruncmd(['mkdir', '-p'] + directories_to_create)
+    runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps')
+    with open(runtime_deps_file, 'rb') as f:
+        runtime_deps = f.read().splitlines()
 
-    def netcp(local_path):
-      """Uses `netcp` to copy a file or directory to the device. Files located
-      inside the build dir are stored to /pkg/bin, otherwise to /pkg/assets.
-      .so files are stored somewhere completely different, into /boot/lib (!).
-      This is because the loader service does not yet correctly handle the
-      namespace in which the caller is being run, and so can only load .so files
-      from a couple hardcoded locations, the only writable one of which is
-      /boot/lib, so we copy all .so files there. This bug is filed upstream as
-      ZX-1619.
-      """
-      in_binary_dir = local_path.startswith(binary_dir + '/')
-      if in_binary_dir:
-        if local_path.endswith('.so'):
-          target_path = os.path.join(
-              '/boot/lib', local_path[len(binary_dir)+1:])
+    def netruncmd(*args):
+        """Runs a list of commands on the target device. Each command is escaped
+        by using pipes.quote(), and then each command is chained by shell ';'.
+        """
+        netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd')
+        final_args = ' ; '.join(
+            ' '.join(pipes.quote(x) for x in command) for command in args)
+        subprocess.check_call([netruncmd_path, device_name, final_args])
+
+    try:
+        unique_id = uuid.uuid4().hex
+        test_root = '/tmp/%s_%s' % (test, unique_id)
+        tmp_root = test_root + '/tmp'
+        staging_root = test_root + '/pkg'
+
+        # Make a staging directory tree on the target.
+        directories_to_create = [
+            tmp_root,
+            '%s/bin' % staging_root,
+            '%s/assets' % staging_root
+        ]
+        netruncmd(['mkdir', '-p'] + directories_to_create)
+
+        def netcp(local_path):
+            """Uses `netcp` to copy a file or directory to the device. Files
+            located inside the build dir are stored to /pkg/bin, otherwise to
+            /pkg/assets. .so files are stored somewhere completely different,
+            into /boot/lib (!). This is because the loader service does not yet
+            correctly handle the namespace in which the caller is being run, and
+            so can only load .so files from a couple hardcoded locations, the
+            only writable one of which is /boot/lib, so we copy all .so files
+            there. This bug is filed upstream as ZX-1619.
+            """
+            in_binary_dir = local_path.startswith(binary_dir + '/')
+            if in_binary_dir:
+                if local_path.endswith('.so'):
+                    target_path = os.path.join('/boot/lib',
+                                               local_path[len(binary_dir) + 1:])
+                else:
+                    target_path = os.path.join(staging_root, 'bin',
+                                               local_path[len(binary_dir) + 1:])
+            else:
+                relative_path = os.path.relpath(local_path, CRASHPAD_DIR)
+                target_path = os.path.join(staging_root, 'assets',
+                                           relative_path)
+            netcp_path = os.path.join(sdk_root, 'tools', 'netcp')
+            subprocess.check_call(
+                [netcp_path, local_path, device_name + ':' + target_path],
+                stderr=open(os.devnull))
+
+        # Copy runtime deps into the staging tree.
+        for dep in runtime_deps:
+            local_path = os.path.normpath(os.path.join(binary_dir, dep))
+            if os.path.isdir(local_path):
+                for root, dirs, files in os.walk(local_path):
+                    for f in files:
+                        netcp(os.path.join(root, f))
+            else:
+                netcp(local_path)
+
+        done_message = 'TERMINATED: ' + unique_id
+        namespace_command = [
+            'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root,
+            '/svc=/svc', '--replace-child-argv0=/pkg/bin/' + test, '--',
+            staging_root + '/bin/' + test
+        ] + extra_command_line
+        netruncmd(namespace_command, ['echo', done_message])
+
+        success = _HandleOutputFromFuchsiaLogListener(loglistener_process,
+                                                      done_message)
+        if not success:
+            raise subprocess.CalledProcessError(1, test)
+    finally:
+        netruncmd(['rm', '-rf', test_root])
+
+
+def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False):
+    """Runs the given iOS |test| app on iPhone 8 with the default OS version."""
+
+    def xctest(binary_dir, test):
+        """Returns a dict containing the xctestrun data needed to run an
+        XCTest-based test app."""
+        test_path = os.path.join(CRASHPAD_DIR, binary_dir)
+        module_data = {
+            'TestBundlePath': os.path.join(test_path, test + '_module.xctest'),
+            'TestHostPath': os.path.join(test_path, test + '.app'),
+            'TestingEnvironmentVariables': {
+                'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:',
+                'DYLD_INSERT_LIBRARIES':
+                    ('__PLATFORMS__/iPhoneSimulator.platform/Developer/'
+                     'usr/lib/libXCTestBundleInject.dylib'),
+                'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator',
+                'IDEiPhoneInternalTestBundleName': test + '.app',
+                'XCInjectBundleInto': '__TESTHOST__/' + test,
+            }
+        }
+        return {test: module_data}
+
+    def xcuitest(binary_dir, test):
+        """Returns a dict containing the xctestrun data needed to run an
+        XCUITest-based test app."""
+
+        test_path = os.path.join(CRASHPAD_DIR, binary_dir)
+        runner_path = os.path.join(test_path, test + '_module-Runner.app')
+        bundle_path = os.path.join(runner_path, 'PlugIns',
+                                   test + '_module.xctest')
+        target_app_path = os.path.join(test_path, test + '.app')
+        module_data = {
+            'IsUITestBundle': True,
+            'IsXCTRunnerHostedTestBundle': True,
+            'TestBundlePath': bundle_path,
+            'TestHostPath': runner_path,
+            'UITargetAppPath': target_app_path,
+            'DependentProductPaths': [
+                bundle_path, runner_path, target_app_path
+            ],
+            'TestingEnvironmentVariables': {
+                'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:',
+                'DYLD_INSERT_LIBRARIES':
+                    ('__PLATFORMS__/iPhoneSimulator.platform/Developer/'
+                     'usr/lib/libXCTestBundleInject.dylib'),
+                'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator',
+                'XCInjectBundleInto': '__TESTHOST__/' + test + '_module-Runner',
+            },
+        }
+        return {test: module_data}
+
+    with tempfile.NamedTemporaryFile() as f:
+        import plistlib
+
+        xctestrun_path = f.name
+        print(xctestrun_path)
+        if is_xcuitest:
+            plistlib.writePlist(xcuitest(binary_dir, test), xctestrun_path)
         else:
-          target_path = os.path.join(
-              staging_root, 'bin', local_path[len(binary_dir)+1:])
-      else:
-        relative_path = os.path.relpath(local_path, CRASHPAD_DIR)
-        target_path = os.path.join(staging_root, 'assets', relative_path)
-      netcp_path = os.path.join(sdk_root, 'tools', 'netcp')
-      subprocess.check_call([netcp_path, local_path,
-                             device_name + ':' + target_path],
-                            stderr=open(os.devnull))
+            plistlib.writePlist(xctest(binary_dir, test), xctestrun_path)
 
-    # Copy runtime deps into the staging tree.
-    for dep in runtime_deps:
-      local_path = os.path.normpath(os.path.join(binary_dir, dep))
-      if os.path.isdir(local_path):
-        for root, dirs, files in os.walk(local_path):
-          for f in files:
-            netcp(os.path.join(root, f))
-      else:
-        netcp(local_path)
-
-    done_message = 'TERMINATED: ' + unique_id
-    namespace_command = [
-        'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc',
-        '--replace-child-argv0=/pkg/bin/' + test, '--',
-        staging_root + '/bin/' + test] + extra_command_line
-    netruncmd(namespace_command, ['echo', done_message])
-
-    success = _HandleOutputFromFuchsiaLogListener(
-        loglistener_process, done_message)
-    if not success:
-      raise subprocess.CalledProcessError(1, test)
-  finally:
-    netruncmd(['rm', '-rf', test_root])
+        subprocess.check_call([
+            'xcodebuild', 'test-without-building', '-xctestrun', xctestrun_path,
+            '-destination', 'platform=iOS Simulator,name=iPhone 8'
+        ])
 
 
 # This script is primarily used from the waterfall so that the list of tests
 # that are run is maintained in-tree, rather than in a separate infrastructure
 # location in the recipe.
 def main(args):
-  parser = argparse.ArgumentParser(description='Run Crashpad unittests.')
-  parser.add_argument('binary_dir', help='Root of build dir')
-  parser.add_argument('test', nargs='*', help='Specific test(s) to run.')
-  parser.add_argument('--gtest_filter',
-                      help='GTest filter applied to GTest binary runs.')
-  args = parser.parse_args()
+    parser = argparse.ArgumentParser(description='Run Crashpad unittests.')
+    parser.add_argument('binary_dir', help='Root of build dir')
+    parser.add_argument('test', nargs='*', help='Specific test(s) to run.')
+    parser.add_argument('--gtest_filter',
+                        help='GTest filter applied to GTest binary runs.')
+    args = parser.parse_args()
 
-  # Tell 64-bit Windows tests where to find 32-bit test executables, for
-  # cross-bitted testing. This relies on the fact that the GYP build by default
-  # uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 for the
-  # 64-bit build. This is not a universally valid assumption, and if it’s not
-  # met, 64-bit tests that require 32-bit build output will disable themselves
-  # dynamically.
-  if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and
-      'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ):
-    binary_dir_32 = args.binary_dir[:-4]
-    if os.path.isdir(binary_dir_32):
-      os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32
+    # Tell 64-bit Windows tests where to find 32-bit test executables, for
+    # cross-bitted testing. This relies on the fact that the GYP build by
+    # default uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64
+    # for the 64-bit build. This is not a universally valid assumption, and if
+    # it’s not met, 64-bit tests that require 32-bit build output will disable
+    # themselves dynamically.
+    if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and
+            'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ):
+        binary_dir_32 = args.binary_dir[:-4]
+        if os.path.isdir(binary_dir_32):
+            os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32
 
-  target_os = _BinaryDirTargetOS(args.binary_dir)
-  is_android = target_os == 'android'
-  is_fuchsia = target_os == 'fuchsia'
+    target_os = _BinaryDirTargetOS(args.binary_dir)
+    is_android = target_os == 'android'
+    is_fuchsia = target_os == 'fuchsia'
+    is_ios = target_os == 'ios'
 
-  tests = [
-      'crashpad_client_test',
-      'crashpad_handler_test',
-      'crashpad_minidump_test',
-      'crashpad_snapshot_test',
-      'crashpad_test_test',
-      'crashpad_util_test',
-  ]
+    tests = [
+        'crashpad_client_test',
+        'crashpad_handler_test',
+        'crashpad_minidump_test',
+        'crashpad_snapshot_test',
+        'crashpad_test_test',
+        'crashpad_util_test',
+    ]
 
-  if is_android:
-    android_device = os.environ.get('ANDROID_DEVICE')
-    if not android_device:
-      adb_devices = subprocess.check_output(['adb', 'devices'],
-                                            shell=IS_WINDOWS_HOST)
-      devices = []
-      for line in adb_devices.splitlines():
-        line = line.decode('utf-8')
-        if (line == 'List of devices attached' or
-            re.match('^\* daemon .+ \*$', line) or
-            line == ''):
-          continue
-        (device, ignore) = line.split('\t')
-        devices.append(device)
-      if len(devices) != 1:
-        print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr)
-        return 2
-      android_device = devices[0]
-      print('Using autodetected Android device:', android_device)
-  elif is_fuchsia:
-    zircon_nodename = os.environ.get('ZIRCON_NODENAME')
-    if not zircon_nodename:
-      netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls')
-      popen = subprocess.Popen([netls, '--nowait'], stdout=subprocess.PIPE)
-      devices = popen.communicate()[0].splitlines()
-      if popen.returncode != 0 or len(devices) != 1:
-        print("Please set ZIRCON_NODENAME to your device's hostname",
-              file=sys.stderr)
-        return 2
-      zircon_nodename = devices[0].strip().split()[1]
-      print('Using autodetected Fuchsia device:', zircon_nodename)
-    _GenerateFuchsiaRuntimeDepsFiles(
-        args.binary_dir, [t for t in tests if not t.endswith('.py')])
-  elif IS_WINDOWS_HOST:
-    tests.append('snapshot/win/end_to_end_test.py')
+    if is_android:
+        android_device = os.environ.get('ANDROID_DEVICE')
+        if not android_device:
+            adb_devices = subprocess.check_output(['adb', 'devices'],
+                                                  shell=IS_WINDOWS_HOST)
+            devices = []
+            for line in adb_devices.splitlines():
+                line = line.decode('utf-8')
+                if (line == 'List of devices attached' or
+                        re.match('^\* daemon .+ \*$', line) or line == ''):
+                    continue
+                (device, ignore) = line.split('\t')
+                devices.append(device)
+            if len(devices) != 1:
+                print("Please set ANDROID_DEVICE to your device's id",
+                      file=sys.stderr)
+                return 2
+            android_device = devices[0]
+            print('Using autodetected Android device:', android_device)
+    elif is_fuchsia:
+        zircon_nodename = os.environ.get('ZIRCON_NODENAME')
+        if not zircon_nodename:
+            netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls')
+            popen = subprocess.Popen([netls, '--nowait'],
+                                     stdout=subprocess.PIPE)
+            devices = popen.communicate()[0].splitlines()
+            if popen.returncode != 0 or len(devices) != 1:
+                print("Please set ZIRCON_NODENAME to your device's hostname",
+                      file=sys.stderr)
+                return 2
+            zircon_nodename = devices[0].strip().split()[1]
+            print('Using autodetected Fuchsia device:', zircon_nodename)
+        _GenerateFuchsiaRuntimeDepsFiles(
+            args.binary_dir, [t for t in tests if not t.endswith('.py')])
+    elif is_ios:
+        tests.append('ios_crash_xcuitests')
+    elif IS_WINDOWS_HOST:
+        tests.append('snapshot/win/end_to_end_test.py')
 
-  if args.test:
-    for t in args.test:
-      if t not in tests:
-        print('Unrecognized test:', t, file=sys.stderr)
-        return 3
-    tests = args.test
+    if args.test:
+        for t in args.test:
+            if t not in tests:
+                print('Unrecognized test:', t, file=sys.stderr)
+                return 3
+        tests = args.test
 
-  for test in tests:
-    print('-' * 80)
-    print(test)
-    print('-' * 80)
-    if test.endswith('.py'):
-      subprocess.check_call(
-          [sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir])
-    else:
-      extra_command_line = []
-      if args.gtest_filter:
-        extra_command_line.append('--gtest_filter=' + args.gtest_filter)
-      if is_android:
-        _RunOnAndroidTarget(args.binary_dir, test, android_device,
-                            extra_command_line)
-      elif is_fuchsia:
-        _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename,
-                            extra_command_line)
-      else:
-        subprocess.check_call([os.path.join(args.binary_dir, test)] +
-                              extra_command_line)
+    for test in tests:
+        print('-' * 80)
+        print(test)
+        print('-' * 80)
+        if test.endswith('.py'):
+            subprocess.check_call([
+                sys.executable,
+                os.path.join(CRASHPAD_DIR, test), args.binary_dir
+            ])
+        else:
+            extra_command_line = []
+            if args.gtest_filter:
+                extra_command_line.append('--gtest_filter=' + args.gtest_filter)
+            if is_android:
+                _RunOnAndroidTarget(args.binary_dir, test, android_device,
+                                    extra_command_line)
+            elif is_fuchsia:
+                _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename,
+                                    extra_command_line)
+            elif is_ios:
+                _RunOnIOSTarget(args.binary_dir,
+                                test,
+                                is_xcuitest=test.startswith('ios'))
+            else:
+                subprocess.check_call([os.path.join(args.binary_dir, test)] +
+                                      extra_command_line)
 
-  return 0
+    return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/build/test.gni b/build/test.gni
index f46520b..01fd7e7 100644
--- a/build/test.gni
+++ b/build/test.gni
@@ -18,9 +18,32 @@
   import("//testing/test.gni")
 } else {
   template("test") {
-    executable(target_name) {
-      testonly = true
-      forward_variables_from(invoker, "*")
+    if (crashpad_is_ios) {
+      import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni")
+
+      _launch_image_bundle_target = target_name + "_launch_image"
+      bundle_data(_launch_image_bundle_target) {
+        forward_variables_from(invoker, [ "testonly" ])
+        sources = [ "//build/ios/Default.png" ]
+        outputs = [ "{{bundle_contents_dir}}/{{source_file_part}}" ]
+      }
+
+      ios_xctest_test(target_name) {
+        testonly = true
+        xctest_module_target = "//test/ios:google_test_runner"
+        info_plist = "//build/ios/Unittest-Info.plist"
+        extra_substitutions = [ "GTEST_BUNDLE_ID_SUFFIX=$target_name" ]
+        forward_variables_from(invoker, "*")
+        if (!defined(deps)) {
+          deps = []
+        }
+        deps += [ ":$_launch_image_bundle_target" ]
+      }
+    } else {
+      executable(target_name) {
+        testonly = true
+        forward_variables_from(invoker, "*")
+      }
     }
   }
 }
diff --git a/client/BUILD.gn b/client/BUILD.gn
index a074b50..b748c3f 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -43,6 +43,13 @@
     ]
   }
 
+  if (crashpad_is_ios) {
+    sources += [
+      "crash_report_database_mac.mm",
+      "crashpad_client_ios.cc",
+    ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android) {
     set_sources_assignment_filter([])
     sources += [
@@ -77,22 +84,36 @@
 
   public_configs = [ "..:crashpad_config" ]
 
-  deps = [
-    "../compat",
+  public_deps = [
     "../third_party/mini_chromium:base",
     "../util",
   ]
 
+  deps = []
+
   if (crashpad_is_win) {
     libs = [ "rpcrt4.lib" ]
     cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
   }
 
-  if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) {
+  # TODO(justincohen): Temporary dependency to bring up the iOS client.
+  if (crashpad_is_ios) {
     deps += [
-      "//zircon/public/lib/fdio",
+      "../minidump",
+      "../snapshot",
     ]
   }
+
+  if (crashpad_is_linux || crashpad_is_android) {
+    deps += [ "../third_party/lss" ]
+  }
+
+  if (crashpad_is_fuchsia) {
+    deps += [ "../third_party/fuchsia" ]
+    if (crashpad_is_in_fuchsia) {
+      deps += [ "//zircon/public/lib/fdio" ]
+    }
+  }
 }
 
 source_set("client_test") {
@@ -116,6 +137,17 @@
     sources += [ "crashpad_client_win_test.cc" ]
   }
 
+  if (crashpad_is_ios) {
+    sources += [ "crashpad_client_ios_test.mm" ]
+    sources -= [
+      "annotation_list_test.cc",
+      "annotation_test.cc",
+      "crash_report_database_test.cc",
+      "prune_crash_reports_test.cc",
+      "settings_test.cc",
+    ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android) {
     sources += [ "crashpad_client_linux_test.cc" ]
   }
@@ -131,9 +163,9 @@
     "../util",
   ]
 
-  data_deps = [
-    "../handler:crashpad_handler",
-  ]
+  if (!crashpad_is_ios && !crashpad_is_fuchsia) {
+    data_deps = [ "../handler:crashpad_handler" ]
+  }
 
   if (crashpad_is_win) {
     data_deps += [ "../handler:crashpad_handler_console" ]
diff --git a/client/client.gyp b/client/client.gyp
index a23d0c8..dcb2d0e 100644
--- a/client/client.gyp
+++ b/client/client.gyp
@@ -23,6 +23,7 @@
       'dependencies': [
         '../compat/compat.gyp:crashpad_compat',
         '../third_party/mini_chromium/mini_chromium.gyp:base',
+        '../third_party/lss/lss.gyp:lss',
         '../util/util.gyp:crashpad_util',
       ],
       'include_dirs': [
diff --git a/client/client_argv_handling.cc b/client/client_argv_handling.cc
index 6933aac..742a000 100644
--- a/client/client_argv_handling.cc
+++ b/client/client_argv_handling.cc
@@ -61,14 +61,14 @@
   return argv_strings;
 }
 
-void ConvertArgvStrings(const std::vector<std::string>& argv_strings,
-                        std::vector<const char*>* argv) {
-  argv->clear();
-  argv->reserve(argv_strings.size() + 1);
-  for (const auto& arg : argv_strings) {
-    argv->push_back(arg.c_str());
+void StringVectorToCStringVector(const std::vector<std::string>& strings,
+                                 std::vector<const char*>* c_strings) {
+  c_strings->clear();
+  c_strings->reserve(strings.size() + 1);
+  for (const auto& str : strings) {
+    c_strings->push_back(str.c_str());
   }
-  argv->push_back(nullptr);
+  c_strings->push_back(nullptr);
 }
 
 }  // namespace crashpad
diff --git a/client/client_argv_handling.h b/client/client_argv_handling.h
index d380b1a..04c66d3 100644
--- a/client/client_argv_handling.h
+++ b/client/client_argv_handling.h
@@ -40,11 +40,11 @@
 //! \brief Flattens a string vector into a const char* vector suitable for use
 //!     in an exec() call.
 //!
-//! \param[in] argv_strings Arguments to be passed to child process, typically
-//!     created by BuildHandlerArgvStrings().
-//! \param[out] argv argv suitable for starting the child process.
-void ConvertArgvStrings(const std::vector<std::string>& argv_strings,
-                        std::vector<const char*>* argv);
+//! \param[in] strings A vector of string data. This vector must remain valid
+//!     for the lifetime of \a c_strings.
+//! \param[out] c_strings A vector of pointers to the string data in \a strings.
+void StringVectorToCStringVector(const std::vector<std::string>& strings,
+                                 std::vector<const char*>* c_strings);
 
 }  // namespace crashpad
 
diff --git a/client/crash_report_database.cc b/client/crash_report_database.cc
index d300a8f..eda0ef4 100644
--- a/client/crash_report_database.cc
+++ b/client/crash_report_database.cc
@@ -27,7 +27,8 @@
       uploaded(false),
       last_upload_attempt_time(0),
       upload_attempts(0),
-      upload_explicitly_requested(false) {}
+      upload_explicitly_requested(false),
+      total_size(0u) {}
 
 CrashReportDatabase::NewReport::NewReport()
     : writer_(std::make_unique<FileWriter>()),
@@ -64,6 +65,15 @@
   return true;
 }
 
+FileReaderInterface* CrashReportDatabase::NewReport::Reader() {
+  auto reader = std::make_unique<FileReader>();
+  if (!reader->Open(file_remover_.get())) {
+    return nullptr;
+  }
+  reader_ = std::move(reader);
+  return reader_.get();
+}
+
 CrashReportDatabase::UploadReport::UploadReport()
     : Report(),
       reader_(std::make_unique<FileReader>()),
diff --git a/client/crash_report_database.h b/client/crash_report_database.h
index d115c74..cc4b8df 100644
--- a/client/crash_report_database.h
+++ b/client/crash_report_database.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
 #define CRASHPAD_CLIENT_CRASH_REPORT_DATABASE_H_
 
+#include <stdint.h>
 #include <time.h>
 
 #include <map>
@@ -98,6 +99,10 @@
     //! Whether this crash report was explicitly requested by user to be
     //! uploaded. This can be true only if report is in the 'pending' state.
     bool upload_explicitly_requested;
+
+    //! The total size in bytes taken by the report, including any potential
+    //! attachments.
+    uint64_t total_size;
   };
 
   //! \brief A crash report that is in the process of being written.
@@ -108,9 +113,13 @@
     NewReport();
     ~NewReport();
 
-    //! An open FileWriter with which to write the report.
+    //! \brief An open FileWriter with which to write the report.
     FileWriter* Writer() const { return writer_.get(); }
 
+    //! \brief Returns a FileReaderInterface to the report, or `nullptr` with a
+    //!     message logged.
+    FileReaderInterface* Reader();
+
     //! A unique identifier by which this report will always be known to the
     //! database.
     const UUID& ReportID() const { return uuid_; }
@@ -137,6 +146,7 @@
                     const base::FilePath::StringType& extension);
 
     std::unique_ptr<FileWriter> writer_;
+    std::unique_ptr<FileReader> reader_;
     ScopedRemoveFile file_remover_;
     std::vector<std::unique_ptr<FileWriter>> attachment_writers_;
     std::vector<ScopedRemoveFile> attachment_removers_;
@@ -163,7 +173,7 @@
     //! This is not implemented on macOS or Windows.
     std::map<std::string, FileReader*> GetAttachments() const {
       return attachment_map_;
-    };
+    }
 
    private:
     friend class CrashReportDatabase;
diff --git a/client/crash_report_database_generic.cc b/client/crash_report_database_generic.cc
index 8e93237..aeaf2af 100644
--- a/client/crash_report_database_generic.cc
+++ b/client/crash_report_database_generic.cc
@@ -15,6 +15,7 @@
 #include "client/crash_report_database.h"
 
 #include <stdint.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 
 #include <utility>
@@ -25,6 +26,7 @@
 #include "util/file/directory_reader.h"
 #include "util/file/filesystem.h"
 #include "util/misc/initialization_state_dcheck.h"
+#include "util/misc/memory_sanitizer.h"
 
 namespace crashpad {
 
@@ -160,6 +162,34 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedLockFile);
 };
 
+off_t GetFileSize(const base::FilePath& filepath) {
+  struct stat statbuf;
+  if (stat(filepath.value().c_str(), &statbuf) == 0) {
+    return statbuf.st_size;
+  }
+  PLOG(ERROR) << "stat " << filepath.value();
+  return 0;
+}
+
+void AddAttachmentSize(const base::FilePath& attachments_dir, uint64_t* size) {
+  // Early return if the attachment directory does not exist.
+  if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
+    return;
+  }
+  DirectoryReader reader;
+  if (!reader.Open(attachments_dir)) {
+    return;
+  }
+  base::FilePath attachment_filename;
+  DirectoryReader::Result result;
+  while ((result = reader.NextFile(&attachment_filename)) ==
+         DirectoryReader::Result::kSuccess) {
+    const base::FilePath attachment_filepath(
+        attachments_dir.Append(attachment_filename));
+    *size += GetFileSize(attachment_filepath);
+  }
+}
+
 }  // namespace
 
 class CrashReportDatabaseGeneric : public CrashReportDatabase {
@@ -253,7 +283,7 @@
   void RemoveAttachmentsByUUID(const UUID& uuid);
 
   // Reads the metadata for a report from path and returns it in report.
-  static bool ReadMetadata(const base::FilePath& path, Report* report);
+  bool ReadMetadata(const base::FilePath& path, Report* report);
 
   // Wraps ReadMetadata and removes the report from the database on failure.
   bool CleaningReadMetadata(const base::FilePath& path, Report* report);
@@ -303,6 +333,9 @@
   base::FilePath attachments_dir =
       static_cast<CrashReportDatabaseGeneric*>(database_)->AttachmentsPath(
           uuid);
+  if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
+    return;
+  }
   DirectoryReader reader;
   if (!reader.Open(attachments_dir)) {
     return;
@@ -842,7 +875,6 @@
   base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory));
   DirectoryReader reader;
   if (!reader.Open(root_attachments_dir)) {
-    LOG(ERROR) << "no attachments dir";
     return;
   }
 
@@ -883,6 +915,9 @@
 
 void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) {
   base::FilePath attachments_dir = AttachmentsPath(uuid);
+  if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
+    return;
+  }
   DirectoryReader reader;
   if (!reader.Open(attachments_dir)) {
     return;
@@ -899,7 +934,6 @@
   LoggingRemoveDirectory(attachments_dir);
 }
 
-// static
 bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path,
                                               Report* report) {
   const base::FilePath metadata_path(
@@ -910,7 +944,8 @@
     return false;
   }
 
-  if (!report->uuid.InitializeFromString(
+  UUID uuid;
+  if (!uuid.InitializeFromString(
           path.BaseName().RemoveFinalExtension().value())) {
     LOG(ERROR) << "Couldn't interpret report uuid";
     return false;
@@ -930,6 +965,12 @@
     return false;
   }
 
+  // Seed the total size with the main report size and then add the sizes of any
+  // potential attachments.
+  uint64_t total_size = GetFileSize(path);
+  AddAttachmentSize(AttachmentsPath(uuid), &total_size);
+
+  report->uuid = uuid;
   report->upload_attempts = metadata.upload_attempts;
   report->last_upload_attempt_time = metadata.last_upload_attempt_time;
   report->creation_time = metadata.creation_time;
@@ -937,6 +978,7 @@
   report->upload_explicitly_requested =
       (metadata.attributes & kAttributeUploadExplicitlyRequested) != 0;
   report->file_path = path;
+  report->total_size = total_size;
   return true;
 }
 
@@ -966,6 +1008,11 @@
   }
 
   ReportMetadata metadata;
+#if defined(MEMORY_SANITIZER)
+  // memset() + re-initialization is required to zero padding bytes for MSan.
+  memset(&metadata, 0, sizeof(metadata));
+#endif  // defined(MEMORY_SANITIZER)
+  metadata = {};
   metadata.creation_time = time(nullptr);
 
   return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata));
@@ -986,6 +1033,11 @@
   }
 
   ReportMetadata metadata;
+#if defined(MEMORY_SANITIZER)
+  // memset() + re-initialization is required to zero padding bytes for MSan.
+  memset(&metadata, 0, sizeof(metadata));
+#endif  // defined(MEMORY_SANITIZER)
+  metadata = {};
   metadata.creation_time = report.creation_time;
   metadata.last_upload_attempt_time = report.last_upload_attempt_time;
   metadata.upload_attempts = report.upload_attempts;
diff --git a/client/crash_report_database_mac.mm b/client/crash_report_database_mac.mm
index 3106dc2..1f8fec9 100644
--- a/client/crash_report_database_mac.mm
+++ b/client/crash_report_database_mac.mm
@@ -29,6 +29,7 @@
 #include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/scoped_generic.h"
+#include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
@@ -279,7 +280,7 @@
   }
 
   // Create the three processing directories for the database.
-  for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
+  for (size_t i = 0; i < base::size(kReportDirectories); ++i) {
     if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
       return false;
   }
@@ -681,6 +682,14 @@
     return false;
   }
 
+  // There are no attachments on Mac so the total size is the main report size.
+  struct stat statbuf;
+  if (stat(path.value().c_str(), &statbuf) != 0) {
+    PLOG(ERROR) << "stat " << path.value();
+    return false;
+  }
+  report->total_size = statbuf.st_size;
+
   return true;
 }
 
diff --git a/client/crash_report_database_test.cc b/client/crash_report_database_test.cc
index 2dbb4fc..20512a4 100644
--- a/client/crash_report_database_test.cc
+++ b/client/crash_report_database_test.cc
@@ -20,7 +20,6 @@
 #include "test/errors.h"
 #include "test/file.h"
 #include "test/filesystem.h"
-#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
@@ -31,8 +30,7 @@
 
 class CrashReportDatabaseTest : public testing::Test {
  public:
-  CrashReportDatabaseTest() {
-  }
+  CrashReportDatabaseTest() {}
 
  protected:
   // testing::Test:
@@ -41,9 +39,7 @@
     ASSERT_TRUE(db_);
   }
 
-  void ResetDatabase() {
-    db_.reset();
-  }
+  void ResetDatabase() { db_.reset(); }
 
   CrashReportDatabase* db() { return db_.get(); }
   base::FilePath path() const {
@@ -57,6 +53,12 @@
     static constexpr char kTest[] = "test";
     ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest)));
 
+    char contents[sizeof(kTest)];
+    FileReaderInterface* reader = new_report->Reader();
+    ASSERT_TRUE(reader->ReadExactly(contents, sizeof(contents)));
+    EXPECT_EQ(memcmp(contents, kTest, sizeof(contents)), 0);
+    EXPECT_EQ(reader->ReadExactly(contents, 1), 0);
+
     UUID uuid;
     EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid),
               CrashReportDatabase::kNoError);
@@ -101,6 +103,7 @@
     EXPECT_EQ(report.last_upload_attempt_time, 0);
     EXPECT_EQ(report.upload_attempts, 0);
     EXPECT_FALSE(report.upload_explicitly_requested);
+    EXPECT_GE(report.total_size, 0u);
   }
 
   void RelocateDatabase() {
@@ -673,7 +676,7 @@
 TEST_F(CrashReportDatabaseTest, Attachments) {
 #if defined(OS_MACOSX) || defined(OS_WIN)
   // Attachments aren't supported on Mac and Windows yet.
-  DISABLED_TEST();
+  GTEST_SKIP();
 #else
   std::unique_ptr<CrashReportDatabase::NewReport> new_report;
   ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
@@ -719,7 +722,7 @@
 TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
 #if defined(OS_MACOSX) || defined(OS_WIN)
   // Attachments aren't supported on Mac and Windows yet.
-  DISABLED_TEST();
+  GTEST_SKIP();
 #else
   // TODO: This is using paths that are specific to the generic implementation
   // and will need to be generalized for Mac and Windows.
@@ -835,6 +838,66 @@
 }
 #endif  // !OS_MACOSX && !OS_WIN
 
+TEST_F(CrashReportDatabaseTest, TotalSize_MainReportOnly) {
+  std::unique_ptr<CrashReportDatabase::NewReport> new_report;
+  ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
+            CrashReportDatabase::kNoError);
+
+  // Main report.
+  static constexpr char main_report_data[] = "dlbvandslhb";
+  ASSERT_TRUE(
+      new_report->Writer()->Write(main_report_data, sizeof(main_report_data)));
+
+  UUID uuid;
+  ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
+            CrashReportDatabase::kNoError);
+
+  CrashReportDatabase::Report report;
+  ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
+            CrashReportDatabase::kNoError);
+
+  EXPECT_EQ(report.total_size, sizeof(main_report_data));
+}
+
+TEST_F(CrashReportDatabaseTest, GetReportSize_RightSizeWithAttachments) {
+#if defined(OS_MACOSX) || defined(OS_WIN)
+  // Attachments aren't supported on Mac and Windows yet.
+  return;
+#else
+  std::unique_ptr<CrashReportDatabase::NewReport> new_report;
+  ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
+            CrashReportDatabase::kNoError);
+
+  // Main report.
+  static constexpr char main_report_data[] = "dlbvandslhb";
+  ASSERT_TRUE(
+      new_report->Writer()->Write(main_report_data, sizeof(main_report_data)));
+
+  // First attachment.
+  FileWriter* attachment_1 = new_report->AddAttachment("my_attachment_1");
+  ASSERT_NE(attachment_1, nullptr);
+  static constexpr char attachment_1_data[] = "vKDnidhvbiudshoihbvdsoiuh nhh";
+  attachment_1->Write(attachment_1_data, sizeof(attachment_1_data));
+
+  // Second attachment.
+  FileWriter* attachment_2 = new_report->AddAttachment("my_attachment_2");
+  ASSERT_NE(attachment_2, nullptr);
+  static constexpr char attachment_2_data[] = "sgvsvgusiyguysigfkhpmo-[";
+  attachment_2->Write(attachment_2_data, sizeof(attachment_2_data));
+
+  UUID uuid;
+  ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid),
+            CrashReportDatabase::kNoError);
+
+  CrashReportDatabase::Report report;
+  ASSERT_EQ(db()->LookUpCrashReport(uuid, &report),
+            CrashReportDatabase::kNoError);
+  EXPECT_EQ(report.total_size,
+            sizeof(main_report_data) + sizeof(attachment_1_data) +
+                sizeof(attachment_2_data));
+#endif
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/client/crash_report_database_win.cc b/client/crash_report_database_win.cc
index 8967770..15137d0 100644
--- a/client/crash_report_database_win.cc
+++ b/client/crash_report_database_win.cc
@@ -458,7 +458,18 @@
         LOG(ERROR) << "invalid string table index";
         return;
       }
-      reports.push_back(ReportDisk(record, report_dir_, string_table));
+      ReportDisk report_disk(record, report_dir_, string_table);
+
+      // There are no attachments on Windows so the total size is the main
+      // report size.
+      struct _stati64 statbuf;
+      if (_wstat64(report_disk.file_path.value().c_str(), &statbuf) == 0) {
+        report_disk.total_size = statbuf.st_size;
+      } else {
+        LOG(ERROR) << "failed to stat report";
+      }
+
+      reports.push_back(report_disk);
     }
   }
   reports_.swap(reports);
diff --git a/client/crashpad_client.h b/client/crashpad_client.h
index ae409d4..f5e02c9 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -16,6 +16,7 @@
 #define CRASHPAD_CLIENT_CRASHPAD_CLIENT_H_
 
 #include <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -24,6 +25,7 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "util/file/file_io.h"
 #include "util/misc/capture_context.h"
 
 #if defined(OS_MACOSX)
@@ -78,6 +80,10 @@
   //! On Fuchsia, this method binds to the exception port of the current default
   //! job, and starts a Crashpad handler to monitor that port.
   //!
+  //! On Linux, this method starts a Crashpad handler, connected to this process
+  //! via an `AF_UNIX` socket pair and installs signal handlers to request crash
+  //! dumps on the client's socket end.
+  //!
   //! \param[in] handler The path to a Crashpad handler executable.
   //! \param[in] database The path to a Crashpad database. The handler will be
   //!     started with this path as its `--database` argument.
@@ -112,6 +118,199 @@
                     bool restartable,
                     bool asynchronous_start);
 
+#if defined(OS_ANDROID) || defined(OS_LINUX) || DOXYGEN
+  //! \brief Retrieve the socket and process ID for the handler.
+  //!
+  //! `StartHandler()` must have successfully been called before calling this
+  //!     method.
+  //!
+  //! \param[out] sock The socket connected to the handler, if not `nullptr`.
+  //! \param[out] pid The handler's process ID, if not `nullptr`.
+  //! \return `true` on success. Otherwise `false` with a message logged.
+  static bool GetHandlerSocket(int* sock, pid_t* pid);
+
+  //! \brief Sets the socket to a presumably-running Crashpad handler process
+  //!      which was started with StartHandler().
+  //!
+  //! This method installs a signal handler to request crash dumps on \a sock.
+  //!
+  //! \param[in] sock A socket connected to a Crashpad handler.
+  //! \param[in] pid The process ID of the handler, used to set the handler as
+  //!     this process' ptracer. 0 indicates it is not necessary to set the
+  //!     handler as this process' ptracer. -1 indicates that the handler's
+  //!     process ID should be determined by communicating over the socket.
+  bool SetHandlerSocket(ScopedFileHandle sock, pid_t pid);
+#endif  // OS_ANDROID || OS_LINUX || DOXYGEN
+
+#if defined(OS_ANDROID) || DOXYGEN
+  //! \brief Installs a signal handler to execute `/system/bin/app_process` and
+  //!     load a Java class in response to a crash.
+  //!
+  //! \param[in] class_name The fully qualified class name to load, which must
+  //!     define a `main()` method to be invoked by `app_process`. Arguments
+  //!     will be passed to this method as though it were the Crashpad handler.
+  //!     This class is expected to load a native library defining
+  //!     crashpad::HandlerMain() and pass the arguments to it.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's environment at the time of the
+  //!     crash will be used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  bool StartJavaHandlerAtCrash(
+      const std::string& class_name,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments);
+
+  //! \brief Executes `/system/bin/app_process` and loads a Java class.
+  //!
+  //! \param[in] class_name The fully qualified class name to load, which must
+  //!     define a `main()` method to be invoked by `app_process`. Arguments
+  //!     will be passed to this method as though it were the Crashpad handler.
+  //!     This class is expected to load a native library defining
+  //!     crashpad::HandlerMain() and pass the arguments to it.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's current environment will be
+  //!     used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //! \param[in] socket The server end of a socket pair. The client end should
+  //!     be used with an ExceptionHandlerClient.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool StartJavaHandlerForClient(
+      const std::string& class_name,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments,
+      int socket);
+
+  //! \brief Installs a signal handler to start a Crashpad handler process by
+  //!     loading it with `/system/bin/linker`.
+  //!
+  //! This method is only supported by Android Q+.
+  //!
+  //! \param[in] handler_trampoline The path to a Crashpad handler trampoline
+  //!     executable, possibly located within an apk, e.g.
+  //!     "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
+  //! \param[in] handler_library The name of a library exporting the symbol
+  //!     `CrashpadHandlerMain()`. The path to this library must be present in
+  //!     `LD_LIBRARY_PATH`.
+  //! \param[in] is_64_bit `true` if \a handler_trampoline and \a
+  //!     handler_library are 64-bit objects. They must have the same bitness.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's environment at the time of the
+  //!     crash will be used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  bool StartHandlerWithLinkerAtCrash(
+      const std::string& handler_trampoline,
+      const std::string& handler_library,
+      bool is_64_bit,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments);
+
+  //! \brief Starts a Crashpad handler process with an initial client by loading
+  //!     it with `/system/bin/linker`.
+  //!
+  //! This method is only supported by Android Q+.
+  //!
+  //! \param[in] handler_trampoline The path to a Crashpad handler trampoline
+  //!     executable, possibly located within an apk, e.g.
+  //!     "/data/app/myapk.apk!/myabi/libcrashpad_handler_trampoline.so".
+  //! \param[in] handler_library The name of a library exporting the symbol
+  //!     `CrashpadHandlerMain()`. The path to this library must be present in
+  //!     `LD_LIBRARY_PATH`.
+  //! \param[in] is_64_bit `true` if \a handler_trampoline and \a
+  //!     handler_library are 64-bit objects. They must have the same bitness.
+  //! \param[in] env A vector of environment variables of the form `var=value`
+  //!     defining the environment in which to execute `app_process`. If this
+  //!     value is `nullptr`, the application's current environment will be
+  //!     used.
+  //! \param[in] database The path to a Crashpad database. The handler will be
+  //!     started with this path as its `--database` argument.
+  //! \param[in] metrics_dir The path to an already existing directory where
+  //!     metrics files can be stored. The handler will be started with this
+  //!     path as its `--metrics-dir` argument.
+  //! \param[in] url The URL of an upload server. The handler will be started
+  //!     with this URL as its `--url` argument.
+  //! \param[in] annotations Process annotations to set in each crash report.
+  //!     The handler will be started with an `--annotation` argument for each
+  //!     element in this map.
+  //! \param[in] arguments Additional arguments to pass to the Crashpad handler.
+  //!     Arguments passed in other parameters and arguments required to perform
+  //!     the handshake are the responsibility of this method, and must not be
+  //!     specified in this parameter.
+  //! \param[in] socket The server end of a socket pair. The client end should
+  //!     be used with an ExceptionHandlerClient.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool StartHandlerWithLinkerForClient(
+      const std::string& handler_trampoline,
+      const std::string& handler_library,
+      bool is_64_bit,
+      const std::vector<std::string>* env,
+      const base::FilePath& database,
+      const base::FilePath& metrics_dir,
+      const std::string& url,
+      const std::map<std::string, std::string>& annotations,
+      const std::vector<std::string>& arguments,
+      int socket);
+#endif  // OS_ANDROID || DOXYGEN
+
 #if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
   //! \brief Installs a signal handler to launch a handler process in reponse to
   //!     a crash.
@@ -135,7 +334,7 @@
   //!     specified in this parameter.
   //!
   //! \return `true` on success, `false` on failure with a message logged.
-  static bool StartHandlerAtCrash(
+  bool StartHandlerAtCrash(
       const base::FilePath& handler,
       const base::FilePath& database,
       const base::FilePath& metrics_dir,
@@ -188,6 +387,12 @@
   //!     CaptureContext() or similar.
   static void DumpWithoutCrash(NativeCPUContext* context);
 
+  //! \brief Disables any installed crash handler, including any
+  //!     FirstChanceHandler and crashes the current process.
+  //!
+  //! \param[in] message A message to be logged before crashing.
+  static void CrashWithoutDump(const std::string& message);
+
   //! \brief The type for custom handlers installed by clients.
   using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*);
 
@@ -209,8 +414,38 @@
   //! \param[in] handler The custom crash signal handler to install.
   static void SetFirstChanceExceptionHandler(FirstChanceHandler handler);
 
+  //! \brief Configures a set of signals that shouldn't have Crashpad signal
+  //!     handlers installed.
+  //!
+  //! This method should be called before calling StartHandler(),
+  //! SetHandlerSocket(), or other methods that install Crashpad signal
+  //! handlers.
+  //!
+  //! \param[in] unhandled_signals The set of unhandled signals
+  void SetUnhandledSignals(const std::set<int>& unhandled_signals);
 #endif  // OS_LINUX || OS_ANDROID || DOXYGEN
 
+#if defined(OS_IOS) || DOXYGEN
+  //! \brief Configures the process to direct its crashes to the iOS in-process
+  //! Crashpad handler.
+  //!
+  //! This method is only defined on iOS.
+  //!
+  //! TODO(justincohen): This method will need to take database, metrics_dir,
+  //! url and annotations eventually.
+  void StartCrashpadInProcessHandler();
+
+  // TODO(justincohen): This method is purely for bringing up iOS interfaces.
+  //! \brief Requests that the handler capture a dump even though there hasn't
+  //!     been a crash.
+  //!
+  //! A handler must have already been installed before calling this method.
+  //!
+  //! \param[in] context A NativeCPUContext, generally captured by
+  //!     CaptureContext() or similar.
+  static void DumpWithoutCrash(NativeCPUContext* context);
+#endif
+
 #if defined(OS_MACOSX) || DOXYGEN
   //! \brief Sets the process’ crash handler to a Mach service registered with
   //!     the bootstrap server.
@@ -384,12 +619,24 @@
   static void UseSystemDefaultHandler();
 #endif
 
+#if defined(OS_CHROMEOS)
+  //! \brief Sets a timestamp on the signal handler to be passed on to
+  //!     crashpad_handler and then eventually Chrome OS's crash_reporter.
+  //!
+  //! \note This method is used by clients that use `StartHandler()` to start
+  //!     a handler and not by clients that use any other handler starting
+  //!     methods.
+  static void SetCrashLoopBefore(uint64_t crash_loop_before_time);
+#endif
+
  private:
 #if defined(OS_MACOSX)
   base::mac::ScopedMachSendRight exception_port_;
 #elif defined(OS_WIN)
   std::wstring ipc_pipe_;
   ScopedKernelHANDLE handler_start_thread_;
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
+  std::set<int> unhandled_signals_;
 #endif  // OS_MACOSX
 
   DISALLOW_COPY_AND_ASSIGN(CrashpadClient);
diff --git a/client/crashpad_client_fuchsia.cc b/client/crashpad_client_fuchsia.cc
index 0d1b65b..3da1f76 100644
--- a/client/crashpad_client_fuchsia.cc
+++ b/client/crashpad_client_fuchsia.cc
@@ -15,15 +15,15 @@
 #include "client/crashpad_client.h"
 
 #include <lib/fdio/spawn.h>
-#include <zircon/process.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/job.h>
+#include <lib/zx/process.h>
 #include <zircon/processargs.h>
 
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/scoped_zx_handle.h"
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "client/client_argv_handling.h"
-#include "util/fuchsia/system_exception_port_key.h"
 
 namespace crashpad {
 
@@ -43,52 +43,44 @@
   DCHECK_EQ(restartable, false);  // Not used on Fuchsia.
   DCHECK_EQ(asynchronous_start, false);  // Not used on Fuchsia.
 
-  zx_handle_t exception_port_raw;
-  zx_status_t status = zx_port_create(0, &exception_port_raw);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "zx_port_create";
-    return false;
-  }
-  base::ScopedZxHandle exception_port(exception_port_raw);
-
-  status = zx_task_bind_exception_port(
-      zx_job_default(), exception_port.get(), kSystemExceptionPortKey, 0);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "zx_task_bind_exception_port";
-    return false;
-  }
-
   std::vector<std::string> argv_strings = BuildHandlerArgvStrings(
       handler, database, metrics_dir, url, annotations, arguments);
 
   std::vector<const char*> argv;
-  ConvertArgvStrings(argv_strings, &argv);
+  StringVectorToCStringVector(argv_strings, &argv);
 
-  // Follow the same protocol as devmgr and crashlogger in Zircon (that is,
-  // process handle as handle 0, with type USER0, exception port handle as
-  // handle 1, also with type PA_USER0) so that it's trivial to replace
-  // crashlogger with crashpad_handler. The exception port is passed on, so
-  // released here. Currently it is assumed that this process's default job
-  // handle is the exception port that should be monitored. In the future, it
-  // might be useful for this to be configurable by the client.
-  constexpr size_t kActionCount = 2;
-  fdio_spawn_action_t actions[] = {
-      {.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
-       .h = {.id = PA_HND(PA_USER0, 0), .handle = ZX_HANDLE_INVALID}},
-      {.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
-       .h = {.id = PA_HND(PA_USER0, 1), .handle = ZX_HANDLE_INVALID}},
-  };
-
-  status = zx_handle_duplicate(
-      zx_job_default(), ZX_RIGHT_SAME_RIGHTS, &actions[0].h.handle);
+  // Set up handles to send to the spawned process:
+  //   0. PA_USER0 job
+  //   1. PA_USER0 exception channel
+  //
+  // Currently it is assumed that this process's default job handle is the
+  // exception channel that should be monitored. In the future, it might be
+  // useful for this to be configurable by the client.
+  zx::job job;
+  zx_status_t status =
+      zx::job::default_job()->duplicate(ZX_RIGHT_SAME_RIGHTS, &job);
   if (status != ZX_OK) {
     ZX_LOG(ERROR, status) << "zx_handle_duplicate";
     return false;
   }
-  actions[1].h.handle = exception_port.release();
+
+  zx::channel exception_channel;
+  status = job.create_exception_channel(0, &exception_channel);
+  if (status != ZX_OK) {
+    ZX_LOG(ERROR, status) << "zx_task_create_exception_channel";
+    return false;
+  }
+
+  constexpr size_t kActionCount = 2;
+  fdio_spawn_action_t actions[] = {
+      {.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
+       .h = {.id = PA_HND(PA_USER0, 0), .handle = job.release()}},
+      {.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
+       .h = {.id = PA_HND(PA_USER0, 1), .handle = exception_channel.release()}},
+  };
 
   char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
-  zx_handle_t child_raw;
+  zx::process child;
   // TODO(scottmg): https://crashpad.chromium.org/bug/196, FDIO_SPAWN_CLONE_ALL
   // is useful during bringup, but should probably be made minimal for real
   // usage.
@@ -99,9 +91,8 @@
                           nullptr,
                           kActionCount,
                           actions,
-                          &child_raw,
+                          child.reset_and_get_address(),
                           error_message);
-  base::ScopedZxHandle child(child_raw);
   if (status != ZX_OK) {
     ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message;
     return false;
diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc
new file mode 100644
index 0000000..342e109
--- /dev/null
+++ b/client/crashpad_client_ios.cc
@@ -0,0 +1,228 @@
+// Copyright 2020 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.
+
+#include "client/crashpad_client.h"
+
+#include <unistd.h>
+
+#include <ios>
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/stl_util.h"
+#include "snapshot/ios/process_snapshot_ios.h"
+#include "util/ios/exception_processor.h"
+#include "util/ios/ios_system_data_collector.h"
+#include "util/mach/exc_server_variants.h"
+#include "util/mach/exception_ports.h"
+#include "util/mach/mach_extensions.h"
+#include "util/mach/mach_message.h"
+#include "util/mach/mach_message_server.h"
+#include "util/misc/initialization_state_dcheck.h"
+#include "util/posix/signals.h"
+#include "util/thread/thread.h"
+
+namespace crashpad {
+
+namespace {
+
+// A base class for signal handler and Mach exception server.
+class CrashHandler : public Thread, public UniversalMachExcServer::Interface {
+ public:
+  static CrashHandler* Get() {
+    static CrashHandler* instance = new CrashHandler();
+    return instance;
+  }
+
+  void Initialize() {
+    INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+    InstallMachExceptionHandler();
+    CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_));
+    INITIALIZATION_STATE_SET_VALID(initialized_);
+  }
+
+  void DumpWithoutCrash(NativeCPUContext* context) {
+    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+    mach_exception_data_type_t code[2] = {};
+    static constexpr int kSimulatedException = -1;
+    HandleMachException(MACH_EXCEPTION_CODES,
+                        mach_thread_self(),
+                        kSimulatedException,
+                        code,
+                        base::size(code),
+                        MACHINE_THREAD_STATE,
+                        reinterpret_cast<ConstThreadState>(context),
+                        MACHINE_THREAD_STATE_COUNT);
+  }
+
+ private:
+  CrashHandler() = default;
+
+  void InstallMachExceptionHandler() {
+    exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
+    CHECK(exception_port_.is_valid());
+
+    kern_return_t kr = mach_port_insert_right(mach_task_self(),
+                                              exception_port_.get(),
+                                              exception_port_.get(),
+                                              MACH_MSG_TYPE_MAKE_SEND);
+    MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right";
+
+    // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
+    const exception_mask_t mask =
+        ExcMaskAll() &
+        ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT |
+          EXC_MASK_RPC_ALERT | EXC_MASK_GUARD);
+    ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
+    exception_ports.GetExceptionPorts(mask, &original_handlers_);
+    exception_ports.SetExceptionPort(
+        mask,
+        exception_port_.get(),
+        EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
+        MACHINE_THREAD_STATE);
+
+    Start();
+  }
+
+  // Thread:
+
+  void ThreadMain() override {
+    UniversalMachExcServer universal_mach_exc_server(this);
+    while (true) {
+      mach_msg_return_t mr =
+          MachMessageServer::Run(&universal_mach_exc_server,
+                                 exception_port_.get(),
+                                 MACH_MSG_OPTION_NONE,
+                                 MachMessageServer::kPersistent,
+                                 MachMessageServer::kReceiveLargeIgnore,
+                                 kMachMessageTimeoutWaitIndefinitely);
+      MACH_CHECK(mr == MACH_SEND_INVALID_DEST, mr) << "MachMessageServer::Run";
+    }
+  }
+
+  // UniversalMachExcServer::Interface:
+
+  kern_return_t CatchMachException(exception_behavior_t behavior,
+                                   exception_handler_t exception_port,
+                                   thread_t thread,
+                                   task_t task,
+                                   exception_type_t exception,
+                                   const mach_exception_data_type_t* code,
+                                   mach_msg_type_number_t code_count,
+                                   thread_state_flavor_t* flavor,
+                                   ConstThreadState old_state,
+                                   mach_msg_type_number_t old_state_count,
+                                   thread_state_t new_state,
+                                   mach_msg_type_number_t* new_state_count,
+                                   const mach_msg_trailer_t* trailer,
+                                   bool* destroy_complex_request) override {
+    *destroy_complex_request = true;
+
+    // TODO(justincohen): Forward exceptions to original_handlers_ with
+    // UniversalExceptionRaise.
+
+    // iOS shouldn't have any child processes, but just in case, those will
+    // inherit the task exception ports, and this process isn’t prepared to
+    // handle them
+    if (task != mach_task_self()) {
+      LOG(WARNING) << "task 0x" << std::hex << task << " != 0x"
+                   << mach_task_self();
+      return KERN_FAILURE;
+    }
+
+    HandleMachException(behavior,
+                        thread,
+                        exception,
+                        code,
+                        code_count,
+                        *flavor,
+                        old_state,
+                        old_state_count);
+
+    // Respond with KERN_FAILURE so the system will continue to handle this
+    // exception as a crash.
+    return KERN_FAILURE;
+  }
+
+  void HandleMachException(exception_behavior_t behavior,
+                           thread_t thread,
+                           exception_type_t exception,
+                           const mach_exception_data_type_t* code,
+                           mach_msg_type_number_t code_count,
+                           thread_state_flavor_t flavor,
+                           ConstThreadState old_state,
+                           mach_msg_type_number_t old_state_count) {
+    // TODO(justincohen): This is incomplete.
+    ProcessSnapshotIOS process_snapshot;
+    process_snapshot.Initialize(system_data_);
+    process_snapshot.SetExceptionFromMachException(behavior,
+                                                   thread,
+                                                   exception,
+                                                   code,
+                                                   code_count,
+                                                   flavor,
+                                                   old_state,
+                                                   old_state_count);
+  }
+
+  // The signal handler installed at OS-level.
+  static void CatchSignal(int signo, siginfo_t* siginfo, void* context) {
+    Get()->HandleAndReraiseSignal(
+        signo, siginfo, reinterpret_cast<ucontext_t*>(context));
+  }
+
+  void HandleAndReraiseSignal(int signo,
+                              siginfo_t* siginfo,
+                              ucontext_t* context) {
+    // TODO(justincohen): This is incomplete.
+    ProcessSnapshotIOS process_snapshot;
+    process_snapshot.Initialize(system_data_);
+    process_snapshot.SetExceptionFromSignal(siginfo, context);
+
+    // Always call system handler.
+    Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_);
+  }
+
+  base::mac::ScopedMachReceiveRight exception_port_;
+  ExceptionPorts::ExceptionHandlerVector original_handlers_;
+  struct sigaction old_action_ = {};
+  IOSSystemDataCollector system_data_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrashHandler);
+};
+
+}  // namespace
+
+CrashpadClient::CrashpadClient() {}
+
+CrashpadClient::~CrashpadClient() {}
+
+void CrashpadClient::StartCrashpadInProcessHandler() {
+  InstallObjcExceptionPreprocessor();
+
+  CrashHandler* crash_handler = CrashHandler::Get();
+  DCHECK(crash_handler);
+  crash_handler->Initialize();
+}
+
+// static
+void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
+  CrashHandler* crash_handler = CrashHandler::Get();
+  DCHECK(crash_handler);
+  crash_handler->DumpWithoutCrash(context);
+}
+
+}  // namespace crashpad
diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm
new file mode 100644
index 0000000..002fddb
--- /dev/null
+++ b/client/crashpad_client_ios_test.mm
@@ -0,0 +1,73 @@
+// Copyright 2020 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.
+
+#include "client/crashpad_client.h"
+
+#import <Foundation/Foundation.h>
+
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using CrashpadIOSClient = PlatformTest;
+
+TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
+  CrashpadClient client;
+  client.StartCrashpadInProcessHandler();
+
+  NativeCPUContext context;
+#if defined(ARCH_CPU_X86_64)
+  CaptureContext(&context);
+#elif defined(ARCH_CPU_ARM64)
+  // TODO(justincohen): Implement CaptureContext for ARM64.
+  mach_msg_type_number_t thread_state_count = MACHINE_THREAD_STATE_COUNT;
+  kern_return_t kr =
+      thread_get_state(mach_thread_self(),
+                       MACHINE_THREAD_STATE,
+                       reinterpret_cast<thread_state_t>(&context),
+                       &thread_state_count);
+  ASSERT_EQ(kr, KERN_SUCCESS);
+#endif
+  client.DumpWithoutCrash(&context);
+}
+
+// This test is covered by a similar XCUITest, but for development purposes
+// it's sometimes easier and faster to run as a gtest.  However, there's no
+// way to correctly run this as a gtest. Leave the test here, disabled, for use
+// during development only.
+TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
+  CrashpadClient client;
+  client.StartCrashpadInProcessHandler();
+  [NSException raise:@"GtestNSException" format:@"ThrowException"];
+}
+
+// This test is covered by a similar XCUITest, but for development purposes
+// it's sometimes easier and faster to run as a gtest.  However, there's no
+// way to correctly run this as a gtest. Leave the test here, disabled, for use
+// during development only.
+TEST_F(CrashpadIOSClient, DISABLED_ThrowException) {
+  CrashpadClient client;
+  client.StartCrashpadInProcessHandler();
+  std::vector<int> empty_vector;
+  empty_vector.at(42);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc
index 54fe268..98c3d09 100644
--- a/client/crashpad_client_linux.cc
+++ b/client/crashpad_client_linux.cc
@@ -16,6 +16,7 @@
 
 #include <fcntl.h>
 #include <stdlib.h>
+#include <sys/prctl.h>
 #include <sys/socket.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
@@ -25,10 +26,14 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "client/client_argv_handling.h"
+#include "third_party/lss/lss.h"
 #include "util/file/file_io.h"
+#include "util/file/filesystem.h"
 #include "util/linux/exception_handler_client.h"
 #include "util/linux/exception_information.h"
+#include "util/linux/scoped_pr_set_dumpable.h"
 #include "util/linux/scoped_pr_set_ptracer.h"
+#include "util/linux/socket.h"
 #include "util/misc/from_pointer_cast.h"
 #include "util/posix/double_fork_and_exec.h"
 #include "util/posix/signals.h"
@@ -41,51 +46,103 @@
   return base::StringPrintf("--%s=%d", name.c_str(), value);
 }
 
-std::string FormatArgumentAddress(const std::string& name, void* addr) {
+std::string FormatArgumentAddress(const std::string& name, const void* addr) {
   return base::StringPrintf("--%s=%p", name.c_str(), addr);
 }
 
+#if defined(OS_ANDROID)
+
+std::vector<std::string> BuildAppProcessArgs(
+    const std::string& class_name,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+#if defined(ARCH_CPU_64_BITS)
+  static constexpr char kAppProcess[] = "/system/bin/app_process64";
+#else
+  static constexpr char kAppProcess[] = "/system/bin/app_process32";
+#endif
+
+  std::vector<std::string> argv;
+  argv.push_back(kAppProcess);
+  argv.push_back("/system/bin");
+  argv.push_back("--application");
+  argv.push_back(class_name);
+
+  std::vector<std::string> handler_argv =
+      BuildHandlerArgvStrings(base::FilePath(kAppProcess),
+                              database,
+                              metrics_dir,
+                              url,
+                              annotations,
+                              arguments);
+
+  if (socket != kInvalidFileHandle) {
+    handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket));
+  }
+
+  argv.insert(argv.end(), handler_argv.begin(), handler_argv.end());
+  return argv;
+}
+
+std::vector<std::string> BuildArgsToLaunchWithLinker(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+  std::vector<std::string> argv;
+  if (is_64_bit) {
+    argv.push_back("/system/bin/linker64");
+  } else {
+    argv.push_back("/system/bin/linker");
+  }
+  argv.push_back(handler_trampoline);
+  argv.push_back(handler_library);
+
+  std::vector<std::string> handler_argv = BuildHandlerArgvStrings(
+      base::FilePath(), database, metrics_dir, url, annotations, arguments);
+
+  if (socket != kInvalidFileHandle) {
+    handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket));
+  }
+
+  argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end());
+  return argv;
+}
+
+#endif  // OS_ANDROID
+
+// A base class for Crashpad signal handler implementations.
 class SignalHandler {
  public:
-  virtual void HandleCrashFatal(int signo,
-                                siginfo_t* siginfo,
-                                void* context) = 0;
-  virtual bool HandleCrashNonFatal(int signo,
-                                   siginfo_t* siginfo,
-                                   void* context) = 0;
+  // Returns the currently installed signal hander. May be `nullptr` if no
+  // handler has been installed.
+  static SignalHandler* Get() { return handler_; }
+
+  // Disables any installed Crashpad signal handler for the calling thread. If a
+  // crash signal is received, any previously installed (non-Crashpad) signal
+  // handler will be restored and the signal reraised.
+  static void DisableForThread() { disabled_for_thread_ = true; }
 
   void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) {
     first_chance_handler_ = handler;
   }
 
- protected:
-  SignalHandler() = default;
-  ~SignalHandler() = default;
+  // The base implementation for all signal handlers, suitable for calling
+  // directly to simulate signal delivery.
+  bool HandleCrash(int signo, siginfo_t* siginfo, void* context) {
+    if (disabled_for_thread_) {
+      return false;
+    }
 
-  CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr;
-};
-
-// Launches a single use handler to snapshot this process.
-class LaunchAtCrashHandler : public SignalHandler {
- public:
-  static LaunchAtCrashHandler* Get() {
-    static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler();
-    return instance;
-  }
-
-  bool Initialize(std::vector<std::string>* argv_in) {
-    argv_strings_.swap(*argv_in);
-
-    argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception",
-                                                  &exception_information_));
-
-    ConvertArgvStrings(argv_strings_, &argv_);
-    return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr);
-  }
-
-  bool HandleCrashNonFatal(int signo,
-                           siginfo_t* siginfo,
-                           void* context) override {
     if (first_chance_handler_ &&
         first_chance_handler_(
             signo, siginfo, static_cast<ucontext_t*>(context))) {
@@ -98,29 +155,100 @@
     exception_information_.context_address =
         FromPointerCast<decltype(exception_information_.context_address)>(
             context);
-    exception_information_.thread_id = syscall(SYS_gettid);
+    exception_information_.thread_id = sys_gettid();
 
-    ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ false);
+    ScopedPrSetDumpable set_dumpable(false);
+    HandleCrashImpl();
+    return false;
+  }
+
+ protected:
+  SignalHandler() = default;
+
+  bool Install(const std::set<int>* unhandled_signals) {
+    DCHECK(!handler_);
+    handler_ = this;
+    return Signals::InstallCrashHandlers(
+        HandleOrReraiseSignal, 0, &old_actions_, unhandled_signals);
+  }
+
+  const ExceptionInformation& GetExceptionInfo() {
+    return exception_information_;
+  }
+
+  virtual void HandleCrashImpl() = 0;
+
+ private:
+  // The signal handler installed at OS-level.
+  static void HandleOrReraiseSignal(int signo,
+                                    siginfo_t* siginfo,
+                                    void* context) {
+    if (handler_->HandleCrash(signo, siginfo, context)) {
+      return;
+    }
+    Signals::RestoreHandlerAndReraiseSignalOnReturn(
+        siginfo, handler_->old_actions_.ActionForSignal(signo));
+  }
+
+  Signals::OldActions old_actions_ = {};
+  ExceptionInformation exception_information_ = {};
+  CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr;
+
+  static SignalHandler* handler_;
+
+  static thread_local bool disabled_for_thread_;
+
+  DISALLOW_COPY_AND_ASSIGN(SignalHandler);
+};
+SignalHandler* SignalHandler::handler_ = nullptr;
+thread_local bool SignalHandler::disabled_for_thread_ = false;
+
+// Launches a single use handler to snapshot this process.
+class LaunchAtCrashHandler : public SignalHandler {
+ public:
+  static LaunchAtCrashHandler* Get() {
+    static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler();
+    return instance;
+  }
+
+  bool Initialize(std::vector<std::string>* argv_in,
+                  const std::vector<std::string>* envp,
+                  const std::set<int>* unhandled_signals) {
+    argv_strings_.swap(*argv_in);
+
+    if (envp) {
+      envp_strings_ = *envp;
+      StringVectorToCStringVector(envp_strings_, &envp_);
+      set_envp_ = true;
+    }
+
+    argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception",
+                                                  &GetExceptionInfo()));
+
+    StringVectorToCStringVector(argv_strings_, &argv_);
+    return Install(unhandled_signals);
+  }
+
+  void HandleCrashImpl() override {
+    ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false);
 
     pid_t pid = fork();
     if (pid < 0) {
-      return false;
+      return;
     }
     if (pid == 0) {
-      execv(argv_[0], const_cast<char* const*>(argv_.data()));
+      if (set_envp_) {
+        execve(argv_[0],
+               const_cast<char* const*>(argv_.data()),
+               const_cast<char* const*>(envp_.data()));
+      } else {
+        execv(argv_[0], const_cast<char* const*>(argv_.data()));
+      }
       _exit(EXIT_FAILURE);
     }
 
     int status;
     waitpid(pid, &status, 0);
-    return false;
-  }
-
-  void HandleCrashFatal(int signo, siginfo_t* siginfo, void* context) override {
-    if (HandleCrashNonFatal(signo, siginfo, context)) {
-      return;
-    }
-    Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
   }
 
  private:
@@ -128,22 +256,107 @@
 
   ~LaunchAtCrashHandler() = delete;
 
-  static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
-    auto state = Get();
-    state->HandleCrashFatal(signo, siginfo, context);
-  }
-
   std::vector<std::string> argv_strings_;
   std::vector<const char*> argv_;
-  ExceptionInformation exception_information_;
+  std::vector<std::string> envp_strings_;
+  std::vector<const char*> envp_;
+  bool set_envp_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler);
 };
 
-// A pointer to the currently installed crash signal handler. This allows
-// the static method CrashpadClient::DumpWithoutCrashing to simulate a crash
-// using the currently configured crash handling strategy.
-static SignalHandler* g_crash_handler;
+class RequestCrashDumpHandler : public SignalHandler {
+ public:
+  static RequestCrashDumpHandler* Get() {
+    static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler();
+    return instance;
+  }
+
+  // pid < 0 indicates the handler pid should be determined by communicating
+  // over the socket.
+  // pid == 0 indicates it is not necessary to set the handler as this process'
+  // ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a
+  // user namespace and the handler's uid matches the uid of the process that
+  // created the namespace.
+  // pid > 0 directly indicates what the handler's pid is expected to be, so
+  // retrieving this information from the handler is not necessary.
+  bool Initialize(ScopedFileHandle sock,
+                  pid_t pid,
+                  const std::set<int>* unhandled_signals) {
+    ExceptionHandlerClient client(sock.get(), true);
+    if (pid < 0) {
+      ucred creds;
+      if (!client.GetHandlerCredentials(&creds)) {
+        return false;
+      }
+      pid = creds.pid;
+    }
+    if (pid > 0 && prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) {
+      PLOG(WARNING) << "prctl";
+      // TODO(jperaza): If this call to set the ptracer failed, it might be
+      // possible to try again just before a dump request, in case the
+      // environment has changed. Revisit ExceptionHandlerClient::SetPtracer()
+      // and consider saving the result of this call in ExceptionHandlerClient
+      // or as a member in this signal handler. ExceptionHandlerClient hasn't
+      // been responsible for maintaining state and a new ExceptionHandlerClient
+      // has been constructed as a local whenever a client needs to communicate
+      // with the handler. ExceptionHandlerClient lifetimes and ownership will
+      // need to be reconsidered if it becomes responsible for state.
+    }
+    sock_to_handler_.reset(sock.release());
+    handler_pid_ = pid;
+    return Install(unhandled_signals);
+  }
+
+  bool GetHandlerSocket(int* sock, pid_t* pid) {
+    if (!sock_to_handler_.is_valid()) {
+      return false;
+    }
+    if (sock) {
+      *sock = sock_to_handler_.get();
+    }
+    if (pid) {
+      *pid = handler_pid_;
+    }
+    return true;
+  }
+
+  void HandleCrashImpl() override {
+    ExceptionHandlerProtocol::ClientInformation info = {};
+    info.exception_information_address =
+        FromPointerCast<VMAddress>(&GetExceptionInfo());
+#if defined(OS_CHROMEOS)
+    info.crash_loop_before_time = crash_loop_before_time_;
+#endif
+
+    ExceptionHandlerClient client(sock_to_handler_.get(), true);
+    client.RequestCrashDump(info);
+  }
+
+#if defined(OS_CHROMEOS)
+  void SetCrashLoopBefore(uint64_t crash_loop_before_time) {
+    crash_loop_before_time_ = crash_loop_before_time;
+  }
+#endif
+
+ private:
+  RequestCrashDumpHandler() = default;
+
+  ~RequestCrashDumpHandler() = delete;
+
+  ScopedFileHandle sock_to_handler_;
+  pid_t handler_pid_ = -1;
+
+#if defined(OS_CHROMEOS)
+  // An optional UNIX timestamp passed to us from Chrome.
+  // This will pass to crashpad_handler and then to Chrome OS crash_reporter.
+  // This should really be a time_t, but it's basically an opaque value (we
+  // don't anything with it except pass it along).
+  uint64_t crash_loop_before_time_ = 0;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(RequestCrashDumpHandler);
+};
 
 }  // namespace
 
@@ -160,14 +373,134 @@
     const std::vector<std::string>& arguments,
     bool restartable,
     bool asynchronous_start) {
-  // TODO(jperaza): Implement this after the Android/Linux ExceptionHandlerSever
-  // supports accepting new connections.
-  // https://crashpad.chromium.org/bug/30
-  NOTREACHED();
-  return false;
+  DCHECK(!asynchronous_start);
+
+  ScopedFileHandle client_sock, handler_sock;
+  if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock,
+                                                        &handler_sock)) {
+    return false;
+  }
+
+  std::vector<std::string> argv = BuildHandlerArgvStrings(
+      handler, database, metrics_dir, url, annotations, arguments);
+
+  argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get()));
+  argv.push_back("--shared-client-connection");
+  if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) {
+    return false;
+  }
+
+  pid_t handler_pid = -1;
+  if (!IsRegularFile(base::FilePath("/proc/sys/kernel/yama/ptrace_scope"))) {
+    handler_pid = 0;
+  }
+
+  auto signal_handler = RequestCrashDumpHandler::Get();
+  return signal_handler->Initialize(
+      std::move(client_sock), handler_pid, &unhandled_signals_);
+}
+
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+// static
+bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) {
+  auto signal_handler = RequestCrashDumpHandler::Get();
+  return signal_handler->GetHandlerSocket(sock, pid);
+}
+
+bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) {
+  auto signal_handler = RequestCrashDumpHandler::Get();
+  return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_);
+}
+#endif  // OS_ANDROID || OS_LINUX
+
+#if defined(OS_ANDROID)
+
+bool CrashpadClient::StartJavaHandlerAtCrash(
+    const std::string& class_name,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments) {
+  std::vector<std::string> argv = BuildAppProcessArgs(class_name,
+                                                      database,
+                                                      metrics_dir,
+                                                      url,
+                                                      annotations,
+                                                      arguments,
+                                                      kInvalidFileHandle);
+
+  auto signal_handler = LaunchAtCrashHandler::Get();
+  return signal_handler->Initialize(&argv, env, &unhandled_signals_);
 }
 
 // static
+bool CrashpadClient::StartJavaHandlerForClient(
+    const std::string& class_name,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+  std::vector<std::string> argv = BuildAppProcessArgs(
+      class_name, database, metrics_dir, url, annotations, arguments, socket);
+  return DoubleForkAndExec(argv, env, socket, false, nullptr);
+}
+
+bool CrashpadClient::StartHandlerWithLinkerAtCrash(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments) {
+  std::vector<std::string> argv =
+      BuildArgsToLaunchWithLinker(handler_trampoline,
+                                  handler_library,
+                                  is_64_bit,
+                                  database,
+                                  metrics_dir,
+                                  url,
+                                  annotations,
+                                  arguments,
+                                  kInvalidFileHandle);
+  auto signal_handler = LaunchAtCrashHandler::Get();
+  return signal_handler->Initialize(&argv, env, &unhandled_signals_);
+}
+
+// static
+bool CrashpadClient::StartHandlerWithLinkerForClient(
+    const std::string& handler_trampoline,
+    const std::string& handler_library,
+    bool is_64_bit,
+    const std::vector<std::string>* env,
+    const base::FilePath& database,
+    const base::FilePath& metrics_dir,
+    const std::string& url,
+    const std::map<std::string, std::string>& annotations,
+    const std::vector<std::string>& arguments,
+    int socket) {
+  std::vector<std::string> argv =
+      BuildArgsToLaunchWithLinker(handler_trampoline,
+                                  handler_library,
+                                  is_64_bit,
+                                  database,
+                                  metrics_dir,
+                                  url,
+                                  annotations,
+                                  arguments,
+                                  socket);
+  return DoubleForkAndExec(argv, env, socket, false, nullptr);
+}
+
+#endif
+
 bool CrashpadClient::StartHandlerAtCrash(
     const base::FilePath& handler,
     const base::FilePath& database,
@@ -179,12 +512,7 @@
       handler, database, metrics_dir, url, annotations, arguments);
 
   auto signal_handler = LaunchAtCrashHandler::Get();
-  if (signal_handler->Initialize(&argv)) {
-    DCHECK(!g_crash_handler);
-    g_crash_handler = signal_handler;
-    return true;
-  }
-  return false;
+  return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_);
 }
 
 // static
@@ -199,14 +527,17 @@
   std::vector<std::string> argv = BuildHandlerArgvStrings(
       handler, database, metrics_dir, url, annotations, arguments);
 
-  argv.push_back(FormatArgumentInt("initial-client", socket));
+  argv.push_back(FormatArgumentInt("initial-client-fd", socket));
 
-  return DoubleForkAndExec(argv, socket, true, nullptr);
+  return DoubleForkAndExec(argv, nullptr, socket, true, nullptr);
 }
 
 // static
 void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
-  DCHECK(g_crash_handler);
+  if (!SignalHandler::Get()) {
+    DLOG(ERROR) << "Crashpad isn't enabled";
+    return;
+  }
 
 #if defined(ARCH_CPU_ARMEL)
   memset(context->uc_regspace, 0, sizeof(context->uc_regspace));
@@ -220,15 +551,34 @@
   siginfo.si_signo = Signals::kSimulatedSigno;
   siginfo.si_errno = 0;
   siginfo.si_code = 0;
-  g_crash_handler->HandleCrashNonFatal(
+  SignalHandler::Get()->HandleCrash(
       siginfo.si_signo, &siginfo, reinterpret_cast<void*>(context));
 }
 
 // static
+void CrashpadClient::CrashWithoutDump(const std::string& message) {
+  SignalHandler::DisableForThread();
+  LOG(FATAL) << message;
+}
+
+// static
 void CrashpadClient::SetFirstChanceExceptionHandler(
     FirstChanceHandler handler) {
-  DCHECK(g_crash_handler);
-  g_crash_handler->SetFirstChanceHandler(handler);
+  DCHECK(SignalHandler::Get());
+  SignalHandler::Get()->SetFirstChanceHandler(handler);
 }
 
+void CrashpadClient::SetUnhandledSignals(const std::set<int>& signals) {
+  DCHECK(!SignalHandler::Get());
+  unhandled_signals_ = signals;
+}
+
+#if defined(OS_CHROMEOS)
+// static
+void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) {
+  auto request_crash_dump_handler = RequestCrashDumpHandler::Get();
+  request_crash_dump_handler->SetCrashLoopBefore(crash_loop_before_time);
+}
+#endif
+
 }  // namespace crashpad
diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc
index 4c5bf4d..2bfda5a 100644
--- a/client/crashpad_client_linux_test.cc
+++ b/client/crashpad_client_linux_test.cc
@@ -14,8 +14,8 @@
 
 #include "client/crashpad_client.h"
 
+#include <dlfcn.h>
 #include <stdlib.h>
-#include <sys/socket.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -37,76 +37,98 @@
 #include "util/file/filesystem.h"
 #include "util/linux/exception_handler_client.h"
 #include "util/linux/exception_information.h"
+#include "util/linux/socket.h"
 #include "util/misc/address_types.h"
 #include "util/misc/from_pointer_cast.h"
 #include "util/posix/signals.h"
 
+#if defined(OS_ANDROID)
+#include <android/set_abort_message.h>
+#include "dlfcn_internal.h"
+
+// Normally this comes from set_abort_message.h, but only at API level 21.
+extern "C" void android_set_abort_message(const char* msg)
+    __attribute__((weak));
+#endif
+
 namespace crashpad {
 namespace test {
 namespace {
 
+struct StartHandlerForSelfTestOptions {
+  bool start_handler_at_crash;
+  bool simulate_crash;
+  bool set_first_chance_handler;
+};
+
+class StartHandlerForSelfTest
+    : public testing::TestWithParam<std::tuple<bool, bool, bool>> {
+ public:
+  StartHandlerForSelfTest() = default;
+  ~StartHandlerForSelfTest() = default;
+
+  void SetUp() override {
+    std::tie(options_.start_handler_at_crash,
+             options_.simulate_crash,
+             options_.set_first_chance_handler) = GetParam();
+  }
+
+  const StartHandlerForSelfTestOptions& Options() const { return options_; }
+
+ private:
+  StartHandlerForSelfTestOptions options_;
+
+  DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfTest);
+};
+
 bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
   return true;
 }
 
-TEST(CrashpadClient, SimulateCrash) {
-  ScopedTempDir temp_dir;
-
-  base::FilePath handler_path = TestPaths::Executable().DirName().Append(
-      FILE_PATH_LITERAL("crashpad_handler"));
-
-  crashpad::CrashpadClient client;
-  ASSERT_TRUE(client.StartHandlerAtCrash(handler_path,
-                                         base::FilePath(temp_dir.path()),
-                                         base::FilePath(),
-                                         "",
-                                         std::map<std::string, std::string>(),
-                                         std::vector<std::string>()));
-
-  auto database =
-      CrashReportDatabase::InitializeWithoutCreating(temp_dir.path());
-  ASSERT_TRUE(database);
-
-  {
-    CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
-
-    CRASHPAD_SIMULATE_CRASH();
-
-    std::vector<CrashReportDatabase::Report> reports;
-    ASSERT_EQ(database->GetPendingReports(&reports),
-              CrashReportDatabase::kNoError);
-    EXPECT_EQ(reports.size(), 0u);
-
-    reports.clear();
-    ASSERT_EQ(database->GetCompletedReports(&reports),
-              CrashReportDatabase::kNoError);
-    EXPECT_EQ(reports.size(), 0u);
-  }
-
-  {
-    CrashpadClient::SetFirstChanceExceptionHandler(nullptr);
-
-    CRASHPAD_SIMULATE_CRASH();
-
-    std::vector<CrashReportDatabase::Report> reports;
-    ASSERT_EQ(database->GetPendingReports(&reports),
-              CrashReportDatabase::kNoError);
-    EXPECT_EQ(reports.size(), 1u);
-
-    reports.clear();
-    ASSERT_EQ(database->GetCompletedReports(&reports),
-              CrashReportDatabase::kNoError);
-    EXPECT_EQ(reports.size(), 0u);
-  }
+bool InstallHandler(CrashpadClient* client,
+                    bool start_at_crash,
+                    const base::FilePath& handler_path,
+                    const base::FilePath& database_path) {
+  return start_at_crash
+             ? client->StartHandlerAtCrash(handler_path,
+                                           database_path,
+                                           base::FilePath(),
+                                           "",
+                                           std::map<std::string, std::string>(),
+                                           std::vector<std::string>())
+             : client->StartHandler(handler_path,
+                                    database_path,
+                                    base::FilePath(),
+                                    "",
+                                    std::map<std::string, std::string>(),
+                                    std::vector<std::string>(),
+                                    false,
+                                    false);
 }
 
 constexpr char kTestAnnotationName[] = "name_of_annotation";
 constexpr char kTestAnnotationValue[] = "value_of_annotation";
 
+#if defined(OS_ANDROID)
+constexpr char kTestAbortMessage[] = "test abort message";
+#endif
+
 void ValidateDump(const CrashReportDatabase::UploadReport* report) {
   ProcessSnapshotMinidump minidump_snapshot;
   ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader()));
 
+#if defined(OS_ANDROID)
+  // This part of the test requires Q. The API level on Q devices will be 28
+  // until the API is finalized, so we can't check API level yet. For now, test
+  // for the presence of a libc symbol which was introduced in Q.
+  if (crashpad::internal::Dlsym(RTLD_DEFAULT, "android_fdsan_close_with_tag")) {
+    const auto& annotations = minidump_snapshot.AnnotationsSimpleMap();
+    auto abort_message = annotations.find("abort_message");
+    ASSERT_NE(annotations.end(), abort_message);
+    EXPECT_EQ(kTestAbortMessage, abort_message->second);
+  }
+#endif
+
   for (const ModuleSnapshot* module : minidump_snapshot.Modules()) {
     for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
       if (static_cast<Annotation::Type>(annotation.type) !=
@@ -126,7 +148,7 @@
   ADD_FAILURE();
 }
 
-CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) {
+CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
   FileHandle in = StdioFileHandle(StdioStream::kStandardInput);
 
   VMSize temp_dir_length;
@@ -135,6 +157,9 @@
   std::string temp_dir(temp_dir_length, '\0');
   CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length);
 
+  StartHandlerForSelfTestOptions options;
+  CheckedReadFileExactly(in, &options, sizeof(options));
+
   base::FilePath handler_path = TestPaths::Executable().DirName().Append(
       FILE_PATH_LITERAL("crashpad_handler"));
 
@@ -144,26 +169,41 @@
   test_annotation.Set(kTestAnnotationValue);
 
   crashpad::CrashpadClient client;
-  if (!client.StartHandlerAtCrash(handler_path,
-                                  base::FilePath(temp_dir),
-                                  base::FilePath(),
-                                  "",
-                                  std::map<std::string, std::string>(),
-                                  std::vector<std::string>())) {
+  if (!InstallHandler(&client,
+                      options.start_handler_at_crash,
+                      handler_path,
+                      base::FilePath(temp_dir))) {
     return EXIT_FAILURE;
   }
 
+#if defined(OS_ANDROID)
+  if (android_set_abort_message) {
+    android_set_abort_message(kTestAbortMessage);
+  }
+#endif
+
+  if (options.simulate_crash) {
+    if (options.set_first_chance_handler) {
+      client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
+    }
+    CRASHPAD_SIMULATE_CRASH();
+    return EXIT_SUCCESS;
+  }
+
   __builtin_trap();
 
   NOTREACHED();
   return EXIT_SUCCESS;
 }
 
-class StartHandlerAtCrashTest : public MultiprocessExec {
+class StartHandlerForSelfInChildTest : public MultiprocessExec {
  public:
-  StartHandlerAtCrashTest() : MultiprocessExec() {
-    SetChildTestMainFunction("StartHandlerAtCrashChild");
-    SetExpectedChildTerminationBuiltinTrap();
+  StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options)
+      : MultiprocessExec(), options_(options) {
+    SetChildTestMainFunction("StartHandlerForSelfTestChild");
+    if (!options.simulate_crash) {
+      SetExpectedChildTerminationBuiltinTrap();
+    }
   }
 
  private:
@@ -174,6 +214,8 @@
         WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length)));
     ASSERT_TRUE(LoggingWriteFile(
         WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length));
+    ASSERT_TRUE(
+        LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_)));
 
     // Wait for child to finish.
     CheckedReadFileAtEOF(ReadPipeHandle());
@@ -189,7 +231,11 @@
     reports.clear();
     ASSERT_EQ(database->GetPendingReports(&reports),
               CrashReportDatabase::kNoError);
-    EXPECT_EQ(reports.size(), 1u);
+    ASSERT_EQ(reports.size(), options_.set_first_chance_handler ? 0u : 1u);
+
+    if (options_.set_first_chance_handler) {
+      return;
+    }
 
     std::unique_ptr<const CrashReportDatabase::UploadReport> report;
     ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report),
@@ -197,14 +243,26 @@
     ValidateDump(report.get());
   }
 
-  DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest);
+  StartHandlerForSelfTestOptions options_;
+
+  DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfInChildTest);
 };
 
-TEST(CrashpadClient, StartHandlerAtCrash) {
-  StartHandlerAtCrashTest test;
+TEST_P(StartHandlerForSelfTest, StartHandlerInChild) {
+  if (Options().set_first_chance_handler && !Options().simulate_crash) {
+    // TODO(jperaza): test first chance handlers with real crashes.
+    return;
+  }
+  StartHandlerForSelfInChildTest test(Options());
   test.Run();
 }
 
+INSTANTIATE_TEST_SUITE_P(StartHandlerForSelfTestSuite,
+                         StartHandlerForSelfTest,
+                         testing::Combine(testing::Bool(),
+                                          testing::Bool(),
+                                          testing::Bool()));
+
 // Test state for starting the handler for another process.
 class StartHandlerForClientTest {
  public:
@@ -213,16 +271,8 @@
 
   bool Initialize(bool sanitize) {
     sanitize_ = sanitize;
-
-    int socks[2];
-    if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) {
-      PLOG(ERROR) << "socketpair";
-      return false;
-    }
-    client_sock_.reset(socks[0]);
-    server_sock_.reset(socks[1]);
-
-    return true;
+    return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_,
+                                                            &server_sock_);
   }
 
   bool StartHandlerOnDemand() {
@@ -298,7 +348,7 @@
     static void HandleCrash(int signo, siginfo_t* siginfo, void* context) {
       auto state = Get();
 
-      char c;
+      char c = 0;
       CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c)));
 
       ExceptionInformation exception_information;
@@ -310,7 +360,7 @@
               context);
       exception_information.thread_id = syscall(SYS_gettid);
 
-      ClientInformation info;
+      ExceptionHandlerProtocol::ClientInformation info;
       info.exception_information_address =
           FromPointerCast<decltype(info.exception_information_address)>(
               &exception_information);
@@ -324,7 +374,7 @@
             FromPointerCast<VMAddress>(&sanitization_info);
       }
 
-      ExceptionHandlerClient handler_client(state->client_sock_);
+      ExceptionHandlerClient handler_client(state->client_sock_, false);
       CHECK_EQ(handler_client.RequestCrashDump(info), 0);
 
       Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc
index 4a40820..2fa8729 100644
--- a/client/crashpad_client_mac.cc
+++ b/client/crashpad_client_mac.cc
@@ -26,6 +26,7 @@
 #include "base/mac/mach_logging.h"
 #include "base/strings/stringprintf.h"
 #include "util/mac/mac_util.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/child_port_handshake.h"
 #include "util/mach/exception_ports.h"
 #include "util/mach/mach_extensions.h"
@@ -338,6 +339,7 @@
     // this interface.
     if (!DoubleForkAndExec(
             argv,
+            nullptr,
             server_write_fd.get(),
             true,
             restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) {
diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc
index 8ceded3..4963f24 100644
--- a/client/crashpad_client_win.cc
+++ b/client/crashpad_client_win.cc
@@ -15,6 +15,7 @@
 #include "client/crashpad_client.h"
 
 #include <windows.h>
+
 #include <signal.h>
 #include <stdint.h>
 #include <string.h>
@@ -35,10 +36,12 @@
 #include "util/misc/random_string.h"
 #include "util/win/address_types.h"
 #include "util/win/command_line.h"
+#include "util/win/context_wrappers.h"
 #include "util/win/critical_section_with_debug_info.h"
 #include "util/win/get_function.h"
 #include "util/win/handle.h"
 #include "util/win/initial_client_data.h"
+#include "util/win/loader_lock.h"
 #include "util/win/nt_internals.h"
 #include "util/win/ntstatus_logging.h"
 #include "util/win/process_info.h"
@@ -73,10 +76,10 @@
 ExceptionInformation g_non_crash_exception_information;
 
 enum class StartupState : int {
-  kNotReady = 0,   // This must be value 0 because it is the initial value of a
-                   // global AtomicWord.
+  kNotReady = 0,  // This must be value 0 because it is the initial value of a
+                  // global AtomicWord.
   kSucceeded = 1,  // The CreateProcess() for the handler succeeded.
-  kFailed = 2,     // The handler failed to start.
+  kFailed = 2,  // The handler failed to start.
 };
 
 // This is a tri-state of type StartupState. It starts at 0 == kNotReady, and
@@ -93,9 +96,8 @@
 CRITICAL_SECTION g_critical_section_with_debug_info;
 
 void SetHandlerStartupState(StartupState state) {
-  DCHECK(state == StartupState::kSucceeded ||
-         state == StartupState::kFailed);
-  base::subtle::Acquire_Store(&g_handler_startup_state,
+  DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed);
+  base::subtle::Release_Store(&g_handler_startup_state,
                               static_cast<base::subtle::AtomicWord>(state));
 }
 
@@ -103,7 +105,7 @@
   // Wait until we know the handler has either succeeded or failed to start.
   base::subtle::AtomicWord startup_state;
   while (
-      (startup_state = base::subtle::Release_Load(&g_handler_startup_state)) ==
+      (startup_state = base::subtle::Acquire_Load(&g_handler_startup_state)) ==
       static_cast<int>(StartupState::kNotReady)) {
     Sleep(1);
   }
@@ -187,11 +189,7 @@
   EXCEPTION_RECORD record = {};
   record.ExceptionCode = STATUS_FATAL_APP_EXIT;
   record.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
-#if defined(ARCH_CPU_64_BITS)
-  record.ExceptionAddress = reinterpret_cast<void*>(context.Rip);
-#else
-  record.ExceptionAddress = reinterpret_cast<void*>(context.Eip);
-#endif  // ARCH_CPU_64_BITS
+  record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
 
   EXCEPTION_POINTERS exception_pointers;
   exception_pointers.ContextRecord = &context;
@@ -349,6 +347,8 @@
 
 bool StartHandlerProcess(
     std::unique_ptr<BackgroundHandlerStartThreadData> data) {
+  CHECK(!IsThreadInLoaderLock());
+
   ScopedCallSetHandlerStartupState scoped_startup_state_caller;
 
   std::wstring command_line;
@@ -476,17 +476,34 @@
     }
   }
 
+  // If the embedded crashpad handler is being started via an entry point in a
+  // DLL (the handler executable is rundll32.exe), then don't pass
+  // the application name to CreateProcess as this appears to generate an
+  // invalid command line where the first argument needed by rundll32 is not in
+  // the correct format as required in:
+  // https://support.microsoft.com/en-ca/help/164787/info-windows-rundll-and-rundll32-interface
+  const base::StringPiece16 kRunDll32Exe(L"rundll32.exe");
+  bool is_embedded_in_dll = false;
+  if (data->handler.value().size() >= kRunDll32Exe.size() &&
+      _wcsicmp(data->handler.value()
+                   .substr(data->handler.value().size() - kRunDll32Exe.size())
+                   .c_str(),
+               kRunDll32Exe.data()) == 0) {
+    is_embedded_in_dll = true;
+  }
+
   PROCESS_INFORMATION process_info;
-  rv = CreateProcess(data->handler.value().c_str(),
-                     &command_line[0],
-                     nullptr,
-                     nullptr,
-                     true,
-                     creation_flags,
-                     nullptr,
-                     nullptr,
-                     &startup_info.StartupInfo,
-                     &process_info);
+  rv = CreateProcess(
+      is_embedded_in_dll ? nullptr : data->handler.value().c_str(),
+      &command_line[0],
+      nullptr,
+      nullptr,
+      true,
+      creation_flags,
+      nullptr,
+      nullptr,
+      &startup_info.StartupInfo,
+      &process_info);
   if (!rv) {
     PLOG(ERROR) << "CreateProcess";
     return false;
@@ -756,11 +773,7 @@
   constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed;
   EXCEPTION_RECORD record = {};
   record.ExceptionCode = kSimulatedExceptionCode;
-#if defined(ARCH_CPU_64_BITS)
-  record.ExceptionAddress = reinterpret_cast<void*>(context.Rip);
-#else
-  record.ExceptionAddress = reinterpret_cast<void*>(context.Eip);
-#endif  // ARCH_CPU_64_BITS
+  record.ExceptionAddress = ProgramCounterFromCONTEXT(&context);
 
   exception_pointers.ExceptionRecord = &record;
 
diff --git a/client/crashpad_client_win_test.cc b/client/crashpad_client_win_test.cc
index 99778ba..b950699 100644
--- a/client/crashpad_client_win_test.cc
+++ b/client/crashpad_client_win_test.cc
@@ -17,7 +17,6 @@
 #include <vector>
 
 #include "base/files/file_path.h"
-#include "base/macros.h"
 #include "base/logging.h"
 #include "gtest/gtest.h"
 #include "test/test_paths.h"
diff --git a/client/crashpad_info.h b/client/crashpad_info.h
index 5db9a6c..ed7b9c1 100644
--- a/client/crashpad_info.h
+++ b/client/crashpad_info.h
@@ -141,15 +141,15 @@
   //!
   //! When handling an exception, the Crashpad handler will scan all modules in
   //! a process. The first one that has a CrashpadInfo structure populated with
-  //! a value other than #kUnset for this field will dictate whether the handler
-  //! is functional or not. If all modules with a CrashpadInfo structure specify
-  //! #kUnset, the handler will be enabled. If disabled, the Crashpad handler
-  //! will still run and receive exceptions, but will not take any action on an
-  //! exception on its own behalf, except for the action necessary to determine
-  //! that it has been disabled.
+  //! a value other than TriState::kUnset for this field will dictate whether
+  //! the handler is functional or not. If all modules with a CrashpadInfo
+  //! structure specify TriState::kUnset, the handler will be enabled. If
+  //! disabled, the Crashpad handler will still run and receive exceptions, but
+  //! will not take any action on an exception on its own behalf, except for the
+  //! action necessary to determine that it has been disabled.
   //!
-  //! The Crashpad handler should not normally be disabled. More commonly, it
-  //! is appropriate to disable crash report upload by calling
+  //! The Crashpad handler should not normally be disabled. More commonly, it is
+  //! appropriate to disable crash report upload by calling
   //! Settings::SetUploadsEnabled().
   void set_crashpad_handler_behavior(TriState crashpad_handler_behavior) {
     crashpad_handler_behavior_ = crashpad_handler_behavior;
@@ -160,15 +160,15 @@
   //!
   //! When handling an exception, the Crashpad handler will scan all modules in
   //! a process. The first one that has a CrashpadInfo structure populated with
-  //! a value other than #kUnset for this field will dictate whether the
-  //! exception is forwarded to the system’s crash reporter. If all modules with
-  //! a CrashpadInfo structure specify #kUnset, forwarding will be enabled.
-  //! Unless disabled, forwarding may still occur if the Crashpad handler is
-  //! disabled by SetCrashpadHandlerState(). Even when forwarding is enabled,
-  //! the Crashpad handler may choose not to forward all exceptions to the
-  //! system’s crash reporter in cases where it has reason to believe that the
-  //! system’s crash reporter would not normally have handled the exception in
-  //! Crashpad’s absence.
+  //! a value other than TriState::kUnset for this field will dictate whether
+  //! the exception is forwarded to the system’s crash reporter. If all modules
+  //! with a CrashpadInfo structure specify TriState::kUnset, forwarding will be
+  //! enabled. Unless disabled, forwarding may still occur if the Crashpad
+  //! handler is disabled by SetCrashpadHandlerState(). Even when forwarding is
+  //! enabled, the Crashpad handler may choose not to forward all exceptions to
+  //! the system’s crash reporter in cases where it has reason to believe that
+  //! the system’s crash reporter would not normally have handled the exception
+  //! in Crashpad’s absence.
   void set_system_crash_reporter_forwarding(
       TriState system_crash_reporter_forwarding) {
     system_crash_reporter_forwarding_ = system_crash_reporter_forwarding;
@@ -179,8 +179,8 @@
   //!
   //! When handling an exception, the Crashpad handler will scan all modules in
   //! a process. The first one that has a CrashpadInfo structure populated with
-  //! a value other than #kUnset for this field will dictate whether the extra
-  //! memory is captured.
+  //! a value other than TriState::kUnset for this field will dictate whether
+  //! the extra memory is captured.
   //!
   //! This causes Crashpad to include pages of data referenced by locals or
   //! other stack memory. Turning this on can increase the size of the minidump
@@ -208,7 +208,7 @@
   //! Note that streams will appear in the minidump in the reverse order to
   //! which they are added.
   //!
-  //! TODO(scottmg) This is currently only supported on Windows.
+  //! TODO(scottmg) This is currently not supported on Mac.
   //!
   //! \param[in] stream_type The stream type identifier to use. This should be
   //!     normally be larger than `MINIDUMP_STREAM_TYPE::LastReservedStream`
diff --git a/client/crashpad_info_note.S b/client/crashpad_info_note.S
index 4c29298..b13d864 100644
--- a/client/crashpad_info_note.S
+++ b/client/crashpad_info_note.S
@@ -26,16 +26,13 @@
 #define NOTE_ALIGN 4
 
   // This section must be "a"llocated so that it appears in the final binary at
-  // runtime, and "w"ritable so that the relocation to CRASHPAD_INFO_SYMBOL can
-  // be performed.
-  .section .note.crashpad.info,"aw",%note
+  // runtime. The reference to CRASHPAD_INFO_SYMBOL uses an offset relative to
+  // this note to avoid making this note writable, which triggers a bug in GNU
+  // ld, or adding text relocations which require the target system to allow
+  // making text segments writable. https://crbug.com/crashpad/260.
+  .section .note.crashpad.info,"a",%note
   .balign NOTE_ALIGN
-  # .globl indicates that it's available to link against other .o files. .hidden
-  # indicates that it will not appear in the executable's symbol table.
-  .globl CRASHPAD_NOTE_REFERENCE
-  .hidden CRASHPAD_NOTE_REFERENCE
-  .type CRASHPAD_NOTE_REFERENCE, %object
-CRASHPAD_NOTE_REFERENCE:
+CRASHPAD_NOTE:
   .long name_end - name  // namesz
   .long desc_end - desc  // descsz
   .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO  // type
@@ -45,15 +42,26 @@
   .balign NOTE_ALIGN
 desc:
 #if defined(__LP64__)
-  .quad CRASHPAD_INFO_SYMBOL
+  .quad CRASHPAD_INFO_SYMBOL - desc
 #else
-#if defined(__LITTLE_ENDIAN__)
-  .long CRASHPAD_INFO_SYMBOL
-  .long 0
-#else
-  .long 0
-  .long CRASHPAD_INFO_SYMBOL
-#endif  // __LITTLE_ENDIAN__
+  .long CRASHPAD_INFO_SYMBOL - desc
 #endif  // __LP64__
 desc_end:
-  .size CRASHPAD_NOTE_REFERENCE, .-CRASHPAD_NOTE_REFERENCE
+  .size CRASHPAD_NOTE, .-CRASHPAD_NOTE
+
+  // CRASHPAD_NOTE can't be referenced directly by GetCrashpadInfo() because the
+  // relocation used to make the reference may require that the address be
+  // 8-byte aligned and notes must have 4-byte alignment.
+  .section .rodata,"a",%progbits
+  .balign 8
+  # .globl indicates that it's available to link against other .o files. .hidden
+  # indicates that it will not appear in the executable's symbol table.
+  .globl CRASHPAD_NOTE_REFERENCE
+  .hidden CRASHPAD_NOTE_REFERENCE
+  .type CRASHPAD_NOTE_REFERENCE, %object
+CRASHPAD_NOTE_REFERENCE:
+  // The value of this quad isn't important. It exists to reference
+  // CRASHPAD_NOTE, causing the linker to include the note into the binary
+  // linking Crashpad. The subtraction from |name| is a convenience to allow the
+  // value to be computed statically.
+  .quad name - CRASHPAD_NOTE
diff --git a/client/prune_crash_reports.cc b/client/prune_crash_reports.cc
index d045eb6..8eed18d 100644
--- a/client/prune_crash_reports.cc
+++ b/client/prune_crash_reports.cc
@@ -15,6 +15,7 @@
 #include "client/prune_crash_reports.h"
 
 #include <sys/stat.h>
+#include <stdint.h>
 
 #include <algorithm>
 #include <vector>
@@ -24,7 +25,7 @@
 
 namespace crashpad {
 
-void PruneCrashReportDatabase(CrashReportDatabase* database,
+size_t PruneCrashReportDatabase(CrashReportDatabase* database,
                               PruneCondition* condition) {
   std::vector<CrashReportDatabase::Report> all_reports;
   CrashReportDatabase::OperationStatus status;
@@ -32,14 +33,14 @@
   status = database->GetPendingReports(&all_reports);
   if (status != CrashReportDatabase::kNoError) {
     LOG(ERROR) << "PruneCrashReportDatabase: Failed to get pending reports";
-    return;
+    return 0;
   }
 
   std::vector<CrashReportDatabase::Report> completed_reports;
   status = database->GetCompletedReports(&completed_reports);
   if (status != CrashReportDatabase::kNoError) {
     LOG(ERROR) << "PruneCrashReportDatabase: Failed to get completed reports";
-    return;
+    return 0;
   }
   all_reports.insert(all_reports.end(), completed_reports.begin(),
                      completed_reports.end());
@@ -50,16 +51,21 @@
         return lhs.creation_time > rhs.creation_time;
       });
 
+  size_t num_pruned = 0;
   for (const auto& report : all_reports) {
     if (condition->ShouldPruneReport(report)) {
       status = database->DeleteReport(report.uuid);
       if (status != CrashReportDatabase::kNoError) {
         LOG(ERROR) << "Database Pruning: Failed to remove report "
                    << report.uuid.ToString();
+      } else {
+        num_pruned++;
       }
     }
   }
 
+  return num_pruned;
+
   // TODO(rsesek): For databases that do not use a directory structure, it is
   // possible for the metadata sidecar to become corrupted and thus leave
   // orphaned crash report files on-disk. https://crashpad.chromium.org/bug/66
@@ -96,19 +102,9 @@
 
 bool DatabaseSizePruneCondition::ShouldPruneReport(
     const CrashReportDatabase::Report& report) {
-#if defined(OS_POSIX)
-  struct stat statbuf;
-  if (stat(report.file_path.value().c_str(), &statbuf) == 0) {
-#elif defined(OS_WIN)
-  struct _stati64 statbuf;
-  if (_wstat64(report.file_path.value().c_str(), &statbuf) == 0) {
-#else
-#error "Not implemented"
-#endif
-    // Round up fractional KB to the next 1-KB boundary.
-    measured_size_in_kb_ +=
-        static_cast<size_t>((statbuf.st_size + 1023) / 1024);
-  }
+  // Round up fractional KB to the next 1-KB boundary.
+  measured_size_in_kb_ +=
+      static_cast<size_t>((report.total_size + 1023) / 1024);
   return measured_size_in_kb_ > max_size_in_kb_;
 }
 
diff --git a/client/prune_crash_reports.h b/client/prune_crash_reports.h
index 6dac5f3..07a7098 100644
--- a/client/prune_crash_reports.h
+++ b/client/prune_crash_reports.h
@@ -37,8 +37,10 @@
 //! \param[in] database The database from which crash reports will be deleted.
 //! \param[in] condition The condition against which all reports in the database
 //!     will be evaluated.
-void PruneCrashReportDatabase(CrashReportDatabase* database,
-                              PruneCondition* condition);
+//!
+//! \return The number of deleted crash reports.
+size_t PruneCrashReportDatabase(CrashReportDatabase* database,
+                                PruneCondition* condition);
 
 std::unique_ptr<PruneCondition> GetDefaultDatabasePruneCondition();
 
diff --git a/client/prune_crash_reports_test.cc b/client/prune_crash_reports_test.cc
index 01990d2..dbe8c0a 100644
--- a/client/prune_crash_reports_test.cc
+++ b/client/prune_crash_reports_test.cc
@@ -15,14 +15,15 @@
 #include "client/prune_crash_reports.h"
 
 #include <stddef.h>
+#include <stdint.h>
 #include <stdlib.h>
 
 #include <algorithm>
+#include <random>
 #include <string>
 #include <vector>
 
 #include "base/numerics/safe_conversions.h"
-#include "base/rand_util.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "test/scoped_temp_dir.h"
@@ -81,56 +82,49 @@
 }
 
 TEST(PruneCrashReports, SizeCondition) {
-  ScopedTempDir temp_dir;
-
   CrashReportDatabase::Report report_1k;
-  report_1k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file1024"));
+  report_1k.total_size = 1024u;
   CrashReportDatabase::Report report_3k;
-  report_3k.file_path = temp_dir.path().Append(FILE_PATH_LITERAL("file3072"));
+  report_3k.total_size = 1024u * 3u;
+  CrashReportDatabase::Report report_unset_size;
 
   {
-    ScopedFileHandle scoped_file_1k(
-        LoggingOpenFileForWrite(report_1k.file_path,
-                                FileWriteMode::kCreateOrFail,
-                                FilePermissions::kOwnerOnly));
-    ASSERT_TRUE(scoped_file_1k.is_valid());
-
-    std::string string;
-    for (int i = 0; i < 128; ++i)
-      string.push_back(static_cast<char>(i));
-
-    for (size_t i = 0; i < 1024; i += string.size()) {
-      ASSERT_TRUE(LoggingWriteFile(scoped_file_1k.get(),
-                                   string.c_str(), string.length()));
-    }
-
-    ScopedFileHandle scoped_file_3k(
-        LoggingOpenFileForWrite(report_3k.file_path,
-                                FileWriteMode::kCreateOrFail,
-                                FilePermissions::kOwnerOnly));
-    ASSERT_TRUE(scoped_file_3k.is_valid());
-
-    for (size_t i = 0; i < 3072; i += string.size()) {
-      ASSERT_TRUE(LoggingWriteFile(scoped_file_3k.get(),
-                                   string.c_str(), string.length()));
-    }
-  }
-
-  {
-    DatabaseSizePruneCondition condition(1);
+    DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1);
+    // |report_1k| should not be pruned as the cumulated size is not past 1kB
+    // yet.
     EXPECT_FALSE(condition.ShouldPruneReport(report_1k));
-    EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
-  }
-
-  {
-    DatabaseSizePruneCondition condition(1);
+    // |report_3k| should be pruned as the cumulated size is now past 1kB.
     EXPECT_TRUE(condition.ShouldPruneReport(report_3k));
   }
 
   {
-    DatabaseSizePruneCondition condition(6);
+    DatabaseSizePruneCondition condition(/*max_size_in_kb=*/1);
+    // |report_3k| should be pruned as the cumulated size is already past 1kB.
+    EXPECT_TRUE(condition.ShouldPruneReport(report_3k));
+  }
+
+  {
+    DatabaseSizePruneCondition condition(/*max_size_in_kb=*/6);
+    // |report_3k| should not be pruned as the cumulated size is not past 6kB
+    // yet.
     EXPECT_FALSE(condition.ShouldPruneReport(report_3k));
+    // |report_3k| should not be pruned as the cumulated size is not past 6kB
+    // yet.
     EXPECT_FALSE(condition.ShouldPruneReport(report_3k));
+    // |report_1k| should be pruned as the cumulated size is now past 6kB.
+    EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
+  }
+
+  {
+    DatabaseSizePruneCondition condition(/*max_size_in_kb=*/0);
+    // |report_unset_size| should not be pruned as its size is 0, regardless of
+    // how many times we try to prune it.
+    EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size));
+    EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size));
+    EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size));
+    EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size));
+    EXPECT_FALSE(condition.ShouldPruneReport(report_unset_size));
+    // |report_1k| should be pruned as the cumulated size is now past 0kB.
     EXPECT_TRUE(condition.ShouldPruneReport(report_1k));
   }
 }
@@ -211,18 +205,16 @@
   using ::testing::Return;
   using ::testing::SetArgPointee;
 
+  const size_t kNumReports = 10;
   std::vector<CrashReportDatabase::Report> reports;
-  for (int i = 0; i < 10; ++i) {
+  for (size_t i = 0; i < kNumReports; ++i) {
     CrashReportDatabase::Report temp;
-    temp.uuid.data_1 = i;
-    temp.creation_time = NDaysAgo(i * 10);
+    temp.uuid.data_1 = static_cast<uint32_t>(i);
+    temp.creation_time = NDaysAgo(static_cast<int>(i) * 10);
     reports.push_back(temp);
   }
-  // The randomness from std::rand() is not, so use a better rand() instead.
-  const auto random_generator = [](ptrdiff_t rand_max) {
-    return base::RandInt(0, base::checked_cast<int>(rand_max) - 1);
-  };
-  std::random_shuffle(reports.begin(), reports.end(), random_generator);
+  std::mt19937 urng(std::random_device{}());
+  std::shuffle(reports.begin(), reports.end(), urng);
   std::vector<CrashReportDatabase::Report> pending_reports(
       reports.begin(), reports.begin() + 5);
   std::vector<CrashReportDatabase::Report> completed_reports(
@@ -241,7 +233,7 @@
   }
 
   StaticCondition delete_all(true);
-  PruneCrashReportDatabase(&db, &delete_all);
+  EXPECT_EQ(PruneCrashReportDatabase(&db, &delete_all), kNumReports);
 }
 
 }  // namespace
diff --git a/client/settings.cc b/client/settings.cc
index 20bd258..0aa525f 100644
--- a/client/settings.cc
+++ b/client/settings.cc
@@ -63,7 +63,8 @@
     CheckedCloseFile(handle_);
   }
   if (!lockfile_path_.empty()) {
-    DCHECK(LoggingRemoveFile(lockfile_path_));
+    const bool success = LoggingRemoveFile(lockfile_path_);
+    DCHECK(success);
   }
 }
 
@@ -84,8 +85,8 @@
 #endif  // OS_FUCHSIA
 
 struct Settings::Data {
-  static const uint32_t kSettingsMagic = 'CPds';
-  static const uint32_t kSettingsVersion = 1;
+  static constexpr uint32_t kSettingsMagic = 'CPds';
+  static constexpr uint32_t kSettingsVersion = 1;
 
   enum Options : uint32_t {
     kUploadsEnabled = 1 << 0,
@@ -225,10 +226,10 @@
   FileHandle handle;
   if (log_open_error) {
     handle = LoggingOpenFileForReadAndWrite(
-        file_path(), mode, FilePermissions::kWorldReadable);
+        file_path(), mode, FilePermissions::kOwnerOnly);
   } else {
     handle = OpenFileForReadAndWrite(
-        file_path(), mode, FilePermissions::kWorldReadable);
+        file_path(), mode, FilePermissions::kOwnerOnly);
   }
 
   return MakeScopedLockedFileHandle(
diff --git a/client/settings.h b/client/settings.h
index a2b0c74..5761c6b 100644
--- a/client/settings.h
+++ b/client/settings.h
@@ -75,6 +75,10 @@
   //!
   //! The default value is `false`.
   //!
+  //! \note
+  //! This setting is ignored if --use-cros-crash-reporter is present
+  //! (which it will be if invoked by Chrome on ChromeOS).
+  //!
   //! \param[out] enabled Whether crash reports should be uploaded.
   //!
   //! \return On success, returns `true`, otherwise returns `false` with an
diff --git a/client/simple_string_dictionary_test.cc b/client/simple_string_dictionary_test.cc
index 7af51a2..cd23216 100644
--- a/client/simple_string_dictionary_test.cc
+++ b/client/simple_string_dictionary_test.cc
@@ -73,7 +73,7 @@
   EXPECT_FALSE(dict.GetValueForKey("key3"));
 
   // Remove by setting value to nullptr
-  dict.SetKeyValue("key2", nullptr);
+  dict.SetKeyValue("key2", base::StringPiece(nullptr, 0));
 
   // Now make sure it's not there anymore
   EXPECT_FALSE(dict.GetValueForKey("key2"));
@@ -254,13 +254,14 @@
 
 TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) {
   TSimpleStringDictionary<4, 6, 6> map;
-  ASSERT_DEATH_CHECK(map.SetKeyValue(nullptr, "hello"), "key");
+  ASSERT_DEATH_CHECK(map.SetKeyValue(base::StringPiece(nullptr, 0), "hello"),
+                     "key");
 }
 
 TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) {
   TSimpleStringDictionary<4, 6, 6> map;
   map.SetKeyValue("hi", "there");
-  ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key");
+  ASSERT_DEATH_CHECK(map.GetValueForKey(base::StringPiece(nullptr, 0)), "key");
   EXPECT_STREQ("there", map.GetValueForKey("hi"));
 }
 
diff --git a/client/simulate_crash_mac.cc b/client/simulate_crash_mac.cc
index 1e2d8e4..43d05b8 100644
--- a/client/simulate_crash_mac.cc
+++ b/client/simulate_crash_mac.cc
@@ -20,7 +20,7 @@
 #include "base/logging.h"
 #include "base/mac/mach_logging.h"
 #include "base/mac/scoped_mach_port.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "util/mach/exc_client_variants.h"
@@ -191,7 +191,7 @@
   base::mac::ScopedMachSendRight thread(mach_thread_self());
   exception_type_t exception = kMachExceptionSimulated;
   mach_exception_data_type_t codes[] = {0, 0};
-  mach_msg_type_number_t code_count = arraysize(codes);
+  mach_msg_type_number_t code_count = base::size(codes);
 
   // Look up the handler for EXC_CRASH exceptions in the same way that the
   // kernel would: try a thread handler, then a task handler, and finally a host
@@ -213,7 +213,7 @@
   bool success = false;
 
   for (size_t target_type_index = 0;
-       !success && target_type_index < arraysize(kTargetTypes);
+       !success && target_type_index < base::size(kTargetTypes);
        ++target_type_index) {
     ExceptionPorts::ExceptionHandlerVector handlers;
     ExceptionPorts exception_ports(kTargetTypes[target_type_index],
diff --git a/client/simulate_crash_mac_test.cc b/client/simulate_crash_mac_test.cc
index 1d63ff6..c2f4c20 100644
--- a/client/simulate_crash_mac_test.cc
+++ b/client/simulate_crash_mac_test.cc
@@ -19,6 +19,7 @@
 #include <sys/types.h>
 
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -342,15 +343,13 @@
 #endif
   };
 
-  for (size_t target_index = 0;
-       target_index < arraysize(kTargets);
+  for (size_t target_index = 0; target_index < base::size(kTargets);
        ++target_index) {
     TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index];
     SCOPED_TRACE(base::StringPrintf(
         "target_index %zu, target %d", target_index, target));
 
-    for (size_t behavior_index = 0;
-         behavior_index < arraysize(kBehaviors);
+    for (size_t behavior_index = 0; behavior_index < base::size(kBehaviors);
          ++behavior_index) {
       exception_behavior_t behavior = kBehaviors[behavior_index];
       SCOPED_TRACE(base::StringPrintf(
@@ -364,8 +363,7 @@
             target, behavior, THREAD_STATE_NONE);
         test_simulate_crash_mac.Run();
       } else {
-        for (size_t flavor_index = 0;
-             flavor_index < arraysize(kFlavors);
+        for (size_t flavor_index = 0; flavor_index < base::size(kFlavors);
              ++flavor_index) {
           thread_state_flavor_t flavor = kFlavors[flavor_index];
           SCOPED_TRACE(base::StringPrintf(
diff --git a/codereview.settings b/codereview.settings
index d863097..976f8e5 100644
--- a/codereview.settings
+++ b/codereview.settings
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 GERRIT_HOST: True
-GERRIT_SQUASH_UPLOADS: True
 CODE_REVIEW_SERVER: https://chromium-review.googlesource.com/
 VIEW_VC: https://chromium.googlesource.com/crashpad/crashpad/+/
 PROJECT: crashpad
diff --git a/compat/BUILD.gn b/compat/BUILD.gn
index 2bfe074..9c22acf 100644
--- a/compat/BUILD.gn
+++ b/compat/BUILD.gn
@@ -17,8 +17,14 @@
 config("compat_config") {
   include_dirs = []
 
-  if (crashpad_is_mac) {
+  if (crashpad_is_mac || crashpad_is_ios) {
     include_dirs += [ "mac" ]
+  } else {
+    include_dirs += [ "non_mac" ]
+  }
+
+  if (crashpad_is_ios) {
+    include_dirs += [ "ios" ]
   }
 
   if (crashpad_is_linux || crashpad_is_android) {
@@ -34,10 +40,14 @@
   } else {
     include_dirs += [ "non_win" ]
   }
+
+  if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) {
+    include_dirs += [ "non_elf" ]
+  }
 }
 
 template("compat_target") {
-  if (crashpad_is_mac) {
+  if (crashpad_is_mac || crashpad_is_ios) {
     # There are no sources to compile, which doesn’t mix will with a
     # static_library.
     group(target_name) {
@@ -53,21 +63,39 @@
 compat_target("compat") {
   sources = []
 
-  if (crashpad_is_mac) {
+  if (crashpad_is_mac || crashpad_is_ios) {
     sources += [
       "mac/AvailabilityMacros.h",
       "mac/kern/exc_resource.h",
       "mac/mach-o/loader.h",
+      "mac/mach/i386/thread_state.h",
       "mac/mach/mach.h",
       "mac/sys/resource.h",
     ]
   } else {
-    sources += [ "non_mac/mach/mach.h" ]
+    sources += [
+      "non_mac/mach-o/loader.h",
+      "non_mac/mach/mach.h",
+      "non_mac/mach/machine.h",
+      "non_mac/mach/vm_prot.h",
+    ]
+  }
+
+  if (crashpad_is_ios) {
+    sources += [
+      "ios/mach/exc.defs",
+      "ios/mach/mach_exc.defs",
+      "ios/mach/mach_types.defs",
+      "ios/mach/machine/machine_types.defs",
+      "ios/mach/std_types.defs",
+    ]
   }
 
   if (crashpad_is_linux || crashpad_is_android) {
     sources += [
       "linux/signal.h",
+      "linux/sys/mman.cc",
+      "linux/sys/mman.h",
       "linux/sys/ptrace.h",
       "linux/sys/user.h",
     ]
@@ -75,6 +103,8 @@
 
   if (crashpad_is_android) {
     sources += [
+      "android/android/api-level.cc",
+      "android/android/api-level.h",
       "android/dlfcn_internal.cc",
       "android/dlfcn_internal.h",
       "android/elf.h",
@@ -114,6 +144,10 @@
     ]
   }
 
+  if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) {
+    sources += [ "non_elf/elf.h" ]
+  }
+
   public_configs = [
     ":compat_config",
     "..:crashpad_config",
@@ -121,7 +155,15 @@
 
   deps = []
 
+  if (!crashpad_is_mac) {
+    deps += [ "../third_party/xnu" ]
+  }
+
   if (crashpad_is_win) {
     deps += [ "../third_party/getopt" ]
   }
+
+  if (!crashpad_is_linux && !crashpad_is_android && !crashpad_is_fuchsia) {
+    deps += [ "../third_party/glibc" ]
+  }
 }
diff --git a/compat/android/android/api-level.cc b/compat/android/android/api-level.cc
new file mode 100644
index 0000000..c29ec1a
--- /dev/null
+++ b/compat/android/android/api-level.cc
@@ -0,0 +1,50 @@
+// 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.
+
+#include <android/api-level.h>
+
+#include <dlfcn.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/system_properties.h>
+
+#include "dlfcn_internal.h"
+
+#if __NDK_MAJOR__ < 20
+
+extern "C" {
+
+int android_get_device_api_level() {
+  using FuncType = int (*)();
+  static const FuncType bionic_get_device_api_level =
+      reinterpret_cast<FuncType>(
+          crashpad::internal::Dlsym(RTLD_NEXT, "android_get_device_api_level"));
+
+  if (bionic_get_device_api_level) {
+    return bionic_get_device_api_level();
+  }
+
+  char api_string[PROP_VALUE_MAX];
+  int length = __system_property_get("ro.build.version.sdk", api_string);
+  if (length <= 0) {
+    return -1;
+  }
+
+  int api_level = atoi(api_string);
+  return api_level > 0 ? api_level : -1;
+}
+
+}  // extern "C"
+
+#endif  // __NDK_MAJOR__ < 20
diff --git a/compat/android/android/api-level.h b/compat/android/android/api-level.h
new file mode 100644
index 0000000..bfff9af
--- /dev/null
+++ b/compat/android/android/api-level.h
@@ -0,0 +1,39 @@
+// 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_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
+#define CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
+
+#include_next <android/api-level.h>
+#include <android/ndk-version.h>
+
+#include <sys/cdefs.h>
+
+#if __NDK_MAJOR__ < 20
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns the API level of the device or -1 if it can't be determined. This
+// function is provided by NDK r20.
+int android_get_device_api_level();
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // __NDK_MAJOR__ < 20
+
+#endif  // CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
diff --git a/compat/android/sys/mman.cc b/compat/android/sys/mman.cc
index 4c29a9d..d890d84 100644
--- a/compat/android/sys/mman.cc
+++ b/compat/android/sys/mman.cc
@@ -35,7 +35,7 @@
 namespace {
 
 template <typename T>
-T Align(T value, uint8_t alignment) {
+T Align(T value, size_t alignment) {
   return (value + alignment - 1) & ~(alignment - 1);
 }
 
diff --git a/compat/compat.gyp b/compat/compat.gyp
index 1229ee0..6294d1f 100644
--- a/compat/compat.gyp
+++ b/compat/compat.gyp
@@ -20,6 +20,8 @@
     {
       'target_name': 'crashpad_compat',
       'sources': [
+        'android/android/api-level.cc',
+        'android/android/api-level.h',
         'android/dlfcn_internal.cc',
         'android/dlfcn_internal.h',
         'android/elf.h',
diff --git a/util/net/http_transport_none.cc b/compat/ios/mach/exc.defs
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/ios/mach/exc.defs
index 1c08c1f..d1648e9 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/ios/mach/exc.defs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_
+#define CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_
 
-#include "base/logging.h"
+#include "third_party/xnu/osfmk/mach/exc.defs"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_IOS_MACH_EXC_DEFS_
diff --git a/util/net/http_transport_none.cc b/compat/ios/mach/mach_exc.defs
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/ios/mach/mach_exc.defs
index 1c08c1f..c562128 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/ios/mach/mach_exc.defs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_
+#define CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_
 
-#include "base/logging.h"
+#include "third_party/xnu/osfmk/mach/mach_exc.defs"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_IOS_MACH_MACH_EXC_DEFS_
diff --git a/util/net/http_transport_none.cc b/compat/ios/mach/mach_types.defs
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/ios/mach/mach_types.defs
index 1c08c1f..dc18b8e 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/ios/mach/mach_types.defs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_
+#define CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_
 
-#include "base/logging.h"
+#include "third_party/xnu/osfmk/mach/mach_types.defs"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_IOS_MACH_MACH_TYPES_DEFS_
diff --git a/util/net/http_transport_none.cc b/compat/ios/mach/machine/machine_types.defs
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/ios/mach/machine/machine_types.defs
index 1c08c1f..e906466 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/ios/mach/machine/machine_types.defs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_
+#define CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_
 
-#include "base/logging.h"
+#include "third_party/xnu/osfmk/mach/machine/machine_types.defs"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_IOS_MACH_MACHINE_MACHINE_TYPES_DEFS_
diff --git a/util/net/http_transport_none.cc b/compat/ios/mach/std_types.defs
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/ios/mach/std_types.defs
index 1c08c1f..e49c6e4 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/ios/mach/std_types.defs
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_
+#define CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_
 
-#include "base/logging.h"
+#include "third_party/xnu/osfmk/mach/std_types.defs"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_IOS_MACH_STD_TYPES_DEFS_
diff --git a/compat/linux/sys/mman.cc b/compat/linux/sys/mman.cc
new file mode 100644
index 0000000..12aaa2c
--- /dev/null
+++ b/compat/linux/sys/mman.cc
@@ -0,0 +1,35 @@
+// Copyright 2019 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.
+
+#include <sys/mman.h>
+
+#include <dlfcn.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#if defined(__GLIBC__)
+
+extern "C" {
+
+int memfd_create(const char* name, unsigned int flags) {
+  using MemfdCreateType = int (*)(const char*, int);
+  static const MemfdCreateType next_memfd_create =
+      reinterpret_cast<MemfdCreateType>(dlsym(RTLD_NEXT, "memfd_create"));
+  return next_memfd_create ? next_memfd_create(name, flags)
+                           : syscall(SYS_memfd_create, name, flags);
+}
+
+}  // extern "C"
+
+#endif  // __GLIBC__
diff --git a/compat/linux/sys/mman.h b/compat/linux/sys/mman.h
new file mode 100644
index 0000000..61c55d7
--- /dev/null
+++ b/compat/linux/sys/mman.h
@@ -0,0 +1,40 @@
+// Copyright 2019 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_COMPAT_LINUX_SYS_MMAN_H_
+#define CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_
+
+#include_next <sys/mman.h>
+
+#include <features.h>
+
+// There's no memfd_create() wrapper before glibc 2.27.
+// This can't select for glibc < 2.27 because linux-chromeos-rel bots build this
+// code using a sysroot which has glibc 2.27, but then run it on Ubuntu 16.04,
+// which doesn't.
+#if defined(__GLIBC__)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int memfd_create(const char* name, unsigned int flags);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // __GLIBC__
+
+#endif  // CRASHPAD_COMPAT_LINUX_SYS_MMAN_H_
diff --git a/compat/linux/sys/ptrace.h b/compat/linux/sys/ptrace.h
index c5e37a0..f8be372 100644
--- a/compat/linux/sys/ptrace.h
+++ b/compat/linux/sys/ptrace.h
@@ -26,7 +26,8 @@
 static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
     static_cast<__ptrace_request>(25);
 #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
-#elif defined(__arm__) || defined(__aarch64__)
+// https://bugs.chromium.org/p/chromium/issues/detail?id=873168
+#elif defined(__arm__) || (defined(__aarch64__) && __GLIBC_PREREQ(2,28))
 static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
     static_cast<__ptrace_request>(22);
 #define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
diff --git a/compat/mac/AvailabilityMacros.h b/compat/mac/AvailabilityMacros.h
index f105f94..a83d2a4 100644
--- a/compat/mac/AvailabilityMacros.h
+++ b/compat/mac/AvailabilityMacros.h
@@ -59,4 +59,16 @@
 #define MAC_OS_X_VERSION_10_13 101300
 #endif
 
+// 10.14 SDK
+
+#ifndef MAC_OS_X_VERSION_10_14
+#define MAC_OS_X_VERSION_10_14 101400
+#endif
+
+// 10.15 SDK
+
+#ifndef MAC_OS_X_VERSION_10_15
+#define MAC_OS_X_VERSION_10_15 101500
+#endif
+
 #endif  // CRASHPAD_COMPAT_MAC_AVAILABILITYMACROS_H_
diff --git a/util/net/http_transport_none.cc b/compat/non_elf/elf.h
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/non_elf/elf.h
index 1c08c1f..1245f16 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/non_elf/elf.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_NON_ELF_ELF_H_
+#define CRASHPAD_COMPAT_NON_ELF_ELF_H_
 
-#include "base/logging.h"
+#include "third_party/glibc/elf/elf.h"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_NON_ELF_ELF_H_
diff --git a/util/net/http_transport_none.cc b/compat/non_mac/mach-o/loader.h
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/non_mac/mach-o/loader.h
index 1c08c1f..98a8b5f 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/non_mac/mach-o/loader.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_
+#define CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_
 
-#include "base/logging.h"
+#include "third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h"
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_NON_MAC_MACH_O_LOADER_H_
diff --git a/util/net/http_transport_none.cc b/compat/non_mac/mach/machine.h
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/non_mac/mach/machine.h
index 1c08c1f..54d4742 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/non_mac/mach/machine.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_
+#define CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_
 
-#include "base/logging.h"
+typedef int cpu_type_t;
+typedef int cpu_subtype_t;
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_NON_MAC_MACH_MACHINE_H_
diff --git a/util/net/http_transport_none.cc b/compat/non_mac/mach/vm_prot.h
similarity index 61%
copy from util/net/http_transport_none.cc
copy to compat/non_mac/mach/vm_prot.h
index 1c08c1f..1200179 100644
--- a/util/net/http_transport_none.cc
+++ b/compat/non_mac/mach/vm_prot.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_
+#define CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_
 
-#include "base/logging.h"
+typedef int vm_prot_t;
 
-namespace crashpad {
-
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_COMPAT_NON_MAC_MACH_VM_PROT_H_
diff --git a/compat/non_win/dbghelp.h b/compat/non_win/dbghelp.h
index 5ce88b8..ded912e 100644
--- a/compat/non_win/dbghelp.h
+++ b/compat/non_win/dbghelp.h
@@ -214,7 +214,7 @@
     //!     `cpuid 0x80000001` `edx`.
     //!
     //! This field is only valid if #VendorId identifies the CPU vendor as
-    //! “AuthenticAMD”.
+    //! “AuthenticAMD” or "HygonGenuine".
     uint32_t AMDExtendedCpuFeatures;
   } X86CpuInfo;
 
diff --git a/compat/win/sys/types.h b/compat/win/sys/types.h
index fcded87..e8fae88 100644
--- a/compat/win/sys/types.h
+++ b/compat/win/sys/types.h
@@ -20,6 +20,4 @@
 
 #include <stdint.h>
 
-typedef unsigned int pid_t;
-
 #endif  // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_
diff --git a/doc/developing.md b/doc/developing.md
index 84d1cf7..431ca1d 100644
--- a/doc/developing.md
+++ b/doc/developing.md
@@ -106,68 +106,67 @@
 [depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s
 no need to install them separately.
 
+#### Fuchsia
+
+In order to instruct gclient to download the Fuchsia SDK, you need to add the
+following to `~/crashpad/.gclient`.
+
+```
+target_os=["fuchsia"]
+```
+
+If you're using this tree to develop for multiple targets, you can also add
+other entries to the the list (e.g. `target_os=["fuchsia", "mac"]`).
+
+#### Optional Linux Configs
+
+To pull and use Crashpad's version of clang and sysroot, make the following
+changes.
+
+Add the following to `~/crashpad/.gclient`.
+```
+"custom_vars": { "pull_linux_clang": True },
+```
+Add these args to `out/Default/args.gn`.
+```
+clang_path = "//third_party/linux/clang/linux-amd64"
+target_sysroot = "//third_party/linux/sysroot"
+```
+
 ### Android
 
-Crashpad’s Android port is in its early stages. This build relies on
-cross-compilation. It’s possible to develop Crashpad for Android on any platform
-that the [Android NDK (Native Development
-Kit)](https://developer.android.com/ndk/) runs on.
+This build relies on cross-compilation. It’s possible to develop Crashpad for
+Android on any platform that the [Android NDK (Native Development Kit)]
+(https://developer.android.com/ndk/) runs on.
 
 If it’s not already present on your system, [download the NDK package for your
 system](https://developer.android.com/ndk/downloads/) and expand it to a
 suitable location. These instructions assume that it’s been expanded to
-`~/android-ndk-r16`.
-
-To build Crashpad, portions of the NDK must be reassembled into a [standalone
-toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html).
-This is a repackaged subset of the NDK suitable for cross-compiling for a single
-Android architecture (such as `arm`, `arm64`, `x86`, and `x86_64`) targeting a
-specific [Android API
-level](https://source.android.com/source/build-numbers.html). The standalone
-toolchain only needs to be built from the NDK one time for each set of options
-desired. To build a standalone toolchain targeting 64-bit ARM and API level 21
-(Android 5.0 “Lollipop”), run:
-
-```
-$ cd ~
-$ python android-ndk-r16/build/tools/make_standalone_toolchain.py \
-      --arch=arm64 --api=21 --install-dir=android-ndk-r16_arm64_api21
-```
+`~/android-ndk-r20`.
 
 Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for
 32-bit platforms. See Chrome’s
 [`build/config/android/config.gni`](https://chromium.googlesource.com/chromium/src/+/master/build/config/android/config.gni)
 which sets `_android_api_level` and `_android64_api_level`.
 
-To configure a Crashpad build for Android using the standalone toolchain
-assembled above, use `gyp_crashpad_android.py`. This script is a wrapper for
-`gyp_crashpad.py` that sets several environment variables directing the build to
-the standalone toolchain, and several GYP options to identify an Android build.
-This must be done after any `gclient sync`, or instead of any `gclient runhooks`
-operation.
+To configure a Crashpad build for Android use `gyp_crashpad_android.py`. This
+script is a wrapper for `gyp_crashpad.py` that sets several environment
+variables directing the build to the toolchain, and several GYP options to
+identify an Android build. This must be done after any `gclient sync`, or
+instead of any `gclient runhooks` operation.
 
 ```
 $ cd ~/crashpad/crashpad
-$ python build/gyp_crashpad_android.py \
-      --ndk ~/android-ndk-r16_arm64_api21 \
-      --generator-output out/android_arm64_api21
+python build/gyp_crashpad_android.py \
+  --ndk ~/usr/lib/android-ndk-r20 --arch arm64 --api-level 21 \
+  --generator-output=out/android_arm64_api21 \
 ```
 
-`gyp_crashpad_android.py` detects the build type based on the characteristics of
-the standalone toolchain given in its `--ndk` argument.
-
-`gyp_crashpad_android.py` sets the build up to use Clang by default. It’s also
-possible to use GCC by providing the `--compiler=gcc` argument to
-`gyp_crashpad_android.py`.
-
-The Android port is incomplete, but targets known to be working include
-`crashpad_test`, `crashpad_util`, and their tests. This list will grow over
-time. To build, direct `ninja` to the specific `out` directory chosen by the
+To build, direct `ninja` to the specific `out` directory chosen by the
 `--generator-output` argument to `gyp_crashpad_android.py`.
 
 ```
-$ ninja -C out/android_arm64_api21/out/Debug \
-      crashpad_test_test crashpad_util_test
+$ ninja -C out/android_arm64_api21/out/Debug all
 ```
 
 ## Testing
@@ -193,6 +192,13 @@
 $ python build/run_tests.py out/Debug
 ```
 
+To run a subset of the tests, use the --gtest\_filter flag, e.g., to run all the
+tests for MinidumpStringWriter:
+
+```sh
+$ python build/run_tests.py out/Debug --gtest_filter MinidumpStringWriter*
+```
+
 ### Windows
 
 On Windows, `end_to_end_test.py` requires the CDB debugger, installed with
@@ -220,6 +226,25 @@
 location on the detected or selected device, run them, and clean up after itself
 when done.
 
+### Fuchsia
+
+To test on Fuchsia, you need a connected device running Fuchsia and then run:
+
+```sh
+$ gn gen out/fuchsia --args='target_os="fuchsia" target_cpu="x64" is_debug=true'
+$ ninja -C out/fuchsia
+$ python build/run_tests.py out/fuchsia
+```
+
+If you have multiple devices running, you will need to specify which device you
+want using their hostname, for instance:
+
+```sh
+$ export ZIRCON_NODENAME=scare-brook-skip-dried; \
+  python build/run_tests.py out/fuchsia; \
+  unset ZIRCON_NODENAME
+```
+
 ## Contributing
 
 Crashpad’s contribution process is very similar to [Chromium’s contribution
@@ -303,7 +328,7 @@
 
 ## Buildbot
 
-The [Crashpad Buildbot](https://build.chromium.org/p/client.crashpad/) performs
-automated builds and tests of Crashpad. Before checking out or updating the
-Crashpad source code, and after checking in a new change, it is prudent to check
-the Buildbot to ensure that “the tree is green.”
+The [Crashpad Buildbot](https://ci.chromium.org/p/crashpad/g/main/console)
+performs automated builds and tests of Crashpad. Before checking out or updating
+the Crashpad source code, and after checking in a new change, it is prudent to
+check the Buildbot to ensure that “the tree is green.”
diff --git a/doc/overview_design.md b/doc/overview_design.md
index 07a7d65..9d0f0ed 100644
--- a/doc/overview_design.md
+++ b/doc/overview_design.md
@@ -240,7 +240,24 @@
 
 #### Linux/Android
 
-TODO(mmentovai): describe this. See this preliminary doc.
+On Linux, a registration is a connected socket pair between a client process and
+the Crashpad handler. This socket pair may be private or shared among many
+client processes.
+
+##### Private Connections
+
+Private connections are the default registration mode when starting the handler
+process in response to a crash or on behalf of another client. This mode is
+required to use a ptrace broker, which is in turn required to trace Android
+isolated processes.
+
+##### Shared Connections
+
+Shared connections are the default mode when using a long-lived handler. The
+same connected socket pair may be shared among any number of clients. The socket
+pair is created by the first process to start the handler at which point the
+client socket end may be shared with other clients by any convenient means (e.g.
+inheritance).
 
 ### Capturing Exceptions
 
@@ -290,8 +307,16 @@
 
 #### Linux/Android
 
-TODO(mmentovai): describe this. See [this preliminary
-doc.](https://goto.google.com/crashpad-android-dd)
+On Linux, exceptions are dispatched as signals to the crashing thread. Crashpad
+signal handlers will send a message over the socket to the Crashpad handler
+notifying it of the crash and the location of exception information to be read
+from the crashing process. When using a shared socket connection, communication
+is entirely one-way. The client sends its dump request to the handler and then
+waits until the handler responds with a SIGCONT or a timeout occurs. When using
+a private socket connection, the handler may respond over the socket to
+communicate with a ptrace broker process. The broker is forked from the crashing
+process, executes ptrace requests against the crashing process, and sends the
+information over the socket to the handler.
 
 ### The CrashpadInfo structure
 
diff --git a/doc/status.md b/doc/status.md
index dcb6e62..3948b44 100644
--- a/doc/status.md
+++ b/doc/status.md
@@ -18,24 +18,27 @@
 
 ## Completed
 
-Crashpad currently consists of a crash-reporting client and some related tools
-for macOS and Windows. The core client work for both platforms is substantially
-complete. Crashpad became the crash reporter client for
-[Chromium](https://www.chromium.org/Home) on macOS as of [March
+Crashpad has complete crash-reporting clients and some related tools for macOS,
+Windows, Fuchsia, and Linux (including Android and Chromium OS). Crashpad became
+the crash reporter client for [Chromium](https://www.chromium.org/Home) on macOS
+as of [March
 2015](https://chromium.googlesource.com/chromium/src/\+/d413b2dcb54d523811d386f1ff4084f677a6d089),
-and on Windows as of [November
-2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c).
+Windows as of [November
+2015](https://chromium.googlesource.com/chromium/src/\+/cfa5b01bb1d06bf96967bd37e21a44752801948c),
+and Android as of [January
+2019](https://chromium.googlesource.com/chromium/src/+/f890e4b5495ab693d2d37aec3c378239946154f7).
+
 
 ## In Progress
 
-Initial work on a Crashpad client for
-[Android](https://crashpad.chromium.org/bug/30) has begun. This is currently in
-the early implementation phase.
+Chromium is transitioning to Crashpad for [Chromium OS and Desktop
+Linux](https://crbug.com/942279).
+
+Work has begun on a Crashpad client for
+[iOS](https://crashpad.chromium.org/bug/31).
 
 ## Future
 
-There are plans to bring Crashpad clients to other operating systems in the
-future, including a more generic non-Android Linux implementation. There are
-also plans to implement a [crash report
+There are also plans to implement a [crash report
 processor](https://crashpad.chromium.org/bug/29) as part of Crashpad. No
 timeline for completing this work has been set yet.
diff --git a/doc/support/crashpad.doxy b/doc/support/crashpad.doxy
index 2f3e2bb..ca1ea25 100644
--- a/doc/support/crashpad.doxy
+++ b/doc/support/crashpad.doxy
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.14
+# Doxyfile 1.8.18
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -17,10 +17,10 @@
 # Project related configuration options
 #---------------------------------------------------------------------------
 
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all text
-# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
-# built into libc) for the transcoding. See
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
 # https://www.gnu.org/software/libiconv/ for the list of possible encodings.
 # The default value is: UTF-8.
 
@@ -93,6 +93,14 @@
 
 OUTPUT_LANGUAGE        = English
 
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION  = None
+
 # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
 # descriptions after the members that are listed in the file and class
 # documentation (similar to Javadoc). Set to NO to disable this.
@@ -181,6 +189,16 @@
 
 JAVADOC_AUTOBRIEF      = NO
 
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
 # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
 # line (until the first dot) of a Qt-style comment as the brief description. If
 # set to NO, the Qt-style will behave just like regular Qt-style comments (thus
@@ -230,15 +248,13 @@
 # "Side Effects:". You can put \n's in the value part of an alias to insert
 # newlines (in the resulting output). You can put ^^ in the value part of an
 # alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
 
 ALIASES                =
 
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST              =
-
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
 # only. Doxygen will then generate output that is more tailored for C. For
 # instance, some of the names that are used will be different. The list of all
@@ -267,17 +283,26 @@
 
 OPTIMIZE_OUTPUT_VHDL   = NO
 
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
 # Doxygen selects the parser to use depending on the extension of the files it
 # parses. With this tag you can assign which parser to use for a given
 # extension. Doxygen has a built-in mapping, but you can override or extend it
 # using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
-# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
-# Fortran. In the later case the parser tries to guess whether the code is fixed
-# or free formatted code, this is the default for Fortran type files), VHDL. For
-# instance to make doxygen treat .inc files as Fortran files (default is PHP),
-# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
 #
 # Note: For files without extension you can use no_extension as a placeholder.
 #
@@ -288,7 +313,7 @@
 
 # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
 # according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
 # The output of markdown processing is further processed by doxygen, so you can
 # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
 # case of backward compatibilities issues.
@@ -300,7 +325,7 @@
 # to that level are automatically included in the table of contents, even if
 # they do not have an id attribute.
 # Note: This feature currently applies only to Markdown headings.
-# Minimum value: 0, maximum value: 99, default value: 0.
+# Minimum value: 0, maximum value: 99, default value: 5.
 # This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
 
 TOC_INCLUDE_HEADINGS   = 0
@@ -436,6 +461,12 @@
 
 EXTRACT_PRIVATE        = NO
 
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
 # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
 # scope will be included in the documentation.
 # The default value is: NO.
@@ -490,8 +521,8 @@
 HIDE_UNDOC_CLASSES     = NO
 
 # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
 # The default value is: NO.
 
 HIDE_FRIEND_COMPOUNDS  = NO
@@ -514,7 +545,7 @@
 # names in lower-case letters. If set to YES, upper-case letters are also
 # allowed. This is useful if you have classes or files whose names only differ
 # in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
+# (including Cygwin) ands Mac users are advised to set this option to NO.
 # The default value is: system dependent.
 
 CASE_SENSE_NAMES       = YES
@@ -746,7 +777,8 @@
 # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
 # are documented, but have no documentation for their parameters or return
 # value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation.
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
 # The default value is: NO.
 
 WARN_NO_PARAMDOC       = NO
@@ -805,8 +837,10 @@
 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
 # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
 # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
-# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
+# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          = *.c \
                          *.cc \
@@ -968,7 +1002,7 @@
 STRIP_CODE_COMMENTS    = YES
 
 # If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# function all documented functions referencing it will be listed.
+# entity all documented functions referencing it will be listed.
 # The default value is: NO.
 
 REFERENCED_BY_RELATION = NO
@@ -1005,7 +1039,7 @@
 #
 # To use it do the following:
 # - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
 # - Make sure the INPUT points to the root of the source tree
 # - Run doxygen as normal
 #
@@ -1183,9 +1217,9 @@
 
 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
 # documentation will contain a main index with vertical navigation menus that
-# are dynamically created via Javascript. If disabled, the navigation index will
+# are dynamically created via JavaScript. If disabled, the navigation index will
 # consists of multiple levels of tabs that are statically embedded in every HTML
-# page. Disable this option to support browsers that do not have Javascript,
+# page. Disable this option to support browsers that do not have JavaScript,
 # like the Qt help browser.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1215,13 +1249,13 @@
 
 # If the GENERATE_DOCSET tag is set to YES, additional index files will be
 # generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: https://developer.apple.com/tools/xcode/), introduced with
-# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
 # Makefile in the HTML output directory. Running make will produce the docset in
 # that directory and running make install will install the docset in
 # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
@@ -1260,7 +1294,7 @@
 # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
 # additional HTML index files: index.hhp, index.hhc, and index.hhk. The
 # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
 # Windows.
 #
 # The HTML Help Workshop contains a compiler that can convert all HTML output
@@ -1336,7 +1370,7 @@
 
 # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
 # Project output. For more information please see Qt Help Project / Namespace
-# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).
+# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
 # The default value is: org.doxygen.Project.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1344,7 +1378,8 @@
 
 # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
 # Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).
+# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
+# folders).
 # The default value is: doc.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1352,21 +1387,23 @@
 
 # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
 # filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_NAME   =
 
 # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
 # custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_ATTRS  =
 
 # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
 # project's filter section matches. Qt Help Project / Filter Attributes (see:
-# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_SECT_FILTER_ATTRS  =
@@ -1450,6 +1487,17 @@
 
 EXT_LINKS_IN_WINDOW    = NO
 
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png The default and svg Looks nicer but requires the
+# pdf2svg tool.
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
 # Use this tag to change the font size of LaTeX formulas included as images in
 # the HTML documentation. When you change the font size after a successful
 # doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1470,8 +1518,14 @@
 
 FORMULA_TRANSPARENT    = YES
 
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
 # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# https://www.mathjax.org) which uses client side Javascript for the rendering
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
 # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
 # installed or if you want to formulas look prettier in the HTML output. When
 # enabled you may also need to install MathJax separately and configure the path
@@ -1499,7 +1553,7 @@
 # Content Delivery Network so you can quickly see the result without installing
 # MathJax. However, it is strongly recommended to install a local copy of
 # MathJax from https://www.mathjax.org before deployment.
-# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.
+# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
@@ -1541,7 +1595,7 @@
 SEARCHENGINE           = YES
 
 # When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
+# implemented using a web server instead of a web client using JavaScript. There
 # are two flavors of web server based searching depending on the EXTERNAL_SEARCH
 # setting. When disabled, doxygen will generate a PHP script for searching and
 # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
@@ -1625,21 +1679,35 @@
 # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
 # invoked.
 #
-# Note that when enabling USE_PDFLATEX this option is only used for generating
-# bitmaps for formulas in the HTML output, but not in the Makefile that is
-# written to the output directory.
-# The default file is: latex.
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_CMD_NAME         = latex
 
 # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
 # index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
 # The default file is: makeindex.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 MAKEINDEX_CMD_NAME     = makeindex
 
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
 # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
 # documents. This may be useful for small projects and may help to save some
 # trees in general.
@@ -1774,6 +1842,14 @@
 
 LATEX_TIMESTAMP        = NO
 
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
 #---------------------------------------------------------------------------
 # Configuration options related to the RTF output
 #---------------------------------------------------------------------------
@@ -1813,9 +1889,9 @@
 
 RTF_HYPERLINKS         = NO
 
-# Load stylesheet definitions from file. Syntax is similar to doxygen's config
-# file, i.e. a series of assignments. You only have to provide replacements,
-# missing definitions are set to their default value.
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
 #
 # See also section "Doxygen usage" for information on how to generate the
 # default style sheet that doxygen normally uses.
@@ -1824,8 +1900,8 @@
 RTF_STYLESHEET_FILE    =
 
 # Set optional variables used in the generation of an RTF document. Syntax is
-# similar to doxygen's config file. A template extensions file can be generated
-# using doxygen -e rtf extensionFile.
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
 # This tag requires that the tag GENERATE_RTF is set to YES.
 
 RTF_EXTENSIONS_FILE    =
@@ -1911,6 +1987,13 @@
 
 XML_PROGRAMLISTING     = YES
 
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the DOCBOOK output
 #---------------------------------------------------------------------------
@@ -2116,12 +2199,6 @@
 
 EXTERNAL_PAGES         = YES
 
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of 'which perl').
-# The default file (with absolute path) is: /usr/bin/perl.
-
-PERL_PATH              = /usr/bin/perl
-
 #---------------------------------------------------------------------------
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
@@ -2135,15 +2212,6 @@
 
 CLASS_DIAGRAMS         = YES
 
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see:
-# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH            =
-
 # You can include diagrams made with dia in doxygen documentation. Doxygen will
 # then run dia to produce the diagram and insert it in the documentation. The
 # DIA_PATH tag allows you to specify the directory where the dia binary resides.
diff --git a/doc/support/generate_doxygen.py b/doc/support/generate_doxygen.py
index a93028d..577b51e 100755
--- a/doc/support/generate_doxygen.py
+++ b/doc/support/generate_doxygen.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# coding: utf-8
 
 # Copyright 2017 The Crashpad Authors. All rights reserved.
 #
@@ -24,26 +23,26 @@
 
 
 def main(args):
-  script_dir = os.path.dirname(__file__)
-  crashpad_dir = os.path.join(script_dir, os.pardir, os.pardir)
+    script_dir = os.path.dirname(__file__)
+    crashpad_dir = os.path.join(script_dir, os.pardir, os.pardir)
 
-  # Run from the Crashpad project root directory.
-  os.chdir(crashpad_dir)
+    # Run from the Crashpad project root directory.
+    os.chdir(crashpad_dir)
 
-  output_dir = os.path.join('out', 'doc', 'doxygen')
+    output_dir = os.path.join('out', 'doc', 'doxygen')
 
-  if os.path.isdir(output_dir) and not os.path.islink(output_dir):
-    shutil.rmtree(output_dir)
-  elif os.path.exists(output_dir):
-    os.unlink(output_dir)
+    if os.path.isdir(output_dir) and not os.path.islink(output_dir):
+        shutil.rmtree(output_dir)
+    elif os.path.exists(output_dir):
+        os.unlink(output_dir)
 
-  os.makedirs(output_dir, 0o755)
+    os.makedirs(output_dir, 0o755)
 
-  doxy_file = os.path.join('doc', 'support', 'crashpad.doxy')
-  subprocess.check_call(['doxygen', doxy_file])
+    doxy_file = os.path.join('doc', 'support', 'crashpad.doxy')
+    subprocess.check_call(['doxygen', doxy_file])
 
-  return 0
+    return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/handler/BUILD.gn b/handler/BUILD.gn
index 2eae30b..17c4cbd 100644
--- a/handler/BUILD.gn
+++ b/handler/BUILD.gn
@@ -42,6 +42,8 @@
   if (crashpad_is_linux || crashpad_is_android) {
     set_sources_assignment_filter([])
     sources += [
+      "linux/capture_snapshot.cc",
+      "linux/capture_snapshot.h",
       "linux/crash_report_exception_handler.cc",
       "linux/crash_report_exception_handler.h",
       "linux/exception_handler_server.cc",
@@ -49,6 +51,13 @@
     ]
   }
 
+  if (crashpad_is_linux) {
+    sources += [
+      "linux/cros_crash_report_exception_handler.cc",
+      "linux/cros_crash_report_exception_handler.h",
+    ]
+  }
+
   if (crashpad_is_win) {
     sources += [
       "win/crash_report_exception_handler.cc",
@@ -56,25 +65,18 @@
     ]
   }
 
-  if (crashpad_is_fuchsia) {
-    sources += [
-      "fuchsia/crash_report_exception_handler.cc",
-      "fuchsia/crash_report_exception_handler.h",
-      "fuchsia/exception_handler_server.cc",
-      "fuchsia/exception_handler_server.h",
-    ]
-  }
-
   public_configs = [ "..:crashpad_config" ]
 
-  deps = [
+  public_deps = [
     "../client",
-    "../compat",
+    "../third_party/mini_chromium:base",
+    "../util",
+  ]
+
+  deps = [
     "../minidump",
     "../snapshot",
-    "../third_party/mini_chromium:base",
     "../tools:tool_support",
-    "../util",
   ]
 
   if (crashpad_is_win) {
@@ -82,12 +84,20 @@
   }
 }
 
+if (crashpad_is_android) {
+  # CrashpadHandlerMain is defined in a separate target so that it can be
+  # overriden by implementers
+  source_set("crashpad_handler_main") {
+    sources = [ "crashpad_handler_main.cc" ]
+
+    deps = [ ":handler" ]
+  }
+}
+
 source_set("handler_test") {
   testonly = true
 
-  sources = [
-    "minidump_to_upload_parameters_test.cc",
-  ]
+  sources = [ "minidump_to_upload_parameters_test.cc" ]
 
   if (crashpad_is_linux || crashpad_is_android) {
     sources += [ "linux/exception_handler_server_test.cc" ]
@@ -110,6 +120,8 @@
   ]
 
   if (crashpad_is_win) {
+    deps += [ "../minidump:test_support" ]
+
     data_deps = [
       ":crashpad_handler_test_extended_handler",
       ":fake_handler_that_crashes_at_startup",
@@ -117,45 +129,28 @@
   }
 }
 
-crashpad_executable("crashpad_handler") {
-  sources = [
-    "main.cc",
-  ]
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_handler") {
+    sources = [ "main.cc" ]
 
-  deps = [
-    ":handler",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../third_party/mini_chromium:base",
-  ]
+    deps = [
+      ":handler",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../third_party/mini_chromium:base",
+      "../tools:tool_support",
+    ]
 
-  if (crashpad_is_mac && crashpad_is_in_chromium) {
-    if (is_component_build) {
-      ldflags = [
-        # The handler is in
-        # Chromium.app/Contents/Versions/X/Chromium Framework.framework/Versions/A/Helpers/
-        # so set rpath up to the base.
-        "-rpath",
-        "@loader_path/../../../../../../../..",
-
-        # The handler is also in
-        # Content Shell.app/Contents/Frameworks/Content Shell Framework.framework/Helpers/
-        # so set the rpath for that too.
-        "-rpath",
-        "@loader_path/../../../../..",
-      ]
-    }
-  }
-
-  if (crashpad_is_win) {
-    if (crashpad_is_in_chromium) {
-      remove_configs = [ "//build/config/win:console" ]
-      configs = [ "//build/config/win:windowed" ]
-    } else {
-      remove_configs =
-          [ "//third_party/mini_chromium/mini_chromium/build:win_console" ]
-      configs =
-          [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
+    if (crashpad_is_win) {
+      if (crashpad_is_in_chromium || crashpad_is_in_dart) {
+        remove_configs = [ "//build/config/win:console" ]
+        configs = [ "//build/config/win:windowed" ]
+      } else {
+        remove_configs =
+            [ "//third_party/mini_chromium/mini_chromium/build:win_console" ]
+        configs =
+            [ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
+      }
     }
   }
 }
@@ -166,42 +161,47 @@
 # handler executable an acceptable name.
 if (crashpad_is_android) {
   copy("crashpad_handler_named_as_so") {
+    deps = [ ":crashpad_handler" ]
+
+    sources = [ "$root_out_dir/crashpad_handler" ]
+
+    outputs = [ "$root_out_dir/libcrashpad_handler.so" ]
+  }
+
+  crashpad_executable("crashpad_handler_trampoline") {
+    set_sources_assignment_filter([])
+    output_name = "libcrashpad_handler_trampoline.so"
+
+    sources = [ "linux/handler_trampoline.cc" ]
+
+    ldflags = [ "-llog" ]
+
+    if (crashpad_is_in_chromium) {
+      no_default_deps = true
+    }
+  }
+}
+
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_handler_test_extended_handler") {
+    testonly = true
+
+    sources = [ "crashpad_handler_test_extended_handler.cc" ]
+
     deps = [
-      ":crashpad_handler",
-    ]
-
-    sources = [
-      "$root_out_dir/crashpad_handler",
-    ]
-
-    outputs = [
-      "$root_out_dir/libcrashpad_handler.so",
+      ":handler",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../minidump:test_support",
+      "../third_party/mini_chromium:base",
+      "../tools:tool_support",
     ]
   }
 }
 
-crashpad_executable("crashpad_handler_test_extended_handler") {
-  testonly = true
-
-  sources = [
-    "crashpad_handler_test_extended_handler.cc",
-  ]
-
-  deps = [
-    ":handler",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../minidump:test_support",
-    "../third_party/mini_chromium:base",
-    "../tools:tool_support",
-  ]
-}
-
 if (crashpad_is_win) {
   crashpad_executable("crashpad_handler_com") {
-    sources = [
-      "main.cc",
-    ]
+    sources = [ "main.cc" ]
 
     # Avoid .exp, .ilk, and .lib file collisions with crashpad_handler.exe by
     # having this target produce crashpad_handler_com.com. Don’t use this target
@@ -213,27 +213,20 @@
       "../build:default_exe_manifest_win",
       "../compat",
       "../third_party/mini_chromium:base",
+      "../tools:tool_support",
     ]
   }
 
   copy("crashpad_handler_console") {
-    deps = [
-      ":crashpad_handler_com",
-    ]
-    sources = [
-      "$root_out_dir/crashpad_handler_com.com",
-    ]
-    outputs = [
-      "$root_out_dir/crashpad_handler.com",
-    ]
+    deps = [ ":crashpad_handler_com" ]
+    sources = [ "$root_out_dir/crashpad_handler_com.com" ]
+    outputs = [ "$root_out_dir/crashpad_handler.com" ]
   }
 
   crashpad_executable("crash_other_program") {
     testonly = true
 
-    sources = [
-      "win/crash_other_program.cc",
-    ]
+    sources = [ "win/crash_other_program.cc" ]
 
     deps = [
       "../client",
@@ -246,9 +239,7 @@
   crashpad_executable("crashy_program") {
     testonly = true
 
-    sources = [
-      "win/crashy_test_program.cc",
-    ]
+    sources = [ "win/crashy_test_program.cc" ]
 
     deps = [
       "../client",
@@ -259,9 +250,7 @@
   crashpad_executable("crashy_signal") {
     testonly = true
 
-    sources = [
-      "win/crashy_signal.cc",
-    ]
+    sources = [ "win/crashy_signal.cc" ]
 
     cflags = [ "/wd4702" ]  # Unreachable code.
 
@@ -274,17 +263,13 @@
   crashpad_executable("fake_handler_that_crashes_at_startup") {
     testonly = true
 
-    sources = [
-      "win/fake_handler_that_crashes_at_startup.cc",
-    ]
+    sources = [ "win/fake_handler_that_crashes_at_startup.cc" ]
   }
 
   crashpad_executable("hanging_program") {
     testonly = true
 
-    sources = [
-      "win/hanging_program.cc",
-    ]
+    sources = [ "win/hanging_program.cc" ]
 
     deps = [
       "../client",
@@ -295,17 +280,13 @@
   crashpad_loadable_module("loader_lock_dll") {
     testonly = true
 
-    sources = [
-      "win/loader_lock_dll.cc",
-    ]
+    sources = [ "win/loader_lock_dll.cc" ]
   }
 
   crashpad_executable("self_destroying_program") {
     testonly = true
 
-    sources = [
-      "win/self_destroying_test_program.cc",
-    ]
+    sources = [ "win/self_destroying_test_program.cc" ]
 
     deps = [
       "../client",
@@ -320,9 +301,7 @@
     crashpad_executable("crashy_z7_loader") {
       testonly = true
 
-      sources = [
-        "win/crashy_test_z7_loader.cc",
-      ]
+      sources = [ "win/crashy_test_z7_loader.cc" ]
 
       deps = [
         "../client",
diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc
index 205d860..e144bdd 100644
--- a/handler/crash_report_upload_thread.cc
+++ b/handler/crash_report_upload_thread.cc
@@ -246,12 +246,6 @@
 CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
     const CrashReportDatabase::UploadReport* report,
     std::string* response_body) {
-#if defined(OS_ANDROID)
-  // TODO(jperaza): This method can be enabled on Android after HTTPTransport is
-  // implemented and Crashpad takes over upload responsibilty on Android.
-  NOTREACHED();
-  return UploadResult::kPermanentFailure;
-#else
   std::map<std::string, std::string> parameters;
 
   FileReader* reader = report->Reader();
@@ -338,7 +332,6 @@
   }
 
   return UploadResult::kSuccess;
-#endif  // OS_ANDROID
 }
 
 void CrashReportUploadThread::DoWork(const WorkerThread* thread) {
diff --git a/handler/crashpad_handler.md b/handler/crashpad_handler.md
index 003ee2e..6d055ed 100644
--- a/handler/crashpad_handler.md
+++ b/handler/crashpad_handler.md
@@ -121,13 +121,6 @@
    Either this option or **--mach-service**, but not both, is required. This
    option is only valid on macOS.
 
- * **--no-identify-client-via-url**
-
-   Do not add client-identifying fields to the URL. By default, `"prod"`,
-   `"ver"`, and `"guid"` annotations are added to the upload URL as name-value
-   pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this
-   option disables that behavior.
-
  * **--initial-client-data**=*HANDLE_request_crash_dump*,*HANDLE_request_non_crash_dump*,*HANDLE_non_crash_dump_completed*,*HANDLE_first_pipe_instance*,*HANDLE_client_process*,*Address_crash_exception_information*,*Address_non_crash_exception_information*,*Address_debug_critical_section*
 
    Register the initial client using the inherited handles and data provided.
@@ -141,6 +134,13 @@
    client to register, and exits when all clients have exited, after waiting for
    any uploads in progress to complete.
 
+ * **--initial-client-fd**=_FD_
+
+   Wait for client requests on _FD_. Either this option or
+   **--trace-parent-with-exception**, but not both, is required. The handler
+   exits when all client connections have been closed. This option is only valid
+   on Linux platforms.
+
  * **--mach-service**=_SERVICE_
 
    Check in with the bootstrap server under the name _SERVICE_. Either this
@@ -198,6 +198,13 @@
    To prevent excessive accumulation of handler processes, _ARGUMENT_ must not
    be `--monitor-self`.
 
+* **--no-identify-client-via-url**
+
+   Do not add client-identifying fields to the URL. By default, `"prod"`,
+   `"ver"`, and `"guid"` annotations are added to the upload URL as name-value
+   pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this
+   option disables that behavior.
+
  * **--no-periodic-tasks**
 
    Do not scan for new pending crash reports or prune the crash report database.
@@ -226,6 +233,12 @@
    for use with collection servers that don’t accept uploads compressed in this
    way.
 
+ * **--no-write-minidump-to-database**
+
+   Do not write the minidump to database. Normally, the minidump is written to
+   database for upload. Use this option with **--write-minidump-to-log** to
+   only write the minidump to log. This option is only available to Android.
+
  * **--pipe-name**=_PIPE_
 
    Listen on the given pipe name for connections from clients. _PIPE_ must be of
@@ -245,17 +258,24 @@
    parent process. This option is only valid on macOS. Use of this option is
    discouraged. It should not be used absent extraordinary circumstances.
 
+ * **--sanitization-information**=_SANITIZATION-INFORMATION-ADDRESS_
+
+   Provides sanitization settings in a SanitizationInformation struct at
+   _SANITIZATION-INFORMATION-ADDRESS_. This option requires
+   **--trace-parent-with-exception** and is only valid on Linux platforms.
+
+ * **--shared-client-connection**
+
+   Indicates that the file descriptor provided by **--initial-client-fd** is
+   shared among mulitple clients. Using a broker process is not supported for
+   clients using this option. This option is only valid on Linux platforms.
+
  * **--trace-parent-with-exception**=_EXCEPTION-INFORMATION-ADDRESS_
 
    Causes the handler process to trace its parent process and exit. The parent
    process should have an ExceptionInformation struct at
-   _EXCEPTION-INFORMATION-ADDRESS_.
-
- * **--initial-client-fd**=_FD_
-
-   Starts the excetion handler server with an initial ExceptionHandlerClient
-   connected on the socket _FD_. The server will exit when all connected client
-   sockets have been closed.
+   _EXCEPTION-INFORMATION-ADDRESS_. This option is only valid on Linux
+   platforms.
 
  * **--url**=_URL_
 
@@ -265,6 +285,19 @@
    library, typically in response to a user requesting this behavior. If this
    option is not specified, this program will behave as if uploads are disabled.
 
+ * **--use-cros-crash-reporter**
+
+   Causes crash reports to be passed via an in-memory file to
+   `/sbin/crash_reporter` instead of storing them in the database. The database
+   is still used for Crashpad settings. This option is only valid on Chromium
+   OS.
+
+* **--write-minidump-to-log**
+
+  Write the minidump to log. By default the minidump is only written to
+  database. Use this option with **--no-write-minidump-to-database** to only
+  write the minidump to log. This option is only available to Android.
+
  * **--help**
 
    Display help and exit.
diff --git a/util/net/http_transport_none.cc b/handler/crashpad_handler_main.cc
similarity index 66%
copy from util/net/http_transport_none.cc
copy to handler/crashpad_handler_main.cc
index 1c08c1f..b47488e 100644
--- a/util/net/http_transport_none.cc
+++ b/handler/crashpad_handler_main.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
-
-#include "base/logging.h"
+#include "handler/handler_main.h"
 
 namespace crashpad {
 
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
+extern "C" {
+
+__attribute__((visibility("default"), used)) int CrashpadHandlerMain(
+    int argc,
+    char* argv[]) {
+  return HandlerMain(argc, argv, nullptr);
 }
 
+}  // extern "C"
+
 }  // namespace crashpad
diff --git a/handler/fuchsia/crash_report_exception_handler.cc b/handler/fuchsia/crash_report_exception_handler.cc
deleted file mode 100644
index 1da0985..0000000
--- a/handler/fuchsia/crash_report_exception_handler.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2017 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.
-
-#include "handler/fuchsia/crash_report_exception_handler.h"
-
-#include <zircon/syscalls/exception.h>
-
-#include "base/fuchsia/fuchsia_logging.h"
-#include "client/settings.h"
-#include "minidump/minidump_file_writer.h"
-#include "minidump/minidump_user_extension_stream_data_source.h"
-#include "snapshot/fuchsia/process_snapshot_fuchsia.h"
-#include "util/fuchsia/koid_utilities.h"
-#include "util/fuchsia/scoped_task_suspend.h"
-
-namespace crashpad {
-
-namespace {
-
-struct ScopedZxTaskResumeAfterException {
-  ScopedZxTaskResumeAfterException(zx_handle_t thread) : thread_(thread) {}
-  ~ScopedZxTaskResumeAfterException() {
-    DCHECK_NE(thread_, ZX_HANDLE_INVALID);
-    // Resuming with ZX_RESUME_TRY_NEXT chains to the next handler. In normal
-    // operation, there won't be another beyond this one, which will result in
-    // the kernel terminating the process.
-    zx_status_t status =
-        zx_task_resume(thread_, ZX_RESUME_EXCEPTION | ZX_RESUME_TRY_NEXT);
-    if (status != ZX_OK) {
-      ZX_LOG(ERROR, status) << "zx_task_resume";
-    }
-  }
-
- private:
-  zx_handle_t thread_;  // weak
-};
-
-}  // namespace
-
-CrashReportExceptionHandler::CrashReportExceptionHandler(
-    CrashReportDatabase* database,
-    CrashReportUploadThread* upload_thread,
-    const std::map<std::string, std::string>* process_annotations,
-    const std::map<std::string, base::FilePath>* process_attachments,
-    const UserStreamDataSources* user_stream_data_sources)
-    : database_(database),
-      upload_thread_(upload_thread),
-      process_annotations_(process_annotations),
-      process_attachments_(process_attachments),
-      user_stream_data_sources_(user_stream_data_sources) {}
-
-CrashReportExceptionHandler::~CrashReportExceptionHandler() {}
-
-bool CrashReportExceptionHandler::HandleException(uint64_t process_id,
-                                                  uint64_t thread_id) {
-  // TODO(scottmg): This function needs to be instrumented with metrics calls,
-  // https://crashpad.chromium.org/bug/230.
-
-  base::ScopedZxHandle process(GetProcessFromKoid(process_id));
-  if (!process.is_valid()) {
-    // There's no way to zx_task_resume() the thread if the process retrieval
-    // fails. Assume that the process has been already killed, and bail.
-    return false;
-  }
-
-  base::ScopedZxHandle thread(GetChildHandleByKoid(process.get(), thread_id));
-  if (!thread.is_valid()) {
-    return false;
-  }
-
-  return HandleExceptionHandles(process.get(), thread.get());
-}
-
-bool CrashReportExceptionHandler::HandleExceptionHandles(zx_handle_t process,
-                                                         zx_handle_t thread) {
-  // Now that the thread has been successfully retrieved, it is possible to
-  // correctly call zx_task_resume() to continue exception processing, even if
-  // something else during this function fails.
-  ScopedZxTaskResumeAfterException resume(thread);
-
-  ProcessSnapshotFuchsia process_snapshot;
-  if (!process_snapshot.Initialize(process)) {
-    return false;
-  }
-
-  CrashpadInfoClientOptions client_options;
-  process_snapshot.GetCrashpadOptions(&client_options);
-
-  if (client_options.crashpad_handler_behavior != TriState::kDisabled) {
-    zx_exception_report_t report;
-    zx_status_t status = zx_object_get_info(thread,
-                                            ZX_INFO_THREAD_EXCEPTION_REPORT,
-                                            &report,
-                                            sizeof(report),
-                                            nullptr,
-                                            nullptr);
-    if (status != ZX_OK) {
-      ZX_LOG(ERROR, status)
-          << "zx_object_get_info ZX_INFO_THREAD_EXCEPTION_REPORT";
-      return false;
-    }
-
-    zx_koid_t thread_id = GetKoidForHandle(thread);
-    if (!process_snapshot.InitializeException(thread_id, report)) {
-      return false;
-    }
-
-    UUID client_id;
-    Settings* const settings = database_->GetSettings();
-    if (settings) {
-      // If GetSettings() or GetClientID() fails, something else will log a
-      // message and client_id will be left at its default value, all zeroes,
-      // which is appropriate.
-      settings->GetClientID(&client_id);
-    }
-
-    process_snapshot.SetClientID(client_id);
-    process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
-
-    std::unique_ptr<CrashReportDatabase::NewReport> new_report;
-    CrashReportDatabase::OperationStatus database_status =
-        database_->PrepareNewCrashReport(&new_report);
-    if (database_status != CrashReportDatabase::kNoError) {
-      return false;
-    }
-
-    process_snapshot.SetReportID(new_report->ReportID());
-
-    MinidumpFileWriter minidump;
-    minidump.InitializeFromSnapshot(&process_snapshot);
-    AddUserExtensionStreams(
-        user_stream_data_sources_, &process_snapshot, &minidump);
-
-    if (!minidump.WriteEverything(new_report->Writer())) {
-      return false;
-    }
-
-    if (process_attachments_) {
-      // Note that attachments are read at this point each time rather than once
-      // so that if the contents of the file has changed it will be re-read for
-      // each upload (e.g. in the case of a log file).
-      for (const auto& it : *process_attachments_) {
-        FileWriter* writer = new_report->AddAttachment(it.first);
-        if (writer) {
-          std::string contents;
-          if (!LoggingReadEntireFile(it.second, &contents)) {
-            // Not being able to read the file isn't considered fatal, and
-            // should not prevent the report from being processed.
-            continue;
-          }
-          writer->Write(contents.data(), contents.size());
-        }
-      }
-    }
-
-    UUID uuid;
-    database_status =
-        database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
-    if (database_status != CrashReportDatabase::kNoError) {
-      return false;
-    }
-
-    if (upload_thread_) {
-      upload_thread_->ReportPending(uuid);
-    }
-  }
-
-  return true;
-}
-
-}  // namespace crashpad
diff --git a/handler/fuchsia/crash_report_exception_handler.h b/handler/fuchsia/crash_report_exception_handler.h
deleted file mode 100644
index 5043d7e..0000000
--- a/handler/fuchsia/crash_report_exception_handler.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2017 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_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
-#define CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
-
-#include <stdint.h>
-#include <zircon/types.h>
-
-#include <map>
-#include <string>
-
-#include "base/files/file_path.h"
-#include "base/macros.h"
-#include "client/crash_report_database.h"
-#include "handler/crash_report_upload_thread.h"
-#include "handler/user_stream_data_source.h"
-
-namespace crashpad {
-
-//! \brief An exception handler that writes crash reports for exception messages
-//!     to a CrashReportDatabase.
-class CrashReportExceptionHandler {
- public:
-  //! \brief Creates a new object that will store crash reports in \a database.
-  //!
-  //! \param[in] database The database to store crash reports in. Weak.
-  //! \param[in] upload_thread The upload thread to notify when a new crash
-  //!     report is written into \a database.
-  //! \param[in] process_annotations A map of annotations to insert as
-  //!     process-level annotations into each crash report that is written. Do
-  //!     not confuse this with module-level annotations, which are under the
-  //!     control of the crashing process, and are used to implement Chrome's
-  //!     "crash keys." Process-level annotations are those that are beyond the
-  //!     control of the crashing process, which must reliably be set even if
-  //!     the process crashes before it’s able to establish its own annotations.
-  //!     To interoperate with Breakpad servers, the recommended practice is to
-  //!     specify values for the `"prod"` and `"ver"` keys as process
-  //!     annotations.
-  //! \param[in] process_attachments A map of file name keys to file paths to be
-  //!     included in the report. Each time a report is written, the file paths
-  //!     will be read in their entirety and included in the report using the
-  //!     file name key as the name in the http upload.
-  //! \param[in] user_stream_data_sources Data sources to be used to extend
-  //!     crash reports. For each crash report that is written, the data sources
-  //!     are called in turn. These data sources may contribute additional
-  //!     minidump streams. `nullptr` if not required.
-  CrashReportExceptionHandler(
-      CrashReportDatabase* database,
-      CrashReportUploadThread* upload_thread,
-      const std::map<std::string, std::string>* process_annotations,
-      const std::map<std::string, base::FilePath>* process_attachments,
-      const UserStreamDataSources* user_stream_data_sources);
-
-  ~CrashReportExceptionHandler();
-
-  //! \brief Called when the exception handler server has caught an exception
-  //!     and wants a crash dump to be taken.
-  //!
-  //! This function is expected to call `zx_task_resume()` in order to complete
-  //! handling of the exception.
-  //!
-  //! \param[in] process_id The koid of the process which sustained the
-  //!     exception.
-  //! \param[in] thread_id The koid of the thread which sustained the exception.
-  //! \return `true` on success, or `false` with an error logged.
-  bool HandleException(uint64_t process_id, uint64_t thread_id);
-
-  //! \brief Called when the exception handler server has caught an exception
-  //!     and wants a crash dump to be taken.
-  //!
-  //! This function is expected to call `zx_task_resume()` in order to complete
-  //! handling of the exception.
-  //!
-  //! \param[in] process The handle to the process which sustained the
-  //!     exception.
-  //! \param[in] thread The handle to the thread of \a process which sustained
-  //!     the exception.
-  //! \return `true` on success, or `false` with an error logged.
-  bool HandleExceptionHandles(zx_handle_t process, zx_handle_t thread);
-
- private:
-  CrashReportDatabase* database_;  // weak
-  CrashReportUploadThread* upload_thread_;  // weak
-  const std::map<std::string, std::string>* process_annotations_;  // weak
-  const std::map<std::string, base::FilePath>* process_attachments_;  // weak
-  const UserStreamDataSources* user_stream_data_sources_;  // weak
-
-  DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
-};
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_HANDLER_FUCHSIA_CRASH_REPORT_EXCEPTION_HANDLER_H_
diff --git a/handler/fuchsia/exception_handler_server.cc b/handler/fuchsia/exception_handler_server.cc
deleted file mode 100644
index 63153d2..0000000
--- a/handler/fuchsia/exception_handler_server.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2017 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.
-
-#include "handler/fuchsia/exception_handler_server.h"
-
-#include <zircon/syscalls/exception.h>
-#include <zircon/syscalls/port.h>
-
-#include <utility>
-
-#include "base/fuchsia/fuchsia_logging.h"
-#include "base/logging.h"
-#include "handler/fuchsia/crash_report_exception_handler.h"
-#include "util/fuchsia/system_exception_port_key.h"
-
-namespace crashpad {
-
-ExceptionHandlerServer::ExceptionHandlerServer(
-    base::ScopedZxHandle root_job,
-    base::ScopedZxHandle exception_port)
-    : root_job_(std::move(root_job)),
-      exception_port_(std::move(exception_port)) {}
-
-ExceptionHandlerServer::~ExceptionHandlerServer() = default;
-
-void ExceptionHandlerServer::Run(CrashReportExceptionHandler* handler) {
-  while (true) {
-    zx_port_packet_t packet;
-    zx_status_t status =
-        zx_port_wait(exception_port_.get(), ZX_TIME_INFINITE, &packet);
-    if (status != ZX_OK) {
-      ZX_LOG(ERROR, status) << "zx_port_wait, aborting";
-      return;
-    }
-
-    if (packet.key != kSystemExceptionPortKey) {
-      LOG(ERROR) << "unexpected packet key, ignoring";
-      continue;
-    }
-
-    bool result =
-        handler->HandleException(packet.exception.pid, packet.exception.tid);
-    if (!result) {
-      LOG(ERROR) << "HandleException failed";
-    }
-  }
-}
-
-}  // namespace crashpad
diff --git a/handler/fuchsia/exception_handler_server.h b/handler/fuchsia/exception_handler_server.h
deleted file mode 100644
index 184d148..0000000
--- a/handler/fuchsia/exception_handler_server.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 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_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
-#define CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
-
-#include "base/macros.h"
-#include "base/fuchsia/scoped_zx_handle.h"
-
-namespace crashpad {
-
-class CrashReportExceptionHandler;
-
-//! \brief Runs the main exception-handling server in Crashpad's handler
-//!     process.
-class ExceptionHandlerServer {
- public:
-  //! \brief Constructs an ExceptionHandlerServer object.
-  //!
-  //! \param[in] root_job The root of the tree of processes that will be handled
-  //!     by this server. It is assumed that \a exception_port is the exception
-  //!     port of this job.
-  //! \param[in] exception_port The exception port that this server will
-  //!     monitor.
-  ExceptionHandlerServer(base::ScopedZxHandle root_job,
-                         base::ScopedZxHandle exception_port);
-  ~ExceptionHandlerServer();
-
-  //! \brief Runs the exception-handling server.
-  //!
-  //! \param[in] handler The handler to which the exceptions are delegated when
-  //!     they are caught in Run(). Ownership is not transferred.
-  void Run(CrashReportExceptionHandler* handler);
-
- private:
-  base::ScopedZxHandle root_job_;
-  base::ScopedZxHandle exception_port_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
-};
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_HANDLER_FUCHSIA_EXCEPTION_HANDLER_SERVER_H_
diff --git a/handler/handler.gyp b/handler/handler.gyp
index 60a6f25..f8313a5 100644
--- a/handler/handler.gyp
+++ b/handler/handler.gyp
@@ -28,6 +28,7 @@
         '../minidump/minidump.gyp:crashpad_minidump',
         '../snapshot/snapshot.gyp:crashpad_snapshot',
         '../third_party/mini_chromium/mini_chromium.gyp:base',
+        '../third_party/zlib/zlib.gyp:zlib',
         '../tools/tools.gyp:crashpad_tool_support',
         '../util/util.gyp:crashpad_util',
       ],
@@ -39,6 +40,8 @@
         'crash_report_upload_thread.h',
         'handler_main.cc',
         'handler_main.h',
+        'linux/capture_snapshot.cc',
+        'linux/capture_snapshot.h',
         'linux/crash_report_exception_handler.cc',
         'linux/crash_report_exception_handler.h',
         'linux/exception_handler_server.cc',
diff --git a/handler/handler_main.cc b/handler/handler_main.cc
index 412ee11..d56857f 100644
--- a/handler/handler_main.cc
+++ b/handler/handler_main.cc
@@ -56,6 +56,10 @@
 #include "util/string/split_string.h"
 #include "util/synchronization/semaphore.h"
 
+#if defined(OS_CHROMEOS)
+#include "handler/linux/cros_crash_report_exception_handler.h"
+#endif
+
 #if defined(OS_LINUX) || defined(OS_ANDROID)
 #include <unistd.h>
 
@@ -70,8 +74,8 @@
 #include "handler/mac/crash_report_exception_handler.h"
 #include "handler/mac/exception_handler_server.h"
 #include "handler/mac/file_limit_annotation.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/child_port_handshake.h"
-#include "util/mach/mach_extensions.h"
 #include "util/posix/close_stdio.h"
 #include "util/posix/signals.h"
 #elif defined(OS_WIN)
@@ -82,12 +86,6 @@
 #include "util/win/handle.h"
 #include "util/win/initial_client_data.h"
 #include "util/win/session_end_watcher.h"
-#elif defined(OS_FUCHSIA)
-#include <zircon/process.h>
-#include <zircon/processargs.h>
-
-#include "handler/fuchsia/crash_report_exception_handler.h"
-#include "handler/fuchsia/exception_handler_server.h"
 #elif defined(OS_LINUX)
 #include "handler/linux/crash_report_exception_handler.h"
 #include "handler/linux/exception_handler_server.h"
@@ -118,6 +116,9 @@
 "                            Address_debug_critical_section\n"
 "                              use precreated data to register initial client\n"
 #endif  // OS_WIN
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+"      --initial-client-fd=FD  a socket connected to a client.\n"
+#endif  // OS_ANDROID || OS_LINUX
 #if defined(OS_MACOSX)
 "      --mach-service=SERVICE  register SERVICE with the bootstrap server\n"
 #endif  // OS_MACOSX
@@ -133,6 +134,10 @@
 "      --no-periodic-tasks     don't scan for new reports or prune the database\n"
 "      --no-rate-limit         don't rate limit crash uploads\n"
 "      --no-upload-gzip        don't use gzip compression when uploading\n"
+#if defined(OS_ANDROID)
+"      --no-write-minidump-to-database\n"
+"                              don't write minidump to database\n"
+#endif  // OS_ANDROID
 #if defined(OS_WIN)
 "      --pipe-name=PIPE        communicate with the client over PIPE\n"
 #endif  // OS_WIN
@@ -141,14 +146,31 @@
 "                              reset the server's exception handler to default\n"
 #endif  // OS_MACOSX
 #if defined(OS_LINUX) || defined(OS_ANDROID)
+"      --sanitization-information=SANITIZATION_INFORMATION_ADDRESS\n"
+"                              the address of a SanitizationInformation struct.\n"
+"      --shared-client-connection the file descriptor provided by\n"
+"                              --initial-client-fd is shared among multiple\n"
+"                              clients\n"
 "      --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n"
 "                              request a dump for the handler's parent process\n"
-"      --initial-client-fd=FD  a socket connected to a client.\n"
-"      --sanitization_information=SANITIZATION_INFORMATION_ADDRESS\n"
-"                              the address of a SanitizationInformation struct.\n"
 #endif  // OS_LINUX || OS_ANDROID
 "      --url=URL               send crash reports to this Breakpad server URL,\n"
 "                              only if uploads are enabled for the database\n"
+#if defined(OS_CHROMEOS)
+"      --use-cros-crash-reporter\n"
+"                              pass crash reports to /sbin/crash_reporter\n"
+"                              instead of storing them in the database\n"
+"      --minidump-dir-for-tests=TEST_MINIDUMP_DIR\n"
+"                              causes /sbin/crash_reporter to leave dumps in\n"
+"                              this directory instead of the normal location\n"
+"      --always-allow-feedback\n"
+"                              pass the --always_allow_feedback flag to\n"
+"                              crash_reporter, thus skipping metrics consent\n"
+"                              checks\n"
+#endif  // OS_CHROMEOS
+#if defined(OS_ANDROID)
+"      --write-minidump-to-log write minidump to log\n"
+#endif  // OS_ANDROID
 "      --help                  display this help and exit\n"
 "      --version               output version information and exit\n",
           me.value().c_str());
@@ -168,8 +190,13 @@
   bool reset_own_crash_exception_port_to_system_default;
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
   VMAddress exception_information_address;
-  int initial_client_fd;
   VMAddress sanitization_information_address;
+  int initial_client_fd;
+  bool shared_client_connection;
+#if defined(OS_ANDROID)
+  bool write_minidump_to_log;
+  bool write_minidump_to_database;
+#endif  // OS_ANDROID
 #elif defined(OS_WIN)
   std::string pipe_name;
   InitialClientData initial_client_data;
@@ -179,6 +206,11 @@
   bool periodic_tasks;
   bool rate_limit;
   bool upload_gzip;
+#if defined(OS_CHROMEOS)
+  bool use_cros_crash_reporter = false;
+  base::FilePath minidump_dir_for_tests;
+  bool always_allow_feedback = false;
+#endif  // OS_CHROMEOS
 };
 
 // Splits |key_value| on '=' and inserts the resulting key and value into |map|.
@@ -238,8 +270,6 @@
 
 #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID)
 
-Signals::OldActions g_old_crash_signal_handlers;
-
 void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
   MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
 
@@ -274,9 +304,7 @@
   }
   Metrics::HandlerCrashed(metrics_code);
 
-  struct sigaction* old_action =
-      g_old_crash_signal_handlers.ActionForSignal(sig);
-  Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
+  Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
 }
 
 void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) {
@@ -284,13 +312,13 @@
   Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
 }
 
-#if defined(OS_MACOSX)
-
 void ReinstallCrashHandler() {
   // This is used to re-enable the metrics-recording crash handler after
   // MonitorSelf() sets up a Crashpad exception handler. On macOS, the
   // metrics-recording handler uses signals and the Crashpad handler uses Mach
   // exceptions, so there’s nothing to re-enable.
+  // On Linux, the signal handler installed by StartHandler() restores the
+  // previously installed signal handler by default.
 }
 
 void InstallCrashHandler() {
@@ -300,6 +328,8 @@
   Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr);
 }
 
+#if defined(OS_MACOSX)
+
 struct ResetSIGTERMTraits {
   static struct sigaction* InvalidValue() {
     return nullptr;
@@ -324,21 +354,6 @@
   g_exception_handler_server->Stop();
 }
 
-#else
-
-void ReinstallCrashHandler() {
-  // This is used to re-enable the metrics-recording crash handler after
-  // MonitorSelf() sets up a Crashpad signal handler.
-  Signals::InstallCrashHandlers(
-      HandleCrashSignal, 0, &g_old_crash_signal_handlers);
-}
-
-void InstallCrashHandler() {
-  ReinstallCrashHandler();
-
-  Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr);
-}
-
 #endif  // OS_MACOSX
 
 #elif defined(OS_WIN)
@@ -396,22 +411,6 @@
   ALLOW_UNUSED_LOCAL(terminate_handler);
 }
 
-#elif defined(OS_FUCHSIA)
-
-void InstallCrashHandler() {
-  // There's nothing to do here. Crashes in this process will already be caught
-  // here because this handler process is in the same job that has had its
-  // exception port bound.
-
-  // TODO(scottmg): This should collect metrics on handler crashes, at a
-  // minimum. https://crashpad.chromium.org/bug/230.
-}
-
-void ReinstallCrashHandler() {
-  // TODO(scottmg): Fuchsia: https://crashpad.chromium.org/bug/196
-  NOTREACHED();
-}
-
 #endif  // OS_MACOSX
 
 void MonitorSelf(const Options& options) {
@@ -448,7 +447,7 @@
   // instance of crashpad_handler to be writing metrics at a time, and it should
   // be the primary instance.
   CrashpadClient crashpad_client;
-#if defined(OS_LINUX) || defined(OS_ANDROID)
+#if defined(OS_ANDROID)
   if (!crashpad_client.StartHandlerAtCrash(executable_path,
                                            options.database,
                                            base::FilePath(),
@@ -500,6 +499,12 @@
 int HandlerMain(int argc,
                 char* argv[],
                 const UserStreamDataSources* user_stream_sources) {
+#if defined(OS_CHROMEOS)
+  if (freopen("/var/log/chrome/chrome", "a", stderr) == nullptr) {
+    PLOG(ERROR) << "Failed to redirect stderr to /var/log/chrome/chrome";
+  }
+#endif
+
   InstallCrashHandler();
   CallMetricsRecordNormalExit metrics_record_normal_exit;
 
@@ -518,6 +523,9 @@
 #if defined(OS_WIN)
     kOptionInitialClientData,
 #endif  // OS_WIN
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+    kOptionInitialClientFD,
+#endif  // OS_ANDROID || OS_LINUX
 #if defined(OS_MACOSX)
     kOptionMachService,
 #endif  // OS_MACOSX
@@ -529,6 +537,9 @@
     kOptionNoPeriodicTasks,
     kOptionNoRateLimit,
     kOptionNoUploadGzip,
+#if defined(OS_ANDROID)
+    kOptionNoWriteMinidumpToDatabase,
+#endif  // OS_ANDROID
 #if defined(OS_WIN)
     kOptionPipeName,
 #endif  // OS_WIN
@@ -536,11 +547,19 @@
     kOptionResetOwnCrashExceptionPortToSystemDefault,
 #endif  // OS_MACOSX
 #if defined(OS_LINUX) || defined(OS_ANDROID)
-    kOptionTraceParentWithException,
-    kOptionInitialClientFD,
     kOptionSanitizationInformation,
+    kOptionSharedClientConnection,
+    kOptionTraceParentWithException,
 #endif
     kOptionURL,
+#if defined(OS_CHROMEOS)
+    kOptionUseCrosCrashReporter,
+    kOptionMinidumpDirForTests,
+    kOptionAlwaysAllowFeedback,
+#endif  // OS_CHROMEOS
+#if defined(OS_ANDROID)
+    kOptionWriteMinidumpToLog,
+#endif  // OS_ANDROID
 
     // Standard options.
     kOptionHelp = -2,
@@ -559,6 +578,9 @@
      nullptr,
      kOptionInitialClientData},
 #endif  // OS_MACOSX
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+    {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD},
+#endif  // OS_ANDROID || OS_LINUX
 #if defined(OS_MACOSX)
     {"mach-service", required_argument, nullptr, kOptionMachService},
 #endif  // OS_MACOSX
@@ -579,6 +601,12 @@
     {"no-periodic-tasks", no_argument, nullptr, kOptionNoPeriodicTasks},
     {"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
     {"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip},
+#if defined(OS_ANDROID)
+    {"no-write-minidump-to-database",
+     no_argument,
+     nullptr,
+     kOptionNoWriteMinidumpToDatabase},
+#endif  // OS_ANDROID
 #if defined(OS_WIN)
     {"pipe-name", required_argument, nullptr, kOptionPipeName},
 #endif  // OS_WIN
@@ -589,17 +617,37 @@
      kOptionResetOwnCrashExceptionPortToSystemDefault},
 #endif  // OS_MACOSX
 #if defined(OS_LINUX) || defined(OS_ANDROID)
-    {"trace-parent-with-exception",
-     required_argument,
-     nullptr,
-     kOptionTraceParentWithException},
-    {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD},
     {"sanitization-information",
      required_argument,
      nullptr,
      kOptionSanitizationInformation},
+    {"shared-client-connection",
+     no_argument,
+     nullptr,
+     kOptionSharedClientConnection},
+    {"trace-parent-with-exception",
+     required_argument,
+     nullptr,
+     kOptionTraceParentWithException},
 #endif  // OS_LINUX || OS_ANDROID
     {"url", required_argument, nullptr, kOptionURL},
+#if defined(OS_CHROMEOS)
+    {"use-cros-crash-reporter",
+      no_argument,
+      nullptr,
+      kOptionUseCrosCrashReporter},
+    {"minidump-dir-for-tests",
+      required_argument,
+      nullptr,
+      kOptionMinidumpDirForTests},
+    {"always-allow-feedback",
+      no_argument,
+      nullptr,
+      kOptionAlwaysAllowFeedback},
+#endif  // OS_CHROMEOS
+#if defined(OS_ANDROID)
+    {"write-minidump-to-log", no_argument, nullptr, kOptionWriteMinidumpToLog},
+#endif  // OS_ANDROID
     {"help", no_argument, nullptr, kOptionHelp},
     {"version", no_argument, nullptr, kOptionVersion},
     {nullptr, 0, nullptr, 0},
@@ -610,13 +658,14 @@
   options.handshake_fd = -1;
 #endif
   options.identify_client_via_url = true;
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+  options.initial_client_fd = kInvalidFileHandle;
+#endif
   options.periodic_tasks = true;
   options.rate_limit = true;
   options.upload_gzip = true;
-#if defined(OS_LINUX) || defined(OS_ANDROID)
-  options.exception_information_address = 0;
-  options.initial_client_fd = kInvalidFileHandle;
-  options.sanitization_information_address = 0;
+#if defined(OS_ANDROID)
+  options.write_minidump_to_database = true;
 #endif
 
   int opt;
@@ -658,6 +707,15 @@
         break;
       }
 #endif  // OS_WIN
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+      case kOptionInitialClientFD: {
+        if (!base::StringToInt(optarg, &options.initial_client_fd)) {
+          ToolSupport::UsageHint(me, "failed to parse --initial-client-fd");
+          return ExitFailure();
+        }
+        break;
+      }
+#endif  // OS_ANDROID || OS_LINUX
       case kOptionMetrics: {
         options.metrics_dir = base::FilePath(
             ToolSupport::CommandLineArgumentToFilePathStringType(optarg));
@@ -695,6 +753,12 @@
         options.upload_gzip = false;
         break;
       }
+#if defined(OS_ANDROID)
+      case kOptionNoWriteMinidumpToDatabase: {
+        options.write_minidump_to_database = false;
+        break;
+      }
+#endif  // OS_ANDROID
 #if defined(OS_WIN)
       case kOptionPipeName: {
         options.pipe_name = optarg;
@@ -708,21 +772,6 @@
       }
 #endif  // OS_MACOSX
 #if defined(OS_LINUX) || defined(OS_ANDROID)
-      case kOptionTraceParentWithException: {
-        if (!StringToNumber(optarg, &options.exception_information_address)) {
-          ToolSupport::UsageHint(
-              me, "failed to parse --trace-parent-with-exception");
-          return ExitFailure();
-        }
-        break;
-      }
-      case kOptionInitialClientFD: {
-        if (!base::StringToInt(optarg, &options.initial_client_fd)) {
-          ToolSupport::UsageHint(me, "failed to parse --initial-client-fd");
-          return ExitFailure();
-        }
-        break;
-      }
       case kOptionSanitizationInformation: {
         if (!StringToNumber(optarg,
                             &options.sanitization_information_address)) {
@@ -732,11 +781,44 @@
         }
         break;
       }
+      case kOptionSharedClientConnection: {
+        options.shared_client_connection = true;
+        break;
+      }
+      case kOptionTraceParentWithException: {
+        if (!StringToNumber(optarg, &options.exception_information_address)) {
+          ToolSupport::UsageHint(
+              me, "failed to parse --trace-parent-with-exception");
+          return ExitFailure();
+        }
+        break;
+      }
 #endif  // OS_LINUX || OS_ANDROID
       case kOptionURL: {
         options.url = optarg;
         break;
       }
+#if defined(OS_CHROMEOS)
+      case kOptionUseCrosCrashReporter: {
+        options.use_cros_crash_reporter = true;
+        break;
+      }
+      case kOptionMinidumpDirForTests: {
+        options.minidump_dir_for_tests = base::FilePath(
+            ToolSupport::CommandLineArgumentToFilePathStringType(optarg));
+        break;
+      }
+      case kOptionAlwaysAllowFeedback: {
+        options.always_allow_feedback = true;
+        break;
+      }
+#endif  // OS_CHROMEOS
+#if defined(OS_ANDROID)
+      case kOptionWriteMinidumpToLog: {
+        options.write_minidump_to_log = true;
+        break;
+      }
+#endif  // OS_ANDROID
       case kOptionHelp: {
         Usage(me);
         MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
@@ -781,7 +863,7 @@
   if (!options.exception_information_address &&
       options.initial_client_fd == kInvalidFileHandle) {
     ToolSupport::UsageHint(
-        me, "--trace-parent-with-exception or --initial_client_fd is required");
+        me, "--trace-parent-with-exception or --initial-client-fd is required");
     return ExitFailure();
   }
   if (options.sanitization_information_address &&
@@ -791,6 +873,20 @@
         "--sanitization_information requires --trace-parent-with-exception");
     return ExitFailure();
   }
+  if (options.shared_client_connection &&
+      options.initial_client_fd == kInvalidFileHandle) {
+    ToolSupport::UsageHint(
+        me, "--shared-client-connection requires --initial-client-fd");
+    return ExitFailure();
+  }
+#if defined(OS_ANDROID)
+  if (!options.write_minidump_to_log && !options.write_minidump_to_database) {
+    ToolSupport::UsageHint(me,
+                           "--no_write_minidump_to_database is required to use "
+                           "with --write_minidump_to_log.");
+    ExitFailure();
+  }
+#endif  // OS_ANDROID
 #endif  // OS_MACOSX
 
   if (options.database.empty()) {
@@ -856,24 +952,62 @@
     upload_thread.Get()->Start();
   }
 
-  CrashReportExceptionHandler exception_handler(
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+  std::unique_ptr<ExceptionHandlerServer::Delegate> exception_handler;
+#else
+  std::unique_ptr<CrashReportExceptionHandler> exception_handler;
+#endif
+
+#if defined(OS_CHROMEOS)
+  if (options.use_cros_crash_reporter) {
+    auto cros_handler = std::make_unique<CrosCrashReportExceptionHandler>(
+        database.get(),
+        &options.annotations,
+        user_stream_sources);
+
+    if (!options.minidump_dir_for_tests.empty()) {
+      cros_handler->SetDumpDir(options.minidump_dir_for_tests);
+    }
+
+    if (options.always_allow_feedback) {
+      cros_handler->SetAlwaysAllowFeedback();
+    }
+
+    exception_handler = std::move(cros_handler);
+  } else {
+    exception_handler = std::make_unique<CrashReportExceptionHandler>(
+        database.get(),
+        static_cast<CrashReportUploadThread*>(upload_thread.Get()),
+        &options.annotations,
+        true,
+        false,
+        user_stream_sources);
+  }
+#else
+  exception_handler = std::make_unique<CrashReportExceptionHandler>(
       database.get(),
       static_cast<CrashReportUploadThread*>(upload_thread.Get()),
       &options.annotations,
-#if defined(OS_FUCHSIA)
-      // TODO(scottmg): Process level file attachments, and for all platforms.
-      nullptr,
-#endif
+#if defined(OS_ANDROID)
+      options.write_minidump_to_database,
+      options.write_minidump_to_log,
+#endif  // OS_ANDROID
+#if defined(OS_LINUX)
+      true,
+      false,
+#endif  // OS_LINUX
       user_stream_sources);
+#endif  // OS_CHROMEOS
 
- #if defined(OS_LINUX) || defined(OS_ANDROID)
+#if defined(OS_LINUX) || defined(OS_ANDROID)
   if (options.exception_information_address) {
-    ClientInformation info;
+    ExceptionHandlerProtocol::ClientInformation info;
     info.exception_information_address = options.exception_information_address;
     info.sanitization_information_address =
         options.sanitization_information_address;
-    return exception_handler.HandleException(getppid(), info) ? EXIT_SUCCESS
-                                                              : ExitFailure();
+    return exception_handler->HandleException(getppid(), geteuid(), info)
+               ? EXIT_SUCCESS
+               : ExitFailure();
   }
 #endif  // OS_LINUX || OS_ANDROID
 
@@ -937,27 +1071,6 @@
   if (!options.pipe_name.empty()) {
     exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name));
   }
-#elif defined(OS_FUCHSIA)
-  // These handles are logically "moved" into these variables when retrieved by
-  // zx_take_startup_handle(). Both are given to ExceptionHandlerServer which
-  // owns them in this process. There is currently no "connect-later" mode on
-  // Fuchsia, all the binding must be done by the client before starting
-  // crashpad_handler.
-  base::ScopedZxHandle root_job(zx_take_startup_handle(PA_HND(PA_USER0, 0)));
-  if (!root_job.is_valid()) {
-    LOG(ERROR) << "no process handle passed in startup handle 0";
-    return EXIT_FAILURE;
-  }
-
-  base::ScopedZxHandle exception_port(
-      zx_take_startup_handle(PA_HND(PA_USER0, 1)));
-  if (!exception_port.is_valid()) {
-    LOG(ERROR) << "no exception port handle passed in startup handle 1";
-    return EXIT_FAILURE;
-  }
-
-  ExceptionHandlerServer exception_handler_server(std::move(root_job),
-                                                  std::move(exception_port));
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
   ExceptionHandlerServer exception_handler_server;
 #endif  // OS_MACOSX
@@ -978,17 +1091,18 @@
 #if defined(OS_WIN)
   if (options.initial_client_data.IsValid()) {
     exception_handler_server.InitializeWithInheritedDataForInitialClient(
-        options.initial_client_data, &exception_handler);
+        options.initial_client_data, exception_handler.get());
   }
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
   if (options.initial_client_fd == kInvalidFileHandle ||
-             !exception_handler_server.InitializeWithClient(
-                 ScopedFileHandle(options.initial_client_fd))) {
+      !exception_handler_server.InitializeWithClient(
+          ScopedFileHandle(options.initial_client_fd),
+          options.shared_client_connection)) {
     return ExitFailure();
   }
 #endif  // OS_WIN
 
-  exception_handler_server.Run(&exception_handler);
+  exception_handler_server.Run(exception_handler.get());
 
   return EXIT_SUCCESS;
 }
diff --git a/handler/handler_main.h b/handler/handler_main.h
index af01dbe..2526541 100644
--- a/handler/handler_main.h
+++ b/handler/handler_main.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_HANDLER_HANDLER_MAIN_H_
 #define CRASHPAD_HANDLER_HANDLER_MAIN_H_
 
+#include "build/build_config.h"
 #include "handler/user_stream_data_source.h"
 
 namespace crashpad {
@@ -34,6 +35,16 @@
                 char* argv[],
                 const UserStreamDataSources* user_stream_sources);
 
+#if defined(OS_ANDROID)
+//! \brief The `main()` entry point for Android libraries.
+//!
+//! This symbol is the entry point for crashpad when it is dynamically loaded
+//! using /system/bin/linker.
+//!
+//! \sa CrashpadClient::StartHandlerWithLinkerAtCrash()
+extern "C" int CrashpadHandlerMain(int argc, char* argv[]);
+#endif
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_HANDLER_HANDLER_MAIN_H_
diff --git a/handler/linux/capture_snapshot.cc b/handler/linux/capture_snapshot.cc
new file mode 100644
index 0000000..0054029
--- /dev/null
+++ b/handler/linux/capture_snapshot.cc
@@ -0,0 +1,119 @@
+// Copyright 2019 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.
+
+#include "handler/linux/capture_snapshot.h"
+
+#include <utility>
+
+#include "snapshot/crashpad_info_client_options.h"
+#include "snapshot/sanitized/sanitization_information.h"
+#include "util/misc/metrics.h"
+#include "util/misc/tri_state.h"
+
+namespace crashpad {
+
+bool CaptureSnapshot(
+    PtraceConnection* connection,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    const std::map<std::string, std::string>& process_annotations,
+    uid_t client_uid,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    std::unique_ptr<ProcessSnapshotLinux>* snapshot,
+    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot) {
+  std::unique_ptr<ProcessSnapshotLinux> process_snapshot(
+      new ProcessSnapshotLinux());
+  if (!process_snapshot->Initialize(connection)) {
+    Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
+    return false;
+  }
+
+  pid_t local_requesting_thread_id = -1;
+  if (requesting_thread_stack_address) {
+    local_requesting_thread_id = process_snapshot->FindThreadWithStackAddress(
+        requesting_thread_stack_address);
+  }
+
+  if (requesting_thread_id) {
+    *requesting_thread_id = local_requesting_thread_id;
+  }
+
+  if (!process_snapshot->InitializeException(info.exception_information_address,
+                                             local_requesting_thread_id)) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kExceptionInitializationFailed);
+    return false;
+  }
+
+  Metrics::ExceptionCode(process_snapshot->Exception()->Exception());
+
+  CrashpadInfoClientOptions client_options;
+  process_snapshot->GetCrashpadOptions(&client_options);
+  if (client_options.crashpad_handler_behavior == TriState::kDisabled) {
+    return false;
+  }
+
+  for (auto& p : process_annotations) {
+    process_snapshot->AddAnnotation(p.first, p.second);
+  }
+
+  if (info.sanitization_information_address) {
+    SanitizationInformation sanitization_info;
+    ProcessMemoryRange range;
+    if (!range.Initialize(connection->Memory(), connection->Is64Bit()) ||
+        !range.Read(info.sanitization_information_address,
+                    sizeof(sanitization_info),
+                    &sanitization_info)) {
+      Metrics::ExceptionCaptureResult(
+          Metrics::CaptureResult::kSanitizationInitializationFailed);
+      return false;
+    }
+
+    auto annotations_whitelist = std::make_unique<std::vector<std::string>>();
+    auto memory_range_whitelist =
+        std::make_unique<std::vector<std::pair<VMAddress, VMAddress>>>();
+    if (!ReadAnnotationsWhitelist(
+            range,
+            sanitization_info.annotations_whitelist_address,
+            annotations_whitelist.get()) ||
+        !ReadMemoryRangeWhitelist(
+            range,
+            sanitization_info.memory_range_whitelist_address,
+            memory_range_whitelist.get())) {
+      Metrics::ExceptionCaptureResult(
+          Metrics::CaptureResult::kSanitizationInitializationFailed);
+      return false;
+    }
+
+    std::unique_ptr<ProcessSnapshotSanitized> sanitized(
+        new ProcessSnapshotSanitized());
+    if (!sanitized->Initialize(process_snapshot.get(),
+                               sanitization_info.annotations_whitelist_address
+                                   ? std::move(annotations_whitelist)
+                                   : nullptr,
+                               std::move(memory_range_whitelist),
+                               sanitization_info.target_module_address,
+                               sanitization_info.sanitize_stacks)) {
+      Metrics::ExceptionCaptureResult(
+          Metrics::CaptureResult::kSkippedDueToSanitization);
+      return false;
+    }
+    *sanitized_snapshot = std::move(sanitized);
+  }
+
+  *snapshot = std::move(process_snapshot);
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/handler/linux/capture_snapshot.h b/handler/linux/capture_snapshot.h
new file mode 100644
index 0000000..78886dc
--- /dev/null
+++ b/handler/linux/capture_snapshot.h
@@ -0,0 +1,67 @@
+// Copyright 2019 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_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_
+#define CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "snapshot/linux/process_snapshot_linux.h"
+#include "snapshot/sanitized/process_snapshot_sanitized.h"
+#include "util/linux/exception_handler_protocol.h"
+#include "util/linux/ptrace_connection.h"
+#include "util/misc/address_types.h"
+
+namespace crashpad {
+
+//! \brief Captures a snapshot of a client over \a connection.
+//!
+//! \param[in] connection A PtraceConnection to the client to snapshot.
+//! \param[in] info Information about the client configuring the snapshot.
+//! \param[in] process_annotations A map of annotations to insert as
+//!     process-level annotations into the snapshot.
+//! \param[in] client_uid The client's user ID.
+//! \param[in] requesting_thread_stack_address An address on the stack of the
+//!     thread requesting the snapshot. If \a info includes an exception
+//!     address, the exception will be assigned to the thread whose stack
+//!     address range contains this address. If 0, \a requesting_thread_id will
+//!     be -1.
+//! \param[out] requesting_thread_id The thread ID of the thread corresponding
+//!     to \a requesting_thread_stack_address. Set to -1 if the thread ID could
+//!     not be determined. Optional.
+//! \param[out] process_snapshot A snapshot of the client process, valid if this
+//!     function returns `true`.
+//! \param[out] sanitized_snapshot A sanitized snapshot of the client process,
+//!     valid if this function returns `true` and sanitization was requested in
+//!     \a info.
+//! \return `true` if \a process_snapshot was successfully created. A message
+//!     will be logged on failure, but not if the snapshot was skipped because
+//!     handling was disabled by CrashpadInfoClientOptions.
+bool CaptureSnapshot(
+    PtraceConnection* connection,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    const std::map<std::string, std::string>& process_annotations,
+    uid_t client_uid,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    std::unique_ptr<ProcessSnapshotLinux>* process_snapshot,
+    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot);
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_HANDLER_LINUX_CAPTURE_SNAPSHOT_H_
diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc
index 101c49f..29f6d0d 100644
--- a/handler/linux/crash_report_exception_handler.cc
+++ b/handler/linux/crash_report_exception_handler.cc
@@ -14,38 +14,75 @@
 
 #include "handler/linux/crash_report_exception_handler.h"
 
-#include <vector>
+#include <memory>
+#include <utility>
 
 #include "base/logging.h"
 #include "client/settings.h"
+#include "handler/linux/capture_snapshot.h"
 #include "minidump/minidump_file_writer.h"
-#include "snapshot/crashpad_info_client_options.h"
 #include "snapshot/linux/process_snapshot_linux.h"
 #include "snapshot/sanitized/process_snapshot_sanitized.h"
-#include "snapshot/sanitized/sanitization_information.h"
+#include "util/file/file_reader.h"
+#include "util/file/output_stream_file_writer.h"
 #include "util/linux/direct_ptrace_connection.h"
 #include "util/linux/ptrace_client.h"
+#include "util/misc/implicit_cast.h"
 #include "util/misc/metrics.h"
-#include "util/misc/tri_state.h"
 #include "util/misc/uuid.h"
+#include "util/stream/base94_output_stream.h"
+#include "util/stream/log_output_stream.h"
+#include "util/stream/zlib_output_stream.h"
 
 namespace crashpad {
 
+namespace {
+
+bool WriteMinidumpLogFromFile(FileReaderInterface* file_reader) {
+  ZlibOutputStream stream(ZlibOutputStream::Mode::kCompress,
+                          std::make_unique<Base94OutputStream>(
+                              Base94OutputStream::Mode::kEncode,
+                              std::make_unique<LogOutputStream>()));
+  FileOperationResult read_result;
+  do {
+    uint8_t buffer[4096];
+    read_result = file_reader->Read(buffer, sizeof(buffer));
+    if (read_result < 0)
+      return false;
+
+    if (read_result > 0 && (!stream.Write(buffer, read_result)))
+      return false;
+  } while (read_result > 0);
+  return stream.Flush();
+}
+
+}  // namespace
+
 CrashReportExceptionHandler::CrashReportExceptionHandler(
     CrashReportDatabase* database,
     CrashReportUploadThread* upload_thread,
     const std::map<std::string, std::string>* process_annotations,
+    bool write_minidump_to_database,
+    bool write_minidump_to_log,
     const UserStreamDataSources* user_stream_data_sources)
     : database_(database),
       upload_thread_(upload_thread),
       process_annotations_(process_annotations),
-      user_stream_data_sources_(user_stream_data_sources) {}
+      write_minidump_to_database_(write_minidump_to_database),
+      write_minidump_to_log_(write_minidump_to_log),
+      user_stream_data_sources_(user_stream_data_sources) {
+  DCHECK(write_minidump_to_database_ | write_minidump_to_log_);
+}
 
 CrashReportExceptionHandler::~CrashReportExceptionHandler() = default;
 
 bool CrashReportExceptionHandler::HandleException(
     pid_t client_process_id,
-    const ClientInformation& info) {
+    uid_t client_uid,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    UUID* local_report_id) {
   Metrics::ExceptionEncountered();
 
   DirectPtraceConnection connection;
@@ -55,13 +92,20 @@
     return false;
   }
 
-  return HandleExceptionWithConnection(&connection, info);
+  return HandleExceptionWithConnection(&connection,
+                                       info,
+                                       client_uid,
+                                       requesting_thread_stack_address,
+                                       requesting_thread_id,
+                                       local_report_id);
 }
 
 bool CrashReportExceptionHandler::HandleExceptionWithBroker(
     pid_t client_process_id,
-    const ClientInformation& info,
-    int broker_sock) {
+    uid_t client_uid,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    int broker_sock,
+    UUID* local_report_id) {
   Metrics::ExceptionEncountered();
 
   PtraceClient client;
@@ -71,123 +115,134 @@
     return false;
   }
 
-  return HandleExceptionWithConnection(&client, info);
+  return HandleExceptionWithConnection(
+      &client, info, client_uid, 0, nullptr, local_report_id);
 }
 
 bool CrashReportExceptionHandler::HandleExceptionWithConnection(
     PtraceConnection* connection,
-    const ClientInformation& info) {
-  ProcessSnapshotLinux process_snapshot;
-  if (!process_snapshot.Initialize(connection)) {
-    Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    uid_t client_uid,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    UUID* local_report_id) {
+  std::unique_ptr<ProcessSnapshotLinux> process_snapshot;
+  std::unique_ptr<ProcessSnapshotSanitized> sanitized_snapshot;
+  if (!CaptureSnapshot(connection,
+                       info,
+                       *process_annotations_,
+                       client_uid,
+                       requesting_thread_stack_address,
+                       requesting_thread_id,
+                       &process_snapshot,
+                       &sanitized_snapshot)) {
     return false;
   }
 
-  if (!process_snapshot.InitializeException(
-          info.exception_information_address)) {
+  UUID client_id;
+  Settings* const settings = database_->GetSettings();
+  if (settings) {
+    // If GetSettings() or GetClientID() fails, something else will log a
+    // message and client_id will be left at its default value, all zeroes,
+    // which is appropriate.
+    settings->GetClientID(&client_id);
+  }
+  process_snapshot->SetClientID(client_id);
+
+  return write_minidump_to_database_
+             ? WriteMinidumpToDatabase(process_snapshot.get(),
+                                       sanitized_snapshot.get(),
+                                       write_minidump_to_log_,
+                                       local_report_id)
+             : WriteMinidumpToLog(process_snapshot.get(),
+                                  sanitized_snapshot.get());
+}
+
+bool CrashReportExceptionHandler::WriteMinidumpToDatabase(
+    ProcessSnapshotLinux* process_snapshot,
+    ProcessSnapshotSanitized* sanitized_snapshot,
+    bool write_minidump_to_log,
+    UUID* local_report_id) {
+  std::unique_ptr<CrashReportDatabase::NewReport> new_report;
+  CrashReportDatabase::OperationStatus database_status =
+      database_->PrepareNewCrashReport(&new_report);
+  if (database_status != CrashReportDatabase::kNoError) {
+    LOG(ERROR) << "PrepareNewCrashReport failed";
     Metrics::ExceptionCaptureResult(
-        Metrics::CaptureResult::kExceptionInitializationFailed);
+        Metrics::CaptureResult::kPrepareNewCrashReportFailed);
     return false;
   }
 
-  Metrics::ExceptionCode(process_snapshot.Exception()->Exception());
+  process_snapshot->SetReportID(new_report->ReportID());
 
-  CrashpadInfoClientOptions client_options;
-  process_snapshot.GetCrashpadOptions(&client_options);
-  if (client_options.crashpad_handler_behavior != TriState::kDisabled) {
-    UUID client_id;
-    Settings* const settings = database_->GetSettings();
-    if (settings) {
-      // If GetSettings() or GetClientID() fails, something else will log a
-      // message and client_id will be left at its default value, all zeroes,
-      // which is appropriate.
-      settings->GetClientID(&client_id);
+  ProcessSnapshot* snapshot =
+      sanitized_snapshot ? implicit_cast<ProcessSnapshot*>(sanitized_snapshot)
+                         : implicit_cast<ProcessSnapshot*>(process_snapshot);
+
+  MinidumpFileWriter minidump;
+  minidump.InitializeFromSnapshot(snapshot);
+  AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
+
+  if (!minidump.WriteEverything(new_report->Writer())) {
+    LOG(ERROR) << "WriteEverything failed";
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kMinidumpWriteFailed);
+    return false;
+  }
+
+  bool write_minidump_to_log_succeed = false;
+  if (write_minidump_to_log) {
+    if (auto* file_reader = new_report->Reader()) {
+      if (WriteMinidumpLogFromFile(file_reader))
+        write_minidump_to_log_succeed = true;
+      else
+        LOG(ERROR) << "WriteMinidumpLogFromFile failed";
     }
+  }
 
-    process_snapshot.SetClientID(client_id);
-    process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
+  UUID uuid;
+  database_status =
+      database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
+  if (database_status != CrashReportDatabase::kNoError) {
+    LOG(ERROR) << "FinishedWritingCrashReport failed";
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
+    return false;
+  }
 
-    std::unique_ptr<CrashReportDatabase::NewReport> new_report;
-    CrashReportDatabase::OperationStatus database_status =
-        database_->PrepareNewCrashReport(&new_report);
-    if (database_status != CrashReportDatabase::kNoError) {
-      LOG(ERROR) << "PrepareNewCrashReport failed";
-      Metrics::ExceptionCaptureResult(
-          Metrics::CaptureResult::kPrepareNewCrashReportFailed);
-      return false;
-    }
+  if (upload_thread_) {
+    upload_thread_->ReportPending(uuid);
+  }
 
-    process_snapshot.SetReportID(new_report->ReportID());
-
-    ProcessSnapshot* snapshot = nullptr;
-    ProcessSnapshotSanitized sanitized;
-    std::vector<std::string> whitelist;
-    if (info.sanitization_information_address) {
-      SanitizationInformation sanitization_info;
-      ProcessMemoryRange range;
-      if (!range.Initialize(connection->Memory(), connection->Is64Bit()) ||
-          !range.Read(info.sanitization_information_address,
-                      sizeof(sanitization_info),
-                      &sanitization_info)) {
-        Metrics::ExceptionCaptureResult(
-            Metrics::CaptureResult::kSanitizationInitializationFailed);
-        return false;
-      }
-
-      if (sanitization_info.annotations_whitelist_address &&
-          !ReadAnnotationsWhitelist(
-              range,
-              sanitization_info.annotations_whitelist_address,
-              &whitelist)) {
-        Metrics::ExceptionCaptureResult(
-            Metrics::CaptureResult::kSanitizationInitializationFailed);
-        return false;
-      }
-
-      if (!sanitized.Initialize(&process_snapshot,
-                                sanitization_info.annotations_whitelist_address
-                                    ? &whitelist
-                                    : nullptr,
-                                sanitization_info.target_module_address,
-                                sanitization_info.sanitize_stacks)) {
-        Metrics::ExceptionCaptureResult(
-            Metrics::CaptureResult::kSkippedDueToSanitization);
-        return true;
-      }
-
-      snapshot = &sanitized;
-    } else {
-      snapshot = &process_snapshot;
-    }
-
-    MinidumpFileWriter minidump;
-    minidump.InitializeFromSnapshot(snapshot);
-    AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
-
-    if (!minidump.WriteEverything(new_report->Writer())) {
-      LOG(ERROR) << "WriteEverything failed";
-      Metrics::ExceptionCaptureResult(
-          Metrics::CaptureResult::kMinidumpWriteFailed);
-      return false;
-    }
-
-    UUID uuid;
-    database_status =
-        database_->FinishedWritingCrashReport(std::move(new_report), &uuid);
-    if (database_status != CrashReportDatabase::kNoError) {
-      LOG(ERROR) << "FinishedWritingCrashReport failed";
-      Metrics::ExceptionCaptureResult(
-          Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
-      return false;
-    }
-
-    if (upload_thread_) {
-      upload_thread_->ReportPending(uuid);
-    }
+  if (local_report_id != nullptr) {
+    *local_report_id = uuid;
   }
 
   Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
-  return true;
+
+  return write_minidump_to_log ? write_minidump_to_log_succeed : true;
+}
+
+bool CrashReportExceptionHandler::WriteMinidumpToLog(
+    ProcessSnapshotLinux* process_snapshot,
+    ProcessSnapshotSanitized* sanitized_snapshot) {
+  ProcessSnapshot* snapshot =
+      sanitized_snapshot ? implicit_cast<ProcessSnapshot*>(sanitized_snapshot)
+                         : implicit_cast<ProcessSnapshot*>(process_snapshot);
+  MinidumpFileWriter minidump;
+  minidump.InitializeFromSnapshot(snapshot);
+  AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
+
+  OutputStreamFileWriter writer(std::make_unique<ZlibOutputStream>(
+      ZlibOutputStream::Mode::kCompress,
+      std::make_unique<Base94OutputStream>(
+          Base94OutputStream::Mode::kEncode,
+          std::make_unique<LogOutputStream>())));
+  if (!minidump.WriteMinidump(&writer, false /* allow_seek */)) {
+    LOG(ERROR) << "WriteMinidump failed";
+    return false;
+  }
+  return writer.Flush();
 }
 
 }  // namespace crashpad
diff --git a/handler/linux/crash_report_exception_handler.h b/handler/linux/crash_report_exception_handler.h
index 24d5995..83d07c0 100644
--- a/handler/linux/crash_report_exception_handler.h
+++ b/handler/linux/crash_report_exception_handler.h
@@ -26,9 +26,13 @@
 #include "util/linux/exception_handler_protocol.h"
 #include "util/linux/ptrace_connection.h"
 #include "util/misc/address_types.h"
+#include "util/misc/uuid.h"
 
 namespace crashpad {
 
+class ProcessSnapshotLinux;
+class ProcessSnapshotSanitized;
+
 //! \brief An exception handler that writes crash reports for exceptions
 //!     to a CrashReportDatabase.
 class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
@@ -49,6 +53,10 @@
   //!     To interoperate with Breakpad servers, the recommended practice is to
   //!     specify values for the `"prod"` and `"ver"` keys as process
   //!     annotations.
+  //! \param[in] write_minidump_to_database Whether the minidump shall be
+  //!     written to database.
+  //! \param[in] write_minidump_to_log Whether the minidump shall be written to
+  //!     log.
   //! \param[in] user_stream_data_sources Data sources to be used to extend
   //!     crash reports. For each crash report that is written, the data sources
   //!     are called in turn. These data sources may contribute additional
@@ -57,26 +65,49 @@
       CrashReportDatabase* database,
       CrashReportUploadThread* upload_thread,
       const std::map<std::string, std::string>* process_annotations,
+      bool write_minidump_to_database,
+      bool write_minidump_to_log,
       const UserStreamDataSources* user_stream_data_sources);
 
-  ~CrashReportExceptionHandler();
+  ~CrashReportExceptionHandler() override;
 
   // ExceptionHandlerServer::Delegate:
 
   bool HandleException(pid_t client_process_id,
-                       const ClientInformation& info) override;
+                       uid_t client_uid,
+                       const ExceptionHandlerProtocol::ClientInformation& info,
+                       VMAddress requesting_thread_stack_address = 0,
+                       pid_t* requesting_thread_id = nullptr,
+                       UUID* local_report_id = nullptr) override;
 
-  bool HandleExceptionWithBroker(pid_t client_process_id,
-                                 const ClientInformation& info,
-                                 int broker_sock) override;
+  bool HandleExceptionWithBroker(
+      pid_t client_process_id,
+      uid_t client_uid,
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      int broker_sock,
+      UUID* local_report_id = nullptr) override;
 
  private:
-  bool HandleExceptionWithConnection(PtraceConnection* connection,
-                                     const ClientInformation& info);
+  bool HandleExceptionWithConnection(
+      PtraceConnection* connection,
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      uid_t client_uid,
+      VMAddress requesting_thread_stack_address,
+      pid_t* requesting_thread_id,
+      UUID* local_report_id = nullptr);
+
+  bool WriteMinidumpToDatabase(ProcessSnapshotLinux* process_snapshot,
+                               ProcessSnapshotSanitized* sanitized_snapshot,
+                               bool write_minidump_to_log,
+                               UUID* local_report_id);
+  bool WriteMinidumpToLog(ProcessSnapshotLinux* process_snapshot,
+                          ProcessSnapshotSanitized* sanitized_snapshot);
 
   CrashReportDatabase* database_;  // weak
   CrashReportUploadThread* upload_thread_;  // weak
   const std::map<std::string, std::string>* process_annotations_;  // weak
+  bool write_minidump_to_database_;
+  bool write_minidump_to_log_;
   const UserStreamDataSources* user_stream_data_sources_;  // weak
 
   DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
diff --git a/handler/linux/cros_crash_report_exception_handler.cc b/handler/linux/cros_crash_report_exception_handler.cc
new file mode 100644
index 0000000..92bc93e
--- /dev/null
+++ b/handler/linux/cros_crash_report_exception_handler.cc
@@ -0,0 +1,286 @@
+// Copyright 2019 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.
+
+#include "handler/linux/cros_crash_report_exception_handler.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "client/settings.h"
+#include "handler/linux/capture_snapshot.h"
+#include "handler/minidump_to_upload_parameters.h"
+#include "minidump/minidump_file_writer.h"
+#include "snapshot/linux/process_snapshot_linux.h"
+#include "snapshot/minidump/process_snapshot_minidump.h"
+#include "snapshot/sanitized/process_snapshot_sanitized.h"
+#include "util/file/file_writer.h"
+#include "util/linux/direct_ptrace_connection.h"
+#include "util/linux/ptrace_client.h"
+#include "util/misc/metrics.h"
+#include "util/misc/uuid.h"
+#include "util/posix/double_fork_and_exec.h"
+
+namespace crashpad {
+
+namespace {
+
+// Returns the process name for a pid.
+const std::string GetProcessNameFromPid(pid_t pid) {
+  // Symlink to process binary is at /proc/###/exe.
+  std::string link_path = "/proc/" + std::to_string(pid) + "/exe";
+
+  constexpr int kMaxSize = 4096;
+  std::unique_ptr<char[]> buf(new char[kMaxSize]);
+  ssize_t size = readlink(link_path.c_str(), buf.get(), kMaxSize);
+  std::string result;
+  if (size < 0) {
+    PLOG(ERROR) << "Failed to readlink " << link_path;
+  } else {
+    result.assign(buf.get(), size);
+    size_t last_slash_pos = result.rfind('/');
+    if (last_slash_pos != std::string::npos) {
+      result = result.substr(last_slash_pos + 1);
+    }
+  }
+  return result;
+}
+
+bool WriteAnnotationsAndMinidump(
+    const std::map<std::string, std::string>& parameters,
+    MinidumpFileWriter& minidump,
+    FileWriter& file_writer) {
+  for (const auto& kv : parameters) {
+    if (kv.first.find(':') != std::string::npos) {
+      LOG(ERROR) << "Annotation key cannot have ':' in it " << kv.first;
+      return false;
+    }
+    if (!file_writer.Write(kv.first.c_str(), strlen(kv.first.c_str()))) {
+      return false;
+    }
+    if (!file_writer.Write(":", 1)) {
+      return false;
+    }
+    size_t value_size = strlen(kv.second.c_str());
+    std::string value_size_str = std::to_string(value_size);
+    if (!file_writer.Write(value_size_str.c_str(), value_size_str.size())) {
+      return false;
+    }
+    if (!file_writer.Write(":", 1)) {
+      return false;
+    }
+    if (!file_writer.Write(kv.second.c_str(), strlen(kv.second.c_str()))) {
+      return false;
+    }
+  }
+
+  static constexpr char kMinidumpName[] =
+      "upload_file_minidump\"; filename=\"dump\":";
+  if (!file_writer.Write(kMinidumpName, sizeof(kMinidumpName) - 1)) {
+    return false;
+  }
+  crashpad::FileOffset dump_size_start_offset = file_writer.Seek(0, SEEK_CUR);
+  if (dump_size_start_offset < 0) {
+    LOG(ERROR) << "Failed to get minidump size start offset";
+    return false;
+  }
+  static constexpr char kMinidumpLengthFilling[] = "00000000000000000000:";
+  if (!file_writer.Write(kMinidumpLengthFilling,
+                         sizeof(kMinidumpLengthFilling) - 1)) {
+    return false;
+  }
+  crashpad::FileOffset dump_start_offset = file_writer.Seek(0, SEEK_CUR);
+  if (dump_start_offset < 0) {
+    LOG(ERROR) << "Failed to get minidump start offset";
+    return false;
+  }
+  if (!minidump.WriteEverything(&file_writer)) {
+    return false;
+  }
+  crashpad::FileOffset dump_end_offset = file_writer.Seek(0, SEEK_CUR);
+  if (dump_end_offset < 0) {
+    LOG(ERROR) << "Failed to get minidump end offset";
+    return false;
+  }
+
+  size_t dump_data_size = dump_end_offset - dump_start_offset;
+  std::string dump_data_size_str = std::to_string(dump_data_size);
+  file_writer.Seek(dump_size_start_offset + strlen(kMinidumpLengthFilling) - 1 -
+                       dump_data_size_str.size(),
+                   SEEK_SET);
+  if (!file_writer.Write(dump_data_size_str.c_str(),
+                         dump_data_size_str.size())) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+CrosCrashReportExceptionHandler::CrosCrashReportExceptionHandler(
+    CrashReportDatabase* database,
+    const std::map<std::string, std::string>* process_annotations,
+    const UserStreamDataSources* user_stream_data_sources)
+    : database_(database),
+      process_annotations_(process_annotations),
+      user_stream_data_sources_(user_stream_data_sources),
+      always_allow_feedback_(false) {}
+
+CrosCrashReportExceptionHandler::~CrosCrashReportExceptionHandler() = default;
+
+bool CrosCrashReportExceptionHandler::HandleException(
+    pid_t client_process_id,
+    uid_t client_uid,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    UUID* local_report_id) {
+  Metrics::ExceptionEncountered();
+
+  DirectPtraceConnection connection;
+  if (!connection.Initialize(client_process_id)) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kDirectPtraceFailed);
+    return false;
+  }
+
+  return HandleExceptionWithConnection(&connection,
+                                       info,
+                                       client_uid,
+                                       requesting_thread_stack_address,
+                                       requesting_thread_id,
+                                       local_report_id);
+}
+
+bool CrosCrashReportExceptionHandler::HandleExceptionWithBroker(
+    pid_t client_process_id,
+    uid_t client_uid,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    int broker_sock,
+    UUID* local_report_id) {
+  Metrics::ExceptionEncountered();
+
+  PtraceClient client;
+  if (!client.Initialize(broker_sock, client_process_id)) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kBrokeredPtraceFailed);
+    return false;
+  }
+
+  return HandleExceptionWithConnection(
+      &client, info, client_uid, 0, nullptr, local_report_id);
+}
+
+bool CrosCrashReportExceptionHandler::HandleExceptionWithConnection(
+    PtraceConnection* connection,
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    uid_t client_uid,
+    VMAddress requesting_thread_stack_address,
+    pid_t* requesting_thread_id,
+    UUID* local_report_id) {
+  std::unique_ptr<ProcessSnapshotLinux> process_snapshot;
+  std::unique_ptr<ProcessSnapshotSanitized> sanitized_snapshot;
+  if (!CaptureSnapshot(connection,
+                       info,
+                       *process_annotations_,
+                       client_uid,
+                       requesting_thread_stack_address,
+                       requesting_thread_id,
+                       &process_snapshot,
+                       &sanitized_snapshot)) {
+    return false;
+  }
+
+  UUID client_id;
+  Settings* const settings = database_->GetSettings();
+  if (settings) {
+    // If GetSettings() or GetClientID() fails, something else will log a
+    // message and client_id will be left at its default value, all zeroes,
+    // which is appropriate.
+    settings->GetClientID(&client_id);
+  }
+  process_snapshot->SetClientID(client_id);
+
+  UUID uuid;
+  uuid.InitializeWithNew();
+  process_snapshot->SetReportID(uuid);
+
+  ProcessSnapshot* snapshot =
+      sanitized_snapshot
+          ? implicit_cast<ProcessSnapshot*>(sanitized_snapshot.get())
+          : implicit_cast<ProcessSnapshot*>(process_snapshot.get());
+
+  MinidumpFileWriter minidump;
+  minidump.InitializeFromSnapshot(snapshot);
+  AddUserExtensionStreams(user_stream_data_sources_, snapshot, &minidump);
+
+  FileWriter file_writer;
+  if (!file_writer.OpenMemfd(base::FilePath("minidump"))) {
+    Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kOpenMemfdFailed);
+    return false;
+  }
+
+  std::map<std::string, std::string> parameters =
+      BreakpadHTTPFormParametersFromMinidump(snapshot);
+  // Used to differentiate between breakpad and crashpad while the switch is
+  // ramping up.
+  parameters.emplace("crash_library", "crashpad");
+
+  if (!WriteAnnotationsAndMinidump(parameters, minidump, file_writer)) {
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kMinidumpWriteFailed);
+    return false;
+  }
+
+  // CrOS uses crash_reporter instead of Crashpad to report crashes.
+  // crash_reporter needs to know the pid and uid of the crashing process.
+  std::vector<std::string> argv({"/sbin/crash_reporter"});
+
+  argv.push_back("--chrome_memfd=" + std::to_string(file_writer.fd()));
+  argv.push_back("--pid=" + std::to_string(*requesting_thread_id));
+  argv.push_back("--uid=" + std::to_string(client_uid));
+  std::string process_name = GetProcessNameFromPid(*requesting_thread_id);
+  argv.push_back("--exe=" + (process_name.empty() ? "chrome" : process_name));
+
+  if (info.crash_loop_before_time != 0) {
+    argv.push_back("--crash_loop_before=" +
+                   std::to_string(info.crash_loop_before_time));
+  }
+  if (!dump_dir_.empty()) {
+    argv.push_back("--chrome_dump_dir=" + dump_dir_.value());
+  }
+  if (always_allow_feedback_) {
+    argv.push_back("--always_allow_feedback");
+  }
+
+  if (!DoubleForkAndExec(argv,
+                         nullptr /* envp */,
+                         file_writer.fd() /* preserve_fd */,
+                         false /* use_path */,
+                         nullptr /* child_function */)) {
+    LOG(ERROR) << "DoubleForkAndExec failed";
+    Metrics::ExceptionCaptureResult(
+        Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
+    return false;
+  }
+
+  if (local_report_id != nullptr) {
+    *local_report_id = uuid;
+  }
+  LOG(INFO) << "Successfully wrote report " << uuid.ToString();
+
+  Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/handler/linux/cros_crash_report_exception_handler.h b/handler/linux/cros_crash_report_exception_handler.h
new file mode 100644
index 0000000..0b14544
--- /dev/null
+++ b/handler/linux/cros_crash_report_exception_handler.h
@@ -0,0 +1,101 @@
+// Copyright 2019 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_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_
+#define CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "client/crash_report_database.h"
+#include "handler/linux/exception_handler_server.h"
+#include "handler/user_stream_data_source.h"
+#include "util/linux/exception_handler_protocol.h"
+#include "util/linux/ptrace_connection.h"
+#include "util/misc/address_types.h"
+#include "util/misc/uuid.h"
+
+namespace crashpad {
+
+//! \brief An exception handler that writes crash reports to the ChromeOS
+//!     crash_reporter.
+class CrosCrashReportExceptionHandler
+    : public ExceptionHandlerServer::Delegate {
+ public:
+  //! \brief Creates a new object that will pass reports to
+  //!     `/sbin/crash_reporter`.
+  //!
+  //! \param[in] database The database that supplies settings for this client.
+  //!     This object does not write its reports to this database.
+  //! \param[in] process_annotations A map of annotations to insert as
+  //!     process-level annotations into each crash report that is written. Do
+  //!     not confuse this with module-level annotations, which are under the
+  //!     control of the crashing process, and are used to implement Chrome’s
+  //!     “crash keys.” Process-level annotations are those that are beyond the
+  //!     control of the crashing process, which must reliably be set even if
+  //!     the process crashes before it’s able to establish its own annotations.
+  //!     To interoperate with Breakpad servers, the recommended practice is to
+  //!     specify values for the `"prod"` and `"ver"` keys as process
+  //!     annotations.
+  //! \param[in] user_stream_data_sources Data sources to be used to extend
+  //!     crash reports. For each crash report that is written, the data sources
+  //!     are called in turn. These data sources may contribute additional
+  //!     minidump streams. `nullptr` if not required.
+  CrosCrashReportExceptionHandler(
+      CrashReportDatabase* database,
+      const std::map<std::string, std::string>* process_annotations,
+      const UserStreamDataSources* user_stream_data_sources);
+
+  ~CrosCrashReportExceptionHandler() override;
+
+  // ExceptionHandlerServer::Delegate:
+
+  bool HandleException(pid_t client_process_id,
+                       uid_t client_uid,
+                       const ExceptionHandlerProtocol::ClientInformation& info,
+                       VMAddress requesting_thread_stack_address = 0,
+                       pid_t* requesting_thread_id = nullptr,
+                       UUID* local_report_id = nullptr) override;
+
+  bool HandleExceptionWithBroker(
+      pid_t client_process_id,
+      uid_t client_uid,
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      int broker_sock,
+      UUID* local_report_id = nullptr) override;
+
+  void SetDumpDir(const base::FilePath& dump_dir) { dump_dir_ = dump_dir; }
+  void SetAlwaysAllowFeedback() { always_allow_feedback_ = true; }
+ private:
+  bool HandleExceptionWithConnection(
+      PtraceConnection* connection,
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      uid_t client_uid,
+      VMAddress requesting_thread_stack_address,
+      pid_t* requesting_thread_id,
+      UUID* local_report_id = nullptr);
+
+  CrashReportDatabase* database_;  // weak
+  const std::map<std::string, std::string>* process_annotations_;  // weak
+  const UserStreamDataSources* user_stream_data_sources_;  // weak
+  base::FilePath dump_dir_;
+  bool always_allow_feedback_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrosCrashReportExceptionHandler);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_HANDLER_LINUX_CROS_CRASH_REPORT_EXCEPTION_HANDLER_H_
diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc
index 748bf6e..c9fbb0a 100644
--- a/handler/linux/exception_handler_server.cc
+++ b/handler/linux/exception_handler_server.cc
@@ -15,10 +15,11 @@
 #include "handler/linux/exception_handler_server.h"
 
 #include <errno.h>
-#include <sys/capability.h>
+#include <linux/capability.h>
 #include <sys/epoll.h>
 #include <sys/eventfd.h>
 #include <sys/socket.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -31,6 +32,8 @@
 #include "build/build_config.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
+#include "util/linux/proc_task_reader.h"
+#include "util/linux/socket.h"
 #include "util/misc/as_underlying_type.h"
 
 namespace crashpad {
@@ -90,54 +93,96 @@
 }
 
 bool HaveCapSysPtrace() {
-  struct __user_cap_header_struct cap_header = {};
-  struct __user_cap_data_struct cap_data = {};
-
+  __user_cap_header_struct cap_header;
   cap_header.pid = getpid();
+  cap_header.version = _LINUX_CAPABILITY_VERSION_3;
 
-  if (capget(&cap_header, &cap_data) != 0) {
+  __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3];
+  if (syscall(SYS_capget, &cap_header, &cap_data) != 0) {
     PLOG(ERROR) << "capget";
+    LOG_IF(ERROR, errno == EINVAL) << "cap_header.version " << std::hex
+                                   << cap_header.version;
     return false;
   }
 
-  if (cap_header.version != _LINUX_CAPABILITY_VERSION_3) {
-    LOG(ERROR) << "Unexpected capability version " << std::hex
-               << cap_header.version;
-    return false;
-  }
-
-  return (cap_data.effective & (1 << CAP_SYS_PTRACE)) != 0;
+  return (cap_data[CAP_TO_INDEX(CAP_SYS_PTRACE)].effective &
+          CAP_TO_MASK(CAP_SYS_PTRACE)) != 0;
 }
 
-bool SendMessageToClient(int client_sock, ServerToClientMessage::Type type) {
-  ServerToClientMessage message = {};
+bool SendMessageToClient(
+    int client_sock,
+    ExceptionHandlerProtocol::ServerToClientMessage::Type type) {
+  ExceptionHandlerProtocol::ServerToClientMessage message = {};
   message.type = type;
-  if (type == ServerToClientMessage::kTypeSetPtracer) {
+  if (type ==
+      ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer) {
     message.pid = getpid();
   }
   return LoggingWriteFile(client_sock, &message, sizeof(message));
 }
 
+int tgkill(pid_t pid, pid_t tid, int signo) {
+  return syscall(SYS_tgkill, pid, tid, signo);
+}
+
+void SendSIGCONT(pid_t pid, pid_t tid) {
+  if (tid > 0) {
+    if (tgkill(pid, tid, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
+      PLOG(ERROR) << "tgkill";
+    }
+    return;
+  }
+
+  std::vector<pid_t> threads;
+  if (!ReadThreadIDs(pid, &threads)) {
+    return;
+  }
+  for (const auto& thread : threads) {
+    if (tgkill(pid, thread, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
+      PLOG(ERROR) << "tgkill";
+    }
+  }
+}
+
+bool SendCredentials(int client_sock) {
+  ExceptionHandlerProtocol::ServerToClientMessage message = {};
+  message.type =
+      ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials;
+  return UnixCredentialSocket::SendMsg(
+             client_sock, &message, sizeof(message)) == 0;
+}
+
 class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
  public:
   PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {}
   ~PtraceStrategyDeciderImpl() = default;
 
-  Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
+  Strategy ChooseStrategy(int sock,
+                          bool multiple_clients,
+                          const ucred& client_credentials) override {
+    if (client_credentials.pid <= 0) {
+      LOG(ERROR) << "invalid credentials";
+      return Strategy::kNoPtrace;
+    }
+
     switch (GetPtraceScope()) {
       case PtraceScope::kClassic:
-        if (getuid() == client_credentials.uid) {
+        if (getuid() == client_credentials.uid || HaveCapSysPtrace()) {
           return Strategy::kDirectPtrace;
         }
-        return TryForkingBroker(sock);
+        return multiple_clients ? Strategy::kNoPtrace : TryForkingBroker(sock);
 
       case PtraceScope::kRestricted:
+        if (multiple_clients) {
+          return Strategy::kDirectPtrace;
+        }
         if (!SendMessageToClient(sock,
-                                 ServerToClientMessage::kTypeSetPtracer)) {
+                                 ExceptionHandlerProtocol::
+                                     ServerToClientMessage::kTypeSetPtracer)) {
           return Strategy::kError;
         }
 
-        Errno status;
+        ExceptionHandlerProtocol::Errno status;
         if (!LoggingReadFileExactly(sock, &status, sizeof(status))) {
           return Strategy::kError;
         }
@@ -169,12 +214,13 @@
 
  private:
   static Strategy TryForkingBroker(int client_sock) {
-    if (!SendMessageToClient(client_sock,
-                             ServerToClientMessage::kTypeForkBroker)) {
+    if (!SendMessageToClient(
+            client_sock,
+            ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker)) {
       return Strategy::kError;
     }
 
-    Errno status;
+    ExceptionHandlerProtocol::Errno status;
     if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) {
       return Strategy::kError;
     }
@@ -190,12 +236,6 @@
 
 }  // namespace
 
-struct ExceptionHandlerServer::Event {
-  enum class Type { kShutdown, kClientMessage } type;
-
-  ScopedFileHandle fd;
-};
-
 ExceptionHandlerServer::ExceptionHandlerServer()
     : clients_(),
       shutdown_event_(),
@@ -211,7 +251,8 @@
   strategy_decider_ = std::move(decider);
 }
 
-bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) {
+bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock,
+                                                  bool multiple_clients) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   pollfd_.reset(epoll_create1(EPOLL_CLOEXEC));
@@ -239,7 +280,9 @@
     return false;
   }
 
-  if (!InstallClientSocket(std::move(sock))) {
+  if (!InstallClientSocket(std::move(sock),
+                           multiple_clients ? Event::Type::kSharedSocketMessage
+                                            : Event::Type::kClientMessage)) {
     return false;
   }
 
@@ -281,8 +324,8 @@
 }
 
 void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) {
-  DCHECK_EQ(AsUnderlyingType(event->type),
-            AsUnderlyingType(Event::Type::kClientMessage));
+  DCHECK_NE(AsUnderlyingType(event->type),
+            AsUnderlyingType(Event::Type::kShutdown));
 
   if (event_type & EPOLLERR) {
     LogSocketError(event->fd.get());
@@ -306,16 +349,30 @@
   return;
 }
 
-bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) {
-  int optval = 1;
+bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket,
+                                                 Event::Type type) {
+  // The handler may not have permission to set SO_PASSCRED on the socket, but
+  // it doesn't need to if the client has already set it.
+  // https://bugs.chromium.org/p/crashpad/issues/detail?id=252
+  int optval;
   socklen_t optlen = sizeof(optval);
-  if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != 0) {
-    PLOG(ERROR) << "setsockopt";
+  if (getsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, &optlen) !=
+      0) {
+    PLOG(ERROR) << "getsockopt";
     return false;
   }
+  if (!optval) {
+    optval = 1;
+    optlen = sizeof(optval);
+    if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
+        0) {
+      PLOG(ERROR) << "setsockopt";
+      return false;
+    }
+  }
 
   auto event = std::make_unique<Event>();
-  event->type = Event::Type::kClientMessage;
+  event->type = type;
   event->fd.reset(socket.release());
 
   Event* eventp = event.get();
@@ -355,53 +412,24 @@
 }
 
 bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) {
-  ClientToServerMessage message;
-  iovec iov;
-  iov.iov_base = &message;
-  iov.iov_len = sizeof(message);
-
-  msghdr msg;
-  msg.msg_name = nullptr;
-  msg.msg_namelen = 0;
-  msg.msg_iov = &iov;
-  msg.msg_iovlen = 1;
-
-  char cmsg_buf[CMSG_SPACE(sizeof(ucred))];
-  msg.msg_control = cmsg_buf;
-  msg.msg_controllen = sizeof(cmsg_buf);
-  msg.msg_flags = 0;
-
-  int res = HANDLE_EINTR(recvmsg(event->fd.get(), &msg, 0));
-  if (res < 0) {
-    PLOG(ERROR) << "recvmsg";
-    return false;
-  }
-  if (res == 0) {
-    // The client had an orderly shutdown.
+  ExceptionHandlerProtocol::ClientToServerMessage message;
+  ucred creds;
+  if (!UnixCredentialSocket::RecvMsg(
+          event->fd.get(), &message, sizeof(message), &creds)) {
     return false;
   }
 
-  if (msg.msg_name != nullptr || msg.msg_namelen != 0) {
-    LOG(ERROR) << "unexpected msg name";
-    return false;
-  }
+  switch (message.type) {
+    case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials:
+      return SendCredentials(event->fd.get());
 
-  if (msg.msg_iovlen != 1) {
-    LOG(ERROR) << "unexpected iovlen";
-    return false;
-  }
-
-  if (msg.msg_iov[0].iov_len != sizeof(ClientToServerMessage)) {
-    LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len;
-    return false;
-  }
-  auto client_msg =
-      reinterpret_cast<ClientToServerMessage*>(msg.msg_iov[0].iov_base);
-
-  switch (client_msg->type) {
-    case ClientToServerMessage::kCrashDumpRequest:
+    case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest:
       return HandleCrashDumpRequest(
-          msg, client_msg->client_info, event->fd.get());
+          creds,
+          message.client_info,
+          message.requesting_thread_stack_address,
+          event->fd.get(),
+          event->type == Event::Type::kSharedSocketMessage);
   }
 
   DCHECK(false);
@@ -410,53 +438,56 @@
 }
 
 bool ExceptionHandlerServer::HandleCrashDumpRequest(
-    const msghdr& msg,
-    const ClientInformation& client_info,
-    int client_sock) {
-  cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
-  if (cmsg == nullptr) {
-    LOG(ERROR) << "missing credentials";
-    return false;
-  }
+    const ucred& creds,
+    const ExceptionHandlerProtocol::ClientInformation& client_info,
+    VMAddress requesting_thread_stack_address,
+    int client_sock,
+    bool multiple_clients) {
+  pid_t client_process_id = creds.pid;
+  pid_t requesting_thread_id = -1;
+  uid_t client_uid = creds.uid;
 
-  if (cmsg->cmsg_level != SOL_SOCKET) {
-    LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level;
-    return false;
-  }
-
-  if (cmsg->cmsg_type != SCM_CREDENTIALS) {
-    LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type;
-    return false;
-  }
-
-  if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) {
-    LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len;
-    return false;
-  }
-
-  ucred* client_credentials = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
-  pid_t client_process_id = client_credentials->pid;
-
-  switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) {
+  switch (
+      strategy_decider_->ChooseStrategy(client_sock, multiple_clients, creds)) {
     case PtraceStrategyDecider::Strategy::kError:
+      if (multiple_clients) {
+        SendSIGCONT(client_process_id, requesting_thread_id);
+      }
       return false;
 
     case PtraceStrategyDecider::Strategy::kNoPtrace:
-      return SendMessageToClient(client_sock,
-                                 ServerToClientMessage::kTypeCrashDumpFailed);
+      if (multiple_clients) {
+        SendSIGCONT(client_process_id, requesting_thread_id);
+        return true;
+      }
+      return SendMessageToClient(
+          client_sock,
+          ExceptionHandlerProtocol::ServerToClientMessage::
+              kTypeCrashDumpFailed);
 
-    case PtraceStrategyDecider::Strategy::kDirectPtrace:
-      delegate_->HandleException(client_process_id, client_info);
+    case PtraceStrategyDecider::Strategy::kDirectPtrace: {
+      delegate_->HandleException(client_process_id,
+                                 client_uid,
+                                 client_info,
+                                 requesting_thread_stack_address,
+                                 &requesting_thread_id);
+      if (multiple_clients) {
+        SendSIGCONT(client_process_id, requesting_thread_id);
+        return true;
+      }
       break;
+    }
 
     case PtraceStrategyDecider::Strategy::kUseBroker:
+      DCHECK(!multiple_clients);
       delegate_->HandleExceptionWithBroker(
-          client_process_id, client_info, client_sock);
+          client_process_id, client_uid, client_info, client_sock);
       break;
   }
 
-  return SendMessageToClient(client_sock,
-                             ServerToClientMessage::kTypeCrashDumpComplete);
+  return SendMessageToClient(
+      client_sock,
+      ExceptionHandlerProtocol::ServerToClientMessage::kTypeCrashDumpComplete);
 }
 
 }  // namespace crashpad
diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h
index 5f49182..ac430a4 100644
--- a/handler/linux/exception_handler_server.h
+++ b/handler/linux/exception_handler_server.h
@@ -18,6 +18,7 @@
 #include <stdint.h>
 #include <sys/socket.h>
 
+#include <atomic>
 #include <memory>
 #include <unordered_map>
 
@@ -26,6 +27,7 @@
 #include "util/linux/exception_handler_protocol.h"
 #include "util/misc/address_types.h"
 #include "util/misc/initialization_state_dcheck.h"
+#include "util/misc/uuid.h"
 
 namespace crashpad {
 
@@ -53,9 +55,12 @@
   //! \brief Chooses an appropriate `ptrace` strategy.
   //!
   //! \param[in] sock A socket conncted to a ExceptionHandlerClient.
+  //! \param[in] multiple_clients `true` if the socket is connected to multiple
+  //!     clients. The broker is not supported in this configuration.
   //! \param[in] client_credentials The credentials for the connected client.
   //! \return the chosen #Strategy.
   virtual Strategy ChooseStrategy(int sock,
+                                  bool multiple_clients,
                                   const ucred& client_credentials) = 0;
 
  protected:
@@ -71,24 +76,43 @@
     //! \brief Called on receipt of a crash dump request from a client.
     //!
     //! \param[in] client_process_id The process ID of the crashing client.
+    //! \param[in] client_uid The user ID of the crashing client.
     //! \param[in] info Information on the client.
+    //! \param[in] requesting_thread_stack_address Any address within the stack
+    //!     range for the the thread that sent the crash dump request. Optional.
+    //!     If unspecified or 0, \a requesting_thread_id will be -1.
+    //! \param[out] requesting_thread_id The thread ID of the thread which
+    //!     requested the crash dump if not `nullptr`. Set to -1 if the thread
+    //!     ID could not be determined. Optional.
+    //! \param[out] local_report_id The unique identifier for the report created
+    //!     in the local report database. Optional.
     //! \return `true` on success. `false` on failure with a message logged.
-    virtual bool HandleException(pid_t client_process_id,
-                                 const ClientInformation& info) = 0;
+    virtual bool HandleException(
+        pid_t client_process_id,
+        uid_t client_uid,
+        const ExceptionHandlerProtocol::ClientInformation& info,
+        VMAddress requesting_thread_stack_address = 0,
+        pid_t* requesting_thread_id = nullptr,
+        UUID* local_report_id = nullptr) = 0;
 
     //! \brief Called on the receipt of a crash dump request from a client for a
     //!     crash that should be mediated by a PtraceBroker.
     //!
     //! \param[in] client_process_id The process ID of the crashing client.
+    //! \param[in] client_uid The uid of the crashing client.
     //! \param[in] info Information on the client.
     //! \param[in] broker_sock A socket connected to the PtraceBroker.
+    //! \param[out] local_report_id The unique identifier for the report created
+    //!     in the local report database. Optional.
     //! \return `true` on success. `false` on failure with a message logged.
-    virtual bool HandleExceptionWithBroker(pid_t client_process_id,
-                                           const ClientInformation& info,
-                                           int broker_sock) = 0;
+    virtual bool HandleExceptionWithBroker(
+        pid_t client_process_id,
+        uid_t client_uid,
+        const ExceptionHandlerProtocol::ClientInformation& info,
+        int broker_sock,
+        UUID* local_report_id = nullptr) = 0;
 
-   protected:
-    ~Delegate() {}
+    virtual ~Delegate() {}
   };
 
   ExceptionHandlerServer();
@@ -105,8 +129,11 @@
   //! This method must be successfully called before Run().
   //!
   //! \param[in] sock A socket on which to receive client requests.
+  //! \param[in] multiple_clients `true` if this socket is used by multiple
+  //!     clients. Using a broker process is not supported in this
+  //!     configuration.
   //! \return `true` on success. `false` on failure with a message logged.
-  bool InitializeWithClient(ScopedFileHandle sock);
+  bool InitializeWithClient(ScopedFileHandle sock, bool multiple_clients);
 
   //! \brief Runs the exception-handling server.
   //!
@@ -126,22 +153,39 @@
   void Stop();
 
  private:
-  struct Event;
+  struct Event {
+    enum class Type {
+      // Used by Stop() to shutdown the server.
+      kShutdown,
+
+      // A message from a client on a private socket connection.
+      kClientMessage,
+
+      // A message from a client on a shared socket connection.
+      kSharedSocketMessage
+    };
+
+    Type type;
+    ScopedFileHandle fd;
+  };
 
   void HandleEvent(Event* event, uint32_t event_type);
-  bool InstallClientSocket(ScopedFileHandle socket);
+  bool InstallClientSocket(ScopedFileHandle socket, Event::Type type);
   bool UninstallClientSocket(Event* event);
   bool ReceiveClientMessage(Event* event);
-  bool HandleCrashDumpRequest(const msghdr& msg,
-                              const ClientInformation& client_info,
-                              int client_sock);
+  bool HandleCrashDumpRequest(
+      const ucred& creds,
+      const ExceptionHandlerProtocol::ClientInformation& client_info,
+      VMAddress requesting_thread_stack_address,
+      int client_sock,
+      bool multiple_clients);
 
   std::unordered_map<int, std::unique_ptr<Event>> clients_;
   std::unique_ptr<Event> shutdown_event_;
   std::unique_ptr<PtraceStrategyDecider> strategy_decider_;
   Delegate* delegate_;
   ScopedFileHandle pollfd_;
-  bool keep_running_;
+  std::atomic<bool> keep_running_;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
diff --git a/handler/linux/exception_handler_server_test.cc b/handler/linux/exception_handler_server_test.cc
index eda14d3..7da5bab 100644
--- a/handler/linux/exception_handler_server_test.cc
+++ b/handler/linux/exception_handler_server_test.cc
@@ -18,16 +18,23 @@
 #include <unistd.h>
 
 #include "base/logging.h"
+#include "build/build_config.h"
 #include "gtest/gtest.h"
+#include "snapshot/linux/process_snapshot_linux.h"
 #include "test/errors.h"
 #include "test/multiprocess.h"
 #include "util/linux/direct_ptrace_connection.h"
 #include "util/linux/exception_handler_client.h"
 #include "util/linux/ptrace_client.h"
 #include "util/linux/scoped_pr_set_ptracer.h"
+#include "util/misc/uuid.h"
 #include "util/synchronization/semaphore.h"
 #include "util/thread/thread.h"
 
+#if defined(OS_ANDROID)
+#include <android/api-level.h>
+#endif
+
 namespace crashpad {
 namespace test {
 namespace {
@@ -101,7 +108,11 @@
   }
 
   bool HandleException(pid_t client_process_id,
-                       const ClientInformation& info) override {
+                       uid_t client_uid,
+                       const ExceptionHandlerProtocol::ClientInformation& info,
+                       VMAddress requesting_thread_stack_address,
+                       pid_t* requesting_thread_id = nullptr,
+                       UUID* local_report_id = nullptr) override {
     DirectPtraceConnection connection;
     bool connected = connection.Initialize(client_process_id);
     EXPECT_TRUE(connected);
@@ -109,12 +120,32 @@
     last_exception_address_ = info.exception_information_address;
     last_client_ = client_process_id;
     sem_.Signal();
-    return connected;
+    if (!connected) {
+      return false;
+    }
+
+    if (requesting_thread_id) {
+      if (requesting_thread_stack_address) {
+        ProcessSnapshotLinux process_snapshot;
+        if (!process_snapshot.Initialize(&connection)) {
+          ADD_FAILURE();
+          return false;
+        }
+        *requesting_thread_id = process_snapshot.FindThreadWithStackAddress(
+            requesting_thread_stack_address);
+      } else {
+        *requesting_thread_id = -1;
+      }
+    }
+    return true;
   }
 
-  bool HandleExceptionWithBroker(pid_t client_process_id,
-                                 const ClientInformation& info,
-                                 int broker_sock) override {
+  bool HandleExceptionWithBroker(
+      pid_t client_process_id,
+      uid_t client_uid,
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      int broker_sock,
+      UUID* local_report_id = nullptr) override {
     PtraceClient client;
     bool connected = client.Initialize(broker_sock, client_process_id);
     EXPECT_TRUE(connected);
@@ -140,12 +171,15 @@
 
   ~MockPtraceStrategyDecider() {}
 
-  Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
+  Strategy ChooseStrategy(int sock,
+                          bool multiple_clients,
+                          const ucred& client_credentials) override {
     if (strategy_ == Strategy::kUseBroker) {
-      ServerToClientMessage message = {};
-      message.type = ServerToClientMessage::kTypeForkBroker;
+      ExceptionHandlerProtocol::ServerToClientMessage message = {};
+      message.type =
+          ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker;
 
-      Errno status;
+      ExceptionHandlerProtocol::Errno status;
       bool result = LoggingWriteFile(sock, &message, sizeof(message)) &&
                     LoggingReadFileExactly(sock, &status, sizeof(status));
       EXPECT_TRUE(result);
@@ -169,13 +203,14 @@
   DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider);
 };
 
-class ExceptionHandlerServerTest : public testing::Test {
+class ExceptionHandlerServerTest : public testing::TestWithParam<bool> {
  public:
   ExceptionHandlerServerTest()
       : server_(),
         delegate_(),
         server_thread_(&server_, &delegate_),
-        sock_to_handler_() {}
+        sock_to_handler_(),
+        use_multi_client_socket_(GetParam()) {}
 
   ~ExceptionHandlerServerTest() = default;
 
@@ -197,7 +232,7 @@
     ~CrashDumpTest() = default;
 
     void MultiprocessParent() override {
-      ClientInformation info;
+      ExceptionHandlerProtocol::ClientInformation info;
       ASSERT_TRUE(
           LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info)));
 
@@ -216,9 +251,8 @@
     void MultiprocessChild() override {
       ASSERT_EQ(close(server_test_->sock_to_client_), 0);
 
-      ClientInformation info;
+      ExceptionHandlerProtocol::ClientInformation info;
       info.exception_information_address = 42;
-
       ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info)));
 
       // If the current ptrace_scope is restricted, the broker needs to be set
@@ -226,7 +260,8 @@
       // ptracer allows the broker to inherit this condition.
       ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true);
 
-      ExceptionHandlerClient client(server_test_->SockToHandler());
+      ExceptionHandlerClient client(server_test_->SockToHandler(),
+                                    server_test_->use_multi_client_socket_);
       ASSERT_EQ(client.RequestCrashDump(info), 0);
     }
 
@@ -239,16 +274,18 @@
 
   void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy,
                                     bool succeeds) {
-    ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
-    ServerThread()->Start();
-
     Server()->SetPtraceStrategyDecider(
         std::make_unique<MockPtraceStrategyDecider>(strategy));
 
+    ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
+    ServerThread()->Start();
+
     CrashDumpTest test(this, succeeds);
     test.Run();
   }
 
+  bool UsingMultiClientSocket() const { return use_multi_client_socket_; }
+
  protected:
   void SetUp() override {
     int socks[2];
@@ -256,7 +293,8 @@
     sock_to_handler_.reset(socks[0]);
     sock_to_client_ = socks[1];
 
-    ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1])));
+    ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]),
+                                             use_multi_client_socket_));
   }
 
  private:
@@ -265,36 +303,37 @@
   RunServerThread server_thread_;
   ScopedFileHandle sock_to_handler_;
   int sock_to_client_;
+  bool use_multi_client_socket_;
 
   DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest);
 };
 
-TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) {
+TEST_P(ExceptionHandlerServerTest, ShutdownWithNoClients) {
   ServerThread()->Start();
   Hangup();
   ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
 }
 
-TEST_F(ExceptionHandlerServerTest, StopWithClients) {
+TEST_P(ExceptionHandlerServerTest, StopWithClients) {
   ServerThread()->Start();
   Server()->Stop();
   ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
 }
 
-TEST_F(ExceptionHandlerServerTest, StopBeforeRun) {
+TEST_P(ExceptionHandlerServerTest, StopBeforeRun) {
   Server()->Stop();
   ServerThread()->Start();
   ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
 }
 
-TEST_F(ExceptionHandlerServerTest, MultipleStops) {
+TEST_P(ExceptionHandlerServerTest, MultipleStops) {
   ServerThread()->Start();
   Server()->Stop();
   Server()->Stop();
   ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
 }
 
-TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
+TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
   ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
   ServerThread()->Start();
 
@@ -302,25 +341,35 @@
   test.Run();
 }
 
-TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) {
+TEST_P(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) {
   ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace,
                                false);
 }
 
-TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) {
+TEST_P(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) {
+  if (UsingMultiClientSocket()) {
+    // The broker is not supported with multiple clients connected on a single
+    // socket.
+    return;
+  }
   ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker,
                                true);
 }
 
-TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) {
+TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) {
   ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace,
                                true);
 }
 
-TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) {
+TEST_P(ExceptionHandlerServerTest, RequestCrashDumpError) {
   ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false);
 }
 
+INSTANTIATE_TEST_SUITE_P(ExceptionHandlerServerTestSuite,
+                         ExceptionHandlerServerTest,
+                         testing::Bool()
+);
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/handler/linux/handler_trampoline.cc b/handler/linux/handler_trampoline.cc
new file mode 100644
index 0000000..3453563
--- /dev/null
+++ b/handler/linux/handler_trampoline.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 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.
+
+#include <android/log.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+
+// The first argument passed to the trampoline is the name of the native library
+// exporting the symbol `CrashpadHandlerMain`. The remaining arguments are the
+// same as for `HandlerMain()`.
+int main(int argc, char* argv[]) {
+  static constexpr char kTag[] = "crashpad";
+
+  if (argc < 2) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "usage: %s <path>", argv[0]);
+    return EXIT_FAILURE;
+  }
+
+  void* handle = dlopen(argv[1], RTLD_LAZY | RTLD_GLOBAL);
+  if (!handle) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "dlopen: %s", dlerror());
+    return EXIT_FAILURE;
+  }
+
+  using MainType = int (*)(int, char*[]);
+  MainType crashpad_main =
+      reinterpret_cast<MainType>(dlsym(handle, "CrashpadHandlerMain"));
+  if (!crashpad_main) {
+    __android_log_print(ANDROID_LOG_FATAL, kTag, "dlsym: %s", dlerror());
+    return EXIT_FAILURE;
+  }
+
+  return crashpad_main(argc - 1, argv + 1);
+}
diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc
index 9919e95..cccf1e9 100644
--- a/handler/mac/crash_report_exception_handler.cc
+++ b/handler/mac/crash_report_exception_handler.cc
@@ -28,6 +28,7 @@
 #include "snapshot/crashpad_info_client_options.h"
 #include "snapshot/mac/process_snapshot_mac.h"
 #include "util/file/file_writer.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/exc_client_variants.h"
 #include "util/mach/exception_behaviors.h"
 #include "util/mach/exception_types.h"
diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h
index 0b44de6..b5a59e4 100644
--- a/handler/mac/crash_report_exception_handler.h
+++ b/handler/mac/crash_report_exception_handler.h
@@ -30,7 +30,8 @@
 
 //! \brief An exception handler that writes crash reports for exception messages
 //!     to a CrashReportDatabase.
-class CrashReportExceptionHandler : public UniversalMachExcServer::Interface {
+class CrashReportExceptionHandler final
+    : public UniversalMachExcServer::Interface {
  public:
   //! \brief Creates a new object that will store crash reports in \a database.
   //!
diff --git a/handler/mac/file_limit_annotation.cc b/handler/mac/file_limit_annotation.cc
index cf56eb8..a01e87d 100644
--- a/handler/mac/file_limit_annotation.cc
+++ b/handler/mac/file_limit_annotation.cc
@@ -24,7 +24,7 @@
 #include <string>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "client/crashpad_info.h"
 #include "client/simple_string_dictionary.h"
@@ -108,7 +108,7 @@
   int mib[] = {CTL_KERN, KERN_MAXFILES};
   size = sizeof(value);
   std::string max_files = FormatFromSysctl(
-      sysctl(mib, arraysize(mib), &value, &size, nullptr, 0), &value, &size);
+      sysctl(mib, base::size(mib), &value, &size, nullptr, 0), &value, &size);
 
   std::string open_files = CountOpenFileDescriptors();
 
diff --git a/handler/minidump_to_upload_parameters.cc b/handler/minidump_to_upload_parameters.cc
index 03c6fe1..6b64081 100644
--- a/handler/minidump_to_upload_parameters.cc
+++ b/handler/minidump_to_upload_parameters.cc
@@ -17,6 +17,7 @@
 #include "base/logging.h"
 #include "client/annotation.h"
 #include "snapshot/module_snapshot.h"
+#include "snapshot/process_snapshot.h"
 #include "util/stdlib/map_insert.h"
 
 namespace crashpad {
@@ -49,7 +50,7 @@
       }
     }
 
-    for (std::string annotation : module->AnnotationsVector()) {
+    for (const std::string& annotation : module->AnnotationsVector()) {
       list_annotations.append(annotation);
       list_annotations.append("\n");
     }
diff --git a/handler/minidump_to_upload_parameters.h b/handler/minidump_to_upload_parameters.h
index 41056f7..94d396f 100644
--- a/handler/minidump_to_upload_parameters.h
+++ b/handler/minidump_to_upload_parameters.h
@@ -18,10 +18,10 @@
 #include <map>
 #include <string>
 
-#include "snapshot/process_snapshot.h"
-
 namespace crashpad {
 
+class ProcessSnapshot;
+
 //! \brief Given a ProcessSnapshot, returns a map of key-value pairs to use as
 //!     HTTP form parameters for upload to a Breakpad crash report colleciton
 //!     server.
diff --git a/handler/win/.gitattributes b/handler/win/.gitattributes
new file mode 100644
index 0000000..6495e51
--- /dev/null
+++ b/handler/win/.gitattributes
@@ -0,0 +1,21 @@
+# Copyright 2019 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.
+
+# This should be a .cc file, which would allow its attributes to be controlled
+# by the *.cc pattern in the root .gitattributes file, but it’s named with a
+# .cpp extension instead. This file needs to be built with VC++6, a vintage 1998
+# compiler, which might not understand .cc to mean C++.  Rather than setting
+# attributes globally for .cpp files, which are undesirable (.cc should be used
+# in its place), provide a file-specific mapping here.
+/z7_test.cpp text eol=lf
diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h
index c2781de..566f047 100644
--- a/handler/win/crash_report_exception_handler.h
+++ b/handler/win/crash_report_exception_handler.h
@@ -31,7 +31,8 @@
 
 //! \brief An exception handler that writes crash reports for exception messages
 //!     to a CrashReportDatabase.
-class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
+class CrashReportExceptionHandler final
+    : public ExceptionHandlerServer::Delegate {
  public:
   //! \brief Creates a new object that will store crash reports in \a database.
   //!
diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc
index a4f4797..9c8adbb 100644
--- a/handler/win/crashy_test_program.cc
+++ b/handler/win/crashy_test_program.cc
@@ -26,7 +26,7 @@
 
 #include "base/files/file_path.h"
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "client/crashpad_client.h"
 #include "client/crashpad_info.h"
@@ -83,11 +83,11 @@
   // All of these allocations are leaked, we want to view them in windbg via
   // !vprot.
   void* reserve = VirtualAlloc(
-      nullptr, arraysize(kPageTypes) * kPageSize, MEM_RESERVE, PAGE_READWRITE);
+      nullptr, base::size(kPageTypes) * kPageSize, MEM_RESERVE, PAGE_READWRITE);
   PCHECK(reserve) << "VirtualAlloc MEM_RESERVE";
   uintptr_t reserve_as_int = reinterpret_cast<uintptr_t>(reserve);
 
-  for (size_t i = 0; i < arraysize(kPageTypes); ++i) {
+  for (size_t i = 0; i < base::size(kPageTypes); ++i) {
     void* result =
         VirtualAlloc(reinterpret_cast<void*>(reserve_as_int + (kPageSize * i)),
                      kPageSize,
@@ -101,7 +101,7 @@
   return 0;
 }
 
-// Creates a suspended background thread, and sets EDI/RDI to point at
+// Creates a suspended background thread, and sets EDI/RDI/X17 to point at
 // g_test_memory so we can confirm it's available in the minidump.
 bool CreateThreadWithRegisterPointingToTestMemory() {
   HANDLE thread = CreateThread(
@@ -121,6 +121,8 @@
   context.Rdi = reinterpret_cast<DWORD64>(g_test_memory);
 #elif defined(ARCH_CPU_X86)
   context.Edi = reinterpret_cast<DWORD>(g_test_memory);
+#elif defined(ARCH_CPU_ARM64)
+  context.X17 = reinterpret_cast<DWORD64>(g_test_memory);
 #endif
   if (!SetThreadContext(thread, &context)) {
     PLOG(ERROR) << "SetThreadContext";
diff --git a/handler/win/hanging_program.cc b/handler/win/hanging_program.cc
index 840c512..f4e5b98 100644
--- a/handler/win/hanging_program.cc
+++ b/handler/win/hanging_program.cc
@@ -17,7 +17,7 @@
 
 #include "base/debug/alias.h"
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "client/crashpad_client.h"
@@ -123,8 +123,8 @@
   fflush(stdout);
 
   // This is not expected to return.
-  DWORD count =
-      WaitForMultipleObjects(arraysize(threads), threads, true, INFINITE);
+  DWORD count = WaitForMultipleObjects(
+      static_cast<DWORD>(base::size(threads)), threads, true, INFINITE);
   if (count == WAIT_FAILED) {
     PLOG(ERROR) << "WaitForMultipleObjects";
   } else {
diff --git a/infra/config/PRESUBMIT.py b/infra/config/PRESUBMIT.py
new file mode 100644
index 0000000..ca04433
--- /dev/null
+++ b/infra/config/PRESUBMIT.py
@@ -0,0 +1,23 @@
+# 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.
+
+
+def CheckChangeOnUpload(input_api, output_api):
+    return input_api.canned_checks.CheckChangedLUCIConfigs(
+        input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+    return input_api.canned_checks.CheckChangedLUCIConfigs(
+        input_api, output_api)
diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg
deleted file mode 100644
index 1fc3324..0000000
--- a/infra/config/cq.cfg
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2017 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.
-
-# See https://luci-config.appspot.com/schemas/projects/refs:cq.cfg for the
-# documentation of this file format.
-
-version: 1
-cq_name: "crashpad"
-cq_status_url: "https://chromium-cq-status.appspot.com"
-
-# This is required for gerrit projects.
-git_repo_url: "https://chromium.googlesource.com/crashpad/crashpad"
-
-# Crashpad uses gerrit with no special options.
-gerrit {}
-
-verifiers {
-  gerrit_cq_ability {
-    committer_list: "project-crashpad-tryjob-access"
-    dry_run_access_list: "project-crashpad-tryjob-access"
-  }
-  try_job {
-    buckets {
-      name: "master.client.crashpad"
-      builders { name: "crashpad_try_win_dbg" }
-      builders { name: "crashpad_try_win_rel" }
-      # https://crbug.com/743139 - disabled until we can move these to swarming,
-      # at which point we can just remove them.
-      #builders { name: "crashpad_try_win_x86_dbg" }
-      #builders { name: "crashpad_try_win_x86_rel" }
-    }
-    buckets {
-      name: "luci.crashpad.try"
-      builders { name: "crashpad_try_mac_dbg" }
-      builders { name: "crashpad_try_mac_rel" }
-      builders { name: "crashpad_try_win_dbg" experiment_percentage: 100 }
-      builders { name: "crashpad_try_win_rel" experiment_percentage: 100 }
-      builders { name: "crashpad_try_linux_dbg" }
-      builders { name: "crashpad_try_linux_rel" }
-      builders { name: "crashpad_try_fuchsia_arm64_dbg" }
-      builders { name: "crashpad_try_fuchsia_arm64_rel" }
-      builders { name: "crashpad_try_fuchsia_x64_dbg" }
-      builders { name: "crashpad_try_fuchsia_x64_rel" }
-      # https://crbug.com/743139 - disabled until we can move these to swarming,
-      # at which point we can just remove them.
-      #builders { name: "crashpad_try_win_x86_dbg" }
-      #builders { name: "crashpad_try_win_x86_rel" }
-    }
-  }
-}
diff --git a/minidump/BUILD.gn b/minidump/BUILD.gn
index 88a1081..4442585 100644
--- a/minidump/BUILD.gn
+++ b/minidump/BUILD.gn
@@ -20,15 +20,12 @@
     "minidump_annotation_writer.h",
     "minidump_byte_array_writer.cc",
     "minidump_byte_array_writer.h",
-    "minidump_context.h",
     "minidump_context_writer.cc",
     "minidump_context_writer.h",
     "minidump_crashpad_info_writer.cc",
     "minidump_crashpad_info_writer.h",
     "minidump_exception_writer.cc",
     "minidump_exception_writer.h",
-    "minidump_extensions.cc",
-    "minidump_extensions.h",
     "minidump_file_writer.cc",
     "minidump_file_writer.h",
     "minidump_handle_writer.cc",
@@ -72,6 +69,7 @@
   public_configs = [ "..:crashpad_config" ]
 
   public_deps = [
+    ":format",
     "../compat",
   ]
 
@@ -89,6 +87,25 @@
   }
 }
 
+# :format is the only part of minidump that snapshot may depend on.
+static_library("format") {
+  sources = [
+    "minidump_context.h",
+    "minidump_extensions.cc",
+    "minidump_extensions.h",
+  ]
+
+  public_configs = [ "..:crashpad_config" ]
+
+  public_deps = [ "../compat" ]
+
+  deps = [
+    "../snapshot:context",
+    "../third_party/mini_chromium:base",
+    "../util",
+  ]
+}
+
 static_library("test_support") {
   testonly = true
 
@@ -113,13 +130,14 @@
 
   public_configs = [ "..:crashpad_config" ]
 
-  public_deps = [
-    ":minidump",
-  ]
+  public_deps = [ ":minidump" ]
 
   deps = [
+    "../snapshot:test_support",
+    "../test",
     "../third_party/gtest:gtest",
     "../third_party/mini_chromium:base",
+    "../util",
   ]
 
   if (crashpad_is_win) {
@@ -154,6 +172,8 @@
     "minidump_writable_test.cc",
   ]
 
+  configs += [ "../build:crashpad_is_in_fuchsia" ]
+
   deps = [
     ":test_support",
     "../snapshot:test_support",
@@ -166,4 +186,6 @@
   if (crashpad_is_win) {
     cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
   }
+
+  configs += [ "..:disable_ubsan" ]
 }
diff --git a/minidump/minidump_annotation_writer_test.cc b/minidump/minidump_annotation_writer_test.cc
index 1641d1d..dc05cc6 100644
--- a/minidump/minidump_annotation_writer_test.cc
+++ b/minidump/minidump_annotation_writer_test.cc
@@ -16,7 +16,7 @@
 
 #include <memory>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_extensions.h"
 #include "minidump/test/minidump_byte_array_writer_test_util.h"
@@ -107,7 +107,7 @@
 
   MinidumpAnnotationListWriter list_writer;
 
-  for (size_t i = 0; i < arraysize(kNames); ++i) {
+  for (size_t i = 0; i < base::size(kNames); ++i) {
     auto annotation = std::make_unique<MinidumpAnnotationWriter>();
     annotation->InitializeWithData(kNames[i], kTypes[i], kValues[i]);
     list_writer.AddObject(std::move(annotation));
diff --git a/minidump/minidump_byte_array_writer_test.cc b/minidump/minidump_byte_array_writer_test.cc
index f20ad35..e4cd526 100644
--- a/minidump/minidump_byte_array_writer_test.cc
+++ b/minidump/minidump_byte_array_writer_test.cc
@@ -17,6 +17,7 @@
 #include <memory>
 
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "minidump/test/minidump_writable_test_util.h"
@@ -34,7 +35,7 @@
       {},
   };
 
-  for (size_t i = 0; i < arraysize(kTests); ++i) {
+  for (size_t i = 0; i < base::size(kTests); ++i) {
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS, i));
 
     StringFile string_file;
@@ -66,7 +67,7 @@
     {},
   };
 
-  for (size_t i = 0; i < arraysize(kTests); ++i) {
+  for (size_t i = 0; i < base::size(kTests); ++i) {
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS, i));
 
     crashpad::MinidumpByteArrayWriter writer;
diff --git a/minidump/minidump_context.h b/minidump/minidump_context.h
index 6f51b5a..3a3e603 100644
--- a/minidump/minidump_context.h
+++ b/minidump/minidump_context.h
@@ -389,11 +389,16 @@
 //! \brief 64-bit ARM-specifc flags for MinidumpContextARM64::context_flags.
 enum MinidumpContextARM64Flags : uint32_t {
   //! \brief Identifies the context structure as 64-bit ARM.
-  kMinidumpContextARM64 = 0x80000000,
+  kMinidumpContextARM64 = 0x00400000,
+
+  //! \brief Indicates the validity of control registers.
+  //!
+  //! Registers `fp`, `lr`, `sp`, `pc`, and `cpsr`.
+  kMinidumpContextARM64Control = kMinidumpContextARM64 | 0x00000001,
 
   //! \brief Indicates the validty of integer registers.
   //!
-  //! Registers `x0`-`x31`, `pc`, and `cpsr`.
+  //! Registers `x0`-`x28`.
   kMinidumpContextARM64Integer = kMinidumpContextARM64 | 0x00000002,
 
   //! \brief Indicates the validity of fpsimd registers.
@@ -401,17 +406,37 @@
   //! Registers `v0`-`v31`, `fpsr`, and `fpcr` are valid.
   kMinidumpContextARM64Fpsimd = kMinidumpContextARM64 | 0x00000004,
 
+  //! \brief Indicates the validity of debug registers.
+  //!
+  //! `bcr`, `bvr`, `wcr`, and `wvr` are valid.
+  kMinidumpContextARM64Debug = kMinidumpContextARM64 | 0x00000008,
+
+  //! \brief Indicates the validity of control, integer and floating point
+  //!     registers.
+  kMinidumpContextARM64Full = kMinidumpContextARM64Control |
+                              kMinidumpContextARM64Integer |
+                              kMinidumpContextARM64Fpsimd,
+
   //! \brief Indicates the validity of all registers.
   kMinidumpContextARM64All =
-      kMinidumpContextARM64Integer | kMinidumpContextARM64Fpsimd,
+      kMinidumpContextARM64Full | kMinidumpContextARM64Debug,
 };
 
 //! \brief A 64-bit ARM CPU context (register state) carried in a minidump file.
 struct MinidumpContextARM64 {
-  uint64_t context_flags;
+  uint32_t context_flags;
 
-  //! \brief General-purpose registers `x0`-`x30`.
-  uint64_t regs[31];
+  //! \brief Current program status register.
+  uint32_t cpsr;
+
+  //! \brief General-purpose registers `x0`-`x28`.
+  uint64_t regs[29];
+
+  //! \brief Frame pointer or `x29`.
+  uint64_t fp;
+
+  //! \brief Link register or `x30`.
+  uint64_t lr;
 
   //! \brief Stack pointer or `x31`.
   uint64_t sp;
@@ -419,17 +444,20 @@
   //! \brief Program counter.
   uint64_t pc;
 
-  //! \brief Current program status register.
-  uint32_t cpsr;
-
-  //! \brief Floating-point status register.
-  uint32_t fpsr;
+  //! \brief NEON registers `v0`-`v31`.
+  uint128_struct fpsimd[32];
 
   //! \brief Floating-point control register.
   uint32_t fpcr;
 
-  //! \brief NEON registers `v0`-`v31`.
-  uint128_struct fpsimd[32];
+  //! \brief Floating-point status register.
+  uint32_t fpsr;
+
+  //! \brief Debug registers.
+  uint32_t bcr[8];
+  uint64_t bvr[8];
+  uint32_t wcr[2];
+  uint64_t wvr[2];
 };
 
 //! \brief 32bit MIPS-specifc flags for MinidumpContextMIPS::context_flags.
diff --git a/minidump/minidump_context_writer.cc b/minidump/minidump_context_writer.cc
index 20adbf3..d7e53a4 100644
--- a/minidump/minidump_context_writer.cc
+++ b/minidump/minidump_context_writer.cc
@@ -321,26 +321,29 @@
   DCHECK_EQ(state(), kStateMutable);
   DCHECK_EQ(context_.context_flags, kMinidumpContextARM64);
 
-  context_.context_flags = kMinidumpContextARM64All;
+  context_.context_flags = kMinidumpContextARM64Full;
 
-  static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs),
-                "GPRs size mismatch");
+  static_assert(
+      sizeof(context_.regs) == sizeof(context_snapshot->regs) -
+                                   2 * sizeof(context_snapshot->regs[0]),
+      "GPRs size mismatch");
   memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs));
+  context_.fp = context_snapshot->regs[29];
+  context_.lr = context_snapshot->regs[30];
   context_.sp = context_snapshot->sp;
   context_.pc = context_snapshot->pc;
+  context_.cpsr = context_snapshot->spsr;
 
-  if (context_snapshot->pstate >
-      std::numeric_limits<decltype(context_.cpsr)>::max()) {
-    LOG(WARNING) << "pstate truncation";
-  }
-  context_.cpsr =
-      static_cast<decltype(context_.cpsr)>(context_snapshot->pstate);
-
-  context_.fpsr = context_snapshot->fpsr;
-  context_.fpcr = context_snapshot->fpcr;
   static_assert(sizeof(context_.fpsimd) == sizeof(context_snapshot->fpsimd),
                 "FPSIMD size mismatch");
   memcpy(context_.fpsimd, context_snapshot->fpsimd, sizeof(context_.fpsimd));
+  context_.fpcr = context_snapshot->fpcr;
+  context_.fpsr = context_snapshot->fpsr;
+
+  memset(context_.bcr, 0, sizeof(context_.bcr));
+  memset(context_.bvr, 0, sizeof(context_.bvr));
+  memset(context_.wcr, 0, sizeof(context_.wcr));
+  memset(context_.wvr, 0, sizeof(context_.wvr));
 }
 
 bool MinidumpContextARM64Writer::WriteObject(FileWriterInterface* file_writer) {
diff --git a/minidump/minidump_exception_writer.cc b/minidump/minidump_exception_writer.cc
index d870de3..b0e1e62 100644
--- a/minidump/minidump_exception_writer.cc
+++ b/minidump/minidump_exception_writer.cc
@@ -21,7 +21,7 @@
 #include "minidump/minidump_context_writer.h"
 #include "snapshot/exception_snapshot.h"
 #include "util/file/file_writer.h"
-#include "util/misc/arraysize_unsafe.h"
+#include "util/misc/arraysize.h"
 
 namespace crashpad {
 
@@ -65,7 +65,7 @@
 
   const size_t parameters = exception_information.size();
   constexpr size_t kMaxParameters =
-      ARRAYSIZE_UNSAFE(exception_.ExceptionRecord.ExceptionInformation);
+      ArraySize(exception_.ExceptionRecord.ExceptionInformation);
   CHECK_LE(parameters, kMaxParameters);
 
   exception_.ExceptionRecord.NumberParameters =
diff --git a/minidump/minidump_exception_writer_test.cc b/minidump/minidump_exception_writer_test.cc
index e4dc5fa..75adbee 100644
--- a/minidump/minidump_exception_writer_test.cc
+++ b/minidump/minidump_exception_writer_test.cc
@@ -17,6 +17,7 @@
 #include <string>
 #include <utility>
 
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_context.h"
 #include "minidump/minidump_context_writer.h"
@@ -80,7 +81,7 @@
             expected->ExceptionRecord.NumberParameters);
   EXPECT_EQ(observed->ExceptionRecord.__unusedAlignment, 0u);
   for (size_t index = 0;
-       index < arraysize(observed->ExceptionRecord.ExceptionInformation);
+       index < base::size(observed->ExceptionRecord.ExceptionInformation);
        ++index) {
     EXPECT_EQ(observed->ExceptionRecord.ExceptionInformation[index],
               expected->ExceptionRecord.ExceptionInformation[index]);
diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h
index f3701dc..97276d5 100644
--- a/minidump/minidump_extensions.h
+++ b/minidump/minidump_extensions.h
@@ -25,14 +25,15 @@
 #include "util/misc/pdb_structures.h"
 #include "util/misc/uuid.h"
 
+#if defined(COMPILER_MSVC)
 // C4200 is "nonstandard extension used : zero-sized array in struct/union".
 // We would like to globally disable this warning, but unfortunately, the
 // compiler is buggy and only supports disabling it with a pragma, so we can't
-// disable it with other silly warnings in build/common.gypi. See:
+// disable it with other silly warnings in the build files. See:
 //   https://connect.microsoft.com/VisualStudio/feedback/details/1114440
-MSVC_PUSH_DISABLE_WARNING(4200);
+#pragma warning(push)
+#pragma warning(disable: 4200)
 
-#if defined(COMPILER_MSVC)
 #define PACKED
 #pragma pack(push, 1)
 #else
@@ -92,10 +93,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
@@ -233,7 +242,7 @@
   kMinidumpOSMacOSX = 0x8101,
 
   //! \brief iOS, Darwin for mobile devices.
-  kMinidumpOSiOS = 0x8102,
+  kMinidumpOSIOS = 0x8102,
 
   //! \brief Linux, not including Android.
   kMinidumpOSLinux = 0x8201,
@@ -255,7 +264,6 @@
   kMinidumpOSUnknown = 0xffffffff,
 };
 
-
 //! \brief A list of ::RVA pointers.
 struct ALIGNAS(4) PACKED MinidumpRVAList {
   //! \brief The number of children present in the #children array.
@@ -490,11 +498,10 @@
 
 #if defined(COMPILER_MSVC)
 #pragma pack(pop)
+#pragma warning(pop)  // C4200
 #endif  // COMPILER_MSVC
 #undef PACKED
 
-MSVC_POP_WARNING();  // C4200
-
 }  // namespace crashpad
 
 #endif  // CRASHPAD_MINIDUMP_MINIDUMP_EXTENSIONS_H_
diff --git a/minidump/minidump_file_writer.cc b/minidump/minidump_file_writer.cc
index 7226545..6727a0d 100644
--- a/minidump/minidump_file_writer.cc
+++ b/minidump/minidump_file_writer.cc
@@ -211,17 +211,30 @@
 }
 
 bool MinidumpFileWriter::WriteEverything(FileWriterInterface* file_writer) {
+  return WriteMinidump(file_writer, true);
+}
+
+bool MinidumpFileWriter::WriteMinidump(FileWriterInterface* file_writer,
+                                       bool allow_seek) {
   DCHECK_EQ(state(), kStateMutable);
 
-  FileOffset start_offset = file_writer->Seek(0, SEEK_CUR);
-  if (start_offset < 0) {
-    return false;
+  FileOffset start_offset = -1;
+  if (allow_seek) {
+    start_offset = file_writer->Seek(0, SEEK_CUR);
+    if (start_offset < 0) {
+      return false;
+    }
+  } else {
+    header_.Signature = MINIDUMP_SIGNATURE;
   }
 
   if (!MinidumpWritable::WriteEverything(file_writer)) {
     return false;
   }
 
+  if (!allow_seek)
+    return true;
+
   FileOffset end_offset = file_writer->Seek(0, SEEK_CUR);
   if (end_offset < 0) {
     return false;
@@ -232,7 +245,7 @@
   // it as a valid minidump file.
   header_.Signature = MINIDUMP_SIGNATURE;
 
-  if (file_writer->Seek(start_offset, SEEK_SET) != 0) {
+  if (file_writer->Seek(start_offset, SEEK_SET) < 0) {
     return false;
   }
 
diff --git a/minidump/minidump_file_writer.h b/minidump/minidump_file_writer.h
index ce2f1d7..0a1b064 100644
--- a/minidump/minidump_file_writer.h
+++ b/minidump/minidump_file_writer.h
@@ -134,6 +134,21 @@
   //! mistaken for valid ones.
   bool WriteEverything(FileWriterInterface* file_writer) override;
 
+  //! \brief Writes this object to a minidump file.
+  //!
+  //! Same as \a WriteEverything, but give the option to disable the seek. It
+  //! is typically used to write to stream backed \a FileWriterInterface which
+  //! doesn't support seek.
+  //!
+  //! \param[in] file_writer The file writer to receive the minidump file’s
+  //!     content.
+  //!
+  //! \param[in] allow_seek Whether seek is allowed.
+  //!
+  //! \return `true` on success. `false` on failure, with an appropriate message
+  //!     logged.
+  bool WriteMinidump(FileWriterInterface* file_writer, bool allow_seek);
+
  protected:
   // MinidumpWritable:
   bool Freeze() override;
diff --git a/minidump/minidump_file_writer_test.cc b/minidump/minidump_file_writer_test.cc
index 730da26..19eaae4 100644
--- a/minidump/minidump_file_writer_test.cc
+++ b/minidump/minidump_file_writer_test.cc
@@ -20,7 +20,8 @@
 #include <string>
 #include <utility>
 
-#include "base/compiler_specific.h"
+#include "base/stl_util.h"
+#include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_stream_writer.h"
 #include "minidump/minidump_user_extension_stream_data_source.h"
@@ -35,7 +36,9 @@
 #include "snapshot/test/test_system_snapshot.h"
 #include "snapshot/test/test_thread_snapshot.h"
 #include "test/gtest_death.h"
+#include "util/file/output_stream_file_writer.h"
 #include "util/file/string_file.h"
+#include "util/stream/output_stream_interface.h"
 
 namespace crashpad {
 namespace test {
@@ -87,6 +90,22 @@
   DISALLOW_COPY_AND_ASSIGN(TestStream);
 };
 
+class StringFileOutputStream : public OutputStreamInterface {
+ public:
+  StringFileOutputStream() = default;
+  ~StringFileOutputStream() override = default;
+  bool Write(const uint8_t* data, size_t size) override {
+    return string_file_.Write(data, size);
+  }
+  bool Flush() override { return true; }
+  const StringFile& string_file() const { return string_file_; }
+
+ private:
+  StringFile string_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(StringFileOutputStream);
+};
+
 TEST(MinidumpFileWriter, OneStream) {
   MinidumpFileWriter minidump_file;
   constexpr time_t kTimestamp = 0x155d2fb8;
@@ -134,7 +153,7 @@
   minidump_file.SetTimestamp(kTimestamp);
 
   static constexpr uint8_t kStreamData[] = "Hello World!";
-  constexpr size_t kStreamSize = arraysize(kStreamData);
+  constexpr size_t kStreamSize = base::size(kStreamData);
   constexpr MinidumpStreamType kStreamType =
       static_cast<MinidumpStreamType>(0x4d);
 
@@ -392,21 +411,16 @@
 }
 
 TEST(MinidumpFileWriter, InitializeFromSnapshot_Exception) {
-  // In a 32-bit environment, this will give a “timestamp out of range” warning,
+  // In a 32-bit environment, this will give a “timestamp out of range” warning,
   // but the test should complete without failure.
   constexpr uint32_t kSnapshotTime = 0xfd469ab8;
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconstant-conversion"
-#define DISABLED_WCONSTANT_CONVERSION
-#endif  // __clang__
-  MSVC_SUPPRESS_WARNING(4309);  // Truncation of constant value.
-  MSVC_SUPPRESS_WARNING(4838);  // Narrowing conversion.
-  constexpr timeval kSnapshotTimeval = {static_cast<time_t>(kSnapshotTime), 0};
-#if defined(DISABLED_WCONSTANT_CONVERSION)
-#pragma clang diagnostic pop
-#undef DISABLED_WCONSTANT_CONVERSION
-#endif  // DISABLED_WCONSTANT_CONVERSION
+  constexpr timeval kSnapshotTimeval = {
+#ifdef OS_WIN
+      static_cast<long>(kSnapshotTime),
+#else
+      static_cast<time_t>(kSnapshotTime),
+#endif
+      0};
 
   TestProcessSnapshot process_snapshot;
   process_snapshot.SetSnapshotTime(kSnapshotTimeval);
@@ -582,6 +596,51 @@
   EXPECT_EQ(memcmp(stream_data, expected_stream.c_str(), kStream0Size), 0);
 }
 
+TEST(MinidumpFileWriter, WriteMinidumpDisallowSeek) {
+  MinidumpFileWriter minidump_file;
+  constexpr time_t kTimestamp = 0x155d2fb8;
+  minidump_file.SetTimestamp(kTimestamp);
+
+  constexpr size_t kStreamSize = 5;
+  constexpr MinidumpStreamType kStreamType =
+      static_cast<MinidumpStreamType>(0x4d);
+  constexpr uint8_t kStreamValue = 0x5a;
+  auto stream =
+      std::make_unique<TestStream>(kStreamType, kStreamSize, kStreamValue);
+  ASSERT_TRUE(minidump_file.AddStream(std::move(stream)));
+
+  std::unique_ptr<StringFileOutputStream> string_file_output_stream =
+      std::make_unique<StringFileOutputStream>();
+  const StringFile& string_file = string_file_output_stream->string_file();
+  OutputStreamFileWriter output_stream(std::move(string_file_output_stream));
+  ASSERT_TRUE(minidump_file.WriteMinidump(&output_stream, false));
+  ASSERT_TRUE(output_stream.Flush());
+
+  constexpr size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER);
+  constexpr size_t kStreamOffset =
+      kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY);
+  constexpr size_t kFileSize = kStreamOffset + kStreamSize;
+
+  ASSERT_EQ(string_file.string().size(), kFileSize);
+
+  const MINIDUMP_DIRECTORY* directory;
+  const MINIDUMP_HEADER* header =
+      MinidumpHeaderAtStart(string_file.string(), &directory);
+  ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, 1, kTimestamp));
+  ASSERT_TRUE(directory);
+
+  EXPECT_EQ(directory[0].StreamType, kStreamType);
+  EXPECT_EQ(directory[0].Location.DataSize, kStreamSize);
+  EXPECT_EQ(directory[0].Location.Rva, kStreamOffset);
+
+  const uint8_t* stream_data = MinidumpWritableAtLocationDescriptor<uint8_t>(
+      string_file.string(), directory[0].Location);
+  ASSERT_TRUE(stream_data);
+
+  std::string expected_stream(kStreamSize, kStreamValue);
+  EXPECT_EQ(memcmp(stream_data, expected_stream.c_str(), kStreamSize), 0);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/minidump/minidump_memory_writer_test.cc b/minidump/minidump_memory_writer_test.cc
index 60b7fa8..90287cb 100644
--- a/minidump/minidump_memory_writer_test.cc
+++ b/minidump/minidump_memory_writer_test.cc
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_extensions.h"
@@ -340,7 +341,7 @@
 
 TEST(MinidumpMemoryWriter, AddFromSnapshot) {
   MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[3] = {};
-  uint8_t values[arraysize(expect_memory_descriptors)] = {};
+  uint8_t values[base::size(expect_memory_descriptors)] = {};
 
   expect_memory_descriptors[0].StartOfMemoryRange = 0;
   expect_memory_descriptors[0].Memory.DataSize = 0x1000;
@@ -356,8 +357,7 @@
 
   std::vector<std::unique_ptr<TestMemorySnapshot>> memory_snapshots_owner;
   std::vector<const MemorySnapshot*> memory_snapshots;
-  for (size_t index = 0;
-       index < arraysize(expect_memory_descriptors);
+  for (size_t index = 0; index < base::size(expect_memory_descriptors);
        ++index) {
     memory_snapshots_owner.push_back(std::make_unique<TestMemorySnapshot>());
     TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get();
@@ -396,7 +396,7 @@
 
 TEST(MinidumpMemoryWriter, CoalesceExplicitMultiple) {
   MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[4] = {};
-  uint8_t values[arraysize(expect_memory_descriptors)] = {};
+  uint8_t values[base::size(expect_memory_descriptors)] = {};
 
   expect_memory_descriptors[0].StartOfMemoryRange = 0;
   expect_memory_descriptors[0].Memory.DataSize = 1000;
diff --git a/minidump/minidump_misc_info_writer.cc b/minidump/minidump_misc_info_writer.cc
index d83ed23..a134076 100644
--- a/minidump/minidump_misc_info_writer.cc
+++ b/minidump/minidump_misc_info_writer.cc
@@ -18,6 +18,7 @@
 
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -26,7 +27,6 @@
 #include "snapshot/process_snapshot.h"
 #include "snapshot/system_snapshot.h"
 #include "util/file/file_writer.h"
-#include "util/misc/arraysize_unsafe.h"
 #include "util/numeric/in_range_cast.h"
 #include "util/numeric/safe_assignment.h"
 
@@ -302,7 +302,7 @@
 
   internal::MinidumpWriterUtil::AssignUTF8ToUTF16(
       misc_info_.TimeZone.StandardName,
-      ARRAYSIZE_UNSAFE(misc_info_.TimeZone.StandardName),
+      base::size(misc_info_.TimeZone.StandardName),
       standard_name);
 
   misc_info_.TimeZone.StandardDate = standard_date;
@@ -310,7 +310,7 @@
 
   internal::MinidumpWriterUtil::AssignUTF8ToUTF16(
       misc_info_.TimeZone.DaylightName,
-      ARRAYSIZE_UNSAFE(misc_info_.TimeZone.DaylightName),
+      base::size(misc_info_.TimeZone.DaylightName),
       daylight_name);
 
   misc_info_.TimeZone.DaylightDate = daylight_date;
@@ -327,12 +327,10 @@
   misc_info_.Flags1 |= MINIDUMP_MISC4_BUILDSTRING;
 
   internal::MinidumpWriterUtil::AssignUTF8ToUTF16(
-      misc_info_.BuildString,
-      ARRAYSIZE_UNSAFE(misc_info_.BuildString),
-      build_string);
+      misc_info_.BuildString, base::size(misc_info_.BuildString), build_string);
   internal::MinidumpWriterUtil::AssignUTF8ToUTF16(
       misc_info_.DbgBldStr,
-      ARRAYSIZE_UNSAFE(misc_info_.DbgBldStr),
+      base::size(misc_info_.DbgBldStr),
       debug_build_string);
 }
 
diff --git a/minidump/minidump_misc_info_writer_test.cc b/minidump/minidump_misc_info_writer_test.cc
index 3c60ebc..bd92733 100644
--- a/minidump/minidump_misc_info_writer_test.cc
+++ b/minidump/minidump_misc_info_writer_test.cc
@@ -21,6 +21,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -31,7 +32,6 @@
 #include "snapshot/test/test_process_snapshot.h"
 #include "snapshot/test/test_system_snapshot.h"
 #include "util/file/string_file.h"
-#include "util/misc/arraysize_unsafe.h"
 #include "util/stdlib/strlcpy.h"
 
 namespace crashpad {
@@ -127,7 +127,7 @@
     SCOPED_TRACE("Standard");
     ExpectNULPaddedString16Equal(expected->TimeZone.StandardName,
                                  observed->TimeZone.StandardName,
-                                 arraysize(expected->TimeZone.StandardName));
+                                 base::size(expected->TimeZone.StandardName));
     ExpectSystemTimeEqual(&expected->TimeZone.StandardDate,
                           &observed->TimeZone.StandardDate);
     EXPECT_EQ(observed->TimeZone.StandardBias, expected->TimeZone.StandardBias);
@@ -136,7 +136,7 @@
     SCOPED_TRACE("Daylight");
     ExpectNULPaddedString16Equal(expected->TimeZone.DaylightName,
                                  observed->TimeZone.DaylightName,
-                                 arraysize(expected->TimeZone.DaylightName));
+                                 base::size(expected->TimeZone.DaylightName));
     ExpectSystemTimeEqual(&expected->TimeZone.DaylightDate,
                           &observed->TimeZone.DaylightDate);
     EXPECT_EQ(observed->TimeZone.DaylightBias, expected->TimeZone.DaylightBias);
@@ -154,13 +154,13 @@
     SCOPED_TRACE("BuildString");
     ExpectNULPaddedString16Equal(expected->BuildString,
                                  observed->BuildString,
-                                 arraysize(expected->BuildString));
+                                 base::size(expected->BuildString));
   }
   {
     SCOPED_TRACE("DbgBldStr");
     ExpectNULPaddedString16Equal(expected->DbgBldStr,
                                  observed->DbgBldStr,
-                                 arraysize(expected->DbgBldStr));
+                                 base::size(expected->DbgBldStr));
   }
 }
 
@@ -176,7 +176,7 @@
   EXPECT_EQ(observed->XStateData.EnabledFeatures,
             expected->XStateData.EnabledFeatures);
   for (size_t feature_index = 0;
-       feature_index < arraysize(observed->XStateData.Features);
+       feature_index < base::size(observed->XStateData.Features);
        ++feature_index) {
     SCOPED_TRACE(base::StringPrintf("feature_index %" PRIuS, feature_index));
     EXPECT_EQ(observed->XStateData.Features[feature_index].Offset,
@@ -396,7 +396,7 @@
   base::string16 standard_name_utf16 = base::UTF8ToUTF16(kStandardName);
   c16lcpy(expected.TimeZone.StandardName,
           standard_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName));
+          base::size(expected.TimeZone.StandardName));
   memcpy(&expected.TimeZone.StandardDate,
          &kStandardDate,
          sizeof(expected.TimeZone.StandardDate));
@@ -404,7 +404,7 @@
   base::string16 daylight_name_utf16 = base::UTF8ToUTF16(kDaylightName);
   c16lcpy(expected.TimeZone.DaylightName,
           daylight_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName));
+          base::size(expected.TimeZone.DaylightName));
   memcpy(&expected.TimeZone.DaylightDate,
          &kDaylightDate,
          sizeof(expected.TimeZone.DaylightDate));
@@ -424,10 +424,9 @@
   constexpr int32_t kBias = 300;
   MINIDUMP_MISC_INFO_N tmp;
   ALLOW_UNUSED_LOCAL(tmp);
-  std::string standard_name(ARRAYSIZE_UNSAFE(tmp.TimeZone.StandardName) + 1,
-                            's');
+  std::string standard_name(base::size(tmp.TimeZone.StandardName) + 1, 's');
   constexpr int32_t kStandardBias = 0;
-  std::string daylight_name(ARRAYSIZE_UNSAFE(tmp.TimeZone.DaylightName), 'd');
+  std::string daylight_name(base::size(tmp.TimeZone.DaylightName), 'd');
   constexpr int32_t kDaylightBias = -60;
 
   // Test using kSystemTimeZero, because not all platforms will be able to
@@ -458,7 +457,7 @@
   base::string16 standard_name_utf16 = base::UTF8ToUTF16(standard_name);
   c16lcpy(expected.TimeZone.StandardName,
           standard_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName));
+          base::size(expected.TimeZone.StandardName));
   memcpy(&expected.TimeZone.StandardDate,
          &kSystemTimeZero,
          sizeof(expected.TimeZone.StandardDate));
@@ -466,7 +465,7 @@
   base::string16 daylight_name_utf16 = base::UTF8ToUTF16(daylight_name);
   c16lcpy(expected.TimeZone.DaylightName,
           daylight_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName));
+          base::size(expected.TimeZone.DaylightName));
   memcpy(&expected.TimeZone.DaylightDate,
          &kSystemTimeZero,
          sizeof(expected.TimeZone.DaylightDate));
@@ -497,12 +496,12 @@
   base::string16 build_string_utf16 = base::UTF8ToUTF16(kBuildString);
   c16lcpy(expected.BuildString,
           build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.BuildString));
+          base::size(expected.BuildString));
   base::string16 debug_build_string_utf16 =
       base::UTF8ToUTF16(kDebugBuildString);
   c16lcpy(expected.DbgBldStr,
           debug_build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.DbgBldStr));
+          base::size(expected.DbgBldStr));
 
   ExpectMiscInfoEqual(&expected, observed);
 }
@@ -516,8 +515,8 @@
 
   MINIDUMP_MISC_INFO_N tmp;
   ALLOW_UNUSED_LOCAL(tmp);
-  std::string build_string(ARRAYSIZE_UNSAFE(tmp.BuildString) + 1, 'B');
-  std::string debug_build_string(ARRAYSIZE_UNSAFE(tmp.DbgBldStr), 'D');
+  std::string build_string(base::size(tmp.BuildString) + 1, 'B');
+  std::string debug_build_string(base::size(tmp.DbgBldStr), 'D');
 
   misc_info_writer->SetBuildString(build_string, debug_build_string);
 
@@ -534,12 +533,12 @@
   base::string16 build_string_utf16 = base::UTF8ToUTF16(build_string);
   c16lcpy(expected.BuildString,
           build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.BuildString));
+          base::size(expected.BuildString));
   base::string16 debug_build_string_utf16 =
       base::UTF8ToUTF16(debug_build_string);
   c16lcpy(expected.DbgBldStr,
           debug_build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.DbgBldStr));
+          base::size(expected.DbgBldStr));
 
   ExpectMiscInfoEqual(&expected, observed);
 }
@@ -679,7 +678,7 @@
   base::string16 standard_name_utf16 = base::UTF8ToUTF16(kStandardName);
   c16lcpy(expected.TimeZone.StandardName,
           standard_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.StandardName));
+          base::size(expected.TimeZone.StandardName));
   memcpy(&expected.TimeZone.StandardDate,
          &kSystemTimeZero,
          sizeof(expected.TimeZone.StandardDate));
@@ -687,7 +686,7 @@
   base::string16 daylight_name_utf16 = base::UTF8ToUTF16(kDaylightName);
   c16lcpy(expected.TimeZone.DaylightName,
           daylight_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.TimeZone.DaylightName));
+          base::size(expected.TimeZone.DaylightName));
   memcpy(&expected.TimeZone.DaylightDate,
          &kSystemTimeZero,
          sizeof(expected.TimeZone.DaylightDate));
@@ -695,12 +694,12 @@
   base::string16 build_string_utf16 = base::UTF8ToUTF16(kBuildString);
   c16lcpy(expected.BuildString,
           build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.BuildString));
+          base::size(expected.BuildString));
   base::string16 debug_build_string_utf16 =
       base::UTF8ToUTF16(kDebugBuildString);
   c16lcpy(expected.DbgBldStr,
           debug_build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expected.DbgBldStr));
+          base::size(expected.DbgBldStr));
 
   ExpectMiscInfoEqual(&expected, observed);
 }
@@ -744,18 +743,18 @@
   expect_misc_info.TimeZone.Bias = 300;
   c16lcpy(expect_misc_info.TimeZone.StandardName,
           standard_time_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expect_misc_info.TimeZone.StandardName));
+          base::size(expect_misc_info.TimeZone.StandardName));
   expect_misc_info.TimeZone.StandardBias = 0;
   c16lcpy(expect_misc_info.TimeZone.DaylightName,
           daylight_time_name_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expect_misc_info.TimeZone.DaylightName));
+          base::size(expect_misc_info.TimeZone.DaylightName));
   expect_misc_info.TimeZone.DaylightBias = -60;
   c16lcpy(expect_misc_info.BuildString,
           build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expect_misc_info.BuildString));
+          base::size(expect_misc_info.BuildString));
   c16lcpy(expect_misc_info.DbgBldStr,
           debug_build_string_utf16.c_str(),
-          ARRAYSIZE_UNSAFE(expect_misc_info.DbgBldStr));
+          base::size(expect_misc_info.DbgBldStr));
 
   const timeval kStartTime =
       { static_cast<time_t>(expect_misc_info.ProcessCreateTime), 0 };
diff --git a/minidump/minidump_module_crashpad_info_writer_test.cc b/minidump/minidump_module_crashpad_info_writer_test.cc
index 63a9338..ba4ab05 100644
--- a/minidump/minidump_module_crashpad_info_writer_test.cc
+++ b/minidump/minidump_module_crashpad_info_writer_test.cc
@@ -19,6 +19,7 @@
 
 #include <utility>
 
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_annotation_writer.h"
 #include "minidump/minidump_simple_string_dictionary_writer.h"
@@ -154,9 +155,9 @@
                 sizeof(MinidumpSimpleStringDictionaryEntry) +
                 sizeof(MinidumpAnnotationList) + 2 +  // padding
                 sizeof(MinidumpAnnotation) + sizeof(MinidumpUTF8String) +
-                arraysize(kEntry) + 2 +  // padding
-                sizeof(MinidumpUTF8String) + arraysize(kKey) +
-                sizeof(MinidumpUTF8String) + arraysize(kValue) +
+                base::size(kEntry) + 2 +  // padding
+                sizeof(MinidumpUTF8String) + base::size(kKey) +
+                sizeof(MinidumpUTF8String) + base::size(kValue) +
                 sizeof(MinidumpUTF8String) + annotation.name.size() + 1 +
                 sizeof(MinidumpByteArray) + annotation.value.size());
 
diff --git a/minidump/minidump_module_writer.cc b/minidump/minidump_module_writer.cc
index 305d37c..6e53352 100644
--- a/minidump/minidump_module_writer.cc
+++ b/minidump/minidump_module_writer.cc
@@ -31,8 +31,7 @@
 
 namespace crashpad {
 
-MinidumpModuleCodeViewRecordWriter::~MinidumpModuleCodeViewRecordWriter() {
-}
+MinidumpModuleCodeViewRecordWriter::~MinidumpModuleCodeViewRecordWriter() {}
 
 namespace internal {
 
@@ -45,8 +44,7 @@
 
 template <typename CodeViewRecordType>
 MinidumpModuleCodeViewRecordPDBLinkWriter<
-    CodeViewRecordType>::~MinidumpModuleCodeViewRecordPDBLinkWriter() {
-}
+    CodeViewRecordType>::~MinidumpModuleCodeViewRecordPDBLinkWriter() {}
 
 template <typename CodeViewRecordType>
 size_t
@@ -82,8 +80,7 @@
     CodeViewRecordPDB20>;
 
 MinidumpModuleCodeViewRecordPDB20Writer::
-    ~MinidumpModuleCodeViewRecordPDB20Writer() {
-}
+    ~MinidumpModuleCodeViewRecordPDB20Writer() {}
 
 void MinidumpModuleCodeViewRecordPDB20Writer::SetTimestampAndAge(
     time_t timestamp,
@@ -100,8 +97,7 @@
     CodeViewRecordPDB70>;
 
 MinidumpModuleCodeViewRecordPDB70Writer::
-    ~MinidumpModuleCodeViewRecordPDB70Writer() {
-}
+    ~MinidumpModuleCodeViewRecordPDB70Writer() {}
 
 void MinidumpModuleCodeViewRecordPDB70Writer::InitializeFromSnapshot(
     const ModuleSnapshot* module_snapshot) {
@@ -115,15 +111,52 @@
   SetUUIDAndAge(uuid, age);
 }
 
+MinidumpModuleCodeViewRecordBuildIDWriter::
+    MinidumpModuleCodeViewRecordBuildIDWriter()
+    : MinidumpModuleCodeViewRecordWriter(), build_id_() {}
+
+MinidumpModuleCodeViewRecordBuildIDWriter::
+    ~MinidumpModuleCodeViewRecordBuildIDWriter() {}
+
+size_t MinidumpModuleCodeViewRecordBuildIDWriter::SizeOfObject() {
+  DCHECK_GE(state(), kStateFrozen);
+  return offsetof(CodeViewRecordBuildID, build_id) + build_id_.size();
+}
+
+void MinidumpModuleCodeViewRecordBuildIDWriter::SetBuildID(
+    const std::vector<uint8_t>& build_id) {
+  DCHECK_EQ(state(), kStateMutable);
+  build_id_ = build_id;
+}
+
+bool MinidumpModuleCodeViewRecordBuildIDWriter::WriteObject(
+    FileWriterInterface* file_writer) {
+  DCHECK_EQ(state(), kStateWritable);
+
+  CodeViewRecordBuildID cv;
+  cv.signature = CodeViewRecordBuildID::kSignature;
+
+  WritableIoVec iov;
+  iov.iov_base = &cv;
+  iov.iov_len = offsetof(CodeViewRecordBuildID, build_id);
+  std::vector<WritableIoVec> iovecs(1, iov);
+
+  if (!build_id_.empty()) {
+    iov.iov_base = build_id_.data();
+    iov.iov_len = build_id_.size();
+    iovecs.push_back(iov);
+  }
+
+  return file_writer->WriteIoVec(&iovecs);
+}
+
 MinidumpModuleMiscDebugRecordWriter::MinidumpModuleMiscDebugRecordWriter()
     : internal::MinidumpWritable(),
       image_debug_misc_(),
       data_(),
-      data_utf16_() {
-}
+      data_utf16_() {}
 
-MinidumpModuleMiscDebugRecordWriter::~MinidumpModuleMiscDebugRecordWriter() {
-}
+MinidumpModuleMiscDebugRecordWriter::~MinidumpModuleMiscDebugRecordWriter() {}
 
 void MinidumpModuleMiscDebugRecordWriter::SetData(const std::string& data,
                                                   bool utf16) {
@@ -203,8 +236,7 @@
   module_.VersionInfo.dwStrucVersion = VS_FFI_STRUCVERSION;
 }
 
-MinidumpModuleWriter::~MinidumpModuleWriter() {
-}
+MinidumpModuleWriter::~MinidumpModuleWriter() {}
 
 void MinidumpModuleWriter::InitializeFromSnapshot(
     const ModuleSnapshot* module_snapshot) {
@@ -242,9 +274,21 @@
   }
   SetFileTypeAndSubtype(file_type, VFT2_UNKNOWN);
 
-  auto codeview_record =
-      std::make_unique<MinidumpModuleCodeViewRecordPDB70Writer>();
-  codeview_record->InitializeFromSnapshot(module_snapshot);
+  auto build_id = module_snapshot->BuildID();
+
+  std::unique_ptr<MinidumpModuleCodeViewRecordWriter> codeview_record;
+  if (!build_id.empty()) {
+    auto cv_record_build_id =
+        std::make_unique<MinidumpModuleCodeViewRecordBuildIDWriter>();
+    cv_record_build_id->SetBuildID(build_id);
+    codeview_record = std::move(cv_record_build_id);
+  } else {
+    auto cv_record_pdb70 =
+        std::make_unique<MinidumpModuleCodeViewRecordPDB70Writer>();
+    cv_record_pdb70->InitializeFromSnapshot(module_snapshot);
+    codeview_record = std::move(cv_record_pdb70);
+  }
+
   SetCodeViewRecord(std::move(codeview_record));
 }
 
@@ -372,11 +416,9 @@
 }
 
 MinidumpModuleListWriter::MinidumpModuleListWriter()
-    : MinidumpStreamWriter(), modules_(), module_list_base_() {
-}
+    : MinidumpStreamWriter(), modules_(), module_list_base_() {}
 
-MinidumpModuleListWriter::~MinidumpModuleListWriter() {
-}
+MinidumpModuleListWriter::~MinidumpModuleListWriter() {}
 
 void MinidumpModuleListWriter::InitializeFromSnapshot(
     const std::vector<const ModuleSnapshot*>& module_snapshots) {
diff --git a/minidump/minidump_module_writer.h b/minidump/minidump_module_writer.h
index 555c411..b328bf0 100644
--- a/minidump/minidump_module_writer.h
+++ b/minidump/minidump_module_writer.h
@@ -88,7 +88,8 @@
 
 //! \brief The writer for a CodeViewRecordPDB20 object in a minidump file.
 //!
-//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead.
+//! Most users will want MinidumpModuleCodeViewRecordPDB70Writer or
+//! MinidumpModuleCodeViewRecordBuildIDWriter instead.
 class MinidumpModuleCodeViewRecordPDB20Writer final
     : public internal::MinidumpModuleCodeViewRecordPDBLinkWriter<
           CodeViewRecordPDB20> {
@@ -136,6 +137,26 @@
   DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordPDB70Writer);
 };
 
+//! \brief The writer for a CodeViewRecordBuildID object in a minidump file.
+class MinidumpModuleCodeViewRecordBuildIDWriter final
+    : public MinidumpModuleCodeViewRecordWriter {
+ public:
+  MinidumpModuleCodeViewRecordBuildIDWriter();
+  ~MinidumpModuleCodeViewRecordBuildIDWriter() override;
+
+  //! \brief Sets the build ID used for symbol lookup.
+  void SetBuildID(const std::vector<uint8_t>& build_id);
+
+ private:
+  // MinidumpWritable:
+  size_t SizeOfObject() override;
+  bool WriteObject(FileWriterInterface* file_writer) override;
+
+  std::vector<uint8_t> build_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCodeViewRecordBuildIDWriter);
+};
+
 //! \brief The writer for an IMAGE_DEBUG_MISC object in a minidump file.
 //!
 //! Most users will want MinidumpModuleCodeViewRecordPDB70Writer instead.
diff --git a/minidump/minidump_module_writer_test.cc b/minidump/minidump_module_writer_test.cc
index 0fddfe8..71db3ac 100644
--- a/minidump/minidump_module_writer_test.cc
+++ b/minidump/minidump_module_writer_test.cc
@@ -20,6 +20,7 @@
 #include <utility>
 
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
@@ -264,6 +265,61 @@
                                                          expected_debug_utf16));
 }
 
+// ExpectModuleWithBuildIDCv() is like ExpectModule( but expects the module to
+// have a BuildID CodeView Record.
+void ExpectModuleWithBuildIDCv(const MINIDUMP_MODULE* expected,
+                               const MINIDUMP_MODULE* observed,
+                               const std::string& file_contents,
+                               const std::string& expected_module_name,
+                               const std::vector<uint8_t>& expected_build_id) {
+  EXPECT_EQ(observed->BaseOfImage, expected->BaseOfImage);
+  EXPECT_EQ(observed->SizeOfImage, expected->SizeOfImage);
+  EXPECT_EQ(observed->CheckSum, expected->CheckSum);
+  EXPECT_EQ(observed->TimeDateStamp, expected->TimeDateStamp);
+  EXPECT_EQ(observed->VersionInfo.dwSignature,
+            implicit_cast<uint32_t>(VS_FFI_SIGNATURE));
+  EXPECT_EQ(observed->VersionInfo.dwStrucVersion,
+            implicit_cast<uint32_t>(VS_FFI_STRUCVERSION));
+  EXPECT_EQ(observed->VersionInfo.dwFileVersionMS,
+            expected->VersionInfo.dwFileVersionMS);
+  EXPECT_EQ(observed->VersionInfo.dwFileVersionLS,
+            expected->VersionInfo.dwFileVersionLS);
+  EXPECT_EQ(observed->VersionInfo.dwProductVersionMS,
+            expected->VersionInfo.dwProductVersionMS);
+  EXPECT_EQ(observed->VersionInfo.dwProductVersionLS,
+            expected->VersionInfo.dwProductVersionLS);
+  EXPECT_EQ(observed->VersionInfo.dwFileFlagsMask,
+            expected->VersionInfo.dwFileFlagsMask);
+  EXPECT_EQ(observed->VersionInfo.dwFileFlags,
+            expected->VersionInfo.dwFileFlags);
+  EXPECT_EQ(observed->VersionInfo.dwFileOS, expected->VersionInfo.dwFileOS);
+  EXPECT_EQ(observed->VersionInfo.dwFileType, expected->VersionInfo.dwFileType);
+  EXPECT_EQ(observed->VersionInfo.dwFileSubtype,
+            expected->VersionInfo.dwFileSubtype);
+  EXPECT_EQ(observed->VersionInfo.dwFileDateMS,
+            expected->VersionInfo.dwFileDateMS);
+  EXPECT_EQ(observed->VersionInfo.dwFileDateLS,
+            expected->VersionInfo.dwFileDateLS);
+  EXPECT_EQ(observed->Reserved0, 0u);
+  EXPECT_EQ(observed->Reserved1, 0u);
+
+  EXPECT_NE(observed->ModuleNameRva, 0u);
+  base::string16 observed_module_name_utf16 =
+      MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva);
+  base::string16 expected_module_name_utf16 =
+      base::UTF8ToUTF16(expected_module_name);
+  EXPECT_EQ(observed_module_name_utf16, expected_module_name_utf16);
+
+  const CodeViewRecordBuildID* codeview_build_id_record =
+      MinidumpWritableAtLocationDescriptor<CodeViewRecordBuildID>(
+          file_contents, observed->CvRecord);
+  ASSERT_TRUE(codeview_build_id_record);
+  EXPECT_EQ(memcmp(expected_build_id.data(),
+                   &codeview_build_id_record->build_id,
+                   expected_build_id.size()),
+            0);
+}
+
 TEST(MinidumpModuleWriter, EmptyModule) {
   MinidumpFileWriter minidump_file_writer;
   auto module_list_writer = std::make_unique<MinidumpModuleListWriter>();
@@ -324,9 +380,22 @@
   constexpr uint32_t kFileType = VFT_DRV;
   constexpr uint32_t kFileSubtype = VFT2_DRV_KEYBOARD;
   static constexpr char kPDBName[] = "statical.pdb";
-  static constexpr uint8_t kPDBUUIDBytes[16] =
-      {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
-       0x08, 0x19, 0x2a, 0x3b, 0x4c, 0x5d, 0x6e, 0x7f};
+  static constexpr uint8_t kPDBUUIDBytes[16] = {0xfe,
+                                                0xdc,
+                                                0xba,
+                                                0x98,
+                                                0x76,
+                                                0x54,
+                                                0x32,
+                                                0x10,
+                                                0x08,
+                                                0x19,
+                                                0x2a,
+                                                0x3b,
+                                                0x4c,
+                                                0x5d,
+                                                0x6e,
+                                                0x7f};
   UUID pdb_uuid;
   pdb_uuid.InitializeFromBytes(kPDBUUIDBytes);
   constexpr uint32_t kPDBAge = 1;
@@ -470,6 +539,50 @@
                                        kDebugUTF16));
 }
 
+TEST(MinidumpModuleWriter, OneModule_CodeViewBuildID) {
+  // MinidumpModuleWriter.OneModule tested with a BuildID CodeView
+  MinidumpFileWriter minidump_file_writer;
+  auto module_list_writer = std::make_unique<MinidumpModuleListWriter>();
+
+  static constexpr char kModuleName[] = "dinosaur";
+  static constexpr char kBuildID[] =
+      "averylonghashcodeormaybeitsjustrandomnumbershardtosay";
+
+  std::vector<uint8_t> build_id_data(kBuildID, kBuildID + 53);
+
+  auto module_writer = std::make_unique<MinidumpModuleWriter>();
+  module_writer->SetName(kModuleName);
+
+  auto codeview_build_id_writer =
+      std::make_unique<MinidumpModuleCodeViewRecordBuildIDWriter>();
+  codeview_build_id_writer->SetBuildID(build_id_data);
+  module_writer->SetCodeViewRecord(std::move(codeview_build_id_writer));
+
+  module_list_writer->AddModule(std::move(module_writer));
+  ASSERT_TRUE(minidump_file_writer.AddStream(std::move(module_list_writer)));
+
+  StringFile string_file;
+  ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
+
+  ASSERT_GT(string_file.string().size(),
+            sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
+                sizeof(MINIDUMP_MODULE_LIST) + 1 * sizeof(MINIDUMP_MODULE));
+
+  const MINIDUMP_MODULE_LIST* module_list = nullptr;
+  ASSERT_NO_FATAL_FAILURE(
+      GetModuleListStream(string_file.string(), &module_list));
+
+  EXPECT_EQ(module_list->NumberOfModules, 1u);
+
+  MINIDUMP_MODULE expected = {};
+
+  ASSERT_NO_FATAL_FAILURE(ExpectModuleWithBuildIDCv(&expected,
+                                                    &module_list->Modules[0],
+                                                    string_file.string(),
+                                                    kModuleName,
+                                                    build_id_data));
+}
+
 TEST(MinidumpModuleWriter, ThreeModules) {
   // As good exercise, this test uses three modules, one with a PDB 7.0 link as
   // its CodeView record, one with no CodeView record, and one with a PDB 2.0
@@ -481,9 +594,22 @@
   constexpr uint64_t kModuleBase0 = 0x100101000;
   constexpr uint32_t kModuleSize0 = 0xf000;
   static constexpr char kPDBName0[] = "main";
-  static constexpr uint8_t kPDBUUIDBytes0[16] =
-      {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11,
-       0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
+  static constexpr uint8_t kPDBUUIDBytes0[16] = {0xaa,
+                                                 0xbb,
+                                                 0xcc,
+                                                 0xdd,
+                                                 0xee,
+                                                 0xff,
+                                                 0x00,
+                                                 0x11,
+                                                 0x22,
+                                                 0x33,
+                                                 0x44,
+                                                 0x55,
+                                                 0x66,
+                                                 0x77,
+                                                 0x88,
+                                                 0x99};
   UUID pdb_uuid_0;
   pdb_uuid_0.InitializeFromBytes(kPDBUUIDBytes0);
   constexpr uint32_t kPDBAge0 = 0;
@@ -650,10 +776,10 @@
 
 TEST(MinidumpModuleWriter, InitializeFromSnapshot) {
   MINIDUMP_MODULE expect_modules[3] = {};
-  const char* module_paths[arraysize(expect_modules)] = {};
-  const char* module_pdbs[arraysize(expect_modules)] = {};
-  UUID uuids[arraysize(expect_modules)] = {};
-  uint32_t ages[arraysize(expect_modules)] = {};
+  const char* module_paths[base::size(expect_modules)] = {};
+  const char* module_pdbs[base::size(expect_modules)] = {};
+  UUID uuids[base::size(expect_modules)] = {};
+  uint32_t ages[base::size(expect_modules)] = {};
 
   expect_modules[0].BaseOfImage = 0x100101000;
   expect_modules[0].SizeOfImage = 0xf000;
@@ -665,9 +791,22 @@
   expect_modules[0].VersionInfo.dwFileType = VFT_APP;
   module_paths[0] = "/usr/bin/true";
   module_pdbs[0] = "true";
-  static constexpr uint8_t kUUIDBytes0[16] =
-      {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
-       0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
+  static constexpr uint8_t kUUIDBytes0[16] = {0x00,
+                                              0x11,
+                                              0x22,
+                                              0x33,
+                                              0x44,
+                                              0x55,
+                                              0x66,
+                                              0x77,
+                                              0x88,
+                                              0x99,
+                                              0xaa,
+                                              0xbb,
+                                              0xcc,
+                                              0xdd,
+                                              0xee,
+                                              0xff};
   uuids[0].InitializeFromBytes(kUUIDBytes0);
   ages[0] = 10;
 
@@ -681,9 +820,22 @@
   expect_modules[1].VersionInfo.dwFileType = VFT_DLL;
   module_paths[1] = "/usr/lib/libSystem.B.dylib";
   module_pdbs[1] = "libSystem.B.dylib.pdb";
-  static constexpr uint8_t kUUIDBytes1[16] =
-      {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+  static constexpr uint8_t kUUIDBytes1[16] = {0x00,
+                                              0x01,
+                                              0x02,
+                                              0x03,
+                                              0x04,
+                                              0x05,
+                                              0x06,
+                                              0x07,
+                                              0x08,
+                                              0x09,
+                                              0x0a,
+                                              0x0b,
+                                              0x0c,
+                                              0x0d,
+                                              0x0e,
+                                              0x0f};
   uuids[1].InitializeFromBytes(kUUIDBytes1);
   ages[1] = 20;
 
@@ -697,15 +849,28 @@
   expect_modules[2].VersionInfo.dwFileType = VFT_UNKNOWN;
   module_paths[2] = "/usr/lib/dyld";
   module_pdbs[2] = "/usr/lib/dyld.pdb";
-  static constexpr uint8_t kUUIDBytes2[16] =
-      {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
-       0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0};
+  static constexpr uint8_t kUUIDBytes2[16] = {0xff,
+                                              0xfe,
+                                              0xfd,
+                                              0xfc,
+                                              0xfb,
+                                              0xfa,
+                                              0xf9,
+                                              0xf8,
+                                              0xf7,
+                                              0xf6,
+                                              0xf5,
+                                              0xf4,
+                                              0xf3,
+                                              0xf2,
+                                              0xf1,
+                                              0xf0};
   uuids[2].InitializeFromBytes(kUUIDBytes2);
   ages[2] = 30;
 
   std::vector<std::unique_ptr<TestModuleSnapshot>> module_snapshots_owner;
   std::vector<const ModuleSnapshot*> module_snapshots;
-  for (size_t index = 0; index < arraysize(expect_modules); ++index) {
+  for (size_t index = 0; index < base::size(expect_modules); ++index) {
     module_snapshots_owner.push_back(std::make_unique<TestModuleSnapshot>());
     TestModuleSnapshot* module_snapshot = module_snapshots_owner.back().get();
     InitializeTestModuleSnapshotFromMinidumpModule(module_snapshot,
diff --git a/minidump/minidump_rva_list_writer_test.cc b/minidump/minidump_rva_list_writer_test.cc
index 3043287..3807432 100644
--- a/minidump/minidump_rva_list_writer_test.cc
+++ b/minidump/minidump_rva_list_writer_test.cc
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "minidump/test/minidump_rva_list_test_util.h"
@@ -86,10 +87,10 @@
   ASSERT_TRUE(list_writer.WriteEverything(&string_file));
 
   const MinidumpRVAList* list =
-      MinidumpRVAListAtStart(string_file.string(), arraysize(kValues));
+      MinidumpRVAListAtStart(string_file.string(), base::size(kValues));
   ASSERT_TRUE(list);
 
-  for (size_t index = 0; index < arraysize(kValues); ++index) {
+  for (size_t index = 0; index < base::size(kValues); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index));
 
     const uint32_t* child = MinidumpWritableAtRVA<uint32_t>(
diff --git a/minidump/minidump_string_writer_test.cc b/minidump/minidump_string_writer_test.cc
index 382baaf..ddb8c48 100644
--- a/minidump/minidump_string_writer_test.cc
+++ b/minidump/minidump_string_writer_test.cc
@@ -18,6 +18,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
@@ -66,15 +67,16 @@
       {4, "\360\220\204\202", 2, {0xd800, 0xdd02}},  // 𐄂 (non-BMP)
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ", input %s", index, kTestData[index].input_string));
 
     // Make sure that the expected output string with its NUL terminator fits in
     // the space provided.
-    ASSERT_EQ(kTestData[index]
-                  .output_string[arraysize(kTestData[index].output_string) - 1],
-              0);
+    ASSERT_EQ(
+        kTestData[index]
+            .output_string[base::size(kTestData[index].output_string) - 1],
+        0);
 
     string_file.Reset();
     crashpad::internal::MinidumpUTF16StringWriter string_writer;
@@ -100,6 +102,12 @@
   }
 }
 
+// Related tracking issues:
+// https://fuchsia.atlassian.net/browse/DX-487
+// https://bugs.chromium.org/p/chromium/issues/detail?id=872892
+// https://bugs.chromium.org/p/chromium/issues/detail?id=889582
+// TODO: Re-enable test once LUCI supports invalid UTF8 characters in test logs.
+#if !defined(CRASHPAD_IS_IN_FUCHSIA)
 TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) {
   StringFile string_file;
 
@@ -112,7 +120,7 @@
       "\303\0\251",  // NUL in middle of valid sequence
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ", input %s", index, kTestData[index]));
     string_file.Reset();
@@ -139,6 +147,7 @@
     EXPECT_NE(output_string.find(0xfffd), base::string16::npos);
   }
 }
+#endif  // !defined(CRASHPAD_IS_IN_FUCHSIA)
 
 TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) {
   StringFile string_file;
@@ -174,7 +183,7 @@
       {4, "\360\220\204\202"},  // 𐄂 (non-BMP)
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ", input %s", index, kTestData[index].string));
 
diff --git a/minidump/minidump_system_info_writer.cc b/minidump/minidump_system_info_writer.cc
index cc87d24..7b4b49d 100644
--- a/minidump/minidump_system_info_writer.cc
+++ b/minidump/minidump_system_info_writer.cc
@@ -17,10 +17,11 @@
 #include <string.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "minidump/minidump_string_writer.h"
 #include "snapshot/system_snapshot.h"
 #include "util/file/file_writer.h"
-#include "util/misc/arraysize_unsafe.h"
+#include "util/misc/arraysize.h"
 #include "util/misc/implicit_cast.h"
 
 namespace crashpad {
@@ -150,7 +151,7 @@
     SetCPUX86VersionAndFeatures(system_snapshot->CPUX86Signature(),
                                 system_snapshot->CPUX86Features() & 0xffffffff);
 
-    if (cpu_vendor == "AuthenticAMD") {
+    if (cpu_vendor == "AuthenticAMD" || cpu_vendor == "HygonGenuine") {
       SetCPUX86AMDExtendedFeatures(
           system_snapshot->CPUX86ExtendedFeatures() & 0xffffffff);
     }
@@ -175,6 +176,9 @@
     case SystemSnapshot::kOperatingSystemFuchsia:
       operating_system = kMinidumpOSFuchsia;
       break;
+    case SystemSnapshot::kOperatingSystemIOS:
+      operating_system = kMinidumpOSIOS;
+      break;
     default:
       NOTREACHED();
       operating_system = kMinidumpOSUnknown;
@@ -212,7 +216,7 @@
          system_info_.ProcessorArchitecture ==
              kMinidumpCPUArchitectureX86Win64);
 
-  static_assert(ARRAYSIZE_UNSAFE(system_info_.Cpu.X86CpuInfo.VendorId) == 3,
+  static_assert(ArraySize(system_info_.Cpu.X86CpuInfo.VendorId) == 3,
                 "VendorId must have 3 elements");
 
   system_info_.Cpu.X86CpuInfo.VendorId[0] = ebx;
@@ -230,7 +234,7 @@
       sizeof(registers) == sizeof(system_info_.Cpu.X86CpuInfo.VendorId),
       "VendorId sizes must be equal");
 
-  for (size_t index = 0; index < arraysize(registers); ++index) {
+  for (size_t index = 0; index < base::size(registers); ++index) {
     memcpy(&registers[index],
            &vendor[index * sizeof(*registers)],
            sizeof(*registers));
@@ -270,9 +274,8 @@
          system_info_.ProcessorArchitecture !=
              kMinidumpCPUArchitectureX86Win64);
 
-  static_assert(
-      ARRAYSIZE_UNSAFE(system_info_.Cpu.OtherCpuInfo.ProcessorFeatures) == 2,
-      "ProcessorFeatures must have 2 elements");
+  static_assert(ArraySize(system_info_.Cpu.OtherCpuInfo.ProcessorFeatures) == 2,
+                "ProcessorFeatures must have 2 elements");
 
   system_info_.Cpu.OtherCpuInfo.ProcessorFeatures[0] = features_0;
   system_info_.Cpu.OtherCpuInfo.ProcessorFeatures[1] = features_1;
diff --git a/minidump/minidump_thread_id_map_test.cc b/minidump/minidump_thread_id_map_test.cc
index 7c6795a..548729e 100644
--- a/minidump/minidump_thread_id_map_test.cc
+++ b/minidump/minidump_thread_id_map_test.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "snapshot/test/test_thread_snapshot.h"
 
@@ -38,7 +39,8 @@
 
   // testing::Test:
   void SetUp() override {
-    for (size_t index = 0; index < arraysize(test_thread_snapshots_); ++index) {
+    for (size_t index = 0; index < base::size(test_thread_snapshots_);
+         ++index) {
       thread_snapshots_.push_back(&test_thread_snapshots_[index]);
     }
   }
@@ -59,7 +61,7 @@
   }
 
   void SetThreadID(size_t index, uint64_t thread_id) {
-    ASSERT_LT(index, arraysize(test_thread_snapshots_));
+    ASSERT_LT(index, base::size(test_thread_snapshots_));
     test_thread_snapshots_[index].SetThreadID(thread_id);
   }
 
diff --git a/minidump/minidump_thread_writer_test.cc b/minidump/minidump_thread_writer_test.cc
index e95c904..956d100 100644
--- a/minidump/minidump_thread_writer_test.cc
+++ b/minidump/minidump_thread_writer_test.cc
@@ -19,6 +19,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_context_writer.h"
@@ -522,10 +523,10 @@
 void RunInitializeFromSnapshotTest(bool thread_id_collision) {
   using MinidumpContextType = typename Traits::MinidumpContextType;
   MINIDUMP_THREAD expect_threads[3] = {};
-  uint64_t thread_ids[arraysize(expect_threads)] = {};
-  uint8_t memory_values[arraysize(expect_threads)] = {};
-  uint32_t context_seeds[arraysize(expect_threads)] = {};
-  MINIDUMP_MEMORY_DESCRIPTOR tebs[arraysize(expect_threads)] = {};
+  uint64_t thread_ids[base::size(expect_threads)] = {};
+  uint8_t memory_values[base::size(expect_threads)] = {};
+  uint32_t context_seeds[base::size(expect_threads)] = {};
+  MINIDUMP_MEMORY_DESCRIPTOR tebs[base::size(expect_threads)] = {};
 
   constexpr size_t kTebSize = 1024;
 
@@ -581,7 +582,7 @@
 
   std::vector<std::unique_ptr<TestThreadSnapshot>> thread_snapshots_owner;
   std::vector<const ThreadSnapshot*> thread_snapshots;
-  for (size_t index = 0; index < arraysize(expect_threads); ++index) {
+  for (size_t index = 0; index < base::size(expect_threads); ++index) {
     thread_snapshots_owner.push_back(std::make_unique<TestThreadSnapshot>());
     TestThreadSnapshot* thread_snapshot = thread_snapshots_owner.back().get();
 
diff --git a/minidump/minidump_unloaded_module_writer.cc b/minidump/minidump_unloaded_module_writer.cc
index 855e196..c8a5f0f 100644
--- a/minidump/minidump_unloaded_module_writer.cc
+++ b/minidump/minidump_unloaded_module_writer.cc
@@ -123,7 +123,8 @@
   DCHECK_EQ(state(), kStateMutable);
   DCHECK(unloaded_modules_.empty());
 
-  for (auto unloaded_module_snapshot : unloaded_module_snapshots) {
+  for (const UnloadedModuleSnapshot& unloaded_module_snapshot :
+       unloaded_module_snapshots) {
     auto unloaded_module = std::make_unique<MinidumpUnloadedModuleWriter>();
     unloaded_module->InitializeFromSnapshot(unloaded_module_snapshot);
     AddUnloadedModule(std::move(unloaded_module));
diff --git a/minidump/minidump_user_stream_writer.cc b/minidump/minidump_user_stream_writer.cc
index 139e827..8401b28 100644
--- a/minidump/minidump_user_stream_writer.cc
+++ b/minidump/minidump_user_stream_writer.cc
@@ -42,7 +42,7 @@
     return snapshot_->Read(this);
   }
 
-  size_t GetSize() const override { return snapshot_ ? snapshot_->Size() : 0; };
+  size_t GetSize() const override { return snapshot_ ? snapshot_->Size() : 0; }
 
   bool MemorySnapshotDelegateRead(void* data, size_t size) override {
     return writer_->Write(data, size);
diff --git a/minidump/minidump_writable.cc b/minidump/minidump_writable.cc
index d2b58f5..fa3e2f1 100644
--- a/minidump/minidump_writable.cc
+++ b/minidump/minidump_writable.cc
@@ -17,6 +17,7 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "util/file/file_writer.h"
 #include "util/numeric/safe_assignment.h"
 
@@ -244,7 +245,7 @@
   // The number of elements in kZeroes must be at least one less than the
   // maximum Alignment() ever encountered.
   static constexpr uint8_t kZeroes[kMaximumAlignment - 1] = {};
-  DCHECK_LE(leading_pad_bytes_, arraysize(kZeroes));
+  DCHECK_LE(leading_pad_bytes_, base::size(kZeroes));
 
   if (leading_pad_bytes_) {
     if (!file_writer->Write(&kZeroes, leading_pad_bytes_)) {
diff --git a/minidump/test/minidump_context_test_util.cc b/minidump/test/minidump_context_test_util.cc
index 28f9410..c94fa20 100644
--- a/minidump/test/minidump_context_test_util.cc
+++ b/minidump/test/minidump_context_test_util.cc
@@ -18,7 +18,7 @@
 #include <sys/types.h>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "snapshot/cpu_context.h"
@@ -128,7 +128,8 @@
   context->ds = static_cast<uint16_t>(value++);
   context->es = static_cast<uint16_t>(value++);
   context->ss = static_cast<uint16_t>(value++);
-  for (size_t index = 0; index < arraysize(context->vector_register); ++index) {
+  for (size_t index = 0; index < base::size(context->vector_register);
+       ++index) {
     context->vector_register[index].lo = value++;
     context->vector_register[index].hi = value++;
   }
@@ -151,7 +152,7 @@
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(context->regs); ++index) {
+  for (size_t index = 0; index < base::size(context->regs); ++index) {
     context->regs[index] = value++;
   }
   context->fp = value++;
@@ -162,7 +163,7 @@
   context->pc = value++;
   context->cpsr = value++;
 
-  for (size_t index = 0; index < arraysize(context->vfp); ++index) {
+  for (size_t index = 0; index < base::size(context->vfp); ++index) {
     context->vfp[index] = value++;
   }
   context->fpscr = value++;
@@ -176,18 +177,20 @@
     return;
   }
 
-  context->context_flags = kMinidumpContextARM64All;
+  context->context_flags = kMinidumpContextARM64Full;
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(context->regs); ++index) {
+  for (size_t index = 0; index < base::size(context->regs); ++index) {
     context->regs[index] = value++;
   }
+  context->fp = value++;
+  context->lr = value++;
   context->sp = value++;
   context->pc = value++;
   context->cpsr = value++;
 
-  for (size_t index = 0; index < arraysize(context->fpsimd); ++index) {
+  for (size_t index = 0; index < base::size(context->fpsimd); ++index) {
     context->fpsimd[index].lo = value++;
     context->fpsimd[index].hi = value++;
   }
@@ -207,7 +210,7 @@
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(context->regs); ++index) {
+  for (size_t index = 0; index < base::size(context->regs); ++index) {
     context->regs[index] = value++;
   }
 
@@ -218,7 +221,7 @@
   context->status = value++;
   context->cause = value++;
 
-  for (size_t index = 0; index < arraysize(context->fpregs.fregs); ++index) {
+  for (size_t index = 0; index < base::size(context->fpregs.fregs); ++index) {
     context->fpregs.fregs[index]._fp_fregs = static_cast<float>(value++);
   }
 
@@ -245,7 +248,7 @@
 
   uint64_t value = seed;
 
-  for (size_t index = 0; index < arraysize(context->regs); ++index) {
+  for (size_t index = 0; index < base::size(context->regs); ++index) {
     context->regs[index] = value++;
   }
 
@@ -256,7 +259,7 @@
   context->status = value++;
   context->cause = value++;
 
-  for (size_t index = 0; index < arraysize(context->fpregs.dregs); ++index) {
+  for (size_t index = 0; index < base::size(context->fpregs.dregs); ++index) {
     context->fpregs.dregs[index] = static_cast<double>(value++);
   }
   context->fpcsr = value++;
@@ -291,35 +294,33 @@
   EXPECT_EQ(observed->reserved_3, expected->reserved_3);
   EXPECT_EQ(observed->mxcsr, expected->mxcsr);
   EXPECT_EQ(observed->mxcsr_mask, expected->mxcsr_mask);
-  for (size_t st_mm_index = 0;
-       st_mm_index < arraysize(expected->st_mm);
+  for (size_t st_mm_index = 0; st_mm_index < base::size(expected->st_mm);
        ++st_mm_index) {
     SCOPED_TRACE(base::StringPrintf("st_mm_index %" PRIuS, st_mm_index));
     EXPECT_EQ(BytesToHexString(observed->st_mm[st_mm_index].st,
-                               arraysize(observed->st_mm[st_mm_index].st)),
+                               base::size(observed->st_mm[st_mm_index].st)),
               BytesToHexString(expected->st_mm[st_mm_index].st,
-                               arraysize(expected->st_mm[st_mm_index].st)));
+                               base::size(expected->st_mm[st_mm_index].st)));
     EXPECT_EQ(
         BytesToHexString(observed->st_mm[st_mm_index].st_reserved,
-                         arraysize(observed->st_mm[st_mm_index].st_reserved)),
+                         base::size(observed->st_mm[st_mm_index].st_reserved)),
         BytesToHexString(expected->st_mm[st_mm_index].st_reserved,
-                         arraysize(expected->st_mm[st_mm_index].st_reserved)));
+                         base::size(expected->st_mm[st_mm_index].st_reserved)));
   }
-  for (size_t xmm_index = 0;
-       xmm_index < arraysize(expected->xmm);
+  for (size_t xmm_index = 0; xmm_index < base::size(expected->xmm);
        ++xmm_index) {
     EXPECT_EQ(BytesToHexString(observed->xmm[xmm_index],
-                               arraysize(observed->xmm[xmm_index])),
+                               base::size(observed->xmm[xmm_index])),
               BytesToHexString(expected->xmm[xmm_index],
-                               arraysize(expected->xmm[xmm_index])))
+                               base::size(expected->xmm[xmm_index])))
         << "xmm_index " << xmm_index;
   }
   EXPECT_EQ(
-      BytesToHexString(observed->reserved_4, arraysize(observed->reserved_4)),
-      BytesToHexString(expected->reserved_4, arraysize(expected->reserved_4)));
+      BytesToHexString(observed->reserved_4, base::size(observed->reserved_4)),
+      BytesToHexString(expected->reserved_4, base::size(expected->reserved_4)));
   EXPECT_EQ(
-      BytesToHexString(observed->available, arraysize(observed->available)),
-      BytesToHexString(expected->available, arraysize(expected->available)));
+      BytesToHexString(observed->available, base::size(observed->available)),
+      BytesToHexString(expected->available, base::size(expected->available)));
 }
 
 }  // namespace
@@ -344,11 +345,11 @@
   EXPECT_EQ(observed->fsave.fpu_cs, expected.fsave.fpu_cs);
   EXPECT_EQ(observed->fsave.fpu_dp, expected.fsave.fpu_dp);
   EXPECT_EQ(observed->fsave.fpu_ds, expected.fsave.fpu_ds);
-  for (size_t index = 0; index < arraysize(expected.fsave.st); ++index) {
+  for (size_t index = 0; index < base::size(expected.fsave.st); ++index) {
     EXPECT_EQ(BytesToHexString(observed->fsave.st[index],
-                               arraysize(observed->fsave.st[index])),
+                               base::size(observed->fsave.st[index])),
               BytesToHexString(expected.fsave.st[index],
-                               arraysize(expected.fsave.st[index])))
+                               base::size(expected.fsave.st[index])))
         << "index " << index;
   }
   if (snapshot) {
@@ -447,7 +448,8 @@
 
   ExpectMinidumpContextFxsave(&expected.fxsave, &observed->fxsave);
 
-  for (size_t index = 0; index < arraysize(expected.vector_register); ++index) {
+  for (size_t index = 0; index < base::size(expected.vector_register);
+       ++index) {
     if (snapshot) {
       EXPECT_EQ(observed->vector_register[index].lo, 0u) << "index " << index;
       EXPECT_EQ(observed->vector_register[index].hi, 0u) << "index " << index;
@@ -487,7 +489,7 @@
 
   EXPECT_EQ(observed->context_flags, expected.context_flags);
 
-  for (size_t index = 0; index < arraysize(expected.regs); ++index) {
+  for (size_t index = 0; index < base::size(expected.regs); ++index) {
     EXPECT_EQ(observed->regs[index], expected.regs[index]);
   }
   EXPECT_EQ(observed->fp, expected.fp);
@@ -498,10 +500,10 @@
   EXPECT_EQ(observed->cpsr, expected.cpsr);
 
   EXPECT_EQ(observed->fpscr, expected.fpscr);
-  for (size_t index = 0; index < arraysize(expected.vfp); ++index) {
+  for (size_t index = 0; index < base::size(expected.vfp); ++index) {
     EXPECT_EQ(observed->vfp[index], expected.vfp[index]);
   }
-  for (size_t index = 0; index < arraysize(expected.extra); ++index) {
+  for (size_t index = 0; index < base::size(expected.extra); ++index) {
     EXPECT_EQ(observed->extra[index], snapshot ? 0 : expected.extra[index]);
   }
 }
@@ -514,14 +516,14 @@
 
   EXPECT_EQ(observed->context_flags, expected.context_flags);
 
-  for (size_t index = 0; index < arraysize(expected.regs); ++index) {
+  for (size_t index = 0; index < base::size(expected.regs); ++index) {
     EXPECT_EQ(observed->regs[index], expected.regs[index]);
   }
   EXPECT_EQ(observed->cpsr, expected.cpsr);
 
   EXPECT_EQ(observed->fpsr, expected.fpsr);
   EXPECT_EQ(observed->fpcr, expected.fpcr);
-  for (size_t index = 0; index < arraysize(expected.fpsimd); ++index) {
+  for (size_t index = 0; index < base::size(expected.fpsimd); ++index) {
     EXPECT_EQ(observed->fpsimd[index].lo, expected.fpsimd[index].lo);
     EXPECT_EQ(observed->fpsimd[index].hi, expected.fpsimd[index].hi);
   }
@@ -535,7 +537,7 @@
 
   EXPECT_EQ(observed->context_flags, expected.context_flags);
 
-  for (size_t index = 0; index < arraysize(expected.regs); ++index) {
+  for (size_t index = 0; index < base::size(expected.regs); ++index) {
     EXPECT_EQ(observed->regs[index], expected.regs[index]);
   }
 
@@ -546,7 +548,7 @@
   EXPECT_EQ(observed->status, expected.status);
   EXPECT_EQ(observed->cause, expected.cause);
 
-  for (size_t index = 0; index < arraysize(expected.fpregs.fregs); ++index) {
+  for (size_t index = 0; index < base::size(expected.fpregs.fregs); ++index) {
     EXPECT_EQ(observed->fpregs.fregs[index]._fp_fregs,
               expected.fpregs.fregs[index]._fp_fregs);
   }
@@ -568,7 +570,7 @@
 
   EXPECT_EQ(observed->context_flags, expected.context_flags);
 
-  for (size_t index = 0; index < arraysize(expected.regs); ++index) {
+  for (size_t index = 0; index < base::size(expected.regs); ++index) {
     EXPECT_EQ(observed->regs[index], expected.regs[index]);
   }
 
@@ -579,7 +581,7 @@
   EXPECT_EQ(observed->status, expected.status);
   EXPECT_EQ(observed->cause, expected.cause);
 
-  for (size_t index = 0; index < arraysize(expected.fpregs.dregs); ++index) {
+  for (size_t index = 0; index < base::size(expected.fpregs.dregs); ++index) {
     EXPECT_EQ(observed->fpregs.dregs[index], expected.fpregs.dregs[index]);
   }
   EXPECT_EQ(observed->fpcsr, expected.fpcsr);
diff --git a/minidump/test/minidump_writable_test_util.cc b/minidump/test/minidump_writable_test_util.cc
index 1a832bf..db07b36 100644
--- a/minidump/test/minidump_writable_test_util.cc
+++ b/minidump/test/minidump_writable_test_util.cc
@@ -210,17 +210,13 @@
 struct MinidumpModuleCrashpadInfoListTraits {
   using ListType = MinidumpModuleCrashpadInfoList;
   enum : size_t { kElementSize = sizeof(MinidumpModuleCrashpadInfoLink) };
-  static size_t ElementCount(const ListType* list) {
-    return list->count;
-  }
+  static size_t ElementCount(const ListType* list) { return list->count; }
 };
 
 struct MinidumpSimpleStringDictionaryListTraits {
   using ListType = MinidumpSimpleStringDictionary;
   enum : size_t { kElementSize = sizeof(MinidumpSimpleStringDictionaryEntry) };
-  static size_t ElementCount(const ListType* list) {
-    return list->count;
-  }
+  static size_t ElementCount(const ListType* list) { return list->count; }
 };
 
 struct MinidumpAnnotationListObjectsTraits {
@@ -253,17 +249,19 @@
 }  // namespace
 
 template <>
-const MINIDUMP_MEMORY_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MEMORY_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const MINIDUMP_MEMORY_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MEMORY_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpListAtLocationDescriptor<MinidumpMemoryListTraits>(
       file_contents, location);
 }
 
 template <>
-const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MODULE_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const MINIDUMP_MODULE_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MODULE_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpListAtLocationDescriptor<MinidumpModuleListTraits>(
       file_contents, location);
 }
@@ -278,25 +276,28 @@
 }
 
 template <>
-const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_THREAD_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const MINIDUMP_THREAD_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_THREAD_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpListAtLocationDescriptor<MinidumpThreadListTraits>(
       file_contents, location);
 }
 
 template <>
-const MINIDUMP_HANDLE_DATA_STREAM* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_HANDLE_DATA_STREAM>(const std::string& file_contents,
-                                 const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const MINIDUMP_HANDLE_DATA_STREAM*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_HANDLE_DATA_STREAM>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpListAtLocationDescriptor<MinidumpHandleDataStreamTraits>(
       file_contents, location);
 }
 
 template <>
-const MINIDUMP_MEMORY_INFO_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MEMORY_INFO_LIST>(const std::string& file_contents,
-                               const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const MINIDUMP_MEMORY_INFO_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MEMORY_INFO_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpListAtLocationDescriptor<MinidumpMemoryInfoListTraits>(
       file_contents, location);
 }
@@ -357,28 +358,51 @@
 }  // namespace
 
 template <>
-const CodeViewRecordPDB20* MinidumpWritableAtLocationDescriptor<
-    CodeViewRecordPDB20>(const std::string& file_contents,
-                         const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const CodeViewRecordPDB20*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB20>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpCVPDBAtLocationDescriptor<CodeViewRecordPDB20>(file_contents,
                                                                 location);
 }
 
 template <>
-const CodeViewRecordPDB70* MinidumpWritableAtLocationDescriptor<
-    CodeViewRecordPDB70>(const std::string& file_contents,
-                         const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+const CodeViewRecordPDB70*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB70>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
   return MinidumpCVPDBAtLocationDescriptor<CodeViewRecordPDB70>(file_contents,
                                                                 location);
 }
 
-TestUInt32MinidumpWritable::TestUInt32MinidumpWritable(uint32_t value)
-    : MinidumpWritable(),
-      value_(value) {
+template <>
+const CodeViewRecordBuildID*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordBuildID>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location) {
+  const CodeViewRecordBuildID* cv =
+      reinterpret_cast<const CodeViewRecordBuildID*>(
+          MinidumpWritableAtLocationDescriptorInternal(
+              file_contents,
+              location,
+              offsetof(CodeViewRecordBuildID, build_id),
+              true));
+
+  if (!cv) {
+    return nullptr;
+  }
+
+  if (cv->signature != CodeViewRecordBuildID::kSignature) {
+    return nullptr;
+  }
+
+  return cv;
 }
 
-TestUInt32MinidumpWritable::~TestUInt32MinidumpWritable() {
-}
+TestUInt32MinidumpWritable::TestUInt32MinidumpWritable(uint32_t value)
+    : MinidumpWritable(), value_(value) {}
+
+TestUInt32MinidumpWritable::~TestUInt32MinidumpWritable() {}
 
 size_t TestUInt32MinidumpWritable::SizeOfObject() {
   return sizeof(value_);
diff --git a/minidump/test/minidump_writable_test_util.h b/minidump/test/minidump_writable_test_util.h
index 5b176d2..6c706fb 100644
--- a/minidump/test/minidump_writable_test_util.h
+++ b/minidump/test/minidump_writable_test_util.h
@@ -104,6 +104,7 @@
 MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_STRING);
 MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordPDB20);
 MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordPDB70);
+MINIDUMP_ALLOW_OVERSIZED_DATA(CodeViewRecordBuildID);
 MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpUTF8String);
 
 // minidump_file_writer_test accesses its variable-sized test streams via a
@@ -179,14 +180,16 @@
     const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const MINIDUMP_MEMORY_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MEMORY_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const MINIDUMP_MEMORY_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MEMORY_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MODULE_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const MINIDUMP_MODULE_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MODULE_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
 const MINIDUMP_UNLOADED_MODULE_LIST*
@@ -195,29 +198,40 @@
     const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_THREAD_LIST>(const std::string& file_contents,
-                          const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const MINIDUMP_THREAD_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_THREAD_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const MINIDUMP_HANDLE_DATA_STREAM* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_HANDLE_DATA_STREAM>(const std::string& file_contents,
-                                 const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const MINIDUMP_HANDLE_DATA_STREAM*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_HANDLE_DATA_STREAM>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const MINIDUMP_MEMORY_INFO_LIST* MinidumpWritableAtLocationDescriptor<
-    MINIDUMP_MEMORY_INFO_LIST>(const std::string& file_contents,
-                               const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const MINIDUMP_MEMORY_INFO_LIST*
+MinidumpWritableAtLocationDescriptor<MINIDUMP_MEMORY_INFO_LIST>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const CodeViewRecordPDB20* MinidumpWritableAtLocationDescriptor<
-    CodeViewRecordPDB20>(const std::string& file_contents,
-                         const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const CodeViewRecordPDB20*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB20>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
-const CodeViewRecordPDB70* MinidumpWritableAtLocationDescriptor<
-    CodeViewRecordPDB70>(const std::string& file_contents,
-                         const MINIDUMP_LOCATION_DESCRIPTOR& location);
+const CodeViewRecordPDB70*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordPDB70>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
+
+template <>
+const CodeViewRecordBuildID*
+MinidumpWritableAtLocationDescriptor<CodeViewRecordBuildID>(
+    const std::string& file_contents,
+    const MINIDUMP_LOCATION_DESCRIPTOR& location);
 
 template <>
 const MinidumpModuleCrashpadInfoList*
diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn
index 134df8b..fea22c2 100644
--- a/snapshot/BUILD.gn
+++ b/snapshot/BUILD.gn
@@ -17,6 +17,9 @@
 
 if (crashpad_is_in_chromium) {
   import("//build/config/compiler/compiler.gni")
+
+  # Prevent Chromium source assignment filters from being inherited.
+  set_sources_assignment_filter([])
 }
 
 static_library("snapshot") {
@@ -25,9 +28,6 @@
     "annotation_snapshot.h",
     "capture_memory.cc",
     "capture_memory.h",
-    "cpu_architecture.h",
-    "cpu_context.cc",
-    "cpu_context.h",
     "crashpad_info_client_options.cc",
     "crashpad_info_client_options.h",
     "exception_snapshot.h",
@@ -36,10 +36,17 @@
     "memory_snapshot.cc",
     "memory_snapshot.h",
     "memory_snapshot_generic.h",
+    "minidump/exception_snapshot_minidump.cc",
+    "minidump/exception_snapshot_minidump.h",
+    "minidump/memory_snapshot_minidump.cc",
+    "minidump/memory_snapshot_minidump.h",
     "minidump/minidump_annotation_reader.cc",
     "minidump/minidump_annotation_reader.h",
+    "minidump/minidump_context_converter.cc",
+    "minidump/minidump_context_converter.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",
@@ -48,6 +55,10 @@
     "minidump/module_snapshot_minidump.h",
     "minidump/process_snapshot_minidump.cc",
     "minidump/process_snapshot_minidump.h",
+    "minidump/system_snapshot_minidump.cc",
+    "minidump/system_snapshot_minidump.h",
+    "minidump/thread_snapshot_minidump.cc",
+    "minidump/thread_snapshot_minidump.h",
     "module_snapshot.h",
     "process_snapshot.h",
     "snapshot_constants.h",
@@ -86,16 +97,9 @@
       "mac/process_snapshot_mac.h",
       "mac/process_types.cc",
       "mac/process_types.h",
-      "mac/process_types/all.proctype",
-      "mac/process_types/annotation.proctype",
-      "mac/process_types/crashpad_info.proctype",
-      "mac/process_types/crashreporterclient.proctype",
       "mac/process_types/custom.cc",
-      "mac/process_types/dyld_images.proctype",
       "mac/process_types/flavors.h",
       "mac/process_types/internal.h",
-      "mac/process_types/loader.proctype",
-      "mac/process_types/nlist.proctype",
       "mac/process_types/traits.h",
       "mac/system_snapshot_mac.cc",
       "mac/system_snapshot_mac.h",
@@ -104,8 +108,26 @@
     ]
   }
 
+  if (crashpad_is_ios) {
+    sources += [
+      "ios/exception_snapshot_ios.cc",
+      "ios/exception_snapshot_ios.h",
+      "ios/memory_snapshot_ios.cc",
+      "ios/memory_snapshot_ios.h",
+      "ios/module_snapshot_ios.cc",
+      "ios/module_snapshot_ios.h",
+      "ios/process_snapshot_ios.cc",
+      "ios/process_snapshot_ios.h",
+      "ios/system_snapshot_ios.cc",
+      "ios/system_snapshot_ios.h",
+      "ios/thread_snapshot_ios.cc",
+      "ios/thread_snapshot_ios.h",
+      "mac/cpu_context_mac.cc",
+      "mac/cpu_context_mac.h",
+    ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android) {
-    set_sources_assignment_filter([])
     sources += [
       "linux/cpu_context_linux.cc",
       "linux/cpu_context_linux.h",
@@ -135,10 +157,16 @@
     ]
   }
 
-  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
+  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
+      crashpad_is_win) {
     sources += [
       "crashpad_types/crashpad_info_reader.cc",
       "crashpad_types/crashpad_info_reader.h",
+    ]
+  }
+
+  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
+    sources += [
       "crashpad_types/image_annotation_reader.cc",
       "crashpad_types/image_annotation_reader.h",
       "elf/elf_dynamic_array_reader.cc",
@@ -162,8 +190,6 @@
       "win/exception_snapshot_win.h",
       "win/memory_map_region_snapshot_win.cc",
       "win/memory_map_region_snapshot_win.h",
-      "win/memory_snapshot_win.cc",
-      "win/memory_snapshot_win.h",
       "win/module_snapshot_win.cc",
       "win/module_snapshot_win.h",
       "win/pe_image_annotations_reader.cc",
@@ -193,6 +219,8 @@
       "fuchsia/exception_snapshot_fuchsia.h",
       "fuchsia/memory_map_fuchsia.cc",
       "fuchsia/memory_map_fuchsia.h",
+      "fuchsia/memory_map_region_snapshot_fuchsia.cc",
+      "fuchsia/memory_map_region_snapshot_fuchsia.h",
       "fuchsia/process_reader_fuchsia.cc",
       "fuchsia/process_reader_fuchsia.h",
       "fuchsia/process_snapshot_fuchsia.cc",
@@ -213,51 +241,58 @@
 
   public_configs = [ "..:crashpad_config" ]
 
+  public_deps = [ ":context" ]
+
   deps = [
     "../client",
     "../compat",
+    "../minidump:format",
+    "../third_party/mini_chromium:base",
+    "../util",
+  ]
+
+  if (crashpad_is_ios) {
+    deps -= [ "../client" ]
+  }
+
+  if (crashpad_is_win) {
+    cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
+    libs = [ "powrprof.lib" ]
+  }
+
+  configs += [ "..:disable_ubsan" ]
+}
+
+# :context is the only part of snapshot that minidump may depend on.
+static_library("context") {
+  sources = [
+    "cpu_architecture.h",
+    "cpu_context.cc",
+    "cpu_context.h",
+  ]
+
+  public_configs = [ "..:crashpad_config" ]
+
+  deps = [
     "../third_party/mini_chromium:base",
     "../util",
   ]
 
   if (crashpad_is_win) {
     cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
-    libs = [ "powrprof.lib" ]
   }
 }
 
-if (crashpad_is_win) {
-  static_library("snapshot_api") {
-    sources = [
-      "api/module_annotations_win.cc",
-      "api/module_annotations_win.h",
-    ]
-
-    public_configs = [ "..:crashpad_config" ]
-
-    cflags = [ "/wd4201" ]
+if (crashpad_is_linux) {
+  crashpad_fuzzer_test("elf_image_reader_fuzzer") {
+    sources = [ "elf/elf_image_reader_fuzzer.cc" ]
 
     deps = [
       ":snapshot",
-      "../compat",
       "../third_party/mini_chromium:base",
-      "../util",
     ]
+    seed_corpus = "elf/elf_image_reader_fuzzer_corpus"
   }
-} else {
-  group("snapshot_api") {
-  }
-}
-
-fuzzer_test("elf_image_reader_fuzzer") {
-  sources = [
-    "elf/elf_image_reader_fuzzer.cc",
-  ]
-
-  deps = [
-    ":snapshot",
-    "../third_party/mini_chromium:base",
-  ]
 }
 
 static_library("test_support") {
@@ -284,9 +319,7 @@
 
   public_configs = [ "..:crashpad_config" ]
 
-  public_deps = [
-    ":snapshot",
-  ]
+  public_deps = [ ":snapshot" ]
 
   deps = [
     "../compat",
@@ -305,9 +338,7 @@
     # There’s no way to make the link depend on this file. “inputs” doesn’t have
     # the intended effect in a config. https://crbug.com/781858,
     # https://crbug.com/796187.
-    inputs = [
-      "elf/test_exported_symbols.sym",
-    ]
+    inputs = [ "elf/test_exported_symbols.sym" ]
     ldflags = [ "-Wl,--dynamic-list," + rebase_path(inputs[0], root_build_dir) ]
   }
 }
@@ -342,27 +373,29 @@
       "sanitized/process_snapshot_sanitized_test.cc",
       "sanitized/sanitization_information_test.cc",
     ]
-  } else {
+  } else if (!crashpad_is_ios) {
     sources += [ "crashpad_info_client_options_test.cc" ]
   }
 
+  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
+      crashpad_is_win) {
+    sources += [ "crashpad_types/crashpad_info_reader_test.cc" ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
     sources += [
-      "crashpad_types/crashpad_info_reader_test.cc",
       "crashpad_types/image_annotation_reader_test.cc",
       "elf/elf_image_reader_test.cc",
       "elf/elf_image_reader_test_note.S",
-      "elf/test_exported_symbols.sym",
     ]
   }
 
   if (crashpad_is_win) {
     sources += [
-      "api/module_annotations_win_test.cc",
       "win/cpu_context_win_test.cc",
       "win/exception_snapshot_win_test.cc",
       "win/extra_memory_ranges_test.cc",
-      "win/pe_image_annotations_reader_test.cc",
+      "win/module_snapshot_win_test.cc",
       "win/pe_image_reader_test.cc",
       "win/process_reader_win_test.cc",
       "win/process_snapshot_win_test.cc",
@@ -377,7 +410,10 @@
   }
 
   if (crashpad_is_fuchsia) {
-    sources += [ "fuchsia/process_reader_fuchsia_test.cc" ]
+    sources += [
+      "fuchsia/process_reader_fuchsia_test.cc",
+      "fuchsia/process_snapshot_fuchsia_test.cc",
+    ]
   }
 
   # public_configs isn’t quite right. snapshot_test_link sets ldflags, and
@@ -387,10 +423,10 @@
   public_configs = [ ":snapshot_test_link" ]
 
   deps = [
-    ":snapshot_api",
     ":test_support",
     "../client",
     "../compat",
+    "../minidump:format",
     "../test",
     "../third_party/gtest:gtest",
     "../third_party/mini_chromium:base",
@@ -437,9 +473,7 @@
 
 crashpad_loadable_module("crashpad_snapshot_test_module") {
   testonly = true
-  sources = [
-    "crashpad_info_client_options_test_module.cc",
-  ]
+  sources = [ "crashpad_info_client_options_test_module.cc" ]
   deps = [
     "../client",
     "../third_party/mini_chromium:base",
@@ -448,9 +482,7 @@
 
 crashpad_loadable_module("crashpad_snapshot_test_module_large") {
   testonly = true
-  sources = [
-    "crashpad_info_size_test_module.cc",
-  ]
+  sources = [ "crashpad_info_size_test_module.cc" ]
 
   deps = []
   if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
@@ -464,9 +496,7 @@
 
 crashpad_loadable_module("crashpad_snapshot_test_module_small") {
   testonly = true
-  sources = [
-    "crashpad_info_size_test_module.cc",
-  ]
+  sources = [ "crashpad_info_size_test_module.cc" ]
 
   deps = []
   if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
@@ -482,9 +512,7 @@
     target_cpu != "mipsel" && target_cpu != "mips64el") {
   crashpad_loadable_module("crashpad_snapshot_test_both_dt_hash_styles") {
     testonly = true
-    sources = [
-      "hash_types_test.cc",
-    ]
+    sources = [ "hash_types_test.cc" ]
 
     # This makes `ld` emit both .hash and .gnu.hash sections.
     ldflags = [ "-Wl,--hash-style=both" ]
@@ -501,18 +529,14 @@
 
   crashpad_executable("crashpad_snapshot_test_no_op") {
     testonly = true
-    sources = [
-      "mac/mach_o_image_annotations_reader_test_no_op.cc",
-    ]
+    sources = [ "mac/mach_o_image_annotations_reader_test_no_op.cc" ]
   }
 }
 
 if (crashpad_is_win) {
   crashpad_executable("crashpad_snapshot_test_annotations") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_annotations.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_annotations.cc" ]
     deps = [
       "../client",
       "../compat",
@@ -522,9 +546,7 @@
 
   crashpad_executable("crashpad_snapshot_test_crashing_child") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_crashing_child.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_crashing_child.cc" ]
     deps = [
       "../client",
       "../compat",
@@ -535,9 +557,7 @@
 
   crashpad_executable("crashpad_snapshot_test_dump_without_crashing") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_dump_without_crashing.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_dump_without_crashing.cc" ]
     deps = [
       "../client",
       "../compat",
@@ -548,9 +568,7 @@
 
   crashpad_executable("crashpad_snapshot_test_extra_memory_ranges") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_extra_memory_ranges.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_extra_memory_ranges.cc" ]
     deps = [
       "../client",
       "../compat",
@@ -560,9 +578,7 @@
 
   crashpad_executable("crashpad_snapshot_test_image_reader") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_image_reader.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_image_reader.cc" ]
     deps = [
       "../client",
       "../compat",
@@ -581,9 +597,7 @@
 
   crashpad_loadable_module("crashpad_snapshot_test_image_reader_module") {
     testonly = true
-    sources = [
-      "win/crashpad_snapshot_test_image_reader_module.cc",
-    ]
+    sources = [ "win/crashpad_snapshot_test_image_reader_module.cc" ]
     deps = [
       "../client",
       "../third_party/mini_chromium:base",
diff --git a/snapshot/api/module_annotations_win.cc b/snapshot/api/module_annotations_win.cc
deleted file mode 100644
index fd46870..0000000
--- a/snapshot/api/module_annotations_win.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 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.
-
-#include "snapshot/api/module_annotations_win.h"
-
-#include "snapshot/win/pe_image_annotations_reader.h"
-#include "snapshot/win/pe_image_reader.h"
-#include "snapshot/win/process_reader_win.h"
-#include "util/misc/from_pointer_cast.h"
-#include "util/win/get_module_information.h"
-
-namespace crashpad {
-
-bool ReadModuleAnnotations(HANDLE process,
-                           HMODULE module,
-                           std::map<std::string, std::string>* annotations) {
-  ProcessReaderWin process_reader;
-  if (!process_reader.Initialize(process, ProcessSuspensionState::kRunning))
-    return false;
-
-  MODULEINFO module_info;
-  if (!CrashpadGetModuleInformation(
-          process, module, &module_info, sizeof(module_info))) {
-    PLOG(ERROR) << "CrashpadGetModuleInformation";
-    return false;
-  }
-
-  PEImageReader image_reader;
-  if (!image_reader.Initialize(
-          &process_reader,
-          FromPointerCast<WinVMAddress>(module_info.lpBaseOfDll),
-          module_info.SizeOfImage,
-          ""))
-    return false;
-
-  PEImageAnnotationsReader annotations_reader(
-      &process_reader, &image_reader, L"");
-
-  *annotations = annotations_reader.SimpleMap();
-  return true;
-}
-
-}  // namespace crashpad
diff --git a/snapshot/api/module_annotations_win.h b/snapshot/api/module_annotations_win.h
deleted file mode 100644
index e024031..0000000
--- a/snapshot/api/module_annotations_win.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2016 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_API_MODULE_ANNOTATIONS_WIN_H_
-#define CRASHPAD_SNAPSHOT_API_MODULE_ANNOTATIONS_WIN_H_
-
-#include <windows.h>
-
-#include <map>
-#include <string>
-
-namespace crashpad {
-
-//! \brief Reads the module annotations from another process.
-//!
-//! \param[in] process The handle to the process that hosts the \a module.
-//!     Requires PROCESS_QUERY_INFORMATION and PROCESS_VM_READ accesses.
-//! \param[in] module The handle to the module from which the \a annotations
-//!     will be read. This module should be loaded in the target process.
-//! \param[out] annotations The map that will be filled with the annotations.
-//!     Remains unchanged if the function returns 'false'.
-//!
-//! \return `true` if the annotations could be read succesfully, even if the
-//!     module doesn't contain any annotations.
-bool ReadModuleAnnotations(HANDLE process,
-                           HMODULE module,
-                           std::map<std::string, std::string>* annotations);
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_SNAPSHOT_API_MODULE_ANNOTATIONS_WIN_H_
diff --git a/snapshot/api/module_annotations_win_test.cc b/snapshot/api/module_annotations_win_test.cc
deleted file mode 100644
index ecfa465..0000000
--- a/snapshot/api/module_annotations_win_test.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2016 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.
-
-#include "snapshot/api/module_annotations_win.h"
-
-#include "client/crashpad_info.h"
-#include "gtest/gtest.h"
-#include "test/win/win_multiprocess.h"
-#include "util/file/file_io.h"
-
-namespace crashpad {
-namespace test {
-namespace {
-
-class ModuleAnnotationsMultiprocessTest final : public WinMultiprocess {
- private:
-  void WinMultiprocessParent() override {
-    // Read the child executable module.
-    HMODULE module = nullptr;
-    CheckedReadFileExactly(ReadPipeHandle(), &module, sizeof(module));
-
-    // Reopen the child process with necessary access.
-    HANDLE process_handle =
-        OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
-                    FALSE,
-                    GetProcessId(ChildProcess()));
-    EXPECT_TRUE(process_handle);
-
-    // Read the module annotations in the child process and verify them.
-    std::map<std::string, std::string> annotations;
-    ASSERT_TRUE(ReadModuleAnnotations(process_handle, module, &annotations));
-
-    EXPECT_GE(annotations.size(), 3u);
-    EXPECT_EQ(annotations["#APITEST# key"], "value");
-    EXPECT_EQ(annotations["#APITEST# x"], "y");
-    EXPECT_EQ(annotations["#APITEST# empty_value"], "");
-
-    // Signal the child process to terminate.
-    char c = ' ';
-    CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
-  }
-
-  void WinMultiprocessChild() override {
-    // Set some test annotations.
-    crashpad::CrashpadInfo* crashpad_info =
-        crashpad::CrashpadInfo::GetCrashpadInfo();
-
-    crashpad::SimpleStringDictionary* simple_annotations =
-        new crashpad::SimpleStringDictionary();
-    simple_annotations->SetKeyValue("#APITEST# key", "value");
-    simple_annotations->SetKeyValue("#APITEST# x", "y");
-    simple_annotations->SetKeyValue("#APITEST# empty_value", "");
-
-    crashpad_info->set_simple_annotations(simple_annotations);
-
-    // Send the executable module.
-    HMODULE module = GetModuleHandle(nullptr);
-    CheckedWriteFile(WritePipeHandle(), &module, sizeof(module));
-
-    // Wait until a signal from the parent process to terminate.
-    char c;
-    CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c));
-  }
-};
-
-TEST(ModuleAnnotationsWin, ReadAnnotations) {
-  WinMultiprocess::Run<ModuleAnnotationsMultiprocessTest>();
-}
-
-}  // namespace
-}  // namespace test
-}  // namespace crashpad
diff --git a/snapshot/capture_memory.cc b/snapshot/capture_memory.cc
index 98f400e..a51626c 100644
--- a/snapshot/capture_memory.cc
+++ b/snapshot/capture_memory.cc
@@ -19,6 +19,7 @@
 #include <limits>
 #include <memory>
 
+#include "base/stl_util.h"
 #include "snapshot/memory_snapshot.h"
 
 namespace crashpad {
@@ -97,17 +98,17 @@
 #elif defined(ARCH_CPU_ARM_FAMILY)
   if (context.architecture == kCPUArchitectureARM64) {
     MaybeCaptureMemoryAround(delegate, context.arm64->pc);
-    for (size_t i = 0; i < arraysize(context.arm64->regs); ++i) {
+    for (size_t i = 0; i < base::size(context.arm64->regs); ++i) {
       MaybeCaptureMemoryAround(delegate, context.arm64->regs[i]);
     }
   } else {
     MaybeCaptureMemoryAround(delegate, context.arm->pc);
-    for (size_t i = 0; i < arraysize(context.arm->regs); ++i) {
+    for (size_t i = 0; i < base::size(context.arm->regs); ++i) {
       MaybeCaptureMemoryAround(delegate, context.arm->regs[i]);
     }
   }
 #elif defined(ARCH_CPU_MIPS_FAMILY)
-  for (size_t i = 0; i < arraysize(context.mipsel->regs); ++i) {
+  for (size_t i = 0; i < base::size(context.mipsel->regs); ++i) {
     MaybeCaptureMemoryAround(delegate, context.mipsel->regs[i]);
   }
 #else
diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc
index 4d7c1e5..6fb8d7e 100644
--- a/snapshot/cpu_context.cc
+++ b/snapshot/cpu_context.cc
@@ -18,7 +18,8 @@
 #include <string.h>
 
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
+#include "util/misc/arraysize.h"
 #include "util/misc/implicit_cast.h"
 
 namespace crashpad {
@@ -55,9 +56,9 @@
   fsave->fpu_dp = fxsave.fpu_dp;
   fsave->fpu_ds = fxsave.fpu_ds;
   fsave->reserved_4 = 0;
-  static_assert(arraysize(fsave->st) == arraysize(fxsave.st_mm),
+  static_assert(ArraySize(fsave->st) == ArraySize(fxsave.st_mm),
                 "FPU stack registers must be equivalent");
-  for (size_t index = 0; index < arraysize(fsave->st); ++index) {
+  for (size_t index = 0; index < base::size(fsave->st); ++index) {
     memcpy(fsave->st[index], fxsave.st_mm[index].st, sizeof(fsave->st[index]));
   }
 }
@@ -77,9 +78,9 @@
   fxsave->reserved_3 = 0;
   fxsave->mxcsr = 0;
   fxsave->mxcsr_mask = 0;
-  static_assert(arraysize(fxsave->st_mm) == arraysize(fsave.st),
+  static_assert(ArraySize(fxsave->st_mm) == ArraySize(fsave.st),
                 "FPU stack registers must be equivalent");
-  for (size_t index = 0; index < arraysize(fsave.st); ++index) {
+  for (size_t index = 0; index < base::size(fsave.st); ++index) {
     memcpy(fxsave->st_mm[index].st, fsave.st[index], sizeof(fsave.st[index]));
     memset(fxsave->st_mm[index].st_reserved,
            0,
diff --git a/snapshot/cpu_context.h b/snapshot/cpu_context.h
index 4dde943..fb23c46 100644
--- a/snapshot/cpu_context.h
+++ b/snapshot/cpu_context.h
@@ -299,7 +299,7 @@
   uint64_t regs[31];
   uint64_t sp;
   uint64_t pc;
-  uint64_t pstate;
+  uint32_t spsr;
 
   uint128_struct fpsimd[32];
   uint32_t fpsr;
diff --git a/snapshot/cpu_context_test.cc b/snapshot/cpu_context_test.cc
index 706f2fe..109510d 100644
--- a/snapshot/cpu_context_test.cc
+++ b/snapshot/cpu_context_test.cc
@@ -18,7 +18,7 @@
 #include <string.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "test/hex_string.h"
 
@@ -124,7 +124,7 @@
       &fxsave.st_mm[6].st, kExponentAllZero, false, kFractionAllZero);
   SetX87Register(
       &fxsave.st_mm[7].st, kExponentNormal, true, kFractionNormal);  // valid
-  for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) {
+  for (size_t index = 0; index < base::size(fxsave.st_mm); ++index) {
     memset(&fxsave.st_mm[index].st_reserved,
            0x5a,
            sizeof(fxsave.st_mm[index].st_reserved));
@@ -148,10 +148,10 @@
   EXPECT_EQ(fsave.fpu_dp, fxsave.fpu_dp);
   EXPECT_EQ(fsave.fpu_ds, fxsave.fpu_ds);
   EXPECT_EQ(fsave.reserved_4, 0);
-  for (size_t index = 0; index < arraysize(fsave.st); ++index) {
-    EXPECT_EQ(BytesToHexString(fsave.st[index], arraysize(fsave.st[index])),
+  for (size_t index = 0; index < base::size(fsave.st); ++index) {
+    EXPECT_EQ(BytesToHexString(fsave.st[index], base::size(fsave.st[index])),
               BytesToHexString(fxsave.st_mm[index].st,
-                               arraysize(fxsave.st_mm[index].st)))
+                               base::size(fxsave.st_mm[index].st)))
         << "index " << index;
   }
 }
@@ -204,14 +204,14 @@
   EXPECT_EQ(fxsave.reserved_3, 0);
   EXPECT_EQ(fxsave.mxcsr, 0u);
   EXPECT_EQ(fxsave.mxcsr_mask, 0u);
-  for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) {
+  for (size_t index = 0; index < base::size(fxsave.st_mm); ++index) {
     EXPECT_EQ(BytesToHexString(fxsave.st_mm[index].st,
-                               arraysize(fxsave.st_mm[index].st)),
-              BytesToHexString(fsave.st[index], arraysize(fsave.st[index])))
+                               base::size(fxsave.st_mm[index].st)),
+              BytesToHexString(fsave.st[index], base::size(fsave.st[index])))
         << "index " << index;
     EXPECT_EQ(BytesToHexString(fxsave.st_mm[index].st_reserved,
-                               arraysize(fxsave.st_mm[index].st_reserved)),
-              std::string(arraysize(fxsave.st_mm[index].st_reserved) * 2, '0'))
+                               base::size(fxsave.st_mm[index].st_reserved)),
+              std::string(base::size(fxsave.st_mm[index].st_reserved) * 2, '0'))
         << "index " << index;
   }
   size_t unused_len = sizeof(fxsave) - offsetof(decltype(fxsave), xmm);
@@ -318,7 +318,7 @@
   // In this set, everything is valid.
   fsw = 0 << 11;  // top = 0: logical 0-7 maps to physical 0-7
   fxsave_tag = 0xff;  // nothing empty
-  for (size_t index = 0; index < arraysize(st_mm); ++index) {
+  for (size_t index = 0; index < base::size(st_mm); ++index) {
     SetX87OrMMXRegister(&st_mm[index], kExponentNormal, true, kFractionAllZero);
   }
   EXPECT_EQ(CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm), 0);
diff --git a/snapshot/crashpad_info_client_options.cc b/snapshot/crashpad_info_client_options.cc
index 4a89d84..3a88a4c 100644
--- a/snapshot/crashpad_info_client_options.cc
+++ b/snapshot/crashpad_info_client_options.cc
@@ -15,7 +15,6 @@
 #include "snapshot/crashpad_info_client_options.h"
 
 #include "base/logging.h"
-#include "client/crashpad_info.h"
 
 namespace crashpad {
 
@@ -39,7 +38,8 @@
 CrashpadInfoClientOptions::CrashpadInfoClientOptions()
     : crashpad_handler_behavior(TriState::kUnset),
       system_crash_reporter_forwarding(TriState::kUnset),
-      gather_indirectly_referenced_memory(TriState::kUnset) {
+      gather_indirectly_referenced_memory(TriState::kUnset),
+      indirectly_referenced_memory_cap(0) {
 }
 
 }  // namespace crashpad
diff --git a/snapshot/crashpad_info_client_options_test.cc b/snapshot/crashpad_info_client_options_test.cc
index 1fe38ac..1c5759e 100644
--- a/snapshot/crashpad_info_client_options_test.cc
+++ b/snapshot/crashpad_info_client_options_test.cc
@@ -32,7 +32,7 @@
 #include <windows.h>
 #include "snapshot/win/process_snapshot_win.h"
 #elif defined(OS_FUCHSIA)
-#include <zircon/process.h>
+#include <lib/zx/process.h>
 #include "snapshot/fuchsia/process_snapshot_fuchsia.h"
 #endif
 
@@ -86,7 +86,7 @@
       GetCurrentProcess(), ProcessSuspensionState::kRunning, 0, 0));
 #elif defined(OS_FUCHSIA)
   ProcessSnapshotFuchsia process_snapshot;
-  EXPECT_TRUE(process_snapshot.Initialize(zx_process_self()));
+  EXPECT_TRUE(process_snapshot.Initialize(*zx::process::self()));
 #else
 #error Port.
 #endif  // OS_MACOSX
@@ -326,10 +326,10 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(CrashpadInfoSizes_ClientOptions,
-                        CrashpadInfoSizes_ClientOptions,
-                        testing::Values(FILE_PATH_LITERAL("small"),
-                                        FILE_PATH_LITERAL("large")));
+INSTANTIATE_TEST_SUITE_P(CrashpadInfoSizes_ClientOptions,
+                         CrashpadInfoSizes_ClientOptions,
+                         testing::Values(FILE_PATH_LITERAL("small"),
+                                         FILE_PATH_LITERAL("large")));
 
 }  // namespace
 }  // namespace test
diff --git a/snapshot/crashpad_info_size_test_note.S b/snapshot/crashpad_info_size_test_note.S
index 96b996d..16b5d49 100644
--- a/snapshot/crashpad_info_size_test_note.S
+++ b/snapshot/crashpad_info_size_test_note.S
@@ -26,9 +26,11 @@
 #define NOTE_ALIGN 4
 
   // This section must be "a"llocated so that it appears in the final binary at
-  // runtime, and "w"ritable so that the relocation to TEST_CRASHPAD_INFO_SYMBOL
-  // can be performed.
-  .section .note.crashpad.info,"aw",%note
+  // runtime. The reference to TEST_CRASHPAD_INFO_SYMBOL uses an offset relative
+  // to this note to avoid making this note writable, which triggers a bug in
+  // GNU ld, or adding text relocations which require the target system to allow
+  // making text segments writable. https://crbug.com/crashpad/260.
+  .section .note.crashpad.info,"a",%note
   .balign NOTE_ALIGN
   .type info_size_test_note, %object
 info_size_test_note:
@@ -41,15 +43,9 @@
   .balign NOTE_ALIGN
 desc:
 #if defined(__LP64__)
-  .quad TEST_CRASHPAD_INFO_SYMBOL
+  .quad TEST_CRASHPAD_INFO_SYMBOL - desc
 #else
-#if defined(__LITTLE_ENDIAN__)
-  .long TEST_CRASHPAD_INFO_SYMBOL
-  .long 0
-#else
-  .long 0
-  .long TEST_CRASHPAD_INFO_SYMBOL
-#endif  // __LITTLE_ENDIAN__
+  .long TEST_CRASHPAD_INFO_SYMBOL - desc
 #endif  // __LP64__
 desc_end:
   .size info_size_test_note, .-info_size_test_note
diff --git a/snapshot/crashpad_types/crashpad_info_reader.cc b/snapshot/crashpad_types/crashpad_info_reader.cc
index dfc438f..07a9b48 100644
--- a/snapshot/crashpad_types/crashpad_info_reader.cc
+++ b/snapshot/crashpad_types/crashpad_info_reader.cc
@@ -66,9 +66,8 @@
       return false;
     }
 
-    if (!memory->Read(address,
-                      std::min(VMSize{info.size}, VMSize{sizeof(info)}),
-                      &info)) {
+    if (!memory->Read(
+            address, std::min<VMSize>(info.size, sizeof(info)), &info)) {
       return false;
     }
 
@@ -116,7 +115,7 @@
 #define NATIVE_TRAITS Traits32
 #endif
   static_assert(!std::is_same<Traits, NATIVE_TRAITS>::value ||
-                    sizeof(info) == sizeof(CrashpadInfo),
+                    sizeof(decltype(info)) == sizeof(CrashpadInfo),
                 "CrashpadInfo size mismtach");
 #undef NATIVE_TRAITS
 };
@@ -161,29 +160,29 @@
     return GET_MEMBER(member);                       \
   }
 
-DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior);
+DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior)
 
 DEFINE_GETTER(TriState,
               SystemCrashReporterForwarding,
-              system_crash_reporter_forwarding);
+              system_crash_reporter_forwarding)
 
 DEFINE_GETTER(TriState,
               GatherIndirectlyReferencedMemory,
-              gather_indirectly_referenced_memory);
+              gather_indirectly_referenced_memory)
 
 DEFINE_GETTER(uint32_t,
               IndirectlyReferencedMemoryCap,
-              indirectly_referenced_memory_cap);
+              indirectly_referenced_memory_cap)
 
-DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges);
+DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges)
 
-DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations);
+DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations)
 
-DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list);
+DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list)
 
 DEFINE_GETTER(VMAddress,
               UserDataMinidumpStreamHead,
-              user_data_minidump_stream_head);
+              user_data_minidump_stream_head)
 
 #undef DEFINE_GETTER
 #undef GET_MEMBER
diff --git a/snapshot/crashpad_types/crashpad_info_reader_test.cc b/snapshot/crashpad_types/crashpad_info_reader_test.cc
index 87bafc6..ebc6a4d 100644
--- a/snapshot/crashpad_types/crashpad_info_reader_test.cc
+++ b/snapshot/crashpad_types/crashpad_info_reader_test.cc
@@ -15,7 +15,6 @@
 #include "snapshot/crashpad_types/crashpad_info_reader.h"
 
 #include <sys/types.h>
-#include <unistd.h>
 
 #include <memory>
 
@@ -31,10 +30,6 @@
 #include "util/misc/from_pointer_cast.h"
 #include "util/process/process_memory_native.h"
 
-#if defined(OS_FUCHSIA)
-#include <zircon/process.h>
-#endif
-
 namespace crashpad {
 namespace test {
 namespace {
diff --git a/snapshot/elf/elf_dynamic_array_reader.cc b/snapshot/elf/elf_dynamic_array_reader.cc
index 9a44e12..a39712a 100644
--- a/snapshot/elf/elf_dynamic_array_reader.cc
+++ b/snapshot/elf/elf_dynamic_array_reader.cc
@@ -42,9 +42,6 @@
     switch (entry.d_tag) {
       case DT_NULL:
         values->swap(local_values);
-        if (size != 0) {
-          LOG(WARNING) << size << " trailing bytes not read";
-        }
         return true;
       case DT_NEEDED:
         // Skip these entries for now.
diff --git a/snapshot/elf/elf_image_reader.cc b/snapshot/elf/elf_image_reader.cc
index bbf9b54..7f6d7c7 100644
--- a/snapshot/elf/elf_image_reader.cc
+++ b/snapshot/elf/elf_image_reader.cc
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/numerics/safe_math.h"
 #include "build/build_config.h"
 #include "util/numeric/checked_vm_address_range.h"
 
@@ -191,7 +192,8 @@
 ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote(
     std::string* name,
     NoteType* type,
-    std::string* desc) {
+    std::string* desc,
+    VMAddress* desc_address) {
   if (!is_valid_) {
     LOG(ERROR) << "invalid note reader";
     return Result::kError;
@@ -215,8 +217,9 @@
     }
 
     retry_ = false;
-    result = range_->Is64Bit() ? ReadNote<Elf64_Nhdr>(name, type, desc)
-                               : ReadNote<Elf32_Nhdr>(name, type, desc);
+    result = range_->Is64Bit()
+                 ? ReadNote<Elf64_Nhdr>(name, type, desc, desc_address)
+                 : ReadNote<Elf32_Nhdr>(name, type, desc, desc_address);
   } while (retry_);
 
   if (result == Result::kSuccess) {
@@ -226,10 +229,19 @@
   return Result::kError;
 }
 
+namespace {
+
+// The maximum size the user can specify for maximum note size. Clamping this
+// ensures that buffer allocations cannot be wildly large. It is not expected
+// that a note would be larger than ~1k in normal usage.
+constexpr size_t kMaxMaxNoteSize = 16384;
+
+}  // namespace
+
 ElfImageReader::NoteReader::NoteReader(const ElfImageReader* elf_reader,
                                        const ProcessMemoryRange* range,
                                        const ProgramHeaderTable* phdr_table,
-                                       ssize_t max_note_size,
+                                       size_t max_note_size,
                                        const std::string& name_filter,
                                        NoteType type_filter,
                                        bool use_filter)
@@ -240,18 +252,21 @@
       phdr_table_(phdr_table),
       segment_range_(),
       phdr_index_(0),
-      max_note_size_(max_note_size),
+      max_note_size_(std::min(kMaxMaxNoteSize, max_note_size)),
       name_filter_(name_filter),
       type_filter_(type_filter),
       use_filter_(use_filter),
       is_valid_(true),
-      retry_(false) {}
+      retry_(false) {
+  DCHECK_LT(max_note_size, kMaxMaxNoteSize);
+}
 
 template <typename NhdrType>
 ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote(
     std::string* name,
     NoteType* type,
-    std::string* desc) {
+    std::string* desc,
+    VMAddress* desc_address) {
   static_assert(sizeof(*type) >= sizeof(NhdrType::n_namesz),
                 "Note field size mismatch");
   DCHECK_LT(current_address_, segment_end_address_);
@@ -263,10 +278,24 @@
   current_address_ += sizeof(note_info);
 
   constexpr size_t align = sizeof(note_info.n_namesz);
-#define PAD(x) (((x) + align - 1) & ~(align - 1))
-  size_t padded_namesz = PAD(note_info.n_namesz);
-  size_t padded_descsz = PAD(note_info.n_descsz);
-  size_t note_size = padded_namesz + padded_descsz;
+
+#define CHECKED_PAD(x, into)                                 \
+  base::CheckAnd(base::CheckAdd(x, align - 1), ~(align - 1)) \
+      .AssignIfValid(&into)
+
+  size_t padded_namesz;
+  if (!CHECKED_PAD(note_info.n_namesz, padded_namesz)) {
+    return Result::kError;
+  }
+  size_t padded_descsz;
+  if (!CHECKED_PAD(note_info.n_descsz, padded_descsz)) {
+    return Result::kError;
+  }
+
+  size_t note_size;
+  if (!base::CheckAdd(padded_namesz, padded_descsz).AssignIfValid(&note_size)) {
+    return Result::kError;
+  }
 
   // Notes typically have 4-byte alignment. However, .note.android.ident may
   // inadvertently use 2-byte alignment.
@@ -275,11 +304,21 @@
   // but there may be 4-byte aligned notes following it. If this note was
   // aligned at less than 4-bytes, expect that the next note will be aligned at
   // 4-bytes and add extra padding, if necessary.
-  VMAddress end_of_note =
-      std::min(PAD(current_address_ + note_size), segment_end_address_);
-#undef PAD
 
-  if (max_note_size_ >= 0 && note_size > static_cast<size_t>(max_note_size_)) {
+  VMAddress end_of_note_candidate;
+  if (!base::CheckAdd(current_address_, note_size)
+           .AssignIfValid(&end_of_note_candidate)) {
+    return Result::kError;
+  }
+  VMAddress end_of_note;
+  if (!CHECKED_PAD(end_of_note_candidate, end_of_note)) {
+    return Result::kError;
+  }
+  end_of_note = std::min(end_of_note, segment_end_address_);
+
+#undef CHECKED_PAD
+
+  if (note_size > max_note_size_) {
     current_address_ = end_of_note;
     retry_ = true;
     return Result::kError;
@@ -317,6 +356,7 @@
           current_address_, note_info.n_descsz, &local_desc[0])) {
     return Result::kError;
   }
+  *desc_address = current_address_;
 
   current_address_ = end_of_note;
 
@@ -467,6 +507,20 @@
   return memory_.Is64Bit() ? header_64_.e_type : header_32_.e_type;
 }
 
+bool ElfImageReader::SoName(std::string* name) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  if (!InitializeDynamicArray()) {
+    return false;
+  }
+
+  VMSize offset;
+  if (!dynamic_array_->GetValue(DT_SONAME, true, &offset)) {
+    return false;
+  }
+
+  return ReadDynamicStringTableAtOffset(offset, name);
+}
+
 bool ElfImageReader::GetDynamicSymbol(const std::string& name,
                                       VMAddress* address,
                                       VMSize* size) {
@@ -772,7 +826,7 @@
 }
 
 std::unique_ptr<ElfImageReader::NoteReader> ElfImageReader::Notes(
-    ssize_t max_note_size) {
+    size_t max_note_size) {
   return std::make_unique<NoteReader>(
       this, &memory_, program_headers_.get(), max_note_size);
 }
@@ -780,7 +834,7 @@
 std::unique_ptr<ElfImageReader::NoteReader>
 ElfImageReader::NotesWithNameAndType(const std::string& name,
                                      NoteReader::NoteType type,
-                                     ssize_t max_note_size) {
+                                     size_t max_note_size) {
   return std::make_unique<NoteReader>(
       this, &memory_, program_headers_.get(), max_note_size, name, type, true);
 }
diff --git a/snapshot/elf/elf_image_reader.h b/snapshot/elf/elf_image_reader.h
index 29f8b1e..ab1799f 100644
--- a/snapshot/elf/elf_image_reader.h
+++ b/snapshot/elf/elf_image_reader.h
@@ -70,15 +70,20 @@
     //! \param[out] name The name of the note owner, if not `nullptr`.
     //! \param[out] type A type for the note, if not `nullptr`.
     //! \param[out] desc The note descriptor.
-    //! \return a #Result value. \a name, \a type, and \a desc are only valid if
-    //!     this method returns Result::kSuccess.
-    Result NextNote(std::string* name, NoteType* type, std::string* desc);
+    //! \param[out] desc_addr The address in the remote process' address space
+    //!     \a desc was read from.
+    //! \return a #Result value. \a name, \a type, \a desc, and \a desc_addr are
+    //!     only valid if this method returns Result::kSuccess.
+    Result NextNote(std::string* name,
+                    NoteType* type,
+                    std::string* desc,
+                    VMAddress* desc_addr);
 
     // private
     NoteReader(const ElfImageReader* elf_reader_,
                const ProcessMemoryRange* range,
                const ProgramHeaderTable* phdr_table,
-               ssize_t max_note_size,
+               size_t max_note_size,
                const std::string& name_filter = std::string(),
                NoteType type_filter = 0,
                bool use_filter = false);
@@ -88,7 +93,10 @@
     // and returns kError if use_filter_ is true and the note's name and type do
     // not match name_filter_ and type_filter_.
     template <typename T>
-    Result ReadNote(std::string* name, NoteType* type, std::string* desc);
+    Result ReadNote(std::string* name,
+                    NoteType* type,
+                    std::string* desc,
+                    VMAddress* desc_addr);
 
     VMAddress current_address_;
     VMAddress segment_end_address_;
@@ -97,7 +105,7 @@
     const ProgramHeaderTable* phdr_table_;  // weak
     std::unique_ptr<ProcessMemoryRange> segment_range_;
     size_t phdr_index_;
-    ssize_t max_note_size_;
+    size_t max_note_size_;
     std::string name_filter_;
     NoteType type_filter_;
     bool use_filter_;
@@ -149,6 +157,13 @@
   //! The load bias is the actual load address minus the preferred load address.
   VMOffset GetLoadBias() const { return load_bias_; }
 
+  //! \brief Determines the name of this object using `DT_SONAME`, if present.
+  //!
+  //! \param[out] name The name of this object, only valid if this method
+  //!     returns `true`.
+  //! \return `true` if a name was found for this object.
+  bool SoName(std::string* name);
+
   //! \brief Reads information from the dynamic symbol table about the symbol
   //!     identified by \a name.
   //!
@@ -196,10 +211,9 @@
   //!
   //! \param[in] max_note_size The maximum note size to read. Notes whose
   //!     combined name, descriptor, and padding size are greater than
-  //!     \a max_note_size will be silently skipped. A \a max_note_size of -1
-  //!     indicates infinite maximum note size.
+  //!     \a max_note_size will be silently skipped.
   //! \return A NoteReader object capable of reading notes in this image.
-  std::unique_ptr<NoteReader> Notes(ssize_t max_note_size);
+  std::unique_ptr<NoteReader> Notes(size_t max_note_size);
 
   //! \brief Return a NoteReader for this image, which scans all PT_NOTE
   //!     segments in the image, filtering by name and type.
@@ -211,12 +225,11 @@
   //! \param[in] type The note type to match.
   //! \param[in] max_note_size The maximum note size to read. Notes whose
   //!     combined name, descriptor, and padding size are greater than
-  //!     \a max_note_size will be silently skipped. A \a max_note_size of -1
-  //!     indicates infinite maximum note size.
+  //!     \a max_note_size will be silently skipped.
   //! \return A NoteReader object capable of reading notes in this image.
   std::unique_ptr<NoteReader> NotesWithNameAndType(const std::string& name,
                                                    NoteReader::NoteType type,
-                                                   ssize_t max_note_size);
+                                                   size_t max_note_size);
 
   //! \brief Return a ProcessMemoryRange restricted to the range of this image.
   //!
diff --git a/snapshot/elf/elf_image_reader_fuzzer.cc b/snapshot/elf/elf_image_reader_fuzzer.cc
index 1686650..b780af4 100644
--- a/snapshot/elf/elf_image_reader_fuzzer.cc
+++ b/snapshot/elf/elf_image_reader_fuzzer.cc
@@ -31,7 +31,8 @@
     VMAddress offset_in_data = address - fake_base_;
     if (offset_in_data > size_)
       return -1;
-    ssize_t read_size = std::min(size_ - offset_in_data, size);
+    size_t read_size =
+        std::min(static_cast<size_t>(size_ - offset_in_data), size);
     memcpy(buffer, &data_[offset_in_data], read_size);
     return read_size;
   }
@@ -65,8 +66,10 @@
   std::string note_name;
   std::string note_desc;
   ElfImageReader::NoteReader::NoteType note_type;
-  auto notes = reader.Notes(-1);
-  while ((result = notes->NextNote(&note_name, &note_type, &note_desc)) ==
+  VMAddress desc_addr;
+  auto notes = reader.Notes(9999);
+  while ((result = notes->NextNote(
+              &note_name, &note_type, &note_desc, &desc_addr)) ==
          ElfImageReader::NoteReader::Result::kSuccess) {
     LOG(ERROR) << note_name << note_type << note_desc;
   }
diff --git a/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes b/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes
new file mode 100644
index 0000000..69463d1
--- /dev/null
+++ b/snapshot/elf/elf_image_reader_fuzzer_corpus/.gitattributes
@@ -0,0 +1,17 @@
+# Copyright 2019 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.
+
+# ELF executables normally don’t have any extension, so there’s no pattern to
+# match in the root .gitattributes file.
+/ret42 binary
diff --git a/snapshot/elf/elf_image_reader_test.cc b/snapshot/elf/elf_image_reader_test.cc
index 11fb1fe..cf8f33b 100644
--- a/snapshot/elf/elf_image_reader_test.cc
+++ b/snapshot/elf/elf_image_reader_test.cc
@@ -32,8 +32,7 @@
 #include "util/process/process_memory_native.h"
 
 #if defined(OS_FUCHSIA)
-
-#include <zircon/syscalls.h>
+#include <lib/zx/process.h>
 
 #include "base/fuchsia/fuchsia_logging.h"
 
@@ -50,8 +49,8 @@
 #endif  // OS_FUCHSIA
 
 extern "C" {
-__attribute__((visibility("default"))) void
-ElfImageReaderTestExportedSymbol(){};
+__attribute__((visibility("default"))) void ElfImageReaderTestExportedSymbol() {
+}
 }  // extern "C"
 
 namespace crashpad {
@@ -61,14 +60,12 @@
 
 #if defined(OS_FUCHSIA)
 
-void LocateExecutable(ProcessType process,
+void LocateExecutable(const ProcessType& process,
                       ProcessMemory* memory,
                       VMAddress* elf_address) {
   uintptr_t debug_address;
-  zx_status_t status = zx_object_get_property(process,
-                                              ZX_PROP_PROCESS_DEBUG_ADDR,
-                                              &debug_address,
-                                              sizeof(debug_address));
+  zx_status_t status = process->get_property(
+      ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address));
   ASSERT_EQ(status, ZX_OK)
       << "zx_object_get_property: ZX_PROP_PROCESS_DEBUG_ADDR";
   // Can be 0 if requested before the loader has loaded anything.
@@ -103,10 +100,9 @@
   ASSERT_TRUE(memory_map.Initialize(connection));
   const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
   ASSERT_TRUE(phdr_mapping);
-  std::vector<const MemoryMap::Mapping*> possible_mappings =
-      memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
-  ASSERT_EQ(possible_mappings.size(), 1u);
-  *elf_address = possible_mappings[0]->range.Base();
+  auto possible_mappings = memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
+  ASSERT_EQ(possible_mappings->Count(), 1u);
+  *elf_address = possible_mappings->Next()->range.Base();
 }
 
 #endif  // OS_FUCHSIA
@@ -157,22 +153,24 @@
   std::string note_name;
   std::string note_desc;
   ElfImageReader::NoteReader::NoteType note_type;
+  VMAddress desc_addr;
 
-  std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(-1);
-  while ((result = notes->NextNote(&note_name, &note_type, &note_desc)) ==
+  std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(10000);
+  while ((result = notes->NextNote(
+              &note_name, &note_type, &note_desc, &desc_addr)) ==
          ElfImageReader::NoteReader::Result::kSuccess) {
   }
   EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes);
 
   notes = reader.Notes(0);
-  EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
+  EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
             ElfImageReader::NoteReader::Result::kNoMoreNotes);
 
   // Find the note defined in elf_image_reader_test_note.S.
   constexpr uint32_t kCrashpadNoteDesc = 42;
   notes = reader.NotesWithNameAndType(
-      CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, -1);
-  ASSERT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
+      CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, 10000);
+  ASSERT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
             ElfImageReader::NoteReader::Result::kSuccess);
   EXPECT_EQ(note_name, CRASHPAD_ELF_NOTE_NAME);
   EXPECT_EQ(note_type,
@@ -181,7 +179,7 @@
   EXPECT_EQ(*reinterpret_cast<decltype(kCrashpadNoteDesc)*>(&note_desc[0]),
             kCrashpadNoteDesc);
 
-  EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc),
+  EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
             ElfImageReader::NoteReader::Result::kNoMoreNotes);
 }
 
diff --git a/snapshot/elf/module_snapshot_elf.cc b/snapshot/elf/module_snapshot_elf.cc
index 62a961d..037e7cd 100644
--- a/snapshot/elf/module_snapshot_elf.cc
+++ b/snapshot/elf/module_snapshot_elf.cc
@@ -20,6 +20,7 @@
 
 #include "base/files/file_path.h"
 #include "snapshot/crashpad_types/image_annotation_reader.h"
+#include "snapshot/memory_snapshot_generic.h"
 #include "util/misc/elf_note_types.h"
 
 namespace crashpad {
@@ -28,14 +29,17 @@
 ModuleSnapshotElf::ModuleSnapshotElf(const std::string& name,
                                      ElfImageReader* elf_reader,
                                      ModuleSnapshot::ModuleType type,
-                                     ProcessMemoryRange* process_memory_range)
+                                     ProcessMemoryRange* process_memory_range,
+                                     const ProcessMemory* process_memory)
     : ModuleSnapshot(),
       name_(name),
       elf_reader_(elf_reader),
       process_memory_range_(process_memory_range),
+      process_memory_(process_memory),
       crashpad_info_(),
       type_(type),
-      initialized_() {}
+      initialized_(),
+      streams_() {}
 
 ModuleSnapshotElf::~ModuleSnapshotElf() = default;
 
@@ -56,9 +60,17 @@
                                         kMaxNoteSize);
   std::string desc;
   VMAddress info_address;
-  if (notes->NextNote(nullptr, nullptr, &desc) ==
+  VMAddress desc_address;
+  if (notes->NextNote(nullptr, nullptr, &desc, &desc_address) ==
       ElfImageReader::NoteReader::Result::kSuccess) {
-    info_address = *reinterpret_cast<VMAddress*>(&desc[0]);
+    VMOffset offset;
+    if (elf_reader_->Memory()->Is64Bit()) {
+      offset = *reinterpret_cast<VMOffset*>(&desc[0]);
+    } else {
+      int32_t offset32 = *reinterpret_cast<int32_t*>(&desc[0]);
+      offset = offset32;
+    }
+    info_address = desc_address + offset;
 
     ProcessMemoryRange range;
     if (range.Initialize(*elf_reader_->Memory())) {
@@ -142,12 +154,10 @@
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   *age = 0;
 
-  std::unique_ptr<ElfImageReader::NoteReader> notes =
-      elf_reader_->NotesWithNameAndType(ELF_NOTE_GNU, NT_GNU_BUILD_ID, 64);
-  std::string desc;
-  notes->NextNote(nullptr, nullptr, &desc);
-  desc.insert(desc.end(), 16 - std::min(desc.size(), size_t{16}), '\0');
-  uuid->InitializeFromBytes(reinterpret_cast<const uint8_t*>(&desc[0]));
+  auto build_id = BuildID();
+  build_id.insert(
+      build_id.end(), 16 - std::min(build_id.size(), size_t{16}), '\0');
+  uuid->InitializeFromBytes(build_id.data());
 
   // TODO(scottmg): https://crashpad.chromium.org/bug/229. These are
   // endian-swapped to match FileID::ConvertIdentifierToUUIDString() in
@@ -162,6 +172,21 @@
   return base::FilePath(Name()).BaseName().value();
 }
 
+std::vector<uint8_t> ModuleSnapshotElf::BuildID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  std::unique_ptr<ElfImageReader::NoteReader> notes =
+      elf_reader_->NotesWithNameAndType(ELF_NOTE_GNU, NT_GNU_BUILD_ID, 64);
+  std::string desc;
+  VMAddress desc_addr;
+  notes->NextNote(nullptr, nullptr, &desc, &desc_addr);
+
+  std::vector<uint8_t> build_id;
+  build_id.reserve(desc.size());
+  std::copy(desc.begin(), desc.end(), std::back_inserter(build_id));
+  return build_id;
+}
+
 std::vector<std::string> ModuleSnapshotElf::AnnotationsVector() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return std::vector<std::string>();
@@ -195,7 +220,33 @@
 
 std::vector<const UserMinidumpStream*>
 ModuleSnapshotElf::CustomMinidumpStreams() const {
-  return std::vector<const UserMinidumpStream*>();
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  streams_.clear();
+
+  std::vector<const UserMinidumpStream*> result;
+  if (!crashpad_info_)
+    return result;
+
+  for (uint64_t cur = crashpad_info_->UserDataMinidumpStreamHead(); cur;) {
+    internal::UserDataMinidumpStreamListEntry list_entry;
+    if (!process_memory_->Read(cur, sizeof(list_entry), &list_entry)) {
+      LOG(WARNING) << "could not read user data stream entry from " << name_;
+      return result;
+    }
+
+    if (list_entry.size != 0) {
+      auto memory = std::make_unique<internal::MemorySnapshotGeneric>();
+      memory->Initialize(
+          process_memory_, list_entry.base_address, list_entry.size);
+      streams_.push_back(std::make_unique<UserMinidumpStream>(
+          list_entry.stream_type, memory.release()));
+      result.push_back(streams_.back().get());
+    }
+
+    cur = list_entry.next;
+  }
+
+  return result;
 }
 
 }  // namespace internal
diff --git a/snapshot/elf/module_snapshot_elf.h b/snapshot/elf/module_snapshot_elf.h
index 15c3f3c..67ecdf7 100644
--- a/snapshot/elf/module_snapshot_elf.h
+++ b/snapshot/elf/module_snapshot_elf.h
@@ -41,11 +41,15 @@
   //! \param[in] name The pathname used to load the module from disk.
   //! \param[in] elf_reader An image reader for the module.
   //! \param[in] type The module's type.
-  //! \param[in] process_memory_range A memory reader for the target process.
+  //! \param[in] process_memory_range A memory reader giving protected access
+  //!     to the target process.
+  //! \param[in] process_memory A memory reader for the target process which can
+  //!     be used to initialize a MemorySnapshot.
   ModuleSnapshotElf(const std::string& name,
                     ElfImageReader* elf_reader,
                     ModuleSnapshot::ModuleType type,
-                    ProcessMemoryRange* process_memory_range);
+                    ProcessMemoryRange* process_memory_range,
+                    const ProcessMemory* process_memory);
   ~ModuleSnapshotElf() override;
 
   //! \brief Initializes the object.
@@ -77,6 +81,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
@@ -87,9 +92,12 @@
   std::string name_;
   ElfImageReader* elf_reader_;
   ProcessMemoryRange* process_memory_range_;
+  const ProcessMemory* process_memory_;
   std::unique_ptr<CrashpadInfoReader> crashpad_info_;
   ModuleType type_;
   InitializationStateDcheck initialized_;
+  // Too const-y: https://crashpad.chromium.org/bug/9.
+  mutable std::vector<std::unique_ptr<const UserMinidumpStream>> streams_;
 
   DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotElf);
 };
diff --git a/snapshot/fuchsia/cpu_context_fuchsia.cc b/snapshot/fuchsia/cpu_context_fuchsia.cc
index 5a02f62..226bfc6 100644
--- a/snapshot/fuchsia/cpu_context_fuchsia.cc
+++ b/snapshot/fuchsia/cpu_context_fuchsia.cc
@@ -21,7 +21,7 @@
 
 #if defined(ARCH_CPU_X86_64)
 
-void InitializeCPUContextX86_64(
+void InitializeCPUContextX86_64_NoFloatingPoint(
     const zx_thread_state_general_regs_t& thread_context,
     CPUContextX86_64* context) {
   memset(context, 0, sizeof(*context));
@@ -45,6 +45,50 @@
   context->rflags = thread_context.rflags;
 }
 
+#elif defined(ARCH_CPU_ARM64)
+
+void InitializeCPUContextARM64(
+    const zx_thread_state_general_regs_t& thread_context,
+    const zx_thread_state_vector_regs_t& vector_context,
+    CPUContextARM64* context) {
+  memset(context, 0, sizeof(*context));
+
+  // Fuchsia stores the link register (x30) on its own while Crashpad stores it
+  // with the other general purpose x0-x28 and x29 frame pointer registers. So
+  // we expect the size and number of elements to be off by one unit.
+  static_assert(sizeof(context->regs) - sizeof(context->regs[30]) ==
+                    sizeof(thread_context.r),
+                "registers size mismatch");
+  static_assert((sizeof(context->regs) - sizeof(context->regs[30])) /
+                        sizeof(context->regs[0]) ==
+                    sizeof(thread_context.r) / sizeof(thread_context.r[0]),
+                "registers number of elements mismatch");
+  memcpy(&context->regs, &thread_context.r, sizeof(thread_context.r));
+  context->regs[30] = thread_context.lr;
+  context->sp = thread_context.sp;
+  context->pc = thread_context.pc;
+
+  // Only the NZCV flags (bits 31 to 28 respectively) of the cpsr register are
+  // readable and writable by userland on ARM64.
+  constexpr uint32_t kNZCV = 0xf0000000;
+  // Fuchsia uses the old "cspr" terminology from armv7 while Crashpad uses the
+  // new "spsr" terminology for armv8.
+  context->spsr = thread_context.cpsr & kNZCV;
+  if (thread_context.cpsr >
+      std::numeric_limits<decltype(context->spsr)>::max()) {
+    LOG(WARNING) << "cpsr truncation: we only expect the first 32 bits to be "
+                    "set in the cpsr";
+  }
+  context->spsr =
+      static_cast<decltype(context->spsr)>(thread_context.cpsr) & kNZCV;
+
+  context->fpcr = vector_context.fpcr;
+  context->fpsr = vector_context.fpsr;
+  static_assert(sizeof(context->fpsimd) == sizeof(vector_context.v),
+                "registers size mismatch");
+  memcpy(&context->fpsimd, &vector_context.v, sizeof(vector_context.v));
+}
+
 #endif  // ARCH_CPU_X86_64
 
 }  // namespace internal
diff --git a/snapshot/fuchsia/cpu_context_fuchsia.h b/snapshot/fuchsia/cpu_context_fuchsia.h
index f5336fd..9227bfc 100644
--- a/snapshot/fuchsia/cpu_context_fuchsia.h
+++ b/snapshot/fuchsia/cpu_context_fuchsia.h
@@ -34,12 +34,28 @@
 //!
 //! \param[in] thread_context The native thread context.
 //! \param[out] context The CPUContextX86_64 structure to initialize.
-void InitializeCPUContextX86_64(
+void InitializeCPUContextX86_64_NoFloatingPoint(
     const zx_thread_state_general_regs_t& thread_context,
     CPUContextX86_64* context);
 
 #endif  // ARCH_CPU_X86_64 || DOXYGEN
 
+#if defined(ARCH_CPU_ARM64) || DOXYGEN
+
+//! \brief Initializes a CPUContextARM64 structure from native context
+//!     structures on Fuchsia.
+//!
+//! \param[in] thread_context The native thread context.
+//! \param[in] vector_context The native vector context that also contains the
+//!                           floating point registers.
+//! \param[out] context The CPUContextARM64 structure to initialize.
+void InitializeCPUContextARM64(
+    const zx_thread_state_general_regs_t& thread_context,
+    const zx_thread_state_vector_regs_t& vector_context,
+    CPUContextARM64* context);
+
+#endif  // ARCH_CPU_ARM64 || DOXYGEN
+
 }  // namespace internal
 }  // namespace crashpad
 
diff --git a/snapshot/fuchsia/exception_snapshot_fuchsia.cc b/snapshot/fuchsia/exception_snapshot_fuchsia.cc
index 44b4e5c..f70b966 100644
--- a/snapshot/fuchsia/exception_snapshot_fuchsia.cc
+++ b/snapshot/fuchsia/exception_snapshot_fuchsia.cc
@@ -59,13 +59,14 @@
 #if defined(ARCH_CPU_X86_64)
       context_.architecture = kCPUArchitectureX86_64;
       context_.x86_64 = &context_arch_;
-      // TODO(scottmg): Float context, once Fuchsia has a debug API to capture
-      // floating point registers. ZX-1750 upstream.
-      InitializeCPUContextX86_64(t.general_registers, context_.x86_64);
+      // TODO(fuchsia/DX-642): Add float context once saved in |t|.
+      InitializeCPUContextX86_64_NoFloatingPoint(t.general_registers,
+                                                 context_.x86_64);
 #elif defined(ARCH_CPU_ARM64)
       context_.architecture = kCPUArchitectureARM64;
       context_.arm64 = &context_arch_;
-      // TODO(scottmg): Implement context capture for arm64.
+      InitializeCPUContextARM64(
+          t.general_registers, t.vector_registers, context_.arm64);
 #else
 #error Port.
 #endif
@@ -85,7 +86,6 @@
 #endif
   }
 
-
   INITIALIZATION_STATE_SET_VALID(initialized_);
 }
 
diff --git a/snapshot/fuchsia/memory_map_fuchsia.cc b/snapshot/fuchsia/memory_map_fuchsia.cc
index b60531d..1586792 100644
--- a/snapshot/fuchsia/memory_map_fuchsia.cc
+++ b/snapshot/fuchsia/memory_map_fuchsia.cc
@@ -14,8 +14,6 @@
 
 #include "snapshot/fuchsia/memory_map_fuchsia.h"
 
-#include <zircon/syscalls.h>
-
 #include "base/fuchsia/fuchsia_logging.h"
 #include "util/numeric/checked_range.h"
 
@@ -25,7 +23,7 @@
 
 MemoryMapFuchsia::~MemoryMapFuchsia() = default;
 
-bool MemoryMapFuchsia::Initialize(zx_handle_t process) {
+bool MemoryMapFuchsia::Initialize(const zx::process& process) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   // There's no way to know what an appropriate buffer size is before starting.
@@ -40,12 +38,11 @@
     size_t actual;
     size_t available;
     zx_status_t status =
-        zx_object_get_info(process,
-                           ZX_INFO_PROCESS_MAPS,
-                           &map_entries_[0],
-                           map_entries_.size() * sizeof(map_entries_[0]),
-                           &actual,
-                           &available);
+        process.get_info(ZX_INFO_PROCESS_MAPS,
+                         &map_entries_[0],
+                         map_entries_.size() * sizeof(map_entries_[0]),
+                         &actual,
+                         &available);
     if (status != ZX_OK) {
       ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_MAPS";
       map_entries_.clear();
diff --git a/snapshot/fuchsia/memory_map_fuchsia.h b/snapshot/fuchsia/memory_map_fuchsia.h
index 88df569..cdbdb17 100644
--- a/snapshot/fuchsia/memory_map_fuchsia.h
+++ b/snapshot/fuchsia/memory_map_fuchsia.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_
 #define CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_
 
+#include <lib/zx/process.h>
 #include <zircon/syscalls/object.h>
 
 #include <vector>
@@ -33,7 +34,7 @@
   //!     regions in the given process.
   //!
   //! \return `true` on success, or `false`, with an error logged.
-  bool Initialize(zx_handle_t process);
+  bool Initialize(const zx::process& process);
 
   //! \brief Searches through the previously retrieved memory map for the given
   //!     address. If found, returns the deepest `zx_info_maps_t` mapping that
@@ -45,6 +46,10 @@
   //!     will be filled out, otherwise `false` and \a map will be unchanged.
   bool FindMappingForAddress(zx_vaddr_t address, zx_info_maps_t* map) const;
 
+  //! \brief Get a vector of `zx_info_maps_t` representing the memory map for
+  //!     this process.
+  const std::vector<zx_info_maps_t>& Entries() const { return map_entries_; }
+
  private:
   std::vector<zx_info_maps_t> map_entries_;
   InitializationStateDcheck initialized_;
diff --git a/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc
new file mode 100644
index 0000000..1133c4d
--- /dev/null
+++ b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.cc
@@ -0,0 +1,84 @@
+// 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.
+
+#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace crashpad {
+namespace internal {
+
+namespace {
+
+// Maps from bitwise OR of Zircon's flags to enumerated Windows version.
+uint32_t MmuFlagsToProtectFlags(zx_vm_option_t flags) {
+  // These bits are currently the lowest 3 of zx_vm_option_t. They're used to
+  // index into a mapping array, so make sure that we notice if they change
+  // values.
+  static_assert(
+      ZX_VM_PERM_READ == 1 && ZX_VM_PERM_WRITE == 2 && ZX_VM_PERM_EXECUTE == 4,
+      "table below will need fixing");
+
+  // The entries set to zero don't have good corresponding Windows minidump
+  // names. They also aren't currently supported by the mapping syscall on
+  // Zircon, so DCHECK that they don't happen in practice. EXECUTE-only also
+  // cannot currently happen, but as that has a good mapping to the Windows
+  // value, leave it in place in case it's supported by the syscall in the
+  // future.
+  static constexpr uint32_t mapping[] = {
+      /* --- */ PAGE_NOACCESS,
+      /* --r */ PAGE_READONLY,
+      /* -w- */ 0,
+      /* -wr */ PAGE_READWRITE,
+      /* x-- */ PAGE_EXECUTE,
+      /* x-r */ PAGE_EXECUTE_READ,
+      /* xw- */ 0,
+      /* xwr */ PAGE_EXECUTE_READWRITE,
+  };
+
+  const uint32_t index =
+      flags & (ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE);
+  DCHECK_LT(index, base::size(mapping));
+
+  const uint32_t protect_flags = mapping[index];
+  DCHECK_NE(protect_flags, 0u);
+  return protect_flags;
+}
+
+}  // namespace
+
+MemoryMapRegionSnapshotFuchsia::MemoryMapRegionSnapshotFuchsia(
+    const zx_info_maps_t& info_map)
+    : memory_info_() {
+  DCHECK_EQ(info_map.type, ZX_INFO_MAPS_TYPE_MAPPING);
+
+  memory_info_.BaseAddress = info_map.base;
+  memory_info_.AllocationBase = info_map.base;
+  memory_info_.RegionSize = info_map.size;
+  memory_info_.State = MEM_COMMIT;
+  memory_info_.Protect = memory_info_.AllocationProtect =
+      MmuFlagsToProtectFlags(info_map.u.mapping.mmu_flags);
+  memory_info_.Type = MEM_MAPPED;
+}
+
+MemoryMapRegionSnapshotFuchsia::~MemoryMapRegionSnapshotFuchsia() {}
+
+const MINIDUMP_MEMORY_INFO&
+MemoryMapRegionSnapshotFuchsia::AsMinidumpMemoryInfo() const {
+  return memory_info_;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h
new file mode 100644
index 0000000..37f7a1a
--- /dev/null
+++ b/snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h
@@ -0,0 +1,39 @@
+// 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_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_
+#define CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_
+
+#include "snapshot/memory_map_region_snapshot.h"
+
+#include <zircon/syscalls/object.h>
+
+namespace crashpad {
+namespace internal {
+
+class MemoryMapRegionSnapshotFuchsia : public MemoryMapRegionSnapshot {
+ public:
+  explicit MemoryMapRegionSnapshotFuchsia(const zx_info_maps_t& info_map);
+  ~MemoryMapRegionSnapshotFuchsia() override;
+
+  virtual const MINIDUMP_MEMORY_INFO& AsMinidumpMemoryInfo() const override;
+
+ private:
+  MINIDUMP_MEMORY_INFO memory_info_;
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_REGION_SNAPSHOT_FUCHSIA_H_
diff --git a/snapshot/fuchsia/process_reader_fuchsia.cc b/snapshot/fuchsia/process_reader_fuchsia.cc
index 0a6a84b..4522e51 100644
--- a/snapshot/fuchsia/process_reader_fuchsia.cc
+++ b/snapshot/fuchsia/process_reader_fuchsia.cc
@@ -14,11 +14,11 @@
 
 #include "snapshot/fuchsia/process_reader_fuchsia.h"
 
+#include <lib/zx/thread.h>
 #include <link.h>
 #include <zircon/syscalls.h>
 
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/scoped_zx_handle.h"
 #include "base/logging.h"
 #include "util/fuchsia/koid_utilities.h"
 
@@ -53,16 +53,40 @@
   }
 
   if (range_with_sp.type != ZX_INFO_MAPS_TYPE_MAPPING) {
-    LOG(ERROR) << "stack range has unexpected type, continuing anyway";
+    LOG(ERROR) << "stack range has unexpected type " << range_with_sp.type
+               << ", aborting";
+    return;
   }
 
-  if (range_with_sp.u.mapping.mmu_flags & ZX_VM_FLAG_PERM_EXECUTE) {
+  if (range_with_sp.u.mapping.mmu_flags & ZX_VM_PERM_EXECUTE) {
     LOG(ERROR)
         << "stack range is unexpectedly marked executable, continuing anyway";
   }
 
+  // The stack covers [range_with_sp.base, range_with_sp.base +
+  // range_with_sp.size). The stack pointer (sp) can be anywhere in that range.
+  // It starts at the end of the range (range_with_sp.base + range_with_sp.size)
+  // and goes downwards until range_with_sp.base. Capture the part of the stack
+  // that is currently used: [sp, range_with_sp.base + range_with_sp.size).
+
+  // Capture up to kExtraCaptureSize additional bytes of stack, but only if
+  // present in the region that was already found.
+  constexpr uint64_t kExtraCaptureSize = 128;
+  const uint64_t start_address =
+      std::max(sp >= kExtraCaptureSize ? sp - kExtraCaptureSize : sp,
+               range_with_sp.base);
+  const size_t region_size =
+      range_with_sp.size - (start_address - range_with_sp.base);
+
+  // Because most Fuchsia processes use safestack, it is very unlikely that a
+  // stack this large would be valid. Even if it were, avoid creating
+  // unreasonably large dumps by artificially limiting the captured amount.
+  constexpr uint64_t kMaxStackCapture = 1048576u;
+  LOG_IF(ERROR, region_size > kMaxStackCapture)
+      << "clamping unexpectedly large stack capture of " << region_size;
+  const size_t clamped_region_size = std::min(region_size, kMaxStackCapture);
   stack_regions->push_back(
-      CheckedRange<zx_vaddr_t, size_t>(range_with_sp.base, range_with_sp.size));
+      CheckedRange<zx_vaddr_t, size_t>(start_address, clamped_region_size));
 
   // TODO(scottmg): https://crashpad.chromium.org/bug/196, once the retrievable
   // registers include FS and similar for ARM, retrieve the region for the
@@ -83,15 +107,13 @@
 
 ProcessReaderFuchsia::~ProcessReaderFuchsia() = default;
 
-bool ProcessReaderFuchsia::Initialize(zx_handle_t process) {
+bool ProcessReaderFuchsia::Initialize(const zx::process& process) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
-  process_ = process;
+  process_ = zx::unowned_process(process);
 
   process_memory_.reset(new ProcessMemoryFuchsia());
-  process_memory_->Initialize(process_);
-
-  memory_map_.Initialize(process_);
+  process_memory_->Initialize(*process_);
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
@@ -119,6 +141,16 @@
   return threads_;
 }
 
+const MemoryMapFuchsia* ProcessReaderFuchsia::MemoryMap() {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  if (!initialized_memory_map_) {
+    InitializeMemoryMap();
+  }
+
+  return memory_map_.get();
+}
+
 void ProcessReaderFuchsia::InitializeModules() {
   DCHECK(!initialized_modules_);
   DCHECK(modules_.empty());
@@ -130,27 +162,12 @@
   // retrieves (some of) the data into internal structures. It may be worth
   // trying to refactor/upstream some of this into Fuchsia.
 
-  std::string app_name;
-  {
-    char name[ZX_MAX_NAME_LEN];
-    zx_status_t status =
-        zx_object_get_property(process_, ZX_PROP_NAME, name, sizeof(name));
-    if (status != ZX_OK) {
-      LOG(ERROR) << "zx_object_get_property ZX_PROP_NAME";
-      return;
-    }
-
-    app_name = name;
-  }
-
   // Starting from the ld.so's _dl_debug_addr, read the link_map structure and
   // walk the list to fill out modules_.
 
   uintptr_t debug_address;
-  zx_status_t status = zx_object_get_property(process_,
-                                              ZX_PROP_PROCESS_DEBUG_ADDR,
-                                              &debug_address,
-                                              sizeof(debug_address));
+  zx_status_t status = process_->get_property(
+      ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address));
   if (status != ZX_OK || debug_address == 0) {
     LOG(ERROR) << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR";
     return;
@@ -206,19 +223,44 @@
       LOG(ERROR) << "ReadCString name";
     }
 
-    // The vDSO is libzircon.so, but it's not actually loaded normally, it's
-    // injected by the kernel, so doesn't have a normal name. When dump_syms is
-    // run on libzircon.so, it uses that file name, and in order for the crash
-    // server to match symbols both the debug id and the name of the binary have
-    // to match. So, map from "<vDSO>" to "libzircon.so" so that symbol
-    // resolution works correctly.
+    // Debug symbols are indexed by module name x build-id on the crash server.
+    // The module name in the indexed Breakpad files is set at build time. So
+    // Crashpad needs to use the same module name at run time for symbol
+    // resolution to work properly.
+    //
+    // TODO(fuchsia/DX-1234): once Crashpad switches to elf-search, the
+    // following overwrites won't be necessary as only shared libraries will
+    // have a soname at runtime, just like at build time.
+    //
+    // * For shared libraries, the soname is used as module name at build time,
+    //   which is the dsoname here except for libzircon.so (because it is
+    //   injected by the kernel, its load name is "<vDSO>" and Crashpad needs to
+    //   replace it for symbol resolution to work properly).
     if (dsoname == "<vDSO>") {
       dsoname = "libzircon.so";
     }
+    // * For executables and loadable modules, the dummy value "<_>" is used as
+    //   module name at build time. This is because executable and loadable
+    //   modules don't have a name on Fuchsia. So we need to use the same dummy
+    //   value at build and run times.
+    //   Most executables have an empty dsoname. Loadable modules (and some rare
+    //   executables) have a non-empty dsoname starting with a specific prefix,
+    //   which Crashpas can use to identify loadable modules and clear the
+    //   dsoname for them.
+    static constexpr const char kLoadableModuleLoadNamePrefix[] = "<VMO#";
+    // Pre-C++ 20 std::basic_string::starts_with
+    if (dsoname.compare(0,
+                        strlen(kLoadableModuleLoadNamePrefix),
+                        kLoadableModuleLoadNamePrefix) == 0) {
+      dsoname = "";
+    }
 
     Module module;
     if (dsoname.empty()) {
-      module.name = app_name;
+      // This value must be kept in sync with what is used at build time to
+      // index symbols for executables and loadable modules.
+      // See fuchsia/DX-1193 for more details.
+      module.name = "<_>";
       module.type = ModuleSnapshot::kModuleTypeExecutable;
     } else {
       module.name = dsoname;
@@ -231,13 +273,15 @@
     std::unique_ptr<ProcessMemoryRange> process_memory_range(
         new ProcessMemoryRange());
     // TODO(scottmg): Could this be limited range?
-    process_memory_range->Initialize(process_memory_.get(), true);
-    process_memory_ranges_.push_back(std::move(process_memory_range));
+    if (process_memory_range->Initialize(process_memory_.get(), true)) {
+      process_memory_ranges_.push_back(std::move(process_memory_range));
 
-    reader->Initialize(*process_memory_ranges_.back(), base);
-    module.reader = reader.get();
-    module_readers_.push_back(std::move(reader));
-    modules_.push_back(module);
+      if (reader->Initialize(*process_memory_ranges_.back(), base)) {
+        module.reader = reader.get();
+        module_readers_.push_back(std::move(reader));
+        modules_.push_back(module);
+      }
+    }
 
     map = next;
   }
@@ -250,9 +294,9 @@
   initialized_threads_ = true;
 
   std::vector<zx_koid_t> thread_koids =
-      GetChildKoids(process_, ZX_INFO_PROCESS_THREADS);
-  std::vector<base::ScopedZxHandle> thread_handles =
-      GetHandlesForChildKoids(process_, thread_koids);
+      GetChildKoids(*process_, ZX_INFO_PROCESS_THREADS);
+  std::vector<zx::thread> thread_handles =
+      GetHandlesForThreadKoids(*process_, thread_koids);
   DCHECK_EQ(thread_koids.size(), thread_handles.size());
 
   for (size_t i = 0; i < thread_handles.size(); ++i) {
@@ -261,8 +305,8 @@
 
     if (thread_handles[i].is_valid()) {
       char name[ZX_MAX_NAME_LEN] = {0};
-      zx_status_t status = zx_object_get_property(
-          thread_handles[i].get(), ZX_PROP_NAME, &name, sizeof(name));
+      zx_status_t status =
+          thread_handles[i].get_property(ZX_PROP_NAME, &name, sizeof(name));
       if (status != ZX_OK) {
         ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME";
       } else {
@@ -270,29 +314,40 @@
       }
 
       zx_info_thread_t thread_info;
-      status = zx_object_get_info(thread_handles[i].get(),
-                                  ZX_INFO_THREAD,
-                                  &thread_info,
-                                  sizeof(thread_info),
-                                  nullptr,
-                                  nullptr);
+      status = thread_handles[i].get_info(
+          ZX_INFO_THREAD, &thread_info, sizeof(thread_info), nullptr, nullptr);
       if (status != ZX_OK) {
         ZX_LOG(WARNING, status) << "zx_object_get_info ZX_INFO_THREAD";
       } else {
         thread.state = thread_info.state;
       }
 
-      zx_thread_state_general_regs_t regs;
-      status = zx_thread_read_state(thread_handles[i].get(),
-                                    ZX_THREAD_STATE_GENERAL_REGS,
-                                    &regs,
-                                    sizeof(regs));
+      zx_thread_state_general_regs_t general_regs;
+      status = thread_handles[i].read_state(
+          ZX_THREAD_STATE_GENERAL_REGS, &general_regs, sizeof(general_regs));
       if (status != ZX_OK) {
-        ZX_LOG(WARNING, status) << "zx_thread_read_state";
+        ZX_LOG(WARNING, status)
+            << "zx_thread_read_state(ZX_THREAD_STATE_GENERAL_REGS)";
       } else {
-        thread.general_registers = regs;
+        thread.general_registers = general_regs;
 
-        GetStackRegions(regs, memory_map_, &thread.stack_regions);
+        const MemoryMapFuchsia* memory_map = MemoryMap();
+        if (memory_map) {
+          // Attempt to retrive stack regions if a memory map was retrieved. In
+          // particular, this may be null when operating on the current process
+          // where the memory map will not be able to be retrieved.
+          GetStackRegions(general_regs, *memory_map, &thread.stack_regions);
+        }
+      }
+
+      zx_thread_state_vector_regs_t vector_regs;
+      status = thread_handles[i].read_state(
+          ZX_THREAD_STATE_VECTOR_REGS, &vector_regs, sizeof(vector_regs));
+      if (status != ZX_OK) {
+        ZX_LOG(WARNING, status)
+            << "zx_thread_read_state(ZX_THREAD_STATE_VECTOR_REGS)";
+      } else {
+        thread.vector_registers = vector_regs;
       }
     }
 
@@ -300,4 +355,15 @@
   }
 }
 
+void ProcessReaderFuchsia::InitializeMemoryMap() {
+  DCHECK(!initialized_memory_map_);
+
+  initialized_memory_map_ = true;
+
+  memory_map_.reset(new MemoryMapFuchsia);
+  if (!memory_map_->Initialize(*process_)) {
+    memory_map_.reset();
+  }
+}
+
 }  // namespace crashpad
diff --git a/snapshot/fuchsia/process_reader_fuchsia.h b/snapshot/fuchsia/process_reader_fuchsia.h
index 2d0878c..91c6331 100644
--- a/snapshot/fuchsia/process_reader_fuchsia.h
+++ b/snapshot/fuchsia/process_reader_fuchsia.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_
 #define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_
 
+#include <lib/zx/process.h>
 #include <zircon/syscalls/debug.h>
 
 #include <memory>
@@ -76,6 +77,10 @@
     //!     returned by `zx_thread_read_state()`.
     zx_thread_state_general_regs_t general_registers = {};
 
+    //! \brief The raw architecture-specific `zx_thread_state_vector_regs_t` as
+    //!     returned by `zx_thread_read_state()`.
+    zx_thread_state_vector_regs_t vector_registers = {};
+
     //! \brief The regions representing the stack. The first entry in the vector
     //!     represents the callstack, and further entries optionally identify
     //!     other stack data when the thread uses a split stack representation.
@@ -94,7 +99,7 @@
   //! \return `true` on success, indicating that this object will respond
   //!     validly to further method calls. `false` on failure. On failure, no
   //!     further method calls should be made.
-  bool Initialize(zx_handle_t process);
+  bool Initialize(const zx::process& process);
 
   //! \return The modules loaded in the process. The first element (at index
   //!     `0`) corresponds to the main executable.
@@ -104,7 +109,10 @@
   const std::vector<Thread>& Threads();
 
   //! \brief Return a memory reader for the target process.
-  ProcessMemory* Memory() { return process_memory_.get(); }
+  const ProcessMemory* Memory() const { return process_memory_.get(); }
+
+  //! \brief Return a memory map for the target process.
+  const MemoryMapFuchsia* MemoryMap();
 
  private:
   //! Performs lazy initialization of the \a modules_ vector on behalf of
@@ -115,15 +123,20 @@
   //! Threads().
   void InitializeThreads();
 
+  //! Performs lazy initialization of the \a memory_map_ on behalf of
+  //! MemoryMap().
+  void InitializeMemoryMap();
+
   std::vector<Module> modules_;
   std::vector<Thread> threads_;
   std::vector<std::unique_ptr<ElfImageReader>> module_readers_;
   std::vector<std::unique_ptr<ProcessMemoryRange>> process_memory_ranges_;
   std::unique_ptr<ProcessMemoryFuchsia> process_memory_;
-  MemoryMapFuchsia memory_map_;
-  zx_handle_t process_;
+  std::unique_ptr<MemoryMapFuchsia> memory_map_;
+  zx::unowned_process process_;
   bool initialized_modules_ = false;
   bool initialized_threads_ = false;
+  bool initialized_memory_map_ = false;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ProcessReaderFuchsia);
diff --git a/snapshot/fuchsia/process_reader_fuchsia_test.cc b/snapshot/fuchsia/process_reader_fuchsia_test.cc
index 546d829..581fb4e 100644
--- a/snapshot/fuchsia/process_reader_fuchsia_test.cc
+++ b/snapshot/fuchsia/process_reader_fuchsia_test.cc
@@ -20,6 +20,7 @@
 #include <zircon/syscalls/port.h>
 #include <zircon/types.h>
 
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "test/multiprocess_exec.h"
 #include "test/test_paths.h"
@@ -31,20 +32,34 @@
 
 TEST(ProcessReaderFuchsia, SelfBasic) {
   ProcessReaderFuchsia process_reader;
-  ASSERT_TRUE(process_reader.Initialize(zx_process_self()));
+  ASSERT_TRUE(process_reader.Initialize(*zx::process::self()));
 
   static constexpr char kTestMemory[] = "Some test memory";
-  char buffer[arraysize(kTestMemory)];
+  char buffer[base::size(kTestMemory)];
   ASSERT_TRUE(process_reader.Memory()->Read(
       reinterpret_cast<zx_vaddr_t>(kTestMemory), sizeof(kTestMemory), &buffer));
   EXPECT_STREQ(kTestMemory, buffer);
 
   const auto& modules = process_reader.Modules();
+  // The process should have at least one module, the executable, and then some
+  // shared libraries, no loadable modules.
   EXPECT_GT(modules.size(), 0u);
+  size_t num_executables = 0u;
+  size_t num_shared_libraries = 0u;
   for (const auto& module : modules) {
     EXPECT_FALSE(module.name.empty());
     EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown);
+
+    if (module.type == ModuleSnapshot::kModuleTypeExecutable) {
+      EXPECT_EQ(module.name, "<_>");
+      num_executables++;
+    } else if (module.type == ModuleSnapshot::kModuleTypeSharedLibrary) {
+      EXPECT_NE(module.name, "<_>");
+      num_shared_libraries++;
+    }
   }
+  EXPECT_EQ(num_executables, 1u);
+  EXPECT_EQ(num_shared_libraries, modules.size() - num_executables);
 
   const auto& threads = process_reader.Threads();
   EXPECT_GT(threads.size(), 0u);
@@ -82,7 +97,7 @@
  private:
   void MultiprocessParent() override {
     ProcessReaderFuchsia process_reader;
-    ASSERT_TRUE(process_reader.Initialize(ChildProcess()));
+    ASSERT_TRUE(process_reader.Initialize(*ChildProcess()));
 
     zx_vaddr_t addr;
     ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &addr, sizeof(addr)));
@@ -149,24 +164,28 @@
     ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &c, 1));
     ASSERT_EQ(c, ' ');
 
-    ScopedTaskSuspend suspend(ChildProcess());
+    ScopedTaskSuspend suspend(*ChildProcess());
 
     ProcessReaderFuchsia process_reader;
-    ASSERT_TRUE(process_reader.Initialize(ChildProcess()));
+    ASSERT_TRUE(process_reader.Initialize(*ChildProcess()));
 
     const auto& threads = process_reader.Threads();
     EXPECT_EQ(threads.size(), 6u);
 
     for (size_t i = 1; i < 6; ++i) {
       ASSERT_GT(threads[i].stack_regions.size(), 0u);
-      EXPECT_EQ(threads[i].stack_regions[0].size(), i * 4096u);
+      EXPECT_GT(threads[i].stack_regions[0].size(), 0u);
+      EXPECT_LE(threads[i].stack_regions[0].size(), i * 4096u);
     }
   }
 
   DISALLOW_COPY_AND_ASSIGN(ThreadsChildTest);
 };
 
-TEST(ProcessReaderFuchsia, ChildThreads) {
+// TODO(scottmg): US-553. ScopedTaskSuspend fails sometimes, with a 50ms
+// timeout. Currently unclear how to make that more reliable, so disable the
+// test for now as otherwise it flakes.
+TEST(ProcessReaderFuchsia, DISABLED_ChildThreads) {
   ThreadsChildTest test;
   test.Run();
 }
diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.cc b/snapshot/fuchsia/process_snapshot_fuchsia.cc
index cc66db2..294c9f9 100644
--- a/snapshot/fuchsia/process_snapshot_fuchsia.cc
+++ b/snapshot/fuchsia/process_snapshot_fuchsia.cc
@@ -14,8 +14,6 @@
 
 #include "snapshot/fuchsia/process_snapshot_fuchsia.h"
 
-#include <zircon/process.h>
-
 #include "base/logging.h"
 #include "util/fuchsia/koid_utilities.h"
 
@@ -25,7 +23,7 @@
 
 ProcessSnapshotFuchsia::~ProcessSnapshotFuchsia() = default;
 
-bool ProcessSnapshotFuchsia::Initialize(zx_handle_t process) {
+bool ProcessSnapshotFuchsia::Initialize(const zx::process& process) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   if (gettimeofday(&snapshot_time_, nullptr) != 0) {
@@ -43,6 +41,16 @@
   InitializeThreads();
   InitializeModules();
 
+  const MemoryMapFuchsia* memory_map = process_reader_.MemoryMap();
+  if (memory_map) {
+    for (const auto& entry : memory_map->Entries()) {
+      if (entry.type == ZX_INFO_MAPS_TYPE_MAPPING) {
+        memory_map_.push_back(
+            std::make_unique<internal::MemoryMapRegionSnapshotFuchsia>(entry));
+      }
+    }
+  }
+
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
@@ -93,12 +101,12 @@
   *options = local_options;
 }
 
-pid_t ProcessSnapshotFuchsia::ProcessID() const {
+crashpad::ProcessID ProcessSnapshotFuchsia::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return GetKoidForHandle(zx_process_self());
+  return GetKoidForHandle(*zx::process::self());
 }
 
-pid_t ProcessSnapshotFuchsia::ParentProcessID() const {
+crashpad::ProcessID ProcessSnapshotFuchsia::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
   return 0;
@@ -177,7 +185,11 @@
 std::vector<const MemoryMapRegionSnapshot*> ProcessSnapshotFuchsia::MemoryMap()
     const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return std::vector<const MemoryMapRegionSnapshot*>();
+  std::vector<const MemoryMapRegionSnapshot*> memory_map;
+  for (const auto& item : memory_map_) {
+    memory_map.push_back(item.get());
+  }
+  return memory_map;
 }
 
 std::vector<HandleSnapshot> ProcessSnapshotFuchsia::Handles() const {
@@ -190,6 +202,11 @@
   return std::vector<const MemorySnapshot*>();
 }
 
+const ProcessMemory* ProcessSnapshotFuchsia::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return process_reader_.Memory();
+}
+
 void ProcessSnapshotFuchsia::InitializeThreads() {
   const std::vector<ProcessReaderFuchsia::Thread>& process_reader_threads =
       process_reader_.Threads();
@@ -209,7 +226,8 @@
         std::make_unique<internal::ModuleSnapshotElf>(reader_module.name,
                                                       reader_module.reader,
                                                       reader_module.type,
-                                                      &memory_range_);
+                                                      &memory_range_,
+                                                      process_reader_.Memory());
     if (module->Initialize()) {
       modules_.push_back(std::move(module));
     }
diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.h b/snapshot/fuchsia/process_snapshot_fuchsia.h
index 2590ed5..6af2ebe 100644
--- a/snapshot/fuchsia/process_snapshot_fuchsia.h
+++ b/snapshot/fuchsia/process_snapshot_fuchsia.h
@@ -15,9 +15,10 @@
 #ifndef CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_
 #define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_
 
+#include <lib/zx/process.h>
 #include <sys/time.h>
-#include <zircon/types.h>
 #include <zircon/syscalls/exception.h>
+#include <zircon/types.h>
 
 #include <memory>
 #include <vector>
@@ -27,12 +28,14 @@
 #include "snapshot/elf/elf_image_reader.h"
 #include "snapshot/elf/module_snapshot_elf.h"
 #include "snapshot/fuchsia/exception_snapshot_fuchsia.h"
+#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h"
 #include "snapshot/fuchsia/process_reader_fuchsia.h"
 #include "snapshot/fuchsia/system_snapshot_fuchsia.h"
 #include "snapshot/fuchsia/thread_snapshot_fuchsia.h"
 #include "snapshot/process_snapshot.h"
 #include "snapshot/unloaded_module_snapshot.h"
 #include "util/misc/initialization_state_dcheck.h"
+#include "util/process/process_id.h"
 #include "util/process/process_memory_range.h"
 
 namespace crashpad {
@@ -50,7 +53,7 @@
   //!
   //! \return `true` if the snapshot could be created, `false` otherwise with
   //!     an appropriate message logged.
-  bool Initialize(zx_handle_t process);
+  bool Initialize(const zx::process& process);
 
   //! \brief Initializes the object's exception.
   //!
@@ -103,8 +106,8 @@
   }
 
   // ProcessSnapshot:
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -120,6 +123,7 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
   // Initializes threads_ on behalf of Initialize().
@@ -135,6 +139,8 @@
   ProcessReaderFuchsia process_reader_;
   ProcessMemoryRange memory_range_;
   std::map<std::string, std::string> annotations_simple_map_;
+  std::vector<std::unique_ptr<internal::MemoryMapRegionSnapshotFuchsia>>
+      memory_map_;
   UUID report_id_;
   UUID client_id_;
   timeval snapshot_time_;
diff --git a/snapshot/fuchsia/process_snapshot_fuchsia_test.cc b/snapshot/fuchsia/process_snapshot_fuchsia_test.cc
new file mode 100644
index 0000000..e1b83ac
--- /dev/null
+++ b/snapshot/fuchsia/process_snapshot_fuchsia_test.cc
@@ -0,0 +1,235 @@
+// 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.
+
+#include "snapshot/fuchsia/memory_map_region_snapshot_fuchsia.h"
+
+#include <dbghelp.h>
+#include <zircon/syscalls.h>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "snapshot/fuchsia/process_snapshot_fuchsia.h"
+#include "test/multiprocess_exec.h"
+#include "util/fuchsia/koid_utilities.h"
+#include "util/fuchsia/scoped_task_suspend.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr struct {
+  uint32_t zircon_perm;
+  size_t pages;
+  uint32_t minidump_perm;
+} kTestMappingPermAndSizes[] = {
+    // Zircon doesn't currently allow write-only, execute-only, or
+    // write-execute-only, returning ZX_ERR_INVALID_ARGS on map.
+    {0, 5, PAGE_NOACCESS},
+    {ZX_VM_PERM_READ, 6, PAGE_READONLY},
+    // {ZX_VM_PERM_WRITE, 7, PAGE_WRITECOPY},
+    // {ZX_VM_PERM_EXECUTE, 8, PAGE_EXECUTE},
+    {ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 9, PAGE_READWRITE},
+    {ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE, 10, PAGE_EXECUTE_READ},
+    // {ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE, 11, PAGE_EXECUTE_WRITECOPY},
+    {ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE,
+     12,
+     PAGE_EXECUTE_READWRITE},
+};
+
+CRASHPAD_CHILD_TEST_MAIN(AddressSpaceChildTestMain) {
+  // Create specifically sized mappings/permissions and write the address in
+  // our address space to the parent so that the reader can check they're read
+  // correctly.
+  for (const auto& t : kTestMappingPermAndSizes) {
+    zx_handle_t vmo = ZX_HANDLE_INVALID;
+    const size_t size = t.pages * PAGE_SIZE;
+    zx_status_t status = zx_vmo_create(size, 0, &vmo);
+    ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
+    status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
+    ZX_CHECK(status == ZX_OK, status) << "zx_vmo_replace_as_executable";
+    uintptr_t mapping_addr = 0;
+    status = zx_vmar_map(
+        zx_vmar_root_self(), t.zircon_perm, 0, vmo, 0, size, &mapping_addr);
+    ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map";
+    CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
+                     &mapping_addr,
+                     sizeof(mapping_addr));
+  }
+
+  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
+  return 0;
+}
+
+bool HasSingleMatchingMapping(
+    const std::vector<const MemoryMapRegionSnapshot*>& memory_map,
+    uintptr_t address,
+    size_t size,
+    uint32_t perm) {
+  const MemoryMapRegionSnapshot* matching = nullptr;
+  for (const auto* region : memory_map) {
+    const MINIDUMP_MEMORY_INFO& mmi = region->AsMinidumpMemoryInfo();
+    if (mmi.BaseAddress == address) {
+      if (matching) {
+        LOG(ERROR) << "multiple mappings matching address";
+        return false;
+      }
+      matching = region;
+    }
+  }
+
+  if (!matching)
+    return false;
+
+  const MINIDUMP_MEMORY_INFO& matching_mmi = matching->AsMinidumpMemoryInfo();
+  return matching_mmi.Protect == perm && matching_mmi.RegionSize == size;
+}
+
+class AddressSpaceTest : public MultiprocessExec {
+ public:
+  AddressSpaceTest() : MultiprocessExec() {
+    SetChildTestMainFunction("AddressSpaceChildTestMain");
+  }
+  ~AddressSpaceTest() {}
+
+ private:
+  void MultiprocessParent() override {
+    uintptr_t test_addresses[base::size(kTestMappingPermAndSizes)];
+    for (size_t i = 0; i < base::size(test_addresses); ++i) {
+      ASSERT_TRUE(ReadFileExactly(
+          ReadPipeHandle(), &test_addresses[i], sizeof(test_addresses[i])));
+    }
+
+    ScopedTaskSuspend suspend(*ChildProcess());
+
+    ProcessSnapshotFuchsia process_snapshot;
+    ASSERT_TRUE(process_snapshot.Initialize(*ChildProcess()));
+
+    for (size_t i = 0; i < base::size(test_addresses); ++i) {
+      const auto& t = kTestMappingPermAndSizes[i];
+      EXPECT_TRUE(HasSingleMatchingMapping(process_snapshot.MemoryMap(),
+                                           test_addresses[i],
+                                           t.pages * PAGE_SIZE,
+                                           t.minidump_perm))
+          << base::StringPrintf(
+                 "index %zu, zircon_perm 0x%x, minidump_perm 0x%x",
+                 i,
+                 t.zircon_perm,
+                 t.minidump_perm);
+    }
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(AddressSpaceTest);
+};
+
+TEST(ProcessSnapshotFuchsiaTest, AddressSpaceMapping) {
+  AddressSpaceTest test;
+  test.Run();
+}
+
+CRASHPAD_CHILD_TEST_MAIN(StackPointerIntoInvalidLocation) {
+  // Map a large block, output the base address of it, and block. The parent
+  // will artificially set the SP into this large block to confirm that a huge
+  // stack is not accidentally captured.
+  zx_handle_t large_vmo;
+  constexpr uint64_t kSize = 1 << 30u;
+  zx_status_t status = zx_vmo_create(kSize, 0, &large_vmo);
+  ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
+  zx_vaddr_t mapped_addr;
+  status = zx_vmar_map(zx_vmar_root_self(),
+                       ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
+                       0,
+                       large_vmo,
+                       0,
+                       kSize,
+                       &mapped_addr);
+  ZX_CHECK(status == ZX_OK, status) << "zx_vmar_map";
+  CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
+                   &mapped_addr,
+                   sizeof(mapped_addr));
+  zx_nanosleep(ZX_TIME_INFINITE);
+  return 0;
+}
+
+class InvalidStackPointerTest : public MultiprocessExec {
+ public:
+  InvalidStackPointerTest() : MultiprocessExec() {
+    SetChildTestMainFunction("StackPointerIntoInvalidLocation");
+    SetExpectedChildTermination(kTerminationNormal,
+                                ZX_TASK_RETCODE_SYSCALL_KILL);
+  }
+  ~InvalidStackPointerTest() {}
+
+ private:
+  void MultiprocessParent() override {
+    uint64_t address_of_large_mapping;
+    ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(),
+                                &address_of_large_mapping,
+                                sizeof(address_of_large_mapping)));
+
+    ScopedTaskSuspend suspend(*ChildProcess());
+
+    std::vector<zx::thread> threads = GetThreadHandles(*ChildProcess());
+    ASSERT_EQ(threads.size(), 1u);
+
+    zx_thread_state_general_regs_t regs;
+    ASSERT_EQ(threads[0].read_state(
+                  ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs)),
+              ZX_OK);
+
+    constexpr uint64_t kOffsetIntoMapping = 1024;
+#if defined(ARCH_CPU_X86_64)
+    regs.rsp = address_of_large_mapping + kOffsetIntoMapping;
+#elif defined(ARCH_CPU_ARM64)
+    regs.sp = address_of_large_mapping + kOffsetIntoMapping;
+#else
+#error
+#endif
+
+    ASSERT_EQ(threads[0].write_state(
+                  ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs)),
+              ZX_OK);
+
+    ProcessSnapshotFuchsia process_snapshot;
+    ASSERT_TRUE(process_snapshot.Initialize(*ChildProcess()));
+
+    ASSERT_EQ(process_snapshot.Threads().size(), 1u);
+    const MemorySnapshot* stack = process_snapshot.Threads()[0]->Stack();
+    ASSERT_TRUE(stack);
+    // Ensure the stack capture isn't unreasonably large.
+    EXPECT_LT(stack->Size(), 10 * 1048576u);
+
+    // As we've corrupted the child, don't let it run again.
+    ASSERT_EQ(ChildProcess()->kill(), ZX_OK);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(InvalidStackPointerTest);
+};
+
+// This is a test for a specific failure detailed in
+// https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=41212. A test of stack
+// behavior that was intentionally overflowing the stack, and so when Crashpad
+// received the exception the SP did not point into the actual stack. This
+// caused Crashpad to erronously capture the "stack" from the next mapping in
+// the address space (which could be very large, cause OOM, etc.).
+TEST(ProcessSnapshotFuchsiaTest, InvalidStackPointer) {
+  InvalidStackPointerTest test;
+  test.Run();
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/snapshot/fuchsia/system_snapshot_fuchsia.cc b/snapshot/fuchsia/system_snapshot_fuchsia.cc
index 889817a..c108529 100644
--- a/snapshot/fuchsia/system_snapshot_fuchsia.cc
+++ b/snapshot/fuchsia/system_snapshot_fuchsia.cc
@@ -38,10 +38,7 @@
   // garnet/bin/uname/uname.c, however, this information isn't provided by
   // uname(). Additionally, uname() seems to hang if the network is in a bad
   // state when attempting to retrieve the nodename, so avoid it for now.
-  char kernel_version[256] = {};
-  zx_status_t status =
-      zx_system_get_version(kernel_version, sizeof(kernel_version));
-  ZX_LOG_IF(ERROR, status != ZX_OK, status) << "zx_system_get_version";
+  std::string kernel_version = zx_system_get_version_string();
 
 #if defined(ARCH_CPU_X86_64)
   static constexpr const char kArch[] = "x86_64";
@@ -50,8 +47,8 @@
 #else
   static constexpr const char kArch[] = "unknown";
 #endif
-  os_version_full_ =
-      base::StringPrintf("Zircon prerelease %s %s", kernel_version, kArch);
+  os_version_full_ = base::StringPrintf(
+      "Zircon prerelease %s %s", kernel_version.c_str(), kArch);
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
 }
@@ -73,7 +70,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.Revision();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  // TODO(fuchsia/DX-712): Read actual revision.
   return 0;
 #endif
 }
@@ -88,7 +85,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.Vendor();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  // TODO(fuchsia/DX-712): Read actual vendor.
   return std::string();
 #endif
 }
@@ -106,7 +103,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.Signature();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  NOTREACHED();
   return 0;
 #endif
 }
@@ -116,7 +113,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.Features();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  NOTREACHED();
   return 0;
 #endif
 }
@@ -126,7 +123,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.ExtendedFeatures();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  NOTREACHED();
   return 0;
 #endif
 }
@@ -135,7 +132,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.Leaf7Features();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  NOTREACHED();
   return 0;
 #endif
 }
@@ -145,7 +142,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.SupportsDAZ();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  NOTREACHED();
   return false;
 #endif
 }
@@ -191,7 +188,7 @@
 #if defined(ARCH_CPU_X86_64)
   return cpuid_.NXEnabled();
 #else
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196.
+  // TODO(fuchsia/DX-712): Read actual NX bit value.
   return false;
 #endif
 }
diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.cc b/snapshot/fuchsia/thread_snapshot_fuchsia.cc
index 03055d8..462cdb1 100644
--- a/snapshot/fuchsia/thread_snapshot_fuchsia.cc
+++ b/snapshot/fuchsia/thread_snapshot_fuchsia.cc
@@ -39,21 +39,22 @@
 #if defined(ARCH_CPU_X86_64)
   context_.architecture = kCPUArchitectureX86_64;
   context_.x86_64 = &context_arch_;
-  // TODO(scottmg): Float context, once Fuchsia has a debug API to capture
-  // floating point registers. ZX-1750 upstream.
-  InitializeCPUContextX86_64(thread.general_registers, context_.x86_64);
+  // TODO(fuchsia/DX-642): Add float context once saved in |thread|.
+  InitializeCPUContextX86_64_NoFloatingPoint(thread.general_registers,
+                                             context_.x86_64);
 #elif defined(ARCH_CPU_ARM64)
   context_.architecture = kCPUArchitectureARM64;
   context_.arm64 = &context_arch_;
-  // TODO(scottmg): Implement context capture for arm64.
+  InitializeCPUContextARM64(
+      thread.general_registers, thread.vector_registers, context_.arm64);
 #else
 #error Port.
 #endif
 
   if (thread.stack_regions.empty()) {
-    stack_.Initialize(process_reader, 0, 0);
+    stack_.Initialize(process_reader->Memory(), 0, 0);
   } else {
-    stack_.Initialize(process_reader,
+    stack_.Initialize(process_reader->Memory(),
                       thread.stack_regions[0].base(),
                       thread.stack_regions[0].size());
     // TODO(scottmg): Handle split stack by adding other parts to ExtraMemory().
diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.h b/snapshot/fuchsia/thread_snapshot_fuchsia.h
index db97597..45d4f11 100644
--- a/snapshot/fuchsia/thread_snapshot_fuchsia.h
+++ b/snapshot/fuchsia/thread_snapshot_fuchsia.h
@@ -67,7 +67,7 @@
 #error Port.
 #endif
   CPUContext context_;
-  MemorySnapshotGeneric<ProcessReaderFuchsia> stack_;
+  MemorySnapshotGeneric stack_;
   zx_koid_t thread_id_;
   zx_vaddr_t thread_specific_data_address_;
   InitializationStateDcheck initialized_;
diff --git a/snapshot/ios/exception_snapshot_ios.cc b/snapshot/ios/exception_snapshot_ios.cc
new file mode 100644
index 0000000..db9e489
--- /dev/null
+++ b/snapshot/ios/exception_snapshot_ios.cc
@@ -0,0 +1,264 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/exception_snapshot_ios.h"
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/strings/stringprintf.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/mac/cpu_context_mac.h"
+#include "util/misc/from_pointer_cast.h"
+
+namespace crashpad {
+
+namespace internal {
+
+ExceptionSnapshotIOS::ExceptionSnapshotIOS()
+    : ExceptionSnapshot(),
+      context_(),
+      codes_(),
+      thread_id_(0),
+      exception_address_(0),
+      exception_(0),
+      exception_info_(0),
+      initialized_() {}
+
+ExceptionSnapshotIOS::~ExceptionSnapshotIOS() {}
+
+void ExceptionSnapshotIOS::InitializeFromSignal(const siginfo_t* siginfo,
+                                                const ucontext_t* context) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  mcontext_t mcontext = context->uc_mcontext;
+#if defined(ARCH_CPU_X86_64)
+  context_.architecture = kCPUArchitectureX86_64;
+  context_.x86_64 = &context_x86_64_;
+  x86_debug_state64_t empty_debug_state = {};
+  InitializeCPUContextX86_64(&context_x86_64_,
+                             THREAD_STATE_NONE,
+                             nullptr,
+                             0,
+                             &mcontext->__ss,
+                             &mcontext->__fs,
+                             &empty_debug_state);
+#elif defined(ARCH_CPU_ARM64)
+  context_.architecture = kCPUArchitectureARM64;
+  context_.arm64 = &context_arm64_;
+  InitializeCPUContextARM64(&context_arm64_,
+                            THREAD_STATE_NONE,
+                            nullptr,
+                            0,
+                            &mcontext->__ss,
+                            &mcontext->__ns);
+#endif
+
+  // Thread ID.
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr =
+      thread_info(mach_thread_self(),
+                  THREAD_IDENTIFIER_INFO,
+                  reinterpret_cast<thread_info_t>(&identifier_info),
+                  &count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_identifier_info";
+  } else {
+    thread_id_ = identifier_info.thread_id;
+  }
+
+  exception_ = siginfo->si_signo;
+  exception_info_ = siginfo->si_code;
+
+  // TODO(justincohen): Investigate recording more codes_.
+
+  exception_address_ = FromPointerCast<uintptr_t>(siginfo->si_addr);
+
+  // TODO(justincohen): Record the source of the exception (signal, mach, etc).
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+}
+
+void ExceptionSnapshotIOS::InitializeFromMachException(
+    exception_behavior_t behavior,
+    thread_t exception_thread,
+    exception_type_t exception,
+    const mach_exception_data_type_t* code,
+    mach_msg_type_number_t code_count,
+    thread_state_flavor_t flavor,
+    ConstThreadState state,
+    mach_msg_type_number_t state_count) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  codes_.push_back(exception);
+  // TODO: rationalize with the macOS implementation.
+  for (mach_msg_type_number_t code_index = 0; code_index < code_count;
+       ++code_index) {
+    codes_.push_back(code[code_index]);
+  }
+  exception_ = exception;
+  exception_info_ = code[0];
+
+  // For serialization, float_state and, on x86, debug_state, will be identical
+  // between here and the thread_snapshot version for thread_id.  That means
+  // this block getting float_state and debug_state can be skipped when doing
+  // proper serialization.
+#if defined(ARCH_CPU_X86_64)
+  x86_thread_state64_t thread_state;
+  x86_float_state64_t float_state;
+  x86_debug_state64_t debug_state;
+  mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+  mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT;
+  mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT;
+  const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64;
+  const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64;
+  const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64;
+#elif defined(ARCH_CPU_ARM64)
+  arm_thread_state64_t thread_state;
+  arm_neon_state64_t float_state;
+  mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT;
+  mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT;
+  const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64;
+  const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64;
+#endif
+
+  kern_return_t kr =
+      thread_get_state(exception_thread,
+                       kThreadStateFlavor,
+                       reinterpret_cast<thread_state_t>(&thread_state),
+                       &thread_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kThreadStateFlavor << ")";
+  }
+
+  kr = thread_get_state(exception_thread,
+                        kFloatStateFlavor,
+                        reinterpret_cast<thread_state_t>(&float_state),
+                        &float_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kFloatStateFlavor << ")";
+  }
+
+#if defined(ARCH_CPU_X86_64)
+  kr = thread_get_state(exception_thread,
+                        kDebugStateFlavor,
+                        reinterpret_cast<thread_state_t>(&debug_state),
+                        &debug_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kDebugStateFlavor << ")";
+  }
+#endif
+
+#if defined(ARCH_CPU_X86_64)
+  context_.architecture = kCPUArchitectureX86_64;
+  context_.x86_64 = &context_x86_64_;
+  InitializeCPUContextX86_64(&context_x86_64_,
+                             flavor,
+                             state,
+                             state_count,
+                             &thread_state,
+                             &float_state,
+                             &debug_state);
+#elif defined(ARCH_CPU_ARM64)
+  context_.architecture = kCPUArchitectureARM64;
+  context_.arm64 = &context_arm64_;
+  InitializeCPUContextARM64(
+      &context_arm64_, flavor, state, state_count, &thread_state, &float_state);
+#endif
+
+  // Thread ID.
+  thread_identifier_info identifier_info;
+  mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
+  kr = thread_info(mach_thread_self(),
+                   THREAD_IDENTIFIER_INFO,
+                   reinterpret_cast<thread_info_t>(&identifier_info),
+                   &count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_identifier_info";
+  } else {
+    thread_id_ = identifier_info.thread_id;
+  }
+
+  // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present
+  // in code[1]. It may or may not be the instruction pointer address (usually
+  // it’s not). code[1] may carry the exception address for other exception
+  // types too, but it’s not guaranteed. But for all other exception types, the
+  // instruction pointer will be the exception address, and in fact will be
+  // equal to codes[1] when it’s carrying the exception address. In those cases,
+  // just use the instruction pointer directly.
+  bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS;
+
+#if defined(ARCH_CPU_X86_64)
+  // For x86 and x86_64 EXC_BAD_ACCESS exceptions, some code[0] values
+  // indicate that code[1] does not (or may not) carry the exception address:
+  // EXC_I386_GPFLT (10.9.5 xnu-2422.115.4/osfmk/i386/trap.c user_trap() for
+  // T_GENERAL_PROTECTION) and the oddball (VM_PROT_READ | VM_PROT_EXECUTE)
+  // which collides with EXC_I386_BOUNDFLT (10.9.5
+  // xnu-2422.115.4/osfmk/i386/fpu.c fpextovrflt()). Other EXC_BAD_ACCESS
+  // exceptions come through 10.9.5 xnu-2422.115.4/osfmk/i386/trap.c
+  // user_page_fault_continue() and do contain the exception address in
+  // code[1].
+  if (exception_ == EXC_BAD_ACCESS &&
+      (exception_info_ == EXC_I386_GPFLT ||
+       exception_info_ == (VM_PROT_READ | VM_PROT_EXECUTE))) {
+    code_1_is_exception_address = false;
+  }
+#endif
+
+  if (code_1_is_exception_address) {
+    exception_address_ = code[1];
+  } else {
+    exception_address_ = context_.InstructionPointer();
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+}
+
+const CPUContext* ExceptionSnapshotIOS::Context() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &context_;
+}
+
+uint64_t ExceptionSnapshotIOS::ThreadID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_id_;
+}
+
+uint32_t ExceptionSnapshotIOS::Exception() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return exception_;
+}
+
+uint32_t ExceptionSnapshotIOS::ExceptionInfo() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return exception_info_;
+}
+
+uint64_t ExceptionSnapshotIOS::ExceptionAddress() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return exception_address_;
+}
+
+const std::vector<uint64_t>& ExceptionSnapshotIOS::Codes() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return codes_;
+}
+
+std::vector<const MemorySnapshot*> ExceptionSnapshotIOS::ExtraMemory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<const MemorySnapshot*>();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/ios/exception_snapshot_ios.h b/snapshot/ios/exception_snapshot_ios.h
new file mode 100644
index 0000000..5c1337d
--- /dev/null
+++ b/snapshot/ios/exception_snapshot_ios.h
@@ -0,0 +1,92 @@
+// Copyright 2020 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_IOS_EXCEPTION_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_EXCEPTION_SNAPSHOT_IOS_H_
+
+#include <mach/mach.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/exception_snapshot.h"
+#include "util/mach/mach_extensions.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+
+namespace internal {
+
+//! \brief An ExceptionSnapshot of an exception sustained by a running (or
+//!     crashed) process on an iOS system.
+class ExceptionSnapshotIOS final : public ExceptionSnapshot {
+ public:
+  ExceptionSnapshotIOS();
+  ~ExceptionSnapshotIOS() override;
+
+  //! \brief Initializes the object from a signal.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  void InitializeFromSignal(const siginfo_t* siginfo,
+                            const ucontext_t* context);
+
+  //! \brief Initialize the object from a Mach exception for the current task.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  void InitializeFromMachException(exception_behavior_t behavior,
+                                   thread_t exception_thread,
+                                   exception_type_t exception,
+                                   const mach_exception_data_type_t* code,
+                                   mach_msg_type_number_t code_count,
+                                   thread_state_flavor_t flavor,
+                                   ConstThreadState state,
+                                   mach_msg_type_number_t state_count);
+  // ExceptionSnapshot:
+
+  const CPUContext* Context() const override;
+  uint64_t ThreadID() const override;
+  uint32_t Exception() const override;
+  uint32_t ExceptionInfo() const override;
+  uint64_t ExceptionAddress() const override;
+  const std::vector<uint64_t>& Codes() const override;
+  virtual std::vector<const MemorySnapshot*> ExtraMemory() const override;
+
+ private:
+#if defined(ARCH_CPU_X86_64)
+  CPUContextX86_64 context_x86_64_;
+#elif defined(ARCH_CPU_ARM64)
+  CPUContextARM64 context_arm64_;
+#else
+#error Port.
+#endif  // ARCH_CPU_X86_64
+  CPUContext context_;
+  std::vector<uint64_t> codes_;
+  uint64_t thread_id_;
+  uintptr_t exception_address_;
+  uint32_t exception_;
+  uint32_t exception_info_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotIOS);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_EXCEPTION_SNAPSHOT_IOS_H_
diff --git a/snapshot/ios/memory_snapshot_ios.cc b/snapshot/ios/memory_snapshot_ios.cc
new file mode 100644
index 0000000..e760465
--- /dev/null
+++ b/snapshot/ios/memory_snapshot_ios.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/memory_snapshot_ios.h"
+
+namespace crashpad {
+namespace internal {
+
+void MemorySnapshotIOS::Initialize(vm_address_t address, vm_size_t size) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  address_ = address;
+  size_ = base::checked_cast<size_t>(size);
+
+  // TODO(justincohen): This is temporary, as MemorySnapshotIOS will likely be
+  // able to point directly to the deserialized data dump rather than copying
+  // data around.
+  buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[size_]);
+  memcpy(buffer_.get(), reinterpret_cast<void*>(address_), size_);
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+}
+
+uint64_t MemorySnapshotIOS::Address() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return address_;
+}
+
+size_t MemorySnapshotIOS::Size() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return size_;
+}
+
+bool MemorySnapshotIOS::Read(Delegate* delegate) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  if (size_ == 0) {
+    return delegate->MemorySnapshotDelegateRead(nullptr, size_);
+  }
+
+  return delegate->MemorySnapshotDelegateRead(buffer_.get(), size_);
+}
+
+const MemorySnapshot* MemorySnapshotIOS::MergeWithOtherSnapshot(
+    const MemorySnapshot* other) const {
+  CheckedRange<uint64_t, size_t> merged(0, 0);
+  if (!LoggingDetermineMergedRange(this, other, &merged))
+    return nullptr;
+
+  auto result = std::make_unique<MemorySnapshotIOS>();
+  result->Initialize(merged.base(), merged.size());
+  return result.release();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/ios/memory_snapshot_ios.h b/snapshot/ios/memory_snapshot_ios.h
new file mode 100644
index 0000000..be99105
--- /dev/null
+++ b/snapshot/ios/memory_snapshot_ios.h
@@ -0,0 +1,63 @@
+// Copyright 2020 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_IOS_MEMORY_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_MEMORY_SNAPSHOT_IOS_H_
+
+#include "base/macros.h"
+#include "snapshot/memory_snapshot.h"
+#include "util/misc/address_types.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A MemorySnapshot of a memory region.
+class MemorySnapshotIOS final : public MemorySnapshot {
+ public:
+  MemorySnapshotIOS() = default;
+  ~MemorySnapshotIOS() = default;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] address The base address of the memory region to snapshot.
+  //! \param[in] size The size of the memory region to snapshot.
+  void Initialize(vm_address_t address, vm_size_t size);
+
+  // MemorySnapshot:
+  uint64_t Address() const override;
+  size_t Size() const override;
+  bool Read(Delegate* delegate) const override;
+  const MemorySnapshot* MergeWithOtherSnapshot(
+      const MemorySnapshot* other) const override;
+
+ private:
+  template <class T>
+  friend const MemorySnapshot* MergeWithOtherSnapshotImpl(
+      const T* self,
+      const MemorySnapshot* other);
+
+  // TODO(justincohen): This is temporary until deserialization is worked out.
+  std::unique_ptr<uint8_t[]> buffer_;
+  vm_address_t address_;
+  vm_size_t size_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemorySnapshotIOS);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_MEMORY_SNAPSHOT_IOS_H_
diff --git a/snapshot/ios/module_snapshot_ios.cc b/snapshot/ios/module_snapshot_ios.cc
new file mode 100644
index 0000000..8b251eb
--- /dev/null
+++ b/snapshot/ios/module_snapshot_ios.cc
@@ -0,0 +1,238 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/module_snapshot_ios.h"
+
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+
+#include "base/files/file_path.h"
+#include "base/mac/mach_logging.h"
+#include "util/misc/from_pointer_cast.h"
+#include "util/misc/uuid.h"
+
+namespace crashpad {
+namespace internal {
+
+ModuleSnapshotIOS::ModuleSnapshotIOS()
+    : ModuleSnapshot(),
+      name_(),
+      address_(0),
+      size_(0),
+      timestamp_(0),
+      dylib_version_(0),
+      source_version_(0),
+      filetype_(0),
+      initialized_() {}
+
+ModuleSnapshotIOS::~ModuleSnapshotIOS() {}
+
+// static.
+const dyld_all_image_infos* ModuleSnapshotIOS::DyldAllImageInfo() {
+  task_dyld_info_data_t dyld_info;
+  mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
+
+  kern_return_t kr = task_info(mach_task_self(),
+                               TASK_DYLD_INFO,
+                               reinterpret_cast<task_info_t>(&dyld_info),
+                               &count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "task_info";
+    return 0;
+  }
+
+  return reinterpret_cast<dyld_all_image_infos*>(dyld_info.all_image_info_addr);
+}
+
+bool ModuleSnapshotIOS::InitializeDyld(const dyld_all_image_infos* images) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  name_ = images->dyldPath;
+  address_ = FromPointerCast<uint64_t>(images->dyldImageLoadAddress);
+  return FinishInitialization();
+}
+
+bool ModuleSnapshotIOS::Initialize(const dyld_image_info* image) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  name_ = image->imageFilePath;
+  address_ = FromPointerCast<uint64_t>(image->imageLoadAddress);
+  timestamp_ = image->imageFileModDate;
+  return FinishInitialization();
+}
+
+bool ModuleSnapshotIOS::FinishInitialization() {
+#ifndef ARCH_CPU_64_BITS
+#error Only 64-bit Mach-O is supported
+#endif
+  DCHECK(address_);
+  const mach_header_64* header =
+      reinterpret_cast<const mach_header_64*>(address_);
+  const load_command* command =
+      reinterpret_cast<const load_command*>(header + 1);
+  // Make sure that the basic load command structure doesn’t overflow the
+  // space allotted for load commands, as well as iterating through ncmds.
+  for (uint32_t cmd_index = 0, cumulative_cmd_size = 0;
+       cmd_index <= header->ncmds && cumulative_cmd_size < header->sizeofcmds;
+       ++cmd_index, cumulative_cmd_size += command->cmdsize) {
+    if (command->cmd == LC_SEGMENT_64) {
+      const segment_command_64* segment =
+          reinterpret_cast<const segment_command_64*>(command);
+      if (strcmp(segment->segname, SEG_TEXT) == 0) {
+        size_ = segment->vmsize;
+      }
+    } else if (command->cmd == LC_ID_DYLIB) {
+      const dylib_command* dylib =
+          reinterpret_cast<const dylib_command*>(command);
+      dylib_version_ = dylib->dylib.current_version;
+    } else if (command->cmd == LC_SOURCE_VERSION) {
+      const source_version_command* source_version =
+          reinterpret_cast<const source_version_command*>(command);
+      source_version_ = source_version->version;
+    } else if (command->cmd == LC_UUID) {
+      const uuid_command* uuid = reinterpret_cast<const uuid_command*>(command);
+      uuid_.InitializeFromBytes(uuid->uuid);
+    }
+
+    command = reinterpret_cast<const load_command*>(
+        reinterpret_cast<const uint8_t*>(command) + command->cmdsize);
+
+    // TODO(justincohen): Warn-able things:
+    // - Bad Mach-O magic (and give up trying to process the module)
+    // - Unrecognized Mach-O type
+    // - No SEG_TEXT
+    // - More than one SEG_TEXT
+    // - More than one LC_ID_DYLIB, LC_SOURCE_VERSION, or LC_UUID
+    // - No LC_ID_DYLIB in a dylib file
+    // - LC_ID_DYLIB in a non-dylib file
+    // And more optional:
+    // - Missing LC_UUID (although it leaves us with a big "?")
+    // - Missing LC_SOURCE_VERSION.
+  }
+
+  filetype_ = header->filetype;
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+std::string ModuleSnapshotIOS::Name() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return name_;
+}
+
+uint64_t ModuleSnapshotIOS::Address() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return address_;
+}
+
+uint64_t ModuleSnapshotIOS::Size() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return size_;
+}
+
+time_t ModuleSnapshotIOS::Timestamp() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return timestamp_;
+}
+
+void ModuleSnapshotIOS::FileVersion(uint16_t* version_0,
+                                    uint16_t* version_1,
+                                    uint16_t* version_2,
+                                    uint16_t* version_3) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  if (filetype_ == MH_DYLIB) {
+    *version_0 = (dylib_version_ & 0xffff0000) >> 16;
+    *version_1 = (dylib_version_ & 0x0000ff00) >> 8;
+    *version_2 = (dylib_version_ & 0x000000ff);
+    *version_3 = 0;
+  } else {
+    *version_0 = 0;
+    *version_1 = 0;
+    *version_2 = 0;
+    *version_3 = 0;
+  }
+}
+
+void ModuleSnapshotIOS::SourceVersion(uint16_t* version_0,
+                                      uint16_t* version_1,
+                                      uint16_t* version_2,
+                                      uint16_t* version_3) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *version_0 = (source_version_ & 0xffff000000000000u) >> 48;
+  *version_1 = (source_version_ & 0x0000ffff00000000u) >> 32;
+  *version_2 = (source_version_ & 0x00000000ffff0000u) >> 16;
+  *version_3 = source_version_ & 0x000000000000ffffu;
+}
+
+ModuleSnapshot::ModuleType ModuleSnapshotIOS::GetModuleType() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  switch (filetype_) {
+    case MH_EXECUTE:
+      return kModuleTypeExecutable;
+    case MH_DYLIB:
+      return kModuleTypeSharedLibrary;
+    case MH_DYLINKER:
+      return kModuleTypeDynamicLoader;
+    case MH_BUNDLE:
+      return kModuleTypeLoadableModule;
+    default:
+      return kModuleTypeUnknown;
+  }
+}
+
+void ModuleSnapshotIOS::UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *uuid = uuid_;
+  *age = 0;
+}
+
+std::string ModuleSnapshotIOS::DebugFileName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return base::FilePath(Name()).BaseName().value();
+}
+
+std::vector<uint8_t> ModuleSnapshotIOS::BuildID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<uint8_t>();
+}
+
+std::vector<std::string> ModuleSnapshotIOS::AnnotationsVector() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<std::string>();
+}
+
+std::map<std::string, std::string> ModuleSnapshotIOS::AnnotationsSimpleMap()
+    const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::map<std::string, std::string>();
+}
+
+std::vector<AnnotationSnapshot> ModuleSnapshotIOS::AnnotationObjects() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<AnnotationSnapshot>();
+}
+
+std::set<CheckedRange<uint64_t>> ModuleSnapshotIOS::ExtraMemoryRanges() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::set<CheckedRange<uint64_t>>();
+}
+
+std::vector<const UserMinidumpStream*>
+ModuleSnapshotIOS::CustomMinidumpStreams() const {
+  return std::vector<const UserMinidumpStream*>();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/ios/module_snapshot_ios.h b/snapshot/ios/module_snapshot_ios.h
new file mode 100644
index 0000000..505c08f
--- /dev/null
+++ b/snapshot/ios/module_snapshot_ios.h
@@ -0,0 +1,111 @@
+// Copyright 2020 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_IOS_MODULE_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_MODULE_SNAPSHOT_IOS_H_
+
+#include <mach-o/dyld_images.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "snapshot/crashpad_info_client_options.h"
+#include "snapshot/module_snapshot.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A ModuleSnapshot of a code module (binary image) loaded into a
+//!     running (or crashed) process on an iOS system.
+class ModuleSnapshotIOS final : public ModuleSnapshot {
+ public:
+  ModuleSnapshotIOS();
+  ~ModuleSnapshotIOS() override;
+
+  // TODO(justincohen): This function is temporary, and will be broken into two
+  // parts.  One to do an in-process dump of all the relevant information, and
+  // two to initialize the snapshot after the in-process dump is loaded.
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] image The mach-o image to be loaded.
+  //!
+  //! \return `true` if the snapshot could be created.
+  bool Initialize(const dyld_image_info* image);
+
+  // TODO(justincohen): This function is temporary, and will be broken into two
+  // parts.  One to do an in-process dump of all the relevant information, and
+  // two to initialize the snapshot after the in-process dump is loaded.
+  //! \brief Initializes the object specifically for the dyld module.
+  //!
+  //! \param[in] images The structure containing the necessary dyld information.
+  //!
+  //! \return `true` if the snapshot could be created.
+  bool InitializeDyld(const dyld_all_image_infos* images);
+
+  //! \brief Returns options from the module’s CrashpadInfo structure.
+  //!
+  //! \param[out] options Options set in the module’s CrashpadInfo structure.
+  void GetCrashpadOptions(CrashpadInfoClientOptions* options);
+
+  static const dyld_all_image_infos* DyldAllImageInfo();
+
+  // ModuleSnapshot:
+  std::string Name() const override;
+  uint64_t Address() const override;
+  uint64_t Size() const override;
+  time_t Timestamp() const override;
+  void FileVersion(uint16_t* version_0,
+                   uint16_t* version_1,
+                   uint16_t* version_2,
+                   uint16_t* version_3) const override;
+  void SourceVersion(uint16_t* version_0,
+                     uint16_t* version_1,
+                     uint16_t* version_2,
+                     uint16_t* version_3) const override;
+  ModuleType GetModuleType() const override;
+  void UUIDAndAge(UUID* uuid, uint32_t* age) const override;
+  std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
+  std::vector<std::string> AnnotationsVector() const override;
+  std::map<std::string, std::string> AnnotationsSimpleMap() const override;
+  std::vector<AnnotationSnapshot> AnnotationObjects() const override;
+  std::set<CheckedRange<uint64_t>> ExtraMemoryRanges() const override;
+  std::vector<const UserMinidumpStream*> CustomMinidumpStreams() const override;
+
+ private:
+  // Gather the the module information based off of a mach_header_64 |address_|.
+  bool FinishInitialization();
+
+  std::string name_;
+  uint64_t address_;
+  uint64_t size_;
+  time_t timestamp_;
+  uint32_t dylib_version_;
+  uint64_t source_version_;
+  uint32_t filetype_;
+  UUID uuid_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotIOS);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_MODULE_SNAPSHOT_IOS_H_
diff --git a/snapshot/ios/process_snapshot_ios.cc b/snapshot/ios/process_snapshot_ios.cc
new file mode 100644
index 0000000..f45c6ee
--- /dev/null
+++ b/snapshot/ios/process_snapshot_ios.cc
@@ -0,0 +1,287 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/process_snapshot_ios.h"
+
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/stl_util.h"
+
+namespace {
+
+void MachTimeValueToTimeval(const time_value& mach, timeval* tv) {
+  tv->tv_sec = mach.seconds;
+  tv->tv_usec = mach.microseconds;
+}
+
+}  // namespace
+
+namespace crashpad {
+
+ProcessSnapshotIOS::ProcessSnapshotIOS()
+    : ProcessSnapshot(),
+      kern_proc_info_(),
+      basic_info_user_time_(),
+      basic_info_system_time_(),
+      thread_times_user_time_(),
+      thread_times_system_time_(),
+      system_(),
+      threads_(),
+      modules_(),
+      exception_(),
+      report_id_(),
+      client_id_(),
+      annotations_simple_map_(),
+      snapshot_time_(),
+      initialized_() {}
+
+ProcessSnapshotIOS::~ProcessSnapshotIOS() {}
+
+bool ProcessSnapshotIOS::Initialize(const IOSSystemDataCollector& system_data) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  // Used by pid, parent pid and snapshot time.
+  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
+  size_t len = sizeof(kern_proc_info_);
+  if (sysctl(mib, base::size(mib), &kern_proc_info_, &len, nullptr, 0)) {
+    PLOG(ERROR) << "sysctl";
+    return false;
+  }
+
+  // Used by user time and system time.
+  task_basic_info_64 task_basic_info;
+  mach_msg_type_number_t task_basic_info_count = TASK_BASIC_INFO_64_COUNT;
+  kern_return_t kr = task_info(mach_task_self(),
+                               TASK_BASIC_INFO_64,
+                               reinterpret_cast<task_info_t>(&task_basic_info),
+                               &task_basic_info_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "task_info TASK_BASIC_INFO_64";
+    return false;
+  }
+
+  task_thread_times_info_data_t task_thread_times;
+  mach_msg_type_number_t task_thread_times_count = TASK_THREAD_TIMES_INFO_COUNT;
+  kr = task_info(mach_task_self(),
+                 TASK_THREAD_TIMES_INFO,
+                 reinterpret_cast<task_info_t>(&task_thread_times),
+                 &task_thread_times_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "task_info TASK_THREAD_TIMES";
+  }
+
+  basic_info_user_time_ = task_basic_info.user_time;
+  basic_info_system_time_ = task_basic_info.system_time;
+  thread_times_user_time_ = task_thread_times.user_time;
+  thread_times_system_time_ = task_thread_times.system_time;
+
+  if (gettimeofday(&snapshot_time_, nullptr) != 0) {
+    PLOG(ERROR) << "gettimeofday";
+    return false;
+  }
+
+  system_.Initialize(system_data);
+  InitializeThreads();
+  InitializeModules();
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+void ProcessSnapshotIOS::SetExceptionFromSignal(const siginfo_t* siginfo,
+                                                const ucontext_t* context) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK(!exception_.get());
+
+  exception_.reset(new internal::ExceptionSnapshotIOS());
+  exception_->InitializeFromSignal(siginfo, context);
+}
+
+void ProcessSnapshotIOS::SetExceptionFromMachException(
+    exception_behavior_t behavior,
+    thread_t exception_thread,
+    exception_type_t exception,
+    const mach_exception_data_type_t* code,
+    mach_msg_type_number_t code_count,
+    thread_state_flavor_t flavor,
+    ConstThreadState old_state,
+    mach_msg_type_number_t old_state_count) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK(!exception_.get());
+
+  exception_.reset(new internal::ExceptionSnapshotIOS());
+  exception_->InitializeFromMachException(behavior,
+                                          exception_thread,
+                                          exception,
+                                          code,
+                                          code_count,
+                                          flavor,
+                                          old_state,
+                                          old_state_count);
+}
+
+pid_t ProcessSnapshotIOS::ProcessID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return kern_proc_info_.kp_proc.p_pid;
+}
+
+pid_t ProcessSnapshotIOS::ParentProcessID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return kern_proc_info_.kp_eproc.e_ppid;
+}
+
+void ProcessSnapshotIOS::SnapshotTime(timeval* snapshot_time) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *snapshot_time = snapshot_time_;
+}
+
+void ProcessSnapshotIOS::ProcessStartTime(timeval* start_time) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *start_time = kern_proc_info_.kp_proc.p_starttime;
+}
+
+void ProcessSnapshotIOS::ProcessCPUTimes(timeval* user_time,
+                                         timeval* system_time) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  // Calculate user and system time the same way the kernel does for
+  // getrusage(). See 10.15.0 xnu-6153.11.26/bsd/kern/kern_resource.c calcru().
+  timerclear(user_time);
+  timerclear(system_time);
+
+  MachTimeValueToTimeval(basic_info_user_time_, user_time);
+  MachTimeValueToTimeval(basic_info_system_time_, system_time);
+
+  timeval thread_user_time;
+  MachTimeValueToTimeval(thread_times_user_time_, &thread_user_time);
+  timeval thread_system_time;
+  MachTimeValueToTimeval(thread_times_system_time_, &thread_system_time);
+
+  timeradd(user_time, &thread_user_time, user_time);
+  timeradd(system_time, &thread_system_time, system_time);
+}
+
+void ProcessSnapshotIOS::ReportID(UUID* report_id) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *report_id = report_id_;
+}
+
+void ProcessSnapshotIOS::ClientID(UUID* client_id) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *client_id = client_id_;
+}
+
+const std::map<std::string, std::string>&
+ProcessSnapshotIOS::AnnotationsSimpleMap() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return annotations_simple_map_;
+}
+
+const SystemSnapshot* ProcessSnapshotIOS::System() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &system_;
+}
+
+std::vector<const ThreadSnapshot*> ProcessSnapshotIOS::Threads() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  std::vector<const ThreadSnapshot*> threads;
+  for (const auto& thread : threads_) {
+    threads.push_back(thread.get());
+  }
+  return threads;
+}
+
+std::vector<const ModuleSnapshot*> ProcessSnapshotIOS::Modules() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  std::vector<const ModuleSnapshot*> modules;
+  for (const auto& module : modules_) {
+    modules.push_back(module.get());
+  }
+  return modules;
+}
+
+std::vector<UnloadedModuleSnapshot> ProcessSnapshotIOS::UnloadedModules()
+    const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<UnloadedModuleSnapshot>();
+}
+
+const ExceptionSnapshot* ProcessSnapshotIOS::Exception() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return exception_.get();
+}
+
+std::vector<const MemoryMapRegionSnapshot*> ProcessSnapshotIOS::MemoryMap()
+    const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<const MemoryMapRegionSnapshot*>();
+}
+
+std::vector<HandleSnapshot> ProcessSnapshotIOS::Handles() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<HandleSnapshot>();
+}
+
+std::vector<const MemorySnapshot*> ProcessSnapshotIOS::ExtraMemory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<const MemorySnapshot*>();
+}
+
+const ProcessMemory* ProcessSnapshotIOS::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return nullptr;
+}
+
+void ProcessSnapshotIOS::InitializeThreads() {
+  mach_msg_type_number_t thread_count = 0;
+  const thread_act_array_t threads =
+      internal::ThreadSnapshotIOS::GetThreads(&thread_count);
+  for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
+    thread_t thread = threads[thread_index];
+    auto thread_snapshot = std::make_unique<internal::ThreadSnapshotIOS>();
+    if (thread_snapshot->Initialize(thread)) {
+      threads_.push_back(std::move(thread_snapshot));
+    }
+    mach_port_deallocate(mach_task_self(), thread);
+  }
+  // TODO(justincohen): This dealloc above and below needs to move with the
+  // call to task_threads inside internal::ThreadSnapshotIOS::GetThreads.
+  vm_deallocate(mach_task_self(),
+                reinterpret_cast<vm_address_t>(threads),
+                sizeof(thread_t) * thread_count);
+}
+
+void ProcessSnapshotIOS::InitializeModules() {
+  const dyld_all_image_infos* image_infos =
+      internal::ModuleSnapshotIOS::DyldAllImageInfo();
+
+  uint32_t image_count = image_infos->infoArrayCount;
+  const dyld_image_info* image_array = image_infos->infoArray;
+  for (uint32_t image_index = 0; image_index < image_count; ++image_index) {
+    const dyld_image_info* image = &image_array[image_index];
+    auto module = std::make_unique<internal::ModuleSnapshotIOS>();
+    if (module->Initialize(image)) {
+      modules_.push_back(std::move(module));
+    }
+  }
+  auto module = std::make_unique<internal::ModuleSnapshotIOS>();
+  if (module->InitializeDyld(image_infos)) {
+    modules_.push_back(std::move(module));
+  }
+}
+
+}  // namespace crashpad
diff --git a/snapshot/ios/process_snapshot_ios.h b/snapshot/ios/process_snapshot_ios.h
new file mode 100644
index 0000000..36b7ba1
--- /dev/null
+++ b/snapshot/ios/process_snapshot_ios.h
@@ -0,0 +1,122 @@
+// Copyright 2020 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_IOS_PROCESS_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_PROCESS_SNAPSHOT_IOS_H_
+
+#include <sys/sysctl.h>
+
+#include <vector>
+
+#include "snapshot/ios/exception_snapshot_ios.h"
+#include "snapshot/ios/module_snapshot_ios.h"
+#include "snapshot/ios/system_snapshot_ios.h"
+#include "snapshot/ios/thread_snapshot_ios.h"
+#include "snapshot/process_snapshot.h"
+#include "snapshot/thread_snapshot.h"
+#include "snapshot/unloaded_module_snapshot.h"
+
+namespace crashpad {
+
+//! \brief A ProcessSnapshot of a running (or crashed) process running on a
+//!     iphoneOS system.
+class ProcessSnapshotIOS final : public ProcessSnapshot {
+ public:
+  ProcessSnapshotIOS();
+  ~ProcessSnapshotIOS() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] system_data A class containing various system data points.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  bool Initialize(const IOSSystemDataCollector& system_data);
+
+  //! \brief Initialize exception information from a signal.
+  void SetExceptionFromSignal(const siginfo_t* siginfo,
+                              const ucontext_t* context);
+
+  //! \brief Initialize exception information from a Mach exception.
+  void SetExceptionFromMachException(exception_behavior_t behavior,
+                                     thread_t exception_thread,
+                                     exception_type_t exception,
+                                     const mach_exception_data_type_t* code,
+                                     mach_msg_type_number_t code_count,
+                                     thread_state_flavor_t flavor,
+                                     ConstThreadState old_state,
+                                     mach_msg_type_number_t old_state_count);
+
+  //! \brief Sets the value to be returned by ClientID().
+  //!
+  //! On iOS, the client ID is under the control of the snapshot producer,
+  //! which may call this method to set the client ID. If this is not done,
+  //! ClientID() will return an identifier consisting entirely of zeroes.
+  void SetClientID(const UUID& client_id) { client_id_ = client_id; }
+
+  //! \brief Sets the value to be returned by ReportID().
+  //!
+  //! On iOS, the crash report ID is under the control of the snapshot
+  //! producer, which may call this method to set the report ID. If this is not
+  //! done, ReportID() will return an identifier consisting entirely of zeroes.
+  void SetReportID(const UUID& report_id) { report_id_ = report_id; }
+
+  // ProcessSnapshot:
+  pid_t ProcessID() const override;
+  pid_t ParentProcessID() const override;
+  void SnapshotTime(timeval* snapshot_time) const override;
+  void ProcessStartTime(timeval* start_time) const override;
+  void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
+  void ReportID(UUID* report_id) const override;
+  void ClientID(UUID* client_id) const override;
+  const std::map<std::string, std::string>& AnnotationsSimpleMap()
+      const override;
+  const SystemSnapshot* System() const override;
+  std::vector<const ThreadSnapshot*> Threads() const override;
+  std::vector<const ModuleSnapshot*> Modules() const override;
+  std::vector<UnloadedModuleSnapshot> UnloadedModules() const override;
+  const ExceptionSnapshot* Exception() const override;
+  std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
+  std::vector<HandleSnapshot> Handles() const override;
+  std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
+
+ private:
+  // Initializes modules_ on behalf of Initialize().
+  void InitializeModules();
+
+  // Initializes threads_ on behalf of Initialize().
+  void InitializeThreads();
+
+  kinfo_proc kern_proc_info_;
+  time_value_t basic_info_user_time_;
+  time_value_t basic_info_system_time_;
+  time_value_t thread_times_user_time_;
+  time_value_t thread_times_system_time_;
+  internal::SystemSnapshotIOS system_;
+  std::vector<std::unique_ptr<internal::ThreadSnapshotIOS>> threads_;
+  std::vector<std::unique_ptr<internal::ModuleSnapshotIOS>> modules_;
+  std::unique_ptr<internal::ExceptionSnapshotIOS> exception_;
+  UUID report_id_;
+  UUID client_id_;
+  std::map<std::string, std::string> annotations_simple_map_;
+  timeval snapshot_time_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotIOS);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_PROCESS_SNAPSHOT_IOS_H_
diff --git a/snapshot/ios/system_snapshot_ios.cc b/snapshot/ios/system_snapshot_ios.cc
new file mode 100644
index 0000000..57c686e
--- /dev/null
+++ b/snapshot/ios/system_snapshot_ios.cc
@@ -0,0 +1,224 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/system_snapshot_ios.h"
+
+#include <mach/mach.h>
+#include <stddef.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/posix/timezone.h"
+#include "util/mac/mac_util.h"
+#include "util/numeric/in_range_cast.h"
+
+namespace crashpad {
+
+namespace internal {
+
+SystemSnapshotIOS::SystemSnapshotIOS()
+    : SystemSnapshot(),
+      os_version_build_(),
+      machine_description_(),
+      os_version_major_(0),
+      os_version_minor_(0),
+      os_version_bugfix_(0),
+      active_(0),
+      inactive_(0),
+      wired_(0),
+      free_(0),
+      cpu_count_(0),
+      cpu_vendor_(),
+      dst_status_(),
+      standard_offset_seconds_(0),
+      daylight_offset_seconds_(0),
+      standard_name_(),
+      daylight_name_(),
+      initialized_() {}
+
+SystemSnapshotIOS::~SystemSnapshotIOS() {}
+
+void SystemSnapshotIOS::Initialize(const IOSSystemDataCollector& system_data) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  system_data.OSVersion(&os_version_major_,
+                        &os_version_minor_,
+                        &os_version_bugfix_,
+                        &os_version_build_);
+  machine_description_ = system_data.MachineDescription();
+  cpu_count_ = system_data.ProcessorCount();
+  cpu_vendor_ = system_data.CPUVendor();
+  if (system_data.HasDaylightSavingTime()) {
+    dst_status_ = system_data.IsDaylightSavingTime()
+                      ? SystemSnapshot::kObservingDaylightSavingTime
+                      : SystemSnapshot::kObservingStandardTime;
+  } else {
+    dst_status_ = SystemSnapshot::kDoesNotObserveDaylightSavingTime;
+  }
+  standard_offset_seconds_ = system_data.StandardOffsetSeconds();
+  daylight_offset_seconds_ = system_data.DaylightOffsetSeconds();
+  standard_name_ = system_data.StandardName();
+  daylight_name_ = system_data.DaylightName();
+
+  // Currently unused by minidump.
+  vm_size_t page_size;
+  host_page_size(mach_host_self(), &page_size);
+  mach_msg_type_number_t host_size =
+      sizeof(vm_statistics_data_t) / sizeof(integer_t);
+  vm_statistics_data_t vm_stat;
+  kern_return_t kr = host_statistics(mach_host_self(),
+                                     HOST_VM_INFO,
+                                     reinterpret_cast<host_info_t>(&vm_stat),
+                                     &host_size);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "host_statistics";
+  }
+  active_ = vm_stat.active_count * page_size;
+  inactive_ = vm_stat.inactive_count * page_size;
+  wired_ = vm_stat.wire_count * page_size;
+  free_ = vm_stat.free_count * page_size;
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+}
+
+CPUArchitecture SystemSnapshotIOS::GetCPUArchitecture() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+#if defined(ARCH_CPU_X86_64)
+  return kCPUArchitectureX86_64;
+#elif defined(ARCH_CPU_ARM64)
+  return kCPUArchitectureARM64;
+#endif
+}
+
+uint32_t SystemSnapshotIOS::CPURevision() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): sysctlbyname machdep.cpu.* returns -1 on iOS/ARM64, but
+  // consider recording this for X86_64 only.
+  return 0;
+}
+
+uint8_t SystemSnapshotIOS::CPUCount() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return cpu_count_;
+}
+
+std::string SystemSnapshotIOS::CPUVendor() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return cpu_vendor_;
+}
+
+void SystemSnapshotIOS::CPUFrequency(uint64_t* current_hz,
+                                     uint64_t* max_hz) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): sysctlbyname hw.cpufrequency returns -1 on iOS/ARM64,
+  // but consider recording this for X86_64 only.
+  *current_hz = 0;
+  *max_hz = 0;
+}
+
+uint32_t SystemSnapshotIOS::CPUX86Signature() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider recording this for X86_64 only.
+  return 0;
+}
+
+uint64_t SystemSnapshotIOS::CPUX86Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider recording this for X86_64 only.
+  return 0;
+}
+
+uint64_t SystemSnapshotIOS::CPUX86ExtendedFeatures() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider recording this for X86_64 only.
+  return 0;
+}
+
+uint32_t SystemSnapshotIOS::CPUX86Leaf7Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider recording this for X86_64 only.
+  return 0;
+}
+
+bool SystemSnapshotIOS::CPUX86SupportsDAZ() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider recording this for X86_64 only.
+  return false;
+}
+
+SystemSnapshot::OperatingSystem SystemSnapshotIOS::GetOperatingSystem() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return kOperatingSystemIOS;
+}
+
+bool SystemSnapshotIOS::OSServer() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return false;
+}
+
+void SystemSnapshotIOS::OSVersion(int* major,
+                                  int* minor,
+                                  int* bugfix,
+                                  std::string* build) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *major = os_version_major_;
+  *minor = os_version_minor_;
+  *bugfix = os_version_bugfix_;
+  build->assign(os_version_build_);
+}
+
+std::string SystemSnapshotIOS::OSVersionFull() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return base::StringPrintf("%d.%d.%d %s",
+                            os_version_major_,
+                            os_version_minor_,
+                            os_version_bugfix_,
+                            os_version_build_.c_str());
+}
+
+std::string SystemSnapshotIOS::MachineDescription() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return machine_description_;
+}
+
+bool SystemSnapshotIOS::NXEnabled() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // TODO(justincohen): Consider using kern.nx when available (pre-iOS 13,
+  // pre-OS X 10.15). Otherwise the bit is always enabled.
+  return true;
+}
+
+void SystemSnapshotIOS::TimeZone(DaylightSavingTimeStatus* dst_status,
+                                 int* standard_offset_seconds,
+                                 int* daylight_offset_seconds,
+                                 std::string* standard_name,
+                                 std::string* daylight_name) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *dst_status = dst_status_;
+  *standard_offset_seconds = standard_offset_seconds_;
+  *daylight_offset_seconds = daylight_offset_seconds_;
+  standard_name->assign(standard_name_);
+  daylight_name->assign(daylight_name_);
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/ios/system_snapshot_ios.h b/snapshot/ios/system_snapshot_ios.h
new file mode 100644
index 0000000..a38de4e
--- /dev/null
+++ b/snapshot/ios/system_snapshot_ios.h
@@ -0,0 +1,94 @@
+// Copyright 2020 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_IOS_SYSTEM_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_SYSTEM_SNAPSHOT_IOS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "snapshot/system_snapshot.h"
+#include "util/ios/ios_system_data_collector.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+
+namespace internal {
+
+//! \brief A SystemSnapshot of the running system, when the system runs iOS.
+class SystemSnapshotIOS final : public SystemSnapshot {
+ public:
+  SystemSnapshotIOS();
+  ~SystemSnapshotIOS() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] system_data A class containing various system data points.
+  void Initialize(const IOSSystemDataCollector& system_data);
+
+  // SystemSnapshot:
+
+  CPUArchitecture GetCPUArchitecture() const override;
+  uint32_t CPURevision() const override;
+  uint8_t CPUCount() const override;
+  std::string CPUVendor() const override;
+  void CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const override;
+  uint32_t CPUX86Signature() const override;
+  uint64_t CPUX86Features() const override;
+  uint64_t CPUX86ExtendedFeatures() const override;
+  uint32_t CPUX86Leaf7Features() const override;
+  bool CPUX86SupportsDAZ() const override;
+  OperatingSystem GetOperatingSystem() const override;
+  bool OSServer() const override;
+  void OSVersion(int* major,
+                 int* minor,
+                 int* bugfix,
+                 std::string* build) const override;
+  std::string OSVersionFull() const override;
+  bool NXEnabled() const override;
+  std::string MachineDescription() const override;
+  void TimeZone(DaylightSavingTimeStatus* dst_status,
+                int* standard_offset_seconds,
+                int* daylight_offset_seconds,
+                std::string* standard_name,
+                std::string* daylight_name) const override;
+
+ private:
+  std::string os_version_build_;
+  std::string machine_description_;
+  int os_version_major_;
+  int os_version_minor_;
+  int os_version_bugfix_;
+  uint64_t active_;
+  uint64_t inactive_;
+  uint64_t wired_;
+  uint64_t free_;
+  int cpu_count_;
+  std::string cpu_vendor_;
+  DaylightSavingTimeStatus dst_status_;
+  int standard_offset_seconds_;
+  int daylight_offset_seconds_;
+  std::string standard_name_;
+  std::string daylight_name_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemSnapshotIOS);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_SYSTEM_SNAPSHOT_IOS_H_
diff --git a/snapshot/ios/thread_snapshot_ios.cc b/snapshot/ios/thread_snapshot_ios.cc
new file mode 100644
index 0000000..a52be51
--- /dev/null
+++ b/snapshot/ios/thread_snapshot_ios.cc
@@ -0,0 +1,477 @@
+// Copyright 2020 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.
+
+#include "snapshot/ios/thread_snapshot_ios.h"
+
+#include "base/mac/mach_logging.h"
+#include "snapshot/mac/cpu_context_mac.h"
+
+namespace {
+
+#if defined(ARCH_CPU_X86_64)
+const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64;
+const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64;
+#elif defined(ARCH_CPU_ARM64)
+const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64;
+const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64;
+#endif
+
+kern_return_t MachVMRegionRecurseDeepest(task_t task,
+                                         vm_address_t* address,
+                                         vm_size_t* size,
+                                         natural_t* depth,
+                                         vm_prot_t* protection,
+                                         unsigned int* user_tag) {
+  vm_region_submap_short_info_64 submap_info;
+  mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
+  while (true) {
+    kern_return_t kr = vm_region_recurse_64(
+        task,
+        address,
+        size,
+        depth,
+        reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
+        &count);
+    if (kr != KERN_SUCCESS) {
+      return kr;
+    }
+
+    if (!submap_info.is_submap) {
+      *protection = submap_info.protection;
+      *user_tag = submap_info.user_tag;
+      return KERN_SUCCESS;
+    }
+
+    ++*depth;
+  }
+}
+
+//! \brief Adjusts the region for the red zone, if the ABI requires one.
+//!
+//! This method performs red zone calculation for CalculateStackRegion(). Its
+//! parameters are local variables used within that method, and may be
+//! modified as needed.
+//!
+//! Where a red zone is required, the region of memory captured for a thread’s
+//! stack will be extended to include the red zone below the stack pointer,
+//! provided that such memory is mapped, readable, and has the correct user
+//! tag value. If these conditions cannot be met fully, as much of the red
+//! zone will be captured as is possible while meeting these conditions.
+//!
+//! \param[in,out] start_address The base address of the region to begin
+//!     capturing stack memory from. On entry, \a start_address is the stack
+//!     pointer. On return, \a start_address may be decreased to encompass a
+//!     red zone.
+//! \param[in,out] region_base The base address of the region that contains
+//!     stack memory. This is distinct from \a start_address in that \a
+//!     region_base will be page-aligned. On entry, \a region_base is the
+//!     base address of a region that contains \a start_address. On return,
+//!     if \a start_address is decremented and is outside of the region
+//!     originally described by \a region_base, \a region_base will also be
+//!     decremented appropriately.
+//! \param[in,out] region_size The size of the region that contains stack
+//!     memory. This region begins at \a region_base. On return, if \a
+//!     region_base is decremented, \a region_size will be incremented
+//!     appropriately.
+//! \param[in] user_tag The Mach VM system’s user tag for the region described
+//!     by the initial values of \a region_base and \a region_size. The red
+//!     zone will only be allowed to extend out of the region described by
+//!     these initial values if the user tag is appropriate for stack memory
+//!     and the expanded region has the same user tag value.
+void LocateRedZone(vm_address_t* const start_address,
+                   vm_address_t* const region_base,
+                   vm_address_t* const region_size,
+                   const unsigned int user_tag) {
+  // x86_64 has a red zone. See AMD64 ABI 0.99.8,
+  // https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-r252.pdf#page=19,
+  // section 3.2.2, “The Stack Frame”.
+  // So does ARM64,
+  // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
+  // section "Red Zone".
+  constexpr vm_size_t kRedZoneSize = 128;
+  vm_address_t red_zone_base =
+      *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0;
+  bool red_zone_ok = false;
+  if (red_zone_base >= *region_base) {
+    // The red zone is within the region already discovered.
+    red_zone_ok = true;
+  } else if (red_zone_base < *region_base && user_tag == VM_MEMORY_STACK) {
+    // Probe to see if there’s a region immediately below the one already
+    // discovered.
+    vm_address_t red_zone_region_base = red_zone_base;
+    vm_size_t red_zone_region_size;
+    natural_t red_zone_depth = 0;
+    vm_prot_t red_zone_protection;
+    unsigned int red_zone_user_tag;
+    kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                                  &red_zone_region_base,
+                                                  &red_zone_region_size,
+                                                  &red_zone_depth,
+                                                  &red_zone_protection,
+                                                  &red_zone_user_tag);
+    if (kr != KERN_SUCCESS) {
+      MACH_LOG(INFO, kr) << "vm_region_recurse";
+      *start_address = *region_base;
+    } else if (red_zone_region_base + red_zone_region_size == *region_base &&
+               (red_zone_protection & VM_PROT_READ) != 0 &&
+               red_zone_user_tag == user_tag) {
+      // The region containing the red zone is immediately below the region
+      // already found, it’s readable (not the guard region), and it has the
+      // same user tag as the region already found, so merge them.
+      red_zone_ok = true;
+      *region_base -= red_zone_region_size;
+      *region_size += red_zone_region_size;
+    }
+  }
+
+  if (red_zone_ok) {
+    // Begin capturing from the base of the red zone (but not the entire
+    // region that encompasses the red zone).
+    *start_address = red_zone_base;
+  } else {
+    // The red zone would go lower into another region in memory, but no
+    // region was found. Memory can only be captured to an address as low as
+    // the base address of the region already found.
+    *start_address = *region_base;
+  }
+}
+
+//! \brief Calculates the base address and size of the region used as a
+//!     thread’s stack.
+//!
+//! The region returned by this method may be formed by merging multiple
+//! adjacent regions in a process’ memory map if appropriate. The base address
+//! of the returned region may be lower than the \a stack_pointer passed in
+//! when the ABI mandates a red zone below the stack pointer.
+//!
+//! \param[in] stack_pointer The stack pointer, referring to the top (lowest
+//!     address) of a thread’s stack.
+//! \param[out] stack_region_size The size of the memory region used as the
+//!     thread’s stack.
+//!
+//! \return The base address (lowest address) of the memory region used as the
+//!     thread’s stack.
+vm_address_t CalculateStackRegion(vm_address_t stack_pointer,
+                                  vm_size_t* stack_region_size) {
+  // For pthreads, it may be possible to compute the stack region based on the
+  // internal _pthread::stackaddr and _pthread::stacksize. The _pthread struct
+  // for a thread can be located at TSD slot 0, or the known offsets of
+  // stackaddr and stacksize from the TSD area could be used.
+  vm_address_t region_base = stack_pointer;
+  vm_size_t region_size;
+  natural_t depth = 0;
+  vm_prot_t protection;
+  unsigned int user_tag;
+  kern_return_t kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                                &region_base,
+                                                &region_size,
+                                                &depth,
+                                                &protection,
+                                                &user_tag);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(INFO, kr) << "mach_vm_region_recurse";
+    *stack_region_size = 0;
+    return 0;
+  }
+
+  if (region_base > stack_pointer) {
+    // There’s nothing mapped at the stack pointer’s address. Something may have
+    // trashed the stack pointer. Note that this shouldn’t happen for a normal
+    // stack guard region violation because the guard region is mapped but has
+    // VM_PROT_NONE protection.
+    *stack_region_size = 0;
+    return 0;
+  }
+
+  vm_address_t start_address = stack_pointer;
+
+  if ((protection & VM_PROT_READ) == 0) {
+    // If the region isn’t readable, the stack pointer probably points to the
+    // guard region. Don’t include it as part of the stack, and don’t include
+    // anything at any lower memory address. The code below may still possibly
+    // find the real stack region at a memory address higher than this region.
+    start_address = region_base + region_size;
+  } else {
+    // If the ABI requires a red zone, adjust the region to include it if
+    // possible.
+    LocateRedZone(&start_address, &region_base, &region_size, user_tag);
+
+    // Regardless of whether the ABI requires a red zone, capture up to
+    // kExtraCaptureSize additional bytes of stack, but only if present in the
+    // region that was already found.
+    constexpr vm_size_t kExtraCaptureSize = 128;
+    start_address = std::max(start_address >= kExtraCaptureSize
+                                 ? start_address - kExtraCaptureSize
+                                 : start_address,
+                             region_base);
+
+    // Align start_address to a 16-byte boundary, which can help readers by
+    // ensuring that data is aligned properly. This could page-align instead,
+    // but that might be wasteful.
+    constexpr vm_size_t kDesiredAlignment = 16;
+    start_address &= ~(kDesiredAlignment - 1);
+    DCHECK_GE(start_address, region_base);
+  }
+
+  region_size -= (start_address - region_base);
+  region_base = start_address;
+
+  vm_size_t total_region_size = region_size;
+
+  // The stack region may have gotten split up into multiple abutting regions.
+  // Try to coalesce them. This frequently happens for the main thread’s stack
+  // when setrlimit(RLIMIT_STACK, …) is called. It may also happen if a region
+  // is split up due to an mprotect() or vm_protect() call.
+  //
+  // Stack regions created by the kernel and the pthreads library will be marked
+  // with the VM_MEMORY_STACK user tag. Scanning for multiple adjacent regions
+  // with the same tag should find an entire stack region. Checking that the
+  // protection on individual regions is not VM_PROT_NONE should guarantee that
+  // this algorithm doesn’t collect map entries belonging to another thread’s
+  // stack: well-behaved stacks (such as those created by the kernel and the
+  // pthreads library) have VM_PROT_NONE guard regions at their low-address
+  // ends.
+  //
+  // Other stack regions may not be so well-behaved and thus if user_tag is not
+  // VM_MEMORY_STACK, the single region that was found is used as-is without
+  // trying to merge it with other adjacent regions.
+  if (user_tag == VM_MEMORY_STACK) {
+    vm_address_t try_address = region_base;
+    vm_address_t original_try_address;
+
+    while (try_address += region_size,
+           original_try_address = try_address,
+           (kr = MachVMRegionRecurseDeepest(mach_task_self(),
+                                            &try_address,
+                                            &region_size,
+                                            &depth,
+                                            &protection,
+                                            &user_tag) == KERN_SUCCESS) &&
+               try_address == original_try_address &&
+               (protection & VM_PROT_READ) != 0 &&
+               user_tag == VM_MEMORY_STACK) {
+      total_region_size += region_size;
+    }
+
+    if (kr != KERN_SUCCESS && kr != KERN_INVALID_ADDRESS) {
+      // Tolerate KERN_INVALID_ADDRESS because it will be returned when there
+      // are no more regions in the map at or above the specified |try_address|.
+      MACH_LOG(INFO, kr) << "vm_region_recurse";
+    }
+  }
+
+  *stack_region_size = total_region_size;
+  return region_base;
+}
+
+}  // namespace
+
+namespace crashpad {
+namespace internal {
+
+ThreadSnapshotIOS::ThreadSnapshotIOS()
+    : ThreadSnapshot(),
+      context_(),
+      stack_(),
+      thread_id_(0),
+      thread_specific_data_address_(0),
+      suspend_count_(0),
+      priority_(0),
+      initialized_() {}
+
+ThreadSnapshotIOS::~ThreadSnapshotIOS() {}
+
+// static
+thread_act_array_t ThreadSnapshotIOS::GetThreads(
+    mach_msg_type_number_t* count) {
+  thread_act_array_t threads;
+  kern_return_t kr = task_threads(mach_task_self(), &threads, count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "task_threads";
+  }
+  return threads;
+}
+
+bool ThreadSnapshotIOS::Initialize(thread_t thread) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  // TODO(justincohen): Move the following thread_get_state, thread_get_info,
+  // thread_policy_get and CalculateStackRegion to the serialize-on-read
+  // section.
+  thread_basic_info basic_info;
+  thread_precedence_policy precedence;
+  vm_size_t stack_region_size;
+  vm_address_t stack_region_address;
+#if defined(ARCH_CPU_X86_64)
+  x86_thread_state64_t thread_state;
+  x86_float_state64_t float_state;
+  x86_debug_state64_t debug_state;
+  mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+  mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT;
+  mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT;
+#elif defined(ARCH_CPU_ARM64)
+  arm_thread_state64_t thread_state;
+  arm_neon_state64_t float_state;
+  mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT;
+  mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT;
+#endif
+
+  kern_return_t kr =
+      thread_get_state(thread,
+                       kThreadStateFlavor,
+                       reinterpret_cast<thread_state_t>(&thread_state),
+                       &thread_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kThreadStateFlavor << ")";
+  }
+
+  kr = thread_get_state(thread,
+                        kFloatStateFlavor,
+                        reinterpret_cast<thread_state_t>(&float_state),
+                        &float_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kFloatStateFlavor << ")";
+  }
+
+#if defined(ARCH_CPU_X86_64)
+  kr = thread_get_state(thread,
+                        kDebugStateFlavor,
+                        reinterpret_cast<thread_state_t>(&debug_state),
+                        &debug_state_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_get_state(" << kDebugStateFlavor << ")";
+  }
+#endif
+
+  mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+  kr = thread_info(thread,
+                   THREAD_BASIC_INFO,
+                   reinterpret_cast<thread_info_t>(&basic_info),
+                   &count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "thread_info(THREAD_BASIC_INFO)";
+  }
+
+  thread_identifier_info identifier_info;
+  count = THREAD_IDENTIFIER_INFO_COUNT;
+  kr = thread_info(thread,
+                   THREAD_IDENTIFIER_INFO,
+                   reinterpret_cast<thread_info_t>(&identifier_info),
+                   &count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << "thread_info(THREAD_IDENTIFIER_INFO)";
+  }
+
+  count = THREAD_PRECEDENCE_POLICY_COUNT;
+  boolean_t get_default = FALSE;
+  kr = thread_policy_get(thread,
+                         THREAD_PRECEDENCE_POLICY,
+                         reinterpret_cast<thread_policy_t>(&precedence),
+                         &count,
+                         &get_default);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(ERROR, kr) << "thread_policy_get";
+  }
+
+#if defined(ARCH_CPU_X86_64)
+  vm_address_t stack_pointer = thread_state.__rsp;
+#elif defined(ARCH_CPU_ARM64)
+  vm_address_t stack_pointer = thread_state.__sp;
+#endif
+  stack_region_address =
+      CalculateStackRegion(stack_pointer, &stack_region_size);
+
+  // TODO(justincohen): Assume the following will fill in snapshot data from
+  // a deserialized object.
+  thread_id_ = identifier_info.thread_id;
+  suspend_count_ = basic_info.suspend_count;
+  priority_ = precedence.importance;
+
+  // thread_identifier_info::thread_handle contains the base of the
+  // thread-specific data area, which on x86 and x86_64 is the thread’s base
+  // address of the %gs segment. 10.9.2 xnu-2422.90.20/osfmk/kern/thread.c
+  // thread_info_internal() gets the value from
+  // machine_thread::cthread_self, which is the same value used to set the
+  // %gs base in xnu-2422.90.20/osfmk/i386/pcb_native.c
+  // act_machine_switch_pcb().
+  //
+  // On ARM64 10.15.0 xnu-6153.11.26/osfmk/kern/thread.c, it sets
+  // thread_identifier_info_t::thread_handle to
+  // thread->machine.cthread_self, which is set to tsd_base in
+  // osfmk/arm64/pcb.c.
+  thread_specific_data_address_ = identifier_info.thread_handle;
+  stack_.Initialize(stack_region_address, stack_region_size);
+
+#if defined(ARCH_CPU_X86_64)
+  context_.architecture = kCPUArchitectureX86_64;
+  context_.x86_64 = &context_x86_64_;
+  InitializeCPUContextX86_64(&context_x86_64_,
+                             THREAD_STATE_NONE,
+                             nullptr,
+                             0,
+                             &thread_state,
+                             &float_state,
+                             &debug_state);
+#elif defined(ARCH_CPU_ARM64)
+  context_.architecture = kCPUArchitectureARM64;
+  context_.arm64 = &context_arm64_;
+  InitializeCPUContextARM64(&context_arm64_,
+                            THREAD_STATE_NONE,
+                            nullptr,
+                            0,
+                            &thread_state,
+                            &float_state);
+#endif
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+const CPUContext* ThreadSnapshotIOS::Context() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &context_;
+}
+
+const MemorySnapshot* ThreadSnapshotIOS::Stack() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &stack_;
+}
+
+uint64_t ThreadSnapshotIOS::ThreadID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_id_;
+}
+
+int ThreadSnapshotIOS::SuspendCount() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return suspend_count_;
+}
+
+int ThreadSnapshotIOS::Priority() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return priority_;
+}
+
+uint64_t ThreadSnapshotIOS::ThreadSpecificDataAddress() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_specific_data_address_;
+}
+
+std::vector<const MemorySnapshot*> ThreadSnapshotIOS::ExtraMemory() const {
+  return std::vector<const MemorySnapshot*>();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/ios/thread_snapshot_ios.h b/snapshot/ios/thread_snapshot_ios.h
new file mode 100644
index 0000000..978a818
--- /dev/null
+++ b/snapshot/ios/thread_snapshot_ios.h
@@ -0,0 +1,77 @@
+// Copyright 2020 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_IOS_THREAD_SNAPSHOT_IOS_H_
+#define CRASHPAD_SNAPSHOT_IOS_THREAD_SNAPSHOT_IOS_H_
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/ios/memory_snapshot_ios.h"
+#include "snapshot/thread_snapshot.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A ThreadSnapshot of a thread on an iOS system.
+class ThreadSnapshotIOS final : public ThreadSnapshot {
+ public:
+  ThreadSnapshotIOS();
+  ~ThreadSnapshotIOS() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \brief thread The Mach thread used to initialize this object.
+  bool Initialize(thread_t thread);
+
+  //! \brief Returns an array of thread_t threads.
+  //!
+  //! \param[out] count The number of threads returned.
+  //!
+  //! \return An array of of size \a count threads.
+  static thread_act_array_t GetThreads(mach_msg_type_number_t* count);
+
+  // ThreadSnapshot:
+  const CPUContext* Context() const override;
+  const MemorySnapshot* Stack() const override;
+  uint64_t ThreadID() const override;
+  int SuspendCount() const override;
+  int Priority() const override;
+  uint64_t ThreadSpecificDataAddress() const override;
+  std::vector<const MemorySnapshot*> ExtraMemory() const override;
+
+ private:
+#if defined(ARCH_CPU_X86_64)
+  CPUContextX86_64 context_x86_64_;
+#elif defined(ARCH_CPU_ARM64)
+  CPUContextARM64 context_arm64_;
+#else
+#error Port.
+#endif  // ARCH_CPU_X86_64
+  CPUContext context_;
+  MemorySnapshotIOS stack_;
+  uint64_t thread_id_;
+  uint64_t thread_specific_data_address_;
+  int suspend_count_;
+  int priority_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotIOS);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_IOS_THREAD_SNAPSHOT_IOS_H_
diff --git a/snapshot/linux/cpu_context_linux.cc b/snapshot/linux/cpu_context_linux.cc
index ebf9d7e..8464a5a 100644
--- a/snapshot/linux/cpu_context_linux.cc
+++ b/snapshot/linux/cpu_context_linux.cc
@@ -17,6 +17,8 @@
 #include <stddef.h>
 #include <string.h>
 
+#include <limits>
+
 #include "base/logging.h"
 
 namespace crashpad {
@@ -62,7 +64,6 @@
   context->dr5 = 0;
   context->dr6 = 0;
   context->dr7 = 0;
-
 }
 
 void InitializeCPUContextX86(const SignalThreadContext32& thread_context,
@@ -241,7 +242,14 @@
   memcpy(context->regs, thread_context.regs, sizeof(context->regs));
   context->sp = thread_context.sp;
   context->pc = thread_context.pc;
-  context->pstate = thread_context.pstate;
+  // Linux seems to only be putting the SPSR register in its "pstate" field.
+  // https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/uapi/asm/ptrace.h
+  if (thread_context.pstate >
+      std::numeric_limits<decltype(context->spsr)>::max()) {
+    LOG(WARNING) << "pstate truncation: we only expect the SPSR bits to be set "
+                    "in the pstate";
+  }
+  context->spsr = static_cast<decltype(context->spsr)>(thread_context.pstate);
 
   memset(&context->fpsimd, 0, sizeof(context->fpsimd));
   context->fpsr = 0;
diff --git a/snapshot/linux/cpu_context_linux.h b/snapshot/linux/cpu_context_linux.h
index 37fbc43..9f46a48 100644
--- a/snapshot/linux/cpu_context_linux.h
+++ b/snapshot/linux/cpu_context_linux.h
@@ -170,7 +170,7 @@
   memcpy(&context->fpregs, &float_context.fpregs, sizeof(context->fpregs));
   context->fpcsr = float_context.fpcsr;
   context->fir = float_context.fpu_id;
-};
+}
 
 #endif  // ARCH_CPU_MIPS_FAMILY || DOXYGEN
 
diff --git a/snapshot/linux/debug_rendezvous_test.cc b/snapshot/linux/debug_rendezvous_test.cc
index 431e2bb..be22c90 100644
--- a/snapshot/linux/debug_rendezvous_test.cc
+++ b/snapshot/linux/debug_rendezvous_test.cc
@@ -37,28 +37,13 @@
 #include "util/process/process_memory_range.h"
 
 #if defined(OS_ANDROID)
-#include <sys/system_properties.h>
+#include <android/api-level.h>
 #endif
 
 namespace crashpad {
 namespace test {
 namespace {
 
-#if defined(OS_ANDROID)
-int AndroidRuntimeAPI() {
-  char api_string[PROP_VALUE_MAX];
-  int length = __system_property_get("ro.build.version.sdk", api_string);
-  if (length <= 0) {
-    return -1;
-  }
-
-  int api_level;
-  bool success =
-      base::StringToInt(base::StringPiece(api_string, length), &api_level);
-  return success ? api_level : -1;
-}
-#endif  // OS_ANDROID
-
 void TestAgainstTarget(PtraceConnection* connection) {
   // Use ElfImageReader on the main executable which can tell us the debug
   // address. glibc declares the symbol _r_debug in link.h which we can use to
@@ -74,10 +59,10 @@
 
   const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
   ASSERT_TRUE(phdr_mapping);
-  std::vector<const MemoryMap::Mapping*> exe_mappings =
-      mappings.FindFilePossibleMmapStarts(*phdr_mapping);
-  ASSERT_EQ(exe_mappings.size(), 1u);
-  LinuxVMAddress elf_address = exe_mappings[0]->range.Base();
+
+  auto exe_mappings = mappings.FindFilePossibleMmapStarts(*phdr_mapping);
+  ASSERT_EQ(exe_mappings->Count(), 1u);
+  LinuxVMAddress elf_address = exe_mappings->Next()->range.Base();
 
   ProcessMemoryLinux memory;
   ASSERT_TRUE(memory.Initialize(connection->GetProcessID()));
@@ -94,7 +79,7 @@
   ASSERT_TRUE(debug.Initialize(range, debug_address));
 
 #if defined(OS_ANDROID)
-  const int android_runtime_api = AndroidRuntimeAPI();
+  const int android_runtime_api = android_get_device_api_level();
   ASSERT_GE(android_runtime_api, 1);
 
   EXPECT_NE(debug.Executable()->name.find("crashpad_snapshot_test"),
@@ -143,13 +128,13 @@
         mappings.FindMapping(module.dynamic_array);
     ASSERT_TRUE(dyn_mapping);
 
-    std::vector<const MemoryMap::Mapping*> possible_mappings =
-        mappings.FindFilePossibleMmapStarts(*dyn_mapping);
-    ASSERT_GE(possible_mappings.size(), 1u);
+    auto possible_mappings = mappings.FindFilePossibleMmapStarts(*dyn_mapping);
+    ASSERT_GE(possible_mappings->Count(), 1u);
 
     std::unique_ptr<ElfImageReader> module_reader;
     const MemoryMap::Mapping* module_mapping = nullptr;
-    for (const auto mapping : possible_mappings) {
+    const MemoryMap::Mapping* mapping = nullptr;
+    while ((mapping = possible_mappings->Next())) {
       auto parsed_module = std::make_unique<ElfImageReader>();
       VMAddress dynamic_address;
       if (parsed_module->Initialize(range, mapping->range.Base()) &&
diff --git a/snapshot/linux/exception_snapshot_linux.cc b/snapshot/linux/exception_snapshot_linux.cc
index 4256f94..cd40b3b 100644
--- a/snapshot/linux/exception_snapshot_linux.cc
+++ b/snapshot/linux/exception_snapshot_linux.cc
@@ -126,7 +126,7 @@
   context_.arm = &context_union_.arm;
 
   CPUContextARM* dest_context = context_.arm;
-  ProcessMemory* memory = reader->Memory();
+  const ProcessMemory* memory = reader->Memory();
 
   LinuxVMAddress gprs_address =
       context_address + offsetof(UContext<ContextTraits32>, mcontext32) +
@@ -203,7 +203,7 @@
   context_.arm64 = &context_union_.arm64;
 
   CPUContextARM64* dest_context = context_.arm64;
-  ProcessMemory* memory = reader->Memory();
+  const ProcessMemory* memory = reader->Memory();
 
   LinuxVMAddress gprs_address =
       context_address + offsetof(UContext<ContextTraits64>, mcontext64) +
@@ -274,7 +274,7 @@
 static bool ReadContext(ProcessReaderLinux* reader,
                         LinuxVMAddress context_address,
                         typename Traits::CPUContext* dest_context) {
-  ProcessMemory* memory = reader->Memory();
+  const ProcessMemory* memory = reader->Memory();
 
   LinuxVMAddress gregs_address = context_address +
                                  offsetof(UContext<Traits>, mcontext) +
diff --git a/snapshot/linux/exception_snapshot_linux_test.cc b/snapshot/linux/exception_snapshot_linux_test.cc
index df9ad9e..e4ff1ab 100644
--- a/snapshot/linux/exception_snapshot_linux_test.cc
+++ b/snapshot/linux/exception_snapshot_linux_test.cc
@@ -23,6 +23,7 @@
 
 #include "base/bit_cast.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "snapshot/cpu_architecture.h"
@@ -170,7 +171,7 @@
   test_context->vfp.head.magic = VFP_MAGIC;
   test_context->vfp.head.size = sizeof(test_context->vfp);
   memset(&test_context->vfp.context, 'v', sizeof(test_context->vfp.context));
-  for (size_t reg = 0; reg < arraysize(test_context->vfp.context.vfp.fpregs);
+  for (size_t reg = 0; reg < base::size(test_context->vfp.context.vfp.fpregs);
        ++reg) {
     test_context->vfp.context.vfp.fpregs[reg] = reg;
   }
@@ -218,7 +219,7 @@
 void InitializeContext(NativeCPUContext* context) {
   memset(context, 'x', sizeof(*context));
 
-  for (size_t index = 0; index < arraysize(context->uc_mcontext.regs);
+  for (size_t index = 0; index < base::size(context->uc_mcontext.regs);
        ++index) {
     context->uc_mcontext.regs[index] = index;
   }
@@ -237,7 +238,7 @@
   test_context->fpsimd.head.size = sizeof(test_context->fpsimd);
   test_context->fpsimd.fpsr = 1;
   test_context->fpsimd.fpcr = 2;
-  for (size_t reg = 0; reg < arraysize(test_context->fpsimd.vregs); ++reg) {
+  for (size_t reg = 0; reg < base::size(test_context->fpsimd.vregs); ++reg) {
     test_context->fpsimd.vregs[reg] = reg;
   }
 
@@ -254,7 +255,7 @@
             0);
   EXPECT_EQ(actual.arm64->sp, expected.uc_mcontext.sp);
   EXPECT_EQ(actual.arm64->pc, expected.uc_mcontext.pc);
-  EXPECT_EQ(actual.arm64->pstate, expected.uc_mcontext.pstate);
+  EXPECT_EQ(actual.arm64->spsr, expected.uc_mcontext.pstate);
 
   auto test_context = reinterpret_cast<const TestCoprocessorContext*>(
       expected.uc_mcontext.__reserved);
@@ -270,7 +271,7 @@
 using NativeCPUContext = ucontext_t;
 
 void InitializeContext(NativeCPUContext* context) {
-  for (size_t reg = 0; reg < arraysize(context->uc_mcontext.gregs); ++reg) {
+  for (size_t reg = 0; reg < base::size(context->uc_mcontext.gregs); ++reg) {
     context->uc_mcontext.gregs[reg] = reg;
   }
   memset(&context->uc_mcontext.fpregs, 44, sizeof(context->uc_mcontext.fpregs));
@@ -285,7 +286,7 @@
 #define CPU_ARCH_NAME mips64
 #endif
 
-  for (size_t reg = 0; reg < arraysize(expected.uc_mcontext.gregs); ++reg) {
+  for (size_t reg = 0; reg < base::size(expected.uc_mcontext.gregs); ++reg) {
     EXPECT_EQ(actual.CPU_ARCH_NAME->regs[reg], expected.uc_mcontext.gregs[reg]);
   }
 
diff --git a/snapshot/linux/process_reader_linux.cc b/snapshot/linux/process_reader_linux.cc
index 33d4f92..b96abfe 100644
--- a/snapshot/linux/process_reader_linux.cc
+++ b/snapshot/linux/process_reader_linux.cc
@@ -17,7 +17,6 @@
 #include <elf.h>
 #include <errno.h>
 #include <sched.h>
-#include <stdio.h>
 #include <string.h>
 #include <sys/resource.h>
 #include <unistd.h>
@@ -25,13 +24,14 @@
 #include <algorithm>
 
 #include "base/logging.h"
-#include "base/strings/string_number_conversions.h"
 #include "build/build_config.h"
 #include "snapshot/linux/debug_rendezvous.h"
-#include "util/file/directory_reader.h"
 #include "util/linux/auxiliary_vector.h"
 #include "util/linux/proc_stat_reader.h"
-#include "util/misc/as_underlying_type.h"
+
+#if defined(OS_ANDROID)
+#include <android/api-level.h>
+#endif
 
 namespace crashpad {
 
@@ -64,31 +64,36 @@
     return false;
   }
 
+  // TODO(jperaza): Collect scheduling priorities via the broker when they can't
+  // be collected directly.
+  have_priorities = false;
+
   // TODO(jperaza): Starting with Linux 3.14, scheduling policy, static
   // priority, and nice value can be collected all in one call with
   // sched_getattr().
   int res = sched_getscheduler(tid);
   if (res < 0) {
-    PLOG(ERROR) << "sched_getscheduler";
-    return false;
+    PLOG(WARNING) << "sched_getscheduler";
+    return true;
   }
   sched_policy = res;
 
   sched_param param;
   if (sched_getparam(tid, &param) != 0) {
-    PLOG(ERROR) << "sched_getparam";
-    return false;
+    PLOG(WARNING) << "sched_getparam";
+    return true;
   }
   static_priority = param.sched_priority;
 
   errno = 0;
   res = getpriority(PRIO_PROCESS, tid);
   if (res == -1 && errno) {
-    PLOG(ERROR) << "getpriority";
-    return false;
+    PLOG(WARNING) << "getpriority";
+    return true;
   }
   nice_value = res;
 
+  have_priorities = true;
   return true;
 }
 
@@ -236,7 +241,7 @@
 
   for (const Thread& thread : threads_) {
     ProcStatReader stat;
-    if (!stat.Initialize(thread.tid)) {
+    if (!stat.Initialize(connection_, thread.tid)) {
       return false;
     }
 
@@ -275,6 +280,81 @@
   return modules_;
 }
 
+void ProcessReaderLinux::InitializeAbortMessage() {
+#if defined(OS_ANDROID)
+  const MemoryMap::Mapping* mapping =
+      memory_map_.FindMappingWithName("[anon:abort message]");
+  if (!mapping) {
+    return;
+  }
+
+  if (is_64_bit_) {
+    ReadAbortMessage<true>(mapping);
+  } else {
+    ReadAbortMessage<false>(mapping);
+  }
+#endif
+}
+
+#if defined(OS_ANDROID)
+
+// These structure definitions and the magic numbers below were copied from
+// bionic/libc/bionic/android_set_abort_message.cpp
+
+template <bool is64Bit>
+struct abort_msg_t {
+  uint32_t size;
+  char msg[0];
+};
+
+template <>
+struct abort_msg_t<true> {
+  uint64_t size;
+  char msg[0];
+};
+
+template <bool is64Bit>
+struct magic_abort_msg_t {
+  uint64_t magic1;
+  uint64_t magic2;
+  abort_msg_t<is64Bit> msg;
+};
+
+template <bool is64Bit>
+void ProcessReaderLinux::ReadAbortMessage(const MemoryMap::Mapping* mapping) {
+  magic_abort_msg_t<is64Bit> header;
+  if (!Memory()->Read(
+          mapping->range.Base(), sizeof(magic_abort_msg_t<is64Bit>), &header)) {
+    return;
+  }
+
+  size_t size = header.msg.size - sizeof(magic_abort_msg_t<is64Bit>) - 1;
+  if (header.magic1 != 0xb18e40886ac388f0ULL ||
+      header.magic2 != 0xc6dfba755a1de0b5ULL ||
+      mapping->range.Size() <
+          offsetof(magic_abort_msg_t<is64Bit>, msg.msg) + size) {
+    return;
+  }
+
+  abort_message_.resize(size);
+  if (!Memory()->Read(
+          mapping->range.Base() + offsetof(magic_abort_msg_t<is64Bit>, msg.msg),
+          size,
+          &abort_message_[0])) {
+    abort_message_.clear();
+  }
+}
+
+#endif  // OS_ANDROID
+
+const std::string& ProcessReaderLinux::AbortMessage() {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  if (abort_message_.empty()) {
+    InitializeAbortMessage();
+  }
+  return abort_message_;
+}
+
 void ProcessReaderLinux::InitializeThreads() {
   DCHECK(threads_.empty());
   initialized_threads_ = true;
@@ -298,23 +378,11 @@
     LOG(WARNING) << "Couldn't initialize main thread.";
   }
 
-  char path[32];
-  snprintf(path, arraysize(path), "/proc/%d/task", pid);
   bool main_thread_found = false;
-  DirectoryReader reader;
-  if (!reader.Open(base::FilePath(path))) {
-    return;
-  }
-  base::FilePath tid_str;
-  DirectoryReader::Result result;
-  while ((result = reader.NextFile(&tid_str)) ==
-         DirectoryReader::Result::kSuccess) {
-    pid_t tid;
-    if (!base::StringToInt(tid_str.value(), &tid)) {
-      LOG(ERROR) << "format error";
-      continue;
-    }
-
+  std::vector<pid_t> thread_ids;
+  bool result = connection_->Threads(&thread_ids);
+  DCHECK(result);
+  for (pid_t tid : thread_ids) {
     if (tid == pid) {
       DCHECK(!main_thread_found);
       main_thread_found = true;
@@ -328,8 +396,6 @@
       threads_.push_back(thread);
     }
   }
-  DCHECK_EQ(AsUnderlyingType(result),
-            AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles));
   DCHECK(main_thread_found);
 }
 
@@ -365,17 +431,15 @@
       return;
     }
 
-    std::vector<const MemoryMap::Mapping*> possible_mappings =
+    auto possible_mappings =
         memory_map_.FindFilePossibleMmapStarts(*phdr_mapping);
-    for (auto riter = possible_mappings.rbegin();
-         riter != possible_mappings.rend();
-         ++riter) {
-      auto mapping = *riter;
+    const MemoryMap::Mapping* mapping = nullptr;
+    while ((mapping = possible_mappings->Next())) {
       auto parsed_exe = std::make_unique<ElfImageReader>();
       if (parsed_exe->Initialize(
               range,
               mapping->range.Base(),
-              /* verbose= */ possible_mappings.size() == 1) &&
+              /* verbose= */ possible_mappings->Count() == 1) &&
           parsed_exe->GetProgramHeaderTableAddress() == phdrs) {
         exe_mapping = mapping;
         exe_reader = std::move(parsed_exe);
@@ -383,7 +447,8 @@
       }
     }
     if (!exe_mapping) {
-      LOG(ERROR) << "no exe mappings " << possible_mappings.size();
+      LOG(ERROR) << "no exe mappings 0x" << std::hex
+                 << phdr_mapping->range.Base();
       return;
     }
   }
@@ -420,18 +485,30 @@
         continue;
       }
 
-      std::vector<const MemoryMap::Mapping*> possible_mappings =
+#if defined(OS_ANDROID)
+      // Beginning at API 21, Bionic provides android_dlopen_ext() which allows
+      // passing a file descriptor with an existing relro segment to the loader.
+      // This means that the mapping attributes of dyn_mapping may be unrelated
+      // to the attributes of the other mappings for the module. In this case,
+      // search all mappings in reverse order from dyn_mapping until a module is
+      // parsed whose dynamic address matches the value in the debug link.
+      static int api_level = android_get_device_api_level();
+      auto possible_mappings =
+          (api_level >= 21 || api_level < 0)
+              ? memory_map_.ReverseIteratorFrom(*dyn_mapping)
+              : memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
+#else
+      auto possible_mappings =
           memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
-      for (auto riter = possible_mappings.rbegin();
-           riter != possible_mappings.rend();
-           ++riter) {
-        auto mapping = *riter;
+#endif
+      const MemoryMap::Mapping* mapping = nullptr;
+      while ((mapping = possible_mappings->Next())) {
         auto parsed_module = std::make_unique<ElfImageReader>();
         VMAddress dynamic_address;
         if (parsed_module->Initialize(
                 range,
                 mapping->range.Base(),
-                /* verbose= */ possible_mappings.size() == 1) &&
+                /* verbose= */ possible_mappings->Count() == 1) &&
             parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
             dynamic_address == entry.dynamic_array) {
           module_mapping = mapping;
@@ -440,13 +517,19 @@
         }
       }
       if (!module_mapping) {
-        LOG(ERROR) << "no module mappings " << possible_mappings.size();
+        LOG(ERROR) << "no module mappings 0x" << std::hex
+                   << dyn_mapping->range.Base();
         continue;
       }
     }
 
     Module module = {};
-    module.name = !entry.name.empty() ? entry.name : module_mapping->name;
+    std::string soname;
+    if (elf_reader->SoName(&soname) && !soname.empty()) {
+      module.name = soname;
+    } else {
+      module.name = !entry.name.empty() ? entry.name : module_mapping->name;
+    }
     module.elf_reader = elf_reader.get();
     module.type = loader_base && elf_reader->Address() == loader_base
                       ? ModuleSnapshot::kModuleTypeDynamicLoader
diff --git a/snapshot/linux/process_reader_linux.h b/snapshot/linux/process_reader_linux.h
index 7c17d9d..258e102 100644
--- a/snapshot/linux/process_reader_linux.h
+++ b/snapshot/linux/process_reader_linux.h
@@ -66,6 +66,10 @@
     int static_priority;
     int nice_value;
 
+    //! \brief `true` if `sched_policy`, `static_priority`, and `nice_value` are
+    //!     all valid.
+    bool have_priorities;
+
    private:
     friend class ProcessReaderLinux;
 
@@ -116,7 +120,7 @@
   pid_t ParentProcessID() const { return process_info_.ParentProcessID(); }
 
   //! \brief Return a memory reader for the target process.
-  ProcessMemory* Memory() { return connection_->Memory(); }
+  const ProcessMemory* Memory() const { return connection_->Memory(); }
 
   //! \brief Return a memory map of the target process.
   MemoryMap* GetMemoryMap() { return &memory_map_; }
@@ -149,15 +153,23 @@
   //!     `0`) corresponds to the main executable.
   const std::vector<Module>& Modules();
 
+  //! \return On Android, the abort message that was passed to
+  //!     android_set_abort_message(). This is only available on Q or later.
+  const std::string& AbortMessage();
+
  private:
   void InitializeThreads();
   void InitializeModules();
+  void InitializeAbortMessage();
+  template <bool Is64Bit>
+  void ReadAbortMessage(const MemoryMap::Mapping* mapping);
 
   PtraceConnection* connection_;  // weak
   ProcessInfo process_info_;
   MemoryMap memory_map_;
   std::vector<Thread> threads_;
   std::vector<Module> modules_;
+  std::string abort_message_;
   std::vector<std::unique_ptr<ElfImageReader>> elf_readers_;
   bool is_64_bit_;
   bool initialized_threads_;
diff --git a/snapshot/linux/process_reader_linux_test.cc b/snapshot/linux/process_reader_linux_test.cc
index d827f35..5b57236 100644
--- a/snapshot/linux/process_reader_linux_test.cc
+++ b/snapshot/linux/process_reader_linux_test.cc
@@ -33,6 +33,7 @@
 
 #include "base/format_macros.h"
 #include "base/memory/free_deleter.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -48,10 +49,17 @@
 #include "util/linux/direct_ptrace_connection.h"
 #include "util/misc/address_sanitizer.h"
 #include "util/misc/from_pointer_cast.h"
+#include "util/misc/memory_sanitizer.h"
 #include "util/synchronization/semaphore.h"
 
 #if defined(OS_ANDROID)
 #include <android/api-level.h>
+#include <android/set_abort_message.h>
+#include "dlfcn_internal.h"
+
+// Normally this comes from set_abort_message.h, but only at API level 21.
+extern "C" void android_set_abort_message(const char* msg)
+    __attribute__((weak));
 #endif
 
 namespace crashpad {
@@ -79,12 +87,14 @@
   EXPECT_EQ(process_reader.ParentProcessID(), getppid());
 
   static constexpr char kTestMemory[] = "Some test memory";
-  char buffer[arraysize(kTestMemory)];
+  char buffer[base::size(kTestMemory)];
   ASSERT_TRUE(process_reader.Memory()->Read(
       reinterpret_cast<LinuxVMAddress>(kTestMemory),
       sizeof(kTestMemory),
       &buffer));
   EXPECT_STREQ(kTestMemory, buffer);
+
+  EXPECT_EQ("", process_reader.AbortMessage());
 }
 
 constexpr char kTestMemory[] = "Read me from another process";
@@ -327,6 +337,11 @@
     thread_pool.StartThreads(kThreadCount, stack_size_);
 
     TestThreadPool::ThreadExpectation expectation;
+#if defined(MEMORY_SANITIZER)
+    // memset() + re-initialization is required to zero padding bytes for MSan.
+    memset(&expectation, 0, sizeof(expectation));
+#endif  // defined(MEMORY_SANITIZER)
+    expectation = {};
     expectation.tls = GetTLS();
     expectation.stack_address = reinterpret_cast<LinuxVMAddress>(&thread_pool);
 
@@ -412,7 +427,7 @@
   }
 
   void MultiprocessChild() override {
-    const LinuxVMSize stack_size = page_size_ * 3;
+    const LinuxVMSize stack_size = page_size_ * 4;
     GrowStack(stack_size, reinterpret_cast<LinuxVMAddress>(&stack_size));
   }
 
@@ -425,7 +440,7 @@
     } else {
       // Write-protect a page on our stack to split up the mapping
       LinuxVMAddress page_addr =
-          stack_address - (stack_address % page_size_) + page_size_;
+          stack_address - (stack_address % page_size_) + 2 * page_size_;
       ASSERT_EQ(
           mprotect(reinterpret_cast<void*>(page_addr), page_size_, PROT_READ),
           0)
@@ -453,7 +468,14 @@
   DISALLOW_COPY_AND_ASSIGN(ChildWithSplitStackTest);
 };
 
-TEST(ProcessReaderLinux, ChildWithSplitStack) {
+// AddressSanitizer with use-after-return detection causes stack variables to
+// be allocated on the heap.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_ChildWithSplitStack DISABLED_ChildWithSplitStack
+#else
+#define MAYBE_ChildWithSplitStack ChildWithSplitStack
+#endif
+TEST(ProcessReaderLinux, MAYBE_ChildWithSplitStack) {
   ChildWithSplitStackTest test;
   test.Run();
 }
@@ -715,7 +737,7 @@
       auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr);
       auto mappings =
           reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping);
-      EXPECT_EQ(mappings.size(), 2u);
+      EXPECT_EQ(mappings->Count(), 2u);
       return;
     }
   }
@@ -761,7 +783,7 @@
     ScopedModuleHandle empty_test_module(LoadTestModule(module_name_));
     ASSERT_TRUE(empty_test_module.valid());
 
-    char c;
+    char c = 0;
     ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c)));
 
     CheckedReadFileAtEOF(ReadPipeHandle());
@@ -777,6 +799,30 @@
   test.Run();
 }
 
+#if defined(OS_ANDROID)
+const char kTestAbortMessage[] = "test abort message";
+
+TEST(ProcessReaderLinux, AbortMessage) {
+  // This test requires Q. The API level on Q devices will be 28 until the API
+  // is finalized, so we can't check API level yet. For now, test for the
+  // presence of a libc symbol which was introduced in Q.
+  if (!crashpad::internal::Dlsym(RTLD_DEFAULT,
+                                 "android_fdsan_close_with_tag")) {
+    GTEST_SKIP();
+  }
+
+  android_set_abort_message(kTestAbortMessage);
+
+  FakePtraceConnection connection;
+  connection.Initialize(getpid());
+
+  ProcessReaderLinux process_reader;
+  ASSERT_TRUE(process_reader.Initialize(&connection));
+
+  EXPECT_EQ(kTestAbortMessage, process_reader.AbortMessage());
+}
+#endif
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/linux/process_snapshot_linux.cc b/snapshot/linux/process_snapshot_linux.cc
index 190e869..35f870e 100644
--- a/snapshot/linux/process_snapshot_linux.cc
+++ b/snapshot/linux/process_snapshot_linux.cc
@@ -43,13 +43,29 @@
 
   InitializeThreads();
   InitializeModules();
+  InitializeAnnotations();
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
 
+pid_t ProcessSnapshotLinux::FindThreadWithStackAddress(
+    VMAddress stack_address) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  for (const auto& thread : process_reader_.Threads()) {
+    if (stack_address >= thread.stack_region_address &&
+        stack_address <
+            thread.stack_region_address + thread.stack_region_size) {
+      return thread.tid;
+    }
+  }
+  return -1;
+}
+
 bool ProcessSnapshotLinux::InitializeException(
-    LinuxVMAddress exception_info_address) {
+    LinuxVMAddress exception_info_address,
+    pid_t exception_thread_id) {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   DCHECK(!exception_);
 
@@ -60,6 +76,10 @@
     return false;
   }
 
+  if (exception_thread_id >= 0) {
+    info.thread_id = exception_thread_id;
+  }
+
   exception_.reset(new internal::ExceptionSnapshotLinux());
   if (!exception_->Initialize(&process_reader_,
                               info.siginfo_address,
@@ -140,12 +160,12 @@
   *options = local_options;
 }
 
-pid_t ProcessSnapshotLinux::ProcessID() const {
+crashpad::ProcessID ProcessSnapshotLinux::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_reader_.ProcessID();
 }
 
-pid_t ProcessSnapshotLinux::ParentProcessID() const {
+crashpad::ProcessID ProcessSnapshotLinux::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_reader_.ParentProcessID();
 }
@@ -234,6 +254,11 @@
   return std::vector<const MemorySnapshot*>();
 }
 
+const ProcessMemory* ProcessSnapshotLinux::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return process_reader_.Memory();
+}
+
 void ProcessSnapshotLinux::InitializeThreads() {
   const std::vector<ProcessReaderLinux::Thread>& process_reader_threads =
       process_reader_.Threads();
@@ -253,11 +278,21 @@
         std::make_unique<internal::ModuleSnapshotElf>(reader_module.name,
                                                       reader_module.elf_reader,
                                                       reader_module.type,
-                                                      &memory_range_);
+                                                      &memory_range_,
+                                                      process_reader_.Memory());
     if (module->Initialize()) {
       modules_.push_back(std::move(module));
     }
   }
 }
 
+void ProcessSnapshotLinux::InitializeAnnotations() {
+#if defined(OS_ANDROID)
+  const std::string& abort_message = process_reader_.AbortMessage();
+  if (!abort_message.empty()) {
+    annotations_simple_map_["abort_message"] = abort_message;
+  }
+#endif
+}
+
 }  // namespace crashpad
diff --git a/snapshot/linux/process_snapshot_linux.h b/snapshot/linux/process_snapshot_linux.h
index 49d648f..06b72af 100644
--- a/snapshot/linux/process_snapshot_linux.h
+++ b/snapshot/linux/process_snapshot_linux.h
@@ -39,6 +39,7 @@
 #include "util/linux/ptrace_connection.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/misc/uuid.h"
+#include "util/process/process_id.h"
 #include "util/process/process_memory_range.h"
 
 namespace crashpad {
@@ -58,11 +59,23 @@
   //!     an appropriate message logged.
   bool Initialize(PtraceConnection* connection);
 
+  //! \brief Finds the thread whose stack contains \a stack_address.
+  //!
+  //! \param[in] stack_address A stack address to search for.
+  //! \return The thread ID of the thread whose stack contains \a stack_address
+  //!     or -1 if no matching thread is found.
+  pid_t FindThreadWithStackAddress(VMAddress stack_address);
+
   //! \brief Initializes the object's exception.
   //!
   //! \param[in] exception_info The address of an ExceptionInformation in the
   //!     target process' address space.
-  bool InitializeException(LinuxVMAddress exception_info);
+  //! \param[in] exception_thread_id The thread ID to assocaite the thread with.
+  //!     Optional. If -1, the exception thread will be identified by the
+  //!     ExceptionInformation struct which contains the thread ID in the target
+  //!     process' namespace.
+  bool InitializeException(LinuxVMAddress exception_info,
+                           pid_t exception_thread_id = -1);
 
   //! \brief Sets the value to be returned by ReportID().
   //!
@@ -78,15 +91,16 @@
   //! ClientID() will return an identifier consisting entirely of zeroes.
   void SetClientID(const UUID& client_id) { client_id_ = client_id; }
 
-  //! \brief Sets the value to be returned by AnnotationsSimpleMap().
+  //! \brief Add an annotation to be returned by AnnotationsSimpleMap().
   //!
-  //! All process annotations are under the control of the snapshot
+  //! Most process annotations are under the control of the snapshot
   //! producer, which may call this method to establish these annotations.
-  //! Contrast this with module annotations, which are under the control of the
-  //! process being snapshotted.
-  void SetAnnotationsSimpleMap(
-      const std::map<std::string, std::string>& annotations_simple_map) {
-    annotations_simple_map_ = annotations_simple_map;
+  //! On Android Q or later, the process snapshot may add an "abort_message"
+  //! annotation, which will contain the abort message passed to the
+  //! android_set_abort_message() function. Contrast this with module
+  //! annotations, which are under the control of the process being snapshotted.
+  void AddAnnotation(const std::string& key, const std::string& value) {
+    annotations_simple_map_[key] = value;
   }
 
   //! \brief Returns options from CrashpadInfo structures found in modules in
@@ -98,8 +112,8 @@
 
   // ProcessSnapshot:
 
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -115,10 +129,12 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
   void InitializeThreads();
   void InitializeModules();
+  void InitializeAnnotations();
 
   std::map<std::string, std::string> annotations_simple_map_;
   timeval snapshot_time_;
diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc
index 46d3845..f3013b5 100644
--- a/snapshot/linux/system_snapshot_linux_test.cc
+++ b/snapshot/linux/system_snapshot_linux_test.cc
@@ -77,7 +77,8 @@
 
   EXPECT_PRED1(
       [](std::string vendor) {
-        return vendor == "GenuineIntel" || vendor == "AuthenticAMD";
+        return vendor == "GenuineIntel" || vendor == "AuthenticAMD" ||
+               vendor == "HygonGenuine";
       },
       system.CPUVendor());
 
diff --git a/snapshot/linux/thread_snapshot_linux.cc b/snapshot/linux/thread_snapshot_linux.cc
index 8ffbfe8..e3e2beb 100644
--- a/snapshot/linux/thread_snapshot_linux.cc
+++ b/snapshot/linux/thread_snapshot_linux.cc
@@ -23,6 +23,109 @@
 namespace crashpad {
 namespace internal {
 
+namespace {
+
+int ComputeThreadPriority(int static_priority,
+                          int sched_policy,
+                          int nice_value) {
+  // Map Linux scheduling policy, static priority, and nice value into a
+  // single int value.
+  //
+  // The possible policies in order of approximate priority (low to high) are
+  //   SCHED_IDLE
+  //   SCHED_BATCH
+  //   SCHED_OTHER
+  //   SCHED_RR
+  //   SCHED_FIFO
+  //
+  // static_priority is not used for OTHER, BATCH, or IDLE and should be 0.
+  // For FIFO and RR, static_priority should range from 1 to 99 with 99 being
+  // the highest priority.
+  //
+  // nice value ranges from -20 to 19, with -20 being highest priority
+
+  enum class Policy : uint8_t {
+    kUnknown = 0,
+    kIdle,
+    kBatch,
+    kOther,
+    kRR,
+    kFIFO
+  };
+
+  struct LinuxPriority {
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+    // nice values affect how dynamic priorities are updated, which only
+    // matters for threads with the same static priority.
+    uint8_t nice_value = 0;
+
+    // The scheduling policy also affects how threads with the same static
+    // priority are ordered, but has greater impact than nice value.
+    Policy policy = Policy::kUnknown;
+
+    // The static priority is the most significant in determining overall
+    // priority.
+    uint8_t static_priority = 0;
+
+    // Put this in the most significant byte position to prevent negative
+    // priorities.
+    uint8_t unused = 0;
+#elif defined(ARCH_CPU_BIG_ENDIAN)
+    uint8_t unused = 0;
+    uint8_t static_priority = 0;
+    Policy policy = Policy::kUnknown;
+    uint8_t nice_value = 0;
+#endif  // ARCH_CPU_LITTLE_ENDIAN
+  };
+  static_assert(sizeof(LinuxPriority) <= sizeof(int), "priority is too large");
+
+  LinuxPriority prio;
+
+  // Lower nice values have higher priority, so negate them and add 20 to put
+  // them in the range 1-40 with 40 being highest priority.
+  if (nice_value < -20 || nice_value > 19) {
+    LOG(WARNING) << "invalid nice value " << nice_value;
+    prio.nice_value = 0;
+  } else {
+    prio.nice_value = -1 * nice_value + 20;
+  }
+
+  switch (sched_policy) {
+    case SCHED_IDLE:
+      prio.policy = Policy::kIdle;
+      break;
+    case SCHED_BATCH:
+      prio.policy = Policy::kBatch;
+      break;
+    case SCHED_OTHER:
+      prio.policy = Policy::kOther;
+      break;
+    case SCHED_RR:
+      prio.policy = Policy::kRR;
+      break;
+    case SCHED_FIFO:
+      prio.policy = Policy::kFIFO;
+      break;
+    default:
+      prio.policy = Policy::kUnknown;
+      LOG(WARNING) << "Unknown scheduling policy " << sched_policy;
+  }
+
+  if (static_priority < 0 || static_priority > 99) {
+    LOG(WARNING) << "invalid static priority " << static_priority;
+  }
+  prio.static_priority = static_priority;
+
+  int priority;
+  if (!ReinterpretBytes(prio, &priority)) {
+    LOG(ERROR) << "Couldn't set priority";
+    return -1;
+  }
+  return priority;
+}
+
+}  // namespace
+
 ThreadSnapshotLinux::ThreadSnapshotLinux()
     : ThreadSnapshot(),
       context_union_(),
@@ -31,11 +134,9 @@
       thread_specific_data_address_(0),
       thread_id_(-1),
       priority_(-1),
-      initialized_() {
-}
+      initialized_() {}
 
-ThreadSnapshotLinux::~ThreadSnapshotLinux() {
-}
+ThreadSnapshotLinux::~ThreadSnapshotLinux() {}
 
 bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader,
                                      const ProcessReaderLinux::Thread& thread) {
@@ -89,7 +190,7 @@
 #error Port.
 #endif
 
-  stack_.Initialize(process_reader,
+  stack_.Initialize(process_reader->Memory(),
                     thread.stack_region_address,
                     thread.stack_region_size);
 
@@ -98,98 +199,11 @@
 
   thread_id_ = thread.tid;
 
-  // Map Linux scheduling policy, static priority, and nice value into a single
-  // int value.
-  //
-  // The possible policies in order of approximate priority (low to high) are
-  //   SCHED_IDLE
-  //   SCHED_BATCH
-  //   SCHED_OTHER
-  //   SCHED_RR
-  //   SCHED_FIFO
-  //
-  // static_priority is not used for OTHER, BATCH, or IDLE and should be 0.
-  // For FIFO and RR, static_priority should range from 1 to 99 with 99 being
-  // the highest priority.
-  //
-  // nice value ranges from -20 to 19, with -20 being highest priority
-
-  enum class Policy : uint8_t {
-    kUnknown = 0,
-    kIdle,
-    kBatch,
-    kOther,
-    kRR,
-    kFIFO
-  };
-
-  struct LinuxPriority {
-#if defined(ARCH_CPU_LITTLE_ENDIAN)
-    // nice values affect how dynamic priorities are updated, which only matters
-    // for threads with the same static priority.
-    uint8_t nice_value = 0;
-
-    // The scheduling policy also affects how threads with the same static
-    // priority are ordered, but has greater impact than nice value.
-    Policy policy = Policy::kUnknown;
-
-    // The static priority is the most significant in determining overall
-    // priority.
-    uint8_t static_priority = 0;
-
-    // Put this in the most significant byte position to prevent negative
-    // priorities.
-    uint8_t unused = 0;
-#elif defined(ARCH_CPU_BIG_ENDIAN)
-    uint8_t unused = 0;
-    uint8_t static_priority = 0;
-    Policy policy = Policy::kUnknown;
-    uint8_t nice_value = 0;
-#endif  // ARCH_CPU_LITTLE_ENDIAN
-  };
-  static_assert(sizeof(LinuxPriority) <= sizeof(int), "priority is too large");
-
-  LinuxPriority prio;
-
-  // Lower nice values have higher priority, so negate them and add 20 to put
-  // them in the range 1-40 with 40 being highest priority.
-  if (thread.nice_value < -20 || thread.nice_value > 19) {
-    LOG(WARNING) << "invalid nice value " << thread.nice_value;
-    prio.nice_value = 0;
-  } else {
-    prio.nice_value = -1 * thread.nice_value + 20;
-  }
-
-  switch (thread.sched_policy) {
-    case SCHED_IDLE:
-      prio.policy = Policy::kIdle;
-      break;
-    case SCHED_BATCH:
-      prio.policy = Policy::kBatch;
-      break;
-    case SCHED_OTHER:
-      prio.policy = Policy::kOther;
-      break;
-    case SCHED_RR:
-      prio.policy = Policy::kRR;
-      break;
-    case SCHED_FIFO:
-      prio.policy = Policy::kFIFO;
-      break;
-    default:
-      prio.policy = Policy::kUnknown;
-      LOG(WARNING) << "Unknown scheduling policy " << thread.sched_policy;
-  }
-
-  if (thread.static_priority < 0 || thread.static_priority > 99) {
-    LOG(WARNING) << "invalid static priority " << thread.static_priority;
-  }
-  prio.static_priority = thread.static_priority;
-
-  if (!ReinterpretBytes(prio, &priority_)) {
-    LOG(ERROR) << "Couldn't set priority";
-    return false;
-  }
+  priority_ =
+      thread.have_priorities
+          ? ComputeThreadPriority(
+                thread.static_priority, thread.sched_policy, thread.nice_value)
+          : -1;
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
diff --git a/snapshot/linux/thread_snapshot_linux.h b/snapshot/linux/thread_snapshot_linux.h
index 17e471f..44cc6f6 100644
--- a/snapshot/linux/thread_snapshot_linux.h
+++ b/snapshot/linux/thread_snapshot_linux.h
@@ -73,7 +73,7 @@
 #endif  // ARCH_CPU_X86_FAMILY
   } context_union_;
   CPUContext context_;
-  MemorySnapshotGeneric<ProcessReaderLinux> stack_;
+  MemorySnapshotGeneric stack_;
   LinuxVMAddress thread_specific_data_address_;
   pid_t thread_id_;
   int priority_;
diff --git a/snapshot/mac/cpu_context_mac.cc b/snapshot/mac/cpu_context_mac.cc
index c91e331..03893c4 100644
--- a/snapshot/mac/cpu_context_mac.cc
+++ b/snapshot/mac/cpu_context_mac.cc
@@ -436,6 +436,136 @@
 
 }  // namespace internal
 
+#elif defined(ARCH_CPU_ARM_FAMILY)
+
+namespace {
+
+void InitializeCPUContextARM64Thread(
+    CPUContextARM64* context,
+    const arm_thread_state64_t* arm_thread_state64) {
+  // The structures of context->regs and arm_thread_state64->__x are laid out
+  // identically for this copy, even though the members are organized
+  // differently.  Because of this difference, there can't be a static assert
+  // similar to the one below for fpsimd.
+  memcpy(context->regs, arm_thread_state64->__x, sizeof(context->regs));
+  context->sp = arm_thread_state64->__sp;
+  context->pc = arm_thread_state64->__pc;
+  context->spsr =
+      static_cast<decltype(context->spsr)>(arm_thread_state64->__cpsr);
+}
+
+void InitializeCPUContextARM64Neon(CPUContextARM64* context,
+                                   const arm_neon_state64_t* arm_neon_state64) {
+  static_assert(sizeof(context->fpsimd) == sizeof(arm_neon_state64->__v),
+                "fpsimd context size mismatch");
+  memcpy(context->fpsimd, arm_neon_state64->__v, sizeof(arm_neon_state64->__v));
+  context->fpsr = arm_neon_state64->__fpsr;
+  context->fpcr = arm_neon_state64->__fpcr;
+}
+
+thread_state_flavor_t InitializeCPUContextARM64Flavor(
+    CPUContextARM64* context,
+    thread_state_flavor_t flavor,
+    ConstThreadState state,
+    mach_msg_type_number_t state_count) {
+  mach_msg_type_number_t expected_state_count;
+  switch (flavor) {
+    case ARM_THREAD_STATE:
+      expected_state_count = ARM_THREAD_STATE_COUNT;
+      break;
+    case ARM_THREAD_STATE64:
+      expected_state_count = ARM_THREAD_STATE64_COUNT;
+      break;
+    case ARM_NEON_STATE64:
+      expected_state_count = ARM_NEON_STATE64_COUNT;
+      break;
+    case THREAD_STATE_NONE: {
+      // This may happen without error when called without exception-style
+      // flavor data, or even from an exception handler when the exception
+      // behavior is EXCEPTION_DEFAULT.
+      return flavor;
+    }
+    default:
+      LOG(WARNING) << "unhandled flavor " << flavor;
+      return THREAD_STATE_NONE;
+  }
+
+  if (state_count < expected_state_count) {
+    LOG(WARNING) << "expected state_count " << expected_state_count
+                 << " for flavor " << flavor << ", observed " << state_count;
+    return THREAD_STATE_NONE;
+  }
+
+  switch (flavor) {
+    case ARM_THREAD_STATE: {
+      const arm_unified_thread_state_t* arm_thread_state =
+          reinterpret_cast<const arm_unified_thread_state_t*>(state);
+      if (arm_thread_state->ash.flavor != ARM_THREAD_STATE64) {
+        LOG(WARNING) << "expected flavor ARM_THREAD_STATE64, observed "
+                     << arm_thread_state->ash.flavor;
+        return THREAD_STATE_NONE;
+      }
+      return InitializeCPUContextARM64Flavor(
+          context,
+          arm_thread_state->ash.flavor,
+          reinterpret_cast<ConstThreadState>(&arm_thread_state->ts_64),
+          arm_thread_state->ash.count);
+    }
+
+    case ARM_THREAD_STATE64: {
+      const arm_thread_state64_t* arm_thread_state =
+          reinterpret_cast<const arm_thread_state64_t*>(state);
+      InitializeCPUContextARM64Thread(context, arm_thread_state);
+      return ARM_THREAD_STATE64;
+    }
+
+    case ARM_NEON_STATE64: {
+      const arm_neon_state64_t* arm_neon_state =
+          reinterpret_cast<const arm_neon_state64_t*>(state);
+      InitializeCPUContextARM64Neon(context, arm_neon_state);
+      return ARM_NEON_STATE64;
+    }
+
+    case THREAD_STATE_NONE: {
+      // This may happen without error when called without exception-style
+      // flavor data, or even from an exception handler when the exception
+      // behavior is EXCEPTION_DEFAULT.
+      return flavor;
+    }
+
+    default: {
+      NOTREACHED();
+      return THREAD_STATE_NONE;
+    }
+  }
+}
+
+}  // namespace
+
+namespace internal {
+
+void InitializeCPUContextARM64(CPUContextARM64* context,
+                               thread_state_flavor_t flavor,
+                               ConstThreadState state,
+                               mach_msg_type_number_t state_count,
+                               const arm_thread_state64_t* arm_thread_state64,
+                               const arm_neon_state64_t* arm_neon_state64) {
+  thread_state_flavor_t set_flavor = THREAD_STATE_NONE;
+  if (flavor != THREAD_STATE_NONE) {
+    set_flavor =
+        InitializeCPUContextARM64Flavor(context, flavor, state, state_count);
+  }
+
+  if (set_flavor != ARM_THREAD_STATE64) {
+    InitializeCPUContextARM64Thread(context, arm_thread_state64);
+  }
+  if (set_flavor != ARM_NEON_STATE64) {
+    InitializeCPUContextARM64Neon(context, arm_neon_state64);
+  }
+}
+
+}  // namespace internal
+
 #endif
 
 }  // namespace crashpad
diff --git a/snapshot/mac/cpu_context_mac.h b/snapshot/mac/cpu_context_mac.h
index 30281c1..05c035a 100644
--- a/snapshot/mac/cpu_context_mac.h
+++ b/snapshot/mac/cpu_context_mac.h
@@ -108,6 +108,44 @@
                                 const x86_float_state64_t* x86_float_state64,
                                 const x86_debug_state64_t* x86_debug_state64);
 
+#elif defined(ARCH_CPU_ARM_FAMILY) || DOXYGEN
+//! \brief Initializes a CPUContextARM64 structure from native context
+//!     structures on iOS.
+//!
+//! \a flavor, \a state, and \a state_count may be supplied by exception
+//! handlers in order for the \a context parameter to be initialized by the
+//! thread state received by the exception handler to the extent possible. In
+//! that case, whatever thread state specified by these three parameters will
+//! supersede \a arm_thread_state64 or \a arm_neon_state64. If thread state in
+//! this format is not available, \a flavor may be set to `THREAD_STATE_NONE`,
+//! and all of \a arm_thread_state64 abd \a arm_neon_state64 will be honored.
+//!
+//! If \a flavor, \a state, and \a state_count are provided but do not contain
+//! valid values, a message will be logged and their values will be ignored as
+//! though \a flavor were specified as `THREAD_STATE_NONE`.
+//!
+//! \param[out] context The CPUContextARM64 structure to initialize.
+//! \param[in] flavor The native thread state flavor of \a state. This may be
+//!     `ARM_THREAD_STATE64`, `ARM_THREAD_STATE` or `ARM_NEON_STATE64`. It may
+//!     also be `THREAD_STATE_NONE` if \a state is not supplied (and is
+//!     `nullptr`).
+//! \param[in] state The native thread state, which may be a casted pointer to
+//!     `arm_thread_state64_t`, `arm_unified_thread_state` or
+//!     `arm_neon_state64_t`. This parameter may be `nullptr` to not supply this
+//!     data, in which case \a flavor must be `THREAD_STATE_NONE`. If a
+//!     “universal” structure is used, it must carry 64-bit state data of the
+//!     correct type.
+//! \param[in] state_count The number of `int`-sized units in \a state. This
+//!     may be 0 if \a state is `nullptr`.
+//! \param[in] arm_thread_state64 The state of the thread’s integer registers.
+//! \param[in] arm_neon_state64 The state of the thread’s floating-point
+//!     registers.
+void InitializeCPUContextARM64(CPUContextARM64* context,
+                               thread_state_flavor_t flavor,
+                               ConstThreadState state,
+                               mach_msg_type_number_t state_count,
+                               const arm_thread_state64_t* arm_thread_state64,
+                               const arm_neon_state64_t* arm_neon_state64);
 #endif
 
 }  // namespace internal
diff --git a/snapshot/mac/exception_snapshot_mac.cc b/snapshot/mac/exception_snapshot_mac.cc
index 50d1a12..8eefac9 100644
--- a/snapshot/mac/exception_snapshot_mac.cc
+++ b/snapshot/mac/exception_snapshot_mac.cc
@@ -142,8 +142,13 @@
 
   thread_id_ = thread->id;
 
-  // Normally, the exception address is present in code[1] for EXC_BAD_ACCESS
-  // exceptions, but not for other types of exceptions.
+  // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present
+  // in code[1]. It may or may not be the instruction pointer address (usually
+  // it’s not). code[1] may carry the exception address for other exception
+  // types too, but it’s not guaranteed. But for all other exception types, the
+  // instruction pointer will be the exception address, and in fact will be
+  // equal to codes[1] when it’s carrying the exception address. In those cases,
+  // just use the instruction pointer directly.
   bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS;
 
 #if defined(ARCH_CPU_X86_FAMILY)
diff --git a/snapshot/mac/mach_o_image_annotations_reader.cc b/snapshot/mac/mach_o_image_annotations_reader.cc
index b02acae..eefd7f4 100644
--- a/snapshot/mac/mach_o_image_annotations_reader.cc
+++ b/snapshot/mac/mach_o_image_annotations_reader.cc
@@ -26,7 +26,6 @@
 #include "snapshot/mac/mach_o_image_reader.h"
 #include "snapshot/mac/process_reader_mac.h"
 #include "snapshot/snapshot_constants.h"
-#include "util/mach/task_memory.h"
 #include "util/stdlib/strnlen.h"
 
 namespace crashpad {
@@ -179,9 +178,9 @@
   }
 }
 
-// TODO(rsesek): When there is a platform-agnostic remote memory reader
-// interface available, use it so that the implementation is not duplicated
-// in the PEImageAnnotationsReader.
+// TODO(https://crbug.com/crashpad/270): Replace implementations of
+// ReadCrashpadAnnotationsList and ReadCrashpadSimpleAnnotations with the
+// platform-agnostic implementations in ImageAnnotationReader.
 void MachOImageAnnotationsReader::ReadCrashpadAnnotationsList(
     std::vector<AnnotationSnapshot>* annotations) const {
   process_types::CrashpadInfo crashpad_info;
diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc
index 6930250..8659a14 100644
--- a/snapshot/mac/mach_o_image_annotations_reader_test.cc
+++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc
@@ -461,7 +461,13 @@
   test_mach_o_image_annotations_reader.Run();
 }
 
-TEST(MachOImageAnnotationsReader, CrashModuleInitialization) {
+#if defined(ADDRESS_SANITIZER)
+// https://crbug.com/844396
+#define MAYBE_CrashModuleInitialization DISABLED_CrashModuleInitialization
+#else
+#define MAYBE_CrashModuleInitialization CrashModuleInitialization
+#endif
+TEST(MachOImageAnnotationsReader, MAYBE_CrashModuleInitialization) {
   TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader(
       TestMachOImageAnnotationsReader::kCrashModuleInitialization);
   test_mach_o_image_annotations_reader.Run();
diff --git a/snapshot/mac/mach_o_image_reader.cc b/snapshot/mac/mach_o_image_reader.cc
index 6baee77..bd258ca 100644
--- a/snapshot/mac/mach_o_image_reader.cc
+++ b/snapshot/mac/mach_o_image_reader.cc
@@ -22,6 +22,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "client/crashpad_info.h"
 #include "snapshot/mac/mach_o_image_segment_reader.h"
@@ -182,7 +183,7 @@
   // This vector is parallel to the kLoadCommandReaders array, and tracks
   // whether a singleton load command matching the |command| field has been
   // found yet.
-  std::vector<uint32_t> singleton_indices(arraysize(kLoadCommandReaders),
+  std::vector<uint32_t> singleton_indices(base::size(kLoadCommandReaders),
                                           kInvalidSegmentIndex);
 
   size_t offset = mach_header.Size();
@@ -236,7 +237,7 @@
     }
 
     for (size_t reader_index = 0;
-         reader_index < arraysize(kLoadCommandReaders);
+         reader_index < base::size(kLoadCommandReaders);
          ++reader_index) {
       if (load_command.cmd != kLoadCommandReaders[reader_index].command) {
         continue;
diff --git a/snapshot/mac/mach_o_image_segment_reader_test.cc b/snapshot/mac/mach_o_image_segment_reader_test.cc
index 2da97e8..4731a5f 100644
--- a/snapshot/mac/mach_o_image_segment_reader_test.cc
+++ b/snapshot/mac/mach_o_image_segment_reader_test.cc
@@ -16,7 +16,7 @@
 
 #include <mach-o/loader.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 
@@ -63,7 +63,7 @@
       SEG_IMPORT,
   };
 
-  for (size_t index = 0; index < arraysize(kSegmentTestData); ++index) {
+  for (size_t index = 0; index < base::size(kSegmentTestData); ++index) {
     EXPECT_EQ(
         MachOImageSegmentReader::SegmentNameString(kSegmentTestData[index]),
         kSegmentTestData[index])
@@ -106,7 +106,7 @@
       SECT_ICON_TIFF,
   };
 
-  for (size_t index = 0; index < arraysize(kSectionTestData); ++index) {
+  for (size_t index = 0; index < base::size(kSectionTestData); ++index) {
     EXPECT_EQ(
         MachOImageSegmentReader::SectionNameString(kSectionTestData[index]),
         kSectionTestData[index])
@@ -169,7 +169,7 @@
       {SEG_IMPORT, "", "__IMPORT,"},
   };
 
-  for (size_t index = 0; index < arraysize(kSegmentAndSectionTestData);
+  for (size_t index = 0; index < base::size(kSegmentAndSectionTestData);
        ++index) {
     const auto& test = kSegmentAndSectionTestData[index];
     EXPECT_EQ(MachOImageSegmentReader::SegmentAndSectionNameString(
diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.cc b/snapshot/mac/mach_o_image_symbol_table_reader.cc
index 361253c..cbdac40 100644
--- a/snapshot/mac/mach_o_image_symbol_table_reader.cc
+++ b/snapshot/mac/mach_o_image_symbol_table_reader.cc
@@ -23,7 +23,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "util/mac/checked_mach_address_range.h"
-#include "util/mach/task_memory.h"
+#include "util/process/process_memory_mac.h"
 
 namespace crashpad {
 
@@ -108,7 +108,7 @@
       return false;
     }
 
-    std::unique_ptr<TaskMemory::MappedMemory> string_table;
+    std::unique_ptr<ProcessMemoryMac::MappedMemory> string_table;
     for (size_t symbol_index = 0; symbol_index < symbol_count; ++symbol_index) {
       const process_types::nlist& symbol = symbols[symbol_index];
       std::string symbol_info = base::StringPrintf(", symbol index %zu%s",
diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.h b/snapshot/mac/mach_o_image_symbol_table_reader.h
index 841b479..b1bcc3a 100644
--- a/snapshot/mac/mach_o_image_symbol_table_reader.h
+++ b/snapshot/mac/mach_o_image_symbol_table_reader.h
@@ -58,7 +58,7 @@
   // there aren’t expected to be very many of those that performance would
   // become a problem. In reality, std::unordered_map does not appear to provide
   // a performance advantage. It appears that the memory copies currently done
-  // by TaskMemory::Read() have substantially more impact on symbol table
+  // by ProcessMemoryMac::Read() have substantially more impact on symbol table
   // operations.
   //
   // This is public so that the type is available to
diff --git a/snapshot/mac/module_snapshot_mac.cc b/snapshot/mac/module_snapshot_mac.cc
index 4160897..947744d 100644
--- a/snapshot/mac/module_snapshot_mac.cc
+++ b/snapshot/mac/module_snapshot_mac.cc
@@ -14,8 +14,8 @@
 
 #include "snapshot/mac/module_snapshot_mac.h"
 
-#include <mach/mach.h>
 #include <mach-o/loader.h>
+#include <mach/mach.h>
 
 #include "base/files/file_path.h"
 #include "base/strings/stringprintf.h"
@@ -34,11 +34,9 @@
       timestamp_(0),
       mach_o_image_reader_(nullptr),
       process_reader_(nullptr),
-      initialized_() {
-}
+      initialized_() {}
 
-ModuleSnapshotMac::~ModuleSnapshotMac() {
-}
+ModuleSnapshotMac::~ModuleSnapshotMac() {}
 
 bool ModuleSnapshotMac::Initialize(
     ProcessReaderMac* process_reader,
@@ -170,6 +168,11 @@
   return base::FilePath(Name()).BaseName().value();
 }
 
+std::vector<uint8_t> ModuleSnapshotMac::BuildID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<uint8_t>();
+}
+
 std::vector<std::string> ModuleSnapshotMac::AnnotationsVector() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   MachOImageAnnotationsReader annotations_reader(
diff --git a/snapshot/mac/module_snapshot_mac.h b/snapshot/mac/module_snapshot_mac.h
index fe2d40a..d78c326 100644
--- a/snapshot/mac/module_snapshot_mac.h
+++ b/snapshot/mac/module_snapshot_mac.h
@@ -77,6 +77,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
diff --git a/snapshot/mac/process_reader_mac.cc b/snapshot/mac/process_reader_mac.cc
index e142fd2..61dc3a1 100644
--- a/snapshot/mac/process_reader_mac.cc
+++ b/snapshot/mac/process_reader_mac.cc
@@ -92,7 +92,7 @@
       threads_(),
       modules_(),
       module_readers_(),
-      task_memory_(),
+      process_memory_(),
       task_(TASK_NULL),
       initialized_(),
       is_64_bit_(false),
@@ -113,9 +113,11 @@
     return false;
   }
 
-  is_64_bit_ = process_info_.Is64Bit();
+  if (!process_memory_.Initialize(task)) {
+    return false;
+  }
 
-  task_memory_.reset(new TaskMemory(task));
+  is_64_bit_ = process_info_.Is64Bit();
   task_ = task;
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
@@ -441,7 +443,7 @@
     Module module;
     module.timestamp = image_info.imageFileModDate;
 
-    if (!task_memory_->ReadCString(image_info.imageFilePath, &module.name)) {
+    if (!process_memory_.ReadCString(image_info.imageFilePath, &module.name)) {
       LOG(WARNING) << "could not read dyld_image_info::imageFilePath";
       // Proceed anyway with an empty module name.
     }
diff --git a/snapshot/mac/process_reader_mac.h b/snapshot/mac/process_reader_mac.h
index 91836db..9feb8b0 100644
--- a/snapshot/mac/process_reader_mac.h
+++ b/snapshot/mac/process_reader_mac.h
@@ -27,9 +27,9 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
-#include "util/mach/task_memory.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/posix/process_info.h"
+#include "util/process/process_memory_mac.h"
 
 namespace crashpad {
 
@@ -138,7 +138,7 @@
   bool CPUTimes(timeval* user_time, timeval* system_time) const;
 
   //! \return Accesses the memory of the target task.
-  TaskMemory* Memory() { return task_memory_.get(); }
+  const ProcessMemoryMac* Memory() const { return &process_memory_; }
 
   //! \return The threads that are in the task (process). The first element (at
   //!     index `0`) corresponds to the main thread.
@@ -232,7 +232,7 @@
   std::vector<Thread> threads_;  // owns send rights
   std::vector<Module> modules_;
   std::vector<std::unique_ptr<MachOImageReader>> module_readers_;
-  std::unique_ptr<TaskMemory> task_memory_;
+  ProcessMemoryMac process_memory_;
   task_t task_;  // weak
   InitializationStateDcheck initialized_;
 
diff --git a/snapshot/mac/process_reader_mac_test.cc b/snapshot/mac/process_reader_mac_test.cc
index 59884a6..9325b2c 100644
--- a/snapshot/mac/process_reader_mac_test.cc
+++ b/snapshot/mac/process_reader_mac_test.cc
@@ -15,6 +15,7 @@
 #include "snapshot/mac/process_reader_mac.h"
 
 #include <AvailabilityMacros.h>
+#include <errno.h>
 #include <OpenCL/opencl.h>
 #include <mach-o/dyld.h>
 #include <mach-o/dyld_images.h>
@@ -26,8 +27,9 @@
 #include <utility>
 
 #include "base/logging.h"
-#include "base/mac/scoped_mach_port.h"
+#include "base/mac/mach_logging.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -63,7 +65,7 @@
   EXPECT_EQ(process_reader.ParentProcessID(), getppid());
 
   static constexpr char kTestMemory[] = "Some test memory";
-  char buffer[arraysize(kTestMemory)];
+  char buffer[base::size(kTestMemory)];
   ASSERT_TRUE(process_reader.Memory()->Read(
       FromPointerCast<mach_vm_address_t>(kTestMemory),
       sizeof(kTestMemory),
@@ -127,8 +129,8 @@
 // This function CHECKs success and returns the thread ID directly.
 uint64_t PthreadToThreadID(pthread_t pthread) {
   uint64_t thread_id;
-  int rv = pthread_threadid_np(pthread, &thread_id);
-  CHECK_EQ(rv, 0);
+  errno = pthread_threadid_np(pthread, &thread_id);
+  PCHECK(errno == 0) << "pthread_threadid_np";
   return thread_id;
 }
 
@@ -410,6 +412,18 @@
   EXPECT_TRUE(found_thread_self);
 }
 
+uint64_t GetThreadID() {
+  thread_identifier_info info;
+  mach_msg_type_number_t info_count = THREAD_IDENTIFIER_INFO_COUNT;
+  kern_return_t kr = thread_info(MachThreadSelf(),
+                                 THREAD_IDENTIFIER_INFO,
+                                 reinterpret_cast<thread_info_t>(&info),
+                                 &info_count);
+  MACH_CHECK(kr == KERN_SUCCESS, kr) << "thread_info";
+
+  return info.thread_id;
+}
+
 class ProcessReaderThreadedChild final : public MachMultiprocess {
  public:
   explicit ProcessReaderThreadedChild(size_t thread_count)
@@ -463,7 +477,7 @@
 
     // This thread isn’t part of the thread pool, but the parent will be able
     // to inspect it. Write an entry for it.
-    uint64_t thread_id = PthreadToThreadID(pthread_self());
+    uint64_t thread_id = GetThreadID();
 
     CheckedWriteFile(write_handle, &thread_id, sizeof(thread_id));
 
@@ -599,11 +613,11 @@
     const size_t source_lengths[] = {
         strlen(sources[0]),
     };
-    static_assert(arraysize(sources) == arraysize(source_lengths),
+    static_assert(base::size(sources) == base::size(source_lengths),
                   "arrays must be parallel");
 
     program_ = clCreateProgramWithSource(
-        context_, arraysize(sources), sources, source_lengths, &rv);
+        context_, base::size(sources), sources, source_lengths, &rv);
     ASSERT_EQ(rv, CL_SUCCESS) << "clCreateProgramWithSource";
 
     rv = clBuildProgram(
diff --git a/snapshot/mac/process_snapshot_mac.cc b/snapshot/mac/process_snapshot_mac.cc
index cf5233a..528e921 100644
--- a/snapshot/mac/process_snapshot_mac.cc
+++ b/snapshot/mac/process_snapshot_mac.cc
@@ -217,6 +217,11 @@
   return std::vector<const MemorySnapshot*>();
 }
 
+const ProcessMemory* ProcessSnapshotMac::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return process_reader_.Memory();
+}
+
 void ProcessSnapshotMac::InitializeThreads() {
   const std::vector<ProcessReaderMac::Thread>& process_reader_threads =
       process_reader_.Threads();
diff --git a/snapshot/mac/process_snapshot_mac.h b/snapshot/mac/process_snapshot_mac.h
index 06bac74..2edc222 100644
--- a/snapshot/mac/process_snapshot_mac.h
+++ b/snapshot/mac/process_snapshot_mac.h
@@ -131,6 +131,7 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
   // Initializes threads_ on behalf of Initialize().
diff --git a/snapshot/mac/process_types.cc b/snapshot/mac/process_types.cc
index 65c39ea..0a1b7f9 100644
--- a/snapshot/mac/process_types.cc
+++ b/snapshot/mac/process_types.cc
@@ -20,9 +20,9 @@
 
 #include <memory>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "snapshot/mac/process_types/internal.h"
-#include "util/mach/task_memory.h"
+#include "util/process/process_memory_mac.h"
 
 namespace crashpad {
 namespace {
@@ -74,7 +74,7 @@
 template <>
 inline void Assign<UInt64Array4, UInt32Array4>(UInt64Array4* destination,
                                                const UInt32Array4& source) {
-  for (size_t index = 0; index < arraysize(source); ++index) {
+  for (size_t index = 0; index < base::size(source); ++index) {
     (*destination)[index] = source[index];
   }
 }
diff --git a/snapshot/mac/process_types/custom.cc b/snapshot/mac/process_types/custom.cc
index 7c7b172..a76c3eb 100644
--- a/snapshot/mac/process_types/custom.cc
+++ b/snapshot/mac/process_types/custom.cc
@@ -19,13 +19,16 @@
 #include <sys/types.h>
 
 #include <algorithm>
+#include <limits>
 #include <type_traits>
 
 #include "base/logging.h"
 #include "base/numerics/safe_math.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "snapshot/mac/process_types/internal.h"
-#include "util/mach/task_memory.h"
+#include "util/mac/mac_util.h"
+#include "util/process/process_memory_mac.h"
 
 #if !DOXYGEN
 
@@ -36,13 +39,13 @@
 namespace {
 
 template <typename T>
-bool ReadIntoAndZero(TaskMemory* task_memory,
+bool ReadIntoAndZero(const ProcessMemoryMac* process_memory,
                      mach_vm_address_t address,
                      mach_vm_size_t size,
                      T* specific) {
   DCHECK_LE(size, sizeof(*specific));
 
-  if (!task_memory->Read(address, size, specific)) {
+  if (!process_memory->Read(address, size, specific)) {
     return false;
   }
 
@@ -84,14 +87,14 @@
     return false;
   }
 
-  TaskMemory* task_memory = process_reader->Memory();
+  const ProcessMemoryMac* process_memory = process_reader->Memory();
   decltype(specific->version) version;
-  if (!task_memory->Read(field_address, sizeof(version), &version)) {
+  if (!process_memory->Read(field_address, sizeof(version), &version)) {
     return false;
   }
 
   const size_t size = T::ExpectedSizeForVersion(version);
-  return ReadIntoAndZero(task_memory, address, size, specific);
+  return ReadIntoAndZero(process_memory, address, size, specific);
 }
 
 template <typename T>
@@ -103,9 +106,9 @@
     return false;
   }
 
-  TaskMemory* task_memory = process_reader->Memory();
+  const ProcessMemoryMac* process_memory = process_reader->Memory();
   decltype(specific->size) size;
-  if (!task_memory->Read(address + offsetof(T, size), sizeof(size), &size)) {
+  if (!process_memory->Read(address + offsetof(T, size), sizeof(size), &size)) {
     return false;
   }
 
@@ -115,7 +118,7 @@
   }
 
   size = std::min(static_cast<size_t>(size), sizeof(*specific));
-  return ReadIntoAndZero(task_memory, address, size, specific);
+  return ReadIntoAndZero(process_memory, address, size, specific);
 }
 
 }  // namespace
@@ -139,18 +142,38 @@
       offsetof(dyld_all_image_infos<Traits>, sharedCacheSlide),  // 11
       offsetof(dyld_all_image_infos<Traits>, sharedCacheUUID),  // 12
       offsetof(dyld_all_image_infos<Traits>, infoArrayChangeTimestamp),  // 13
-      offsetof(dyld_all_image_infos<Traits>, end_14_15),  // 14
-      offsetof(dyld_all_image_infos<Traits>, end_14_15),  // 15
+      offsetof(dyld_all_image_infos<Traits>, end_14),  // 14
+      std::numeric_limits<size_t>::max(),  // 15, see below
       sizeof(dyld_all_image_infos<Traits>),  // 16
   };
 
-  if (version >= arraysize(kSizeForVersion)) {
-    return kSizeForVersion[arraysize(kSizeForVersion) - 1];
+  if (version >= base::size(kSizeForVersion)) {
+    return kSizeForVersion[base::size(kSizeForVersion) - 1];
   }
 
   static_assert(std::is_unsigned<decltype(version)>::value,
                 "version must be unsigned");
-  return kSizeForVersion[version];
+
+  if (version == 15) {
+    // Disambiguate between the two different layouts for version 15. The
+    // original one introduced in macOS 10.12 had the same size as version 14.
+    // The revised one in macOS 10.13 grew. It’s safe to assume that the
+    // dyld_all_image_infos structure came from the same system that’s now
+    // interpreting it, so use an OS version check.
+    int mac_os_x_minor_version = MacOSXMinorVersion();
+    if (mac_os_x_minor_version == 12) {
+      return offsetof(dyld_all_image_infos<Traits>, end_14);
+    }
+
+    DCHECK_GE(mac_os_x_minor_version, 13);
+    DCHECK_LE(mac_os_x_minor_version, 14);
+    return offsetof(dyld_all_image_infos<Traits>, platform);
+  }
+
+  size_t size = kSizeForVersion[version];
+  DCHECK_NE(size, std::numeric_limits<size_t>::max());
+
+  return size;
 }
 
 // static
diff --git a/snapshot/mac/process_types/dyld_images.proctype b/snapshot/mac/process_types/dyld_images.proctype
index f92a800..3b04085 100644
--- a/snapshot/mac/process_types/dyld_images.proctype
+++ b/snapshot/mac/process_types/dyld_images.proctype
@@ -121,23 +121,29 @@
   // the runtimes that use versions 14 and 15 were built with SDKs that did not
   // have this extra padding, it’s necessary to treat the element at index 4 on
   // 32-bit systems as outside of the version 14 and 15 structure. This is why
-  // |reserved| is only declared a 4-element array, with a special end_14_15
-  // member (not present in the native definition) available to indicate the
-  // end of the native version 14 and 15 structure, preceding the padding in the
-  // 32-bit structure that would natively be addressed at index 4 of |reserved|.
-  // Treat reserved_4_32 as only available in version 16 of the structure.
+  // |reserved| is only declared a 4-element array, with a special end_14 member
+  // (not present in the native definition) available to indicate the end of the
+  // native version 14 structure and the 10.12 version 15 structure, preceding
+  // the padding in the 32-bit structure that would natively be addressed at
+  // index 4 of |reserved|. Treat reserved_4_32 as only available in version 16
+  // of the structure.
   PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, reserved, [4])
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_4_64)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_5)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_6)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_7)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_8)
-  PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14_15)
+  PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved32_32Only, reserved_4_32)
 
-  // Version 16 (macOS 10.13)
+  // Version 15 (macOS 10.13). <mach-o/dyld_images.h> incorrectly claims that
+  // these were introduced at version 16. These fields are not present in macOS
+  // 10.12, which also identifies its structure as version 15.
   PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, compact_dyld_image_info_addr)
   PROCESS_TYPE_STRUCT_MEMBER(ULong, compact_dyld_image_info_size)  // size_t
+
+  // Version 16 (macOS 10.15)
+  PROCESS_TYPE_STRUCT_MEMBER(uint32_t, platform)  // dyld_platform_t
 PROCESS_TYPE_STRUCT_END(dyld_all_image_infos)
 
 #endif  // ! PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO &&
diff --git a/snapshot/mac/process_types_test.cc b/snapshot/mac/process_types_test.cc
index f116c4d..0ad2869 100644
--- a/snapshot/mac/process_types_test.cc
+++ b/snapshot/mac/process_types_test.cc
@@ -18,9 +18,10 @@
 #include <mach/mach.h>
 #include <string.h>
 
+#include <limits>
 #include <vector>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -47,13 +48,11 @@
 TEST(ProcessTypes, DyldImagesSelf) {
   // Get the in-process view of dyld_all_image_infos, and check it for sanity.
   const dyld_all_image_infos* self_image_infos = DyldGetAllImageInfos();
-  int mac_os_x_minor_version = MacOSXMinorVersion();
+  const int mac_os_x_minor_version = MacOSXMinorVersion();
 
-  // The 10.13 SDK defines dyld_all_image_infos version 16 and says that it’s
-  // used on 10.13, but 10.13db1 17A264c uses version 15.
-  //
-  // TODO(mark): Recheck later in the beta period, up to the 10.13 release.
-  if (mac_os_x_minor_version >= 12) {
+  if (mac_os_x_minor_version >= 15) {
+    EXPECT_GE(self_image_infos->version, 16u);
+  } else if (mac_os_x_minor_version >= 12) {
     EXPECT_GE(self_image_infos->version, 15u);
   } else if (mac_os_x_minor_version >= 9) {
     EXPECT_GE(self_image_infos->version, 13u);
@@ -104,7 +103,7 @@
   ProcessReaderMac process_reader;
   ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
 
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
   constexpr uint32_t kDyldAllImageInfosVersionInSDK = 16;
 #elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
   constexpr uint32_t kDyldAllImageInfosVersionInSDK = 15;
@@ -120,12 +119,33 @@
 
   // Make sure that the size of the structure as declared in the SDK matches the
   // size expected for the version of the structure that the SDK describes.
-  EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion(
-                &process_reader, kDyldAllImageInfosVersionInSDK),
-            sizeof(dyld_all_image_infos));
+  //
+  // There are two possible layouts for version 15, and the
+  // ExpectedSizeForVersion() implementation infers the correct one based on the
+  // run-time OS version, so if the SDK defines the version 15 structure, this
+  // test can only be performed if the run-time OS natively uses the same format
+  // structure as the SDK.
+  bool test_expected_size_for_version_matches_sdk_sizeof;
+#if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_12
+  test_expected_size_for_version_matches_sdk_sizeof =
+      mac_os_x_minor_version == 12;
+#elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 && \
+    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_15
+  test_expected_size_for_version_matches_sdk_sizeof =
+      mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14;
+#else
+  test_expected_size_for_version_matches_sdk_sizeof = true;
+#endif
+
+  if (test_expected_size_for_version_matches_sdk_sizeof) {
+    EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion(
+                  &process_reader, kDyldAllImageInfosVersionInSDK),
+              sizeof(dyld_all_image_infos));
+  }
 
   // Make sure that the computed sizes of various versions of this structure are
   // correct at different bitnessses.
+  constexpr size_t kSpecialCase = std::numeric_limits<size_t>::max();
   constexpr struct {
     uint32_t version;
     size_t size_32;
@@ -144,13 +164,40 @@
       {12, 84, 160},
       {13, 104, 184},
       {14, 164, 304},
-      {15, 164, 304},
-      {16, 176, 320},
+      {15, kSpecialCase, kSpecialCase},
+      {16, 184, 328},
   };
-  for (size_t index = 0; index < arraysize(kVersionsAndSizes); ++index) {
+  for (size_t index = 0; index < base::size(kVersionsAndSizes); ++index) {
     uint32_t version = kVersionsAndSizes[index].version;
     SCOPED_TRACE(base::StringPrintf("index %zu, version %u", index, version));
 
+    if (version == 15) {
+      if (mac_os_x_minor_version == 12) {
+        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
+                      process_types::internal::Traits32>::
+                      ExpectedSizeForVersion(version),
+                  164u);
+        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
+                      process_types::internal::Traits64>::
+                      ExpectedSizeForVersion(version),
+                  304u);
+      } else if (mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14) {
+        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
+                      process_types::internal::Traits32>::
+                      ExpectedSizeForVersion(version),
+                  176u);
+        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
+                      process_types::internal::Traits64>::
+                      ExpectedSizeForVersion(version),
+                  320u);
+      }
+
+      continue;
+    }
+
+    ASSERT_NE(kVersionsAndSizes[index].size_32, kSpecialCase);
+    ASSERT_NE(kVersionsAndSizes[index].size_64, kSpecialCase);
+
     EXPECT_EQ(
         process_types::internal::dyld_all_image_infos<
             process_types::internal::Traits32>::ExpectedSizeForVersion(version),
@@ -268,8 +315,7 @@
               self_image_infos->sharedCacheBaseAddress);
     EXPECT_EQ(proctype_image_infos.dyldPath,
               reinterpret_cast<uint64_t>(self_image_infos->dyldPath));
-    for (size_t index = 0;
-         index < arraysize(self_image_infos->notifyPorts);
+    for (size_t index = 0; index < base::size(self_image_infos->notifyPorts);
          ++index) {
       EXPECT_EQ(proctype_image_infos.notifyPorts[index],
                 self_image_infos->notifyPorts[index])
@@ -289,8 +335,7 @@
   // process_types version. It’s difficult to compare the reserved fields in
   // these older SDKs, so only do it where the declarations match.
   if (proctype_image_infos.version >= 14) {
-    for (size_t index = 0;
-         index < arraysize(proctype_image_infos.reserved);
+    for (size_t index = 0; index < base::size(proctype_image_infos.reserved);
          ++index) {
       EXPECT_EQ(proctype_image_infos.reserved[index],
                 implicit_cast<uint64_t>(self_image_infos->reserved[index]))
@@ -308,7 +353,7 @@
 #endif
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
-  if (proctype_image_infos.version >= 16) {
+  if (proctype_image_infos.version >= 15 && mac_os_x_minor_version >= 13) {
     EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_addr,
               self_image_infos->compact_dyld_image_info_addr);
     EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_size,
@@ -316,6 +361,12 @@
   }
 #endif
 
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
+  if (proctype_image_infos.version >= 16) {
+    EXPECT_EQ(proctype_image_infos.platform, self_image_infos->platform);
+  }
+#endif
+
   if (proctype_image_infos.version >= 1) {
     std::vector<process_types::dyld_image_info> proctype_image_info_vector(
         proctype_image_infos.infoArrayCount);
diff --git a/snapshot/mac/system_snapshot_mac.cc b/snapshot/mac/system_snapshot_mac.cc
index 21f254e..5410be0 100644
--- a/snapshot/mac/system_snapshot_mac.cc
+++ b/snapshot/mac/system_snapshot_mac.cc
@@ -14,6 +14,7 @@
 
 #include "snapshot/mac/system_snapshot_mac.h"
 
+#include <AvailabilityMacros.h>
 #include <stddef.h>
 #include <sys/sysctl.h>
 #include <sys/types.h>
@@ -22,6 +23,7 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/scoped_clear_last_error.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
@@ -35,10 +37,15 @@
 namespace {
 
 template <typename T>
+int ReadIntSysctlByName_NoLog(const char* name, T* value) {
+  size_t value_len = sizeof(*value);
+  return sysctlbyname(name, value, &value_len, nullptr, 0);
+}
+
+template <typename T>
 T ReadIntSysctlByName(const char* name, T default_value) {
   T value;
-  size_t value_len = sizeof(value);
-  if (sysctlbyname(name, &value, &value_len, nullptr, 0) != 0) {
+  if (ReadIntSysctlByName_NoLog(name, &value) != 0) {
     PLOG(WARNING) << "sysctlbyname " << name;
     return default_value;
   }
@@ -338,7 +345,35 @@
 
 bool SystemSnapshotMac::NXEnabled() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return ReadIntSysctlByName<int>("kern.nx", 0);
+
+  int value;
+  if (ReadIntSysctlByName_NoLog("kern.nx", &value) != 0) {
+    {
+      // Support for the kern.nx sysctlbyname is compiled out of production
+      // kernels on macOS 10.14.5 and later, although it’s available in
+      // development and debug kernels. Compare 10.14.3
+      // xnu-4903.241.1/bsd/kern/kern_sysctl.c to 10.15.0
+      // xnu-6153.11.26/bsd/kern/kern_sysctl.c (10.14.4 and 10.14.5 xnu source
+      // are not yet available). In newer production kernels, NX is always
+      // enabled. See 10.15.0 xnu-6153.11.26/osfmk/x86_64/pmap.c nx_enabled.
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
+      const bool nx_always_enabled = true;
+#else  // DT >= 10.14
+      base::ScopedClearLastError reset_errno;
+      const bool nx_always_enabled = MacOSXMinorVersion() >= 14;
+#endif  // DT >= 10.14
+      if (nx_always_enabled) {
+        return true;
+      }
+    }
+
+    // Even if sysctlbyname should have worked, NX is enabled by default in all
+    // supported configurations, so return true even while warning.
+    PLOG(WARNING) << "sysctlbyname kern.nx";
+    return true;
+  }
+
+  return value;
 }
 
 void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status,
diff --git a/snapshot/mac/system_snapshot_mac_test.cc b/snapshot/mac/system_snapshot_mac_test.cc
index 69048eb..2b66592 100644
--- a/snapshot/mac/system_snapshot_mac_test.cc
+++ b/snapshot/mac/system_snapshot_mac_test.cc
@@ -126,6 +126,12 @@
   EXPECT_FALSE(system_snapshot().MachineDescription().empty());
 }
 
+TEST_F(SystemSnapshotMacTest, NXEnabled) {
+  // Assume NX will always be enabled, as it was always enabled by default on
+  // all supported versions of macOS.
+  EXPECT_TRUE(system_snapshot().NXEnabled());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/mac/thread_snapshot_mac.cc b/snapshot/mac/thread_snapshot_mac.cc
index f45fc41..8fbb3e7 100644
--- a/snapshot/mac/thread_snapshot_mac.cc
+++ b/snapshot/mac/thread_snapshot_mac.cc
@@ -49,7 +49,7 @@
   thread_specific_data_address_ =
       process_reader_thread.thread_specific_data_address;
 
-  stack_.Initialize(process_reader,
+  stack_.Initialize(process_reader->Memory(),
                     process_reader_thread.stack_region_address,
                     process_reader_thread.stack_region_size);
 
diff --git a/snapshot/mac/thread_snapshot_mac.h b/snapshot/mac/thread_snapshot_mac.h
index 8f5d722..946b008 100644
--- a/snapshot/mac/thread_snapshot_mac.h
+++ b/snapshot/mac/thread_snapshot_mac.h
@@ -70,7 +70,7 @@
   } context_union_;
 #endif
   CPUContext context_;
-  MemorySnapshotGeneric<ProcessReaderMac> stack_;
+  MemorySnapshotGeneric stack_;
   uint64_t thread_id_;
   uint64_t thread_specific_data_address_;
   thread_t thread_;
diff --git a/snapshot/memory_snapshot.h b/snapshot/memory_snapshot.h
index 9e274a0..5dbac5a 100644
--- a/snapshot/memory_snapshot.h
+++ b/snapshot/memory_snapshot.h
@@ -111,31 +111,6 @@
                           const MemorySnapshot* b,
                           CheckedRange<uint64_t, size_t>* merged);
 
-namespace internal {
-
-//! \brief A standard implementation of MemorySnapshot::MergeWithOtherSnapshot()
-//!     for concrete MemorySnapshot implementations that use a
-//!     `process_reader_`.
-template <class T>
-const MemorySnapshot* MergeWithOtherSnapshotImpl(const T* self,
-                                                 const MemorySnapshot* other) {
-  const T* other_as_memory_snapshot_concrete =
-      reinterpret_cast<const T*>(other);
-  if (self->process_reader_ !=
-      other_as_memory_snapshot_concrete->process_reader_) {
-    LOG(ERROR) << "different process_reader_ for snapshots";
-    return nullptr;
-  }
-  CheckedRange<uint64_t, size_t> merged(0, 0);
-  if (!LoggingDetermineMergedRange(self, other, &merged))
-    return nullptr;
-
-  std::unique_ptr<T> result(new T());
-  result->Initialize(self->process_reader_, merged.base(), merged.size());
-  return result.release();
-}
-
-}  // namespace internal
 }  // namespace crashpad
 
 #endif  // CRASHPAD_SNAPSHOT_MEMORY_SNAPSHOT_H_
diff --git a/snapshot/memory_snapshot_generic.h b/snapshot/memory_snapshot_generic.h
index 402e913..d8bc9ec 100644
--- a/snapshot/memory_snapshot_generic.h
+++ b/snapshot/memory_snapshot_generic.h
@@ -19,6 +19,7 @@
 #include <sys/types.h>
 
 #include "base/macros.h"
+#include "base/numerics/safe_math.h"
 #include "snapshot/memory_snapshot.h"
 #include "util/misc/address_types.h"
 #include "util/misc/initialization_state_dcheck.h"
@@ -28,9 +29,8 @@
 namespace internal {
 
 //! \brief A MemorySnapshot of a memory region in a process on the running
-//!     system. Used on Mac, Linux, Android, and Fuchsia, templated on the
-//!     platform-specific ProcessReader type.
-template <class ProcessReaderType>
+//!     system. Works on multiple platforms by using a platform-specific
+//!     ProcessMemory object.
 class MemorySnapshotGeneric final : public MemorySnapshot {
  public:
   MemorySnapshotGeneric() = default;
@@ -42,25 +42,25 @@
   //! until Read() is called, and the memory snapshot data is discared when
   //! Read() returns.
   //!
-  //! \param[in] process_reader A reader for the process being snapshotted.
+  //! \param[in] process_memory A reader for the process being snapshotted.
   //! \param[in] address The base address of the memory region to snapshot, in
   //!     the snapshot process’ address space.
   //! \param[in] size The size of the memory region to snapshot.
-  void Initialize(ProcessReaderType* process_reader,
+  void Initialize(const ProcessMemory* process_memory,
                   VMAddress address,
                   VMSize size) {
     INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-    process_reader_ = process_reader;
+    process_memory_ = process_memory;
     address_ = address;
-    size_ = size;
+    size_ = base::checked_cast<size_t>(size);
     INITIALIZATION_STATE_SET_VALID(initialized_);
   }
 
   // MemorySnapshot:
 
   uint64_t Address() const override {
-  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return address_;
+    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+    return address_;
   }
 
   size_t Size() const override {
@@ -76,7 +76,7 @@
     }
 
     std::unique_ptr<uint8_t[]> buffer(new uint8_t[size_]);
-    if (!process_reader_->Memory()->Read(address_, size_, buffer.get())) {
+    if (!process_memory_->Read(address_, size_, buffer.get())) {
       return false;
     }
     return delegate->MemorySnapshotDelegateRead(buffer.get(), size_);
@@ -84,7 +84,19 @@
 
   const MemorySnapshot* MergeWithOtherSnapshot(
       const MemorySnapshot* other) const override {
-    return MergeWithOtherSnapshotImpl(this, other);
+    const MemorySnapshotGeneric* other_as_memory_snapshot_concrete =
+        reinterpret_cast<const MemorySnapshotGeneric*>(other);
+    if (process_memory_ != other_as_memory_snapshot_concrete->process_memory_) {
+      LOG(ERROR) << "different process_memory_ for snapshots";
+      return nullptr;
+    }
+    CheckedRange<uint64_t, size_t> merged(0, 0);
+    if (!LoggingDetermineMergedRange(this, other, &merged))
+      return nullptr;
+
+    auto result = std::make_unique<MemorySnapshotGeneric>();
+    result->Initialize(process_memory_, merged.base(), merged.size());
+    return result.release();
   }
 
  private:
@@ -93,9 +105,9 @@
       const T* self,
       const MemorySnapshot* other);
 
-  ProcessReaderType* process_reader_;  // weak
-  uint64_t address_;
-  uint64_t size_;
+  const ProcessMemory* process_memory_;  // weak
+  VMAddress address_;
+  size_t size_;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(MemorySnapshotGeneric);
diff --git a/snapshot/minidump/exception_snapshot_minidump.cc b/snapshot/minidump/exception_snapshot_minidump.cc
new file mode 100644
index 0000000..afd3282
--- /dev/null
+++ b/snapshot/minidump/exception_snapshot_minidump.cc
@@ -0,0 +1,111 @@
+// Copyright 2019 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.
+
+#include "snapshot/minidump/exception_snapshot_minidump.h"
+
+#include "snapshot/minidump/minidump_string_reader.h"
+
+namespace crashpad {
+namespace internal {
+
+ExceptionSnapshotMinidump::ExceptionSnapshotMinidump()
+    : ExceptionSnapshot(),
+      minidump_exception_stream_(),
+      context_(),
+      exception_information_(),
+      initialized_() {}
+
+ExceptionSnapshotMinidump::~ExceptionSnapshotMinidump() {}
+
+bool ExceptionSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
+                                           CPUArchitecture arch,
+                                           RVA minidump_exception_stream_rva) {
+  DCHECK(initialized_.is_uninitialized());
+  initialized_.set_invalid();
+
+  std::vector<unsigned char> minidump_context;
+
+  if (!file_reader->SeekSet(minidump_exception_stream_rva)) {
+    return false;
+  }
+
+  if (!file_reader->ReadExactly(&minidump_exception_stream_,
+                                sizeof(minidump_exception_stream_))) {
+    return false;
+  }
+
+  const size_t num_parameters =
+      minidump_exception_stream_.ExceptionRecord.NumberParameters;
+  for (size_t i = 0; i < num_parameters; ++i) {
+    exception_information_.push_back(
+        minidump_exception_stream_.ExceptionRecord.ExceptionInformation[i]);
+  }
+
+  if (!file_reader->SeekSet(minidump_exception_stream_.ThreadContext.Rva)) {
+    return false;
+  }
+
+  minidump_context.resize(minidump_exception_stream_.ThreadContext.DataSize);
+
+  if (!file_reader->ReadExactly(minidump_context.data(),
+                                minidump_context.size())) {
+    return false;
+  }
+
+  if (!context_.Initialize(arch, minidump_context)) {
+    return false;
+  }
+
+  initialized_.set_valid();
+  return true;
+}
+
+const CPUContext* ExceptionSnapshotMinidump::Context() const {
+  DCHECK(initialized_.is_valid());
+  return context_.Get();
+}
+
+uint64_t ExceptionSnapshotMinidump::ThreadID() const {
+  DCHECK(initialized_.is_valid());
+  return minidump_exception_stream_.ThreadId;
+}
+
+uint32_t ExceptionSnapshotMinidump::Exception() const {
+  DCHECK(initialized_.is_valid());
+  return minidump_exception_stream_.ExceptionRecord.ExceptionCode;
+}
+
+uint32_t ExceptionSnapshotMinidump::ExceptionInfo() const {
+  DCHECK(initialized_.is_valid());
+  return minidump_exception_stream_.ExceptionRecord.ExceptionFlags;
+}
+
+uint64_t ExceptionSnapshotMinidump::ExceptionAddress() const {
+  DCHECK(initialized_.is_valid());
+  return minidump_exception_stream_.ExceptionRecord.ExceptionAddress;
+}
+
+const std::vector<uint64_t>& ExceptionSnapshotMinidump::Codes() const {
+  DCHECK(initialized_.is_valid());
+  return exception_information_;
+}
+
+std::vector<const MemorySnapshot*> ExceptionSnapshotMinidump::ExtraMemory()
+    const {
+  DCHECK(initialized_.is_valid());
+  return std::vector<const MemorySnapshot*>();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/minidump/exception_snapshot_minidump.h b/snapshot/minidump/exception_snapshot_minidump.h
new file mode 100644
index 0000000..62f834f
--- /dev/null
+++ b/snapshot/minidump/exception_snapshot_minidump.h
@@ -0,0 +1,76 @@
+// Copyright 2019 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_EXCEPTION_SNAPSHOT_MINIDUMP_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_EXCEPTION_SNAPSHOT_MINIDUMP_H_
+
+#include <windows.h>
+#include <dbghelp.h>
+
+#include "build/build_config.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/exception_snapshot.h"
+#include "snapshot/minidump/minidump_context_converter.h"
+#include "util/file/file_reader.h"
+#include "util/misc/initialization_state.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief An ExceptionSnapshot based on a minidump file.
+class ExceptionSnapshotMinidump final : public ExceptionSnapshot {
+ public:
+  ExceptionSnapshotMinidump();
+  ~ExceptionSnapshotMinidump() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] file_reader A file reader corresponding to a minidump file.
+  //!     The file reader must support seeking.
+  //! \param[in] arch The CPU architecture of this snapshot.
+  //! \param[in] minidump_exception_stream_rva The file offset in \a file_reader
+  //!     at which the MINIDUMP_EXCEPTION_STREAM structure is located.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  bool Initialize(FileReaderInterface* file_reader,
+                  CPUArchitecture arch,
+                  RVA minidump_exception_stream_rva);
+
+  // ExceptionSnapshot:
+  const CPUContext* Context() const override;
+  uint64_t ThreadID() const override;
+  uint32_t Exception() const override;
+  uint32_t ExceptionInfo() const override;
+  uint64_t ExceptionAddress() const override;
+  const std::vector<uint64_t>& Codes() const override;
+  std::vector<const MemorySnapshot*> ExtraMemory() const override;
+
+  // Allow callers to explicitly check whether this exception snapshot has been
+  // initialized.
+  bool IsValid() const { return initialized_.is_valid(); }
+
+ private:
+  MINIDUMP_EXCEPTION_STREAM minidump_exception_stream_;
+  MinidumpContextConverter context_;
+  std::vector<uint64_t> exception_information_;
+  InitializationState initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotMinidump);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_EXCEPTION_SNAPSHOT_MINIDUMP_H_
diff --git a/snapshot/minidump/memory_snapshot_minidump.cc b/snapshot/minidump/memory_snapshot_minidump.cc
new file mode 100644
index 0000000..2c6c899
--- /dev/null
+++ b/snapshot/minidump/memory_snapshot_minidump.cc
@@ -0,0 +1,111 @@
+// 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.
+
+#include "snapshot/minidump/memory_snapshot_minidump.h"
+
+#include <memory>
+
+#include "base/numerics/safe_math.h"
+
+namespace crashpad {
+namespace internal {
+
+MemorySnapshotMinidump::MemorySnapshotMinidump()
+    : MemorySnapshot(),
+      address_(0),
+      data_(),
+      initialized_() {}
+
+MemorySnapshotMinidump::~MemorySnapshotMinidump() {}
+
+bool MemorySnapshotMinidump::Initialize(FileReaderInterface* file_reader,
+                                        RVA location) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  MINIDUMP_MEMORY_DESCRIPTOR descriptor;
+
+  if (!file_reader->SeekSet(location)) {
+    return false;
+  }
+
+  if (!file_reader->ReadExactly(&descriptor, sizeof(descriptor))) {
+    return false;
+  }
+
+  address_ = descriptor.StartOfMemoryRange;
+  data_.resize(descriptor.Memory.DataSize);
+
+  if (!file_reader->SeekSet(descriptor.Memory.Rva)) {
+    return false;
+  }
+
+  if (!file_reader->ReadExactly(data_.data(), data_.size())) {
+    return false;
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+uint64_t MemorySnapshotMinidump::Address() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return address_;
+}
+
+size_t MemorySnapshotMinidump::Size() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return data_.size();
+}
+
+bool MemorySnapshotMinidump::Read(Delegate* delegate) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return delegate->MemorySnapshotDelegateRead(
+      const_cast<uint8_t*>(data_.data()), data_.size());
+}
+
+const MemorySnapshot* MemorySnapshotMinidump::MergeWithOtherSnapshot(
+    const MemorySnapshot* other) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  // TODO: Verify type of other
+  auto other_cast = reinterpret_cast<const MemorySnapshotMinidump*>(other);
+
+  INITIALIZATION_STATE_DCHECK_VALID(other_cast->initialized_);
+
+  if (other_cast->address_ < address_) {
+    return other_cast->MergeWithOtherSnapshot(this);
+  }
+
+  CheckedRange<uint64_t, size_t> merged(0, 0);
+  if (!LoggingDetermineMergedRange(this, other, &merged)) {
+    return nullptr;
+  }
+
+  auto result = std::make_unique<MemorySnapshotMinidump>();
+  result->address_ = merged.base();
+  result->data_ = data_;
+
+  if (result->data_.size() == merged.size()) {
+    return result.release();
+  }
+
+  result->data_.resize(
+      base::checked_cast<size_t>(other_cast->address_ - address_));
+  result->data_.insert(result->data_.end(), other_cast->data_.begin(),
+                       other_cast->data_.end());
+  return result.release();
+}
+
+} // namespace internal
+} // namespace crashpad
diff --git a/snapshot/minidump/memory_snapshot_minidump.h b/snapshot/minidump/memory_snapshot_minidump.h
new file mode 100644
index 0000000..69521be
--- /dev/null
+++ b/snapshot/minidump/memory_snapshot_minidump.h
@@ -0,0 +1,63 @@
+// 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_MEMORY_SNAPSHOT_MINIDUMP_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_MEMORY_SNAPSHOT_MINIDUMP_H_
+
+#include <windows.h>
+#include <dbghelp.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "snapshot/memory_snapshot.h"
+#include "util/file/file_reader.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+class MemorySnapshotMinidump : public MemorySnapshot {
+ public:
+  MemorySnapshotMinidump();
+  ~MemorySnapshotMinidump() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] file_reader A file reader corresponding to a minidump file.
+  //!     The file reader must support seeking.
+  //! \param[in] location The location within the file where we will find a
+  //!     MINIDUMP_MEMORY_DESCRIPTOR from which to initialize this object.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  bool Initialize(FileReaderInterface* file_reader, RVA location);
+
+  uint64_t Address() const override;
+  size_t Size() const override;
+  bool Read(Delegate* delegate) const override;
+  const MemorySnapshot* MergeWithOtherSnapshot(
+      const MemorySnapshot* other) const override;
+
+ private:
+  uint64_t address_;
+  std::vector<uint8_t> data_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemorySnapshotMinidump);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_MEMORY_SNAPSHOT_MINIDUMP_H_
diff --git a/snapshot/minidump/minidump_context_converter.cc b/snapshot/minidump/minidump_context_converter.cc
new file mode 100644
index 0000000..0c840de
--- /dev/null
+++ b/snapshot/minidump/minidump_context_converter.cc
@@ -0,0 +1,275 @@
+// Copyright 2019 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.
+
+#include "snapshot/minidump/minidump_context_converter.h"
+
+#include "base/stl_util.h"
+#include "minidump/minidump_context.h"
+
+namespace crashpad {
+namespace internal {
+
+MinidumpContextConverter::MinidumpContextConverter() : initialized_() {
+  context_.architecture = CPUArchitecture::kCPUArchitectureUnknown;
+}
+
+bool MinidumpContextConverter::Initialize(
+    CPUArchitecture arch,
+    const std::vector<unsigned char>& minidump_context) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  if (minidump_context.size() == 0) {
+    // Thread has no context.
+    context_.architecture = CPUArchitecture::kCPUArchitectureUnknown;
+    INITIALIZATION_STATE_SET_VALID(initialized_);
+    return true;
+  }
+
+  context_.architecture = arch;
+
+  if (context_.architecture == CPUArchitecture::kCPUArchitectureX86) {
+    context_memory_.resize(sizeof(CPUContextX86));
+    context_.x86 = reinterpret_cast<CPUContextX86*>(context_memory_.data());
+    const MinidumpContextX86* src =
+        reinterpret_cast<const MinidumpContextX86*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextX86)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextX86)) {
+      return false;
+    }
+
+    if (src->context_flags & kMinidumpContextX86Extended) {
+      context_.x86->fxsave = src->fxsave;
+    } else if (src->context_flags & kMinidumpContextX86FloatingPoint) {
+      CPUContextX86::FsaveToFxsave(src->fsave, &context_.x86->fxsave);
+    }
+
+    context_.x86->eax = src->eax;
+    context_.x86->ebx = src->ebx;
+    context_.x86->ecx = src->ecx;
+    context_.x86->edx = src->edx;
+    context_.x86->edi = src->edi;
+    context_.x86->esi = src->esi;
+    context_.x86->ebp = src->ebp;
+    context_.x86->esp = src->esp;
+    context_.x86->eip = src->eip;
+    context_.x86->eflags = src->eflags;
+    context_.x86->cs = static_cast<uint16_t>(src->cs);
+    context_.x86->ds = static_cast<uint16_t>(src->ds);
+    context_.x86->es = static_cast<uint16_t>(src->es);
+    context_.x86->fs = static_cast<uint16_t>(src->fs);
+    context_.x86->gs = static_cast<uint16_t>(src->gs);
+    context_.x86->ss = static_cast<uint16_t>(src->ss);
+    context_.x86->dr0 = src->dr0;
+    context_.x86->dr1 = src->dr1;
+    context_.x86->dr2 = src->dr2;
+    context_.x86->dr3 = src->dr3;
+    context_.x86->dr6 = src->dr6;
+    context_.x86->dr7 = src->dr7;
+
+    // Minidump passes no value for dr4/5. Our output context has space for
+    // them. According to spec they're obsolete, but when present read as
+    // aliases for dr6/7, so we'll do this.
+    context_.x86->dr4 = src->dr6;
+    context_.x86->dr5 = src->dr7;
+  } else if (context_.architecture == CPUArchitecture::kCPUArchitectureX86_64) {
+    context_memory_.resize(sizeof(CPUContextX86_64));
+    context_.x86_64 =
+        reinterpret_cast<CPUContextX86_64*>(context_memory_.data());
+    const MinidumpContextAMD64* src =
+        reinterpret_cast<const MinidumpContextAMD64*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextAMD64)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextAMD64)) {
+      return false;
+    }
+
+    context_.x86_64->fxsave = src->fxsave;
+    context_.x86_64->cs = src->cs;
+    context_.x86_64->fs = src->fs;
+    context_.x86_64->gs = src->gs;
+    context_.x86_64->rflags = src->eflags;
+    context_.x86_64->dr0 = src->dr0;
+    context_.x86_64->dr1 = src->dr1;
+    context_.x86_64->dr2 = src->dr2;
+    context_.x86_64->dr3 = src->dr3;
+    context_.x86_64->dr6 = src->dr6;
+    context_.x86_64->dr7 = src->dr7;
+    context_.x86_64->rax = src->rax;
+    context_.x86_64->rcx = src->rcx;
+    context_.x86_64->rdx = src->rdx;
+    context_.x86_64->rbx = src->rbx;
+    context_.x86_64->rsp = src->rsp;
+    context_.x86_64->rbp = src->rbp;
+    context_.x86_64->rsi = src->rsi;
+    context_.x86_64->rdi = src->rdi;
+    context_.x86_64->r8 = src->r8;
+    context_.x86_64->r9 = src->r9;
+    context_.x86_64->r10 = src->r10;
+    context_.x86_64->r11 = src->r11;
+    context_.x86_64->r12 = src->r12;
+    context_.x86_64->r13 = src->r13;
+    context_.x86_64->r14 = src->r14;
+    context_.x86_64->r15 = src->r15;
+    context_.x86_64->rip = src->rip;
+
+    // See comments on x86 above.
+    context_.x86_64->dr4 = src->dr6;
+    context_.x86_64->dr5 = src->dr7;
+  } else if (context_.architecture == CPUArchitecture::kCPUArchitectureARM) {
+    context_memory_.resize(sizeof(CPUContextARM));
+    context_.arm = reinterpret_cast<CPUContextARM*>(context_memory_.data());
+    const MinidumpContextARM* src =
+        reinterpret_cast<const MinidumpContextARM*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextARM)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextARM)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < base::size(src->regs); i++) {
+      context_.arm->regs[i] = src->regs[i];
+    }
+
+    context_.arm->fp = src->fp;
+    context_.arm->ip = src->ip;
+    context_.arm->sp = src->sp;
+    context_.arm->lr = src->lr;
+    context_.arm->pc = src->pc;
+    context_.arm->cpsr = src->cpsr;
+    context_.arm->vfp_regs.fpscr = src->fpscr;
+
+    for (size_t i = 0; i < base::size(src->vfp); i++) {
+      context_.arm->vfp_regs.vfp[i] = src->vfp[i];
+    }
+
+    context_.arm->have_fpa_regs = false;
+    context_.arm->have_vfp_regs =
+        !!(src->context_flags & kMinidumpContextARMVFP);
+  } else if (context_.architecture == CPUArchitecture::kCPUArchitectureARM64) {
+    context_memory_.resize(sizeof(CPUContextARM64));
+    context_.arm64 = reinterpret_cast<CPUContextARM64*>(context_memory_.data());
+    const MinidumpContextARM64* src =
+        reinterpret_cast<const MinidumpContextARM64*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextARM64)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextARM64)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < base::size(src->regs); i++) {
+      context_.arm64->regs[i] = src->regs[i];
+    }
+
+    context_.arm64->regs[29] = src->fp;
+    context_.arm64->regs[30] = src->lr;
+
+    for (size_t i = 0; i < base::size(src->fpsimd); i++) {
+      context_.arm64->fpsimd[i] = src->fpsimd[i];
+    }
+
+    context_.arm64->sp = src->sp;
+    context_.arm64->pc = src->pc;
+    context_.arm64->fpcr = src->fpcr;
+    context_.arm64->fpsr = src->fpsr;
+    context_.arm64->spsr = src->cpsr;
+  } else if (context_.architecture == CPUArchitecture::kCPUArchitectureMIPSEL) {
+    context_memory_.resize(sizeof(CPUContextMIPS));
+    context_.mipsel = reinterpret_cast<CPUContextMIPS*>(context_memory_.data());
+    const MinidumpContextMIPS* src =
+        reinterpret_cast<const MinidumpContextMIPS*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextMIPS)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextMIPS)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < base::size(src->regs); i++) {
+      context_.mipsel->regs[i] = src->regs[i];
+    }
+
+    context_.mipsel->mdhi = static_cast<uint32_t>(src->mdhi);
+    context_.mipsel->mdlo = static_cast<uint32_t>(src->mdlo);
+    context_.mipsel->dsp_control = src->dsp_control;
+
+    for (size_t i = 0; i < base::size(src->hi); i++) {
+      context_.mipsel->hi[i] = src->hi[i];
+      context_.mipsel->lo[i] = src->lo[i];
+    }
+
+    context_.mipsel->cp0_epc = static_cast<uint32_t>(src->epc);
+    context_.mipsel->cp0_badvaddr = static_cast<uint32_t>(src->badvaddr);
+    context_.mipsel->cp0_status = src->status;
+    context_.mipsel->cp0_cause = src->cause;
+    context_.mipsel->fpcsr = src->fpcsr;
+    context_.mipsel->fir = src->fir;
+
+    memcpy(&context_.mipsel->fpregs, &src->fpregs, sizeof(src->fpregs));
+  } else if (context_.architecture ==
+             CPUArchitecture::kCPUArchitectureMIPS64EL) {
+    context_memory_.resize(sizeof(CPUContextMIPS64));
+    context_.mips64 =
+        reinterpret_cast<CPUContextMIPS64*>(context_memory_.data());
+    const MinidumpContextMIPS64* src =
+        reinterpret_cast<const MinidumpContextMIPS64*>(minidump_context.data());
+    if (minidump_context.size() < sizeof(MinidumpContextMIPS64)) {
+      return false;
+    }
+
+    if (!(src->context_flags & kMinidumpContextMIPS64)) {
+      return false;
+    }
+
+    for (size_t i = 0; i < base::size(src->regs); i++) {
+      context_.mips64->regs[i] = src->regs[i];
+    }
+
+    context_.mips64->mdhi = src->mdhi;
+    context_.mips64->mdlo = src->mdlo;
+    context_.mips64->dsp_control = src->dsp_control;
+
+    for (size_t i = 0; i < base::size(src->hi); i++) {
+      context_.mips64->hi[i] = src->hi[i];
+      context_.mips64->lo[i] = src->lo[i];
+    }
+
+    context_.mips64->cp0_epc = src->epc;
+    context_.mips64->cp0_badvaddr = src->badvaddr;
+    context_.mips64->cp0_status = src->status;
+    context_.mips64->cp0_cause = src->cause;
+    context_.mips64->fpcsr = src->fpcsr;
+    context_.mips64->fir = src->fir;
+
+    memcpy(&context_.mips64->fpregs, &src->fpregs, sizeof(src->fpregs));
+  } else {
+    // Architecture is listed as "unknown".
+    DLOG(ERROR) << "Unknown architecture";
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/minidump/minidump_context_converter.h b/snapshot/minidump/minidump_context_converter.h
new file mode 100644
index 0000000..48e36c1
--- /dev/null
+++ b/snapshot/minidump/minidump_context_converter.h
@@ -0,0 +1,47 @@
+// Copyright 2019 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_CONTEXT_CONVERTER_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_CONTEXT_CONVERTER_H_
+
+#include <vector>
+
+#include "snapshot/cpu_context.h"
+#include "util/misc/initialization_state.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+class MinidumpContextConverter {
+ public:
+  MinidumpContextConverter();
+
+  bool Initialize(CPUArchitecture arch,
+                  const std::vector<unsigned char>& minidump_context);
+  const CPUContext* Get() const {
+    INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+    return &context_;
+  }
+
+ private:
+  CPUContext context_;
+  std::vector<unsigned char> context_memory_;
+  InitializationStateDcheck initialized_;
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_MINIDUMP_CONTEXT_CONVERTER_H_
diff --git a/snapshot/minidump/minidump_stream.h b/snapshot/minidump/minidump_stream.h
new file mode 100644
index 0000000..2b0ac2e
--- /dev/null
+++ b/snapshot/minidump/minidump_stream.h
@@ -0,0 +1,44 @@
+// 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>
+
+#include "base/macros.h"
+
+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/minidump_string_reader.cc b/snapshot/minidump/minidump_string_reader.cc
index 4a5cb13..d642e9d 100644
--- a/snapshot/minidump/minidump_string_reader.cc
+++ b/snapshot/minidump/minidump_string_reader.cc
@@ -17,14 +17,18 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
 #include "minidump/minidump_extensions.h"
 
 namespace crashpad {
 namespace internal {
 
-bool ReadMinidumpUTF8String(FileReaderInterface* file_reader,
+namespace {
+
+template<typename StringType>
+bool ReadMinidumpString(FileReaderInterface* file_reader,
                             RVA rva,
-                            std::string* string) {
+                            StringType* string) {
   if (rva == 0) {
     string->clear();
     return true;
@@ -39,7 +43,7 @@
     return false;
   }
 
-  std::string local_string(string_size, '\0');
+  StringType local_string(string_size / sizeof((*string)[0]), '\0');
   if (!file_reader->ReadExactly(&local_string[0], string_size)) {
     return false;
   }
@@ -48,5 +52,33 @@
   return true;
 }
 
+}  // namespace
+
+bool ReadMinidumpUTF8String(FileReaderInterface* file_reader,
+                            RVA rva,
+                            std::string* string) {
+  return ReadMinidumpString(file_reader, rva, string);
+}
+
+bool ReadMinidumpUTF16String(FileReaderInterface* file_reader,
+                            RVA rva,
+                            base::string16* string) {
+  return ReadMinidumpString(file_reader, rva, string);
+}
+
+bool ReadMinidumpUTF16String(FileReaderInterface* file_reader,
+                            RVA rva,
+                            std::string* string) {
+  base::string16 string_raw;
+
+  if (!ReadMinidumpString(file_reader, rva, &string_raw)) {
+    return false;
+  }
+
+  base::UTF16ToUTF8(string_raw.data(), string_raw.size(), string);
+
+  return true;
+}
+
 }  // namespace internal
 }  // namespace crashpad
diff --git a/snapshot/minidump/minidump_string_reader.h b/snapshot/minidump/minidump_string_reader.h
index e5667ec..b7ecdac 100644
--- a/snapshot/minidump/minidump_string_reader.h
+++ b/snapshot/minidump/minidump_string_reader.h
@@ -20,6 +20,7 @@
 
 #include <string>
 
+#include "base/strings/string16.h"
 #include "util/file/file_reader.h"
 
 namespace crashpad {
@@ -34,6 +35,24 @@
                             RVA rva,
                             std::string* string);
 
+//! \brief Reads a MinidumpUTF16String from a minidump file at offset \a rva in
+//!     \a file_reader, and returns it in \a string.
+//!
+//! \return `true` on success, with \a string set. `false` on failure, with a
+//!     message logged.
+bool ReadMinidumpUTF16String(FileReaderInterface* file_reader,
+                             RVA rva,
+                             base::string16* string);
+
+//! \brief Reads a MinidumpUTF16String from a minidump file at offset \a rva in
+//!     \a file_reader, and returns it in \a string.
+//!
+//! \return `true` on success, with \a string set. `false` on failure, with a
+//!     message logged.
+bool ReadMinidumpUTF16String(FileReaderInterface* file_reader,
+                             RVA rva,
+                             std::string* string);
+
 }  // namespace internal
 }  // namespace crashpad
 
diff --git a/snapshot/minidump/module_snapshot_minidump.cc b/snapshot/minidump/module_snapshot_minidump.cc
index 06cd1bb..a316e83 100644
--- a/snapshot/minidump/module_snapshot_minidump.cc
+++ b/snapshot/minidump/module_snapshot_minidump.cc
@@ -14,10 +14,16 @@
 
 #include "snapshot/minidump/module_snapshot_minidump.h"
 
+#include <stddef.h>
+#include <string.h>
+
+#include "base/logging.h"
 #include "minidump/minidump_extensions.h"
 #include "snapshot/minidump/minidump_annotation_reader.h"
 #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h"
 #include "snapshot/minidump/minidump_string_list_reader.h"
+#include "snapshot/minidump/minidump_string_reader.h"
+#include "util/misc/pdb_structures.h"
 
 namespace crashpad {
 namespace internal {
@@ -27,11 +33,15 @@
       minidump_module_(),
       annotations_vector_(),
       annotations_simple_map_(),
-      initialized_() {
-}
+      annotation_objects_(),
+      uuid_(),
+      build_id_(),
+      name_(),
+      debug_file_name_(),
+      age_(0),
+      initialized_() {}
 
-ModuleSnapshotMinidump::~ModuleSnapshotMinidump() {
-}
+ModuleSnapshotMinidump::~ModuleSnapshotMinidump() {}
 
 bool ModuleSnapshotMinidump::Initialize(
     FileReaderInterface* file_reader,
@@ -53,32 +63,87 @@
     return false;
   }
 
+  ReadMinidumpUTF16String(file_reader, minidump_module_.ModuleNameRva, &name_);
+
+  if (minidump_module_.CvRecord.Rva != 0 &&
+      !InitializeModuleCodeView(file_reader)) {
+    return false;
+  }
+
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
 
+bool ModuleSnapshotMinidump::InitializeModuleCodeView(
+    FileReaderInterface* file_reader) {
+  uint32_t signature;
+
+  DCHECK_NE(minidump_module_.CvRecord.Rva, 0u);
+
+  if (minidump_module_.CvRecord.DataSize < sizeof(signature)) {
+    LOG(ERROR) << "CodeView record in module too small to contain signature";
+    return false;
+  }
+
+  if (!file_reader->SeekSet(minidump_module_.CvRecord.Rva)) {
+    return false;
+  }
+
+  std::vector<uint8_t> cv_record;
+  cv_record.resize(minidump_module_.CvRecord.DataSize);
+
+  if (!file_reader->ReadExactly(cv_record.data(), cv_record.size())) {
+    return false;
+  }
+
+  signature = *reinterpret_cast<uint32_t*>(cv_record.data());
+
+  if (signature == CodeViewRecordPDB70::kSignature) {
+    if (cv_record.size() < offsetof(CodeViewRecordPDB70, pdb_name)) {
+      LOG(ERROR) << "CodeView record in module marked as PDB70 but too small";
+      return false;
+    }
+
+    auto cv_record_pdb70 =
+        reinterpret_cast<CodeViewRecordPDB70*>(cv_record.data());
+
+    age_ = cv_record_pdb70->age;
+    uuid_ = cv_record_pdb70->uuid;
+
+    std::copy(cv_record.begin() + offsetof(CodeViewRecordPDB70, pdb_name),
+              cv_record.end(),
+              std::back_inserter(debug_file_name_));
+    return true;
+  }
+
+  if (signature == CodeViewRecordBuildID::kSignature) {
+    std::copy(cv_record.begin() + offsetof(CodeViewRecordBuildID, build_id),
+              cv_record.end(),
+              std::back_inserter(build_id_));
+    return true;
+  }
+
+  LOG(ERROR) << "Bad CodeView signature in module";
+  return false;
+}
+
 std::string ModuleSnapshotMinidump::Name() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return std::string();
+  return name_;
 }
 
 uint64_t ModuleSnapshotMinidump::Address() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return 0;
+  return minidump_module_.BaseOfImage;
 }
 
 uint64_t ModuleSnapshotMinidump::Size() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return 0;
+  return minidump_module_.SizeOfImage;
 }
 
 time_t ModuleSnapshotMinidump::Timestamp() const {
-  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return 0;
+  return minidump_module_.TimeDateStamp;
 }
 
 void ModuleSnapshotMinidump::FileVersion(uint16_t* version_0,
@@ -86,11 +151,12 @@
                                          uint16_t* version_2,
                                          uint16_t* version_3) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  *version_0 = 0;
-  *version_1 = 0;
-  *version_2 = 0;
-  *version_3 = 0;
+  uint32_t version_01 = minidump_module_.VersionInfo.dwFileVersionMS;
+  uint32_t version_23 = minidump_module_.VersionInfo.dwFileVersionLS;
+  *version_0 = static_cast<uint16_t>(version_01 >> 16);
+  *version_1 = static_cast<uint16_t>(version_01 & 0xFFFF);
+  *version_2 = static_cast<uint16_t>(version_23 >> 16);
+  *version_3 = static_cast<uint16_t>(version_23 & 0xFFFF);
 }
 
 void ModuleSnapshotMinidump::SourceVersion(uint16_t* version_0,
@@ -98,31 +164,40 @@
                                            uint16_t* version_2,
                                            uint16_t* version_3) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  *version_0 = 0;
-  *version_1 = 0;
-  *version_2 = 0;
-  *version_3 = 0;
+  uint32_t version_01 = minidump_module_.VersionInfo.dwProductVersionMS;
+  uint32_t version_23 = minidump_module_.VersionInfo.dwProductVersionLS;
+  *version_0 = static_cast<uint16_t>(version_01 >> 16);
+  *version_1 = static_cast<uint16_t>(version_01 & 0xFFFF);
+  *version_2 = static_cast<uint16_t>(version_23 >> 16);
+  *version_3 = static_cast<uint16_t>(version_23 & 0xFFFF);
 }
 
 ModuleSnapshot::ModuleType ModuleSnapshotMinidump::GetModuleType() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  switch (minidump_module_.VersionInfo.dwFileType) {
+    case VFT_APP:
+      return kModuleTypeExecutable;
+    case VFT_DLL:
+      return kModuleTypeSharedLibrary;
+  }
   return kModuleTypeUnknown;
 }
 
 void ModuleSnapshotMinidump::UUIDAndAge(crashpad::UUID* uuid,
                                         uint32_t* age) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  *uuid = crashpad::UUID();
-  *age = 0;
+  *uuid = uuid_;
+  *age = age_;
 }
 
 std::string ModuleSnapshotMinidump::DebugFileName() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return std::string();
+  return debug_file_name_;
+}
+
+std::vector<uint8_t> ModuleSnapshotMinidump::BuildID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return build_id_;
 }
 
 std::vector<std::string> ModuleSnapshotMinidump::AnnotationsVector() const {
@@ -167,7 +242,7 @@
 
   MinidumpModuleCrashpadInfo minidump_module_crashpad_info;
   if (minidump_module_crashpad_info_location->DataSize <
-          sizeof(minidump_module_crashpad_info)) {
+      sizeof(minidump_module_crashpad_info)) {
     LOG(ERROR) << "minidump_module_crashpad_info size mismatch";
     return false;
   }
@@ -182,7 +257,7 @@
   }
 
   if (minidump_module_crashpad_info.version !=
-          MinidumpModuleCrashpadInfo::kVersion) {
+      MinidumpModuleCrashpadInfo::kVersion) {
     LOG(ERROR) << "minidump_module_crashpad_info version mismatch";
     return false;
   }
diff --git a/snapshot/minidump/module_snapshot_minidump.h b/snapshot/minidump/module_snapshot_minidump.h
index e9dfa77..023d0d5 100644
--- a/snapshot/minidump/module_snapshot_minidump.h
+++ b/snapshot/minidump/module_snapshot_minidump.h
@@ -75,6 +75,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
@@ -88,10 +89,19 @@
                                     const MINIDUMP_LOCATION_DESCRIPTOR*
                                         minidump_module_crashpad_info_location);
 
+  // Initializes data from the CodeView record, which usually points toward
+  // debug symbols.
+  bool InitializeModuleCodeView(FileReaderInterface* file_reader);
+
   MINIDUMP_MODULE minidump_module_;
   std::vector<std::string> annotations_vector_;
   std::map<std::string, std::string> annotations_simple_map_;
   std::vector<AnnotationSnapshot> annotation_objects_;
+  UUID uuid_;
+  std::vector<uint8_t> build_id_;
+  std::string name_;
+  std::string debug_file_name_;
+  uint32_t age_;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotMinidump);
diff --git a/snapshot/minidump/process_snapshot_minidump.cc b/snapshot/minidump/process_snapshot_minidump.cc
index 652e0e4..6a032f9 100644
--- a/snapshot/minidump/process_snapshot_minidump.cc
+++ b/snapshot/minidump/process_snapshot_minidump.cc
@@ -16,27 +16,55 @@
 
 #include <utility>
 
+#include "base/strings/utf_string_conversions.h"
+#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"
 
 namespace crashpad {
 
+namespace internal {
+
+class MemoryMapRegionSnapshotMinidump : public MemoryMapRegionSnapshot {
+ public:
+  MemoryMapRegionSnapshotMinidump(MINIDUMP_MEMORY_INFO info) : info_(info) {}
+  ~MemoryMapRegionSnapshotMinidump() override = default;
+
+  const MINIDUMP_MEMORY_INFO& AsMinidumpMemoryInfo() const override {
+    return info_;
+  }
+
+ private:
+  MINIDUMP_MEMORY_INFO info_;
+};
+
+}  // namespace internal
+
 ProcessSnapshotMinidump::ProcessSnapshotMinidump()
     : ProcessSnapshot(),
       header_(),
       stream_directory_(),
       stream_map_(),
       modules_(),
+      threads_(),
       unloaded_modules_(),
+      mem_regions_(),
+      mem_regions_exposed_(),
+      custom_streams_(),
       crashpad_info_(),
+      system_snapshot_(),
+      exception_snapshot_(),
+      arch_(CPUArchitecture::kCPUArchitectureUnknown),
       annotations_simple_map_(),
       file_reader_(nullptr),
-      process_id_(static_cast<pid_t>(-1)),
-      initialized_() {
-}
+      process_id_(kInvalidProcessID),
+      create_time_(0),
+      user_time_(0),
+      kernel_time_(0),
+      initialized_() {}
 
-ProcessSnapshotMinidump::~ProcessSnapshotMinidump() {
-}
+ProcessSnapshotMinidump::~ProcessSnapshotMinidump() {}
 
 bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
@@ -84,9 +112,10 @@
     stream_map_[stream_type] = &directory.Location;
   }
 
-  if (!InitializeCrashpadInfo() ||
-      !InitializeMiscInfo() ||
-      !InitializeModules()) {
+  if (!InitializeCrashpadInfo() || !InitializeMiscInfo() ||
+      !InitializeModules() || !InitializeSystemSnapshot() ||
+      !InitializeMemoryInfo() || !InitializeThreads() ||
+      !InitializeCustomMinidumpStreams() || !InitializeExceptionSnapshot()) {
     return false;
   }
 
@@ -94,12 +123,12 @@
   return true;
 }
 
-pid_t ProcessSnapshotMinidump::ProcessID() const {
+crashpad::ProcessID ProcessSnapshotMinidump::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_id_;
 }
 
-pid_t ProcessSnapshotMinidump::ParentProcessID() const {
+crashpad::ProcessID ProcessSnapshotMinidump::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   NOTREACHED();  // https://crashpad.chromium.org/bug/10
   return 0;
@@ -107,25 +136,22 @@
 
 void ProcessSnapshotMinidump::SnapshotTime(timeval* snapshot_time) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  snapshot_time->tv_sec = 0;
+  snapshot_time->tv_sec = header_.TimeDateStamp;
   snapshot_time->tv_usec = 0;
 }
 
 void ProcessSnapshotMinidump::ProcessStartTime(timeval* start_time) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  start_time->tv_sec = 0;
+  start_time->tv_sec = create_time_;
   start_time->tv_usec = 0;
 }
 
 void ProcessSnapshotMinidump::ProcessCPUTimes(timeval* user_time,
                                               timeval* system_time) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  user_time->tv_sec = 0;
+  user_time->tv_sec = user_time_;
   user_time->tv_usec = 0;
-  system_time->tv_sec = 0;
+  system_time->tv_sec = kernel_time_;
   system_time->tv_usec = 0;
 }
 
@@ -153,14 +179,16 @@
 
 const SystemSnapshot* ProcessSnapshotMinidump::System() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return nullptr;
+  return &system_snapshot_;
 }
 
 std::vector<const ThreadSnapshot*> ProcessSnapshotMinidump::Threads() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return std::vector<const ThreadSnapshot*>();
+  std::vector<const ThreadSnapshot*> threads;
+  for (const auto& thread : threads_) {
+    threads.push_back(thread.get());
+  }
+  return threads;
 }
 
 std::vector<const ModuleSnapshot*> ProcessSnapshotMinidump::Modules() const {
@@ -181,15 +209,17 @@
 
 const ExceptionSnapshot* ProcessSnapshotMinidump::Exception() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  if (exception_snapshot_.IsValid()) {
+    return &exception_snapshot_;
+  }
+  // Allow caller to know whether the minidump contained an exception stream.
   return nullptr;
 }
 
 std::vector<const MemoryMapRegionSnapshot*> ProcessSnapshotMinidump::MemoryMap()
     const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  NOTREACHED();  // https://crashpad.chromium.org/bug/10
-  return std::vector<const MemoryMapRegionSnapshot*>();
+  return mem_regions_exposed_;
 }
 
 std::vector<HandleSnapshot> ProcessSnapshotMinidump::Handles() const {
@@ -205,6 +235,23 @@
   return std::vector<const MemorySnapshot*>();
 }
 
+const ProcessMemory* ProcessSnapshotMinidump::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  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()) {
@@ -263,12 +310,18 @@
   switch (stream_it->second->DataSize) {
     case sizeof(MINIDUMP_MISC_INFO_5):
     case sizeof(MINIDUMP_MISC_INFO_4):
+      full_version_ = base::UTF16ToUTF8(info.BuildString);
+      full_version_ = full_version_.substr(0, full_version_.find(";"));
+      FALLTHROUGH;
     case sizeof(MINIDUMP_MISC_INFO_3):
     case sizeof(MINIDUMP_MISC_INFO_2):
     case sizeof(MINIDUMP_MISC_INFO):
       // TODO(jperaza): Save the remaining misc info.
       // https://crashpad.chromium.org/bug/10
       process_id_ = info.ProcessId;
+      create_time_ = info.ProcessCreateTime;
+      user_time_ = info.ProcessUserTime;
+      kernel_time_ = info.ProcessKernelTime;
   }
 
   return true;
@@ -300,7 +353,7 @@
   }
 
   if (sizeof(MINIDUMP_MODULE_LIST) + module_count * sizeof(MINIDUMP_MODULE) !=
-          stream_it->second->DataSize) {
+      stream_it->second->DataSize) {
     LOG(ERROR) << "module_list size mismatch";
     return false;
   }
@@ -342,7 +395,7 @@
   }
 
   if (crashpad_info_.module_list.DataSize <
-          sizeof(MinidumpModuleCrashpadInfoList)) {
+      sizeof(MinidumpModuleCrashpadInfoList)) {
     LOG(ERROR) << "module_crashpad_info_list size mismatch";
     return false;
   }
@@ -358,8 +411,8 @@
   }
 
   if (crashpad_info_.module_list.DataSize !=
-          sizeof(MinidumpModuleCrashpadInfoList) +
-              crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink)) {
+      sizeof(MinidumpModuleCrashpadInfoList) +
+          crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink)) {
     LOG(ERROR) << "module_crashpad_info_list size mismatch";
     return false;
   }
@@ -379,7 +432,8 @@
         minidump_links[crashpad_module_index];
     if (!module_crashpad_info_links
              ->insert(std::make_pair(minidump_link.minidump_module_list_index,
-                                     minidump_link.location)).second) {
+                                     minidump_link.location))
+             .second) {
       LOG(WARNING)
           << "duplicate module_crashpad_info_list minidump_module_list_index "
           << minidump_link.minidump_module_list_index;
@@ -390,4 +444,164 @@
   return true;
 }
 
+bool ProcessSnapshotMinidump::InitializeMemoryInfo() {
+  const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMemoryInfoList);
+  if (stream_it == stream_map_.end()) {
+    return true;
+  }
+
+  if (stream_it->second->DataSize < sizeof(MINIDUMP_MEMORY_INFO_LIST)) {
+    LOG(ERROR) << "memory_info_list size mismatch";
+    return false;
+  }
+
+  if (!file_reader_->SeekSet(stream_it->second->Rva)) {
+    return false;
+  }
+
+  MINIDUMP_MEMORY_INFO_LIST list;
+
+  if (!file_reader_->ReadExactly(&list, sizeof(list))) {
+    return false;
+  }
+
+  if (list.SizeOfHeader != sizeof(list)) {
+    return false;
+  }
+
+  if (list.SizeOfEntry != sizeof(MINIDUMP_MEMORY_INFO)) {
+    return false;
+  }
+
+  if (sizeof(MINIDUMP_MEMORY_INFO_LIST) +
+          list.NumberOfEntries * list.SizeOfEntry !=
+      stream_it->second->DataSize) {
+    LOG(ERROR) << "memory_info_list size mismatch";
+    return false;
+  }
+
+  for (uint32_t i = 0; i < list.NumberOfEntries; i++) {
+    MINIDUMP_MEMORY_INFO info;
+
+    if (!file_reader_->ReadExactly(&info, sizeof(info))) {
+      return false;
+    }
+
+    mem_regions_.emplace_back(
+        std::make_unique<internal::MemoryMapRegionSnapshotMinidump>(info));
+    mem_regions_exposed_.emplace_back(mem_regions_.back().get());
+  }
+
+  return true;
+}
+
+bool ProcessSnapshotMinidump::InitializeThreads() {
+  const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadList);
+  if (stream_it == stream_map_.end()) {
+    return true;
+  }
+
+  if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_LIST)) {
+    LOG(ERROR) << "thread_list size mismatch";
+    return false;
+  }
+
+  if (!file_reader_->SeekSet(stream_it->second->Rva)) {
+    return false;
+  }
+
+  uint32_t thread_count;
+  if (!file_reader_->ReadExactly(&thread_count, sizeof(thread_count))) {
+    return false;
+  }
+
+  if (sizeof(MINIDUMP_THREAD_LIST) + thread_count * sizeof(MINIDUMP_THREAD) !=
+      stream_it->second->DataSize) {
+    LOG(ERROR) << "thread_list size mismatch";
+    return false;
+  }
+
+  for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
+    const RVA thread_rva = stream_it->second->Rva + sizeof(thread_count) +
+                           thread_index * sizeof(MINIDUMP_THREAD);
+
+    auto thread = std::make_unique<internal::ThreadSnapshotMinidump>();
+    if (!thread->Initialize(file_reader_, thread_rva, arch_)) {
+      return false;
+    }
+
+    threads_.push_back(std::move(thread));
+  }
+
+  return true;
+}
+
+bool ProcessSnapshotMinidump::InitializeSystemSnapshot() {
+  const auto& stream_it = stream_map_.find(kMinidumpStreamTypeSystemInfo);
+  if (stream_it == stream_map_.end()) {
+    return true;
+  }
+
+  if (stream_it->second->DataSize < sizeof(MINIDUMP_SYSTEM_INFO)) {
+    LOG(ERROR) << "system info size mismatch";
+    return false;
+  }
+
+  if (!system_snapshot_.Initialize(
+          file_reader_, stream_it->second->Rva, full_version_)) {
+    return false;
+  }
+
+  arch_ = system_snapshot_.GetCPUArchitecture();
+  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;
+}
+
+bool ProcessSnapshotMinidump::InitializeExceptionSnapshot() {
+  const auto& stream_it = stream_map_.find(kMinidumpStreamTypeException);
+  if (stream_it == stream_map_.end()) {
+    return true;
+  }
+
+  if (stream_it->second->DataSize < sizeof(MINIDUMP_EXCEPTION_STREAM)) {
+    LOG(ERROR) << "system info size mismatch";
+    return false;
+  }
+
+  if (!exception_snapshot_.Initialize(
+          file_reader_, arch_, stream_it->second->Rva)) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace crashpad
diff --git a/snapshot/minidump/process_snapshot_minidump.h b/snapshot/minidump/process_snapshot_minidump.h
index 9ba5d9c..cf2d82b 100644
--- a/snapshot/minidump/process_snapshot_minidump.h
+++ b/snapshot/minidump/process_snapshot_minidump.h
@@ -29,7 +29,11 @@
 #include "minidump/minidump_extensions.h"
 #include "snapshot/exception_snapshot.h"
 #include "snapshot/memory_snapshot.h"
+#include "snapshot/minidump/exception_snapshot_minidump.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"
 #include "snapshot/module_snapshot.h"
 #include "snapshot/process_snapshot.h"
 #include "snapshot/system_snapshot.h"
@@ -38,9 +42,14 @@
 #include "util/file/file_reader.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/misc/uuid.h"
+#include "util/process/process_id.h"
 
 namespace crashpad {
 
+namespace internal {
+class MemoryMapRegionSnapshotMinidump;
+}  // namespace internal
+
 //! \brief A ProcessSnapshot based on a minidump file.
 class ProcessSnapshotMinidump final : public ProcessSnapshot {
  public:
@@ -58,8 +67,8 @@
 
   // ProcessSnapshot:
 
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -75,6 +84,17 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   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
@@ -85,6 +105,18 @@
   // Initialize().
   bool InitializeModules();
 
+  // Initializes data carried in a MINIDUMP_THREAD_LIST stream on behalf of
+  // Initialize().
+  bool InitializeThreads();
+
+  // Initializes data carried in a MINIDUMP_MEMORY_INFO_LIST stream on behalf of
+  // Initialize().
+  bool InitializeMemoryInfo();
+
+  // Initializes data carried in a MINIDUMP_SYSTEM_INFO stream on behalf of
+  // Initialize().
+  bool InitializeSystemSnapshot();
+
   // Initializes data carried in a MinidumpModuleCrashpadInfoList structure on
   // behalf of InitializeModules(). This makes use of MinidumpCrashpadInfo as
   // well, so it must be called after InitializeCrashpadInfo().
@@ -96,15 +128,34 @@
   // Initialize().
   bool InitializeMiscInfo();
 
+  // Initializes custom minidump streams.
+  bool InitializeCustomMinidumpStreams();
+
+  // Initializes data carried in a MINIDUMP_EXCEPTION_STREAM stream on behalf of
+  // Initialize().
+  bool InitializeExceptionSnapshot();
+
   MINIDUMP_HEADER header_;
   std::vector<MINIDUMP_DIRECTORY> stream_directory_;
   std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
   std::vector<std::unique_ptr<internal::ModuleSnapshotMinidump>> modules_;
+  std::vector<std::unique_ptr<internal::ThreadSnapshotMinidump>> threads_;
   std::vector<UnloadedModuleSnapshot> unloaded_modules_;
+  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_;
+  internal::ExceptionSnapshotMinidump exception_snapshot_;
+  CPUArchitecture arch_;
   std::map<std::string, std::string> annotations_simple_map_;
+  std::string full_version_;
   FileReaderInterface* file_reader_;  // weak
-  pid_t process_id_;
+  crashpad::ProcessID process_id_;
+  uint32_t create_time_;
+  uint32_t user_time_;
+  uint32_t kernel_time_;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotMinidump);
diff --git a/snapshot/minidump/process_snapshot_minidump_test.cc b/snapshot/minidump/process_snapshot_minidump_test.cc
index ed17939..9794979 100644
--- a/snapshot/minidump/process_snapshot_minidump_test.cc
+++ b/snapshot/minidump/process_snapshot_minidump_test.cc
@@ -18,17 +18,72 @@
 #include <dbghelp.h>
 #include <string.h>
 
+#include <algorithm>
 #include <memory>
 
+#include "base/numerics/safe_math.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
+#include "minidump/minidump_context.h"
+#include "snapshot/memory_map_region_snapshot.h"
 #include "snapshot/minidump/minidump_annotation_reader.h"
 #include "snapshot/module_snapshot.h"
 #include "util/file/string_file.h"
+#include "util/misc/pdb_structures.h"
 
 namespace crashpad {
 namespace test {
 namespace {
 
+class ReadToVector : public crashpad::MemorySnapshot::Delegate {
+ public:
+  std::vector<uint8_t> result;
+
+  bool MemorySnapshotDelegateRead(void* data, size_t size) override {
+    result.resize(size);
+    memcpy(result.data(), data, size);
+    return true;
+  }
+};
+
+MinidumpContextARM64 GetArm64MinidumpContext() {
+  MinidumpContextARM64 minidump_context;
+
+  minidump_context.context_flags = kMinidumpContextARM64Full;
+
+  minidump_context.cpsr = 0;
+
+  for (int i = 0; i < 29; i++) {
+    minidump_context.regs[i] = i + 1;
+  }
+
+  minidump_context.fp = 30;
+  minidump_context.lr = 31;
+  minidump_context.sp = 32;
+  minidump_context.pc = 33;
+
+  for (int i = 0; i < 32; i++) {
+    minidump_context.fpsimd[i].lo = i * 2 + 34;
+    minidump_context.fpsimd[i].hi = i * 2 + 35;
+  }
+
+  minidump_context.fpcr = 98;
+  minidump_context.fpsr = 99;
+
+  for (int i = 0; i < 8; i++) {
+    minidump_context.bcr[i] = i * 2 + 100;
+    minidump_context.bvr[i] = i * 2 + 101;
+  }
+
+  for (int i = 0; i < 2; i++) {
+    minidump_context.wcr[i] = i * 2 + 115;
+    minidump_context.wvr[i] = i * 2 + 116;
+  }
+
+  return minidump_context;
+}
+
 TEST(ProcessSnapshotMinidump, EmptyFile) {
   StringFile string_file;
   ProcessSnapshotMinidump process_snapshot;
@@ -287,7 +342,55 @@
   EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
 
   MINIDUMP_MODULE minidump_module = {};
-  uint32_t minidump_module_count = 4;
+  constexpr uint32_t minidump_module_count = 4;
+  RVA name_rvas[minidump_module_count];
+  std::string names[minidump_module_count] = {
+      "libtacotruck",
+      "libevidencebased",
+      "libgeorgism",
+      "librealistutopia",
+  };
+
+  minidump_module.BaseOfImage = 0xbadf00d;
+  minidump_module.SizeOfImage = 9001;
+  minidump_module.TimeDateStamp = 1970;
+  minidump_module.VersionInfo.dwFileVersionMS = 0xAABBCCDD;
+  minidump_module.VersionInfo.dwFileVersionLS = 0xEEFF4242;
+  minidump_module.VersionInfo.dwProductVersionMS = 0xAAAABBBB;
+  minidump_module.VersionInfo.dwProductVersionLS = 0xCCCCDDDD;
+  minidump_module.VersionInfo.dwFileType = VFT_APP;
+
+  for (uint32_t i = 0; i < minidump_module_count; i++) {
+    name_rvas[i] = static_cast<RVA>(string_file.SeekGet());
+    auto name16 = base::UTF8ToUTF16(names[i]);
+    uint32_t size =
+        base::checked_cast<uint32_t>(sizeof(name16[0]) * name16.size());
+    EXPECT_TRUE(string_file.Write(&size, sizeof(size)));
+    EXPECT_TRUE(string_file.Write(&name16[0], size));
+  }
+
+  CodeViewRecordPDB70 pdb70_cv;
+  pdb70_cv.signature = CodeViewRecordPDB70::kSignature;
+  pdb70_cv.age = 7;
+  pdb70_cv.uuid.InitializeFromString("00112233-4455-6677-8899-aabbccddeeff");
+  pdb70_cv.pdb_name[0] = '\0';
+
+  auto pdb70_loc = static_cast<RVA>(string_file.SeekGet());
+  auto pdb70_size = sizeof(pdb70_cv);
+
+  EXPECT_TRUE(string_file.Write(&pdb70_cv, sizeof(pdb70_cv)));
+
+  CodeViewRecordBuildID build_id_cv;
+  build_id_cv.signature = CodeViewRecordBuildID::kSignature;
+
+  auto build_id_cv_loc = static_cast<RVA>(string_file.SeekGet());
+
+  EXPECT_TRUE(string_file.Write(&build_id_cv,
+                                offsetof(CodeViewRecordBuildID, build_id)));
+  EXPECT_TRUE(string_file.Write("atestbuildidbecausewhynot", 25));
+
+  auto build_id_cv_size =
+      static_cast<size_t>(string_file.SeekGet() - build_id_cv_loc);
 
   MINIDUMP_DIRECTORY minidump_module_list_directory = {};
   minidump_module_list_directory.StreamType = kMinidumpStreamTypeModuleList;
@@ -302,7 +405,18 @@
   for (uint32_t minidump_module_index = 0;
        minidump_module_index < minidump_module_count;
        ++minidump_module_index) {
+    if (minidump_module_index % 2) {
+      minidump_module.CvRecord.Rva = pdb70_loc;
+      minidump_module.CvRecord.DataSize = static_cast<uint32_t>(pdb70_size);
+    } else {
+      minidump_module.CvRecord.Rva = build_id_cv_loc;
+      minidump_module.CvRecord.DataSize =
+          static_cast<uint32_t>(build_id_cv_size);
+    }
+
+    minidump_module.ModuleNameRva = name_rvas[minidump_module_index];
     EXPECT_TRUE(string_file.Write(&minidump_module, sizeof(minidump_module)));
+    minidump_module.TimeDateStamp++;
   }
 
   MinidumpModuleCrashpadInfo crashpad_module_0 = {};
@@ -398,6 +512,47 @@
   std::vector<const ModuleSnapshot*> modules = process_snapshot.Modules();
   ASSERT_EQ(modules.size(), minidump_module_count);
 
+  for (uint32_t i = 0; i < minidump_module_count; i++) {
+    EXPECT_EQ(modules[i]->Name(), names[i]);
+    EXPECT_EQ(modules[i]->Address(), 0xbadf00dU);
+    EXPECT_EQ(modules[i]->Size(), 9001U);
+    EXPECT_EQ(modules[i]->Timestamp(), static_cast<time_t>(1970U + i));
+
+    uint16_t v0;
+    uint16_t v1;
+    uint16_t v2;
+    uint16_t v3;
+
+    modules[i]->FileVersion(&v0, &v1, &v2, &v3);
+    EXPECT_EQ(v0, 0xAABBU);
+    EXPECT_EQ(v1, 0xCCDDU);
+    EXPECT_EQ(v2, 0xEEFFU);
+    EXPECT_EQ(v3, 0x4242U);
+
+    modules[i]->SourceVersion(&v0, &v1, &v2, &v3);
+    EXPECT_EQ(v0, 0xAAAAU);
+    EXPECT_EQ(v1, 0xBBBBU);
+    EXPECT_EQ(v2, 0xCCCCU);
+    EXPECT_EQ(v3, 0xDDDDU);
+
+    EXPECT_EQ(modules[i]->GetModuleType(),
+              ModuleSnapshot::kModuleTypeExecutable);
+
+    if (i % 2) {
+      uint32_t age;
+      UUID uuid;
+      modules[i]->UUIDAndAge(&uuid, &age);
+
+      EXPECT_EQ(uuid.ToString(), "00112233-4455-6677-8899-aabbccddeeff");
+      EXPECT_EQ(age, 7U);
+    } else {
+      auto build_id = modules[i]->BuildID();
+      std::string build_id_text(build_id.data(),
+                                build_id.data() + build_id.size());
+      EXPECT_EQ(build_id_text, "atestbuildidbecausewhynot");
+    }
+  }
+
   auto annotations_simple_map = modules[0]->AnnotationsSimpleMap();
   EXPECT_EQ(annotations_simple_map, dictionary_0);
 
@@ -426,7 +581,7 @@
   MINIDUMP_HEADER header = {};
   ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
 
-  static const pid_t kTestProcessId = 42;
+  static const crashpad::ProcessID kTestProcessId = 42;
   MINIDUMP_MISC_INFO misc_info = {};
   misc_info.SizeOfInfo = sizeof(misc_info);
   misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID;
@@ -452,6 +607,827 @@
   EXPECT_EQ(process_snapshot.ProcessID(), kTestProcessId);
 }
 
+TEST(ProcessSnapshotMinidump, SnapshotTime) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.TimeDateStamp = 42;
+  ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(&string_file));
+
+  timeval snapshot_time;
+  process_snapshot.SnapshotTime(&snapshot_time);
+  EXPECT_EQ(snapshot_time.tv_sec, 42);
+  EXPECT_EQ(snapshot_time.tv_usec, 0);
+}
+
+TEST(ProcessSnapshotMinidump, MiscTimes) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_MISC_INFO misc_info = {};
+  misc_info.SizeOfInfo = sizeof(misc_info);
+  misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_TIMES;
+  misc_info.ProcessCreateTime = 42;
+  misc_info.ProcessUserTime = 43;
+  misc_info.ProcessKernelTime = 44;
+
+  MINIDUMP_DIRECTORY misc_directory = {};
+  misc_directory.StreamType = kMinidumpStreamTypeMiscInfo;
+  misc_directory.Location.DataSize = sizeof(misc_info);
+  misc_directory.Location.Rva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&misc_info, sizeof(misc_info)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 1;
+  ASSERT_TRUE(string_file.SeekSet(0));
+  ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  ASSERT_TRUE(process_snapshot.Initialize(&string_file));
+
+  timeval start_time, user_time, kernel_time;
+  process_snapshot.ProcessStartTime(&start_time);
+  process_snapshot.ProcessCPUTimes(&user_time, &kernel_time);
+  EXPECT_EQ(static_cast<uint32_t>(start_time.tv_sec),
+            misc_info.ProcessCreateTime);
+  EXPECT_EQ(start_time.tv_usec, 0);
+  EXPECT_EQ(static_cast<uint32_t>(user_time.tv_sec), misc_info.ProcessUserTime);
+  EXPECT_EQ(user_time.tv_usec, 0);
+  EXPECT_EQ(static_cast<uint32_t>(kernel_time.tv_sec),
+            misc_info.ProcessKernelTime);
+  EXPECT_EQ(kernel_time.tv_usec, 0);
+}
+
+TEST(ProcessSnapshotMinidump, Threads) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_THREAD minidump_thread = {};
+  uint32_t minidump_thread_count = 4;
+
+  minidump_thread.ThreadId = 42;
+  minidump_thread.Teb = 24;
+
+  MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
+  minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
+  minidump_thread_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_LIST) +
+      minidump_thread_count * sizeof(MINIDUMP_THREAD);
+  minidump_thread_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count)));
+  for (uint32_t minidump_thread_index = 0;
+       minidump_thread_index < minidump_thread_count;
+       ++minidump_thread_index) {
+    EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
+    minidump_thread.ThreadId++;
+  }
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  EXPECT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 1;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
+  ASSERT_EQ(threads.size(), minidump_thread_count);
+
+  uint32_t thread_id = 42;
+  for (const auto& thread : threads) {
+    EXPECT_EQ(thread->ThreadID(), thread_id);
+    EXPECT_EQ(thread->ThreadSpecificDataAddress(), 24UL);
+    thread_id++;
+  }
+}
+
+TEST(ProcessSnapshotMinidump, System) {
+  const char* cpu_info = "GenuineIntel";
+  const uint32_t* cpu_info_bytes = reinterpret_cast<const uint32_t*>(cpu_info);
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_SYSTEM_INFO minidump_system_info = {};
+
+  minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureX86;
+  minidump_system_info.ProcessorLevel = 3;
+  minidump_system_info.ProcessorRevision = 3;
+  minidump_system_info.NumberOfProcessors = 8;
+  minidump_system_info.ProductType = kMinidumpOSTypeServer;
+  minidump_system_info.PlatformId = kMinidumpOSFuchsia;
+  minidump_system_info.MajorVersion = 3;
+  minidump_system_info.MinorVersion = 4;
+  minidump_system_info.BuildNumber = 56;
+  minidump_system_info.CSDVersionRva = WriteString(&string_file, "Snazzle");
+  minidump_system_info.Cpu.X86CpuInfo.VendorId[0] = cpu_info_bytes[0];
+  minidump_system_info.Cpu.X86CpuInfo.VendorId[1] = cpu_info_bytes[1];
+  minidump_system_info.Cpu.X86CpuInfo.VendorId[2] = cpu_info_bytes[2];
+
+  MINIDUMP_MISC_INFO_5 minidump_misc_info = {};
+  base::string16 build_string;
+  ASSERT_TRUE(base::UTF8ToUTF16(
+      "MyOSVersion; MyMachineDescription", 33, &build_string));
+  std::copy(build_string.begin(), build_string.end(),
+            minidump_misc_info.BuildString);
+
+  MINIDUMP_DIRECTORY minidump_system_info_directory = {};
+  minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo;
+  minidump_system_info_directory.Location.DataSize =
+      sizeof(MINIDUMP_SYSTEM_INFO);
+  minidump_system_info_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(
+      string_file.Write(&minidump_system_info, sizeof(minidump_system_info)));
+
+  MINIDUMP_DIRECTORY minidump_misc_info_directory = {};
+  minidump_misc_info_directory.StreamType = kMinidumpStreamTypeMiscInfo;
+  minidump_misc_info_directory.Location.DataSize = sizeof(MINIDUMP_MISC_INFO_5);
+  minidump_misc_info_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(
+      string_file.Write(&minidump_misc_info, sizeof(minidump_misc_info)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_system_info_directory,
+                                sizeof(minidump_system_info_directory)));
+  ASSERT_TRUE(string_file.Write(&minidump_misc_info_directory,
+                                sizeof(minidump_misc_info_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 2;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  const SystemSnapshot* s = process_snapshot.System();
+
+  EXPECT_EQ(s->GetCPUArchitecture(), kCPUArchitectureX86);
+  EXPECT_EQ(s->CPURevision(), 3UL);
+  EXPECT_EQ(s->CPUVendor(), "GenuineIntel");
+  EXPECT_EQ(s->GetOperatingSystem(),
+            SystemSnapshot::OperatingSystem::kOperatingSystemFuchsia);
+  EXPECT_EQ(s->OSVersionFull(), "MyOSVersion");
+
+  int major, minor, bugfix;
+  std::string build;
+  s->OSVersion(&major, &minor, &bugfix, &build);
+
+  EXPECT_EQ(major, 3);
+  EXPECT_EQ(minor, 4);
+  EXPECT_EQ(bugfix, 56);
+  EXPECT_EQ(build, "Snazzle");
+}
+
+TEST(ProcessSnapshotMinidump, ThreadContextARM64) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_SYSTEM_INFO minidump_system_info = {};
+
+  minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureARM64;
+  minidump_system_info.ProductType = kMinidumpOSTypeServer;
+  minidump_system_info.PlatformId = kMinidumpOSFuchsia;
+  minidump_system_info.CSDVersionRva = WriteString(&string_file, "");
+
+  MINIDUMP_DIRECTORY minidump_system_info_directory = {};
+  minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo;
+  minidump_system_info_directory.Location.DataSize =
+      sizeof(MINIDUMP_SYSTEM_INFO);
+  minidump_system_info_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(
+      string_file.Write(&minidump_system_info, sizeof(minidump_system_info)));
+
+  MINIDUMP_THREAD minidump_thread = {};
+  uint32_t minidump_thread_count = 1;
+
+  minidump_thread.ThreadId = 42;
+  minidump_thread.Teb = 24;
+
+  MinidumpContextARM64 minidump_context = GetArm64MinidumpContext();
+
+  minidump_thread.ThreadContext.DataSize = sizeof(minidump_context);
+  minidump_thread.ThreadContext.Rva = static_cast<RVA>(string_file.SeekGet());
+
+  EXPECT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context)));
+
+  MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
+  minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
+  minidump_thread_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_LIST) +
+      minidump_thread_count * sizeof(MINIDUMP_THREAD);
+  minidump_thread_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count)));
+  EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_system_info_directory,
+                                sizeof(minidump_system_info_directory)));
+  ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 2;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
+  ASSERT_EQ(threads.size(), minidump_thread_count);
+
+  const CPUContext* ctx_generic = threads[0]->Context();
+
+  ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureARM64);
+
+  const CPUContextARM64* ctx = ctx_generic->arm64;
+
+  EXPECT_EQ(ctx->spsr, 0UL);
+
+  for (unsigned int i = 0; i < 31; i++) {
+    EXPECT_EQ(ctx->regs[i], i + 1);
+  }
+
+  EXPECT_EQ(ctx->sp, 32UL);
+  EXPECT_EQ(ctx->pc, 33UL);
+  EXPECT_EQ(ctx->fpcr, 98UL);
+  EXPECT_EQ(ctx->fpsr, 99UL);
+
+  for (unsigned int i = 0; i < 32; i++) {
+    EXPECT_EQ(ctx->fpsimd[i].lo, i * 2 + 34);
+    EXPECT_EQ(ctx->fpsimd[i].hi, i * 2 + 35);
+  }
+}
+
+TEST(ProcessSnapshotMinidump, ThreadContextX86_64) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_SYSTEM_INFO minidump_system_info = {};
+
+  minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureAMD64;
+  minidump_system_info.ProductType = kMinidumpOSTypeServer;
+  minidump_system_info.PlatformId = kMinidumpOSFuchsia;
+  minidump_system_info.CSDVersionRva = WriteString(&string_file, "");
+
+  MINIDUMP_DIRECTORY minidump_system_info_directory = {};
+  minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo;
+  minidump_system_info_directory.Location.DataSize =
+      sizeof(MINIDUMP_SYSTEM_INFO);
+  minidump_system_info_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(
+      string_file.Write(&minidump_system_info, sizeof(minidump_system_info)));
+
+  MINIDUMP_THREAD minidump_thread = {};
+  uint32_t minidump_thread_count = 1;
+
+  minidump_thread.ThreadId = 42;
+  minidump_thread.Teb = 24;
+
+  MinidumpContextAMD64 minidump_context;
+
+  minidump_context.context_flags = kMinidumpContextAMD64Full;
+
+  minidump_context.mx_csr = 0;
+  minidump_context.cs = 1;
+  minidump_context.ds = 2;
+  minidump_context.es = 3;
+  minidump_context.fs = 4;
+  minidump_context.gs = 5;
+  minidump_context.ss = 6;
+  minidump_context.eflags = 7;
+  minidump_context.dr0 = 8;
+  minidump_context.dr1 = 9;
+  minidump_context.dr2 = 10;
+  minidump_context.dr3 = 11;
+  minidump_context.dr6 = 12;
+  minidump_context.dr7 = 13;
+  minidump_context.rax = 14;
+  minidump_context.rcx = 15;
+  minidump_context.rdx = 16;
+  minidump_context.rbx = 17;
+  minidump_context.rsp = 18;
+  minidump_context.rbp = 19;
+  minidump_context.rsi = 20;
+  minidump_context.rdi = 21;
+  minidump_context.r8 = 22;
+  minidump_context.r9 = 23;
+  minidump_context.r10 = 24;
+  minidump_context.r11 = 25;
+  minidump_context.r12 = 26;
+  minidump_context.r13 = 27;
+  minidump_context.r14 = 28;
+  minidump_context.r15 = 29;
+  minidump_context.rip = 30;
+  minidump_context.vector_control = 31;
+  minidump_context.debug_control = 32;
+  minidump_context.last_branch_to_rip = 33;
+  minidump_context.last_branch_from_rip = 34;
+  minidump_context.last_exception_to_rip = 35;
+  minidump_context.last_exception_from_rip = 36;
+  minidump_context.fxsave.fcw = 37;
+  minidump_context.fxsave.fsw = 38;
+  minidump_context.fxsave.ftw = 39;
+  minidump_context.fxsave.reserved_1 = 40;
+  minidump_context.fxsave.fop = 41;
+  minidump_context.fxsave.fpu_ip_64 = 42;
+  minidump_context.fxsave.fpu_dp_64 = 43;
+
+  for (size_t i = 0; i < base::size(minidump_context.vector_register); i++) {
+    minidump_context.vector_register[i].lo = i * 2 + 44;
+    minidump_context.vector_register[i].hi = i * 2 + 45;
+  }
+
+  for (uint8_t i = 0; i < base::size(minidump_context.fxsave.reserved_4); i++) {
+    minidump_context.fxsave.reserved_4[i] = i * 2 + 115;
+    minidump_context.fxsave.available[i] = i * 2 + 116;
+  }
+
+  for (size_t i = 0; i < base::size(minidump_context.fxsave.st_mm); i++) {
+    for (uint8_t j = 0;
+         j < base::size(minidump_context.fxsave.st_mm[0].mm_value);
+         j++) {
+      minidump_context.fxsave.st_mm[i].mm_value[j] = j + 1;
+      minidump_context.fxsave.st_mm[i].mm_reserved[j] = j + 1;
+    }
+  }
+
+  for (size_t i = 0; i < base::size(minidump_context.fxsave.xmm); i++) {
+    for (uint8_t j = 0; j < base::size(minidump_context.fxsave.xmm[0]); j++) {
+      minidump_context.fxsave.xmm[i][j] = j + 1;
+    }
+  }
+
+  minidump_thread.ThreadContext.DataSize = sizeof(minidump_context);
+  minidump_thread.ThreadContext.Rva = static_cast<RVA>(string_file.SeekGet());
+
+  EXPECT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context)));
+
+  MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
+  minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
+  minidump_thread_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_LIST) +
+      minidump_thread_count * sizeof(MINIDUMP_THREAD);
+  minidump_thread_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count)));
+  EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_system_info_directory,
+                                sizeof(minidump_system_info_directory)));
+  ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 2;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
+  ASSERT_EQ(threads.size(), minidump_thread_count);
+
+  const CPUContext* ctx_generic = threads[0]->Context();
+
+  ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureX86_64);
+
+  const CPUContextX86_64* ctx = ctx_generic->x86_64;
+  EXPECT_EQ(ctx->cs, 1);
+  EXPECT_EQ(ctx->fs, 4);
+  EXPECT_EQ(ctx->gs, 5);
+  EXPECT_EQ(ctx->rflags, 7UL);
+  EXPECT_EQ(ctx->dr0, 8UL);
+  EXPECT_EQ(ctx->dr1, 9U);
+  EXPECT_EQ(ctx->dr2, 10U);
+  EXPECT_EQ(ctx->dr3, 11U);
+  EXPECT_EQ(ctx->dr4, 12U);
+  EXPECT_EQ(ctx->dr5, 13U);
+  EXPECT_EQ(ctx->dr6, 12U);
+  EXPECT_EQ(ctx->dr7, 13U);
+  EXPECT_EQ(ctx->rax, 14U);
+  EXPECT_EQ(ctx->rcx, 15U);
+  EXPECT_EQ(ctx->rdx, 16U);
+  EXPECT_EQ(ctx->rbx, 17U);
+  EXPECT_EQ(ctx->rsp, 18U);
+  EXPECT_EQ(ctx->rbp, 19U);
+  EXPECT_EQ(ctx->rsi, 20U);
+  EXPECT_EQ(ctx->rdi, 21U);
+  EXPECT_EQ(ctx->r8, 22U);
+  EXPECT_EQ(ctx->r9, 23U);
+  EXPECT_EQ(ctx->r10, 24U);
+  EXPECT_EQ(ctx->r11, 25U);
+  EXPECT_EQ(ctx->r12, 26U);
+  EXPECT_EQ(ctx->r13, 27U);
+  EXPECT_EQ(ctx->r14, 28U);
+  EXPECT_EQ(ctx->r15, 29U);
+  EXPECT_EQ(ctx->rip, 30U);
+  EXPECT_EQ(ctx->fxsave.fcw, 37U);
+  EXPECT_EQ(ctx->fxsave.fsw, 38U);
+  EXPECT_EQ(ctx->fxsave.ftw, 39U);
+  EXPECT_EQ(ctx->fxsave.reserved_1, 40U);
+  EXPECT_EQ(ctx->fxsave.fop, 41U);
+  EXPECT_EQ(ctx->fxsave.fpu_ip_64, 42U);
+  EXPECT_EQ(ctx->fxsave.fpu_dp_64, 43U);
+
+  for (uint8_t i = 0; i < base::size(ctx->fxsave.reserved_4); i++) {
+    EXPECT_EQ(ctx->fxsave.reserved_4[i], i * 2 + 115);
+    EXPECT_EQ(ctx->fxsave.available[i], i * 2 + 116);
+  }
+
+  for (size_t i = 0; i < base::size(ctx->fxsave.st_mm); i++) {
+    for (uint8_t j = 0; j < base::size(ctx->fxsave.st_mm[0].mm_value); j++) {
+      EXPECT_EQ(ctx->fxsave.st_mm[i].mm_value[j], j + 1);
+      EXPECT_EQ(ctx->fxsave.st_mm[i].mm_reserved[j], j + 1);
+    }
+  }
+
+  for (size_t i = 0; i < base::size(ctx->fxsave.xmm); i++) {
+    for (uint8_t j = 0; j < base::size(ctx->fxsave.xmm[0]); j++) {
+      EXPECT_EQ(ctx->fxsave.xmm[i][j], j + 1);
+    }
+  }
+}
+
+TEST(ProcessSnapshotMinidump, MemoryMap) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_MEMORY_INFO minidump_memory_info_1 = {};
+  MINIDUMP_MEMORY_INFO minidump_memory_info_2 = {};
+  uint32_t minidump_memory_info_count = 2;
+
+  minidump_memory_info_1.BaseAddress = 1;
+  minidump_memory_info_1.AllocationBase = 2;
+  minidump_memory_info_1.AllocationProtect = 3;
+  minidump_memory_info_1.RegionSize = 4;
+  minidump_memory_info_1.State = 5;
+  minidump_memory_info_1.Protect = 6;
+  minidump_memory_info_1.Type = 6;
+
+  minidump_memory_info_2.BaseAddress = 7;
+  minidump_memory_info_2.AllocationBase = 8;
+  minidump_memory_info_2.AllocationProtect = 9;
+  minidump_memory_info_2.RegionSize = 10;
+  minidump_memory_info_2.State = 11;
+  minidump_memory_info_2.Protect = 12;
+  minidump_memory_info_2.Type = 13;
+
+  MINIDUMP_MEMORY_INFO_LIST minidump_memory_info_list = {};
+
+  minidump_memory_info_list.SizeOfHeader = sizeof(minidump_memory_info_list);
+  minidump_memory_info_list.SizeOfEntry = sizeof(MINIDUMP_MEMORY_INFO);
+  minidump_memory_info_list.NumberOfEntries = minidump_memory_info_count;
+
+  MINIDUMP_DIRECTORY minidump_memory_info_list_directory = {};
+  minidump_memory_info_list_directory.StreamType =
+      kMinidumpStreamTypeMemoryInfoList;
+  minidump_memory_info_list_directory.Location.DataSize =
+      sizeof(minidump_memory_info_list) +
+      minidump_memory_info_count * sizeof(MINIDUMP_MEMORY_INFO);
+  minidump_memory_info_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  EXPECT_TRUE(string_file.Write(&minidump_memory_info_list,
+                                sizeof(minidump_memory_info_list)));
+  EXPECT_TRUE(string_file.Write(&minidump_memory_info_1,
+                                sizeof(minidump_memory_info_1)));
+  EXPECT_TRUE(string_file.Write(&minidump_memory_info_2,
+                                sizeof(minidump_memory_info_2)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  EXPECT_TRUE(string_file.Write(&minidump_memory_info_list_directory,
+                                sizeof(minidump_memory_info_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 1;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const MemoryMapRegionSnapshot*> map =
+      process_snapshot.MemoryMap();
+  ASSERT_EQ(map.size(), minidump_memory_info_count);
+  EXPECT_EQ(memcmp(&map[0]->AsMinidumpMemoryInfo(),
+                   &minidump_memory_info_1,
+                   sizeof(minidump_memory_info_1)),
+            0);
+  EXPECT_EQ(memcmp(&map[1]->AsMinidumpMemoryInfo(),
+                   &minidump_memory_info_2,
+                   sizeof(minidump_memory_info_2)),
+            0);
+}
+
+TEST(ProcessSnapshotMinidump, Stacks) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  MINIDUMP_THREAD minidump_thread = {};
+  uint32_t minidump_thread_count = 1;
+
+  minidump_thread.ThreadId = 42;
+  minidump_thread.Stack.StartOfMemoryRange = 0xbeefd00d;
+
+  std::vector<uint8_t> minidump_stack = {'1',
+                                         '2',
+                                         '3',
+                                         '4',
+                                         '5',
+                                         '6',
+                                         '7',
+                                         '8',
+                                         '9',
+                                         'a',
+                                         'b',
+                                         'c',
+                                         'd',
+                                         'e',
+                                         'f'};
+
+  minidump_thread.Stack.Memory.DataSize =
+      base::checked_cast<uint32_t>(minidump_stack.size());
+  minidump_thread.Stack.Memory.Rva = static_cast<RVA>(string_file.SeekGet());
+
+  EXPECT_TRUE(string_file.Write(minidump_stack.data(), minidump_stack.size()));
+
+  MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
+  minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
+  minidump_thread_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_LIST) +
+      minidump_thread_count * sizeof(MINIDUMP_THREAD);
+  minidump_thread_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&minidump_thread_count, sizeof(minidump_thread_count)));
+  EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 1;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
+  ASSERT_EQ(threads.size(), minidump_thread_count);
+
+  ReadToVector delegate;
+  threads[0]->Stack()->Read(&delegate);
+
+  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);
+}
+
+TEST(ProcessSnapshotMinidump, Exception) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  uint32_t exception_signo =
+      static_cast<uint32_t>(-1);  // crashpad::Signals::kSimulatedSigno
+
+  MINIDUMP_EXCEPTION minidump_exception = {};
+  minidump_exception.ExceptionCode = exception_signo;
+  minidump_exception.ExceptionFlags = 2;
+  minidump_exception.ExceptionRecord = 4;
+  minidump_exception.ExceptionAddress = 0xdeedb00f;
+  minidump_exception.NumberParameters = 2;
+  minidump_exception.ExceptionInformation[0] = 51;
+  minidump_exception.ExceptionInformation[1] = 62;
+
+  MINIDUMP_SYSTEM_INFO minidump_system_info = {};
+
+  minidump_system_info.ProcessorArchitecture = kMinidumpCPUArchitectureARM64;
+  minidump_system_info.ProductType = kMinidumpOSTypeServer;
+  minidump_system_info.PlatformId = kMinidumpOSFuchsia;
+  minidump_system_info.CSDVersionRva = WriteString(&string_file, "");
+
+  MINIDUMP_DIRECTORY minidump_system_info_directory = {};
+  minidump_system_info_directory.StreamType = kMinidumpStreamTypeSystemInfo;
+  minidump_system_info_directory.Location.DataSize =
+      sizeof(MINIDUMP_SYSTEM_INFO);
+  minidump_system_info_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(
+      string_file.Write(&minidump_system_info, sizeof(minidump_system_info)));
+
+  MINIDUMP_EXCEPTION_STREAM minidump_exception_stream = {};
+  minidump_exception_stream.ThreadId = 5;
+  minidump_exception_stream.ExceptionRecord = minidump_exception;
+
+  MinidumpContextARM64 minidump_context = GetArm64MinidumpContext();
+
+  minidump_exception_stream.ThreadContext.DataSize = sizeof(minidump_context);
+  minidump_exception_stream.ThreadContext.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(string_file.Write(&minidump_context, sizeof(minidump_context)));
+
+  MINIDUMP_DIRECTORY minidump_exception_directory = {};
+  minidump_exception_directory.StreamType = kMinidumpStreamTypeException;
+  minidump_exception_directory.Location.DataSize =
+      sizeof(MINIDUMP_EXCEPTION_STREAM);
+  minidump_exception_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  ASSERT_TRUE(string_file.Write(&minidump_exception_stream,
+                                sizeof(minidump_exception_stream)));
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_exception_directory,
+                                sizeof(minidump_exception_directory)));
+  ASSERT_TRUE(string_file.Write(&minidump_system_info_directory,
+                                sizeof(minidump_system_info_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 2;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  const ExceptionSnapshot* s = process_snapshot.Exception();
+
+  EXPECT_EQ(s->ThreadID(), 5UL);
+  EXPECT_EQ(s->Exception(), exception_signo);
+  EXPECT_EQ(s->ExceptionInfo(), 2U);
+  EXPECT_EQ(s->ExceptionAddress(), 0xdeedb00f);
+
+  const std::vector<uint64_t> codes = s->Codes();
+  EXPECT_EQ(codes.size(), 2UL);
+  EXPECT_EQ(codes[0], 51UL);
+  EXPECT_EQ(codes[1], 62UL);
+
+  const CPUContext* ctx_generic = s->Context();
+
+  ASSERT_EQ(ctx_generic->architecture, CPUArchitecture::kCPUArchitectureARM64);
+
+  const CPUContextARM64* ctx = ctx_generic->arm64;
+
+  EXPECT_EQ(ctx->spsr, 0UL);
+
+  for (unsigned int i = 0; i < 31; i++) {
+    EXPECT_EQ(ctx->regs[i], i + 1);
+  }
+
+  EXPECT_EQ(ctx->sp, 32UL);
+  EXPECT_EQ(ctx->pc, 33UL);
+  EXPECT_EQ(ctx->fpcr, 98UL);
+  EXPECT_EQ(ctx->fpsr, 99UL);
+
+  for (unsigned int i = 0; i < 32; i++) {
+    EXPECT_EQ(ctx->fpsimd[i].lo, i * 2 + 34);
+    EXPECT_EQ(ctx->fpsimd[i].hi, i * 2 + 35);
+  }
+}
+
+TEST(ProcessSnapshotMinidump, NoExceptionInMinidump) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 0;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  const ExceptionSnapshot* s = process_snapshot.Exception();
+  EXPECT_EQ(s, nullptr);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/minidump/system_snapshot_minidump.cc b/snapshot/minidump/system_snapshot_minidump.cc
new file mode 100644
index 0000000..06bae48
--- /dev/null
+++ b/snapshot/minidump/system_snapshot_minidump.cc
@@ -0,0 +1,198 @@
+// 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.
+
+#include "snapshot/minidump/system_snapshot_minidump.h"
+
+#include "snapshot/minidump/minidump_string_reader.h"
+
+namespace crashpad {
+namespace internal {
+
+SystemSnapshotMinidump::SystemSnapshotMinidump()
+    : SystemSnapshot(), minidump_system_info_(), initialized_() {}
+
+SystemSnapshotMinidump::~SystemSnapshotMinidump() {}
+
+bool SystemSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
+                                        RVA minidump_system_info_rva,
+                                        const std::string& version) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  full_version_ = version;
+
+  if (!file_reader->SeekSet(minidump_system_info_rva)) {
+    return false;
+  }
+
+  if (!file_reader->ReadExactly(&minidump_system_info_,
+                                sizeof(minidump_system_info_))) {
+    return false;
+  }
+
+  if (!ReadMinidumpUTF8String(file_reader,
+                              minidump_system_info_.CSDVersionRva,
+                              &minidump_build_name_)) {
+    return false;
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+CPUArchitecture SystemSnapshotMinidump::GetCPUArchitecture() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  switch (minidump_system_info_.ProcessorArchitecture) {
+    case kMinidumpCPUArchitectureAMD64:
+      return kCPUArchitectureX86_64;
+    case kMinidumpCPUArchitectureX86:
+    case kMinidumpCPUArchitectureX86Win64:
+      return kCPUArchitectureX86;
+    case kMinidumpCPUArchitectureARM:
+    case kMinidumpCPUArchitectureARM32Win64:
+      return kCPUArchitectureARM;
+    case kMinidumpCPUArchitectureARM64:
+    case kMinidumpCPUArchitectureARM64Breakpad:
+      return kCPUArchitectureARM64;
+    case kMinidumpCPUArchitectureMIPS:
+      return kCPUArchitectureMIPSEL;
+    // No word on how MIPS64 is signalled
+
+    default:
+      return CPUArchitecture::kCPUArchitectureUnknown;
+  }
+}
+
+uint32_t SystemSnapshotMinidump::CPURevision() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_system_info_.ProcessorRevision;
+}
+
+uint8_t SystemSnapshotMinidump::CPUCount() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_system_info_.NumberOfProcessors;
+}
+
+std::string SystemSnapshotMinidump::CPUVendor() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  if (GetCPUArchitecture() == kCPUArchitectureX86) {
+    const char* ptr = reinterpret_cast<const char*>(
+        minidump_system_info_.Cpu.X86CpuInfo.VendorId);
+    return std::string(ptr, ptr + (3 * sizeof(uint32_t)));
+  } else {
+    return std::string();
+  }
+}
+
+void SystemSnapshotMinidump::CPUFrequency(uint64_t* current_hz,
+                                          uint64_t* max_hz) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+}
+
+uint32_t SystemSnapshotMinidump::CPUX86Signature() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return 0;
+}
+
+uint64_t SystemSnapshotMinidump::CPUX86Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return 0;
+}
+
+uint64_t SystemSnapshotMinidump::CPUX86ExtendedFeatures() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return 0;
+}
+
+uint32_t SystemSnapshotMinidump::CPUX86Leaf7Features() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return 0;
+}
+
+bool SystemSnapshotMinidump::CPUX86SupportsDAZ() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return false;
+}
+
+SystemSnapshot::OperatingSystem SystemSnapshotMinidump::GetOperatingSystem()
+    const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  switch (minidump_system_info_.PlatformId) {
+    case kMinidumpOSMacOSX:
+      return OperatingSystem::kOperatingSystemMacOSX;
+    case kMinidumpOSWin32s:
+    case kMinidumpOSWin32Windows:
+    case kMinidumpOSWin32NT:
+      return OperatingSystem::kOperatingSystemWindows;
+    case kMinidumpOSLinux:
+      return OperatingSystem::kOperatingSystemLinux;
+    case kMinidumpOSAndroid:
+      return OperatingSystem::kOperatingSystemAndroid;
+    case kMinidumpOSFuchsia:
+      return OperatingSystem::kOperatingSystemFuchsia;
+    default:
+      return OperatingSystem::kOperatingSystemUnknown;
+  }
+}
+
+bool SystemSnapshotMinidump::OSServer() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_system_info_.ProductType == kMinidumpOSTypeServer;
+}
+
+void SystemSnapshotMinidump::OSVersion(int* major,
+                                       int* minor,
+                                       int* bugfix,
+                                       std::string* build) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  *major = minidump_system_info_.MajorVersion;
+  *minor = minidump_system_info_.MinorVersion;
+  *bugfix = minidump_system_info_.BuildNumber;
+  *build = minidump_build_name_;
+}
+
+std::string SystemSnapshotMinidump::OSVersionFull() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return full_version_;
+}
+
+std::string SystemSnapshotMinidump::MachineDescription() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return std::string();
+}
+
+bool SystemSnapshotMinidump::NXEnabled() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+  return false;
+}
+
+void SystemSnapshotMinidump::TimeZone(DaylightSavingTimeStatus* dst_status,
+                                      int* standard_offset_seconds,
+                                      int* daylight_offset_seconds,
+                                      std::string* standard_name,
+                                      std::string* daylight_name) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  NOTREACHED();  // https://crashpad.chromium.org/bug/10
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/minidump/system_snapshot_minidump.h b/snapshot/minidump/system_snapshot_minidump.h
new file mode 100644
index 0000000..0f2880e
--- /dev/null
+++ b/snapshot/minidump/system_snapshot_minidump.h
@@ -0,0 +1,87 @@
+// 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_SYSTEM_SNAPSHOT_MINIDUMP_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_SYSTEM_SNAPSHOT_MINIDUMP_H_
+
+#include <windows.h>
+
+#include "base/macros.h"
+#include "minidump/minidump_extensions.h"
+#include "snapshot/system_snapshot.h"
+#include "util/file/file_reader.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A SystemSnapshot based on a minidump file.
+class SystemSnapshotMinidump : public SystemSnapshot {
+ public:
+  SystemSnapshotMinidump();
+  ~SystemSnapshotMinidump() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] file_reader A file reader corresponding to a minidump file.
+  //!     The file reader must support seeking.
+  //! \param[in] minidump_system_info_rva The file offset in \a file_reader at
+  //!     which the thread’s MINIDUMP_SYSTEM_INFO structure is located.
+  //! \param[in] version The OS version taken from the build string in
+  //!     MINIDUMP_MISC_INFO_4.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  bool Initialize(FileReaderInterface* file_reader,
+                  RVA minidump_system_info_rva,
+                  const std::string& version);
+
+  CPUArchitecture GetCPUArchitecture() const override;
+  uint32_t CPURevision() const override;
+  uint8_t CPUCount() const override;
+  std::string CPUVendor() const override;
+  void CPUFrequency(uint64_t* current_hz, uint64_t* max_hz) const override;
+  uint32_t CPUX86Signature() const override;
+  uint64_t CPUX86Features() const override;
+  uint64_t CPUX86ExtendedFeatures() const override;
+  uint32_t CPUX86Leaf7Features() const override;
+  bool CPUX86SupportsDAZ() const override;
+  OperatingSystem GetOperatingSystem() const override;
+  bool OSServer() const override;
+  void OSVersion(int* major,
+                 int* minor,
+                 int* bugfix,
+                 std::string* build) const override;
+  std::string OSVersionFull() const override;
+  std::string MachineDescription() const override;
+  bool NXEnabled() const override;
+  void TimeZone(DaylightSavingTimeStatus* dst_status,
+                int* standard_offset_seconds,
+                int* daylight_offset_seconds,
+                std::string* standard_name,
+                std::string* daylight_name) const override;
+
+ private:
+  MINIDUMP_SYSTEM_INFO minidump_system_info_;
+  std::string minidump_build_name_;
+  std::string full_version_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemSnapshotMinidump);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_SYSTEM_SNAPSHOT_MINIDUMP_H_
diff --git a/snapshot/minidump/thread_snapshot_minidump.cc b/snapshot/minidump/thread_snapshot_minidump.cc
new file mode 100644
index 0000000..8a5a2fd
--- /dev/null
+++ b/snapshot/minidump/thread_snapshot_minidump.cc
@@ -0,0 +1,114 @@
+// 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.
+
+#include "snapshot/minidump/thread_snapshot_minidump.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include "base/stl_util.h"
+#include "minidump/minidump_context.h"
+
+namespace crashpad {
+namespace internal {
+
+ThreadSnapshotMinidump::ThreadSnapshotMinidump()
+    : ThreadSnapshot(),
+      minidump_thread_(),
+      context_(),
+      stack_(),
+      initialized_() {}
+
+ThreadSnapshotMinidump::~ThreadSnapshotMinidump() {}
+
+bool ThreadSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
+                                        RVA minidump_thread_rva,
+                                        CPUArchitecture arch) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  std::vector<unsigned char> minidump_context;
+
+  if (!file_reader->SeekSet(minidump_thread_rva)) {
+    return false;
+  }
+
+  if (!file_reader->ReadExactly(&minidump_thread_, sizeof(minidump_thread_))) {
+    return false;
+  }
+
+  if (!file_reader->SeekSet(minidump_thread_.ThreadContext.Rva)) {
+    return false;
+  }
+
+  minidump_context.resize(minidump_thread_.ThreadContext.DataSize);
+
+  if (!file_reader->ReadExactly(minidump_context.data(),
+                                minidump_context.size())) {
+    return false;
+  }
+
+  if (!context_.Initialize(arch, minidump_context)) {
+    return false;
+  }
+
+  RVA stack_info_location =
+      minidump_thread_rva + offsetof(MINIDUMP_THREAD, Stack);
+
+  if (!stack_.Initialize(file_reader, stack_info_location)) {
+    return false;
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+uint64_t ThreadSnapshotMinidump::ThreadID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_thread_.ThreadId;
+}
+
+int ThreadSnapshotMinidump::SuspendCount() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_thread_.SuspendCount;
+}
+
+uint64_t ThreadSnapshotMinidump::ThreadSpecificDataAddress() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_thread_.Teb;
+}
+
+int ThreadSnapshotMinidump::Priority() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return minidump_thread_.Priority;
+}
+
+const CPUContext* ThreadSnapshotMinidump::Context() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return context_.Get();
+}
+
+const MemorySnapshot* ThreadSnapshotMinidump::Stack() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &stack_;
+}
+
+std::vector<const MemorySnapshot*> ThreadSnapshotMinidump::ExtraMemory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  // This doesn't correspond to anything minidump can give us, with the
+  // exception of the BackingStore field in the MINIDUMP_THREAD_EX structure,
+  // which is only valid for IA-64.
+  return std::vector<const MemorySnapshot*>();
+}
+
+}  // namespace internal
+}  // namespace crashpad
diff --git a/snapshot/minidump/thread_snapshot_minidump.h b/snapshot/minidump/thread_snapshot_minidump.h
new file mode 100644
index 0000000..262b8da
--- /dev/null
+++ b/snapshot/minidump/thread_snapshot_minidump.h
@@ -0,0 +1,80 @@
+// 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_THREAD_SNAPSHOT_MINIDUMP_H_
+#define CRASHPAD_SNAPSHOT_MINIDUMP_THREAD_SNAPSHOT_MINIDUMP_H_
+
+#include <windows.h>
+
+#include "minidump/minidump_extensions.h"
+#include "snapshot/cpu_context.h"
+#include "snapshot/minidump/memory_snapshot_minidump.h"
+#include "snapshot/minidump/minidump_context_converter.h"
+#include "snapshot/thread_snapshot.h"
+#include "util/file/file_reader.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A ThreadSnapshot based on a thread in a minidump file.
+class ThreadSnapshotMinidump : public ThreadSnapshot {
+ public:
+  ThreadSnapshotMinidump();
+  ~ThreadSnapshotMinidump() override;
+
+  //! \brief Initializes the object.
+  //!
+  //! \param[in] file_reader A file reader corresponding to a minidump file.
+  //!     The file reader must support seeking.
+  //! \param[in] minidump_thread_rva The file offset in \a file_reader at which
+  //!     the thread’s MINIDUMP_THREAD structure is located.
+  //! \param[in] arch The architecture of the system this thread is running on.
+  //!     Used to decode CPU Context.
+  //!
+  //! \return `true` if the snapshot could be created, `false` otherwise with
+  //!     an appropriate message logged.
+  bool Initialize(FileReaderInterface* file_reader,
+                  RVA minidump_thread_rva,
+                  CPUArchitecture arch);
+
+  const CPUContext* Context() const override;
+  const MemorySnapshot* Stack() const override;
+  uint64_t ThreadID() const override;
+  int SuspendCount() const override;
+  int Priority() const override;
+  uint64_t ThreadSpecificDataAddress() const override;
+  std::vector<const MemorySnapshot*> ExtraMemory() const override;
+
+ private:
+  //! \brief Initializes the CPU Context
+  //!
+  //! \param[in] minidump_context the raw bytes of the context data from the
+  //!     minidump file.
+  //!
+  //! \return `true` if the context could be decoded without error.
+  bool InitializeContext(const std::vector<unsigned char>& minidump_context);
+
+  MINIDUMP_THREAD minidump_thread_;
+  MinidumpContextConverter context_;
+  MemorySnapshotMinidump stack_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotMinidump);
+};
+
+}  // namespace internal
+}  // namespace crashpad
+
+#endif  // CRASHPAD_SNAPSHOT_MINIDUMP_THREAD_SNAPSHOT_MINIDUMP_H_
diff --git a/snapshot/module_snapshot.h b/snapshot/module_snapshot.h
index eea7466..f7eb83c 100644
--- a/snapshot/module_snapshot.h
+++ b/snapshot/module_snapshot.h
@@ -146,6 +146,7 @@
   //! Windows with incremental linking. On other platforms \a age will always be
   //! `0`.
   //!
+  //! \sa BuildID()
   //! \sa DebugFileName()
   virtual void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const = 0;
 
@@ -159,6 +160,20 @@
   //! \sa UUIDAndAge()
   virtual std::string DebugFileName() const = 0;
 
+  //! \brief Returns the module’s build ID.
+  //!
+  //! On ELF platforms, the build ID is a variable-length byte stream that
+  //! identifies a library uniquely, and is usually used to look up its debug
+  //! symbols when stored separately. This will return an empty vector if it is
+  //! unsupported.
+  //!
+  //! BuildID() and UUIDAndAge() are never available in the same place. When
+  //! UUIDAndAge() is unavailable, it will be filled out with the contents of
+  //! BuildID() (either 0-padded or truncated) and age will be zero.
+  //!
+  //! \sa UUIDAndAge()
+  virtual std::vector<uint8_t> BuildID() const = 0;
+
   //! \brief Returns string annotations recorded in the module.
   //!
   //! This method retrieves annotations recorded in a module. These annotations
diff --git a/snapshot/posix/timezone.cc b/snapshot/posix/timezone.cc
index 47c6ecf..9451e11 100644
--- a/snapshot/posix/timezone.cc
+++ b/snapshot/posix/timezone.cc
@@ -18,6 +18,7 @@
 #include <time.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 
 namespace crashpad {
@@ -60,7 +61,7 @@
         {0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6,
          7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12};
     for (size_t index = 0;
-         index < arraysize(kMonthDeltas) && !found_transition;
+         index < base::size(kMonthDeltas) && !found_transition;
          ++index) {
       // Look at a day of each month at local noon. Set tm_isdst to -1 to avoid
       // giving mktime() any hints about whether to consider daylight saving
diff --git a/snapshot/posix/timezone_test.cc b/snapshot/posix/timezone_test.cc
index 814506f..b4405ba 100644
--- a/snapshot/posix/timezone_test.cc
+++ b/snapshot/posix/timezone_test.cc
@@ -22,6 +22,7 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
@@ -154,7 +155,7 @@
       {"UTC", false, 0, 0, "UTC", "UTC"},
   };
 
-  for (size_t index = 0; index < arraysize(kTestTimeZones); ++index) {
+  for (size_t index = 0; index < base::size(kTestTimeZones); ++index) {
     const auto& test_time_zone = kTestTimeZones[index];
     const char* tz = test_time_zone.tz;
     SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz));
diff --git a/snapshot/process_snapshot.h b/snapshot/process_snapshot.h
index 8d5520c..08d9f2b 100644
--- a/snapshot/process_snapshot.h
+++ b/snapshot/process_snapshot.h
@@ -24,6 +24,7 @@
 
 #include "snapshot/handle_snapshot.h"
 #include "util/misc/uuid.h"
+#include "util/process/process_id.h"
 
 namespace crashpad {
 
@@ -31,6 +32,7 @@
 class MemoryMapRegionSnapshot;
 class MemorySnapshot;
 class ModuleSnapshot;
+class ProcessMemory;
 class SystemSnapshot;
 class ThreadSnapshot;
 class UnloadedModuleSnapshot;
@@ -49,10 +51,10 @@
   virtual ~ProcessSnapshot() {}
 
   //! \brief Returns the snapshot process’ process ID.
-  virtual pid_t ProcessID() const = 0;
+  virtual crashpad::ProcessID ProcessID() const = 0;
 
   //! \brief Returns the snapshot process’ parent process’ process ID.
-  virtual pid_t ParentProcessID() const = 0;
+  virtual crashpad::ProcessID ParentProcessID() const = 0;
 
   //! \brief Returns the time that the snapshot was taken in \a snapshot_time.
   //!
@@ -193,6 +195,14 @@
   //!     are scoped to the lifetime of the ProcessSnapshot object that they
   //!     were obtained from.
   virtual std::vector<const MemorySnapshot*> ExtraMemory() const = 0;
+
+  //! \brief Returns a ProcessMemory object that allows accessing the process'
+  //!     memory directly.
+  //!
+  //! \return A ProcessMemory object. The caller does not take ownership of this
+  //!     object, it is scoped to the lifetime of the ProcessSnapshot object
+  //!     that it was obtained from.
+  virtual const ProcessMemory* Memory() const = 0;
 };
 
 }  // namespace crashpad
diff --git a/snapshot/sanitized/module_snapshot_sanitized.cc b/snapshot/sanitized/module_snapshot_sanitized.cc
index 94cb3d0..32b1357 100644
--- a/snapshot/sanitized/module_snapshot_sanitized.cc
+++ b/snapshot/sanitized/module_snapshot_sanitized.cc
@@ -81,6 +81,10 @@
   return snapshot_->DebugFileName();
 }
 
+std::vector<uint8_t> ModuleSnapshotSanitized::BuildID() const {
+  return snapshot_->BuildID();
+}
+
 std::vector<std::string> ModuleSnapshotSanitized::AnnotationsVector() const {
   // TODO(jperaza): If/when AnnotationsVector() begins to be used, determine
   // whether and how the content should be sanitized.
diff --git a/snapshot/sanitized/module_snapshot_sanitized.h b/snapshot/sanitized/module_snapshot_sanitized.h
index 4f375ce..bbe1f5f 100644
--- a/snapshot/sanitized/module_snapshot_sanitized.h
+++ b/snapshot/sanitized/module_snapshot_sanitized.h
@@ -56,6 +56,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
diff --git a/snapshot/sanitized/process_snapshot_sanitized.cc b/snapshot/sanitized/process_snapshot_sanitized.cc
index 76caeb5..722abac 100644
--- a/snapshot/sanitized/process_snapshot_sanitized.cc
+++ b/snapshot/sanitized/process_snapshot_sanitized.cc
@@ -84,12 +84,14 @@
 
 bool ProcessSnapshotSanitized::Initialize(
     const ProcessSnapshot* snapshot,
-    const std::vector<std::string>* annotations_whitelist,
+    std::unique_ptr<const std::vector<std::string>> annotations_whitelist,
+    std::unique_ptr<const std::vector<std::pair<VMAddress, VMAddress>>>
+        memory_range_whitelist,
     VMAddress target_module_address,
     bool sanitize_stacks) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
   snapshot_ = snapshot;
-  annotations_whitelist_ = annotations_whitelist;
+  annotations_whitelist_ = std::move(annotations_whitelist);
   sanitize_stacks_ = sanitize_stacks;
 
   if (target_module_address) {
@@ -140,7 +142,7 @@
   if (annotations_whitelist_) {
     for (const auto module : snapshot_->Modules()) {
       modules_.emplace_back(std::make_unique<internal::ModuleSnapshotSanitized>(
-          module, annotations_whitelist_));
+          module, annotations_whitelist_.get()));
     }
   }
 
@@ -157,16 +159,18 @@
     }
   }
 
+  process_memory_.Initialize(snapshot_->Memory(), memory_range_whitelist.get());
+
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
 
-pid_t ProcessSnapshotSanitized::ProcessID() const {
+crashpad::ProcessID ProcessSnapshotSanitized::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return snapshot_->ProcessID();
 }
 
-pid_t ProcessSnapshotSanitized::ParentProcessID() const {
+crashpad::ProcessID ProcessSnapshotSanitized::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return snapshot_->ParentProcessID();
 }
@@ -262,4 +266,9 @@
   return snapshot_->ExtraMemory();
 }
 
+const ProcessMemory* ProcessSnapshotSanitized::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return &process_memory_;
+}
+
 }  // namespace crashpad
diff --git a/snapshot/sanitized/process_snapshot_sanitized.h b/snapshot/sanitized/process_snapshot_sanitized.h
index f5cf5fa..2d38798 100644
--- a/snapshot/sanitized/process_snapshot_sanitized.h
+++ b/snapshot/sanitized/process_snapshot_sanitized.h
@@ -29,6 +29,8 @@
 #include "util/misc/address_types.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/misc/range_set.h"
+#include "util/process/process_id.h"
+#include "util/process/process_memory_sanitized.h"
 
 namespace crashpad {
 
@@ -48,6 +50,8 @@
   //! \param[in] annotations_whitelist A list of annotations names to allow to
   //!     be returned by AnnotationsSimpleMap() or from this object's module
   //!     snapshots. If `nullptr`, all annotations will be returned.
+  //! \param[in] memory_range_whitelist A list of memory ranges to allow to be
+  //!     accessible via Memory(), or `nullptr` to allow all ranges.
   //! \param[in] target_module_address An address in the target process'
   //!     address space within the bounds of a module to target. If the
   //!     crashing thread's context and stack do not contain any pointers into
@@ -59,15 +63,18 @@
   //!     internal::StackSnapshotSanitized.
   //! \return `false` if \a snapshot does not meet sanitization requirements and
   //!     should be filtered entirely. Otherwise `true`.
-  bool Initialize(const ProcessSnapshot* snapshot,
-                  const std::vector<std::string>* annotations_whitelist,
-                  VMAddress target_module_address,
-                  bool sanitize_stacks);
+  bool Initialize(
+      const ProcessSnapshot* snapshot,
+      std::unique_ptr<const std::vector<std::string>> annotations_whitelist,
+      std::unique_ptr<const std::vector<std::pair<VMAddress, VMAddress>>>
+          memory_range_whitelist,
+      VMAddress target_module_address,
+      bool sanitize_stacks);
 
   // ProcessSnapshot:
 
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -83,6 +90,7 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
   // Only used when annotations_whitelist_ != nullptr.
@@ -93,7 +101,8 @@
 
   RangeSet address_ranges_;
   const ProcessSnapshot* snapshot_;
-  const std::vector<std::string>* annotations_whitelist_;
+  ProcessMemorySanitized process_memory_;
+  std::unique_ptr<const std::vector<std::string>> annotations_whitelist_;
   bool sanitize_stacks_;
   InitializationStateDcheck initialized_;
 
diff --git a/snapshot/sanitized/process_snapshot_sanitized_test.cc b/snapshot/sanitized/process_snapshot_sanitized_test.cc
index cb413e3..5c5ff1a 100644
--- a/snapshot/sanitized/process_snapshot_sanitized_test.cc
+++ b/snapshot/sanitized/process_snapshot_sanitized_test.cc
@@ -145,6 +145,22 @@
   }
 }
 
+void ExpectProcessMemory(ProcessSnapshot* snapshot,
+                         VMAddress whitelisted_byte,
+                         bool sanitized) {
+  auto memory = snapshot->Memory();
+
+  char out;
+  EXPECT_TRUE(memory->Read(whitelisted_byte, 1, &out));
+
+  bool unwhitelisted_read = memory->Read(whitelisted_byte + 1, 1, &out);
+  if (sanitized) {
+    EXPECT_FALSE(unwhitelisted_read);
+  } else {
+    EXPECT_TRUE(unwhitelisted_read);
+  }
+}
+
 class StackSanitizationChecker : public MemorySnapshot::Delegate {
  public:
   StackSanitizationChecker() = default;
@@ -163,23 +179,9 @@
 
   // MemorySnapshot::Delegate
   bool MemorySnapshotDelegateRead(void* data, size_t size) override {
-#if defined(ADDRESS_SANITIZER) && (defined(OS_LINUX) || defined(OS_ANDROID))
-    // AddressSanitizer causes stack variables to be stored separately from the
-    // call stack.
-    auto addr_not_in_stack_range =
-        [](VMAddress addr, VMAddress stack_addr, VMSize stack_size) {
-          return addr < stack_addr || addr >= stack_addr + stack_size;
-        };
-    EXPECT_PRED3(addr_not_in_stack_range,
-                 addrs_.code_pointer_address,
-                 stack_->Address(),
-                 size);
-    EXPECT_PRED3(addr_not_in_stack_range,
-                 addrs_.string_address,
-                 stack_->Address(),
-                 size);
-    return true;
-#else
+    // AddressSanitizer with use-after-return detection causes stack variables
+    // to be allocated on the heap.
+#if !defined(ADDRESS_SANITIZER)
     size_t pointer_offset;
     if (!AssignIfInRange(&pointer_offset,
                          addrs_.code_pointer_address - stack_->Address())) {
@@ -209,8 +211,8 @@
     } else {
       EXPECT_STREQ(string, kSensitiveStackData);
     }
+#endif  // !ADDRESS_SANITIZER
     return true;
-#endif  // ADDRESS_SANITIZER && (OS_LINUX || OS_ANDROID)
   }
 
  private:
@@ -265,20 +267,34 @@
 
     ExpectAnnotations(&snapshot, /* sanitized= */ false);
     ExpectStackData(&snapshot, addrs, /* sanitized= */ false);
+    ExpectProcessMemory(&snapshot,
+                        addrs.string_address,
+                        /* sanitized= */ false);
 
-    std::vector<std::string> whitelist;
-    whitelist.push_back(kWhitelistedAnnotationName);
+    auto annotations_whitelist = std::make_unique<std::vector<std::string>>();
+    annotations_whitelist->push_back(kWhitelistedAnnotationName);
+
+    auto memory_ranges_whitelist =
+        std::make_unique<std::vector<std::pair<VMAddress, VMAddress>>>();
+    memory_ranges_whitelist->push_back(
+        std::make_pair(addrs.string_address, addrs.string_address + 1));
 
     ProcessSnapshotSanitized sanitized;
-    ASSERT_TRUE(sanitized.Initialize(
-        &snapshot, &whitelist, addrs.module_address, true));
+    ASSERT_TRUE(sanitized.Initialize(&snapshot,
+                                     std::move(annotations_whitelist),
+                                     std::move(memory_ranges_whitelist),
+                                     addrs.module_address,
+                                     true));
 
     ExpectAnnotations(&sanitized, /* sanitized= */ true);
     ExpectStackData(&sanitized, addrs, /* sanitized= */ true);
+    ExpectProcessMemory(&sanitized,
+                        addrs.string_address,
+                        /* sanitized= */ true);
 
     ProcessSnapshotSanitized screened_snapshot;
     EXPECT_FALSE(screened_snapshot.Initialize(
-        &snapshot, nullptr, addrs.non_module_address, false));
+        &snapshot, nullptr, nullptr, addrs.non_module_address, false));
   }
 
   DISALLOW_COPY_AND_ASSIGN(SanitizeTest);
diff --git a/snapshot/sanitized/sanitization_information.cc b/snapshot/sanitized/sanitization_information.cc
index f7be9b0..ff376a3 100644
--- a/snapshot/sanitized/sanitization_information.cc
+++ b/snapshot/sanitized/sanitization_information.cc
@@ -14,6 +14,8 @@
 
 #include "snapshot/sanitized/sanitization_information.h"
 
+#include <limits>
+
 #include "client/annotation.h"
 
 namespace crashpad {
@@ -21,9 +23,9 @@
 namespace {
 
 template <typename Pointer>
-bool ReadWhitelist(const ProcessMemoryRange& memory,
-                   VMAddress whitelist_address,
-                   std::vector<std::string>* whitelist) {
+bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory,
+                              VMAddress whitelist_address,
+                              std::vector<std::string>* whitelist) {
   if (!whitelist_address) {
     return true;
   }
@@ -53,9 +55,60 @@
 bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory,
                               VMAddress whitelist_address,
                               std::vector<std::string>* whitelist) {
-  return memory.Is64Bit()
-             ? ReadWhitelist<uint64_t>(memory, whitelist_address, whitelist)
-             : ReadWhitelist<uint32_t>(memory, whitelist_address, whitelist);
+  return memory.Is64Bit() ? ReadAnnotationsWhitelist<uint64_t>(
+                                memory, whitelist_address, whitelist)
+                          : ReadAnnotationsWhitelist<uint32_t>(
+                                memory, whitelist_address, whitelist);
+}
+
+bool ReadMemoryRangeWhitelist(
+    const ProcessMemoryRange& memory,
+    VMAddress whitelist_address,
+    std::vector<std::pair<VMAddress, VMAddress>>* whitelist) {
+  whitelist->clear();
+  if (!whitelist_address) {
+    return true;
+  }
+
+  SanitizationMemoryRangeWhitelist list;
+  if (!memory.Read(whitelist_address, sizeof(list), &list)) {
+    LOG(ERROR) << "Failed to read memory range whitelist.";
+    return false;
+  }
+
+  if (!list.size) {
+    return true;
+  }
+
+  // An upper bound of entries that we never expect to see more than.
+  constexpr size_t kMaxListSize = 256;
+  if (list.size > kMaxListSize) {
+    LOG(ERROR) << "Whitelist exceeded maximum, size=" << list.size;
+    return false;
+  }
+
+  SanitizationMemoryRangeWhitelist::Range ranges[list.size];
+  if (!memory.Read(list.entries, sizeof(ranges), &ranges)) {
+    LOG(ERROR) << "Failed to read memory range whitelist entries.";
+    return false;
+  }
+
+  const VMAddress vm_max = memory.Is64Bit()
+                               ? std::numeric_limits<uint64_t>::max()
+                               : std::numeric_limits<uint32_t>::max();
+  std::vector<std::pair<VMAddress, VMAddress>> local_whitelist;
+  for (size_t i = 0; i < list.size; i++) {
+    if (ranges[i].base > vm_max || ranges[i].length > vm_max - ranges[i].base) {
+      LOG(ERROR) << "Invalid memory range whitelist entry base="
+                 << ranges[i].base << " length=" << ranges[i].length;
+      return false;
+    }
+    local_whitelist.emplace_back(ranges[i].base,
+                                 ranges[i].base + ranges[i].length);
+  }
+
+  whitelist->swap(local_whitelist);
+  return true;
 }
 
 }  // namespace crashpad
diff --git a/snapshot/sanitized/sanitization_information.h b/snapshot/sanitized/sanitization_information.h
index 6a6d908..0717ba7 100644
--- a/snapshot/sanitized/sanitization_information.h
+++ b/snapshot/sanitized/sanitization_information.h
@@ -18,6 +18,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "util/misc/address_types.h"
@@ -45,10 +46,29 @@
   //!     if there is no target module.
   VMAddress target_module_address;
 
+  //! \brief The address in the client process' address space of a
+  //!     a \a SanitizationMemoryRangeWhitelist, a list of whitelisted address
+  //!     ranges allowed to be accessed by ProcessMemorySanitized. This value
+  //!     is 0 if no memory is allowed to be read using ProcessMemorySanitized.
+  VMAddress memory_range_whitelist_address;
+
   //! \brief Non-zero if stacks should be sanitized for possible PII.
   uint8_t sanitize_stacks;
 };
 
+//! \brief Describes a list of white listed memory ranges.
+struct SanitizationMemoryRangeWhitelist {
+  //! \brief Describes a range of memory.
+  struct Range {
+    VMAddress base;
+    VMSize length;
+  };
+
+  //! \brief Address of an array of |size| elements of type Range.
+  VMAddress entries;
+  VMSize size;
+};
+
 #pragma pack(pop)
 
 //! \brief Reads an annotations whitelist from another process.
@@ -63,6 +83,19 @@
                               VMAddress whitelist_address,
                               std::vector<std::string>* whitelist);
 
+//! \brief Reads a memory range whitelist from another process.
+//!
+//! \param[in] memory A memory reader for the target process.
+//! \param[in] whitelist_address The address in the target process' address
+//!     space of a nullptr terminated array of NUL-terminated strings.
+//! \param[out] whitelist A list of whitelisted memory regions, valid only if
+//!     this function returns `true`.
+//! \return `true` on success, `false` on failure with a message logged.
+bool ReadMemoryRangeWhitelist(
+    const ProcessMemoryRange& memory,
+    VMAddress whitelist_address,
+    std::vector<std::pair<VMAddress, VMAddress>>* whitelist);
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_SNAPSHOT_SANITIZED_SANITIZATION_INFORMATION_H_
diff --git a/snapshot/sanitized/sanitization_information_test.cc b/snapshot/sanitized/sanitization_information_test.cc
index a7bf826..e426f28 100644
--- a/snapshot/sanitized/sanitization_information_test.cc
+++ b/snapshot/sanitized/sanitization_information_test.cc
@@ -14,6 +14,7 @@
 
 #include "snapshot/sanitized/sanitization_information.h"
 
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "util/misc/from_pointer_cast.h"
@@ -59,8 +60,8 @@
 
 TEST_F(WhitelistTest, NonEmptyWhitelist) {
   ASSERT_TRUE(ReadWhitelist(kNonEmptyWhitelist));
-  ASSERT_EQ(whitelist_.size(), arraysize(kNonEmptyWhitelist) - 1);
-  for (size_t index = 0; index < arraysize(kNonEmptyWhitelist) - 1; ++index) {
+  ASSERT_EQ(whitelist_.size(), base::size(kNonEmptyWhitelist) - 1);
+  for (size_t index = 0; index < base::size(kNonEmptyWhitelist) - 1; ++index) {
     EXPECT_EQ(whitelist_[index], kNonEmptyWhitelist[index]);
   }
 }
diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp
index a78d21f..788ea3d 100644
--- a/snapshot/snapshot.gyp
+++ b/snapshot/snapshot.gyp
@@ -109,16 +109,27 @@
         'memory_snapshot_generic.h',
         'minidump/minidump_annotation_reader.cc',
         'minidump/minidump_annotation_reader.h',
+        'minidump/minidump_context_converter.cc',
+        'minidump/minidump_context_converter.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/exception_snapshot_minidump.cc',
+        'minidump/exception_snapshot_minidump.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',
         'minidump/process_snapshot_minidump.h',
+        'minidump/system_snapshot_minidump.cc',
+        'minidump/system_snapshot_minidump.h',
+        'minidump/thread_snapshot_minidump.cc',
+        'minidump/thread_snapshot_minidump.h',
         'module_snapshot.h',
         'posix/timezone.cc',
         'posix/timezone.h',
@@ -146,8 +157,6 @@
         'win/capture_memory_delegate_win.h',
         'win/memory_map_region_snapshot_win.cc',
         'win/memory_map_region_snapshot_win.h',
-        'win/memory_snapshot_win.cc',
-        'win/memory_snapshot_win.h',
         'win/module_snapshot_win.cc',
         'win/module_snapshot_win.h',
         'win/pe_image_annotations_reader.cc',
@@ -203,32 +212,5 @@
         }],
       ],
     },
-    {
-      'variables': {
-        'conditions': [
-          ['OS == "win"', {
-            'snapshot_api_target_type%': 'static_library',
-          }, {
-            # There are no source files except on Windows.
-            'snapshot_api_target_type%': 'none',
-          }],
-        ],
-      },
-      'target_name': 'crashpad_snapshot_api',
-      'type': '<(snapshot_api_target_type)',
-      'dependencies': [
-        'crashpad_snapshot',
-        '../compat/compat.gyp:crashpad_compat',
-        '../third_party/mini_chromium/mini_chromium.gyp:base',
-        '../util/util.gyp:crashpad_util',
-      ],
-      'include_dirs': [
-        '..',
-      ],
-      'sources': [
-        'api/module_annotations_win.cc',
-        'api/module_annotations_win.h',
-      ],
-    },
   ],
 }
diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp
index b795d92..cf7b8ae 100644
--- a/snapshot/snapshot_test.gyp
+++ b/snapshot/snapshot_test.gyp
@@ -57,7 +57,6 @@
         'crashpad_snapshot_test_module_large',
         'crashpad_snapshot_test_module_small',
         'snapshot.gyp:crashpad_snapshot',
-        'snapshot.gyp:crashpad_snapshot_api',
         '../client/client.gyp:crashpad_client',
         '../compat/compat.gyp:crashpad_compat',
         '../test/test.gyp:crashpad_gtest_main',
@@ -70,7 +69,6 @@
         '..',
       ],
       'sources': [
-        'api/module_annotations_win_test.cc',
         'cpu_context_test.cc',
         'memory_snapshot_test.cc',
         'crashpad_info_client_options_test.cc',
@@ -96,7 +94,7 @@
         'win/cpu_context_win_test.cc',
         'win/exception_snapshot_win_test.cc',
         'win/extra_memory_ranges_test.cc',
-        'win/pe_image_annotations_reader_test.cc',
+        'win/module_snapshot_win_test.cc',
         'win/pe_image_reader_test.cc',
         'win/process_reader_win_test.cc',
         'win/process_snapshot_win_test.cc',
diff --git a/snapshot/system_snapshot.h b/snapshot/system_snapshot.h
index a363c0c..d78cc23 100644
--- a/snapshot/system_snapshot.h
+++ b/snapshot/system_snapshot.h
@@ -50,6 +50,9 @@
 
     //! \brief Fuchsia.
     kOperatingSystemFuchsia,
+
+    //! \brief iOS.
+    kOperatingSystemIOS,
   };
 
   //! \brief A system’s daylight saving time status.
diff --git a/snapshot/test/test_cpu_context.cc b/snapshot/test/test_cpu_context.cc
index 1e785af..746e340 100644
--- a/snapshot/test/test_cpu_context.cc
+++ b/snapshot/test/test_cpu_context.cc
@@ -17,7 +17,7 @@
 #include <string.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 
 namespace crashpad {
 namespace test {
@@ -44,28 +44,28 @@
   fxsave->reserved_3 = static_cast<uint16_t>(value++);
   fxsave->mxcsr = value++;
   fxsave->mxcsr_mask = value++;
-  for (size_t st_mm_index = 0; st_mm_index < arraysize(fxsave->st_mm);
+  for (size_t st_mm_index = 0; st_mm_index < base::size(fxsave->st_mm);
        ++st_mm_index) {
-    for (size_t byte = 0; byte < arraysize(fxsave->st_mm[st_mm_index].st);
+    for (size_t byte = 0; byte < base::size(fxsave->st_mm[st_mm_index].st);
          ++byte) {
       fxsave->st_mm[st_mm_index].st[byte] = static_cast<uint8_t>(value++);
     }
     for (size_t byte = 0;
-         byte < arraysize(fxsave->st_mm[st_mm_index].st_reserved);
+         byte < base::size(fxsave->st_mm[st_mm_index].st_reserved);
          ++byte) {
       fxsave->st_mm[st_mm_index].st_reserved[byte] =
           static_cast<uint8_t>(value);
     }
   }
-  for (size_t xmm_index = 0; xmm_index < arraysize(fxsave->xmm); ++xmm_index) {
-    for (size_t byte = 0; byte < arraysize(fxsave->xmm[xmm_index]); ++byte) {
+  for (size_t xmm_index = 0; xmm_index < base::size(fxsave->xmm); ++xmm_index) {
+    for (size_t byte = 0; byte < base::size(fxsave->xmm[xmm_index]); ++byte) {
       fxsave->xmm[xmm_index][byte] = static_cast<uint8_t>(value++);
     }
   }
-  for (size_t byte = 0; byte < arraysize(fxsave->reserved_4); ++byte) {
+  for (size_t byte = 0; byte < base::size(fxsave->reserved_4); ++byte) {
     fxsave->reserved_4[byte] = static_cast<uint8_t>(value++);
   }
-  for (size_t byte = 0; byte < arraysize(fxsave->available); ++byte) {
+  for (size_t byte = 0; byte < base::size(fxsave->available); ++byte) {
     fxsave->available[byte] = static_cast<uint8_t>(value++);
   }
 
@@ -174,7 +174,7 @@
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(arm->regs); ++index) {
+  for (size_t index = 0; index < base::size(arm->regs); ++index) {
     arm->regs[index] = value++;
   }
   arm->fp = value++;
@@ -185,7 +185,7 @@
   arm->pc = value++;
   arm->cpsr = value++;
 
-  for (size_t index = 0; index < arraysize(arm->vfp_regs.vfp); ++index) {
+  for (size_t index = 0; index < base::size(arm->vfp_regs.vfp); ++index) {
     arm->vfp_regs.vfp[index] = value++;
   }
   arm->vfp_regs.fpscr = value++;
@@ -205,14 +205,14 @@
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(arm64->regs); ++index) {
+  for (size_t index = 0; index < base::size(arm64->regs); ++index) {
     arm64->regs[index] = value++;
   }
   arm64->sp = value++;
   arm64->pc = value++;
-  arm64->pstate = value++;
+  arm64->spsr = value++;
 
-  for (size_t index = 0; index < arraysize(arm64->fpsimd); ++index) {
+  for (size_t index = 0; index < base::size(arm64->fpsimd); ++index) {
     arm64->fpsimd[index].lo = value++;
     arm64->fpsimd[index].hi = value++;
   }
@@ -231,7 +231,7 @@
 
   uint32_t value = seed;
 
-  for (size_t index = 0; index < arraysize(mipsel->regs); ++index) {
+  for (size_t index = 0; index < base::size(mipsel->regs); ++index) {
     mipsel->regs[index] = value++;
   }
 
@@ -242,7 +242,7 @@
   mipsel->cp0_status = value++;
   mipsel->cp0_cause = value++;
 
-  for (size_t index = 0; index < arraysize(mipsel->fpregs.fregs); ++index) {
+  for (size_t index = 0; index < base::size(mipsel->fpregs.fregs); ++index) {
     mipsel->fpregs.fregs[index]._fp_fregs = static_cast<float>(value++);
   }
 
@@ -267,7 +267,7 @@
 
   uint64_t value = seed;
 
-  for (size_t index = 0; index < arraysize(mips64->regs); ++index) {
+  for (size_t index = 0; index < base::size(mips64->regs); ++index) {
     mips64->regs[index] = value++;
   }
 
@@ -278,7 +278,7 @@
   mips64->cp0_status = value++;
   mips64->cp0_cause = value++;
 
-  for (size_t index = 0; index < arraysize(mips64->fpregs.dregs); ++index) {
+  for (size_t index = 0; index < base::size(mips64->fpregs.dregs); ++index) {
     mips64->fpregs.dregs[index] = static_cast<double>(value++);
   }
 
diff --git a/snapshot/test/test_module_snapshot.cc b/snapshot/test/test_module_snapshot.cc
index 9141eda..ad0c2de 100644
--- a/snapshot/test/test_module_snapshot.cc
+++ b/snapshot/test/test_module_snapshot.cc
@@ -30,11 +30,9 @@
       debug_file_name_(),
       annotations_vector_(),
       annotations_simple_map_(),
-      extra_memory_ranges_() {
-}
+      extra_memory_ranges_() {}
 
-TestModuleSnapshot::~TestModuleSnapshot() {
-}
+TestModuleSnapshot::~TestModuleSnapshot() {}
 
 std::string TestModuleSnapshot::Name() const {
   return name_;
@@ -85,6 +83,10 @@
   return debug_file_name_;
 }
 
+std::vector<uint8_t> TestModuleSnapshot::BuildID() const {
+  return build_id_;
+}
+
 std::vector<std::string> TestModuleSnapshot::AnnotationsVector() const {
   return annotations_vector_;
 }
diff --git a/snapshot/test/test_module_snapshot.h b/snapshot/test/test_module_snapshot.h
index d1262fa..fb84aaf 100644
--- a/snapshot/test/test_module_snapshot.h
+++ b/snapshot/test/test_module_snapshot.h
@@ -64,6 +64,9 @@
     uuid_ = uuid;
     age_ = age;
   }
+  void SetBuildID(const std::vector<uint8_t>& build_id) {
+    build_id_ = build_id;
+  }
   void SetDebugFileName(const std::string& debug_file_name) {
     debug_file_name_ = debug_file_name;
   }
@@ -101,6 +104,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
@@ -117,6 +121,7 @@
   ModuleType module_type_;
   uint32_t age_;
   crashpad::UUID uuid_;
+  std::vector<uint8_t> build_id_;
   std::string debug_file_name_;
   std::vector<std::string> annotations_vector_;
   std::map<std::string, std::string> annotations_simple_map_;
diff --git a/snapshot/test/test_process_snapshot.cc b/snapshot/test/test_process_snapshot.cc
index c430fd9..42a049a 100644
--- a/snapshot/test/test_process_snapshot.cc
+++ b/snapshot/test/test_process_snapshot.cc
@@ -35,17 +35,18 @@
       exception_(),
       memory_map_(),
       handles_(),
-      extra_memory_() {
+      extra_memory_(),
+      process_memory_() {
 }
 
 TestProcessSnapshot::~TestProcessSnapshot() {
 }
 
-pid_t TestProcessSnapshot::ProcessID() const {
+crashpad::ProcessID TestProcessSnapshot::ProcessID() const {
   return process_id_;
 }
 
-pid_t TestProcessSnapshot::ParentProcessID() const {
+crashpad::ProcessID TestProcessSnapshot::ParentProcessID() const {
   return parent_process_id_;
 }
 
@@ -126,5 +127,9 @@
   return extra_memory;
 }
 
+const ProcessMemory* TestProcessSnapshot::Memory() const {
+  return process_memory_.get();
+}
+
 }  // namespace test
 }  // namespace crashpad
diff --git a/snapshot/test/test_process_snapshot.h b/snapshot/test/test_process_snapshot.h
index 5c63e12..5495cba 100644
--- a/snapshot/test/test_process_snapshot.h
+++ b/snapshot/test/test_process_snapshot.h
@@ -35,6 +35,8 @@
 #include "snapshot/thread_snapshot.h"
 #include "snapshot/unloaded_module_snapshot.h"
 #include "util/misc/uuid.h"
+#include "util/process/process_id.h"
+#include "util/process/process_memory.h"
 
 namespace crashpad {
 namespace test {
@@ -46,8 +48,10 @@
   TestProcessSnapshot();
   ~TestProcessSnapshot() override;
 
-  void SetProcessID(pid_t process_id) { process_id_ = process_id; }
-  void SetParentProcessID(pid_t parent_process_id) {
+  void SetProcessID(crashpad::ProcessID process_id) {
+    process_id_ = process_id;
+  }
+  void SetParentProcessID(crashpad::ProcessID parent_process_id) {
     parent_process_id_ = parent_process_id;
   }
   void SetSnapshotTime(const timeval& snapshot_time) {
@@ -134,10 +138,19 @@
     extra_memory_.push_back(std::move(extra_memory));
   }
 
+  //! \brief Add a process memory object to be returned by Memory().
+  //!
+  //! \param[in] process_memory The memory object that will be returned by
+  //!     Memory(). The TestProcessSnapshot object takes ownership of \a
+  //!     extra_memory.
+  void SetProcessMemory(std::unique_ptr<ProcessMemory> process_memory) {
+    process_memory_ = std::move(process_memory);
+  }
+
   // ProcessSnapshot:
 
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -153,10 +166,11 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
-  pid_t process_id_;
-  pid_t parent_process_id_;
+  crashpad::ProcessID process_id_;
+  crashpad::ProcessID parent_process_id_;
   timeval snapshot_time_;
   timeval process_start_time_;
   timeval process_cpu_user_time_;
@@ -172,6 +186,7 @@
   std::vector<std::unique_ptr<MemoryMapRegionSnapshot>> memory_map_;
   std::vector<HandleSnapshot> handles_;
   std::vector<std::unique_ptr<MemorySnapshot>> extra_memory_;
+  std::unique_ptr<ProcessMemory> process_memory_;
 
   DISALLOW_COPY_AND_ASSIGN(TestProcessSnapshot);
 };
diff --git a/snapshot/win/capture_memory_delegate_win.cc b/snapshot/win/capture_memory_delegate_win.cc
index fc1c608..ee5e5d6 100644
--- a/snapshot/win/capture_memory_delegate_win.cc
+++ b/snapshot/win/capture_memory_delegate_win.cc
@@ -17,7 +17,7 @@
 #include <utility>
 
 #include "base/numerics/safe_conversions.h"
-#include "snapshot/win/memory_snapshot_win.h"
+#include "snapshot/memory_snapshot_generic.h"
 
 namespace crashpad {
 namespace internal {
@@ -25,7 +25,7 @@
 CaptureMemoryDelegateWin::CaptureMemoryDelegateWin(
     ProcessReaderWin* process_reader,
     const ProcessReaderWin::Thread& thread,
-    std::vector<std::unique_ptr<MemorySnapshotWin>>* snapshots,
+    std::vector<std::unique_ptr<MemorySnapshotGeneric>>* snapshots,
     uint32_t* budget_remaining)
     : stack_(thread.stack_region_address, thread.stack_region_size),
       process_reader_(process_reader),
@@ -39,7 +39,8 @@
 bool CaptureMemoryDelegateWin::ReadMemory(uint64_t at,
                                           uint64_t num_bytes,
                                           void* into) const {
-  return process_reader_->ReadMemory(at, num_bytes, into);
+  return process_reader_->Memory()->Read(
+      at, base::checked_cast<size_t>(num_bytes), into);
 }
 
 std::vector<CheckedRange<uint64_t>> CaptureMemoryDelegateWin::GetReadableRanges(
@@ -56,9 +57,9 @@
     return;
   if (budget_remaining_ && *budget_remaining_ == 0)
     return;
-  snapshots_->push_back(std::make_unique<internal::MemorySnapshotWin>());
-  internal::MemorySnapshotWin* snapshot = snapshots_->back().get();
-  snapshot->Initialize(process_reader_, range.base(), range.size());
+  snapshots_->push_back(std::make_unique<internal::MemorySnapshotGeneric>());
+  internal::MemorySnapshotGeneric* snapshot = snapshots_->back().get();
+  snapshot->Initialize(process_reader_->Memory(), range.base(), range.size());
   if (budget_remaining_) {
     if (!base::IsValueInRangeForNumericType<int64_t>(range.size())) {
       *budget_remaining_ = 0;
diff --git a/snapshot/win/capture_memory_delegate_win.h b/snapshot/win/capture_memory_delegate_win.h
index 175b4c9..85c3db9 100644
--- a/snapshot/win/capture_memory_delegate_win.h
+++ b/snapshot/win/capture_memory_delegate_win.h
@@ -28,7 +28,7 @@
 namespace crashpad {
 namespace internal {
 
-class MemorySnapshotWin;
+class MemorySnapshotGeneric;
 
 class CaptureMemoryDelegateWin : public CaptureMemory::Delegate {
  public:
@@ -38,15 +38,15 @@
   //! \param[in] thread The thread being inspected. Memory ranges overlapping
   //!     this thread's stack will be ignored on the assumption that they're
   //!     already captured elsewhere.
-  //! \param[in] snapshots A vector of MemorySnapshotWin to which the captured
-  //!     memory will be added.
+  //! \param[in] snapshots A vector of MemorySnapshotGeneric to which the
+  //!     captured memory will be added.
   //! \param[in] budget_remaining If non-null, a pointer to the remaining number
   //!     of bytes to capture. If this is `0`, no further memory will be
   //!     captured.
   CaptureMemoryDelegateWin(
       ProcessReaderWin* process_reader,
       const ProcessReaderWin::Thread& thread,
-      std::vector<std::unique_ptr<MemorySnapshotWin>>* snapshots,
+      std::vector<std::unique_ptr<MemorySnapshotGeneric>>* snapshots,
       uint32_t* budget_remaining);
 
   // MemoryCaptureDelegate:
@@ -60,7 +60,7 @@
  private:
   CheckedRange<uint64_t, uint64_t> stack_;
   ProcessReaderWin* process_reader_;  // weak
-  std::vector<std::unique_ptr<MemorySnapshotWin>>* snapshots_;  // weak
+  std::vector<std::unique_ptr<MemorySnapshotGeneric>>* snapshots_;  // weak
   uint32_t* budget_remaining_;
 };
 
diff --git a/snapshot/win/cpu_context_win.cc b/snapshot/win/cpu_context_win.cc
index 1a76c6d..db2e803 100644
--- a/snapshot/win/cpu_context_win.cc
+++ b/snapshot/win/cpu_context_win.cc
@@ -126,7 +126,13 @@
 
 }  // namespace
 
-#if defined(ARCH_CPU_64_BITS)
+#if defined(ARCH_CPU_X86)
+
+void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
+  CommonInitializeX86Context(context, out);
+}
+
+#elif defined(ARCH_CPU_X86_64)
 
 void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
   CommonInitializeX86Context(context, out);
@@ -192,12 +198,36 @@
   }
 }
 
-#else  // ARCH_CPU_64_BITS
+#elif defined(ARCH_CPU_ARM64)
 
-void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
-  CommonInitializeX86Context(context, out);
+void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out) {
+  memset(out, 0, sizeof(*out));
+
+  LOG_IF(ERROR, !HasContextPart(context, CONTEXT_ARM64)) << "non-arm64 context";
+
+  if (HasContextPart(context, CONTEXT_CONTROL)) {
+    out->spsr = context.Cpsr;
+    out->pc = context.Pc;
+    out->regs[30] = context.Lr;
+    out->sp = context.Sp;
+    out->regs[29] = context.Fp;
+  }
+
+  if (HasContextPart(context, CONTEXT_INTEGER)) {
+    memcpy(&out->regs[0], &context.X0, 18 * sizeof(context.X0));
+    // Don't copy x18 which is reserved as platform register.
+    memcpy(&out->regs[19], &context.X19, 10 * sizeof(context.X0));
+  }
+
+  if (HasContextPart(context, CONTEXT_FLOATING_POINT)) {
+    static_assert(sizeof(out->fpsimd) == sizeof(context.V),
+                  "types must be equivalent");
+    memcpy(&out->fpsimd, &context.V, sizeof(out->fpsimd));
+  }
 }
 
-#endif  // ARCH_CPU_64_BITS
+#else
+#error Unsupported Windows Arch
+#endif  // ARCH_CPU_X86
 
 }  // namespace crashpad
diff --git a/snapshot/win/cpu_context_win.h b/snapshot/win/cpu_context_win.h
index a384ccb..9718f49 100644
--- a/snapshot/win/cpu_context_win.h
+++ b/snapshot/win/cpu_context_win.h
@@ -23,8 +23,17 @@
 
 struct CPUContextX86;
 struct CPUContextX86_64;
+struct CPUContextARM64;
 
-#if defined(ARCH_CPU_64_BITS) || DOXYGEN
+#if defined(ARCH_CPU_X86) || DOXYGEN
+
+//! \brief Initializes a CPUContextX86 structure from a native context structure
+//!     on Windows.
+void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
+
+#endif  // ARCH_CPU_X86
+
+#if defined(ARCH_CPU_X86_64) || DOXYGEN
 
 //! \brief Initializes a CPUContextX86 structure from a native context structure
 //!     on Windows.
@@ -34,13 +43,15 @@
 //!     structure on Windows.
 void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out);
 
-#else  // ARCH_CPU_64_BITS
+#endif  // ARCH_CPU_X86_64
 
-//! \brief Initializes a CPUContextX86 structure from a native context structure
-//!     on Windows.
-void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
+#if defined(ARCH_CPU_ARM64) || DOXYGEN
 
-#endif  // ARCH_CPU_64_BITS
+//! \brief Initializes a CPUContextARM64 structure from a native context
+//!     structure on Windows.
+void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out);
+
+#endif  // ARCH_CPU_ARM64
 
 }  // namespace crashpad
 
diff --git a/snapshot/win/cpu_context_win_test.cc b/snapshot/win/cpu_context_win_test.cc
index aa2afe9..e63f04e 100644
--- a/snapshot/win/cpu_context_win_test.cc
+++ b/snapshot/win/cpu_context_win_test.cc
@@ -16,11 +16,11 @@
 
 #include <windows.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
-#include "test/hex_string.h"
 #include "snapshot/cpu_context.h"
+#include "test/hex_string.h"
 
 namespace crashpad {
 namespace test {
@@ -87,13 +87,13 @@
     for (size_t st_mm = 0; st_mm < 7; ++st_mm) {
       EXPECT_EQ(
           BytesToHexString(cpu_context_x86.fxsave.st_mm[st_mm].st,
-                           arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st)),
-          std::string(arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st) * 2,
+                           base::size(cpu_context_x86.fxsave.st_mm[st_mm].st)),
+          std::string(base::size(cpu_context_x86.fxsave.st_mm[st_mm].st) * 2,
                       '0'))
           << "st_mm " << st_mm;
     }
     EXPECT_EQ(BytesToHexString(cpu_context_x86.fxsave.st_mm[7].st,
-                               arraysize(cpu_context_x86.fxsave.st_mm[7].st)),
+                               base::size(cpu_context_x86.fxsave.st_mm[7].st)),
               "0000000000000080ff7f");
 
     EXPECT_EQ(cpu_context_x86.dr0, 3u);
diff --git a/snapshot/win/crashpad_snapshot_test_crashing_child.cc b/snapshot/win/crashpad_snapshot_test_crashing_child.cc
index 759cc13..cc1369e 100644
--- a/snapshot/win/crashpad_snapshot_test_crashing_child.cc
+++ b/snapshot/win/crashpad_snapshot_test_crashing_child.cc
@@ -16,10 +16,10 @@
 #include <windows.h>
 
 #include "base/logging.h"
-#include "build/build_config.h"
 #include "client/crashpad_client.h"
 #include "util/misc/capture_context.h"
 #include "util/win/address_types.h"
+#include "util/win/context_wrappers.h"
 
 int wmain(int argc, wchar_t* argv[]) {
   CHECK_EQ(argc, 2);
@@ -32,11 +32,9 @@
 
   CONTEXT context;
   crashpad::CaptureContext(&context);
-#if defined(ARCH_CPU_64_BITS)
-  crashpad::WinVMAddress break_address = context.Rip;
-#else
-  crashpad::WinVMAddress break_address = context.Eip;
-#endif
+  crashpad::WinVMAddress break_address =
+      reinterpret_cast<crashpad::WinVMAddress>(
+          crashpad::ProgramCounterFromCONTEXT(&context));
 
   // This does not used CheckedWriteFile() because at high optimization
   // settings, a lot of logging code can be inlined, causing there to be a large
diff --git a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc
index e2c524a..cc441a4 100644
--- a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc
+++ b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc
@@ -20,6 +20,7 @@
 #include "client/simulate_crash.h"
 #include "util/misc/capture_context.h"
 #include "util/win/address_types.h"
+#include "util/win/context_wrappers.h"
 
 int wmain(int argc, wchar_t* argv[]) {
   CHECK_EQ(argc, 2);
@@ -32,11 +33,9 @@
 
   CONTEXT context;
   crashpad::CaptureContext(&context);
-#if defined(ARCH_CPU_64_BITS)
-  crashpad::WinVMAddress break_address = context.Rip;
-#else
-  crashpad::WinVMAddress break_address = context.Eip;
-#endif
+  crashpad::WinVMAddress break_address =
+      reinterpret_cast<crashpad::WinVMAddress>(
+          crashpad::ProgramCounterFromCONTEXT(&context));
 
   // This does not used CheckedWriteFile() because at high optimization
   // settings, a lot of logging code can be inlined, causing there to be a large
diff --git a/snapshot/win/crashpad_snapshot_test_image_reader.cc b/snapshot/win/crashpad_snapshot_test_image_reader.cc
index 4ebd98e..8bccbd6 100644
--- a/snapshot/win/crashpad_snapshot_test_image_reader.cc
+++ b/snapshot/win/crashpad_snapshot_test_image_reader.cc
@@ -15,6 +15,7 @@
 #include <windows.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "client/crashpad_info.h"
 #include "util/file/file_io.h"
 #include "util/synchronization/semaphore.h"
@@ -28,7 +29,7 @@
 
   // Allocate a bunch of pointers to things on the stack.
   int* pointers[1000];
-  for (size_t i = 0; i < arraysize(pointers); ++i) {
+  for (size_t i = 0; i < base::size(pointers); ++i) {
     pointers[i] = new int[2048];
   }
 
@@ -52,7 +53,7 @@
   // verify the cap on pointed-to memory.
   crashpad::Semaphore semaphore(0);
   crashpad::ScopedKernelHANDLE threads[100];
-  for (size_t i = 0; i < arraysize(threads); ++i) {
+  for (size_t i = 0; i < base::size(threads); ++i) {
     threads[i].reset(CreateThread(nullptr,
                                   0,
                                   &LotsOfReferencesThreadProc,
@@ -65,7 +66,7 @@
     }
   }
 
-  for (size_t i = 0; i < arraysize(threads); ++i) {
+  for (size_t i = 0; i < base::size(threads); ++i) {
     semaphore.Wait();
   }
 
diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py
index fd602fb..b1919ad 100755
--- a/snapshot/win/end_to_end_test.py
+++ b/snapshot/win/end_to_end_test.py
@@ -29,479 +29,452 @@
 import win32pipe
 import winerror
 
-
 g_temp_dirs = []
 g_had_failures = False
 
 
 def MakeTempDir():
-  global g_temp_dirs
-  new_dir = tempfile.mkdtemp()
-  g_temp_dirs.append(new_dir)
-  return new_dir
+    global g_temp_dirs
+    new_dir = tempfile.mkdtemp()
+    g_temp_dirs.append(new_dir)
+    return new_dir
 
 
 def CleanUpTempDirs():
-  global g_temp_dirs
-  for d in g_temp_dirs:
-    subprocess.call(['rmdir', '/s', '/q', d], shell=True)
+    global g_temp_dirs
+    for d in g_temp_dirs:
+        subprocess.call(['rmdir', '/s', '/q', d], shell=True)
 
 
 def FindInstalledWindowsApplication(app_path):
-  search_paths = [os.getenv('PROGRAMFILES(X86)'),
-                  os.getenv('PROGRAMFILES'),
-                  os.getenv('PROGRAMW6432'),
-                  os.getenv('LOCALAPPDATA')]
-  search_paths += os.getenv('PATH', '').split(os.pathsep)
+    search_paths = [
+        os.getenv('PROGRAMFILES(X86)'),
+        os.getenv('PROGRAMFILES'),
+        os.getenv('PROGRAMW6432'),
+        os.getenv('LOCALAPPDATA')
+    ]
+    search_paths += os.getenv('PATH', '').split(os.pathsep)
 
-  for search_path in search_paths:
-    if not search_path:
-      continue
-    path = os.path.join(search_path, app_path)
-    if os.path.isfile(path):
-      return path
+    for search_path in search_paths:
+        if not search_path:
+            continue
+        path = os.path.join(search_path, app_path)
+        if os.path.isfile(path):
+            return path
 
-  return None
-
-
-def GetCdbPath():
-  """Search in some reasonable places to find cdb.exe. Searches x64 before x86
-  and newer versions before older versions.
-  """
-  possible_paths = (
-      os.path.join('Windows Kits', '10', 'Debuggers', 'x64'),
-      os.path.join('Windows Kits', '10', 'Debuggers', 'x86'),
-      os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'),
-      os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'),
-      os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
-      os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
-      'Debugging Tools For Windows (x64)',
-      'Debugging Tools For Windows (x86)',
-      'Debugging Tools For Windows',)
-  for possible_path in possible_paths:
-    app_path = os.path.join(possible_path, 'cdb.exe')
-    app_path = FindInstalledWindowsApplication(app_path)
-    if app_path:
-      return app_path
-  return None
-
-
-def NamedPipeExistsAndReady(pipe_name):
-  """Returns False if pipe_name does not exist. If pipe_name does exist, blocks
-  until the pipe is ready to service clients, and then returns True.
-
-  This is used as a drop-in replacement for os.path.exists() and os.access() to
-  test for the pipe's existence. Both of those calls tickle the pipe in a way
-  that appears to the server to be a client connecting, triggering error
-  messages when no data is received.
-
-  Although this function only needs to test pipe existence (waiting for
-  CreateNamedPipe()), it actually winds up testing pipe readiness
-  (waiting for ConnectNamedPipe()). This is unnecessary but harmless.
-  """
-  try:
-    win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER)
-  except pywintypes.error as e:
-    if e[0] == winerror.ERROR_FILE_NOT_FOUND:
-      return False
-    raise
-  return True
-
-
-def GetDumpFromProgram(
-    out_dir, pipe_name, executable_name, expect_exit_code, *args):
-  """Initialize a crash database, and run |executable_name| connecting to a
-  crash handler. If pipe_name is set, crashpad_handler will be started first. If
-  pipe_name is empty, the executable is responsible for starting
-  crashpad_handler. *args will be passed after other arguments to
-  executable_name. If the child process does not exit with |expect_exit_code|,
-  an exception will be raised. Returns the path to the minidump generated by
-  crashpad_handler for further testing.
-  """
-  test_database = MakeTempDir()
-  handler = None
-
-  try:
-    subprocess.check_call(
-        [os.path.join(out_dir, 'crashpad_database_util.exe'), '--create',
-         '--database=' + test_database])
-
-    if pipe_name is not None:
-      handler = subprocess.Popen([
-          os.path.join(out_dir, 'crashpad_handler.com'),
-          '--pipe-name=' + pipe_name,
-          '--database=' + test_database
-      ])
-
-      # Wait until the server is ready.
-      printed = False
-      while not NamedPipeExistsAndReady(pipe_name):
-        if not printed:
-          print('Waiting for crashpad_handler to be ready...')
-          printed = True
-        time.sleep(0.001)
-
-      command = [os.path.join(out_dir, executable_name), pipe_name] + list(args)
-    else:
-      command = ([os.path.join(out_dir, executable_name),
-                  os.path.join(out_dir, 'crashpad_handler.com'),
-                  test_database] +
-                 list(args))
-    print('Running %s' % os.path.basename(command[0]))
-    exit_code = subprocess.call(command)
-    if exit_code != expect_exit_code:
-      raise subprocess.CalledProcessError(exit_code, executable_name)
-
-    out = subprocess.check_output([
-        os.path.join(out_dir, 'crashpad_database_util.exe'),
-        '--database=' + test_database,
-        '--show-pending-reports',
-        '--show-all-report-info',
-    ])
-    for line in out.splitlines():
-      if line.strip().startswith('Path:'):
-        return line.partition(':')[2].strip()
-  finally:
-    if handler:
-      handler.kill()
-
-
-def GetDumpFromCrashyProgram(out_dir, pipe_name):
-  return GetDumpFromProgram(out_dir,
-                            pipe_name,
-                            'crashy_program.exe',
-                            win32con.EXCEPTION_ACCESS_VIOLATION)
-
-
-def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
-  return GetDumpFromProgram(
-      out_dir, pipe_name, 'crash_other_program.exe', 0, *args)
-
-
-def GetDumpFromSignal(out_dir, pipe_name, *args):
-  STATUS_FATAL_APP_EXIT = 0x40000015  # Not known by win32con.
-  return GetDumpFromProgram(out_dir,
-                            pipe_name,
-                            'crashy_signal.exe',
-                            STATUS_FATAL_APP_EXIT,
-                            *args)
-
-
-def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
-  return GetDumpFromProgram(out_dir,
-                            pipe_name,
-                            'self_destroying_program.exe',
-                            win32con.EXCEPTION_BREAKPOINT)
-
-
-def GetDumpFromZ7Program(out_dir, pipe_name):
-  return GetDumpFromProgram(out_dir,
-                            pipe_name,
-                            'crashy_z7_loader.exe',
-                            win32con.EXCEPTION_ACCESS_VIOLATION)
-
-
-class CdbRun(object):
-  """Run cdb.exe passing it a cdb command and capturing the output.
-  `Check()` searches for regex patterns in sequence allowing verification of
-  expected output.
-  """
-
-  def __init__(self, cdb_path, dump_path, command):
-    # Run a command line that loads the dump, runs the specified cdb command,
-    # and then quits, and capturing stdout.
-    self.out = subprocess.check_output([
-        cdb_path,
-        '-z', dump_path,
-        '-c', command + ';q'
-    ])
-
-  def Check(self, pattern, message, re_flags=0):
-    match_obj = re.search(pattern, self.out, re_flags)
-    if match_obj:
-      # Matched. Consume up to end of match.
-      self.out = self.out[match_obj.end(0):]
-      print('ok - %s' % message)
-      sys.stdout.flush()
-    else:
-      print('-' * 80, file=sys.stderr)
-      print('FAILED - %s' % message, file=sys.stderr)
-      print('-' * 80, file=sys.stderr)
-      print('did not match:\n  %s' % pattern, file=sys.stderr)
-      print('-' * 80, file=sys.stderr)
-      print('remaining output was:\n  %s' % self.out, file=sys.stderr)
-      print('-' * 80, file=sys.stderr)
-      sys.stderr.flush()
-      global g_had_failures
-      g_had_failures = True
-
-  def Find(self, pattern, re_flags=0):
-    match_obj = re.search(pattern, self.out, re_flags)
-    if match_obj:
-      # Matched. Consume up to end of match.
-      self.out = self.out[match_obj.end(0):]
-      return match_obj
     return None
 
 
-def RunTests(cdb_path,
-             dump_path,
-             start_handler_dump_path,
-             destroyed_dump_path,
-             z7_dump_path,
-             other_program_path,
-             other_program_no_exception_path,
-             sigabrt_main_path,
-             sigabrt_background_path,
-             pipe_name):
-  """Runs various tests in sequence. Runs a new cdb instance on the dump for
-  each block of tests to reduce the chances that output from one command is
-  confused for output from another.
-  """
-  out = CdbRun(cdb_path, dump_path, '.ecxr')
-  out.Check('This dump file has an exception of interest stored in it',
-            'captured exception')
+def GetCdbPath():
+    """Search in some reasonable places to find cdb.exe. Searches x64 before x86
+    and newer versions before older versions.
+    """
+    possible_paths = (
+        os.path.join('Windows Kits', '10', 'Debuggers', 'x64'),
+        os.path.join('Windows Kits', '10', 'Debuggers', 'x86'),
+        os.path.join('Windows Kits', '8.1', 'Debuggers', 'x64'),
+        os.path.join('Windows Kits', '8.1', 'Debuggers', 'x86'),
+        os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
+        os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
+        'Debugging Tools For Windows (x64)',
+        'Debugging Tools For Windows (x86)',
+        'Debugging Tools For Windows',
+    )
+    for possible_path in possible_paths:
+        app_path = os.path.join(possible_path, 'cdb.exe')
+        app_path = FindInstalledWindowsApplication(app_path)
+        if app_path:
+            return app_path
+    return None
 
-  # When SomeCrashyFunction is inlined, cdb doesn't demangle its namespace as
-  # "`anonymous namespace'" and instead gives the decorated form.
-  out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
-                'SomeCrashyFunction',
-            'exception at correct location')
 
-  out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr')
-  out.Check('This dump file has an exception of interest stored in it',
-            'captured exception (using StartHandler())')
-  out.Check('crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
-                'SomeCrashyFunction',
-            'exception at correct location (using StartHandler())')
+def NamedPipeExistsAndReady(pipe_name):
+    """Returns False if pipe_name does not exist. If pipe_name does exist,
+    blocks until the pipe is ready to service clients, and then returns True.
 
-  out = CdbRun(cdb_path, dump_path, '!peb')
-  out.Check(r'PEB at', 'found the PEB')
-  out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved')
-  out.Check(r'Base TimeStamp                     Module', 'module list present')
-  pipe_name_escaped = pipe_name.replace('\\', '\\\\')
-  out.Check(r'CommandLine: *\'.*crashy_program\.exe *' + pipe_name_escaped,
-            'some PEB data is correct')
-  out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured',
-            re.IGNORECASE)
+    This is used as a drop-in replacement for os.path.exists() and os.access()
+    to test for the pipe's existence. Both of those calls tickle the pipe in a
+    way that appears to the server to be a client connecting, triggering error
+    messages when no data is received.
 
-  out = CdbRun(cdb_path, dump_path, '?? @$peb->ProcessParameters')
-  out.Check(r' ImagePathName *: _UNICODE_STRING ".*\\crashy_program\.exe"',
-            'PEB->ProcessParameters.ImagePathName string captured')
-  out.Check(' DesktopInfo *: '
-            '_UNICODE_STRING "(?!--- memory read error at address ).*"',
-            'PEB->ProcessParameters.DesktopInfo string captured')
+    Although this function only needs to test pipe existence (waiting for
+    CreateNamedPipe()), it actually winds up testing pipe readiness (waiting for
+    ConnectNamedPipe()). This is unnecessary but harmless.
+    """
+    try:
+        win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER)
+    except pywintypes.error as e:
+        if e[0] == winerror.ERROR_FILE_NOT_FOUND:
+            return False
+        raise
+    return True
 
-  out = CdbRun(cdb_path, dump_path, '!teb')
-  out.Check(r'TEB at', 'found the TEB')
-  out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data')
-  out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue')
 
-  out = CdbRun(cdb_path, dump_path, '!gle')
-  out.Check('LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the '
-            'file specified.', '!gle gets last error')
-  out.Check('LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found}  The '
-            'file %hs does not exist.', '!gle gets last ntstatus')
+def GetDumpFromProgram(out_dir, pipe_name, executable_name, expect_exit_code,
+                       *args):
+    """Initialize a crash database, and run |executable_name| connecting to a
+    crash handler. If pipe_name is set, crashpad_handler will be started first.
+    If pipe_name is empty, the executable is responsible for starting
+    crashpad_handler. *args will be passed after other arguments to
+    executable_name. If the child process does not exit with |expect_exit_code|,
+    an exception will be raised. Returns the path to the minidump generated by
+    crashpad_handler for further testing.
+    """
+    test_database = MakeTempDir()
+    handler = None
 
-  if False:
-    # TODO(scottmg): Re-enable when we grab ntdll!RtlCriticalSectionList.
-    out = CdbRun(cdb_path, dump_path, '!locks')
-    out.Check(r'CritSec crashy_program!crashpad::`anonymous namespace\'::'
-              r'g_test_critical_section', 'lock was captured')
-    if platform.win32_ver()[0] != '7':
-      # We can't allocate CRITICAL_SECTIONs with .DebugInfo on Win 7.
-      out.Check(r'\*\*\* Locked', 'lock debug info was captured, and is locked')
+    try:
+        subprocess.check_call([
+            os.path.join(out_dir, 'crashpad_database_util.exe'), '--create',
+            '--database=' + test_database
+        ])
 
-  out = CdbRun(cdb_path, dump_path, '!handle')
-  out.Check(r'\d+ Handles', 'captured handles')
-  out.Check(r'Event\s+\d+', 'capture some event handles')
-  out.Check(r'File\s+\d+', 'capture some file handles')
+        if pipe_name is not None:
+            handler = subprocess.Popen([
+                os.path.join(out_dir, 'crashpad_handler.com'),
+                '--pipe-name=' + pipe_name, '--database=' + test_database
+            ])
 
-  out = CdbRun(cdb_path, dump_path, 'lm')
-  out.Check(r'Unloaded modules:', 'captured some unloaded modules')
-  out.Check(r'lz32\.dll', 'found expected unloaded module lz32')
-  out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror')
+            # Wait until the server is ready.
+            printed = False
+            while not NamedPipeExistsAndReady(pipe_name):
+                if not printed:
+                    print('Waiting for crashpad_handler to be ready...')
+                    printed = True
+                time.sleep(0.001)
 
-  out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2')
-  out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved')
-  out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE)
+            command = [os.path.join(out_dir, executable_name), pipe_name
+                      ] + list(args)
+        else:
+            command = ([
+                os.path.join(out_dir, executable_name),
+                os.path.join(out_dir, 'crashpad_handler.com'), test_database
+            ] + list(args))
+        print('Running %s' % os.path.basename(command[0]))
+        exit_code = subprocess.call(command)
+        if exit_code != expect_exit_code:
+            raise subprocess.CalledProcessError(exit_code, executable_name)
 
-  # Check that there is no stack trace in the self-destroyed process. Confirm
-  # that the top is where we expect it (that's based only on IP), but subsequent
-  # stack entries will not be available. This confirms that we have a mostly
-  # valid dump, but that the stack was omitted.
-  out.Check(r'self_destroying_program!crashpad::`anonymous namespace\'::'
-                r'FreeOwnStackAndBreak.*\nquit:',
-            'at correct location, no additional stack entries')
+        out = subprocess.check_output([
+            os.path.join(out_dir, 'crashpad_database_util.exe'),
+            '--database=' + test_database,
+            '--show-pending-reports',
+            '--show-all-report-info',
+        ])
+        for line in out.splitlines():
+            if line.strip().startswith('Path:'):
+                return line.partition(':')[2].strip()
+    finally:
+        if handler:
+            handler.kill()
 
-  # Dump memory pointed to be EDI on the background suspended thread. We don't
-  # know the index of the thread because the system may have started other
-  # threads, so first do a run to extract the thread index that's suspended, and
-  # then another run to dump the data pointed to by EDI for that thread.
-  out = CdbRun(cdb_path, dump_path, '.ecxr;~')
-  match_obj = out.Find(r'(\d+)\s+Id: [0-9a-f.]+ Suspend: 1 Teb:')
-  if match_obj:
-    thread = match_obj.group(1)
-    out = CdbRun(cdb_path, dump_path, '.ecxr;~' + thread + 's;db /c14 edi')
-  out.Check(r'63 62 61 60 5f 5e 5d 5c-5b 5a 59 58 57 56 55 54 53 52 51 50',
-            'data pointed to by registers captured')
 
-  # Move up one stack frame after jumping to the exception, and examine memory.
-  out = CdbRun(cdb_path, dump_path,
-               '.ecxr; .f+; dd /c100 poi(offset_pointer)-20')
-  out.Check(r'80000078 00000079 8000007a 0000007b 8000007c 0000007d 8000007e '
-            r'0000007f 80000080 00000081 80000082 00000083 80000084 00000085 '
-            r'80000086 00000087 80000088 00000089 8000008a 0000008b 8000008c '
-            r'0000008d 8000008e 0000008f 80000090 00000091 80000092 00000093 '
-            r'80000094 00000095 80000096 00000097',
-            'data pointed to by stack captured')
+def GetDumpFromCrashyProgram(out_dir, pipe_name):
+    return GetDumpFromProgram(out_dir, pipe_name, 'crashy_program.exe',
+                              win32con.EXCEPTION_ACCESS_VIOLATION)
 
-  # Attempt to retrieve the value of g_extra_memory_pointer (by name), and then
-  # examine the memory at which it points. Both should have been saved.
-  out = CdbRun(cdb_path, dump_path,
-               'dd poi(crashy_program!crashpad::g_extra_memory_pointer)+0x1f30 '
-               'L8')
-  out.Check(r'0000655e 0000656b 00006578 00006585',
-            'extra memory range captured')
-  out.Check(r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\? '
-            r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\?',
-            '  and not memory after range')
 
-  if False:
-    # TODO(scottmg): This is flakily capturing too much memory in Debug builds,
-    # possibly because a stale pointer is being captured via the stack.
-    # See: https://bugs.chromium.org/p/crashpad/issues/detail?id=101.
-    out = CdbRun(cdb_path, dump_path,
-                'dd poi(crashy_program!crashpad::g_extra_memory_not_saved)'
-                '+0x1f30 L4')
-    # We save only the pointer, not the pointed-to data. If the pointer itself
-    # wasn't saved, then we won't get any memory printed, so here we're
-    # confirming the pointer was saved but the memory wasn't.
-    out.Check(r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\? '
-              r'\?\?\?\?\?\?\?\? \?\?\?\?\?\?\?\?',
-              'extra memory removal')
+def GetDumpFromOtherProgram(out_dir, pipe_name, *args):
+    return GetDumpFromProgram(out_dir, pipe_name, 'crash_other_program.exe', 0,
+                              *args)
 
-  out = CdbRun(cdb_path, dump_path, '.dumpdebug')
-  out.Check(r'type \?\?\? \(333333\), size 00001000',
-            'first user stream')
-  out.Check(r'type \?\?\? \(222222\), size 00000080',
-            'second user stream')
 
-  if z7_dump_path:
-    out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm')
+def GetDumpFromSignal(out_dir, pipe_name, *args):
+    STATUS_FATAL_APP_EXIT = 0x40000015  # Not known by win32con.
+    return GetDumpFromProgram(out_dir, pipe_name, 'crashy_signal.exe',
+                              STATUS_FATAL_APP_EXIT, *args)
+
+
+def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name):
+    return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe',
+                              win32con.EXCEPTION_BREAKPOINT)
+
+
+def GetDumpFromZ7Program(out_dir, pipe_name):
+    return GetDumpFromProgram(out_dir, pipe_name, 'crashy_z7_loader.exe',
+                              win32con.EXCEPTION_ACCESS_VIOLATION)
+
+
+class CdbRun(object):
+    """Run cdb.exe passing it a cdb command and capturing the output.
+    `Check()` searches for regex patterns in sequence allowing verification of
+    expected output.
+    """
+
+    def __init__(self, cdb_path, dump_path, command):
+        # Run a command line that loads the dump, runs the specified cdb
+        # command, and then quits, and capturing stdout.
+        self.out = subprocess.check_output(
+            [cdb_path, '-z', dump_path, '-c', command + ';q'])
+
+    def Check(self, pattern, message, re_flags=0):
+        match_obj = re.search(pattern, self.out, re_flags)
+        if match_obj:
+            # Matched. Consume up to end of match.
+            self.out = self.out[match_obj.end(0):]
+            print('ok - %s' % message)
+            sys.stdout.flush()
+        else:
+            print('-' * 80, file=sys.stderr)
+            print('FAILED - %s' % message, file=sys.stderr)
+            print('-' * 80, file=sys.stderr)
+            print('did not match:\n  %s' % pattern, file=sys.stderr)
+            print('-' * 80, file=sys.stderr)
+            print('remaining output was:\n  %s' % self.out, file=sys.stderr)
+            print('-' * 80, file=sys.stderr)
+            sys.stderr.flush()
+            global g_had_failures
+            g_had_failures = True
+
+    def Find(self, pattern, re_flags=0):
+        match_obj = re.search(pattern, self.out, re_flags)
+        if match_obj:
+            # Matched. Consume up to end of match.
+            self.out = self.out[match_obj.end(0):]
+            return match_obj
+        return None
+
+
+def RunTests(cdb_path, dump_path, start_handler_dump_path, destroyed_dump_path,
+             z7_dump_path, other_program_path, other_program_no_exception_path,
+             sigabrt_main_path, sigabrt_background_path, pipe_name):
+    """Runs various tests in sequence. Runs a new cdb instance on the dump for
+    each block of tests to reduce the chances that output from one command is
+    confused for output from another.
+    """
+    out = CdbRun(cdb_path, dump_path, '.ecxr')
     out.Check('This dump file has an exception of interest stored in it',
-              'captured exception in z7 module')
-    # Older versions of cdb display relative to exports for /Z7 modules, newer
-    # ones just display the offset.
-    out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):',
-              'exception in z7 at correct location')
-    out.Check(r'z7_test  C \(codeview symbols\)     z7_test\.dll',
-              'expected non-pdb symbol format')
+              'captured exception')
 
-  out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~')
-  out.Check('Unknown exception - code deadbea7',
-            'other program dump exception code')
-  out.Check('!Sleep', 'other program reasonable location')
-  out.Check("hanging_program!`anonymous namespace'::Thread1",
-            'other program dump right thread')
-  count = 0
-  while True:
-    match_obj = out.Find(r'Id.*Suspend: (\d+) ')
+    # When SomeCrashyFunction is inlined, cdb doesn't demangle its namespace as
+    # "`anonymous namespace'" and instead gives the decorated form.
+    out.Check(
+        'crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
+        'SomeCrashyFunction', 'exception at correct location')
+
+    out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr')
+    out.Check('This dump file has an exception of interest stored in it',
+              'captured exception (using StartHandler())')
+    out.Check(
+        'crashy_program!crashpad::(`anonymous namespace\'|\?A0x[0-9a-f]+)::'
+        'SomeCrashyFunction',
+        'exception at correct location (using StartHandler())')
+
+    out = CdbRun(cdb_path, dump_path, '!peb')
+    out.Check(r'PEB at', 'found the PEB')
+    out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+',
+              'PEB_LDR_DATA saved')
+    out.Check(r'Base TimeStamp                     Module',
+              'module list present')
+    pipe_name_escaped = pipe_name.replace('\\', '\\\\')
+    out.Check(r'CommandLine: *\'.*crashy_program\.exe *' + pipe_name_escaped,
+              'some PEB data is correct')
+    out.Check(r'SystemRoot=C:\\Windows', 'some of environment captured',
+              re.IGNORECASE)
+
+    out = CdbRun(cdb_path, dump_path, '?? @$peb->ProcessParameters')
+    out.Check(r' ImagePathName *: _UNICODE_STRING ".*\\crashy_program\.exe"',
+              'PEB->ProcessParameters.ImagePathName string captured')
+    out.Check(
+        ' DesktopInfo *: '
+        '_UNICODE_STRING "(?!--- memory read error at address ).*"',
+        'PEB->ProcessParameters.DesktopInfo string captured')
+
+    out = CdbRun(cdb_path, dump_path, '!teb')
+    out.Check(r'TEB at', 'found the TEB')
+    out.Check(r'ExceptionList:\s+[0-9a-fA-F]+', 'some valid teb data')
+    out.Check(r'LastErrorValue:\s+2', 'correct LastErrorValue')
+
+    out = CdbRun(cdb_path, dump_path, '!gle')
+    out.Check(
+        'LastErrorValue: \(Win32\) 0x2 \(2\) - The system cannot find the '
+        'file specified.', '!gle gets last error')
+    out.Check(
+        'LastStatusValue: \(NTSTATUS\) 0xc000000f - {File Not Found}  The '
+        'file %hs does not exist.', '!gle gets last ntstatus')
+
+    if False:
+        # TODO(scottmg): Re-enable when we grab ntdll!RtlCriticalSectionList.
+        out = CdbRun(cdb_path, dump_path, '!locks')
+        out.Check(
+            r'CritSec crashy_program!crashpad::`anonymous namespace\'::'
+            r'g_test_critical_section', 'lock was captured')
+        if platform.win32_ver()[0] != '7':
+            # We can't allocate CRITICAL_SECTIONs with .DebugInfo on Win 7.
+            out.Check(r'\*\*\* Locked',
+                      'lock debug info was captured, and is locked')
+
+    out = CdbRun(cdb_path, dump_path, '!handle')
+    out.Check(r'\d+ Handles', 'captured handles')
+    out.Check(r'Event\s+\d+', 'capture some event handles')
+    out.Check(r'File\s+\d+', 'capture some file handles')
+
+    out = CdbRun(cdb_path, dump_path, 'lm')
+    out.Check(r'Unloaded modules:', 'captured some unloaded modules')
+    out.Check(r'lz32\.dll', 'found expected unloaded module lz32')
+    out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror')
+
+    out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2')
+    out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+',
+              'PEB_LDR_DATA saved')
+    out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE)
+
+    # Check that there is no stack trace in the self-destroyed process. Confirm
+    # that the top is where we expect it (that's based only on IP), but
+    # subsequent stack entries will not be available. This confirms that we have
+    # a mostly valid dump, but that the stack was omitted.
+    out.Check(
+        r'self_destroying_program!crashpad::`anonymous namespace\'::'
+        r'FreeOwnStackAndBreak.*\nquit:',
+        'at correct location, no additional stack entries')
+
+    # Dump memory pointed to be EDI on the background suspended thread. We don't
+    # know the index of the thread because the system may have started other
+    # threads, so first do a run to extract the thread index that's suspended,
+    # and then another run to dump the data pointed to by EDI for that thread.
+    out = CdbRun(cdb_path, dump_path, '.ecxr;~')
+    match_obj = out.Find(r'(\d+)\s+Id: [0-9a-f.]+ Suspend: 1 Teb:')
     if match_obj:
-      if match_obj.group(1) != '0':
-        out.Check(r'FAILED', 'all suspend counts should be 0')
-      else:
-        count += 1
-    else:
-      break
-  assert count > 2
+        thread = match_obj.group(1)
+        out = CdbRun(cdb_path, dump_path, '.ecxr;~' + thread + 's;db /c14 edi')
+    out.Check(r'63 62 61 60 5f 5e 5d 5c-5b 5a 59 58 57 56 55 54 53 52 51 50',
+              'data pointed to by registers captured')
 
-  out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k')
-  out.Check('Unknown exception - code 0cca11ed',
-            'other program with no exception given')
-  out.Check('!RaiseException', 'other program in RaiseException()')
+    # Move up one stack frame after jumping to the exception, and examine
+    # memory.
+    out = CdbRun(cdb_path, dump_path,
+                 '.ecxr; .f+; dd /c100 poi(offset_pointer)-20')
+    out.Check(
+        r'80000078 00000079 8000007a 0000007b 8000007c 0000007d 8000007e '
+        r'0000007f 80000080 00000081 80000082 00000083 80000084 00000085 '
+        r'80000086 00000087 80000088 00000089 8000008a 0000008b 8000008c '
+        r'0000008d 8000008e 0000008f 80000090 00000091 80000092 00000093 '
+        r'80000094 00000095 80000096 00000097',
+        'data pointed to by stack captured')
 
-  out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr')
-  out.Check('code 40000015', 'got sigabrt signal')
-  out.Check('::HandleAbortSignal', '  stack in expected location')
+    # Attempt to retrieve the value of g_extra_memory_pointer (by name), and
+    # then examine the memory at which it points. Both should have been saved.
+    out = CdbRun(
+        cdb_path, dump_path,
+        'dd poi(crashy_program!crashpad::g_extra_memory_pointer)+0x1f30 '
+        'L8')
+    out.Check(r'0000655e 0000656b 00006578 00006585',
+              'extra memory range captured')
 
-  out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr')
-  out.Check('code 40000015', 'got sigabrt signal from background thread')
+    out = CdbRun(cdb_path, dump_path, '.dumpdebug')
+    out.Check(r'type \?\?\? \(333333\), size 00001000', 'first user stream')
+    out.Check(r'type \?\?\? \(222222\), size 00000080', 'second user stream')
+
+    if z7_dump_path:
+        out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm')
+        out.Check('This dump file has an exception of interest stored in it',
+                  'captured exception in z7 module')
+        # Older versions of cdb display relative to exports for /Z7 modules,
+        # newer ones just display the offset.
+        out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):',
+                  'exception in z7 at correct location')
+        out.Check(r'z7_test  C \(codeview symbols\)     z7_test\.dll',
+                  'expected non-pdb symbol format')
+
+    out = CdbRun(cdb_path, other_program_path, '.ecxr;k;~')
+    out.Check('Unknown exception - code deadbea7',
+              'other program dump exception code')
+    out.Check('!Sleep', 'other program reasonable location')
+    out.Check("hanging_program!`anonymous namespace'::Thread1",
+              'other program dump right thread')
+    count = 0
+    while True:
+        match_obj = out.Find(r'Id.*Suspend: (\d+) ')
+        if match_obj:
+            if match_obj.group(1) != '0':
+                out.Check(r'FAILED', 'all suspend counts should be 0')
+            else:
+                count += 1
+        else:
+            break
+    assert count > 2
+
+    out = CdbRun(cdb_path, other_program_no_exception_path, '.ecxr;k')
+    out.Check('Unknown exception - code 0cca11ed',
+              'other program with no exception given')
+    out.Check('!RaiseException', 'other program in RaiseException()')
+
+    out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr')
+    out.Check('code 40000015', 'got sigabrt signal')
+    out.Check('::HandleAbortSignal', '  stack in expected location')
+
+    out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr')
+    out.Check('code 40000015', 'got sigabrt signal from background thread')
 
 
 def main(args):
-  try:
-    if len(args) != 1:
-      print('must supply binary dir', file=sys.stderr)
-      return 1
+    try:
+        if len(args) != 1:
+            print('must supply binary dir', file=sys.stderr)
+            return 1
 
-    cdb_path = GetCdbPath()
-    if not cdb_path:
-      print('could not find cdb', file=sys.stderr)
-      return 1
+        cdb_path = GetCdbPath()
+        if not cdb_path:
+            print('could not find cdb', file=sys.stderr)
+            return 1
 
-    # Make sure we can download Windows symbols.
-    if not os.environ.get('_NT_SYMBOL_PATH'):
-      symbol_dir = MakeTempDir()
-      protocol = 'https' if platform.win32_ver()[0] != 'XP' else 'http'
-      os.environ['_NT_SYMBOL_PATH'] = (
-          'SRV*' + symbol_dir + '*' +
-          protocol + '://msdl.microsoft.com/download/symbols')
+        # Make sure we can download Windows symbols.
+        if not os.environ.get('_NT_SYMBOL_PATH'):
+            symbol_dir = MakeTempDir()
+            protocol = 'https' if platform.win32_ver()[0] != 'XP' else 'http'
+            os.environ['_NT_SYMBOL_PATH'] = (
+                'SRV*' + symbol_dir + '*' + protocol +
+                '://msdl.microsoft.com/download/symbols')
 
-    pipe_name = r'\\.\pipe\end-to-end_%s_%s' % (
-        os.getpid(), str(random.getrandbits(64)))
+        pipe_name = r'\\.\pipe\end-to-end_%s_%s' % (os.getpid(),
+                                                    str(random.getrandbits(64)))
 
-    crashy_dump_path = GetDumpFromCrashyProgram(args[0], pipe_name)
-    if not crashy_dump_path:
-      return 1
+        crashy_dump_path = GetDumpFromCrashyProgram(args[0], pipe_name)
+        if not crashy_dump_path:
+            return 1
 
-    start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None)
-    if not start_handler_dump_path:
-      return 1
+        start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None)
+        if not start_handler_dump_path:
+            return 1
 
-    destroyed_dump_path = GetDumpFromSelfDestroyingProgram(args[0], pipe_name)
-    if not destroyed_dump_path:
-      return 1
+        destroyed_dump_path = GetDumpFromSelfDestroyingProgram(
+            args[0], pipe_name)
+        if not destroyed_dump_path:
+            return 1
 
-    z7_dump_path = None
-    if not args[0].endswith('_x64'):
-      z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name)
-      if not z7_dump_path:
-        return 1
+        z7_dump_path = None
+        if not args[0].endswith('_x64'):
+            z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name)
+            if not z7_dump_path:
+                return 1
 
-    other_program_path = GetDumpFromOtherProgram(args[0], pipe_name)
-    if not other_program_path:
-      return 1
+        other_program_path = GetDumpFromOtherProgram(args[0], pipe_name)
+        if not other_program_path:
+            return 1
 
-    other_program_no_exception_path = GetDumpFromOtherProgram(
-        args[0], pipe_name, 'noexception')
-    if not other_program_no_exception_path:
-      return 1
+        other_program_no_exception_path = GetDumpFromOtherProgram(
+            args[0], pipe_name, 'noexception')
+        if not other_program_no_exception_path:
+            return 1
 
-    sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main')
-    if not sigabrt_main_path:
-      return 1
+        sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main')
+        if not sigabrt_main_path:
+            return 1
 
-    sigabrt_background_path = GetDumpFromSignal(
-        args[0], pipe_name, 'background')
-    if not sigabrt_background_path:
-      return 1
+        sigabrt_background_path = GetDumpFromSignal(args[0], pipe_name,
+                                                    'background')
+        if not sigabrt_background_path:
+            return 1
 
-    RunTests(cdb_path,
-             crashy_dump_path,
-             start_handler_dump_path,
-             destroyed_dump_path,
-             z7_dump_path,
-             other_program_path,
-             other_program_no_exception_path,
-             sigabrt_main_path,
-             sigabrt_background_path,
-             pipe_name)
+        RunTests(cdb_path, crashy_dump_path, start_handler_dump_path,
+                 destroyed_dump_path, z7_dump_path, other_program_path,
+                 other_program_no_exception_path, sigabrt_main_path,
+                 sigabrt_background_path, pipe_name)
 
-    return 1 if g_had_failures else 0
-  finally:
-    CleanUpTempDirs()
+        return 1 if g_had_failures else 0
+    finally:
+        CleanUpTempDirs()
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/snapshot/win/exception_snapshot_win.cc b/snapshot/win/exception_snapshot_win.cc
index 64b5342..3413a40 100644
--- a/snapshot/win/exception_snapshot_win.cc
+++ b/snapshot/win/exception_snapshot_win.cc
@@ -17,9 +17,9 @@
 #include "client/crashpad_client.h"
 #include "snapshot/capture_memory.h"
 #include "snapshot/memory_snapshot.h"
-#include "snapshot/win/cpu_context_win.h"
+#include "snapshot/memory_snapshot_generic.h"
 #include "snapshot/win/capture_memory_delegate_win.h"
-#include "snapshot/win/memory_snapshot_win.h"
+#include "snapshot/win/cpu_context_win.h"
 #include "snapshot/win/process_reader_win.h"
 #include "util/win/nt_internals.h"
 
@@ -28,22 +28,13 @@
 
 namespace {
 
+#if defined(ARCH_CPU_X86_FAMILY)
 #if defined(ARCH_CPU_32_BITS)
 using Context32 = CONTEXT;
 #elif defined(ARCH_CPU_64_BITS)
 using Context32 = WOW64_CONTEXT;
 #endif
 
-#if defined(ARCH_CPU_64_BITS)
-void NativeContextToCPUContext64(const CONTEXT& context_record,
-                                 CPUContext* context,
-                                 CPUContextUnion* context_union) {
-  context->architecture = kCPUArchitectureX86_64;
-  context->x86_64 = &context_union->x86_64;
-  InitializeX64Context(context_record, context->x86_64);
-}
-#endif
-
 void NativeContextToCPUContext32(const Context32& context_record,
                                  CPUContext* context,
                                  CPUContextUnion* context_union) {
@@ -51,6 +42,25 @@
   context->x86 = &context_union->x86;
   InitializeX86Context(context_record, context->x86);
 }
+#endif  // ARCH_CPU_X86_FAMILY
+
+#if defined(ARCH_CPU_64_BITS)
+void NativeContextToCPUContext64(const CONTEXT& context_record,
+                                 CPUContext* context,
+                                 CPUContextUnion* context_union) {
+#if defined(ARCH_CPU_X86_64)
+  context->architecture = kCPUArchitectureX86_64;
+  context->x86_64 = &context_union->x86_64;
+  InitializeX64Context(context_record, context->x86_64);
+#elif defined(ARCH_CPU_ARM64)
+  context->architecture = kCPUArchitectureARM64;
+  context->arm64 = &context_union->arm64;
+  InitializeARM64Context(context_record, context->arm64);
+#else
+#error Unsupported Windows 64-bit Arch
+#endif
+}
+#endif
 
 }  // namespace
 
@@ -106,6 +116,8 @@
     }
   }
 #endif
+
+#if !defined(ARCH_CPU_ARM64)
   if (!is_64_bit) {
     if (!InitializeFromExceptionPointers<EXCEPTION_RECORD32,
                                          process_types::EXCEPTION_POINTERS32>(
@@ -116,6 +128,7 @@
       return false;
     }
   }
+#endif
 
   CaptureMemoryDelegateWin capture_memory_delegate(
       process_reader, *thread, &extra_memory_, nullptr);
@@ -176,9 +189,9 @@
                                   CPUContext* context,
                                   CPUContextUnion* context_union)) {
   ExceptionPointersType exception_pointers;
-  if (!process_reader->ReadMemory(exception_pointers_address,
-                                  sizeof(exception_pointers),
-                                  &exception_pointers)) {
+  if (!process_reader->Memory()->Read(exception_pointers_address,
+                                      sizeof(exception_pointers),
+                                      &exception_pointers)) {
     LOG(ERROR) << "EXCEPTION_POINTERS read failed";
     return false;
   }
@@ -188,7 +201,7 @@
   }
 
   ExceptionRecordType first_record;
-  if (!process_reader->ReadMemory(
+  if (!process_reader->Memory()->Read(
           static_cast<WinVMAddress>(exception_pointers.ExceptionRecord),
           sizeof(first_record),
           &first_record)) {
@@ -240,7 +253,7 @@
     }
 
     ContextType context_record;
-    if (!process_reader->ReadMemory(
+    if (!process_reader->Memory()->Read(
             static_cast<WinVMAddress>(exception_pointers.ContextRecord),
             sizeof(context_record),
             &context_record)) {
diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h
index f6e29d9..fd4e854 100644
--- a/snapshot/win/exception_snapshot_win.h
+++ b/snapshot/win/exception_snapshot_win.h
@@ -36,14 +36,16 @@
 
 namespace internal {
 
-class MemorySnapshotWin;
+class MemorySnapshotGeneric;
 
-#if defined(ARCH_CPU_X86_FAMILY)
 union CPUContextUnion {
+#if defined(ARCH_CPU_X86_FAMILY)
   CPUContextX86 x86;
   CPUContextX86_64 x86_64;
-};
+#elif defined(ARCH_CPU_ARM64)
+  CPUContextARM64 arm64;
 #endif
+};
 
 class ExceptionSnapshotWin final : public ExceptionSnapshot {
  public:
@@ -91,12 +93,10 @@
                                     CPUContext* context,
                                     CPUContextUnion* context_union));
 
-#if defined(ARCH_CPU_X86_FAMILY)
   CPUContextUnion context_union_;
-#endif
   CPUContext context_;
   std::vector<uint64_t> codes_;
-  std::vector<std::unique_ptr<internal::MemorySnapshotWin>> extra_memory_;
+  std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>> extra_memory_;
   uint64_t thread_id_;
   uint64_t exception_address_;
   uint32_t exception_flags_;
diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc
index 537b4ca..07a25a8 100644
--- a/snapshot/win/exception_snapshot_win_test.cc
+++ b/snapshot/win/exception_snapshot_win_test.cc
@@ -23,7 +23,6 @@
 #include "gtest/gtest.h"
 #include "snapshot/win/process_snapshot_win.h"
 #include "test/errors.h"
-#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -103,7 +102,12 @@
     // Verify the exception happened at the expected location with a bit of
     // slop space to allow for reading the current PC before the exception
     // happens. See TestCrashingChild().
+#if !defined(NDEBUG)
+    // Debug build is likely not optimized and contains more instructions.
+    constexpr uint64_t kAllowedOffset = 200;
+#else
     constexpr uint64_t kAllowedOffset = 100;
+#endif
     EXPECT_GT(snapshot.Exception()->ExceptionAddress(), break_near_);
     EXPECT_LT(snapshot.Exception()->ExceptionAddress(),
               break_near_ + kAllowedOffset);
@@ -176,7 +180,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ExceptionSnapshotWinTest, ChildCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestCrashingChild(TestPaths::Architecture::k32Bit);
@@ -214,6 +218,9 @@
     // ASan instrumentation inserts more instructions between the expected
     // location and what's reported. https://crbug.com/845011.
     constexpr uint64_t kAllowedOffset = 500;
+#elif !defined(NDEBUG)
+    // Debug build is likely not optimized and contains more instructions.
+    constexpr uint64_t kAllowedOffset = 200;
 #else
     constexpr uint64_t kAllowedOffset = 100;
 #endif
@@ -280,14 +287,20 @@
   EXPECT_EQ(child.WaitForExit(), 0u);
 }
 
-TEST(SimulateCrash, ChildDumpWithoutCrashing) {
+#if defined(ADDRESS_SANITIZER)
+// https://crbug.com/845011
+#define MAYBE_ChildDumpWithoutCrashing DISABLED_ChildDumpWithoutCrashing
+#else
+#define MAYBE_ChildDumpWithoutCrashing ChildDumpWithoutCrashing
+#endif
+TEST(SimulateCrash, MAYBE_ChildDumpWithoutCrashing) {
   TestDumpWithoutCrashingChild(TestPaths::Architecture::kDefault);
 }
 
 #if defined(ARCH_CPU_64_BITS)
 TEST(SimulateCrash, ChildDumpWithoutCrashingWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestDumpWithoutCrashingChild(TestPaths::Architecture::k32Bit);
diff --git a/snapshot/win/extra_memory_ranges_test.cc b/snapshot/win/extra_memory_ranges_test.cc
index dcef805..e0c17a2 100644
--- a/snapshot/win/extra_memory_ranges_test.cc
+++ b/snapshot/win/extra_memory_ranges_test.cc
@@ -25,7 +25,6 @@
 #include "client/simple_address_range_bag.h"
 #include "gtest/gtest.h"
 #include "snapshot/win/process_snapshot_win.h"
-#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -110,7 +109,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ExtraMemoryRanges, DontCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestExtraMemoryRanges(kDontCrash, TestPaths::Architecture::k32Bit);
@@ -118,7 +117,7 @@
 
 TEST(ExtraMemoryRanges, CrashDebugBreakWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestExtraMemoryRanges(kCrashDebugBreak, TestPaths::Architecture::k32Bit);
diff --git a/snapshot/win/memory_snapshot_win.cc b/snapshot/win/memory_snapshot_win.cc
deleted file mode 100644
index 17e8ac1..0000000
--- a/snapshot/win/memory_snapshot_win.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2015 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.
-
-#include <memory>
-
-#include "snapshot/win/memory_snapshot_win.h"
-
-namespace crashpad {
-namespace internal {
-
-MemorySnapshotWin::MemorySnapshotWin()
-    : MemorySnapshot(),
-      process_reader_(nullptr),
-      address_(0),
-      size_(0),
-      initialized_() {
-}
-
-MemorySnapshotWin::~MemorySnapshotWin() {
-}
-
-void MemorySnapshotWin::Initialize(ProcessReaderWin* process_reader,
-                                   uint64_t address,
-                                   uint64_t size) {
-  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-  process_reader_ = process_reader;
-  address_ = address;
-  DLOG_IF(WARNING, size >= std::numeric_limits<size_t>::max())
-      << "size overflow";
-  size_ = static_cast<size_t>(size);
-  INITIALIZATION_STATE_SET_VALID(initialized_);
-}
-
-uint64_t MemorySnapshotWin::Address() const {
-  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return address_;
-}
-
-size_t MemorySnapshotWin::Size() const {
-  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-  return size_;
-}
-
-bool MemorySnapshotWin::Read(Delegate* delegate) const {
-  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
-
-  if (size_ == 0) {
-    return delegate->MemorySnapshotDelegateRead(nullptr, size_);
-  }
-
-  std::unique_ptr<uint8_t[]> buffer(new uint8_t[size_]);
-  if (!process_reader_->ReadMemory(address_, size_, buffer.get())) {
-    return false;
-  }
-  return delegate->MemorySnapshotDelegateRead(buffer.get(), size_);
-}
-
-const MemorySnapshot* MemorySnapshotWin::MergeWithOtherSnapshot(
-    const MemorySnapshot* other) const {
-  return MergeWithOtherSnapshotImpl(this, other);
-}
-
-}  // namespace internal
-}  // namespace crashpad
diff --git a/snapshot/win/memory_snapshot_win.h b/snapshot/win/memory_snapshot_win.h
deleted file mode 100644
index ebc878b..0000000
--- a/snapshot/win/memory_snapshot_win.h
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2015 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_WIN_MEMORY_SNAPSHOT_WIN_H_
-#define CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "base/macros.h"
-#include "snapshot/memory_snapshot.h"
-#include "snapshot/win/process_reader_win.h"
-#include "util/misc/initialization_state_dcheck.h"
-
-namespace crashpad {
-namespace internal {
-
-//! \brief A MemorySnapshot of a memory region in a process on the running
-//!     system, when the system runs Windows.
-class MemorySnapshotWin final : public MemorySnapshot {
- public:
-  MemorySnapshotWin();
-  ~MemorySnapshotWin() override;
-
-  //! \brief Initializes the object.
-  //!
-  //! Memory is read lazily. No attempt is made to read the memory snapshot data
-  //! until Read() is called, and the memory snapshot data is discared when
-  //! Read() returns.
-  //!
-  //! \param[in] process_reader A reader for the process being snapshotted.
-  //! \param[in] address The base address of the memory region to snapshot, in
-  //!     the snapshot process' address space.
-  //! \param[in] size The size of the memory region to snapshot.
-  void Initialize(ProcessReaderWin* process_reader,
-                  uint64_t address,
-                  uint64_t size);
-
-  // MemorySnapshot:
-
-  uint64_t Address() const override;
-  size_t Size() const override;
-  bool Read(Delegate* delegate) const override;
-  const MemorySnapshot* MergeWithOtherSnapshot(
-      const MemorySnapshot* other) const override;
-
- private:
-  template <class T>
-  friend const MemorySnapshot* MergeWithOtherSnapshotImpl(
-      const T* self,
-      const MemorySnapshot* other);
-
-  ProcessReaderWin* process_reader_;  // weak
-  uint64_t address_;
-  size_t size_;
-  InitializationStateDcheck initialized_;
-
-  DISALLOW_COPY_AND_ASSIGN(MemorySnapshotWin);
-};
-
-}  // namespace internal
-}  // namespace crashpad
-
-#endif  // CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_
diff --git a/snapshot/win/module_snapshot_win.cc b/snapshot/win/module_snapshot_win.cc
index 880e9da..c7aaad8 100644
--- a/snapshot/win/module_snapshot_win.cc
+++ b/snapshot/win/module_snapshot_win.cc
@@ -19,7 +19,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "client/crashpad_info.h"
 #include "client/simple_address_range_bag.h"
-#include "snapshot/win/memory_snapshot_win.h"
+#include "snapshot/memory_snapshot_generic.h"
 #include "snapshot/win/pe_image_annotations_reader.h"
 #include "snapshot/win/pe_image_reader.h"
 #include "util/misc/tri_state.h"
@@ -33,17 +33,18 @@
       name_(),
       pdb_name_(),
       uuid_(),
-      pe_image_reader_(),
+      memory_range_(),
+      streams_(),
+      vs_fixed_file_info_(),
+      initialized_vs_fixed_file_info_(),
       process_reader_(nullptr),
+      pe_image_reader_(),
+      crashpad_info_(),
       timestamp_(0),
       age_(0),
-      initialized_(),
-      vs_fixed_file_info_(),
-      initialized_vs_fixed_file_info_() {
-}
+      initialized_() {}
 
-ModuleSnapshotWin::~ModuleSnapshotWin() {
-}
+ModuleSnapshotWin::~ModuleSnapshotWin() {}
 
 bool ModuleSnapshotWin::Initialize(
     ProcessReaderWin* process_reader,
@@ -75,6 +76,26 @@
     pdb_name_ = base::UTF16ToUTF8(name_);
   }
 
+  if (!memory_range_.Initialize(process_reader_->Memory(),
+                                process_reader_->Is64Bit())) {
+    return false;
+  }
+
+  WinVMAddress crashpad_info_address;
+  WinVMSize crashpad_info_size;
+  if (pe_image_reader_->GetCrashpadInfoSection(&crashpad_info_address,
+                                               &crashpad_info_size)) {
+    ProcessMemoryRange info_range;
+    info_range.Initialize(memory_range_);
+    info_range.RestrictRange(crashpad_info_address,
+                             crashpad_info_address + crashpad_info_size);
+
+    auto info = std::make_unique<CrashpadInfoReader>();
+    if (info->Initialize(&info_range, crashpad_info_address)) {
+      crashpad_info_ = std::move(info);
+    }
+  }
+
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
@@ -173,6 +194,11 @@
   return pdb_name_;
 }
 
+std::vector<uint8_t> ModuleSnapshotWin::BuildID() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return std::vector<uint8_t>();
+}
+
 std::vector<std::string> ModuleSnapshotWin::AnnotationsVector() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   // These correspond to system-logged things on Mac. We don't currently track
@@ -228,8 +254,7 @@
 template <class Traits>
 void ModuleSnapshotWin::GetCrashpadOptionsInternal(
     CrashpadInfoClientOptions* options) {
-  process_types::CrashpadInfo<Traits> crashpad_info;
-  if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) {
+  if (!crashpad_info_) {
     options->crashpad_handler_behavior = TriState::kUnset;
     options->system_crash_reporter_forwarding = TriState::kUnset;
     options->gather_indirectly_referenced_memory = TriState::kUnset;
@@ -238,19 +263,13 @@
   }
 
   options->crashpad_handler_behavior =
-      CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
-          crashpad_info.crashpad_handler_behavior);
-
+      crashpad_info_->CrashpadHandlerBehavior();
   options->system_crash_reporter_forwarding =
-      CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
-          crashpad_info.system_crash_reporter_forwarding);
-
+      crashpad_info_->SystemCrashReporterForwarding();
   options->gather_indirectly_referenced_memory =
-      CrashpadInfoClientOptions::TriStateFromCrashpadInfo(
-          crashpad_info.gather_indirectly_referenced_memory);
-
+      crashpad_info_->GatherIndirectlyReferencedMemory();
   options->indirectly_referenced_memory_cap =
-      crashpad_info.indirectly_referenced_memory_cap;
+      crashpad_info_->IndirectlyReferencedMemoryCap();
 }
 
 const VS_FIXEDFILEINFO* ModuleSnapshotWin::VSFixedFileInfo() const {
@@ -270,16 +289,13 @@
 template <class Traits>
 void ModuleSnapshotWin::GetCrashpadExtraMemoryRanges(
     std::set<CheckedRange<uint64_t>>* ranges) const {
-  process_types::CrashpadInfo<Traits> crashpad_info;
-  if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info) ||
-      !crashpad_info.extra_address_ranges) {
+  if (!crashpad_info_ || !crashpad_info_->ExtraMemoryRanges())
     return;
-  }
 
   std::vector<SimpleAddressRangeBag::Entry> simple_ranges(
       SimpleAddressRangeBag::num_entries);
-  if (!process_reader_->ReadMemory(
-          crashpad_info.extra_address_ranges,
+  if (!process_reader_->Memory()->Read(
+          crashpad_info_->ExtraMemoryRanges(),
           simple_ranges.size() * sizeof(simple_ranges[0]),
           &simple_ranges[0])) {
     LOG(WARNING) << "could not read simple address_ranges from "
@@ -298,24 +314,23 @@
 template <class Traits>
 void ModuleSnapshotWin::GetCrashpadUserMinidumpStreams(
     std::vector<std::unique_ptr<const UserMinidumpStream>>* streams) const {
-  process_types::CrashpadInfo<Traits> crashpad_info;
-  if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info))
+  if (!crashpad_info_)
     return;
 
-  for (uint64_t cur = crashpad_info.user_data_minidump_stream_head; cur;) {
+  for (uint64_t cur = crashpad_info_->UserDataMinidumpStreamHead(); cur;) {
     internal::UserDataMinidumpStreamListEntry list_entry;
-    if (!process_reader_->ReadMemory(
-          cur, sizeof(list_entry), &list_entry)) {
+    if (!process_reader_->Memory()->Read(
+            cur, sizeof(list_entry), &list_entry)) {
       LOG(WARNING) << "could not read user data stream entry from "
                    << base::UTF16ToUTF8(name_);
       return;
     }
 
     if (list_entry.size != 0) {
-      std::unique_ptr<internal::MemorySnapshotWin> memory(
-          new internal::MemorySnapshotWin());
+      std::unique_ptr<internal::MemorySnapshotGeneric> memory(
+          new internal::MemorySnapshotGeneric());
       memory->Initialize(
-          process_reader_, list_entry.base_address, list_entry.size);
+          process_reader_->Memory(), list_entry.base_address, list_entry.size);
       streams->push_back(std::make_unique<UserMinidumpStream>(
           list_entry.stream_type, memory.release()));
     }
diff --git a/snapshot/win/module_snapshot_win.h b/snapshot/win/module_snapshot_win.h
index 693588d..b32216c 100644
--- a/snapshot/win/module_snapshot_win.h
+++ b/snapshot/win/module_snapshot_win.h
@@ -25,6 +25,7 @@
 
 #include "base/macros.h"
 #include "snapshot/crashpad_info_client_options.h"
+#include "snapshot/crashpad_types/crashpad_info_reader.h"
 #include "snapshot/module_snapshot.h"
 #include "snapshot/win/process_reader_win.h"
 #include "util/misc/initialization_state.h"
@@ -83,6 +84,7 @@
   ModuleType GetModuleType() const override;
   void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
   std::string DebugFileName() const override;
+  std::vector<uint8_t> BuildID() const override;
   std::vector<std::string> AnnotationsVector() const override;
   std::map<std::string, std::string> AnnotationsSimpleMap() const override;
   std::vector<AnnotationSnapshot> AnnotationObjects() const override;
@@ -109,18 +111,19 @@
   std::wstring name_;
   std::string pdb_name_;
   UUID uuid_;
-  std::unique_ptr<PEImageReader> pe_image_reader_;
-  ProcessReaderWin* process_reader_;  // weak
-  time_t timestamp_;
-  uint32_t age_;
+  ProcessMemoryRange memory_range_;
   // Too const-y: https://crashpad.chromium.org/bug/9.
   mutable std::vector<std::unique_ptr<const UserMinidumpStream>> streams_;
-  InitializationStateDcheck initialized_;
-
   // VSFixedFileInfo() is logically const, but updates these members on the
   // call. See https://crashpad.chromium.org/bug/9.
   mutable VS_FIXEDFILEINFO vs_fixed_file_info_;
   mutable InitializationState initialized_vs_fixed_file_info_;
+  ProcessReaderWin* process_reader_;  // weak
+  std::unique_ptr<PEImageReader> pe_image_reader_;
+  std::unique_ptr<CrashpadInfoReader> crashpad_info_;
+  time_t timestamp_;
+  uint32_t age_;
+  InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotWin);
 };
diff --git a/snapshot/win/pe_image_annotations_reader_test.cc b/snapshot/win/module_snapshot_win_test.cc
similarity index 86%
rename from snapshot/win/pe_image_annotations_reader_test.cc
rename to snapshot/win/module_snapshot_win_test.cc
index e102600..d9786cf 100644
--- a/snapshot/win/pe_image_annotations_reader_test.cc
+++ b/snapshot/win/module_snapshot_win_test.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "snapshot/win/pe_image_annotations_reader.h"
+#include "snapshot/win/module_snapshot_win.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -30,7 +30,6 @@
 #include "snapshot/annotation_snapshot.h"
 #include "snapshot/win/pe_image_reader.h"
 #include "snapshot/win/process_reader_win.h"
-#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -74,20 +73,15 @@
   std::map<std::string, std::string> all_annotations_simple_map;
   std::vector<AnnotationSnapshot> all_annotation_objects;
   for (const ProcessInfo::Module& module : modules) {
-    PEImageReader pe_image_reader;
-    pe_image_reader.Initialize(&process_reader,
-                               module.dll_base,
-                               module.size,
-                               base::UTF16ToUTF8(module.name));
-    PEImageAnnotationsReader module_annotations_reader(
-        &process_reader, &pe_image_reader, module.name);
+    internal::ModuleSnapshotWin module_snapshot;
+    module_snapshot.Initialize(&process_reader, module);
 
     std::map<std::string, std::string> module_annotations_simple_map =
-        module_annotations_reader.SimpleMap();
+        module_snapshot.AnnotationsSimpleMap();
     all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
                                       module_annotations_simple_map.end());
 
-    auto module_annotations_list = module_annotations_reader.AnnotationsList();
+    auto module_annotations_list = module_snapshot.AnnotationObjects();
     all_annotation_objects.insert(all_annotation_objects.end(),
                                   module_annotations_list.begin(),
                                   module_annotations_list.end());
@@ -146,26 +140,26 @@
   EXPECT_EQ(child.WaitForExit(), expected_exit_code);
 }
 
-TEST(PEImageAnnotationsReader, DontCrash) {
+TEST(ModuleSnapshotWinTest, DontCrash) {
   TestAnnotationsOnCrash(kDontCrash, TestPaths::Architecture::kDefault);
 }
 
-TEST(PEImageAnnotationsReader, CrashDebugBreak) {
+TEST(ModuleSnapshotWinTest, CrashDebugBreak) {
   TestAnnotationsOnCrash(kCrashDebugBreak, TestPaths::Architecture::kDefault);
 }
 
 #if defined(ARCH_CPU_64_BITS)
-TEST(PEImageAnnotationsReader, DontCrashWOW64) {
+TEST(ModuleSnapshotWinTest, DontCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestAnnotationsOnCrash(kDontCrash, TestPaths::Architecture::k32Bit);
 }
 
-TEST(PEImageAnnotationsReader, CrashDebugBreakWOW64) {
+TEST(ModuleSnapshotWinTest, CrashDebugBreakWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestAnnotationsOnCrash(kCrashDebugBreak, TestPaths::Architecture::k32Bit);
diff --git a/snapshot/win/pe_image_annotations_reader.cc b/snapshot/win/pe_image_annotations_reader.cc
index 6796145..058342d 100644
--- a/snapshot/win/pe_image_annotations_reader.cc
+++ b/snapshot/win/pe_image_annotations_reader.cc
@@ -17,6 +17,7 @@
 #include <string.h>
 #include <sys/types.h>
 
+#include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "client/annotation.h"
 #include "client/simple_string_dictionary.h"
@@ -92,7 +93,7 @@
 
   std::vector<SimpleStringDictionary::Entry>
       simple_annotations(SimpleStringDictionary::num_entries);
-  if (!process_reader_->ReadMemory(
+  if (!process_reader_->Memory()->Read(
           crashpad_info.simple_annotations,
           simple_annotations.size() * sizeof(simple_annotations[0]),
           &simple_annotations[0])) {
@@ -127,9 +128,9 @@
   }
 
   process_types::AnnotationList<Traits> annotation_list_object;
-  if (!process_reader_->ReadMemory(crashpad_info.annotations_list,
-                                   sizeof(annotation_list_object),
-                                   &annotation_list_object)) {
+  if (!process_reader_->Memory()->Read(crashpad_info.annotations_list,
+                                       sizeof(annotation_list_object),
+                                       &annotation_list_object)) {
     LOG(WARNING) << "could not read annotations list object in "
                  << base::UTF16ToUTF8(name_);
     return;
@@ -140,7 +141,7 @@
        current.link_node != annotation_list_object.tail_pointer &&
        index < kMaxNumberOfAnnotations;
        ++index) {
-    if (!process_reader_->ReadMemory(
+    if (!process_reader_->Memory()->Read(
             current.link_node, sizeof(current), &current)) {
       LOG(WARNING) << "could not read annotation at index " << index << " in "
                    << base::UTF16ToUTF8(name_);
@@ -155,7 +156,8 @@
     snapshot.type = current.type;
 
     char name[Annotation::kNameMaxLength];
-    if (!process_reader_->ReadMemory(current.name, arraysize(name), name)) {
+    if (!process_reader_->Memory()->Read(
+            current.name, base::size(name), name)) {
       LOG(WARNING) << "could not read annotation name at index " << index
                    << " in " << base::UTF16ToUTF8(name_);
       continue;
@@ -167,7 +169,7 @@
     size_t value_length =
         std::min(static_cast<size_t>(current.size), Annotation::kValueMaxSize);
     snapshot.value.resize(value_length);
-    if (!process_reader_->ReadMemory(
+    if (!process_reader_->Memory()->Read(
             current.value, value_length, snapshot.value.data())) {
       LOG(WARNING) << "could not read annotation value at index " << index
                    << " in " << base::UTF16ToUTF8(name_);
diff --git a/snapshot/win/pe_image_reader.cc b/snapshot/win/pe_image_reader.cc
index e705bb5..e69faef 100644
--- a/snapshot/win/pe_image_reader.cc
+++ b/snapshot/win/pe_image_reader.cc
@@ -21,6 +21,7 @@
 #include <memory>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "client/crashpad_info.h"
 #include "snapshot/win/pe_image_resource_reader.h"
@@ -71,6 +72,18 @@
   return true;
 }
 
+bool PEImageReader::GetCrashpadInfoSection(WinVMAddress* address,
+                                           WinVMSize* size) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  if (module_subrange_reader_.Is64Bit()) {
+    return GetCrashpadInfoSectionInternal<process_types::internal::Traits64>(
+        address, size);
+  } else {
+    return GetCrashpadInfoSectionInternal<process_types::internal::Traits32>(
+        address, size);
+  }
+}
+
 template <class Traits>
 bool PEImageReader::GetCrashpadInfo(
     process_types::CrashpadInfo<Traits>* crashpad_info) const {
@@ -275,7 +288,7 @@
       version_info.wType != 0 ||
       wcsncmp(version_info.szKey,
               L"VS_VERSION_INFO",
-              arraysize(version_info.szKey)) != 0) {
+              base::size(version_info.szKey)) != 0) {
     LOG(WARNING) << "unexpected VS_VERSIONINFO in "
                  << module_subrange_reader_.name();
     return false;
@@ -293,6 +306,31 @@
   return true;
 }
 
+template <class Traits>
+bool PEImageReader::GetCrashpadInfoSectionInternal(WinVMAddress* address,
+                                                   WinVMSize* size) const {
+  IMAGE_SECTION_HEADER section;
+  if (!GetSectionByName<typename NtHeadersForTraits<Traits>::type>("CPADinfo",
+                                                                   &section)) {
+    return false;
+  }
+
+  process_types::CrashpadInfo<Traits> crashpad_info;
+  if (section.Misc.VirtualSize <
+      offsetof(process_types::CrashpadInfo<Traits>, size) +
+          sizeof(crashpad_info.size)) {
+    LOG(WARNING) << "small crashpad info section size "
+                 << section.Misc.VirtualSize << ", "
+                 << module_subrange_reader_.name();
+    return false;
+  }
+
+  *address = Address() + section.VirtualAddress;
+  *size = std::min<WinVMSize>(sizeof(crashpad_info), section.Misc.VirtualSize);
+
+  return true;
+}
+
 template <class NtHeadersType>
 bool PEImageReader::ReadNtHeaders(NtHeadersType* nt_headers,
                                   WinVMAddress* nt_headers_address) const {
diff --git a/snapshot/win/pe_image_reader.h b/snapshot/win/pe_image_reader.h
index 56a991b..ebdbe57 100644
--- a/snapshot/win/pe_image_reader.h
+++ b/snapshot/win/pe_image_reader.h
@@ -94,6 +94,16 @@
   //! This is the value passed as \a size to Initialize().
   WinVMSize Size() const { return module_subrange_reader_.Size(); }
 
+  //! \brief Obtains the module's CrashpadInfo structure address and size.
+  //!
+  //! \param[out] address The CrashpadInfo structure address.
+  //! \param[out] size The CrashpadInfo structure size.
+  //!
+  //! \return `true` on success, `false` on failure. If the module does not have
+  //!     a `CPADinfo` section, this will return `false` without logging any
+  //!     messages. Other failures will result in messages being logged.
+  bool GetCrashpadInfoSection(WinVMAddress* address, WinVMSize* size) const;
+
   //! \brief Obtains the module's CrashpadInfo structure.
   //!
   //! \return `true` on success, `false` on failure. If the module does not have
@@ -137,6 +147,13 @@
   bool VSFixedFileInfo(VS_FIXEDFILEINFO* vs_fixed_file_info) const;
 
  private:
+  //! \brief Performs the internal logic for GetCrashpadInfoSection().
+  //!
+  //! \sa GetCrashpadInfoSection
+  template <class Traits>
+  bool GetCrashpadInfoSectionInternal(WinVMAddress* address,
+                                      WinVMSize* size) const;
+
   //! \brief Reads the `IMAGE_NT_HEADERS` from the beginning of the image.
   //!
   //! \param[out] nt_headers The contents of the templated NtHeadersType
diff --git a/snapshot/win/pe_image_reader_test.cc b/snapshot/win/pe_image_reader_test.cc
index ac456e2..161d9c8 100644
--- a/snapshot/win/pe_image_reader_test.cc
+++ b/snapshot/win/pe_image_reader_test.cc
@@ -14,7 +14,9 @@
 
 #include "snapshot/win/pe_image_reader.h"
 
-#define PSAPI_VERSION 1
+#ifndef PSAPI_VERSION
+#define PSAPI_VERSION 2
+#endif
 #include <psapi.h>
 
 #include "base/files/file_path.h"
diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc
index 2fa0258..51940c0 100644
--- a/snapshot/win/process_reader_win.cc
+++ b/snapshot/win/process_reader_win.cc
@@ -151,11 +151,17 @@
       PLOG(ERROR) << "SuspendThread";
       return false;
     }
-    DCHECK(previous_suspend_count > 0 ||
-           suspension_state == ProcessSuspensionState::kRunning);
-    thread->suspend_count =
-        previous_suspend_count -
-        (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
+    if (previous_suspend_count <= 0 &&
+        suspension_state == ProcessSuspensionState::kSuspended) {
+      LOG(WARNING) << "Thread " << thread->id
+                   << " should be suspended, but previous_suspend_count is "
+                   << previous_suspend_count;
+      thread->suspend_count = 0;
+    } else {
+      thread->suspend_count =
+          previous_suspend_count -
+          (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
+    }
 
     memset(&thread->context, 0, sizeof(thread->context));
 #if defined(ARCH_CPU_32_BITS)
@@ -204,6 +210,7 @@
 ProcessReaderWin::ProcessReaderWin()
     : process_(INVALID_HANDLE_VALUE),
       process_info_(),
+      process_memory_(),
       threads_(),
       modules_(),
       suspension_state_(),
@@ -220,67 +227,15 @@
 
   process_ = process;
   suspension_state_ = suspension_state;
-  process_info_.Initialize(process);
+  if (!process_info_.Initialize(process))
+    return false;
+  if (!process_memory_.Initialize(process))
+    return false;
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
 
-bool ProcessReaderWin::ReadMemory(WinVMAddress at,
-                                  WinVMSize num_bytes,
-                                  void* into) const {
-  if (num_bytes == 0)
-    return 0;
-
-  SIZE_T bytes_read;
-  if (!ReadProcessMemory(process_,
-                         reinterpret_cast<void*>(at),
-                         into,
-                         base::checked_cast<SIZE_T>(num_bytes),
-                         &bytes_read) ||
-      num_bytes != bytes_read) {
-    PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of "
-                << num_bytes << " bytes failed";
-    return false;
-  }
-  return true;
-}
-
-WinVMSize ProcessReaderWin::ReadAvailableMemory(WinVMAddress at,
-                                                WinVMSize num_bytes,
-                                                void* into) const {
-  if (num_bytes == 0)
-    return 0;
-
-  auto ranges = process_info_.GetReadableRanges(
-      CheckedRange<WinVMAddress, WinVMSize>(at, num_bytes));
-
-  // We only read up until the first unavailable byte, so we only read from the
-  // first range. If we have no ranges, then no bytes were accessible anywhere
-  // in the range.
-  if (ranges.empty()) {
-    LOG(ERROR) << base::StringPrintf(
-        "range at 0x%llx, size 0x%llx completely inaccessible", at, num_bytes);
-    return 0;
-  }
-
-  // If the start address was adjusted, we couldn't read even the first
-  // requested byte.
-  if (ranges.front().base() != at) {
-    LOG(ERROR) << base::StringPrintf(
-        "start of range at 0x%llx, size 0x%llx inaccessible", at, num_bytes);
-    return 0;
-  }
-
-  DCHECK_LE(ranges.front().size(), num_bytes);
-
-  // If we fail on a normal read, then something went very wrong.
-  if (!ReadMemory(ranges.front().base(), ranges.front().size(), into))
-    return 0;
-
-  return ranges.front().size();
-}
-
 bool ProcessReaderWin::StartTime(timeval* start_time) const {
   FILETIME creation, exit, kernel, user;
   if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) {
@@ -398,7 +353,7 @@
     process_types::NT_TIB<Traits> tib;
     thread.teb_address = thread_basic_info.TebBaseAddress;
     thread.teb_size = sizeof(process_types::TEB<Traits>);
-    if (ReadMemory(thread.teb_address, sizeof(tib), &tib)) {
+    if (process_memory_.Read(thread.teb_address, sizeof(tib), &tib)) {
       WinVMAddress base = 0;
       WinVMAddress limit = 0;
       // If we're reading a WOW64 process, then the TIB we just retrieved is the
@@ -409,7 +364,7 @@
         thread.teb_address = tib.Wow64Teb;
         thread.teb_size =
             sizeof(process_types::TEB<process_types::internal::Traits32>);
-        if (ReadMemory(thread.teb_address, sizeof(tib32), &tib32)) {
+        if (process_memory_.Read(thread.teb_address, sizeof(tib32), &tib32)) {
           base = tib32.StackBase;
           limit = tib32.StackLimit;
         }
diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h
index 1794a51..a4e32aa 100644
--- a/snapshot/win/process_reader_win.h
+++ b/snapshot/win/process_reader_win.h
@@ -23,6 +23,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "util/misc/initialization_state_dcheck.h"
+#include "util/process/process_memory_win.h"
 #include "util/win/address_types.h"
 #include "util/win/process_info.h"
 
@@ -84,25 +85,8 @@
   //! \return `true` if the target task is a 64-bit process.
   bool Is64Bit() const { return process_info_.Is64Bit(); }
 
-  //! \brief Attempts to read \a num_bytes bytes from the target process
-  //!     starting at address \a at into \a into.
-  //!
-  //! \return `true` if the entire region could be read, or `false` with an
-  //!     error logged.
-  //!
-  //! \sa ReadAvailableMemory
-  bool ReadMemory(WinVMAddress at, WinVMSize num_bytes, void* into) const;
-
-  //! \brief Attempts to read \a num_bytes bytes from the target process
-  //!     starting at address \a at into \a into. If some of the specified range
-  //!     is not accessible, reads up to the first inaccessible byte.
-  //!
-  //! \return The actual number of bytes read.
-  //!
-  //! \sa ReadMemory
-  WinVMSize ReadAvailableMemory(WinVMAddress at,
-                                WinVMSize num_bytes,
-                                void* into) const;
+  //! \brief Return a memory reader for the target process.
+  const ProcessMemoryWin* Memory() const { return &process_memory_; }
 
   //! \brief Determines the target process' start time.
   //!
@@ -145,6 +129,7 @@
 
   HANDLE process_;
   ProcessInfo process_info_;
+  ProcessMemoryWin process_memory_;
   std::vector<Thread> threads_;
   std::vector<ProcessInfo::Module> modules_;
   ProcessSuspensionState suspension_state_;
diff --git a/snapshot/win/process_reader_win_test.cc b/snapshot/win/process_reader_win_test.cc
index d0d4d96..de22008 100644
--- a/snapshot/win/process_reader_win_test.cc
+++ b/snapshot/win/process_reader_win_test.cc
@@ -17,11 +17,13 @@
 #include <windows.h>
 #include <string.h>
 
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "test/win/win_multiprocess.h"
 #include "util/misc/from_pointer_cast.h"
 #include "util/synchronization/semaphore.h"
 #include "util/thread/thread.h"
+#include "util/win/context_wrappers.h"
 #include "util/win/scoped_process_suspend.h"
 
 namespace crashpad {
@@ -42,8 +44,8 @@
   EXPECT_EQ(process_reader.GetProcessInfo().ProcessID(), GetCurrentProcessId());
 
   static constexpr char kTestMemory[] = "Some test memory";
-  char buffer[arraysize(kTestMemory)];
-  ASSERT_TRUE(process_reader.ReadMemory(
+  char buffer[base::size(kTestMemory)];
+  ASSERT_TRUE(process_reader.Memory()->Read(
       reinterpret_cast<uintptr_t>(kTestMemory), sizeof(kTestMemory), &buffer));
   EXPECT_STREQ(kTestMemory, buffer);
 }
@@ -72,7 +74,7 @@
 
     char buffer[sizeof(kTestMemory)];
     ASSERT_TRUE(
-        process_reader.ReadMemory(address, sizeof(kTestMemory), &buffer));
+        process_reader.Memory()->Read(address, sizeof(kTestMemory), &buffer));
     EXPECT_EQ(strcmp(kTestMemory, buffer), 0);
   }
 
@@ -106,12 +108,7 @@
   ASSERT_GE(threads.size(), 1u);
 
   EXPECT_EQ(threads[0].id, GetCurrentThreadId());
-#if defined(ARCH_CPU_64_BITS)
-  EXPECT_NE(threads[0].context.native.Rip, 0u);
-#else
-  EXPECT_NE(threads[0].context.native.Eip, 0u);
-#endif
-
+  EXPECT_NE(ProgramCounterFromCONTEXT(&threads[0].context.native), nullptr);
   EXPECT_EQ(threads[0].suspend_count, 0u);
 }
 
@@ -133,7 +130,7 @@
 
     void ThreadMain() override {
       done_->Wait();
-    };
+    }
 
    private:
     Semaphore* done_;
@@ -188,7 +185,7 @@
     // the pipe.
     CheckedReadFileAtEOF(ReadPipeHandle());
 
-    for (size_t i = 0; i < arraysize(threads); ++i)
+    for (size_t i = 0; i < base::size(threads); ++i)
       done.Signal();
     for (auto& thread : threads)
       thread.Join();
diff --git a/snapshot/win/process_snapshot_win.cc b/snapshot/win/process_snapshot_win.cc
index 289f50c..cafe7b4 100644
--- a/snapshot/win/process_snapshot_win.cc
+++ b/snapshot/win/process_snapshot_win.cc
@@ -21,8 +21,10 @@
 #include <utility>
 
 #include "base/logging.h"
-#include "base/strings/stringprintf.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
 #include "util/misc/from_pointer_cast.h"
 #include "util/misc/time.h"
 #include "util/win/nt_internals.h"
@@ -63,9 +65,9 @@
 
   if (exception_information_address != 0) {
     ExceptionInformation exception_information = {};
-    if (!process_reader_.ReadMemory(exception_information_address,
-                                    sizeof(exception_information),
-                                    &exception_information)) {
+    if (!process_reader_.Memory()->Read(exception_information_address,
+                                        sizeof(exception_information),
+                                        &exception_information)) {
       LOG(WARNING) << "ReadMemory ExceptionInformation failed";
       return false;
     }
@@ -121,12 +123,12 @@
   *options = options_;
 }
 
-pid_t ProcessSnapshotWin::ProcessID() const {
+crashpad::ProcessID ProcessSnapshotWin::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_reader_.GetProcessInfo().ProcessID();
 }
 
-pid_t ProcessSnapshotWin::ParentProcessID() const {
+crashpad::ProcessID ProcessSnapshotWin::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_reader_.GetProcessInfo().ParentProcessID();
 }
@@ -232,6 +234,11 @@
   return extra_memory;
 }
 
+const ProcessMemory* ProcessSnapshotWin::Memory() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return process_reader_.Memory();
+}
+
 void ProcessSnapshotWin::InitializeThreads(
     bool gather_indirectly_referenced_memory,
     uint32_t indirectly_referenced_memory_cap) {
@@ -271,7 +278,7 @@
   // and 32-reading-32, so at the moment, we simply do not retrieve unloaded
   // modules for 64-reading-32. See https://crashpad.chromium.org/bug/89.
 
-#if defined(ARCH_CPU_X86_64)
+#if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
   if (!process_reader_.Is64Bit()) {
     LOG(ERROR)
         << "reading unloaded modules across bitness not currently supported";
@@ -298,9 +305,9 @@
       FromPointerCast<WinVMAddress>(event_trace_address);
 
   Traits::Pointer pointer_to_array;
-  if (!process_reader_.ReadMemory(address_in_target_process,
-                                  sizeof(pointer_to_array),
-                                  &pointer_to_array)) {
+  if (!process_reader_.Memory()->Read(address_in_target_process,
+                                      sizeof(pointer_to_array),
+                                      &pointer_to_array)) {
     LOG(ERROR) << "failed to read target address";
     return;
   }
@@ -311,7 +318,7 @@
 
   const size_t data_size = *element_size * *element_count;
   std::vector<uint8_t> data(data_size);
-  if (!process_reader_.ReadMemory(pointer_to_array, data_size, &data[0])) {
+  if (!process_reader_.Memory()->Read(pointer_to_array, data_size, &data[0])) {
     LOG(ERROR) << "failed to read unloaded module data";
     return;
   }
@@ -328,7 +335,7 @@
           uet.TimeDateStamp,
           base::UTF16ToUTF8(base::StringPiece16(
               uet.ImageName,
-              wcsnlen(uet.ImageName, arraysize(uet.ImageName))))));
+              wcsnlen(uet.ImageName, base::size(uet.ImageName))))));
     }
   }
 }
@@ -377,14 +384,15 @@
   AddMemorySnapshot(peb_address, peb_size, &extra_memory_);
 
   process_types::PEB<Traits> peb_data;
-  if (!process_reader_.ReadMemory(peb_address, peb_size, &peb_data)) {
+  if (!process_reader_.Memory()->Read(
+          peb_address, base::checked_cast<size_t>(peb_size), &peb_data)) {
     LOG(ERROR) << "ReadMemory PEB";
     return;
   }
 
   process_types::PEB_LDR_DATA<Traits> peb_ldr_data;
   AddMemorySnapshot(peb_data.Ldr, sizeof(peb_ldr_data), &extra_memory_);
-  if (!process_reader_.ReadMemory(
+  if (!process_reader_.Memory()->Read(
           peb_data.Ldr, sizeof(peb_ldr_data), &peb_ldr_data)) {
     LOG(ERROR) << "ReadMemory PEB_LDR_DATA";
   } else {
@@ -406,9 +414,9 @@
   }
 
   process_types::RTL_USER_PROCESS_PARAMETERS<Traits> process_parameters;
-  if (!process_reader_.ReadMemory(peb_data.ProcessParameters,
-                                  sizeof(process_parameters),
-                                  &process_parameters)) {
+  if (!process_reader_.Memory()->Read(peb_data.ProcessParameters,
+                                      sizeof(process_parameters),
+                                      &process_parameters)) {
     LOG(ERROR) << "ReadMemory RTL_USER_PROCESS_PARAMETERS";
     return;
   }
@@ -451,7 +459,7 @@
 void ProcessSnapshotWin::AddMemorySnapshot(
     WinVMAddress address,
     WinVMSize size,
-    std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into) {
+    std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) {
   if (size == 0)
     return;
 
@@ -472,14 +480,14 @@
     }
   }
 
-  into->push_back(std::make_unique<internal::MemorySnapshotWin>());
-  into->back()->Initialize(&process_reader_, address, size);
+  into->push_back(std::make_unique<internal::MemorySnapshotGeneric>());
+  into->back()->Initialize(process_reader_.Memory(), address, size);
 }
 
 template <class Traits>
 void ProcessSnapshotWin::AddMemorySnapshotForUNICODE_STRING(
     const process_types::UNICODE_STRING<Traits>& us,
-    std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into) {
+    std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) {
   AddMemorySnapshot(us.Buffer, us.Length, into);
 }
 
@@ -487,7 +495,7 @@
 void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY(
     const process_types::LIST_ENTRY<Traits>& le,
     size_t offset_of_member,
-    std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into) {
+    std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) {
   // Walk the doubly-linked list of entries, adding the list memory itself, as
   // well as pointed-to strings.
   typename Traits::Pointer last = le.Blink;
@@ -496,7 +504,7 @@
   for (;;) {
     // |cur| is the pointer to LIST_ENTRY embedded in the LDR_DATA_TABLE_ENTRY.
     // So we need to offset back to the beginning of the structure.
-    if (!process_reader_.ReadMemory(
+    if (!process_reader_.Memory()->Read(
             cur - offset_of_member, sizeof(entry), &entry)) {
       return;
     }
@@ -520,16 +528,16 @@
   // be more than enough.
   std::wstring env_block;
   env_block.resize(32768);
-  WinVMSize bytes_read = process_reader_.ReadAvailableMemory(
+  size_t bytes_read = process_reader_.Memory()->ReadAvailableMemory(
       start_of_environment_block,
       env_block.size() * sizeof(env_block[0]),
       &env_block[0]);
   env_block.resize(
       static_cast<unsigned int>(bytes_read / sizeof(env_block[0])));
   static constexpr wchar_t terminator[] = {0, 0};
-  size_t at = env_block.find(std::wstring(terminator, arraysize(terminator)));
+  size_t at = env_block.find(std::wstring(terminator, base::size(terminator)));
   if (at != std::wstring::npos)
-    env_block.resize(at + arraysize(terminator));
+    env_block.resize(at + base::size(terminator));
 
   return env_block.size() * sizeof(env_block[0]);
 }
@@ -537,13 +545,13 @@
 template <class Traits>
 void ProcessSnapshotWin::ReadLock(
     WinVMAddress start,
-    std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into) {
+    std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into) {
   // We're walking the RTL_CRITICAL_SECTION_DEBUG ProcessLocksList, but starting
   // from an actual RTL_CRITICAL_SECTION, so start by getting to the first
   // RTL_CRITICAL_SECTION_DEBUG.
 
   process_types::RTL_CRITICAL_SECTION<Traits> critical_section;
-  if (!process_reader_.ReadMemory(
+  if (!process_reader_.Memory()->Read(
           start, sizeof(critical_section), &critical_section)) {
     LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION";
     return;
diff --git a/snapshot/win/process_snapshot_win.h b/snapshot/win/process_snapshot_win.h
index 9250c13..8b0bf52 100644
--- a/snapshot/win/process_snapshot_win.h
+++ b/snapshot/win/process_snapshot_win.h
@@ -31,6 +31,7 @@
 #include "snapshot/exception_snapshot.h"
 #include "snapshot/memory_map_region_snapshot.h"
 #include "snapshot/memory_snapshot.h"
+#include "snapshot/memory_snapshot_generic.h"
 #include "snapshot/module_snapshot.h"
 #include "snapshot/process_snapshot.h"
 #include "snapshot/system_snapshot.h"
@@ -38,12 +39,12 @@
 #include "snapshot/unloaded_module_snapshot.h"
 #include "snapshot/win/exception_snapshot_win.h"
 #include "snapshot/win/memory_map_region_snapshot_win.h"
-#include "snapshot/win/memory_snapshot_win.h"
 #include "snapshot/win/module_snapshot_win.h"
 #include "snapshot/win/system_snapshot_win.h"
 #include "snapshot/win/thread_snapshot_win.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/misc/uuid.h"
+#include "util/process/process_id.h"
 #include "util/win/address_types.h"
 #include "util/win/process_structs.h"
 
@@ -112,8 +113,8 @@
 
   // ProcessSnapshot:
 
-  pid_t ProcessID() const override;
-  pid_t ParentProcessID() const override;
+  crashpad::ProcessID ProcessID() const override;
+  crashpad::ProcessID ParentProcessID() const override;
   void SnapshotTime(timeval* snapshot_time) const override;
   void ProcessStartTime(timeval* start_time) const override;
   void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
@@ -129,6 +130,7 @@
   std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
   std::vector<HandleSnapshot> Handles() const override;
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
+  const ProcessMemory* Memory() const override;
 
  private:
   // Initializes threads_ on behalf of Initialize().
@@ -152,18 +154,18 @@
   void AddMemorySnapshot(
       WinVMAddress address,
       WinVMSize size,
-      std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into);
+      std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into);
 
   template <class Traits>
   void AddMemorySnapshotForUNICODE_STRING(
       const process_types::UNICODE_STRING<Traits>& us,
-      std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into);
+      std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into);
 
   template <class Traits>
   void AddMemorySnapshotForLdrLIST_ENTRY(
       const process_types::LIST_ENTRY<Traits>& le,
       size_t offset_of_member,
-      std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into);
+      std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into);
 
   WinVMSize DetermineSizeOfEnvironmentBlock(
       WinVMAddress start_of_environment_block);
@@ -173,10 +175,10 @@
   template <class Traits>
   void ReadLock(
       WinVMAddress start,
-      std::vector<std::unique_ptr<internal::MemorySnapshotWin>>* into);
+      std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>>* into);
 
   internal::SystemSnapshotWin system_;
-  std::vector<std::unique_ptr<internal::MemorySnapshotWin>> extra_memory_;
+  std::vector<std::unique_ptr<internal::MemorySnapshotGeneric>> extra_memory_;
   std::vector<std::unique_ptr<internal::ThreadSnapshotWin>> threads_;
   std::vector<std::unique_ptr<internal::ModuleSnapshotWin>> modules_;
   std::vector<UnloadedModuleSnapshot> unloaded_modules_;
diff --git a/snapshot/win/process_snapshot_win_test.cc b/snapshot/win/process_snapshot_win_test.cc
index 8a3e6a4..7192aa8 100644
--- a/snapshot/win/process_snapshot_win_test.cc
+++ b/snapshot/win/process_snapshot_win_test.cc
@@ -20,7 +20,6 @@
 #include "snapshot/win/pe_image_reader.h"
 #include "snapshot/win/process_reader_win.h"
 #include "test/errors.h"
-#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -120,7 +119,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ProcessSnapshotTest, CrashpadInfoChildWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestImageReaderChild(TestPaths::Architecture::k32Bit);
diff --git a/snapshot/win/process_subrange_reader.cc b/snapshot/win/process_subrange_reader.cc
index 996fbdf..124c5d0 100644
--- a/snapshot/win/process_subrange_reader.cc
+++ b/snapshot/win/process_subrange_reader.cc
@@ -15,6 +15,7 @@
 #include "snapshot/win/process_subrange_reader.h"
 
 #include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "snapshot/win/process_reader_win.h"
 
 namespace crashpad {
@@ -82,7 +83,8 @@
     return false;
   }
 
-  return process_reader_->ReadMemory(address, size, into);
+  return process_reader_->Memory()->Read(
+      address, base::checked_cast<size_t>(size), into);
 }
 
 bool ProcessSubrangeReader::InitializeInternal(ProcessReaderWin* process_reader,
diff --git a/snapshot/win/system_snapshot_win.cc b/snapshot/win/system_snapshot_win.cc
index 74f34a7..f07c4f6 100644
--- a/snapshot/win/system_snapshot_win.cc
+++ b/snapshot/win/system_snapshot_win.cc
@@ -26,7 +26,9 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
 #include "util/win/module_version.h"
+#include "util/win/scoped_registry_key.h"
 
 namespace crashpad {
 
@@ -75,11 +77,9 @@
       os_version_minor_(0),
       os_version_bugfix_(0),
       os_server_(false),
-      initialized_() {
-}
+      initialized_() {}
 
-SystemSnapshotWin::~SystemSnapshotWin() {
-}
+SystemSnapshotWin::~SystemSnapshotWin() {}
 
 void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
@@ -125,13 +125,20 @@
 CPUArchitecture SystemSnapshotWin::GetCPUArchitecture() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   return process_reader_->Is64Bit() ? kCPUArchitectureX86_64
                                     : kCPUArchitectureX86;
+#elif defined(ARCH_CPU_ARM64)
+  return kCPUArchitectureARM64;
+#else
+#error Unsupported Windows Arch
+#endif
 }
 
 uint32_t SystemSnapshotWin::CPURevision() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   uint32_t raw = CPUX86Signature();
   uint8_t stepping = raw & 0xf;
   uint8_t model = (raw & 0xf0) >> 4;
@@ -149,6 +156,14 @@
   uint16_t adjusted_family = family + extended_family;
   uint8_t adjusted_model = model + (extended_model << 4);
   return (adjusted_family << 16) | (adjusted_model << 8) | stepping;
+#elif defined(ARCH_CPU_ARM64)
+  SYSTEM_INFO system_info;
+  GetSystemInfo(&system_info);
+
+  return system_info.wProcessorRevision;
+#else
+#error Unsupported Windows Arch
+#endif
 }
 
 uint8_t SystemSnapshotWin::CPUCount() const {
@@ -166,6 +181,7 @@
 std::string SystemSnapshotWin::CPUVendor() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4];
   __cpuid(cpu_info, 0);
   char vendor[12];
@@ -173,6 +189,42 @@
   *reinterpret_cast<int*>(vendor + 4) = cpu_info[3];
   *reinterpret_cast<int*>(vendor + 8) = cpu_info[2];
   return std::string(vendor, sizeof(vendor));
+#elif defined(ARCH_CPU_ARM64)
+  HKEY key;
+
+  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+                   L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
+                   0,
+                   KEY_QUERY_VALUE,
+                   &key) != ERROR_SUCCESS) {
+    return std::string();
+  }
+
+  crashpad::ScopedRegistryKey scoped_key(key);
+  DWORD type;
+  wchar_t vendor_identifier[1024];
+  DWORD vendor_identifier_size = sizeof(vendor_identifier);
+
+  if (RegQueryValueEx(key,
+                      L"VendorIdentifier",
+                      nullptr,
+                      &type,
+                      reinterpret_cast<BYTE*>(vendor_identifier),
+                      &vendor_identifier_size) != ERROR_SUCCESS ||
+      type != REG_SZ) {
+    return std::string();
+  }
+
+  std::string return_value;
+  DCHECK_EQ(vendor_identifier_size % sizeof(wchar_t), 0u);
+  base::UTF16ToUTF8(vendor_identifier,
+                    vendor_identifier_size / sizeof(wchar_t),
+                    &return_value);
+
+  return return_value.c_str();
+#else
+#error Unsupported Windows Arch
+#endif
 }
 
 void SystemSnapshotWin::CPUFrequency(uint64_t* current_hz,
@@ -212,36 +264,52 @@
 uint32_t SystemSnapshotWin::CPUX86Signature() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4];
   // We will never run on any processors that don't support at least function 1.
   __cpuid(cpu_info, 1);
   return cpu_info[0];
+#else
+  NOTREACHED();
+  return 0;
+#endif
 }
 
 uint64_t SystemSnapshotWin::CPUX86Features() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4];
   // We will never run on any processors that don't support at least function 1.
   __cpuid(cpu_info, 1);
   return (static_cast<uint64_t>(cpu_info[2]) << 32) |
          static_cast<uint64_t>(cpu_info[3]);
+#else
+  NOTREACHED();
+  return 0;
+#endif
 }
 
 uint64_t SystemSnapshotWin::CPUX86ExtendedFeatures() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4];
   // We will never run on any processors that don't support at least extended
   // function 1.
   __cpuid(cpu_info, 0x80000001);
   return (static_cast<uint64_t>(cpu_info[2]) << 32) |
          static_cast<uint64_t>(cpu_info[3]);
+#else
+  NOTREACHED();
+  return 0;
+#endif
 }
 
 uint32_t SystemSnapshotWin::CPUX86Leaf7Features() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4];
 
   // Make sure leaf 7 can be called.
@@ -251,11 +319,16 @@
 
   __cpuidex(cpu_info, 7, 0);
   return cpu_info[1];
+#else
+  NOTREACHED();
+  return 0;
+#endif
 }
 
 bool SystemSnapshotWin::CPUX86SupportsDAZ() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
+#if defined(ARCH_CPU_X86_FAMILY)
   // The correct way to check for denormals-as-zeros (DAZ) support is to examine
   // mxcsr mask, which can be done with fxsave. See Intel Software Developer's
   // Manual, Volume 1: Basic Architecture (253665-051), 11.6.3 "Checking for the
@@ -277,6 +350,10 @@
 
   // Test the DAZ bit.
   return (mxcsr_mask & (1 << 6)) != 0;
+#else
+  NOTREACHED();
+  return 0;
+#endif
 }
 
 SystemSnapshot::OperatingSystem SystemSnapshotWin::GetOperatingSystem() const {
diff --git a/snapshot/win/system_snapshot_win_test.cc b/snapshot/win/system_snapshot_win_test.cc
index c87a6a6..d93c654 100644
--- a/snapshot/win/system_snapshot_win_test.cc
+++ b/snapshot/win/system_snapshot_win_test.cc
@@ -60,6 +60,10 @@
   EXPECT_EQ(cpu_architecture, kCPUArchitectureX86);
 #elif defined(ARCH_CPU_X86_64)
   EXPECT_EQ(cpu_architecture, kCPUArchitectureX86_64);
+#elif defined(ARCH_CPU_ARM64)
+  EXPECT_EQ(cpu_architecture, kCPUArchitectureARM64);
+#else
+#error Unsupported Windows Arch
 #endif
 }
 
@@ -69,17 +73,22 @@
 
 TEST_F(SystemSnapshotWinTest, CPUVendor) {
   std::string cpu_vendor = system_snapshot().CPUVendor();
-
-  // There are a variety of other values, but we don't expect to run our tests
-  // on them.
+#if defined(ARCH_CPU_X86_FAMILY)
   EXPECT_TRUE(cpu_vendor == "GenuineIntel" || cpu_vendor == "AuthenticAMD");
+#elif defined(ARCH_CPU_ARM64)
+  EXPECT_FALSE(cpu_vendor.empty());
+#else
+#error Unsupported Windows Arch
+#endif
 }
 
+#if defined(ARCH_CPU_X86_FAMILY)
 TEST_F(SystemSnapshotWinTest, CPUX86SupportsDAZ) {
   // Most SSE2+ machines support Denormals-Are-Zero. This may fail if run on
   // older machines.
   EXPECT_TRUE(system_snapshot().CPUX86SupportsDAZ());
 }
+#endif
 
 TEST_F(SystemSnapshotWinTest, GetOperatingSystem) {
   EXPECT_EQ(system_snapshot().GetOperatingSystem(),
diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc
index 3b927f9..02cc63c 100644
--- a/snapshot/win/thread_snapshot_win.cc
+++ b/snapshot/win/thread_snapshot_win.cc
@@ -48,22 +48,27 @@
   if (process_reader->GetProcessInfo().LoggingRangeIsFullyReadable(
           CheckedRange<WinVMAddress, WinVMSize>(thread_.stack_region_address,
                                                 thread_.stack_region_size))) {
-    stack_.Initialize(process_reader,
+    stack_.Initialize(process_reader->Memory(),
                       thread_.stack_region_address,
                       thread_.stack_region_size);
   } else {
-    stack_.Initialize(process_reader, 0, 0);
+    stack_.Initialize(process_reader->Memory(), 0, 0);
   }
 
   if (process_reader->GetProcessInfo().LoggingRangeIsFullyReadable(
           CheckedRange<WinVMAddress, WinVMSize>(thread_.teb_address,
                                                 thread_.teb_size))) {
-    teb_.Initialize(process_reader, thread_.teb_address, thread_.teb_size);
+    teb_.Initialize(
+        process_reader->Memory(), thread_.teb_address, thread_.teb_size);
   } else {
-    teb_.Initialize(process_reader, 0, 0);
+    teb_.Initialize(process_reader->Memory(), 0, 0);
   }
 
-#if defined(ARCH_CPU_X86_64)
+#if defined(ARCH_CPU_X86)
+  context_.architecture = kCPUArchitectureX86;
+  context_.x86 = &context_union_.x86;
+  InitializeX86Context(process_reader_thread.context.native, context_.x86);
+#elif defined(ARCH_CPU_X86_64)
   if (process_reader->Is64Bit()) {
     context_.architecture = kCPUArchitectureX86_64;
     context_.x86_64 = &context_union_.x86_64;
@@ -73,11 +78,13 @@
     context_.x86 = &context_union_.x86;
     InitializeX86Context(process_reader_thread.context.wow64, context_.x86);
   }
+#elif defined(ARCH_CPU_ARM64)
+  context_.architecture = kCPUArchitectureARM64;
+  context_.arm64 = &context_union_.arm64;
+  InitializeARM64Context(process_reader_thread.context.native, context_.arm64);
 #else
-  context_.architecture = kCPUArchitectureX86;
-  context_.x86 = &context_union_.x86;
-  InitializeX86Context(process_reader_thread.context.native, context_.x86);
-#endif  // ARCH_CPU_X86_64
+#error Unsupported Windows Arch
+#endif  // ARCH_CPU_X86
 
   CaptureMemoryDelegateWin capture_memory_delegate(
       process_reader,
diff --git a/snapshot/win/thread_snapshot_win.h b/snapshot/win/thread_snapshot_win.h
index 273fc7f..64ec43d 100644
--- a/snapshot/win/thread_snapshot_win.h
+++ b/snapshot/win/thread_snapshot_win.h
@@ -24,8 +24,8 @@
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/memory_snapshot.h"
+#include "snapshot/memory_snapshot_generic.h"
 #include "snapshot/thread_snapshot.h"
-#include "snapshot/win/memory_snapshot_win.h"
 #include "snapshot/win/process_reader_win.h"
 #include "util/misc/initialization_state_dcheck.h"
 
@@ -71,18 +71,22 @@
   std::vector<const MemorySnapshot*> ExtraMemory() const override;
 
  private:
-#if defined(ARCH_CPU_X86_FAMILY)
   union {
+#if defined(ARCH_CPU_X86_FAMILY)
     CPUContextX86 x86;
     CPUContextX86_64 x86_64;
-  } context_union_;
+#elif defined(ARCH_CPU_ARM64)
+    CPUContextARM64 arm64;
+#else
+#error Unsupported Windows Arch
 #endif
+  } context_union_;
   CPUContext context_;
-  MemorySnapshotWin stack_;
-  MemorySnapshotWin teb_;
+  MemorySnapshotGeneric stack_;
+  MemorySnapshotGeneric teb_;
   ProcessReaderWin::Thread thread_;
   InitializationStateDcheck initialized_;
-  std::vector<std::unique_ptr<MemorySnapshotWin>> pointed_to_memory_;
+  std::vector<std::unique_ptr<MemorySnapshotGeneric>> pointed_to_memory_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotWin);
 };
diff --git a/test/BUILD.gn b/test/BUILD.gn
index d1e6297..047c786 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -25,8 +25,6 @@
     "filesystem.cc",
     "filesystem.h",
     "gtest_death.h",
-    "gtest_disabled.cc",
-    "gtest_disabled.h",
     "hex_string.cc",
     "hex_string.h",
     "main_arguments.cc",
@@ -36,6 +34,7 @@
     "multiprocess_exec.h",
     "process_type.cc",
     "process_type.h",
+    "scoped_guarded_page.h",
     "scoped_module_handle.cc",
     "scoped_module_handle.h",
     "scoped_temp_dir.cc",
@@ -45,9 +44,12 @@
   ]
 
   if (crashpad_is_posix || crashpad_is_fuchsia) {
-    sources += [ "scoped_temp_dir_posix.cc" ]
+    sources += [
+      "scoped_guarded_page_posix.cc",
+      "scoped_temp_dir_posix.cc",
+    ]
 
-    if (!crashpad_is_fuchsia) {
+    if (!crashpad_is_fuchsia && !crashpad_is_ios) {
       sources += [
         "multiprocess_exec_posix.cc",
         "multiprocess_posix.cc",
@@ -55,19 +57,32 @@
     }
   }
 
+  if (crashpad_is_mac || crashpad_is_ios) {
+    sources += [
+      "mac/mach_errors.cc",
+      "mac/mach_errors.h",
+    ]
+  }
+
   if (crashpad_is_mac) {
     sources += [
       "mac/dyld.cc",
       "mac/dyld.h",
       "mac/exception_swallower.cc",
       "mac/exception_swallower.h",
-      "mac/mach_errors.cc",
-      "mac/mach_errors.h",
       "mac/mach_multiprocess.cc",
       "mac/mach_multiprocess.h",
     ]
   }
 
+  if (crashpad_is_ios) {
+    sources -= [
+      "multiprocess.h",
+      "multiprocess_exec.cc",
+      "multiprocess_exec.h",
+    ]
+  }
+
   if (crashpad_is_linux || crashpad_is_android) {
     set_sources_assignment_filter([])
     sources += [
@@ -81,6 +96,7 @@
   if (crashpad_is_win) {
     sources += [
       "multiprocess_exec_win.cc",
+      "scoped_guarded_page_win.cc",
       "scoped_temp_dir_win.cc",
       "win/child_launcher.cc",
       "win/child_launcher.h",
@@ -101,12 +117,10 @@
 
   configs += [
     "../build:crashpad_is_in_chromium",
-    "../build:crashpad_is_in_fuchsia"
+    "../build:crashpad_is_in_fuchsia",
   ]
 
-  data = [
-    "test_paths_test_data_root.txt",
-  ]
+  data = [ "test_paths_test_data_root.txt" ]
 
   deps = [
     "../compat",
@@ -123,14 +137,30 @@
     ]
   }
 
+  if (crashpad_is_ios) {
+    deps += [ ":test_bundle_data" ]
+  }
+
   if (crashpad_is_win) {
     libs = [ "shell32.lib" ]
   }
 
-  if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) {
-    deps += [
-      "//zircon/public/lib/fdio",
-    ]
+  if (crashpad_is_fuchsia) {
+    public_deps = [ "../third_party/fuchsia" ]
+    if (crashpad_is_in_fuchsia) {
+      deps += [ "//zircon/public/lib/fdio" ]
+    }
+  }
+}
+
+if (crashpad_is_ios) {
+  bundle_data("test_bundle_data") {
+    testonly = true
+
+    sources = [ "test_paths_test_data_root.txt" ]
+
+    outputs = [ "{{bundle_resources_dir}}/crashpad_test_data/" +
+                "{{source_root_relative_dir}}/{{source_file_part}}" ]
   }
 }
 
@@ -141,13 +171,14 @@
     "hex_string_test.cc",
     "main_arguments_test.cc",
     "multiprocess_exec_test.cc",
+    "scoped_guarded_page_test.cc",
     "scoped_temp_dir_test.cc",
     "test_paths_test.cc",
   ]
 
   # TODO(crbug.com/812974): Remove !crashpad_is_fuchsia when Fuchsia is no
   # longer treated as a posix platform.
-  if (crashpad_is_posix && !crashpad_is_fuchsia) {
+  if (crashpad_is_posix && !crashpad_is_fuchsia && !crashpad_is_ios) {
     sources += [ "multiprocess_posix_test.cc" ]
   }
 
@@ -155,6 +186,13 @@
     sources += [ "mac/mach_multiprocess_test.cc" ]
   }
 
+  if (crashpad_is_ios) {
+    sources -= [
+      "multiprocess_exec_test.cc",
+      "scoped_guarded_page_test.cc",
+    ]
+  }
+
   if (crashpad_is_win) {
     sources += [
       "win/win_child_process_test.cc",
@@ -171,26 +209,24 @@
     "../util",
   ]
 
-  data_deps = [
-    ":crashpad_test_test_multiprocess_exec_test_child",
-  ]
+  data_deps = [ ":crashpad_test_test_multiprocess_exec_test_child" ]
+
+  if (crashpad_is_ios) {
+    data_deps -= [ ":crashpad_test_test_multiprocess_exec_test_child" ]
+  }
 }
 
-crashpad_executable("crashpad_test_test_multiprocess_exec_test_child") {
-  sources = [
-    "multiprocess_exec_test_child.cc",
-  ]
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_test_test_multiprocess_exec_test_child") {
+    sources = [ "multiprocess_exec_test_child.cc" ]
 
-  deps = [
-    "../third_party/mini_chromium:base",
-  ]
+    deps = [ "../third_party/mini_chromium:base" ]
+  }
 }
 
 static_library("gmock_main") {
   testonly = true
-  sources = [
-    "gtest_main.cc",
-  ]
+  sources = [ "gtest_main.cc" ]
   configs += [ "../build:crashpad_is_in_chromium" ]
   defines = [ "CRASHPAD_TEST_LAUNCHER_GMOCK" ]
   deps = [
@@ -200,13 +236,17 @@
     "../third_party/mini_chromium:base",
     "../third_party/mini_chromium:base_test_support",
   ]
+  if (crashpad_is_android) {
+    deps += [ "../util" ]
+  }
+  if (crashpad_is_ios) {
+    deps += [ "ios:google_test_setup" ]
+  }
 }
 
 static_library("gtest_main") {
   testonly = true
-  sources = [
-    "gtest_main.cc",
-  ]
+  sources = [ "gtest_main.cc" ]
   configs += [ "../build:crashpad_is_in_chromium" ]
   defines = [ "CRASHPAD_TEST_LAUNCHER_GTEST" ]
   deps = [
@@ -215,4 +255,10 @@
     "../third_party/mini_chromium:base",
     "../third_party/mini_chromium:base_test_support",
   ]
+  if (crashpad_is_android) {
+    deps += [ "../util" ]
+  }
+  if (crashpad_is_ios) {
+    deps += [ "ios:google_test_setup" ]
+  }
 }
diff --git a/test/fuchsia_crashpad_tests.cmx b/test/fuchsia_crashpad_tests.cmx
new file mode 100644
index 0000000..d187098
--- /dev/null
+++ b/test/fuchsia_crashpad_tests.cmx
@@ -0,0 +1,24 @@
+{
+    "facets": {
+        "fuchsia.test": {
+            "system-services": [
+                "fuchsia.net.NameLookup",
+                "fuchsia.posix.socket.Provider"
+            ]
+        }
+    },
+    "program": {
+        "binary": "test/crashpad_tests"
+    },
+    "sandbox": {
+        "features": [
+            "isolated-temp",
+            "deprecated-ambient-replace-as-executable"
+        ],
+        "services": [
+            "fuchsia.net.NameLookup",
+            "fuchsia.posix.socket.Provider",
+            "fuchsia.process.Launcher"
+        ]
+    }
+}
diff --git a/test/gtest_death.h b/test/gtest_death.h
index 69f6361..f205a2f 100644
--- a/test/gtest_death.h
+++ b/test/gtest_death.h
@@ -25,7 +25,7 @@
 
 //! \file
 
-#if defined(OS_MACOSX) || DOXYGEN
+#if (defined(OS_MACOSX) && !defined(OS_IOS)) || DOXYGEN
 
 //! \brief Wraps the gtest `ASSERT_DEATH_IF_SUPPORTED()` macro to make
 //!     assertions about death caused by crashes.
@@ -73,14 +73,14 @@
                  regex);                                                  \
   } while (false)
 
-#else  // OS_MACOSX
+#else  // OS_MACOSX && !OS_IOS
 
 #define ASSERT_DEATH_CRASH(statement, regex) \
   ASSERT_DEATH_IF_SUPPORTED(statement, regex)
 #define EXPECT_DEATH_CRASH(statement, regex) \
   EXPECT_DEATH_IF_SUPPORTED(statement, regex)
 
-#endif  // OS_MACOSX
+#endif  // OS_MACOSX && !OS_IOS
 
 #if !(!defined(MINI_CHROMIUM_BASE_LOGGING_H_) && \
       defined(OFFICIAL_BUILD) &&                 \
diff --git a/test/gtest_disabled.cc b/test/gtest_disabled.cc
deleted file mode 100644
index fab6802..0000000
--- a/test/gtest_disabled.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2017 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.
-
-#include "test/gtest_disabled.h"
-
-#include <stdio.h>
-
-#include "base/format_macros.h"
-#include "base/logging.h"
-#include "base/strings/stringprintf.h"
-
-namespace crashpad {
-namespace test {
-
-namespace {
-
-DisabledTestGtestEnvironment* g_instance;
-
-}  // namespace
-
-// static
-DisabledTestGtestEnvironment* DisabledTestGtestEnvironment::Get() {
-  if (!g_instance) {
-    g_instance = new DisabledTestGtestEnvironment();
-  }
-  return g_instance;
-}
-
-void DisabledTestGtestEnvironment::DisabledTest() {
-  const testing::TestInfo* test_info =
-      testing::UnitTest::GetInstance()->current_test_info();
-  std::string disabled_test = base::StringPrintf(
-      "%s.%s", test_info->test_case_name(), test_info->name());
-
-  // Show a DISABLED message using a format similar to gtest, along with a hint
-  // explaining that OK or FAILED will also appear.
-  printf(
-      "This test has been disabled dynamically.\n"
-      "It will appear as both DISABLED and OK or FAILED.\n"
-      "[ DISABLED ] %s\n",
-      disabled_test.c_str());
-
-  disabled_tests_.push_back(disabled_test);
-}
-
-DisabledTestGtestEnvironment::DisabledTestGtestEnvironment()
-    : testing::Environment(),
-      disabled_tests_() {
-  DCHECK(!g_instance);
-}
-
-DisabledTestGtestEnvironment::~DisabledTestGtestEnvironment() {
-  DCHECK_EQ(this, g_instance);
-  g_instance = nullptr;
-}
-
-void DisabledTestGtestEnvironment::TearDown() {
-  if (!disabled_tests_.empty()) {
-    printf(
-        "[ DISABLED ] %" PRIuS " dynamically disabled test%s, listed below:\n"
-        "[ DISABLED ] %s also counted in PASSED or FAILED below.\n",
-        disabled_tests_.size(),
-        disabled_tests_.size() == 1 ? "" : "s",
-        disabled_tests_.size() == 1 ? "This test is" : "These tests are");
-    for (const std::string& disabled_test : disabled_tests_) {
-      printf("[ DISABLED ] %s\n", disabled_test.c_str());
-    }
-  }
-}
-
-}  // namespace test
-}  // namespace crashpad
diff --git a/test/gtest_disabled.h b/test/gtest_disabled.h
deleted file mode 100644
index 9415cba..0000000
--- a/test/gtest_disabled.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2017 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_TEST_GTEST_DISABLED_H_
-#define CRASHPAD_TEST_GTEST_DISABLED_H_
-
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "gtest/gtest.h"
-
-//! \file
-
-namespace crashpad {
-namespace test {
-
-//! \brief Provides support for dynamically disabled gtest tests.
-//!
-//! A test runner must register this with gtest as follows prior to calling
-//! `RUN_ALL_TESTS()`:
-//! \code
-//!   testing::AddGlobalTestEnvironment(
-//!       crashpad::test::DisabledTestGtestEnvironment::Get());
-//! \endcode
-class DisabledTestGtestEnvironment final : public testing::Environment {
- public:
-  //! \brief Returns the DisabledTestGtestEnvironment singleton instance,
-  //!     creating it if necessary.
-  static DisabledTestGtestEnvironment* Get();
-
-  //! \brief Displays a message about a test being disabled, and arranges for
-  //!     this information to be duplicated in TearDown().
-  //!
-  //! This method is for the internal use of the DISABLED_TEST() macro. Do not
-  //! call it directly, use the macro instead.
-  void DisabledTest();
-
- private:
-  DisabledTestGtestEnvironment();
-  ~DisabledTestGtestEnvironment() override;
-
-  // testing::Environment:
-  void TearDown() override;
-
-  std::vector<std::string> disabled_tests_;
-
-  DISALLOW_COPY_AND_ASSIGN(DisabledTestGtestEnvironment);
-};
-
-}  // namespace test
-}  // namespace crashpad
-
-//! \brief Displays a message about a test being disabled, and returns early.
-//!
-//! gtest only provides a mechanism for tests to be disabled statically, by
-//! prefixing test case names or test names with `DISABLED_`. When it is
-//! necessary to disable tests dynamically, gtest provides no assistance. This
-//! macro displays a message about the disabled test and returns early. The
-//! dynamically disabled test will also be displayed during gtest global test
-//! environment tear-down before the test executable exits.
-//!
-//! This macro may only be invoked from the context of a gtest test.
-//!
-//! There’s a long-standing <a
-//! href="https://groups.google.com/d/topic/googletestframework/Nwh3u7YFuN4">gtest
-//! feature request</a> to provide this functionality directly in gtest, but
-//! since it hasn’t been implemented, this macro provides a local mechanism to
-//! achieve it.
-#define DISABLED_TEST()                                                    \
-  do {                                                                     \
-    ::crashpad::test::DisabledTestGtestEnvironment::Get()->DisabledTest(); \
-    return;                                                                \
-  } while (false)
-
-#endif  // CRASHPAD_TEST_GTEST_DISABLED_H_
diff --git a/test/gtest_main.cc b/test/gtest_main.cc
index ebdbeb9..73cdddf 100644
--- a/test/gtest_main.cc
+++ b/test/gtest_main.cc
@@ -14,7 +14,6 @@
 
 #include "build/build_config.h"
 #include "gtest/gtest.h"
-#include "test/gtest_disabled.h"
 #include "test/main_arguments.h"
 #include "test/multiprocess_exec.h"
 
@@ -22,6 +21,14 @@
 #include "gmock/gmock.h"
 #endif  // CRASHPAD_TEST_LAUNCHER_GMOCK
 
+#if defined(OS_ANDROID)
+#include "util/linux/initial_signal_dispositions.h"
+#endif  // OS_ANDROID
+
+#if defined(OS_IOS)
+#include "test/ios/google_test_setup.h"
+#endif
+
 #if defined(OS_WIN)
 #include "test/win/win_child_process.h"
 #endif  // OS_WIN
@@ -34,6 +41,7 @@
 
 namespace {
 
+#if !defined(OS_IOS)
 bool GetChildTestFunctionName(std::string* child_func_name) {
   constexpr size_t arg_length =
       sizeof(crashpad::test::internal::kChildTestFunction) - 1;
@@ -46,19 +54,24 @@
   }
   return false;
 }
+#endif  // !OS_IOS
 
 }  // namespace
 
 int main(int argc, char* argv[]) {
-  crashpad::test::InitializeMainArguments(argc, argv);
-  testing::AddGlobalTestEnvironment(
-      crashpad::test::DisabledTestGtestEnvironment::Get());
+#if defined(OS_ANDROID)
+  crashpad::InitializeSignalDispositions();
+#endif  // OS_ANDROID
 
+  crashpad::test::InitializeMainArguments(argc, argv);
+
+#if !defined(OS_IOS)
   std::string child_func_name;
   if (GetChildTestFunctionName(&child_func_name)) {
     return crashpad::test::internal::CheckedInvokeMultiprocessChild(
         child_func_name);
   }
+#endif  // !OS_IOS
 
 #if defined(CRASHPAD_IS_IN_CHROMIUM)
 
@@ -68,6 +81,8 @@
   // runner.
   const bool use_chromium_test_launcher =
       !crashpad::test::WinChildProcess::IsChildProcess();
+#elif defined(OS_ANDROID)
+  constexpr bool use_chromium_test_launcher = false;
 #else  // OS_WIN
   constexpr bool use_chromium_test_launcher = true;
 #endif  // OS_WIN
@@ -79,7 +94,7 @@
     return base::LaunchUnitTests(
         argc,
         argv,
-        base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+        base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
   }
 
 #endif  // CRASHPAD_IS_IN_CHROMIUM
@@ -92,5 +107,12 @@
 #error #define CRASHPAD_TEST_LAUNCHER_GTEST or CRASHPAD_TEST_LAUNCHER_GMOCK
 #endif  // CRASHPAD_TEST_LAUNCHER_GMOCK
 
+#if defined(OS_IOS)
+  // iOS needs to run tests within the context of an app, so call a helper that
+  // invokes UIApplicationMain().  The application delegate will call
+  // RUN_ALL_TESTS() and exit before returning control to this function.
+  crashpad::test::IOSLaunchApplicationAndRunTests(argc, argv);
+#else
   return RUN_ALL_TESTS();
+#endif
 }
diff --git a/test/hex_string.h b/test/hex_string.h
index 435a692..b0d4453 100644
--- a/test/hex_string.h
+++ b/test/hex_string.h
@@ -29,8 +29,8 @@
 //!   uint8_t expected[10];
 //!   uint8_t observed[10];
 //!   // …
-//!   EXPECT_EQ(BytesToHexString(observed, arraysize(observed)),
-//!             BytesToHexString(expected, arraysize(expected)));
+//!   EXPECT_EQ(BytesToHexString(observed, base::size(observed)),
+//!             BytesToHexString(expected, base::size(expected)));
 //! \endcode
 std::string BytesToHexString(const void* bytes, size_t length);
 
diff --git a/test/hex_string_test.cc b/test/hex_string_test.cc
index 68745e6..3a09eb7 100644
--- a/test/hex_string_test.cc
+++ b/test/hex_string_test.cc
@@ -14,7 +14,7 @@
 
 #include "test/hex_string.h"
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 
 namespace crashpad {
@@ -25,7 +25,7 @@
   EXPECT_EQ(BytesToHexString(nullptr, 0), "");
 
   static constexpr char kBytes[] = "Abc123xyz \x0a\x7f\xf0\x9f\x92\xa9_";
-  EXPECT_EQ(BytesToHexString(kBytes, arraysize(kBytes)),
+  EXPECT_EQ(BytesToHexString(kBytes, base::size(kBytes)),
             "41626331323378797a200a7ff09f92a95f00");
 }
 
diff --git a/test/ios/BUILD.gn b/test/ios/BUILD.gn
new file mode 100644
index 0000000..d548a27
--- /dev/null
+++ b/test/ios/BUILD.gn
@@ -0,0 +1,80 @@
+# Copyright 2020 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.
+
+import("../../build/crashpad_buildconfig.gni")
+
+if (crashpad_is_in_chromium) {
+  import("//build/config/ios/rules.gni")
+} else if (crashpad_is_standalone) {
+  import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni")
+}
+
+group("all_tests") {
+  testonly = true
+  deps = [
+    ":ios_crash_xcuitests_module",
+    "host:ios_crash_xcuitests",
+  ]
+}
+
+source_set("google_test_runner_shared_headers") {
+  testonly = true
+  sources = [ "cptest_google_test_runner_delegate.h" ]
+}
+
+source_set("google_test_runner") {
+  testonly = true
+  sources = [ "cptest_google_test_runner.mm" ]
+  configs += [ "../..:crashpad_config" ]
+  deps = [
+    "../../build:ios_enable_arc",
+    "../../build:ios_xctest",
+    "../../test/ios:google_test_runner_shared_headers",
+    "../../third_party/mini_chromium:base",
+  ]
+  libs = [ "UIKit.framework" ]
+}
+
+source_set("google_test_setup") {
+  testonly = true
+  sources = [
+    "google_test_setup.h",
+    "google_test_setup.mm",
+  ]
+  configs += [ "../..:crashpad_config" ]
+  deps = [
+    ":google_test_runner_shared_headers",
+    "../../build:ios_enable_arc",
+    "../../third_party/gtest:gtest",
+    "../../third_party/mini_chromium:base",
+  ]
+  libs = [ "UIKit.framework" ]
+}
+
+source_set("xcuitests") {
+  testonly = true
+  sources = [ "crash_type_xctest.mm" ]
+  configs += [ "../..:crashpad_config" ]
+  deps = [
+    "../../build:ios_enable_arc",
+    "../../build:ios_xctest",
+    "../../test/ios/host:app_shared_sources",
+    "../../third_party/edo",
+  ]
+}
+
+ios_xcuitest_test("ios_crash_xcuitests_module") {
+  xcode_test_application_name = "ios_crash_xcuitests"
+  deps = [ ":xcuitests" ]
+}
diff --git a/test/ios/cptest_google_test_runner.mm b/test/ios/cptest_google_test_runner.mm
new file mode 100644
index 0000000..ae933fc
--- /dev/null
+++ b/test/ios/cptest_google_test_runner.mm
@@ -0,0 +1,41 @@
+// Copyright 2020 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.
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#include "base/logging.h"
+#import "test/ios/cptest_google_test_runner_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface CPTestGoogleTestRunner : XCTestCase
+@end
+
+@implementation CPTestGoogleTestRunner
+
+- (void)testRunGoogleTests {
+  id appDelegate = UIApplication.sharedApplication.delegate;
+  DCHECK([appDelegate
+      conformsToProtocol:@protocol(CPTestGoogleTestRunnerDelegate)]);
+
+  id<CPTestGoogleTestRunnerDelegate> runnerDelegate =
+      static_cast<id<CPTestGoogleTestRunnerDelegate>>(appDelegate);
+  DCHECK(runnerDelegate.supportsRunningGoogleTestsWithXCTest);
+  XCTAssertTrue([runnerDelegate runGoogleTests] == 0);
+}
+
+@end
diff --git a/test/ios/cptest_google_test_runner_delegate.h b/test/ios/cptest_google_test_runner_delegate.h
new file mode 100644
index 0000000..f88d63d
--- /dev/null
+++ b/test/ios/cptest_google_test_runner_delegate.h
@@ -0,0 +1,30 @@
+// Copyright 2020 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_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_
+#define CRASHPAD_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_
+
+@protocol CPTestGoogleTestRunnerDelegate
+
+// Returns YES if this delegate supports running GoogleTests via a call to
+// |runGoogleTests|.
+@property(nonatomic, readonly, assign)
+    BOOL supportsRunningGoogleTestsWithXCTest;
+
+// Runs GoogleTests and returns the final exit code.
+- (int)runGoogleTests;
+
+@end
+
+#endif  // CRASHPAD_TEST_IOS_CPTEST_GOOGLE_TEST_RUNNER_DELEGATE_H_
diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm
new file mode 100644
index 0000000..4529124
--- /dev/null
+++ b/test/ios/crash_type_xctest.mm
@@ -0,0 +1,241 @@
+// Copyright 2020 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.
+
+#import <XCTest/XCTest.h>
+
+#include <objc/runtime.h>
+#import "Service/Sources/EDOClientService.h"
+#import "test/ios/host/cptest_shared_object.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface CPTestTestCase : XCTestCase {
+  XCUIApplication* _app;
+}
+
+@end
+
+@implementation CPTestTestCase
+
+- (void)handleCrashUnderSymbol:(id)arg1 {
+  // For now, do nothing.  In the future this can be something testable.
+}
+
++ (void)setUp {
+  // Swizzle away the handleCrashUnderSymbol callback.  Without this, any time
+  // the host app is intentionally crashed, the test is immediately failed.
+  SEL originalSelector = NSSelectorFromString(@"handleCrashUnderSymbol:");
+  SEL swizzledSelector = @selector(handleCrashUnderSymbol:);
+
+  Method originalMethod = class_getInstanceMethod(
+      objc_getClass("XCUIApplicationImpl"), originalSelector);
+  Method swizzledMethod =
+      class_getInstanceMethod([self class], swizzledSelector);
+
+  method_exchangeImplementations(originalMethod, swizzledMethod);
+
+  // Override EDO default error handler.  Without this, the default EDO error
+  // handler will throw an error and fail the test.
+  [EDOClientService setErrorHandler:^(NSError* error){
+      // Do nothing.
+  }];
+}
+
+- (void)setUp {
+  _app = [[XCUIApplication alloc] init];
+  [_app launch];
+}
+
+- (void)testEDO {
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  NSString* result = [rootObject testEDO];
+  XCTAssertEqualObjects(result, @"crashpad");
+}
+
+- (void)testSegv {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashSegv];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testKillAbort {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashKillAbort];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testTrap {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashTrap];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testAbort {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashAbort];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testBadAccess {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashBadAccess];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testException {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashException];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testNSException {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashNSException];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testCrashUnreocgnizedSelectorAfterDelay {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashUnreocgnizedSelectorAfterDelay];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testCatchUIGestureEnvironmentNSException {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Tap the button with the string UIGestureEnvironmentException.
+  [_app.buttons[@"UIGestureEnvironmentException"] tap];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testCatchNSException {
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // The app should not crash
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject catchNSException];
+
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+- (void)testRecursion {
+  // TODO(justincohen): Crashpad iOS does not currently support stack type
+  // crashes.
+  return;
+
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+  // Crash the app.
+  CPTestSharedObject* rootObject = [EDOClientService rootObjectWithPort:12345];
+  [rootObject crashRecursion];
+
+  // Confirm the app is not running.
+  XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+  XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+  // TODO: Query the app for crash data
+  [_app launch];
+  XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
+@end
diff --git a/test/ios/google_test_setup.h b/test/ios/google_test_setup.h
new file mode 100644
index 0000000..98bb942
--- /dev/null
+++ b/test/ios/google_test_setup.h
@@ -0,0 +1,33 @@
+// Copyright 2019 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_TEST_IOS_GOOGLE_TEST_SETUP_
+#define CRASHPAD_TEST_IOS_GOOGLE_TEST_SETUP_
+
+namespace crashpad {
+namespace test {
+
+//! \brief Runs all registered tests in the context of a UIKit application.
+//!
+//! Invokes UIApplicationMain() to launch the iOS application and runs all
+//! registered tests after the application finishes
+//! launching. UIApplicationMain() brings up the main runloop and never returns,
+//! so therefore this function never returns either.  It invokes _exit() to
+//! terminate the application after tests have completed.
+void IOSLaunchApplicationAndRunTests(int argc, char* argv[]);
+
+}  // namespace test
+}  // namespace crashpad
+
+#endif  // CRASHPAD_TEST_IOS_GOOGLE_TEST_SETUP_
diff --git a/test/ios/google_test_setup.mm b/test/ios/google_test_setup.mm
new file mode 100644
index 0000000..109c4b6
--- /dev/null
+++ b/test/ios/google_test_setup.mm
@@ -0,0 +1,134 @@
+// Copyright 2019 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.
+
+#include "test/ios/google_test_setup.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#include "gtest/gtest.h"
+#include "test/ios/cptest_google_test_runner_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface UIApplication (Testing)
+- (void)_terminateWithStatus:(int)status;
+@end
+
+namespace {
+
+// The iOS watchdog timer will kill an app that doesn't spin the main event
+// loop often enough. This uses a Gtest TestEventListener to spin the current
+// loop after each test finishes. However, if any individual test takes too
+// long, it is still possible that the app will get killed.
+class IOSRunLoopListener : public testing::EmptyTestEventListener {
+ public:
+  virtual void OnTestEnd(const testing::TestInfo& test_info) {
+    @autoreleasepool {
+      // At the end of the test, spin the default loop for a moment.
+      NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001];
+      [[NSRunLoop currentRunLoop] runUntilDate:stop_date];
+    }
+  }
+};
+
+void RegisterTestEndListener() {
+  testing::TestEventListeners& listeners =
+      testing::UnitTest::GetInstance()->listeners();
+  listeners.Append(new IOSRunLoopListener);
+}
+
+}  // namespace
+
+@interface CPTestUnitTestApplicationDelegate
+    : NSObject <CPTestGoogleTestRunnerDelegate>
+@property(nonatomic, readwrite, strong) UIWindow* window;
+- (void)runTests;
+@end
+
+@implementation CPTestUnitTestApplicationDelegate
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
+  self.window.backgroundColor = UIColor.whiteColor;
+  [self.window makeKeyAndVisible];
+
+  UIViewController* controller = [[UIViewController alloc] init];
+  [self.window setRootViewController:controller];
+
+  // Add a label with the app name.
+  UILabel* label = [[UILabel alloc] initWithFrame:controller.view.bounds];
+  label.text = [[NSProcessInfo processInfo] processName];
+  label.textAlignment = NSTextAlignmentCenter;
+  label.textColor = UIColor.blackColor;
+  [controller.view addSubview:label];
+
+  // Queue up the test run.
+  if (![self supportsRunningGoogleTestsWithXCTest]) {
+    // When running in XCTest mode, XCTest will invoke |runGoogleTest| directly.
+    // Otherwise, schedule a call to |runTests|.
+    [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1];
+  }
+
+  return YES;
+}
+
+- (BOOL)supportsRunningGoogleTestsWithXCTest {
+  return getenv("XCTestConfigurationFilePath") != nullptr;
+}
+
+- (int)runGoogleTests {
+  RegisterTestEndListener();
+  int exitStatus = RUN_ALL_TESTS();
+  return exitStatus;
+}
+
+- (void)runTests {
+  DCHECK(![self supportsRunningGoogleTestsWithXCTest]);
+
+  int exitStatus = [self runGoogleTests];
+
+  // If a test app is too fast, it will exit before Instruments has has a
+  // a chance to initialize and no test results will be seen.
+  // TODO(crbug.com/137010): Figure out how much time is actually needed, and
+  // sleep only to make sure that much time has elapsed since launch.
+  [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
+  self.window = nil;
+
+  // Use the hidden selector to try and cleanly take down the app (otherwise
+  // things can think the app crashed even on a zero exit status).
+  UIApplication* application = [UIApplication sharedApplication];
+  [application _terminateWithStatus:exitStatus];
+
+  exit(exitStatus);
+}
+
+@end
+
+namespace crashpad {
+namespace test {
+
+void IOSLaunchApplicationAndRunTests(int argc, char* argv[]) {
+  @autoreleasepool {
+    int exit_status = UIApplicationMain(
+        argc, argv, nil, @"CPTestUnitTestApplicationDelegate");
+    exit(exit_status);
+  }
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/test/ios/host/BUILD.gn b/test/ios/host/BUILD.gn
new file mode 100644
index 0000000..cd6995a
--- /dev/null
+++ b/test/ios/host/BUILD.gn
@@ -0,0 +1,57 @@
+# Copyright 2020 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.
+
+import("../../../build/crashpad_buildconfig.gni")
+
+if (crashpad_is_in_chromium) {
+  import("//build/config/ios/rules.gni")
+} else if (crashpad_is_standalone) {
+  import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni")
+}
+
+source_set("app_shared_sources") {
+  testonly = true
+  sources = [ "cptest_shared_object.h" ]
+  configs += [ "../../..:crashpad_config" ]
+  deps = [ "../../../build:ios_enable_arc" ]
+  libs = [ "UIKit.framework" ]
+}
+
+static_library("app_host_sources") {
+  testonly = true
+  sources = [
+    "cptest_application_delegate.h",
+    "cptest_application_delegate.mm",
+    "cptest_crash_view_controller.h",
+    "cptest_crash_view_controller.mm",
+    "main.mm",
+  ]
+  configs += [ "../../..:crashpad_config" ]
+  deps = [
+    ":app_shared_sources",
+    "../../../build:ios_enable_arc",
+    "../../../client",
+    "../../../third_party/edo",
+  ]
+  libs = [
+    "Foundation.framework",
+    "UIKit.framework",
+  ]
+}
+
+ios_app_bundle("ios_crash_xcuitests") {
+  info_plist = "Info.plist"
+  testonly = true
+  deps = [ ":app_host_sources" ]
+}
diff --git a/test/ios/host/Info.plist b/test/ios/host/Info.plist
new file mode 100644
index 0000000..8944a2e
--- /dev/null
+++ b/test/ios/host/Info.plist
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleDisplayName</key>
+  <string>${PRODUCT_NAME}</string>
+  <key>CFBundleExecutable</key>
+  <string>${EXECUTABLE_NAME}</string>
+  <key>CFBundleIdentifier</key>
+  <string>${IOS_BUNDLE_ID_PREFIX}.gtest.${EXECUTABLE_NAME:rfc1034identifier}</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>${PRODUCT_NAME}</string>
+  <key>CFBundlePackageType</key>
+  <string>APPL</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>LSRequiresIPhoneOS</key>
+  <true/>
+  <key>UILaunchImages</key>
+  <array>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{320, 480}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{320, 568}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{375, 667}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{414, 736}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{375, 812}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{414, 896}</string>
+    </dict>
+  </array>
+  <key>UILaunchImages~ipad</key>
+  <array>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{768, 1024}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{1024, 1366}</string>
+    </dict>
+    <dict>
+      <key>UILaunchImageMinimumOSVersion</key>
+      <string>9.0</string>
+      <key>UILaunchImageName</key>
+      <string>Default</string>
+      <key>UILaunchImageSize</key>
+      <string>{832, 1114}</string>
+    </dict>
+  </array>
+  <key>UISupportedInterfaceOrientation</key>
+  <array>
+    <string>UIInterfaceOrientationPortrait</string>
+    <string>UIInterfaceOrientationLandscapeLeft</string>
+    <string>UIInterfaceOrientationLandscapeRight</string>
+  </array>
+</dict>
+</plist>
diff --git a/util/net/http_transport_none.cc b/test/ios/host/cptest_application_delegate.h
similarity index 60%
copy from util/net/http_transport_none.cc
copy to test/ios/host/cptest_application_delegate.h
index 1c08c1f..ca835f1 100644
--- a/util/net/http_transport_none.cc
+++ b/test/ios/host/cptest_application_delegate.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_
+#define CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_
 
-#include "base/logging.h"
+#import <UIKit/UIKit.h>
 
-namespace crashpad {
+@interface CPTestApplicationDelegate : UIResponder <UIApplicationDelegate>
+@end
 
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_TEST_IOS_HOST_CPTEST_APPLICATION_DELEGATE_H_
diff --git a/test/ios/host/cptest_application_delegate.mm b/test/ios/host/cptest_application_delegate.mm
new file mode 100644
index 0000000..80baf8b
--- /dev/null
+++ b/test/ios/host/cptest_application_delegate.mm
@@ -0,0 +1,126 @@
+// Copyright 2020 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.
+
+#import "test/ios/host/cptest_application_delegate.h"
+
+#include <dispatch/dispatch.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <vector>
+
+#import "Service/Sources/EDOHostNamingService.h"
+#import "Service/Sources/EDOHostService.h"
+#include "client/crashpad_client.h"
+#import "test/ios/host/cptest_crash_view_controller.h"
+#import "test/ios/host/cptest_shared_object.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation CPTestApplicationDelegate
+
+@synthesize window = _window;
+
+- (BOOL)application:(UIApplication*)application
+    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+  // Start up crashpad.
+  crashpad::CrashpadClient client;
+  client.StartCrashpadInProcessHandler();
+
+  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+  [self.window makeKeyAndVisible];
+  self.window.backgroundColor = UIColor.greenColor;
+
+  CPTestCrashViewController* controller =
+      [[CPTestCrashViewController alloc] init];
+  self.window.rootViewController = controller;
+
+  // Start up EDO.
+  [EDOHostService serviceWithPort:12345
+                       rootObject:[[CPTestSharedObject alloc] init]
+                            queue:dispatch_get_main_queue()];
+  return YES;
+}
+
+@end
+
+@implementation CPTestSharedObject
+
+- (NSString*)testEDO {
+  return @"crashpad";
+}
+
+- (void)crashBadAccess {
+  strcpy(nullptr, "bla");
+}
+
+- (void)crashKillAbort {
+  kill(getpid(), SIGABRT);
+}
+
+- (void)crashSegv {
+  long* zero = nullptr;
+  *zero = 0xc045004d;
+}
+
+- (void)crashTrap {
+  __builtin_trap();
+}
+
+- (void)crashAbort {
+  abort();
+}
+
+- (void)crashException {
+  std::vector<int> empty_vector = {};
+  empty_vector.at(42);
+}
+
+- (void)crashNSException {
+  // EDO has its own sinkhole.
+  dispatch_async(dispatch_get_main_queue(), ^{
+    NSArray* empty_array = @[];
+    [empty_array objectAtIndex:42];
+  });
+}
+
+- (void)catchNSException {
+  @try {
+    NSArray* empty_array = @[];
+    [empty_array objectAtIndex:42];
+  } @catch (NSException* exception) {
+  } @finally {
+  }
+}
+
+- (void)crashUnreocgnizedSelectorAfterDelay {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+  [self performSelector:@selector(does_not_exist) withObject:nil afterDelay:1];
+#pragma clang diagnostic pop
+}
+
+- (void)recurse {
+  [self recurse];
+}
+
+- (void)crashRecursion {
+  [self recurse];
+}
+
+@end
diff --git a/util/net/http_transport_none.cc b/test/ios/host/cptest_crash_view_controller.h
similarity index 61%
copy from util/net/http_transport_none.cc
copy to test/ios/host/cptest_crash_view_controller.h
index 1c08c1f..2a41e12 100644
--- a/util/net/http_transport_none.cc
+++ b/test/ios/host/cptest_crash_view_controller.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2020 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.
@@ -12,15 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
+#ifndef CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_
+#define CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_
 
-#include "base/logging.h"
+#import <UIKit/UIKit.h>
 
-namespace crashpad {
+@interface CPTestCrashViewController : UIViewController
+@end
 
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
-
-}  // namespace crashpad
+#endif  // CRASHPAD_TEST_IOS_HOST_CPTEST_CRASH_VIEW_CONTROLLER_H_
diff --git a/test/ios/host/cptest_crash_view_controller.mm b/test/ios/host/cptest_crash_view_controller.mm
new file mode 100644
index 0000000..c467722
--- /dev/null
+++ b/test/ios/host/cptest_crash_view_controller.mm
@@ -0,0 +1,65 @@
+// Copyright 2020 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.
+
+#import "test/ios/host/cptest_crash_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation CPTestCrashViewController
+
+- (void)loadView {
+  self.view = [[UIView alloc] init];
+
+  UIStackView* buttonStack = [[UIStackView alloc] init];
+  buttonStack.axis = UILayoutConstraintAxisVertical;
+  buttonStack.spacing = 6;
+
+  UIButton* button = [UIButton new];
+  [button setTitle:@"UIGestureEnvironmentException"
+          forState:UIControlStateNormal];
+  UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc]
+      initWithTarget:self
+              action:@selector(throwUIGestureEnvironmentException)];
+  [button addGestureRecognizer:tapGesture];
+  [button setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+  [buttonStack addArrangedSubview:button];
+
+  [self.view addSubview:buttonStack];
+
+  [buttonStack setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+  [NSLayoutConstraint activateConstraints:@[
+    [buttonStack.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
+    [buttonStack.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+    [buttonStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+    [buttonStack.trailingAnchor
+        constraintEqualToAnchor:self.view.trailingAnchor],
+  ]];
+}
+
+- (void)throwUIGestureEnvironmentException {
+  NSArray* empty_array = @[];
+  [empty_array objectAtIndex:42];
+}
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  self.view.backgroundColor = UIColor.redColor;
+}
+
+@end
diff --git a/test/ios/host/cptest_shared_object.h b/test/ios/host/cptest_shared_object.h
new file mode 100644
index 0000000..70c814b
--- /dev/null
+++ b/test/ios/host/cptest_shared_object.h
@@ -0,0 +1,55 @@
+// Copyright 2020 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_TEST_IOS_HOST_SHARED_OBJECT_H_
+#define CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_
+
+#import <UIKit/UIKit.h>
+
+@interface CPTestSharedObject : NSObject
+// Returns the string "crashpad" for testing EDO.
+- (NSString*)testEDO;
+
+// Triggers an EXC_BAD_ACCESS exception and crash.
+- (void)crashBadAccess;
+
+// Triggers a crash with a call to kill(SIGABRT).
+- (void)crashKillAbort;
+
+// Triggers a segfault crash.
+- (void)crashSegv;
+
+// Trigger a crash with a __builtin_trap.
+- (void)crashTrap;
+
+// Trigger a crash with an abort().
+- (void)crashAbort;
+
+// Trigger a crash with an uncaught exception.
+- (void)crashException;
+
+// Trigger a crash with an uncaught NSException.
+- (void)crashNSException;
+
+// Trigger an unrecognized selector after delay.
+- (void)crashUnreocgnizedSelectorAfterDelay;
+
+// Trigger a caught NSxception.
+- (void)catchNSException;
+
+// Trigger a crash with an infinite recursion.
+- (void)crashRecursion;
+@end
+
+#endif  // CRASHPAD_TEST_IOS_HOST_SHARED_OBJECT_H_
diff --git a/test/ios/host/main.mm b/test/ios/host/main.mm
new file mode 100644
index 0000000..a0ae5c4
--- /dev/null
+++ b/test/ios/host/main.mm
@@ -0,0 +1,30 @@
+// Copyright 2020 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.
+
+#import <UIKit/UIKit.h>
+
+#import "test/ios/host/cptest_application_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+int main(int argc, char* argv[]) {
+  NSString* appDelegateClassName;
+  @autoreleasepool {
+    // Setup code that might create autoreleased objects goes here.
+    appDelegateClassName = NSStringFromClass([CPTestApplicationDelegate class]);
+  }
+  return UIApplicationMain(argc, argv, nil, appDelegateClassName);
+}
diff --git a/test/linux/fake_ptrace_connection.cc b/test/linux/fake_ptrace_connection.cc
index 022b132..b03228a 100644
--- a/test/linux/fake_ptrace_connection.cc
+++ b/test/linux/fake_ptrace_connection.cc
@@ -92,5 +92,11 @@
   return memory_.get();
 }
 
+bool FakePtraceConnection::Threads(std::vector<pid_t>* threads) {
+  // TODO(jperaza): Implement this if/when it's needed.
+  NOTREACHED();
+  return false;
+}
+
 }  // namespace test
 }  // namespace crashpad
diff --git a/test/linux/fake_ptrace_connection.h b/test/linux/fake_ptrace_connection.h
index 6597c47..01a6f92 100644
--- a/test/linux/fake_ptrace_connection.h
+++ b/test/linux/fake_ptrace_connection.h
@@ -62,6 +62,9 @@
   //!     ADD_FAILURE() and returning `nullptr` on failure.
   ProcessMemory* Memory() override;
 
+  //! \todo Not yet implemented.
+  bool Threads(std::vector<pid_t>* threads) override;
+
  private:
   std::set<pid_t> attachments_;
   std::unique_ptr<ProcessMemoryLinux> memory_;
diff --git a/test/mac/exception_swallower.cc b/test/mac/exception_swallower.cc
index 18c5cf7..946938f 100644
--- a/test/mac/exception_swallower.cc
+++ b/test/mac/exception_swallower.cc
@@ -24,6 +24,7 @@
 #include "base/mac/scoped_mach_port.h"
 #include "base/strings/stringprintf.h"
 #include "handler/mac/exception_handler_server.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/exc_server_variants.h"
 #include "util/mach/exception_ports.h"
 #include "util/mach/mach_extensions.h"
diff --git a/test/mac/mach_errors.cc b/test/mac/mach_errors.cc
index b68448f..db8ed66 100644
--- a/test/mac/mach_errors.cc
+++ b/test/mac/mach_errors.cc
@@ -14,8 +14,6 @@
 
 #include "test/mac/mach_errors.h"
 
-#include <servers/bootstrap.h>
-
 #include "base/strings/stringprintf.h"
 
 namespace {
@@ -50,28 +48,5 @@
                             FormatMachErrorNumber(mach_err).c_str());
 }
 
-std::string BootstrapErrorMessage(kern_return_t bootstrap_err,
-                                  const std::string& base) {
-  switch (bootstrap_err) {
-    case BOOTSTRAP_SUCCESS:
-    case BOOTSTRAP_NOT_PRIVILEGED:
-    case BOOTSTRAP_NAME_IN_USE:
-    case BOOTSTRAP_UNKNOWN_SERVICE:
-    case BOOTSTRAP_SERVICE_ACTIVE:
-    case BOOTSTRAP_BAD_COUNT:
-    case BOOTSTRAP_NO_MEMORY:
-    case BOOTSTRAP_NO_CHILDREN:
-      // Show known bootstrap errors in decimal because that's how they're
-      // defined in <servers/bootstrap.h>.
-      return base::StringPrintf("%s%s (%d)",
-                                FormatBase(base).c_str(),
-                                bootstrap_strerror(bootstrap_err),
-                                bootstrap_err);
-
-    default:
-      return MachErrorMessage(bootstrap_err, base);
-  }
-}
-
 }  // namespace test
 }  // namespace crashpad
diff --git a/test/mac/mach_errors.h b/test/mac/mach_errors.h
index 9e43ef2..0afcdad 100644
--- a/test/mac/mach_errors.h
+++ b/test/mac/mach_errors.h
@@ -22,9 +22,9 @@
 namespace crashpad {
 namespace test {
 
-// These functions format messages in a similar way to the logging macros in
-// base/mac/mach_logging.h. They exist to interoperate with gtest assertions,
-// which don’t interoperate with logging but can be streamed to.
+// This function formats messages in a similar way to the Mach error logging
+// macros in base/mac/mach_logging.h. It exists to interoperate with gtest
+// assertions, which don’t interoperate with logging but can be streamed to.
 //
 // Where non-test code could do:
 //   MACH_CHECK(kr == KERN_SUCCESS, kr) << "vm_deallocate";
@@ -47,23 +47,6 @@
 std::string MachErrorMessage(mach_error_t mach_err,
                              const std::string& base = std::string());
 
-//! \brief Formats a bootstrap error message.
-//!
-//! The returned string will combine the \a base string, if supplied, with a
-//! textual and numeric description of the error.
-//!
-//! \param[in] bootstrap_err The bootstrap error code.
-//! \param[in] base A string to prepend to the error description.
-//!
-//! \return A string of the format `"Permission denied (1100)"` if \a
-//!     bootstrap_err has the value `BOOTSTRAP_NOT_PRIVILEGED` on a system where
-//!     this is defined to be 1100. If \a base is not empty, it will be
-//!     prepended to this string, separated by a colon. If \a bootstrap_err is
-//!     not a valid bootstrap error code, it will be interpreted as a Mach error
-//!     code in the manner of MachErrorMessage().
-std::string BootstrapErrorMessage(kern_return_t bootstrap_err,
-                                  const std::string& base = std::string());
-
 }  // namespace test
 }  // namespace crashpad
 
diff --git a/test/mac/mach_multiprocess.cc b/test/mac/mach_multiprocess.cc
index f29a8b0..59aa653 100644
--- a/test/mac/mach_multiprocess.cc
+++ b/test/mac/mach_multiprocess.cc
@@ -27,6 +27,7 @@
 #include "test/errors.h"
 #include "test/mac/mach_errors.h"
 #include "util/file/file_io.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/mach_extensions.h"
 #include "util/mach/mach_message.h"
 #include "util/misc/implicit_cast.h"
diff --git a/test/multiprocess.h b/test/multiprocess.h
index d027502..61df98b 100644
--- a/test/multiprocess.h
+++ b/test/multiprocess.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_TEST_MULTIPROCESS_H_
 #define CRASHPAD_TEST_MULTIPROCESS_H_
 
+#include <stdint.h>
 #include <sys/types.h>
 
 #include "base/macros.h"
@@ -26,7 +27,13 @@
 
 namespace internal {
 struct MultiprocessInfo;
-};
+}  // namespace internal
+
+#if defined(OS_FUCHSIA)
+using ReturnCodeType = int64_t;
+#else
+using ReturnCodeType = int;
+#endif
 
 //! \brief Manages a multiprocess test.
 //!
@@ -76,7 +83,7 @@
 
   //! \brief Sets the expected termination reason and code.
   //!
-  //! The default expected termination reasaon is
+  //! The default expected termination reason is
   //! TerminationReason::kTerminationNormal, and the default expected
   //! termination code is `EXIT_SUCCESS` (`0`).
   //!
@@ -91,7 +98,8 @@
   //!     TerminationReason::kTerminationSignal, this is the signal that is
   //!     expected to kill the child. On Linux platforms, SIG_DFL will be
   //!     installed for \a code in the child process.
-  void SetExpectedChildTermination(TerminationReason reason, int code);
+  void SetExpectedChildTermination(TerminationReason reason,
+                                   ReturnCodeType code);
 
 #if !defined(OS_WIN)
   //! \brief Sets termination reason and code appropriately for a child that
@@ -211,7 +219,7 @@
   virtual void MultiprocessChild() = 0;
 
   internal::MultiprocessInfo* info_;
-  int code_;
+  ReturnCodeType code_;
   TerminationReason reason_;
 
   DISALLOW_COPY_AND_ASSIGN(Multiprocess);
diff --git a/test/multiprocess_exec.h b/test/multiprocess_exec.h
index 84335b2..1168f80 100644
--- a/test/multiprocess_exec.h
+++ b/test/multiprocess_exec.h
@@ -122,7 +122,9 @@
   //!
   //! This method is only valid during the body of MultiprocessParent().
   //!
-  //! \return A platform-specific type representing the child process.
+  //! \return A platform-specific type representing the child process. This
+  //!     method can fail on macOS because access to a child's task port
+  //!     requires the task_for_pid entitlement.
   ProcessType ChildProcess();
 
  protected:
diff --git a/test/multiprocess_exec_fuchsia.cc b/test/multiprocess_exec_fuchsia.cc
index 95436b4..a1e2acf 100644
--- a/test/multiprocess_exec_fuchsia.cc
+++ b/test/multiprocess_exec_fuchsia.cc
@@ -16,13 +16,12 @@
 
 #include <lib/fdio/io.h>
 #include <lib/fdio/spawn.h>
-#include <zircon/process.h>
+#include <lib/zx/process.h>
+#include <lib/zx/time.h>
 #include <zircon/processargs.h>
-#include <zircon/syscalls.h>
 
 #include "base/files/scoped_file.h"
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/scoped_zx_handle.h"
 #include "gtest/gtest.h"
 
 namespace crashpad {
@@ -31,14 +30,12 @@
 namespace {
 
 void AddPipe(fdio_spawn_action_t* action, int target_fd, int* fd_out) {
-  zx_handle_t handle;
-  uint32_t id;
-  zx_status_t status = fdio_pipe_half(&handle, &id);
-  ZX_CHECK(status >= 0, status) << "fdio_pipe_half";
+  zx_handle_t handle = ZX_HANDLE_INVALID;
+  zx_status_t status = fdio_pipe_half(fd_out, &handle);
+  ZX_CHECK(status == ZX_OK, status) << "fdio_pipe_half";
   action->action = FDIO_SPAWN_ACTION_ADD_HANDLE;
-  action->h.id = PA_HND(PA_HND_TYPE(id), target_fd);
+  action->h.id = PA_HND(PA_FD, target_fd);
   action->h.handle = handle;
-  *fd_out = status;
 }
 
 }  // namespace
@@ -49,7 +46,7 @@
   MultiprocessInfo() {}
   base::ScopedFD stdin_write;
   base::ScopedFD stdout_read;
-  base::ScopedZxHandle child;
+  zx::process child;
 };
 
 }  // namespace internal
@@ -68,19 +65,14 @@
   // Wait until the child exits.
   zx_signals_t signals;
   ASSERT_EQ(
-      zx_object_wait_one(
-          info_->child.get(), ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals),
+      info_->child.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), &signals),
       ZX_OK);
   ASSERT_EQ(signals, ZX_TASK_TERMINATED);
 
   // Get the child's exit code.
   zx_info_process_t proc_info;
-  zx_status_t status = zx_object_get_info(info_->child.get(),
-                                          ZX_INFO_PROCESS,
-                                          &proc_info,
-                                          sizeof(proc_info),
-                                          nullptr,
-                                          nullptr);
+  zx_status_t status = info_->child.get_info(
+      ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr, nullptr);
   if (status != ZX_OK) {
     ZX_LOG(ERROR, status) << "zx_object_get_info";
     ADD_FAILURE() << "Unable to get exit code of child";
@@ -93,7 +85,7 @@
 }
 
 void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
-                                               int code) {
+                                               ReturnCodeType code) {
   EXPECT_EQ(info_, nullptr)
       << "SetExpectedChildTermination() must be called before Run()";
   reason_ = reason;
@@ -101,7 +93,8 @@
 }
 
 void Multiprocess::SetExpectedChildTerminationBuiltinTrap() {
-  SetExpectedChildTermination(kTerminationNormal, -1);
+  constexpr ReturnCodeType kExpectedReturnCode = ZX_TASK_RETCODE_EXCEPTION_KILL;
+  SetExpectedChildTermination(kTerminationNormal, kExpectedReturnCode);
 }
 
 Multiprocess::~Multiprocess() {
@@ -188,7 +181,7 @@
   uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO;
 
   char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
-  zx_handle_t child;
+  zx::process child;
   zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID,
                                       flags,
                                       command_.value().c_str(),
@@ -196,14 +189,14 @@
                                       nullptr,
                                       kActionCount,
                                       actions,
-                                      &child,
+                                      child.reset_and_get_address(),
                                       error_message);
   ZX_CHECK(status == ZX_OK, status) << "fdio_spawn_etc: " << error_message;
-  info()->child.reset(child);
+  info()->child = std::move(child);
 }
 
 ProcessType MultiprocessExec::ChildProcess() {
-  return info()->child.get();
+  return zx::unowned_process(info()->child);
 }
 
 }  // namespace test
diff --git a/test/multiprocess_exec_posix.cc b/test/multiprocess_exec_posix.cc
index c91b7f7..534013c 100644
--- a/test/multiprocess_exec_posix.cc
+++ b/test/multiprocess_exec_posix.cc
@@ -20,6 +20,7 @@
 #include <unistd.h>
 
 #include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
 #include "util/misc/scoped_forbid_return.h"
@@ -29,6 +30,10 @@
 #include <stdio_ext.h>
 #endif
 
+#if defined(OS_MACOSX)
+#include "util/mach/task_for_pid.h"
+#endif
+
 namespace crashpad {
 namespace test {
 
@@ -149,7 +154,11 @@
 }
 
 ProcessType MultiprocessExec::ChildProcess() {
+#if defined(OS_MACOSX)
+  return TaskForPID(ChildPID());
+#else
   return ChildPID();
+#endif
 }
 
 }  // namespace test
diff --git a/test/multiprocess_exec_test.cc b/test/multiprocess_exec_test.cc
index 99a1b01..d290efa 100644
--- a/test/multiprocess_exec_test.cc
+++ b/test/multiprocess_exec_test.cc
@@ -72,8 +72,7 @@
   TestMultiprocessExec exec;
   exec.SetChildTestMainFunction("SimpleMultiprocess");
   exec.Run();
-};
-
+}
 
 CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocessReturnsNonZero) {
   return 123;
@@ -96,7 +95,7 @@
   exec.SetExpectedChildTermination(
       Multiprocess::TerminationReason::kTerminationNormal, 123);
   exec.Run();
-};
+}
 
 #if !defined(OS_WIN)
 
diff --git a/test/multiprocess_exec_win.cc b/test/multiprocess_exec_win.cc
index 3897818..b75f8f5 100644
--- a/test/multiprocess_exec_win.cc
+++ b/test/multiprocess_exec_win.cc
@@ -58,7 +58,7 @@
 }
 
 void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
-                                               int code) {
+                                               ReturnCodeType code) {
   EXPECT_EQ(info_, nullptr)
       << "SetExpectedChildTermination() must be called before Run()";
   reason_ = reason;
diff --git a/test/multiprocess_posix.cc b/test/multiprocess_posix.cc
index 2e0c385..d2de318 100644
--- a/test/multiprocess_posix.cc
+++ b/test/multiprocess_posix.cc
@@ -154,7 +154,7 @@
 }
 
 void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
-                                               int code) {
+                                               ReturnCodeType code) {
   EXPECT_EQ(info_, nullptr)
       << "SetExpectedChildTermination() must be called before Run()";
   reason_ = reason;
diff --git a/test/process_type.cc b/test/process_type.cc
index 0e3c9e6..94fd912 100644
--- a/test/process_type.cc
+++ b/test/process_type.cc
@@ -15,8 +15,8 @@
 #include "test/process_type.h"
 
 #if defined(OS_FUCHSIA)
-#include <zircon/process.h>
-#elif defined(OS_POSIX)
+#include <lib/zx/process.h>
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
 #include <unistd.h>
 #endif
 
@@ -25,11 +25,13 @@
 
 ProcessType GetSelfProcess() {
 #if defined(OS_FUCHSIA)
-  return zx_process_self();
-#elif defined(OS_POSIX)
+  return zx::process::self();
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
   return getpid();
 #elif defined(OS_WIN)
   return GetCurrentProcess();
+#elif defined(OS_MACOSX)
+  return mach_task_self();
 #endif
 }
 
diff --git a/test/process_type.h b/test/process_type.h
index a25b122..dc99687 100644
--- a/test/process_type.h
+++ b/test/process_type.h
@@ -18,23 +18,27 @@
 #include "build/build_config.h"
 
 #if defined(OS_FUCHSIA)
-#include <zircon/types.h>
-#elif defined(OS_POSIX)
+#include <lib/zx/process.h>
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
 #include <sys/types.h>
 #elif defined(OS_WIN)
 #include <windows.h>
+#elif defined(OS_MACOSX)
+#include <mach/mach.h>
 #endif
 
 namespace crashpad {
 namespace test {
 
 #if defined(OS_FUCHSIA)
-using ProcessType = zx_handle_t;
-#elif defined(OS_POSIX) || DOXYGEN
+using ProcessType = zx::unowned_process;
+#elif defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
 //! \brief Alias for platform-specific type to represent a process.
 using ProcessType = pid_t;
 #elif defined(OS_WIN)
 using ProcessType = HANDLE;
+#elif defined(OS_MACOSX)
+using ProcessType = task_t;
 #else
 #error Port.
 #endif
diff --git a/test/scoped_guarded_page.h b/test/scoped_guarded_page.h
new file mode 100644
index 0000000..55ef272
--- /dev/null
+++ b/test/scoped_guarded_page.h
@@ -0,0 +1,49 @@
+// 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_TEST_SCOPED_GUARDED_PAGE_
+#define CRASHPAD_TEST_SCOPED_GUARDED_PAGE_
+
+#include "base/macros.h"
+
+namespace crashpad {
+namespace test {
+
+//! \brief A RAII object that allocates a read-write page with an inacessible
+//!     page following it.
+//!
+//! Upon construction, a mapping will be created. Failure to create the mapping
+//! is fatal. On destruction, the mapping is freed.
+//!
+//! This object should not be used in multi-threded contexts, the POSIX
+//! implementation can not be made thread-safe.
+class ScopedGuardedPage {
+ public:
+  ScopedGuardedPage();
+  ~ScopedGuardedPage();
+
+  //! \brief Returns the address of the read-write page.
+  //!
+  //! \return The address of the read-write page.
+  void* Pointer() const { return ptr_; }
+
+ private:
+  void* ptr_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedGuardedPage);
+};
+
+}  // namespace test
+}  // namespace crashpad
+
+#endif  // CRASHPAD_TEST_SCOPED_GUARDED_PAGE_
diff --git a/test/scoped_guarded_page_posix.cc b/test/scoped_guarded_page_posix.cc
new file mode 100644
index 0000000..750fe18
--- /dev/null
+++ b/test/scoped_guarded_page_posix.cc
@@ -0,0 +1,47 @@
+// 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.
+
+#include "test/scoped_guarded_page.h"
+
+#include <sys/mman.h>
+
+#include "base/logging.h"
+#include "base/process/process_metrics.h"
+
+namespace crashpad {
+namespace test {
+
+ScopedGuardedPage::ScopedGuardedPage() {
+  ptr_ = mmap(nullptr,
+              base::GetPageSize() * 2,
+              PROT_READ | PROT_WRITE,
+              MAP_PRIVATE | MAP_ANONYMOUS,
+              -1,
+              0);
+  PCHECK(ptr_ != MAP_FAILED) << "mmap";
+
+  // Simply mprotect()ing the guard page PROT_NONE does not make it inaccessible
+  // using ptrace() or /proc/$pid/mem so we munmap() the following page instead.
+  // Unfortunately, this means that the guarded page is not thread safe from
+  // other threads mapping a single page into the empty region.
+  char* second_page = static_cast<char*>(ptr_) + base::GetPageSize();
+  PCHECK(munmap(second_page, base::GetPageSize()) >= 0) << "munmap";
+}
+
+ScopedGuardedPage::~ScopedGuardedPage() {
+  PCHECK(munmap(ptr_, base::GetPageSize()) >= 0) << "munmap";
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/test/scoped_guarded_page_test.cc b/test/scoped_guarded_page_test.cc
new file mode 100644
index 0000000..023d1ed
--- /dev/null
+++ b/test/scoped_guarded_page_test.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "test/scoped_guarded_page.h"
+
+#include "base/process/process_metrics.h"
+#include "gtest/gtest.h"
+#include "test/gtest_death.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(ScopedGuardedPage, BasicFunctionality) {
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+  ScopedGuardedPage page;
+  char* address = (char*)page.Pointer();
+  EXPECT_NE(address, nullptr);
+  address[0] = 0;
+  address[base::GetPageSize() - 1] = 0;
+  EXPECT_DEATH_CRASH({ address[base::GetPageSize()] = 0; }, "");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/test/scoped_guarded_page_win.cc b/test/scoped_guarded_page_win.cc
new file mode 100644
index 0000000..23a6897
--- /dev/null
+++ b/test/scoped_guarded_page_win.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include "test/scoped_guarded_page.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+#include "base/process/process_metrics.h"
+
+namespace crashpad {
+namespace test {
+
+ScopedGuardedPage::ScopedGuardedPage() {
+  const size_t page_size = base::GetPageSize();
+  ptr_ = VirtualAlloc(nullptr, page_size * 2, MEM_RESERVE, PAGE_NOACCESS);
+  PCHECK(ptr_ != nullptr) << "VirtualAlloc";
+
+  PCHECK(VirtualAlloc(ptr_, page_size, MEM_COMMIT, PAGE_READWRITE) != nullptr)
+      << "VirtualAlloc";
+}
+
+ScopedGuardedPage::~ScopedGuardedPage() {
+  PCHECK(VirtualFree(ptr_, 0, MEM_RELEASE)) << "VirtualFree";
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/test/scoped_temp_dir_win.cc b/test/scoped_temp_dir_win.cc
index ab4ff95..8031921 100644
--- a/test/scoped_temp_dir_win.cc
+++ b/test/scoped_temp_dir_win.cc
@@ -64,7 +64,7 @@
     // the one we generate exists, keep trying another path name until we reach
     // some limit.
     base::FilePath path_to_create = GenerateCandidateName();
-    if (CreateDirectory(path_to_create.value().c_str(), NULL))
+    if (CreateDirectory(path_to_create.value().c_str(), nullptr))
       return path_to_create;
   }
 
diff --git a/test/test.gyp b/test/test.gyp
index 2973708..d00256a 100644
--- a/test/test.gyp
+++ b/test/test.gyp
@@ -37,8 +37,6 @@
         'filesystem.cc',
         'filesystem.h',
         'gtest_death.h',
-        'gtest_disabled.cc',
-        'gtest_disabled.h',
         'hex_string.cc',
         'hex_string.h',
         'linux/fake_ptrace_connection.cc',
@@ -63,6 +61,8 @@
         'multiprocess_posix.cc',
         'process_type.cc',
         'process_type.h',
+        'scoped_guarded_page.h',
+        'scoped_guarded_page_posix.cc',
         'scoped_module_handle.cc',
         'scoped_module_handle.h',
         'scoped_temp_dir.cc',
diff --git a/test/test_paths.cc b/test/test_paths.cc
index 50165e4..475e2b0 100644
--- a/test/test_paths.cc
+++ b/test/test_paths.cc
@@ -44,16 +44,9 @@
 
 base::FilePath TestDataRootInternal() {
 #if defined(OS_FUCHSIA)
-  base::FilePath asset_path("/pkg/assets");
-#if defined(CRASHPAD_IS_IN_FUCHSIA)
-  // Tests are not yet packaged when running in the Fuchsia tree, so assets do
-  // not appear as expected at /pkg/assets. Override the default so that tests
-  // can find their data for now.
-  // https://crashpad.chromium.org/bug/196.
-  asset_path = base::FilePath("/system/data/crashpad_test_data");
-#endif
+  base::FilePath asset_path("/pkg/data");
   if (!IsTestDataRoot(asset_path)) {
-    LOG(WARNING) << "Test data root seems invalid, continuing anyway";
+    LOG(WARNING) << "test data root seems invalid, continuing anyway";
   }
   return asset_path;
 #else  // defined(OS_FUCHSIA)
@@ -72,14 +65,23 @@
     return base::FilePath(environment_value);
   }
 
-  // In a standalone build, the test executable is usually at
-  // out/{Debug,Release} relative to the Crashpad root.
   base::FilePath executable_path;
   if (Paths::Executable(&executable_path)) {
+#if defined(OS_IOS) || defined(OS_ANDROID)
+    // On Android and iOS, test data is in a crashpad_test_data directory
+    // adjacent to the main executable. On iOS, this refers to the main
+    // executable file inside the .app bundle, so crashpad_test_data is also
+    // inside the bundle.
+    base::FilePath candidate = executable_path.DirName()
+                               .Append("crashpad_test_data");
+#else  // OS_IOS || OS_ANDRID
+    // In a standalone build, the test executable is usually at
+    // out/{Debug,Release} relative to the Crashpad root.
     base::FilePath candidate =
         base::FilePath(executable_path.DirName()
                            .Append(base::FilePath::kParentDirectory)
                            .Append(base::FilePath::kParentDirectory));
+#endif  // OS_IOS || OS_ANDROID
     if (IsTestDataRoot(candidate)) {
       return candidate;
     }
@@ -129,11 +131,7 @@
   base::FilePath executable_path;
   CHECK(Paths::Executable(&executable_path));
 #if defined(CRASHPAD_IS_IN_FUCHSIA)
-  // Tests are not yet packaged when running in the Fuchsia tree, so binaries do
-  // not appear as expected at /pkg/bin. Override the default of /pkg/bin/app
-  // so that tests can find the correct location for now.
-  // https://crashpad.chromium.org/bug/196.
-  executable_path = base::FilePath("/system/test/crashpad_test_data/app");
+  executable_path = base::FilePath("/pkg/bin/app");
 #endif
   return executable_path;
 }
@@ -202,16 +200,7 @@
 #if defined(OS_WIN)
       extension = FILE_PATH_LITERAL(".exe");
 #elif defined(OS_FUCHSIA)
-#if defined(CRASHPAD_IS_IN_FUCHSIA)
-      // Tests are not yet packaged when running in the Fuchsia tree, so
-      // binaries do not appear as expected at /pkg/bin. Override the default of
-      // /pkg/bin/app so that tests can find the correct location for now.
-      // https://crashpad.chromium.org/bug/196.
-      directory =
-          base::FilePath(FILE_PATH_LITERAL("/system/test/crashpad_test_data"));
-#else
       directory = base::FilePath(FILE_PATH_LITERAL("/pkg/bin"));
-#endif
 #endif  // OS_WIN
       break;
 
@@ -233,12 +222,7 @@
 
     case FileType::kCertificate:
 #if defined(CRASHPAD_IS_IN_FUCHSIA)
-      // When running in the Fuchsia tree, the .pem files are packaged as assets
-      // into the test data folder. This will need to be rationalized when
-      // things are actually run from a package.
-      // https://crashpad.chromium.org/bug/196.
-      directory =
-          base::FilePath(FILE_PATH_LITERAL("/system/test/crashpad_test_data"));
+      directory = base::FilePath(FILE_PATH_LITERAL("/pkg/data"));
 #endif
       extension = FILE_PATH_LITERAL(".pem");
       break;
diff --git a/test/test_test.gyp b/test/test_test.gyp
index 3c6d706..ddd920e 100644
--- a/test/test_test.gyp
+++ b/test/test_test.gyp
@@ -39,6 +39,7 @@
         'main_arguments_test.cc',
         'multiprocess_exec_test.cc',
         'multiprocess_posix_test.cc',
+        'scoped_guarded_page_test.cc',
         'scoped_temp_dir_test.cc',
         'test_paths_test.cc',
         'win/win_child_process_test.cc',
diff --git a/test/win/win_multiprocess_with_temp_dir.cc b/test/win/win_multiprocess_with_temp_dir.cc
index b61ebf6..ca1e01a 100644
--- a/test/win/win_multiprocess_with_temp_dir.cc
+++ b/test/win/win_multiprocess_with_temp_dir.cc
@@ -17,6 +17,7 @@
 #include <tlhelp32.h>
 
 #include "test/errors.h"
+#include "util/process/process_id.h"
 #include "util/win/process_info.h"
 
 namespace crashpad {
@@ -28,20 +29,20 @@
 
 // Returns the process IDs of all processes that have |parent_pid| as
 // parent process ID.
-std::vector<pid_t> GetPotentialChildProcessesOf(pid_t parent_pid) {
+std::vector<ProcessID> GetPotentialChildProcessesOf(ProcessID parent_pid) {
   ScopedFileHANDLE snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
   if (!snapshot.is_valid()) {
     ADD_FAILURE() << ErrorMessage("CreateToolhelp32Snapshot");
-    return std::vector<pid_t>();
+    return std::vector<ProcessID>();
   }
 
   PROCESSENTRY32 entry = {sizeof(entry)};
   if (!Process32First(snapshot.get(), &entry)) {
     ADD_FAILURE() << ErrorMessage("Process32First");
-    return std::vector<pid_t>();
+    return std::vector<ProcessID>();
   }
 
-  std::vector<pid_t> child_pids;
+  std::vector<ProcessID> child_pids;
   do {
     if (entry.th32ParentProcessID == parent_pid)
       child_pids.push_back(entry.th32ProcessID);
@@ -68,11 +69,11 @@
 // not their offspring. For this to work without race, |parent| has to be
 // suspended or have exited.
 void WaitForAllChildProcessesOf(HANDLE parent) {
-  pid_t parent_pid = GetProcessId(parent);
-  std::vector<pid_t> child_pids = GetPotentialChildProcessesOf(parent_pid);
+  ProcessID parent_pid = GetProcessId(parent);
+  std::vector<ProcessID> child_pids = GetPotentialChildProcessesOf(parent_pid);
 
   ULARGE_INTEGER parent_creationtime = GetProcessCreationTime(parent);
-  for (pid_t child_pid : child_pids) {
+  for (ProcessID child_pid : child_pids) {
     // Try and open the process. This may fail for reasons such as:
     // 1. The process isn't |parent|'s child process, but rather a
     //    higher-privilege sub-process of an earlier process that had
diff --git a/third_party/apple_cf/BUILD.gn b/third_party/apple_cf/BUILD.gn
new file mode 100644
index 0000000..9e37354
--- /dev/null
+++ b/third_party/apple_cf/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2019 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.
+
+source_set("apple_cf") {
+  sources = [ "CFStreamAbstract.h" ]
+}
diff --git a/third_party/apple_cf/CFStreamAbstract.h b/third_party/apple_cf/CFStreamAbstract.h
index bf12c92..ac26664 100644
--- a/third_party/apple_cf/CFStreamAbstract.h
+++ b/third_party/apple_cf/CFStreamAbstract.h
@@ -1,15 +1,15 @@
 /*
- * Copyright (c) 2010 Apple Inc. All rights reserved.
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
- * 
+ *
  * This file contains Original Code and/or Modifications of Original Code
  * as defined in and that are subject to the Apple Public Source License
  * Version 2.0 (the 'License'). You may not use this file except in
  * compliance with the License. Please obtain a copy of the License at
  * http://www.opensource.apple.com/apsl/ and read it before using this
  * file.
- * 
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
@@ -17,12 +17,12 @@
  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  * Please see the License for the specific language governing rights and
  * limitations under the License.
- * 
+ *
  * @APPLE_LICENSE_HEADER_END@
  */
 
 /*	CFStreamAbstract.h
-	Copyright (c) 2000-2009, Apple Inc. All rights reserved.
+	Copyright (c) 2000-2014, Apple Inc. All rights reserved.
 */
 
 #if !defined(__COREFOUNDATION_CFSTREAMABSTRACT__)
@@ -134,11 +134,18 @@
 void *_CFWriteStreamGetClient(CFWriteStreamRef writeStream);
 
 // Returns an array of the runloops and modes on which the stream is currently scheduled
+// Note that these are unretained mutable arrays - use the copy variant instead.
 CF_EXPORT
 CFArrayRef _CFReadStreamGetRunLoopsAndModes(CFReadStreamRef readStream);
 CF_EXPORT
 CFArrayRef _CFWriteStreamGetRunLoopsAndModes(CFWriteStreamRef writeStream);
 
+// Returns an array of the runloops and modes on which the stream is currently scheduled
+CF_EXPORT
+CFArrayRef _CFReadStreamCopyRunLoopsAndModes(CFReadStreamRef readStream);
+CF_EXPORT
+CFArrayRef _CFWriteStreamCopyRunLoopsAndModes(CFWriteStreamRef writeStream);
+
 /* Deprecated versions; here for backwards compatibility. */
 typedef struct {
     CFIndex version; /* == 1 */
diff --git a/third_party/apple_cf/README.crashpad b/third_party/apple_cf/README.crashpad
index b6c6cfa..2006648 100644
--- a/third_party/apple_cf/README.crashpad
+++ b/third_party/apple_cf/README.crashpad
@@ -2,7 +2,7 @@
 Short Name: CF
 URL: https://opensource.apple.com/source/CF/
 URL: https://opensource.apple.com/tarballs/CF/
-Version: 550.43 (from Mac OS X 10.6.8)
+Version: 1153.18 (from OS X 10.10.3)
 License: APSL 2.0
 License File: APPLE_LICENSE
 Security Critical: no
diff --git a/third_party/cpp-httplib/BUILD.gn b/third_party/cpp-httplib/BUILD.gn
index be2a6d7..2cbb72f 100644
--- a/third_party/cpp-httplib/BUILD.gn
+++ b/third_party/cpp-httplib/BUILD.gn
@@ -15,7 +15,5 @@
 source_set("cpp-httplib") {
   testonly = true
   include_dirs = [ "cpp-httplib" ]
-  sources = [
-    "cpp-httplib/httplib.h",
-  ]
+  sources = [ "cpp-httplib/httplib.h" ]
 }
diff --git a/third_party/edo/BUILD.gn b/third_party/edo/BUILD.gn
new file mode 100644
index 0000000..0205489
--- /dev/null
+++ b/third_party/edo/BUILD.gn
@@ -0,0 +1,142 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../build/crashpad_buildconfig.gni")
+
+if (crashpad_is_in_chromium) {
+  group("edo") {
+    testonly = true
+    public_deps = [
+      "//ios/third_party/edo",
+    ]
+  }
+} else {
+  config("config") {
+    include_dirs = [ "../../third_party/edo/edo" ]
+  }
+
+  source_set("edo") {
+    testonly = true
+
+    sources = [
+      "edo/Channel/Sources/EDOBlockingQueue.h",
+      "edo/Channel/Sources/EDOBlockingQueue.m",
+      "edo/Channel/Sources/EDOChannel.h",
+      "edo/Channel/Sources/EDOChannelErrors.h",
+      "edo/Channel/Sources/EDOChannelErrors.m",
+      "edo/Channel/Sources/EDOChannelForwarder.h",
+      "edo/Channel/Sources/EDOChannelForwarder.m",
+      "edo/Channel/Sources/EDOChannelMultiplexer.h",
+      "edo/Channel/Sources/EDOChannelMultiplexer.m",
+      "edo/Channel/Sources/EDOChannelPool.h",
+      "edo/Channel/Sources/EDOChannelPool.m",
+      "edo/Channel/Sources/EDOChannelUtil.h",
+      "edo/Channel/Sources/EDOChannelUtil.m",
+      "edo/Channel/Sources/EDOHostPort.h",
+      "edo/Channel/Sources/EDOHostPort.m",
+      "edo/Channel/Sources/EDOListenSocket.h",
+      "edo/Channel/Sources/EDOListenSocket.m",
+      "edo/Channel/Sources/EDOSocket.h",
+      "edo/Channel/Sources/EDOSocket.m",
+      "edo/Channel/Sources/EDOSocketChannel.h",
+      "edo/Channel/Sources/EDOSocketChannel.m",
+      "edo/Channel/Sources/EDOSocketPort.h",
+      "edo/Channel/Sources/EDOSocketPort.m",
+      "edo/Device/Sources/EDODeviceChannel.h",
+      "edo/Device/Sources/EDODeviceChannel.m",
+      "edo/Device/Sources/EDODeviceConnector.h",
+      "edo/Device/Sources/EDODeviceConnector.m",
+      "edo/Device/Sources/EDODeviceDetector.h",
+      "edo/Device/Sources/EDODeviceDetector.m",
+      "edo/Device/Sources/EDOUSBMuxUtil.h",
+      "edo/Device/Sources/EDOUSBMuxUtil.m",
+      "edo/DeviceForwarder/Sources/EDODeviceForwardersManager.h",
+      "edo/DeviceForwarder/Sources/EDODeviceForwardersManager.m",
+      "edo/Measure/Sources/EDONumericMeasure.h",
+      "edo/Measure/Sources/EDONumericMeasure.m",
+      "edo/Service/Sources/EDOBlockObject.h",
+      "edo/Service/Sources/EDOBlockObject.m",
+      "edo/Service/Sources/EDOClassMessage.h",
+      "edo/Service/Sources/EDOClassMessage.m",
+      "edo/Service/Sources/EDOClientService+Private.h",
+      "edo/Service/Sources/EDOClientService.h",
+      "edo/Service/Sources/EDOClientService.m",
+      "edo/Service/Sources/EDOClientServiceStatsCollector.h",
+      "edo/Service/Sources/EDOClientServiceStatsCollector.m",
+      "edo/Service/Sources/EDODeallocationTracker.h",
+      "edo/Service/Sources/EDODeallocationTracker.m",
+      "edo/Service/Sources/EDOExecutor.h",
+      "edo/Service/Sources/EDOExecutor.m",
+      "edo/Service/Sources/EDOExecutorMessage.h",
+      "edo/Service/Sources/EDOExecutorMessage.m",
+      "edo/Service/Sources/EDOHostNamingService+Private.h",
+      "edo/Service/Sources/EDOHostNamingService.h",
+      "edo/Service/Sources/EDOHostNamingService.m",
+      "edo/Service/Sources/EDOHostService+Handlers.h",
+      "edo/Service/Sources/EDOHostService+Handlers.m",
+      "edo/Service/Sources/EDOHostService+Private.h",
+      "edo/Service/Sources/EDOHostService.h",
+      "edo/Service/Sources/EDOHostService.m",
+      "edo/Service/Sources/EDOInvocationMessage.h",
+      "edo/Service/Sources/EDOInvocationMessage.m",
+      "edo/Service/Sources/EDOMessage.h",
+      "edo/Service/Sources/EDOMessage.m",
+      "edo/Service/Sources/EDOMethodSignatureMessage.h",
+      "edo/Service/Sources/EDOMethodSignatureMessage.m",
+      "edo/Service/Sources/EDOObject+EDOParameter.m",
+      "edo/Service/Sources/EDOObject+Invocation.m",
+      "edo/Service/Sources/EDOObject+Private.h",
+      "edo/Service/Sources/EDOObject.h",
+      "edo/Service/Sources/EDOObject.m",
+      "edo/Service/Sources/EDOObjectAliveMessage.h",
+      "edo/Service/Sources/EDOObjectAliveMessage.m",
+      "edo/Service/Sources/EDOObjectMessage.h",
+      "edo/Service/Sources/EDOObjectMessage.m",
+      "edo/Service/Sources/EDOObjectReleaseMessage.h",
+      "edo/Service/Sources/EDOObjectReleaseMessage.m",
+      "edo/Service/Sources/EDOParameter.h",
+      "edo/Service/Sources/EDOParameter.m",
+      "edo/Service/Sources/EDOProtocolObject.h",
+      "edo/Service/Sources/EDOProtocolObject.m",
+      "edo/Service/Sources/EDORemoteVariable.h",
+      "edo/Service/Sources/EDORemoteVariable.m",
+      "edo/Service/Sources/EDOServiceError.h",
+      "edo/Service/Sources/EDOServiceError.m",
+      "edo/Service/Sources/EDOServiceException.h",
+      "edo/Service/Sources/EDOServiceException.m",
+      "edo/Service/Sources/EDOServicePort.h",
+      "edo/Service/Sources/EDOServicePort.m",
+      "edo/Service/Sources/EDOServiceRequest.h",
+      "edo/Service/Sources/EDOServiceRequest.m",
+      "edo/Service/Sources/EDOTimingFunctions.h",
+      "edo/Service/Sources/EDOTimingFunctions.m",
+      "edo/Service/Sources/EDOValueObject+EDOParameter.m",
+      "edo/Service/Sources/EDOValueObject.h",
+      "edo/Service/Sources/EDOValueObject.m",
+      "edo/Service/Sources/EDOValueType.m",
+      "edo/Service/Sources/EDOWeakObject.h",
+      "edo/Service/Sources/EDOWeakObject.m",
+      "edo/Service/Sources/NSBlock+EDOInvocation.m",
+      "edo/Service/Sources/NSKeyedArchiver+EDOAdditions.h",
+      "edo/Service/Sources/NSKeyedArchiver+EDOAdditions.m",
+      "edo/Service/Sources/NSKeyedUnarchiver+EDOAdditions.h",
+      "edo/Service/Sources/NSKeyedUnarchiver+EDOAdditions.m",
+      "edo/Service/Sources/NSObject+EDOParameter.h",
+      "edo/Service/Sources/NSObject+EDOParameter.m",
+      "edo/Service/Sources/NSObject+EDOValue.h",
+      "edo/Service/Sources/NSObject+EDOValue.m",
+      "edo/Service/Sources/NSObject+EDOValueObject.h",
+      "edo/Service/Sources/NSObject+EDOValueObject.m",
+      "edo/Service/Sources/NSObject+EDOWeakObject.h",
+      "edo/Service/Sources/NSObject+EDOWeakObject.m",
+      "edo/Service/Sources/NSProxy+EDOParameter.h",
+      "edo/Service/Sources/NSProxy+EDOParameter.m",
+    ]
+
+    public_configs = [ ":config" ]
+    deps = [
+      "../../build:ios_enable_arc",
+    ]
+  }
+}
diff --git a/third_party/edo/README.crashpad b/third_party/edo/README.crashpad
new file mode 100644
index 0000000..fb83158
--- /dev/null
+++ b/third_party/edo/README.crashpad
@@ -0,0 +1,10 @@
+Name: eDistantObject
+Short Name: EDO
+URL: https://github.com/google/eDistantObject
+Revision: See DEPS
+License: Apache 2.0
+License File: edo/LICENSE
+Security Critical: no
+
+Description:
+iOS remote method invocations (distant object) over Inter-process communication layer.
diff --git a/third_party/fuchsia/BUILD.gn b/third_party/fuchsia/BUILD.gn
new file mode 100644
index 0000000..7e06289
--- /dev/null
+++ b/third_party/fuchsia/BUILD.gn
@@ -0,0 +1,38 @@
+# 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.
+
+import("../../build/crashpad_buildconfig.gni")
+
+if (crashpad_is_in_fuchsia) {
+  group("fuchsia") {
+    public_deps = [
+      "//zircon/public/lib/fdio",
+      "//zircon/public/lib/zx",
+    ]
+  }
+} else if (crashpad_is_in_chromium) {
+  group("fuchsia") {
+    public_deps = [
+      "//third_party/fuchsia-sdk/sdk/pkg/fdio",
+      "//third_party/fuchsia-sdk/sdk/pkg/zx",
+    ]
+  }
+} else {
+  group("fuchsia") {
+    public_deps = [
+      "//third_party/fuchsia/sdk/$host_os-amd64/pkg/fdio",
+      "//third_party/fuchsia/sdk/$host_os-amd64/pkg/zx",
+    ]
+  }
+}
diff --git a/third_party/fuchsia/runner.py b/third_party/fuchsia/runner.py
new file mode 100755
index 0000000..229d8a4
--- /dev/null
+++ b/third_party/fuchsia/runner.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# 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.
+
+import os
+import sys
+
+os.execv(sys.argv[1], sys.argv[1:])
diff --git a/third_party/glibc/BUILD.gn b/third_party/glibc/BUILD.gn
new file mode 100644
index 0000000..3f2b08b
--- /dev/null
+++ b/third_party/glibc/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2019 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.
+
+source_set("glibc") {
+  sources = [ "elf/elf.h" ]
+}
diff --git a/third_party/glibc/COPYING.LIB b/third_party/glibc/COPYING.LIB
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/third_party/glibc/COPYING.LIB
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/third_party/glibc/README.crashpad b/third_party/glibc/README.crashpad
new file mode 100644
index 0000000..b550ad9
--- /dev/null
+++ b/third_party/glibc/README.crashpad
@@ -0,0 +1,16 @@
+Name: GNU C Library
+Short Name: glibc
+URL: https://www.gnu.org/software/libc/
+URL: https://sourceware.org/git/?p=glibc.git
+Version: 2.29
+License: GNU LGPL 2.1
+License File: COPYING.LIB
+Security Critical: no
+
+Description:
+glibc is the GNU Project’s implementation of the C standard library.
+
+Local Modifications:
+ - Only elf/elf.h is included. Its #include of <features.h> has been removed,
+   and it uses of __BEGIN_DECLS and __END_DECLS have been replaced with inline
+   versions in the manner that misc/sys/cdefs.h defines those macros.
diff --git a/third_party/glibc/elf/elf.h b/third_party/glibc/elf/elf.h
new file mode 100644
index 0000000..331536b
--- /dev/null
+++ b/third_party/glibc/elf/elf.h
@@ -0,0 +1,4003 @@
+/* This file defines standard ELF types, structures, and macros.
+   Copyright (C) 1995-2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _ELF_H
+#define	_ELF_H 1
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/* Standard ELF types.  */
+
+#include <stdint.h>
+
+/* Type for a 16-bit quantity.  */
+typedef uint16_t Elf32_Half;
+typedef uint16_t Elf64_Half;
+
+/* Types for signed and unsigned 32-bit quantities.  */
+typedef uint32_t Elf32_Word;
+typedef	int32_t  Elf32_Sword;
+typedef uint32_t Elf64_Word;
+typedef	int32_t  Elf64_Sword;
+
+/* Types for signed and unsigned 64-bit quantities.  */
+typedef uint64_t Elf32_Xword;
+typedef	int64_t  Elf32_Sxword;
+typedef uint64_t Elf64_Xword;
+typedef	int64_t  Elf64_Sxword;
+
+/* Type of addresses.  */
+typedef uint32_t Elf32_Addr;
+typedef uint64_t Elf64_Addr;
+
+/* Type of file offsets.  */
+typedef uint32_t Elf32_Off;
+typedef uint64_t Elf64_Off;
+
+/* Type for section indices, which are 16-bit quantities.  */
+typedef uint16_t Elf32_Section;
+typedef uint16_t Elf64_Section;
+
+/* Type for version symbol information.  */
+typedef Elf32_Half Elf32_Versym;
+typedef Elf64_Half Elf64_Versym;
+
+
+/* The ELF file header.  This appears at the start of every ELF file.  */
+
+#define EI_NIDENT (16)
+
+typedef struct
+{
+  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
+  Elf32_Half	e_type;			/* Object file type */
+  Elf32_Half	e_machine;		/* Architecture */
+  Elf32_Word	e_version;		/* Object file version */
+  Elf32_Addr	e_entry;		/* Entry point virtual address */
+  Elf32_Off	e_phoff;		/* Program header table file offset */
+  Elf32_Off	e_shoff;		/* Section header table file offset */
+  Elf32_Word	e_flags;		/* Processor-specific flags */
+  Elf32_Half	e_ehsize;		/* ELF header size in bytes */
+  Elf32_Half	e_phentsize;		/* Program header table entry size */
+  Elf32_Half	e_phnum;		/* Program header table entry count */
+  Elf32_Half	e_shentsize;		/* Section header table entry size */
+  Elf32_Half	e_shnum;		/* Section header table entry count */
+  Elf32_Half	e_shstrndx;		/* Section header string table index */
+} Elf32_Ehdr;
+
+typedef struct
+{
+  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
+  Elf64_Half	e_type;			/* Object file type */
+  Elf64_Half	e_machine;		/* Architecture */
+  Elf64_Word	e_version;		/* Object file version */
+  Elf64_Addr	e_entry;		/* Entry point virtual address */
+  Elf64_Off	e_phoff;		/* Program header table file offset */
+  Elf64_Off	e_shoff;		/* Section header table file offset */
+  Elf64_Word	e_flags;		/* Processor-specific flags */
+  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
+  Elf64_Half	e_phentsize;		/* Program header table entry size */
+  Elf64_Half	e_phnum;		/* Program header table entry count */
+  Elf64_Half	e_shentsize;		/* Section header table entry size */
+  Elf64_Half	e_shnum;		/* Section header table entry count */
+  Elf64_Half	e_shstrndx;		/* Section header string table index */
+} Elf64_Ehdr;
+
+/* Fields in the e_ident array.  The EI_* macros are indices into the
+   array.  The macros under each EI_* macro are the values the byte
+   may have.  */
+
+#define EI_MAG0		0		/* File identification byte 0 index */
+#define ELFMAG0		0x7f		/* Magic number byte 0 */
+
+#define EI_MAG1		1		/* File identification byte 1 index */
+#define ELFMAG1		'E'		/* Magic number byte 1 */
+
+#define EI_MAG2		2		/* File identification byte 2 index */
+#define ELFMAG2		'L'		/* Magic number byte 2 */
+
+#define EI_MAG3		3		/* File identification byte 3 index */
+#define ELFMAG3		'F'		/* Magic number byte 3 */
+
+/* Conglomeration of the identification bytes, for easy testing as a word.  */
+#define	ELFMAG		"\177ELF"
+#define	SELFMAG		4
+
+#define EI_CLASS	4		/* File class byte index */
+#define ELFCLASSNONE	0		/* Invalid class */
+#define ELFCLASS32	1		/* 32-bit objects */
+#define ELFCLASS64	2		/* 64-bit objects */
+#define ELFCLASSNUM	3
+
+#define EI_DATA		5		/* Data encoding byte index */
+#define ELFDATANONE	0		/* Invalid data encoding */
+#define ELFDATA2LSB	1		/* 2's complement, little endian */
+#define ELFDATA2MSB	2		/* 2's complement, big endian */
+#define ELFDATANUM	3
+
+#define EI_VERSION	6		/* File version byte index */
+					/* Value must be EV_CURRENT */
+
+#define EI_OSABI	7		/* OS ABI identification */
+#define ELFOSABI_NONE		0	/* UNIX System V ABI */
+#define ELFOSABI_SYSV		0	/* Alias.  */
+#define ELFOSABI_HPUX		1	/* HP-UX */
+#define ELFOSABI_NETBSD		2	/* NetBSD.  */
+#define ELFOSABI_GNU		3	/* Object uses GNU ELF extensions.  */
+#define ELFOSABI_LINUX		ELFOSABI_GNU /* Compatibility alias.  */
+#define ELFOSABI_SOLARIS	6	/* Sun Solaris.  */
+#define ELFOSABI_AIX		7	/* IBM AIX.  */
+#define ELFOSABI_IRIX		8	/* SGI Irix.  */
+#define ELFOSABI_FREEBSD	9	/* FreeBSD.  */
+#define ELFOSABI_TRU64		10	/* Compaq TRU64 UNIX.  */
+#define ELFOSABI_MODESTO	11	/* Novell Modesto.  */
+#define ELFOSABI_OPENBSD	12	/* OpenBSD.  */
+#define ELFOSABI_ARM_AEABI	64	/* ARM EABI */
+#define ELFOSABI_ARM		97	/* ARM */
+#define ELFOSABI_STANDALONE	255	/* Standalone (embedded) application */
+
+#define EI_ABIVERSION	8		/* ABI version */
+
+#define EI_PAD		9		/* Byte index of padding bytes */
+
+/* Legal values for e_type (object file type).  */
+
+#define ET_NONE		0		/* No file type */
+#define ET_REL		1		/* Relocatable file */
+#define ET_EXEC		2		/* Executable file */
+#define ET_DYN		3		/* Shared object file */
+#define ET_CORE		4		/* Core file */
+#define	ET_NUM		5		/* Number of defined types */
+#define ET_LOOS		0xfe00		/* OS-specific range start */
+#define ET_HIOS		0xfeff		/* OS-specific range end */
+#define ET_LOPROC	0xff00		/* Processor-specific range start */
+#define ET_HIPROC	0xffff		/* Processor-specific range end */
+
+/* Legal values for e_machine (architecture).  */
+
+#define EM_NONE		 0	/* No machine */
+#define EM_M32		 1	/* AT&T WE 32100 */
+#define EM_SPARC	 2	/* SUN SPARC */
+#define EM_386		 3	/* Intel 80386 */
+#define EM_68K		 4	/* Motorola m68k family */
+#define EM_88K		 5	/* Motorola m88k family */
+#define EM_IAMCU	 6	/* Intel MCU */
+#define EM_860		 7	/* Intel 80860 */
+#define EM_MIPS		 8	/* MIPS R3000 big-endian */
+#define EM_S370		 9	/* IBM System/370 */
+#define EM_MIPS_RS3_LE	10	/* MIPS R3000 little-endian */
+				/* reserved 11-14 */
+#define EM_PARISC	15	/* HPPA */
+				/* reserved 16 */
+#define EM_VPP500	17	/* Fujitsu VPP500 */
+#define EM_SPARC32PLUS	18	/* Sun's "v8plus" */
+#define EM_960		19	/* Intel 80960 */
+#define EM_PPC		20	/* PowerPC */
+#define EM_PPC64	21	/* PowerPC 64-bit */
+#define EM_S390		22	/* IBM S390 */
+#define EM_SPU		23	/* IBM SPU/SPC */
+				/* reserved 24-35 */
+#define EM_V800		36	/* NEC V800 series */
+#define EM_FR20		37	/* Fujitsu FR20 */
+#define EM_RH32		38	/* TRW RH-32 */
+#define EM_RCE		39	/* Motorola RCE */
+#define EM_ARM		40	/* ARM */
+#define EM_FAKE_ALPHA	41	/* Digital Alpha */
+#define EM_SH		42	/* Hitachi SH */
+#define EM_SPARCV9	43	/* SPARC v9 64-bit */
+#define EM_TRICORE	44	/* Siemens Tricore */
+#define EM_ARC		45	/* Argonaut RISC Core */
+#define EM_H8_300	46	/* Hitachi H8/300 */
+#define EM_H8_300H	47	/* Hitachi H8/300H */
+#define EM_H8S		48	/* Hitachi H8S */
+#define EM_H8_500	49	/* Hitachi H8/500 */
+#define EM_IA_64	50	/* Intel Merced */
+#define EM_MIPS_X	51	/* Stanford MIPS-X */
+#define EM_COLDFIRE	52	/* Motorola Coldfire */
+#define EM_68HC12	53	/* Motorola M68HC12 */
+#define EM_MMA		54	/* Fujitsu MMA Multimedia Accelerator */
+#define EM_PCP		55	/* Siemens PCP */
+#define EM_NCPU		56	/* Sony nCPU embeeded RISC */
+#define EM_NDR1		57	/* Denso NDR1 microprocessor */
+#define EM_STARCORE	58	/* Motorola Start*Core processor */
+#define EM_ME16		59	/* Toyota ME16 processor */
+#define EM_ST100	60	/* STMicroelectronic ST100 processor */
+#define EM_TINYJ	61	/* Advanced Logic Corp. Tinyj emb.fam */
+#define EM_X86_64	62	/* AMD x86-64 architecture */
+#define EM_PDSP		63	/* Sony DSP Processor */
+#define EM_PDP10	64	/* Digital PDP-10 */
+#define EM_PDP11	65	/* Digital PDP-11 */
+#define EM_FX66		66	/* Siemens FX66 microcontroller */
+#define EM_ST9PLUS	67	/* STMicroelectronics ST9+ 8/16 mc */
+#define EM_ST7		68	/* STmicroelectronics ST7 8 bit mc */
+#define EM_68HC16	69	/* Motorola MC68HC16 microcontroller */
+#define EM_68HC11	70	/* Motorola MC68HC11 microcontroller */
+#define EM_68HC08	71	/* Motorola MC68HC08 microcontroller */
+#define EM_68HC05	72	/* Motorola MC68HC05 microcontroller */
+#define EM_SVX		73	/* Silicon Graphics SVx */
+#define EM_ST19		74	/* STMicroelectronics ST19 8 bit mc */
+#define EM_VAX		75	/* Digital VAX */
+#define EM_CRIS		76	/* Axis Communications 32-bit emb.proc */
+#define EM_JAVELIN	77	/* Infineon Technologies 32-bit emb.proc */
+#define EM_FIREPATH	78	/* Element 14 64-bit DSP Processor */
+#define EM_ZSP		79	/* LSI Logic 16-bit DSP Processor */
+#define EM_MMIX		80	/* Donald Knuth's educational 64-bit proc */
+#define EM_HUANY	81	/* Harvard University machine-independent object files */
+#define EM_PRISM	82	/* SiTera Prism */
+#define EM_AVR		83	/* Atmel AVR 8-bit microcontroller */
+#define EM_FR30		84	/* Fujitsu FR30 */
+#define EM_D10V		85	/* Mitsubishi D10V */
+#define EM_D30V		86	/* Mitsubishi D30V */
+#define EM_V850		87	/* NEC v850 */
+#define EM_M32R		88	/* Mitsubishi M32R */
+#define EM_MN10300	89	/* Matsushita MN10300 */
+#define EM_MN10200	90	/* Matsushita MN10200 */
+#define EM_PJ		91	/* picoJava */
+#define EM_OPENRISC	92	/* OpenRISC 32-bit embedded processor */
+#define EM_ARC_COMPACT	93	/* ARC International ARCompact */
+#define EM_XTENSA	94	/* Tensilica Xtensa Architecture */
+#define EM_VIDEOCORE	95	/* Alphamosaic VideoCore */
+#define EM_TMM_GPP	96	/* Thompson Multimedia General Purpose Proc */
+#define EM_NS32K	97	/* National Semi. 32000 */
+#define EM_TPC		98	/* Tenor Network TPC */
+#define EM_SNP1K	99	/* Trebia SNP 1000 */
+#define EM_ST200	100	/* STMicroelectronics ST200 */
+#define EM_IP2K		101	/* Ubicom IP2xxx */
+#define EM_MAX		102	/* MAX processor */
+#define EM_CR		103	/* National Semi. CompactRISC */
+#define EM_F2MC16	104	/* Fujitsu F2MC16 */
+#define EM_MSP430	105	/* Texas Instruments msp430 */
+#define EM_BLACKFIN	106	/* Analog Devices Blackfin DSP */
+#define EM_SE_C33	107	/* Seiko Epson S1C33 family */
+#define EM_SEP		108	/* Sharp embedded microprocessor */
+#define EM_ARCA		109	/* Arca RISC */
+#define EM_UNICORE	110	/* PKU-Unity & MPRC Peking Uni. mc series */
+#define EM_EXCESS	111	/* eXcess configurable cpu */
+#define EM_DXP		112	/* Icera Semi. Deep Execution Processor */
+#define EM_ALTERA_NIOS2 113	/* Altera Nios II */
+#define EM_CRX		114	/* National Semi. CompactRISC CRX */
+#define EM_XGATE	115	/* Motorola XGATE */
+#define EM_C166		116	/* Infineon C16x/XC16x */
+#define EM_M16C		117	/* Renesas M16C */
+#define EM_DSPIC30F	118	/* Microchip Technology dsPIC30F */
+#define EM_CE		119	/* Freescale Communication Engine RISC */
+#define EM_M32C		120	/* Renesas M32C */
+				/* reserved 121-130 */
+#define EM_TSK3000	131	/* Altium TSK3000 */
+#define EM_RS08		132	/* Freescale RS08 */
+#define EM_SHARC	133	/* Analog Devices SHARC family */
+#define EM_ECOG2	134	/* Cyan Technology eCOG2 */
+#define EM_SCORE7	135	/* Sunplus S+core7 RISC */
+#define EM_DSP24	136	/* New Japan Radio (NJR) 24-bit DSP */
+#define EM_VIDEOCORE3	137	/* Broadcom VideoCore III */
+#define EM_LATTICEMICO32 138	/* RISC for Lattice FPGA */
+#define EM_SE_C17	139	/* Seiko Epson C17 */
+#define EM_TI_C6000	140	/* Texas Instruments TMS320C6000 DSP */
+#define EM_TI_C2000	141	/* Texas Instruments TMS320C2000 DSP */
+#define EM_TI_C5500	142	/* Texas Instruments TMS320C55x DSP */
+#define EM_TI_ARP32	143	/* Texas Instruments App. Specific RISC */
+#define EM_TI_PRU	144	/* Texas Instruments Prog. Realtime Unit */
+				/* reserved 145-159 */
+#define EM_MMDSP_PLUS	160	/* STMicroelectronics 64bit VLIW DSP */
+#define EM_CYPRESS_M8C	161	/* Cypress M8C */
+#define EM_R32C		162	/* Renesas R32C */
+#define EM_TRIMEDIA	163	/* NXP Semi. TriMedia */
+#define EM_QDSP6	164	/* QUALCOMM DSP6 */
+#define EM_8051		165	/* Intel 8051 and variants */
+#define EM_STXP7X	166	/* STMicroelectronics STxP7x */
+#define EM_NDS32	167	/* Andes Tech. compact code emb. RISC */
+#define EM_ECOG1X	168	/* Cyan Technology eCOG1X */
+#define EM_MAXQ30	169	/* Dallas Semi. MAXQ30 mc */
+#define EM_XIMO16	170	/* New Japan Radio (NJR) 16-bit DSP */
+#define EM_MANIK	171	/* M2000 Reconfigurable RISC */
+#define EM_CRAYNV2	172	/* Cray NV2 vector architecture */
+#define EM_RX		173	/* Renesas RX */
+#define EM_METAG	174	/* Imagination Tech. META */
+#define EM_MCST_ELBRUS	175	/* MCST Elbrus */
+#define EM_ECOG16	176	/* Cyan Technology eCOG16 */
+#define EM_CR16		177	/* National Semi. CompactRISC CR16 */
+#define EM_ETPU		178	/* Freescale Extended Time Processing Unit */
+#define EM_SLE9X	179	/* Infineon Tech. SLE9X */
+#define EM_L10M		180	/* Intel L10M */
+#define EM_K10M		181	/* Intel K10M */
+				/* reserved 182 */
+#define EM_AARCH64	183	/* ARM AARCH64 */
+				/* reserved 184 */
+#define EM_AVR32	185	/* Amtel 32-bit microprocessor */
+#define EM_STM8		186	/* STMicroelectronics STM8 */
+#define EM_TILE64	187	/* Tileta TILE64 */
+#define EM_TILEPRO	188	/* Tilera TILEPro */
+#define EM_MICROBLAZE	189	/* Xilinx MicroBlaze */
+#define EM_CUDA		190	/* NVIDIA CUDA */
+#define EM_TILEGX	191	/* Tilera TILE-Gx */
+#define EM_CLOUDSHIELD	192	/* CloudShield */
+#define EM_COREA_1ST	193	/* KIPO-KAIST Core-A 1st gen. */
+#define EM_COREA_2ND	194	/* KIPO-KAIST Core-A 2nd gen. */
+#define EM_ARC_COMPACT2	195	/* Synopsys ARCompact V2 */
+#define EM_OPEN8	196	/* Open8 RISC */
+#define EM_RL78		197	/* Renesas RL78 */
+#define EM_VIDEOCORE5	198	/* Broadcom VideoCore V */
+#define EM_78KOR	199	/* Renesas 78KOR */
+#define EM_56800EX	200	/* Freescale 56800EX DSC */
+#define EM_BA1		201	/* Beyond BA1 */
+#define EM_BA2		202	/* Beyond BA2 */
+#define EM_XCORE	203	/* XMOS xCORE */
+#define EM_MCHP_PIC	204	/* Microchip 8-bit PIC(r) */
+				/* reserved 205-209 */
+#define EM_KM32		210	/* KM211 KM32 */
+#define EM_KMX32	211	/* KM211 KMX32 */
+#define EM_EMX16	212	/* KM211 KMX16 */
+#define EM_EMX8		213	/* KM211 KMX8 */
+#define EM_KVARC	214	/* KM211 KVARC */
+#define EM_CDP		215	/* Paneve CDP */
+#define EM_COGE		216	/* Cognitive Smart Memory Processor */
+#define EM_COOL		217	/* Bluechip CoolEngine */
+#define EM_NORC		218	/* Nanoradio Optimized RISC */
+#define EM_CSR_KALIMBA	219	/* CSR Kalimba */
+#define EM_Z80		220	/* Zilog Z80 */
+#define EM_VISIUM	221	/* Controls and Data Services VISIUMcore */
+#define EM_FT32		222	/* FTDI Chip FT32 */
+#define EM_MOXIE	223	/* Moxie processor */
+#define EM_AMDGPU	224	/* AMD GPU */
+				/* reserved 225-242 */
+#define EM_RISCV	243	/* RISC-V */
+
+#define EM_BPF		247	/* Linux BPF -- in-kernel virtual machine */
+#define EM_CSKY		252     /* C_SKY */
+
+#define EM_NUM		253
+
+/* Old spellings/synonyms.  */
+
+#define EM_ARC_A5	EM_ARC_COMPACT
+
+/* If it is necessary to assign new unofficial EM_* values, please
+   pick large random numbers (0x8523, 0xa7f2, etc.) to minimize the
+   chances of collision with official or non-GNU unofficial values.  */
+
+#define EM_ALPHA	0x9026
+
+/* Legal values for e_version (version).  */
+
+#define EV_NONE		0		/* Invalid ELF version */
+#define EV_CURRENT	1		/* Current version */
+#define EV_NUM		2
+
+/* Section header.  */
+
+typedef struct
+{
+  Elf32_Word	sh_name;		/* Section name (string tbl index) */
+  Elf32_Word	sh_type;		/* Section type */
+  Elf32_Word	sh_flags;		/* Section flags */
+  Elf32_Addr	sh_addr;		/* Section virtual addr at execution */
+  Elf32_Off	sh_offset;		/* Section file offset */
+  Elf32_Word	sh_size;		/* Section size in bytes */
+  Elf32_Word	sh_link;		/* Link to another section */
+  Elf32_Word	sh_info;		/* Additional section information */
+  Elf32_Word	sh_addralign;		/* Section alignment */
+  Elf32_Word	sh_entsize;		/* Entry size if section holds table */
+} Elf32_Shdr;
+
+typedef struct
+{
+  Elf64_Word	sh_name;		/* Section name (string tbl index) */
+  Elf64_Word	sh_type;		/* Section type */
+  Elf64_Xword	sh_flags;		/* Section flags */
+  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
+  Elf64_Off	sh_offset;		/* Section file offset */
+  Elf64_Xword	sh_size;		/* Section size in bytes */
+  Elf64_Word	sh_link;		/* Link to another section */
+  Elf64_Word	sh_info;		/* Additional section information */
+  Elf64_Xword	sh_addralign;		/* Section alignment */
+  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
+} Elf64_Shdr;
+
+/* Special section indices.  */
+
+#define SHN_UNDEF	0		/* Undefined section */
+#define SHN_LORESERVE	0xff00		/* Start of reserved indices */
+#define SHN_LOPROC	0xff00		/* Start of processor-specific */
+#define SHN_BEFORE	0xff00		/* Order section before all others
+					   (Solaris).  */
+#define SHN_AFTER	0xff01		/* Order section after all others
+					   (Solaris).  */
+#define SHN_HIPROC	0xff1f		/* End of processor-specific */
+#define SHN_LOOS	0xff20		/* Start of OS-specific */
+#define SHN_HIOS	0xff3f		/* End of OS-specific */
+#define SHN_ABS		0xfff1		/* Associated symbol is absolute */
+#define SHN_COMMON	0xfff2		/* Associated symbol is common */
+#define SHN_XINDEX	0xffff		/* Index is in extra table.  */
+#define SHN_HIRESERVE	0xffff		/* End of reserved indices */
+
+/* Legal values for sh_type (section type).  */
+
+#define SHT_NULL	  0		/* Section header table entry unused */
+#define SHT_PROGBITS	  1		/* Program data */
+#define SHT_SYMTAB	  2		/* Symbol table */
+#define SHT_STRTAB	  3		/* String table */
+#define SHT_RELA	  4		/* Relocation entries with addends */
+#define SHT_HASH	  5		/* Symbol hash table */
+#define SHT_DYNAMIC	  6		/* Dynamic linking information */
+#define SHT_NOTE	  7		/* Notes */
+#define SHT_NOBITS	  8		/* Program space with no data (bss) */
+#define SHT_REL		  9		/* Relocation entries, no addends */
+#define SHT_SHLIB	  10		/* Reserved */
+#define SHT_DYNSYM	  11		/* Dynamic linker symbol table */
+#define SHT_INIT_ARRAY	  14		/* Array of constructors */
+#define SHT_FINI_ARRAY	  15		/* Array of destructors */
+#define SHT_PREINIT_ARRAY 16		/* Array of pre-constructors */
+#define SHT_GROUP	  17		/* Section group */
+#define SHT_SYMTAB_SHNDX  18		/* Extended section indeces */
+#define	SHT_NUM		  19		/* Number of defined types.  */
+#define SHT_LOOS	  0x60000000	/* Start OS-specific.  */
+#define SHT_GNU_ATTRIBUTES 0x6ffffff5	/* Object attributes.  */
+#define SHT_GNU_HASH	  0x6ffffff6	/* GNU-style hash table.  */
+#define SHT_GNU_LIBLIST	  0x6ffffff7	/* Prelink library list */
+#define SHT_CHECKSUM	  0x6ffffff8	/* Checksum for DSO content.  */
+#define SHT_LOSUNW	  0x6ffffffa	/* Sun-specific low bound.  */
+#define SHT_SUNW_move	  0x6ffffffa
+#define SHT_SUNW_COMDAT   0x6ffffffb
+#define SHT_SUNW_syminfo  0x6ffffffc
+#define SHT_GNU_verdef	  0x6ffffffd	/* Version definition section.  */
+#define SHT_GNU_verneed	  0x6ffffffe	/* Version needs section.  */
+#define SHT_GNU_versym	  0x6fffffff	/* Version symbol table.  */
+#define SHT_HISUNW	  0x6fffffff	/* Sun-specific high bound.  */
+#define SHT_HIOS	  0x6fffffff	/* End OS-specific type */
+#define SHT_LOPROC	  0x70000000	/* Start of processor-specific */
+#define SHT_HIPROC	  0x7fffffff	/* End of processor-specific */
+#define SHT_LOUSER	  0x80000000	/* Start of application-specific */
+#define SHT_HIUSER	  0x8fffffff	/* End of application-specific */
+
+/* Legal values for sh_flags (section flags).  */
+
+#define SHF_WRITE	     (1 << 0)	/* Writable */
+#define SHF_ALLOC	     (1 << 1)	/* Occupies memory during execution */
+#define SHF_EXECINSTR	     (1 << 2)	/* Executable */
+#define SHF_MERGE	     (1 << 4)	/* Might be merged */
+#define SHF_STRINGS	     (1 << 5)	/* Contains nul-terminated strings */
+#define SHF_INFO_LINK	     (1 << 6)	/* `sh_info' contains SHT index */
+#define SHF_LINK_ORDER	     (1 << 7)	/* Preserve order after combining */
+#define SHF_OS_NONCONFORMING (1 << 8)	/* Non-standard OS specific handling
+					   required */
+#define SHF_GROUP	     (1 << 9)	/* Section is member of a group.  */
+#define SHF_TLS		     (1 << 10)	/* Section hold thread-local data.  */
+#define SHF_COMPRESSED	     (1 << 11)	/* Section with compressed data. */
+#define SHF_MASKOS	     0x0ff00000	/* OS-specific.  */
+#define SHF_MASKPROC	     0xf0000000	/* Processor-specific */
+#define SHF_ORDERED	     (1 << 30)	/* Special ordering requirement
+					   (Solaris).  */
+#define SHF_EXCLUDE	     (1U << 31)	/* Section is excluded unless
+					   referenced or allocated (Solaris).*/
+
+/* Section compression header.  Used when SHF_COMPRESSED is set.  */
+
+typedef struct
+{
+  Elf32_Word	ch_type;	/* Compression format.  */
+  Elf32_Word	ch_size;	/* Uncompressed data size.  */
+  Elf32_Word	ch_addralign;	/* Uncompressed data alignment.  */
+} Elf32_Chdr;
+
+typedef struct
+{
+  Elf64_Word	ch_type;	/* Compression format.  */
+  Elf64_Word	ch_reserved;
+  Elf64_Xword	ch_size;	/* Uncompressed data size.  */
+  Elf64_Xword	ch_addralign;	/* Uncompressed data alignment.  */
+} Elf64_Chdr;
+
+/* Legal values for ch_type (compression algorithm).  */
+#define ELFCOMPRESS_ZLIB	1	   /* ZLIB/DEFLATE algorithm.  */
+#define ELFCOMPRESS_LOOS	0x60000000 /* Start of OS-specific.  */
+#define ELFCOMPRESS_HIOS	0x6fffffff /* End of OS-specific.  */
+#define ELFCOMPRESS_LOPROC	0x70000000 /* Start of processor-specific.  */
+#define ELFCOMPRESS_HIPROC	0x7fffffff /* End of processor-specific.  */
+
+/* Section group handling.  */
+#define GRP_COMDAT	0x1		/* Mark group as COMDAT.  */
+
+/* Symbol table entry.  */
+
+typedef struct
+{
+  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
+  Elf32_Addr	st_value;		/* Symbol value */
+  Elf32_Word	st_size;		/* Symbol size */
+  unsigned char	st_info;		/* Symbol type and binding */
+  unsigned char	st_other;		/* Symbol visibility */
+  Elf32_Section	st_shndx;		/* Section index */
+} Elf32_Sym;
+
+typedef struct
+{
+  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
+  unsigned char	st_info;		/* Symbol type and binding */
+  unsigned char st_other;		/* Symbol visibility */
+  Elf64_Section	st_shndx;		/* Section index */
+  Elf64_Addr	st_value;		/* Symbol value */
+  Elf64_Xword	st_size;		/* Symbol size */
+} Elf64_Sym;
+
+/* The syminfo section if available contains additional information about
+   every dynamic symbol.  */
+
+typedef struct
+{
+  Elf32_Half si_boundto;		/* Direct bindings, symbol bound to */
+  Elf32_Half si_flags;			/* Per symbol flags */
+} Elf32_Syminfo;
+
+typedef struct
+{
+  Elf64_Half si_boundto;		/* Direct bindings, symbol bound to */
+  Elf64_Half si_flags;			/* Per symbol flags */
+} Elf64_Syminfo;
+
+/* Possible values for si_boundto.  */
+#define SYMINFO_BT_SELF		0xffff	/* Symbol bound to self */
+#define SYMINFO_BT_PARENT	0xfffe	/* Symbol bound to parent */
+#define SYMINFO_BT_LOWRESERVE	0xff00	/* Beginning of reserved entries */
+
+/* Possible bitmasks for si_flags.  */
+#define SYMINFO_FLG_DIRECT	0x0001	/* Direct bound symbol */
+#define SYMINFO_FLG_PASSTHRU	0x0002	/* Pass-thru symbol for translator */
+#define SYMINFO_FLG_COPY	0x0004	/* Symbol is a copy-reloc */
+#define SYMINFO_FLG_LAZYLOAD	0x0008	/* Symbol bound to object to be lazy
+					   loaded */
+/* Syminfo version values.  */
+#define SYMINFO_NONE		0
+#define SYMINFO_CURRENT		1
+#define SYMINFO_NUM		2
+
+
+/* How to extract and insert information held in the st_info field.  */
+
+#define ELF32_ST_BIND(val)		(((unsigned char) (val)) >> 4)
+#define ELF32_ST_TYPE(val)		((val) & 0xf)
+#define ELF32_ST_INFO(bind, type)	(((bind) << 4) + ((type) & 0xf))
+
+/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field.  */
+#define ELF64_ST_BIND(val)		ELF32_ST_BIND (val)
+#define ELF64_ST_TYPE(val)		ELF32_ST_TYPE (val)
+#define ELF64_ST_INFO(bind, type)	ELF32_ST_INFO ((bind), (type))
+
+/* Legal values for ST_BIND subfield of st_info (symbol binding).  */
+
+#define STB_LOCAL	0		/* Local symbol */
+#define STB_GLOBAL	1		/* Global symbol */
+#define STB_WEAK	2		/* Weak symbol */
+#define	STB_NUM		3		/* Number of defined types.  */
+#define STB_LOOS	10		/* Start of OS-specific */
+#define STB_GNU_UNIQUE	10		/* Unique symbol.  */
+#define STB_HIOS	12		/* End of OS-specific */
+#define STB_LOPROC	13		/* Start of processor-specific */
+#define STB_HIPROC	15		/* End of processor-specific */
+
+/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
+
+#define STT_NOTYPE	0		/* Symbol type is unspecified */
+#define STT_OBJECT	1		/* Symbol is a data object */
+#define STT_FUNC	2		/* Symbol is a code object */
+#define STT_SECTION	3		/* Symbol associated with a section */
+#define STT_FILE	4		/* Symbol's name is file name */
+#define STT_COMMON	5		/* Symbol is a common data object */
+#define STT_TLS		6		/* Symbol is thread-local data object*/
+#define	STT_NUM		7		/* Number of defined types.  */
+#define STT_LOOS	10		/* Start of OS-specific */
+#define STT_GNU_IFUNC	10		/* Symbol is indirect code object */
+#define STT_HIOS	12		/* End of OS-specific */
+#define STT_LOPROC	13		/* Start of processor-specific */
+#define STT_HIPROC	15		/* End of processor-specific */
+
+
+/* Symbol table indices are found in the hash buckets and chain table
+   of a symbol hash table section.  This special index value indicates
+   the end of a chain, meaning no further symbols are found in that bucket.  */
+
+#define STN_UNDEF	0		/* End of a chain.  */
+
+
+/* How to extract and insert information held in the st_other field.  */
+
+#define ELF32_ST_VISIBILITY(o)	((o) & 0x03)
+
+/* For ELF64 the definitions are the same.  */
+#define ELF64_ST_VISIBILITY(o)	ELF32_ST_VISIBILITY (o)
+
+/* Symbol visibility specification encoded in the st_other field.  */
+#define STV_DEFAULT	0		/* Default symbol visibility rules */
+#define STV_INTERNAL	1		/* Processor specific hidden class */
+#define STV_HIDDEN	2		/* Sym unavailable in other modules */
+#define STV_PROTECTED	3		/* Not preemptible, not exported */
+
+
+/* Relocation table entry without addend (in section of type SHT_REL).  */
+
+typedef struct
+{
+  Elf32_Addr	r_offset;		/* Address */
+  Elf32_Word	r_info;			/* Relocation type and symbol index */
+} Elf32_Rel;
+
+/* I have seen two different definitions of the Elf64_Rel and
+   Elf64_Rela structures, so we'll leave them out until Novell (or
+   whoever) gets their act together.  */
+/* The following, at least, is used on Sparc v9, MIPS, and Alpha.  */
+
+typedef struct
+{
+  Elf64_Addr	r_offset;		/* Address */
+  Elf64_Xword	r_info;			/* Relocation type and symbol index */
+} Elf64_Rel;
+
+/* Relocation table entry with addend (in section of type SHT_RELA).  */
+
+typedef struct
+{
+  Elf32_Addr	r_offset;		/* Address */
+  Elf32_Word	r_info;			/* Relocation type and symbol index */
+  Elf32_Sword	r_addend;		/* Addend */
+} Elf32_Rela;
+
+typedef struct
+{
+  Elf64_Addr	r_offset;		/* Address */
+  Elf64_Xword	r_info;			/* Relocation type and symbol index */
+  Elf64_Sxword	r_addend;		/* Addend */
+} Elf64_Rela;
+
+/* How to extract and insert information held in the r_info field.  */
+
+#define ELF32_R_SYM(val)		((val) >> 8)
+#define ELF32_R_TYPE(val)		((val) & 0xff)
+#define ELF32_R_INFO(sym, type)		(((sym) << 8) + ((type) & 0xff))
+
+#define ELF64_R_SYM(i)			((i) >> 32)
+#define ELF64_R_TYPE(i)			((i) & 0xffffffff)
+#define ELF64_R_INFO(sym,type)		((((Elf64_Xword) (sym)) << 32) + (type))
+
+/* Program segment header.  */
+
+typedef struct
+{
+  Elf32_Word	p_type;			/* Segment type */
+  Elf32_Off	p_offset;		/* Segment file offset */
+  Elf32_Addr	p_vaddr;		/* Segment virtual address */
+  Elf32_Addr	p_paddr;		/* Segment physical address */
+  Elf32_Word	p_filesz;		/* Segment size in file */
+  Elf32_Word	p_memsz;		/* Segment size in memory */
+  Elf32_Word	p_flags;		/* Segment flags */
+  Elf32_Word	p_align;		/* Segment alignment */
+} Elf32_Phdr;
+
+typedef struct
+{
+  Elf64_Word	p_type;			/* Segment type */
+  Elf64_Word	p_flags;		/* Segment flags */
+  Elf64_Off	p_offset;		/* Segment file offset */
+  Elf64_Addr	p_vaddr;		/* Segment virtual address */
+  Elf64_Addr	p_paddr;		/* Segment physical address */
+  Elf64_Xword	p_filesz;		/* Segment size in file */
+  Elf64_Xword	p_memsz;		/* Segment size in memory */
+  Elf64_Xword	p_align;		/* Segment alignment */
+} Elf64_Phdr;
+
+/* Special value for e_phnum.  This indicates that the real number of
+   program headers is too large to fit into e_phnum.  Instead the real
+   value is in the field sh_info of section 0.  */
+
+#define PN_XNUM		0xffff
+
+/* Legal values for p_type (segment type).  */
+
+#define	PT_NULL		0		/* Program header table entry unused */
+#define PT_LOAD		1		/* Loadable program segment */
+#define PT_DYNAMIC	2		/* Dynamic linking information */
+#define PT_INTERP	3		/* Program interpreter */
+#define PT_NOTE		4		/* Auxiliary information */
+#define PT_SHLIB	5		/* Reserved */
+#define PT_PHDR		6		/* Entry for header table itself */
+#define PT_TLS		7		/* Thread-local storage segment */
+#define	PT_NUM		8		/* Number of defined types */
+#define PT_LOOS		0x60000000	/* Start of OS-specific */
+#define PT_GNU_EH_FRAME	0x6474e550	/* GCC .eh_frame_hdr segment */
+#define PT_GNU_STACK	0x6474e551	/* Indicates stack executability */
+#define PT_GNU_RELRO	0x6474e552	/* Read-only after relocation */
+#define PT_LOSUNW	0x6ffffffa
+#define PT_SUNWBSS	0x6ffffffa	/* Sun Specific segment */
+#define PT_SUNWSTACK	0x6ffffffb	/* Stack segment */
+#define PT_HISUNW	0x6fffffff
+#define PT_HIOS		0x6fffffff	/* End of OS-specific */
+#define PT_LOPROC	0x70000000	/* Start of processor-specific */
+#define PT_HIPROC	0x7fffffff	/* End of processor-specific */
+
+/* Legal values for p_flags (segment flags).  */
+
+#define PF_X		(1 << 0)	/* Segment is executable */
+#define PF_W		(1 << 1)	/* Segment is writable */
+#define PF_R		(1 << 2)	/* Segment is readable */
+#define PF_MASKOS	0x0ff00000	/* OS-specific */
+#define PF_MASKPROC	0xf0000000	/* Processor-specific */
+
+/* Legal values for note segment descriptor types for core files. */
+
+#define NT_PRSTATUS	1		/* Contains copy of prstatus struct */
+#define NT_PRFPREG	2		/* Contains copy of fpregset
+					   struct.  */
+#define NT_FPREGSET	2		/* Contains copy of fpregset struct */
+#define NT_PRPSINFO	3		/* Contains copy of prpsinfo struct */
+#define NT_PRXREG	4		/* Contains copy of prxregset struct */
+#define NT_TASKSTRUCT	4		/* Contains copy of task structure */
+#define NT_PLATFORM	5		/* String from sysinfo(SI_PLATFORM) */
+#define NT_AUXV		6		/* Contains copy of auxv array */
+#define NT_GWINDOWS	7		/* Contains copy of gwindows struct */
+#define NT_ASRS		8		/* Contains copy of asrset struct */
+#define NT_PSTATUS	10		/* Contains copy of pstatus struct */
+#define NT_PSINFO	13		/* Contains copy of psinfo struct */
+#define NT_PRCRED	14		/* Contains copy of prcred struct */
+#define NT_UTSNAME	15		/* Contains copy of utsname struct */
+#define NT_LWPSTATUS	16		/* Contains copy of lwpstatus struct */
+#define NT_LWPSINFO	17		/* Contains copy of lwpinfo struct */
+#define NT_PRFPXREG	20		/* Contains copy of fprxregset struct */
+#define NT_SIGINFO	0x53494749	/* Contains copy of siginfo_t,
+					   size might increase */
+#define NT_FILE		0x46494c45	/* Contains information about mapped
+					   files */
+#define NT_PRXFPREG	0x46e62b7f	/* Contains copy of user_fxsr_struct */
+#define NT_PPC_VMX	0x100		/* PowerPC Altivec/VMX registers */
+#define NT_PPC_SPE	0x101		/* PowerPC SPE/EVR registers */
+#define NT_PPC_VSX	0x102		/* PowerPC VSX registers */
+#define NT_PPC_TAR	0x103		/* Target Address Register */
+#define NT_PPC_PPR	0x104		/* Program Priority Register */
+#define NT_PPC_DSCR	0x105		/* Data Stream Control Register */
+#define NT_PPC_EBB	0x106		/* Event Based Branch Registers */
+#define NT_PPC_PMU	0x107		/* Performance Monitor Registers */
+#define NT_PPC_TM_CGPR	0x108		/* TM checkpointed GPR Registers */
+#define NT_PPC_TM_CFPR	0x109		/* TM checkpointed FPR Registers */
+#define NT_PPC_TM_CVMX	0x10a		/* TM checkpointed VMX Registers */
+#define NT_PPC_TM_CVSX	0x10b		/* TM checkpointed VSX Registers */
+#define NT_PPC_TM_SPR	0x10c		/* TM Special Purpose Registers */
+#define NT_PPC_TM_CTAR	0x10d		/* TM checkpointed Target Address
+					   Register */
+#define NT_PPC_TM_CPPR	0x10e		/* TM checkpointed Program Priority
+					   Register */
+#define NT_PPC_TM_CDSCR	0x10f		/* TM checkpointed Data Stream Control
+					   Register */
+#define NT_PPC_PKEY	0x110		/* Memory Protection Keys
+					   registers.  */
+#define NT_386_TLS	0x200		/* i386 TLS slots (struct user_desc) */
+#define NT_386_IOPERM	0x201		/* x86 io permission bitmap (1=deny) */
+#define NT_X86_XSTATE	0x202		/* x86 extended state using xsave */
+#define NT_S390_HIGH_GPRS	0x300	/* s390 upper register halves */
+#define NT_S390_TIMER	0x301		/* s390 timer register */
+#define NT_S390_TODCMP	0x302		/* s390 TOD clock comparator register */
+#define NT_S390_TODPREG	0x303		/* s390 TOD programmable register */
+#define NT_S390_CTRS	0x304		/* s390 control registers */
+#define NT_S390_PREFIX	0x305		/* s390 prefix register */
+#define NT_S390_LAST_BREAK	0x306	/* s390 breaking event address */
+#define NT_S390_SYSTEM_CALL	0x307	/* s390 system call restart data */
+#define NT_S390_TDB	0x308		/* s390 transaction diagnostic block */
+#define NT_S390_VXRS_LOW	0x309	/* s390 vector registers 0-15
+					   upper half.  */
+#define NT_S390_VXRS_HIGH	0x30a	/* s390 vector registers 16-31.  */
+#define NT_S390_GS_CB	0x30b		/* s390 guarded storage registers.  */
+#define NT_S390_GS_BC	0x30c		/* s390 guarded storage
+					   broadcast control block.  */
+#define NT_S390_RI_CB	0x30d		/* s390 runtime instrumentation.  */
+#define NT_ARM_VFP	0x400		/* ARM VFP/NEON registers */
+#define NT_ARM_TLS	0x401		/* ARM TLS register */
+#define NT_ARM_HW_BREAK	0x402		/* ARM hardware breakpoint registers */
+#define NT_ARM_HW_WATCH	0x403		/* ARM hardware watchpoint registers */
+#define NT_ARM_SYSTEM_CALL	0x404	/* ARM system call number */
+#define NT_ARM_SVE	0x405		/* ARM Scalable Vector Extension
+					   registers */
+#define NT_VMCOREDD	0x700		/* Vmcore Device Dump Note.  */
+#define NT_MIPS_DSP	0x800		/* MIPS DSP ASE registers.  */
+#define NT_MIPS_FP_MODE	0x801		/* MIPS floating-point mode.  */
+
+/* Legal values for the note segment descriptor types for object files.  */
+
+#define NT_VERSION	1		/* Contains a version string.  */
+
+
+/* Dynamic section entry.  */
+
+typedef struct
+{
+  Elf32_Sword	d_tag;			/* Dynamic entry type */
+  union
+    {
+      Elf32_Word d_val;			/* Integer value */
+      Elf32_Addr d_ptr;			/* Address value */
+    } d_un;
+} Elf32_Dyn;
+
+typedef struct
+{
+  Elf64_Sxword	d_tag;			/* Dynamic entry type */
+  union
+    {
+      Elf64_Xword d_val;		/* Integer value */
+      Elf64_Addr d_ptr;			/* Address value */
+    } d_un;
+} Elf64_Dyn;
+
+/* Legal values for d_tag (dynamic entry type).  */
+
+#define DT_NULL		0		/* Marks end of dynamic section */
+#define DT_NEEDED	1		/* Name of needed library */
+#define DT_PLTRELSZ	2		/* Size in bytes of PLT relocs */
+#define DT_PLTGOT	3		/* Processor defined value */
+#define DT_HASH		4		/* Address of symbol hash table */
+#define DT_STRTAB	5		/* Address of string table */
+#define DT_SYMTAB	6		/* Address of symbol table */
+#define DT_RELA		7		/* Address of Rela relocs */
+#define DT_RELASZ	8		/* Total size of Rela relocs */
+#define DT_RELAENT	9		/* Size of one Rela reloc */
+#define DT_STRSZ	10		/* Size of string table */
+#define DT_SYMENT	11		/* Size of one symbol table entry */
+#define DT_INIT		12		/* Address of init function */
+#define DT_FINI		13		/* Address of termination function */
+#define DT_SONAME	14		/* Name of shared object */
+#define DT_RPATH	15		/* Library search path (deprecated) */
+#define DT_SYMBOLIC	16		/* Start symbol search here */
+#define DT_REL		17		/* Address of Rel relocs */
+#define DT_RELSZ	18		/* Total size of Rel relocs */
+#define DT_RELENT	19		/* Size of one Rel reloc */
+#define DT_PLTREL	20		/* Type of reloc in PLT */
+#define DT_DEBUG	21		/* For debugging; unspecified */
+#define DT_TEXTREL	22		/* Reloc might modify .text */
+#define DT_JMPREL	23		/* Address of PLT relocs */
+#define	DT_BIND_NOW	24		/* Process relocations of object */
+#define	DT_INIT_ARRAY	25		/* Array with addresses of init fct */
+#define	DT_FINI_ARRAY	26		/* Array with addresses of fini fct */
+#define	DT_INIT_ARRAYSZ	27		/* Size in bytes of DT_INIT_ARRAY */
+#define	DT_FINI_ARRAYSZ	28		/* Size in bytes of DT_FINI_ARRAY */
+#define DT_RUNPATH	29		/* Library search path */
+#define DT_FLAGS	30		/* Flags for the object being loaded */
+#define DT_ENCODING	32		/* Start of encoded range */
+#define DT_PREINIT_ARRAY 32		/* Array with addresses of preinit fct*/
+#define DT_PREINIT_ARRAYSZ 33		/* size in bytes of DT_PREINIT_ARRAY */
+#define DT_SYMTAB_SHNDX	34		/* Address of SYMTAB_SHNDX section */
+#define	DT_NUM		35		/* Number used */
+#define DT_LOOS		0x6000000d	/* Start of OS-specific */
+#define DT_HIOS		0x6ffff000	/* End of OS-specific */
+#define DT_LOPROC	0x70000000	/* Start of processor-specific */
+#define DT_HIPROC	0x7fffffff	/* End of processor-specific */
+#define	DT_PROCNUM	DT_MIPS_NUM	/* Most used by any processor */
+
+/* DT_* entries which fall between DT_VALRNGHI & DT_VALRNGLO use the
+   Dyn.d_un.d_val field of the Elf*_Dyn structure.  This follows Sun's
+   approach.  */
+#define DT_VALRNGLO	0x6ffffd00
+#define DT_GNU_PRELINKED 0x6ffffdf5	/* Prelinking timestamp */
+#define DT_GNU_CONFLICTSZ 0x6ffffdf6	/* Size of conflict section */
+#define DT_GNU_LIBLISTSZ 0x6ffffdf7	/* Size of library list */
+#define DT_CHECKSUM	0x6ffffdf8
+#define DT_PLTPADSZ	0x6ffffdf9
+#define DT_MOVEENT	0x6ffffdfa
+#define DT_MOVESZ	0x6ffffdfb
+#define DT_FEATURE_1	0x6ffffdfc	/* Feature selection (DTF_*).  */
+#define DT_POSFLAG_1	0x6ffffdfd	/* Flags for DT_* entries, effecting
+					   the following DT_* entry.  */
+#define DT_SYMINSZ	0x6ffffdfe	/* Size of syminfo table (in bytes) */
+#define DT_SYMINENT	0x6ffffdff	/* Entry size of syminfo */
+#define DT_VALRNGHI	0x6ffffdff
+#define DT_VALTAGIDX(tag)	(DT_VALRNGHI - (tag))	/* Reverse order! */
+#define DT_VALNUM 12
+
+/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the
+   Dyn.d_un.d_ptr field of the Elf*_Dyn structure.
+
+   If any adjustment is made to the ELF object after it has been
+   built these entries will need to be adjusted.  */
+#define DT_ADDRRNGLO	0x6ffffe00
+#define DT_GNU_HASH	0x6ffffef5	/* GNU-style hash table.  */
+#define DT_TLSDESC_PLT	0x6ffffef6
+#define DT_TLSDESC_GOT	0x6ffffef7
+#define DT_GNU_CONFLICT	0x6ffffef8	/* Start of conflict section */
+#define DT_GNU_LIBLIST	0x6ffffef9	/* Library list */
+#define DT_CONFIG	0x6ffffefa	/* Configuration information.  */
+#define DT_DEPAUDIT	0x6ffffefb	/* Dependency auditing.  */
+#define DT_AUDIT	0x6ffffefc	/* Object auditing.  */
+#define	DT_PLTPAD	0x6ffffefd	/* PLT padding.  */
+#define	DT_MOVETAB	0x6ffffefe	/* Move table.  */
+#define DT_SYMINFO	0x6ffffeff	/* Syminfo table.  */
+#define DT_ADDRRNGHI	0x6ffffeff
+#define DT_ADDRTAGIDX(tag)	(DT_ADDRRNGHI - (tag))	/* Reverse order! */
+#define DT_ADDRNUM 11
+
+/* The versioning entry types.  The next are defined as part of the
+   GNU extension.  */
+#define DT_VERSYM	0x6ffffff0
+
+#define DT_RELACOUNT	0x6ffffff9
+#define DT_RELCOUNT	0x6ffffffa
+
+/* These were chosen by Sun.  */
+#define DT_FLAGS_1	0x6ffffffb	/* State flags, see DF_1_* below.  */
+#define	DT_VERDEF	0x6ffffffc	/* Address of version definition
+					   table */
+#define	DT_VERDEFNUM	0x6ffffffd	/* Number of version definitions */
+#define	DT_VERNEED	0x6ffffffe	/* Address of table with needed
+					   versions */
+#define	DT_VERNEEDNUM	0x6fffffff	/* Number of needed versions */
+#define DT_VERSIONTAGIDX(tag)	(DT_VERNEEDNUM - (tag))	/* Reverse order! */
+#define DT_VERSIONTAGNUM 16
+
+/* Sun added these machine-independent extensions in the "processor-specific"
+   range.  Be compatible.  */
+#define DT_AUXILIARY    0x7ffffffd      /* Shared object to load before self */
+#define DT_FILTER       0x7fffffff      /* Shared object to get values from */
+#define DT_EXTRATAGIDX(tag)	((Elf32_Word)-((Elf32_Sword) (tag) <<1>>1)-1)
+#define DT_EXTRANUM	3
+
+/* Values of `d_un.d_val' in the DT_FLAGS entry.  */
+#define DF_ORIGIN	0x00000001	/* Object may use DF_ORIGIN */
+#define DF_SYMBOLIC	0x00000002	/* Symbol resolutions starts here */
+#define DF_TEXTREL	0x00000004	/* Object contains text relocations */
+#define DF_BIND_NOW	0x00000008	/* No lazy binding for this object */
+#define DF_STATIC_TLS	0x00000010	/* Module uses the static TLS model */
+
+/* State flags selectable in the `d_un.d_val' element of the DT_FLAGS_1
+   entry in the dynamic section.  */
+#define DF_1_NOW	0x00000001	/* Set RTLD_NOW for this object.  */
+#define DF_1_GLOBAL	0x00000002	/* Set RTLD_GLOBAL for this object.  */
+#define DF_1_GROUP	0x00000004	/* Set RTLD_GROUP for this object.  */
+#define DF_1_NODELETE	0x00000008	/* Set RTLD_NODELETE for this object.*/
+#define DF_1_LOADFLTR	0x00000010	/* Trigger filtee loading at runtime.*/
+#define DF_1_INITFIRST	0x00000020	/* Set RTLD_INITFIRST for this object*/
+#define DF_1_NOOPEN	0x00000040	/* Set RTLD_NOOPEN for this object.  */
+#define DF_1_ORIGIN	0x00000080	/* $ORIGIN must be handled.  */
+#define DF_1_DIRECT	0x00000100	/* Direct binding enabled.  */
+#define DF_1_TRANS	0x00000200
+#define DF_1_INTERPOSE	0x00000400	/* Object is used to interpose.  */
+#define DF_1_NODEFLIB	0x00000800	/* Ignore default lib search path.  */
+#define DF_1_NODUMP	0x00001000	/* Object can't be dldump'ed.  */
+#define DF_1_CONFALT	0x00002000	/* Configuration alternative created.*/
+#define DF_1_ENDFILTEE	0x00004000	/* Filtee terminates filters search. */
+#define	DF_1_DISPRELDNE	0x00008000	/* Disp reloc applied at build time. */
+#define	DF_1_DISPRELPND	0x00010000	/* Disp reloc applied at run-time.  */
+#define	DF_1_NODIRECT	0x00020000	/* Object has no-direct binding. */
+#define	DF_1_IGNMULDEF	0x00040000
+#define	DF_1_NOKSYMS	0x00080000
+#define	DF_1_NOHDR	0x00100000
+#define	DF_1_EDITED	0x00200000	/* Object is modified after built.  */
+#define	DF_1_NORELOC	0x00400000
+#define	DF_1_SYMINTPOSE	0x00800000	/* Object has individual interposers.  */
+#define	DF_1_GLOBAUDIT	0x01000000	/* Global auditing required.  */
+#define	DF_1_SINGLETON	0x02000000	/* Singleton symbols are used.  */
+#define	DF_1_STUB	0x04000000
+#define	DF_1_PIE	0x08000000
+
+/* Flags for the feature selection in DT_FEATURE_1.  */
+#define DTF_1_PARINIT	0x00000001
+#define DTF_1_CONFEXP	0x00000002
+
+/* Flags in the DT_POSFLAG_1 entry effecting only the next DT_* entry.  */
+#define DF_P1_LAZYLOAD	0x00000001	/* Lazyload following object.  */
+#define DF_P1_GROUPPERM	0x00000002	/* Symbols from next object are not
+					   generally available.  */
+
+/* Version definition sections.  */
+
+typedef struct
+{
+  Elf32_Half	vd_version;		/* Version revision */
+  Elf32_Half	vd_flags;		/* Version information */
+  Elf32_Half	vd_ndx;			/* Version Index */
+  Elf32_Half	vd_cnt;			/* Number of associated aux entries */
+  Elf32_Word	vd_hash;		/* Version name hash value */
+  Elf32_Word	vd_aux;			/* Offset in bytes to verdaux array */
+  Elf32_Word	vd_next;		/* Offset in bytes to next verdef
+					   entry */
+} Elf32_Verdef;
+
+typedef struct
+{
+  Elf64_Half	vd_version;		/* Version revision */
+  Elf64_Half	vd_flags;		/* Version information */
+  Elf64_Half	vd_ndx;			/* Version Index */
+  Elf64_Half	vd_cnt;			/* Number of associated aux entries */
+  Elf64_Word	vd_hash;		/* Version name hash value */
+  Elf64_Word	vd_aux;			/* Offset in bytes to verdaux array */
+  Elf64_Word	vd_next;		/* Offset in bytes to next verdef
+					   entry */
+} Elf64_Verdef;
+
+
+/* Legal values for vd_version (version revision).  */
+#define VER_DEF_NONE	0		/* No version */
+#define VER_DEF_CURRENT	1		/* Current version */
+#define VER_DEF_NUM	2		/* Given version number */
+
+/* Legal values for vd_flags (version information flags).  */
+#define VER_FLG_BASE	0x1		/* Version definition of file itself */
+#define VER_FLG_WEAK	0x2		/* Weak version identifier */
+
+/* Versym symbol index values.  */
+#define	VER_NDX_LOCAL		0	/* Symbol is local.  */
+#define	VER_NDX_GLOBAL		1	/* Symbol is global.  */
+#define	VER_NDX_LORESERVE	0xff00	/* Beginning of reserved entries.  */
+#define	VER_NDX_ELIMINATE	0xff01	/* Symbol is to be eliminated.  */
+
+/* Auxialiary version information.  */
+
+typedef struct
+{
+  Elf32_Word	vda_name;		/* Version or dependency names */
+  Elf32_Word	vda_next;		/* Offset in bytes to next verdaux
+					   entry */
+} Elf32_Verdaux;
+
+typedef struct
+{
+  Elf64_Word	vda_name;		/* Version or dependency names */
+  Elf64_Word	vda_next;		/* Offset in bytes to next verdaux
+					   entry */
+} Elf64_Verdaux;
+
+
+/* Version dependency section.  */
+
+typedef struct
+{
+  Elf32_Half	vn_version;		/* Version of structure */
+  Elf32_Half	vn_cnt;			/* Number of associated aux entries */
+  Elf32_Word	vn_file;		/* Offset of filename for this
+					   dependency */
+  Elf32_Word	vn_aux;			/* Offset in bytes to vernaux array */
+  Elf32_Word	vn_next;		/* Offset in bytes to next verneed
+					   entry */
+} Elf32_Verneed;
+
+typedef struct
+{
+  Elf64_Half	vn_version;		/* Version of structure */
+  Elf64_Half	vn_cnt;			/* Number of associated aux entries */
+  Elf64_Word	vn_file;		/* Offset of filename for this
+					   dependency */
+  Elf64_Word	vn_aux;			/* Offset in bytes to vernaux array */
+  Elf64_Word	vn_next;		/* Offset in bytes to next verneed
+					   entry */
+} Elf64_Verneed;
+
+
+/* Legal values for vn_version (version revision).  */
+#define VER_NEED_NONE	 0		/* No version */
+#define VER_NEED_CURRENT 1		/* Current version */
+#define VER_NEED_NUM	 2		/* Given version number */
+
+/* Auxiliary needed version information.  */
+
+typedef struct
+{
+  Elf32_Word	vna_hash;		/* Hash value of dependency name */
+  Elf32_Half	vna_flags;		/* Dependency specific information */
+  Elf32_Half	vna_other;		/* Unused */
+  Elf32_Word	vna_name;		/* Dependency name string offset */
+  Elf32_Word	vna_next;		/* Offset in bytes to next vernaux
+					   entry */
+} Elf32_Vernaux;
+
+typedef struct
+{
+  Elf64_Word	vna_hash;		/* Hash value of dependency name */
+  Elf64_Half	vna_flags;		/* Dependency specific information */
+  Elf64_Half	vna_other;		/* Unused */
+  Elf64_Word	vna_name;		/* Dependency name string offset */
+  Elf64_Word	vna_next;		/* Offset in bytes to next vernaux
+					   entry */
+} Elf64_Vernaux;
+
+
+/* Legal values for vna_flags.  */
+#define VER_FLG_WEAK	0x2		/* Weak version identifier */
+
+
+/* Auxiliary vector.  */
+
+/* This vector is normally only used by the program interpreter.  The
+   usual definition in an ABI supplement uses the name auxv_t.  The
+   vector is not usually defined in a standard <elf.h> file, but it
+   can't hurt.  We rename it to avoid conflicts.  The sizes of these
+   types are an arrangement between the exec server and the program
+   interpreter, so we don't fully specify them here.  */
+
+typedef struct
+{
+  uint32_t a_type;		/* Entry type */
+  union
+    {
+      uint32_t a_val;		/* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+	 though, since it does not work when using 32-bit definitions
+	 on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf32_auxv_t;
+
+typedef struct
+{
+  uint64_t a_type;		/* Entry type */
+  union
+    {
+      uint64_t a_val;		/* Integer value */
+      /* We use to have pointer elements added here.  We cannot do that,
+	 though, since it does not work when using 32-bit definitions
+	 on 64-bit platforms and vice versa.  */
+    } a_un;
+} Elf64_auxv_t;
+
+/* Legal values for a_type (entry type).  */
+
+#define AT_NULL		0		/* End of vector */
+#define AT_IGNORE	1		/* Entry should be ignored */
+#define AT_EXECFD	2		/* File descriptor of program */
+#define AT_PHDR		3		/* Program headers for program */
+#define AT_PHENT	4		/* Size of program header entry */
+#define AT_PHNUM	5		/* Number of program headers */
+#define AT_PAGESZ	6		/* System page size */
+#define AT_BASE		7		/* Base address of interpreter */
+#define AT_FLAGS	8		/* Flags */
+#define AT_ENTRY	9		/* Entry point of program */
+#define AT_NOTELF	10		/* Program is not ELF */
+#define AT_UID		11		/* Real uid */
+#define AT_EUID		12		/* Effective uid */
+#define AT_GID		13		/* Real gid */
+#define AT_EGID		14		/* Effective gid */
+#define AT_CLKTCK	17		/* Frequency of times() */
+
+/* Some more special a_type values describing the hardware.  */
+#define AT_PLATFORM	15		/* String identifying platform.  */
+#define AT_HWCAP	16		/* Machine-dependent hints about
+					   processor capabilities.  */
+
+/* This entry gives some information about the FPU initialization
+   performed by the kernel.  */
+#define AT_FPUCW	18		/* Used FPU control word.  */
+
+/* Cache block sizes.  */
+#define AT_DCACHEBSIZE	19		/* Data cache block size.  */
+#define AT_ICACHEBSIZE	20		/* Instruction cache block size.  */
+#define AT_UCACHEBSIZE	21		/* Unified cache block size.  */
+
+/* A special ignored value for PPC, used by the kernel to control the
+   interpretation of the AUXV. Must be > 16.  */
+#define AT_IGNOREPPC	22		/* Entry should be ignored.  */
+
+#define	AT_SECURE	23		/* Boolean, was exec setuid-like?  */
+
+#define AT_BASE_PLATFORM 24		/* String identifying real platforms.*/
+
+#define AT_RANDOM	25		/* Address of 16 random bytes.  */
+
+#define AT_HWCAP2	26		/* More machine-dependent hints about
+					   processor capabilities.  */
+
+#define AT_EXECFN	31		/* Filename of executable.  */
+
+/* Pointer to the global system page used for system calls and other
+   nice things.  */
+#define AT_SYSINFO	32
+#define AT_SYSINFO_EHDR	33
+
+/* Shapes of the caches.  Bits 0-3 contains associativity; bits 4-7 contains
+   log2 of line size; mask those to get cache size.  */
+#define AT_L1I_CACHESHAPE	34
+#define AT_L1D_CACHESHAPE	35
+#define AT_L2_CACHESHAPE	36
+#define AT_L3_CACHESHAPE	37
+
+/* Shapes of the caches, with more room to describe them.
+   *GEOMETRY are comprised of cache line size in bytes in the bottom 16 bits
+   and the cache associativity in the next 16 bits.  */
+#define AT_L1I_CACHESIZE	40
+#define AT_L1I_CACHEGEOMETRY	41
+#define AT_L1D_CACHESIZE	42
+#define AT_L1D_CACHEGEOMETRY	43
+#define AT_L2_CACHESIZE		44
+#define AT_L2_CACHEGEOMETRY	45
+#define AT_L3_CACHESIZE		46
+#define AT_L3_CACHEGEOMETRY	47
+
+#define AT_MINSIGSTKSZ		51 /* Stack needed for signal delivery
+				      (AArch64).  */
+
+/* Note section contents.  Each entry in the note section begins with
+   a header of a fixed form.  */
+
+typedef struct
+{
+  Elf32_Word n_namesz;			/* Length of the note's name.  */
+  Elf32_Word n_descsz;			/* Length of the note's descriptor.  */
+  Elf32_Word n_type;			/* Type of the note.  */
+} Elf32_Nhdr;
+
+typedef struct
+{
+  Elf64_Word n_namesz;			/* Length of the note's name.  */
+  Elf64_Word n_descsz;			/* Length of the note's descriptor.  */
+  Elf64_Word n_type;			/* Type of the note.  */
+} Elf64_Nhdr;
+
+/* Known names of notes.  */
+
+/* Solaris entries in the note section have this name.  */
+#define ELF_NOTE_SOLARIS	"SUNW Solaris"
+
+/* Note entries for GNU systems have this name.  */
+#define ELF_NOTE_GNU		"GNU"
+
+
+/* Defined types of notes for Solaris.  */
+
+/* Value of descriptor (one word) is desired pagesize for the binary.  */
+#define ELF_NOTE_PAGESIZE_HINT	1
+
+
+/* Defined note types for GNU systems.  */
+
+/* ABI information.  The descriptor consists of words:
+   word 0: OS descriptor
+   word 1: major version of the ABI
+   word 2: minor version of the ABI
+   word 3: subminor version of the ABI
+*/
+#define NT_GNU_ABI_TAG	1
+#define ELF_NOTE_ABI	NT_GNU_ABI_TAG /* Old name.  */
+
+/* Known OSes.  These values can appear in word 0 of an
+   NT_GNU_ABI_TAG note section entry.  */
+#define ELF_NOTE_OS_LINUX	0
+#define ELF_NOTE_OS_GNU		1
+#define ELF_NOTE_OS_SOLARIS2	2
+#define ELF_NOTE_OS_FREEBSD	3
+
+/* Synthetic hwcap information.  The descriptor begins with two words:
+   word 0: number of entries
+   word 1: bitmask of enabled entries
+   Then follow variable-length entries, one byte followed by a
+   '\0'-terminated hwcap name string.  The byte gives the bit
+   number to test if enabled, (1U << bit) & bitmask.  */
+#define NT_GNU_HWCAP	2
+
+/* Build ID bits as generated by ld --build-id.
+   The descriptor consists of any nonzero number of bytes.  */
+#define NT_GNU_BUILD_ID	3
+
+/* Version note generated by GNU gold containing a version string.  */
+#define NT_GNU_GOLD_VERSION	4
+
+/* Program property.  */
+#define NT_GNU_PROPERTY_TYPE_0 5
+
+/* Note section name of program property.   */
+#define NOTE_GNU_PROPERTY_SECTION_NAME ".note.gnu.property"
+
+/* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0).  */
+
+/* Stack size.  */
+#define GNU_PROPERTY_STACK_SIZE			1
+/* No copy relocation on protected data symbol.  */
+#define GNU_PROPERTY_NO_COPY_ON_PROTECTED	2
+
+/* Processor-specific semantics, lo */
+#define GNU_PROPERTY_LOPROC			0xc0000000
+/* Processor-specific semantics, hi */
+#define GNU_PROPERTY_HIPROC			0xdfffffff
+/* Application-specific semantics, lo */
+#define GNU_PROPERTY_LOUSER			0xe0000000
+/* Application-specific semantics, hi */
+#define GNU_PROPERTY_HIUSER			0xffffffff
+
+/* The x86 instruction sets indicated by the corresponding bits are
+   used in program.  Their support in the hardware is optional.  */
+#define GNU_PROPERTY_X86_ISA_1_USED		0xc0000000
+/* The x86 instruction sets indicated by the corresponding bits are
+   used in program and they must be supported by the hardware.   */
+#define GNU_PROPERTY_X86_ISA_1_NEEDED		0xc0000001
+/* X86 processor-specific features used in program.  */
+#define GNU_PROPERTY_X86_FEATURE_1_AND		0xc0000002
+
+#define GNU_PROPERTY_X86_ISA_1_486		(1U << 0)
+#define GNU_PROPERTY_X86_ISA_1_586		(1U << 1)
+#define GNU_PROPERTY_X86_ISA_1_686		(1U << 2)
+#define GNU_PROPERTY_X86_ISA_1_SSE		(1U << 3)
+#define GNU_PROPERTY_X86_ISA_1_SSE2		(1U << 4)
+#define GNU_PROPERTY_X86_ISA_1_SSE3		(1U << 5)
+#define GNU_PROPERTY_X86_ISA_1_SSSE3		(1U << 6)
+#define GNU_PROPERTY_X86_ISA_1_SSE4_1		(1U << 7)
+#define GNU_PROPERTY_X86_ISA_1_SSE4_2		(1U << 8)
+#define GNU_PROPERTY_X86_ISA_1_AVX		(1U << 9)
+#define GNU_PROPERTY_X86_ISA_1_AVX2		(1U << 10)
+#define GNU_PROPERTY_X86_ISA_1_AVX512F		(1U << 11)
+#define GNU_PROPERTY_X86_ISA_1_AVX512CD		(1U << 12)
+#define GNU_PROPERTY_X86_ISA_1_AVX512ER		(1U << 13)
+#define GNU_PROPERTY_X86_ISA_1_AVX512PF		(1U << 14)
+#define GNU_PROPERTY_X86_ISA_1_AVX512VL		(1U << 15)
+#define GNU_PROPERTY_X86_ISA_1_AVX512DQ		(1U << 16)
+#define GNU_PROPERTY_X86_ISA_1_AVX512BW		(1U << 17)
+
+/* This indicates that all executable sections are compatible with
+   IBT.  */
+#define GNU_PROPERTY_X86_FEATURE_1_IBT		(1U << 0)
+/* This indicates that all executable sections are compatible with
+   SHSTK.  */
+#define GNU_PROPERTY_X86_FEATURE_1_SHSTK	(1U << 1)
+
+/* Move records.  */
+typedef struct
+{
+  Elf32_Xword m_value;		/* Symbol value.  */
+  Elf32_Word m_info;		/* Size and index.  */
+  Elf32_Word m_poffset;		/* Symbol offset.  */
+  Elf32_Half m_repeat;		/* Repeat count.  */
+  Elf32_Half m_stride;		/* Stride info.  */
+} Elf32_Move;
+
+typedef struct
+{
+  Elf64_Xword m_value;		/* Symbol value.  */
+  Elf64_Xword m_info;		/* Size and index.  */
+  Elf64_Xword m_poffset;	/* Symbol offset.  */
+  Elf64_Half m_repeat;		/* Repeat count.  */
+  Elf64_Half m_stride;		/* Stride info.  */
+} Elf64_Move;
+
+/* Macro to construct move records.  */
+#define ELF32_M_SYM(info)	((info) >> 8)
+#define ELF32_M_SIZE(info)	((unsigned char) (info))
+#define ELF32_M_INFO(sym, size)	(((sym) << 8) + (unsigned char) (size))
+
+#define ELF64_M_SYM(info)	ELF32_M_SYM (info)
+#define ELF64_M_SIZE(info)	ELF32_M_SIZE (info)
+#define ELF64_M_INFO(sym, size)	ELF32_M_INFO (sym, size)
+
+
+/* Motorola 68k specific definitions.  */
+
+/* Values for Elf32_Ehdr.e_flags.  */
+#define EF_CPU32	0x00810000
+
+/* m68k relocs.  */
+
+#define R_68K_NONE	0		/* No reloc */
+#define R_68K_32	1		/* Direct 32 bit  */
+#define R_68K_16	2		/* Direct 16 bit  */
+#define R_68K_8		3		/* Direct 8 bit  */
+#define R_68K_PC32	4		/* PC relative 32 bit */
+#define R_68K_PC16	5		/* PC relative 16 bit */
+#define R_68K_PC8	6		/* PC relative 8 bit */
+#define R_68K_GOT32	7		/* 32 bit PC relative GOT entry */
+#define R_68K_GOT16	8		/* 16 bit PC relative GOT entry */
+#define R_68K_GOT8	9		/* 8 bit PC relative GOT entry */
+#define R_68K_GOT32O	10		/* 32 bit GOT offset */
+#define R_68K_GOT16O	11		/* 16 bit GOT offset */
+#define R_68K_GOT8O	12		/* 8 bit GOT offset */
+#define R_68K_PLT32	13		/* 32 bit PC relative PLT address */
+#define R_68K_PLT16	14		/* 16 bit PC relative PLT address */
+#define R_68K_PLT8	15		/* 8 bit PC relative PLT address */
+#define R_68K_PLT32O	16		/* 32 bit PLT offset */
+#define R_68K_PLT16O	17		/* 16 bit PLT offset */
+#define R_68K_PLT8O	18		/* 8 bit PLT offset */
+#define R_68K_COPY	19		/* Copy symbol at runtime */
+#define R_68K_GLOB_DAT	20		/* Create GOT entry */
+#define R_68K_JMP_SLOT	21		/* Create PLT entry */
+#define R_68K_RELATIVE	22		/* Adjust by program base */
+#define R_68K_TLS_GD32      25          /* 32 bit GOT offset for GD */
+#define R_68K_TLS_GD16      26          /* 16 bit GOT offset for GD */
+#define R_68K_TLS_GD8       27          /* 8 bit GOT offset for GD */
+#define R_68K_TLS_LDM32     28          /* 32 bit GOT offset for LDM */
+#define R_68K_TLS_LDM16     29          /* 16 bit GOT offset for LDM */
+#define R_68K_TLS_LDM8      30          /* 8 bit GOT offset for LDM */
+#define R_68K_TLS_LDO32     31          /* 32 bit module-relative offset */
+#define R_68K_TLS_LDO16     32          /* 16 bit module-relative offset */
+#define R_68K_TLS_LDO8      33          /* 8 bit module-relative offset */
+#define R_68K_TLS_IE32      34          /* 32 bit GOT offset for IE */
+#define R_68K_TLS_IE16      35          /* 16 bit GOT offset for IE */
+#define R_68K_TLS_IE8       36          /* 8 bit GOT offset for IE */
+#define R_68K_TLS_LE32      37          /* 32 bit offset relative to
+					   static TLS block */
+#define R_68K_TLS_LE16      38          /* 16 bit offset relative to
+					   static TLS block */
+#define R_68K_TLS_LE8       39          /* 8 bit offset relative to
+					   static TLS block */
+#define R_68K_TLS_DTPMOD32  40          /* 32 bit module number */
+#define R_68K_TLS_DTPREL32  41          /* 32 bit module-relative offset */
+#define R_68K_TLS_TPREL32   42          /* 32 bit TP-relative offset */
+/* Keep this the last entry.  */
+#define R_68K_NUM	43
+
+/* Intel 80386 specific definitions.  */
+
+/* i386 relocs.  */
+
+#define R_386_NONE	   0		/* No reloc */
+#define R_386_32	   1		/* Direct 32 bit  */
+#define R_386_PC32	   2		/* PC relative 32 bit */
+#define R_386_GOT32	   3		/* 32 bit GOT entry */
+#define R_386_PLT32	   4		/* 32 bit PLT address */
+#define R_386_COPY	   5		/* Copy symbol at runtime */
+#define R_386_GLOB_DAT	   6		/* Create GOT entry */
+#define R_386_JMP_SLOT	   7		/* Create PLT entry */
+#define R_386_RELATIVE	   8		/* Adjust by program base */
+#define R_386_GOTOFF	   9		/* 32 bit offset to GOT */
+#define R_386_GOTPC	   10		/* 32 bit PC relative offset to GOT */
+#define R_386_32PLT	   11
+#define R_386_TLS_TPOFF	   14		/* Offset in static TLS block */
+#define R_386_TLS_IE	   15		/* Address of GOT entry for static TLS
+					   block offset */
+#define R_386_TLS_GOTIE	   16		/* GOT entry for static TLS block
+					   offset */
+#define R_386_TLS_LE	   17		/* Offset relative to static TLS
+					   block */
+#define R_386_TLS_GD	   18		/* Direct 32 bit for GNU version of
+					   general dynamic thread local data */
+#define R_386_TLS_LDM	   19		/* Direct 32 bit for GNU version of
+					   local dynamic thread local data
+					   in LE code */
+#define R_386_16	   20
+#define R_386_PC16	   21
+#define R_386_8		   22
+#define R_386_PC8	   23
+#define R_386_TLS_GD_32	   24		/* Direct 32 bit for general dynamic
+					   thread local data */
+#define R_386_TLS_GD_PUSH  25		/* Tag for pushl in GD TLS code */
+#define R_386_TLS_GD_CALL  26		/* Relocation for call to
+					   __tls_get_addr() */
+#define R_386_TLS_GD_POP   27		/* Tag for popl in GD TLS code */
+#define R_386_TLS_LDM_32   28		/* Direct 32 bit for local dynamic
+					   thread local data in LE code */
+#define R_386_TLS_LDM_PUSH 29		/* Tag for pushl in LDM TLS code */
+#define R_386_TLS_LDM_CALL 30		/* Relocation for call to
+					   __tls_get_addr() in LDM code */
+#define R_386_TLS_LDM_POP  31		/* Tag for popl in LDM TLS code */
+#define R_386_TLS_LDO_32   32		/* Offset relative to TLS block */
+#define R_386_TLS_IE_32	   33		/* GOT entry for negated static TLS
+					   block offset */
+#define R_386_TLS_LE_32	   34		/* Negated offset relative to static
+					   TLS block */
+#define R_386_TLS_DTPMOD32 35		/* ID of module containing symbol */
+#define R_386_TLS_DTPOFF32 36		/* Offset in TLS block */
+#define R_386_TLS_TPOFF32  37		/* Negated offset in static TLS block */
+#define R_386_SIZE32	   38 		/* 32-bit symbol size */
+#define R_386_TLS_GOTDESC  39		/* GOT offset for TLS descriptor.  */
+#define R_386_TLS_DESC_CALL 40		/* Marker of call through TLS
+					   descriptor for
+					   relaxation.  */
+#define R_386_TLS_DESC     41		/* TLS descriptor containing
+					   pointer to code and to
+					   argument, returning the TLS
+					   offset for the symbol.  */
+#define R_386_IRELATIVE	   42		/* Adjust indirectly by program base */
+#define R_386_GOT32X	   43		/* Load from 32 bit GOT entry,
+					   relaxable. */
+/* Keep this the last entry.  */
+#define R_386_NUM	   44
+
+/* SUN SPARC specific definitions.  */
+
+/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
+
+#define STT_SPARC_REGISTER	13	/* Global register reserved to app. */
+
+/* Values for Elf64_Ehdr.e_flags.  */
+
+#define EF_SPARCV9_MM		3
+#define EF_SPARCV9_TSO		0
+#define EF_SPARCV9_PSO		1
+#define EF_SPARCV9_RMO		2
+#define EF_SPARC_LEDATA		0x800000 /* little endian data */
+#define EF_SPARC_EXT_MASK	0xFFFF00
+#define EF_SPARC_32PLUS		0x000100 /* generic V8+ features */
+#define EF_SPARC_SUN_US1	0x000200 /* Sun UltraSPARC1 extensions */
+#define EF_SPARC_HAL_R1		0x000400 /* HAL R1 extensions */
+#define EF_SPARC_SUN_US3	0x000800 /* Sun UltraSPARCIII extensions */
+
+/* SPARC relocs.  */
+
+#define R_SPARC_NONE		0	/* No reloc */
+#define R_SPARC_8		1	/* Direct 8 bit */
+#define R_SPARC_16		2	/* Direct 16 bit */
+#define R_SPARC_32		3	/* Direct 32 bit */
+#define R_SPARC_DISP8		4	/* PC relative 8 bit */
+#define R_SPARC_DISP16		5	/* PC relative 16 bit */
+#define R_SPARC_DISP32		6	/* PC relative 32 bit */
+#define R_SPARC_WDISP30		7	/* PC relative 30 bit shifted */
+#define R_SPARC_WDISP22		8	/* PC relative 22 bit shifted */
+#define R_SPARC_HI22		9	/* High 22 bit */
+#define R_SPARC_22		10	/* Direct 22 bit */
+#define R_SPARC_13		11	/* Direct 13 bit */
+#define R_SPARC_LO10		12	/* Truncated 10 bit */
+#define R_SPARC_GOT10		13	/* Truncated 10 bit GOT entry */
+#define R_SPARC_GOT13		14	/* 13 bit GOT entry */
+#define R_SPARC_GOT22		15	/* 22 bit GOT entry shifted */
+#define R_SPARC_PC10		16	/* PC relative 10 bit truncated */
+#define R_SPARC_PC22		17	/* PC relative 22 bit shifted */
+#define R_SPARC_WPLT30		18	/* 30 bit PC relative PLT address */
+#define R_SPARC_COPY		19	/* Copy symbol at runtime */
+#define R_SPARC_GLOB_DAT	20	/* Create GOT entry */
+#define R_SPARC_JMP_SLOT	21	/* Create PLT entry */
+#define R_SPARC_RELATIVE	22	/* Adjust by program base */
+#define R_SPARC_UA32		23	/* Direct 32 bit unaligned */
+
+/* Additional Sparc64 relocs.  */
+
+#define R_SPARC_PLT32		24	/* Direct 32 bit ref to PLT entry */
+#define R_SPARC_HIPLT22		25	/* High 22 bit PLT entry */
+#define R_SPARC_LOPLT10		26	/* Truncated 10 bit PLT entry */
+#define R_SPARC_PCPLT32		27	/* PC rel 32 bit ref to PLT entry */
+#define R_SPARC_PCPLT22		28	/* PC rel high 22 bit PLT entry */
+#define R_SPARC_PCPLT10		29	/* PC rel trunc 10 bit PLT entry */
+#define R_SPARC_10		30	/* Direct 10 bit */
+#define R_SPARC_11		31	/* Direct 11 bit */
+#define R_SPARC_64		32	/* Direct 64 bit */
+#define R_SPARC_OLO10		33	/* 10bit with secondary 13bit addend */
+#define R_SPARC_HH22		34	/* Top 22 bits of direct 64 bit */
+#define R_SPARC_HM10		35	/* High middle 10 bits of ... */
+#define R_SPARC_LM22		36	/* Low middle 22 bits of ... */
+#define R_SPARC_PC_HH22		37	/* Top 22 bits of pc rel 64 bit */
+#define R_SPARC_PC_HM10		38	/* High middle 10 bit of ... */
+#define R_SPARC_PC_LM22		39	/* Low miggle 22 bits of ... */
+#define R_SPARC_WDISP16		40	/* PC relative 16 bit shifted */
+#define R_SPARC_WDISP19		41	/* PC relative 19 bit shifted */
+#define R_SPARC_GLOB_JMP	42	/* was part of v9 ABI but was removed */
+#define R_SPARC_7		43	/* Direct 7 bit */
+#define R_SPARC_5		44	/* Direct 5 bit */
+#define R_SPARC_6		45	/* Direct 6 bit */
+#define R_SPARC_DISP64		46	/* PC relative 64 bit */
+#define R_SPARC_PLT64		47	/* Direct 64 bit ref to PLT entry */
+#define R_SPARC_HIX22		48	/* High 22 bit complemented */
+#define R_SPARC_LOX10		49	/* Truncated 11 bit complemented */
+#define R_SPARC_H44		50	/* Direct high 12 of 44 bit */
+#define R_SPARC_M44		51	/* Direct mid 22 of 44 bit */
+#define R_SPARC_L44		52	/* Direct low 10 of 44 bit */
+#define R_SPARC_REGISTER	53	/* Global register usage */
+#define R_SPARC_UA64		54	/* Direct 64 bit unaligned */
+#define R_SPARC_UA16		55	/* Direct 16 bit unaligned */
+#define R_SPARC_TLS_GD_HI22	56
+#define R_SPARC_TLS_GD_LO10	57
+#define R_SPARC_TLS_GD_ADD	58
+#define R_SPARC_TLS_GD_CALL	59
+#define R_SPARC_TLS_LDM_HI22	60
+#define R_SPARC_TLS_LDM_LO10	61
+#define R_SPARC_TLS_LDM_ADD	62
+#define R_SPARC_TLS_LDM_CALL	63
+#define R_SPARC_TLS_LDO_HIX22	64
+#define R_SPARC_TLS_LDO_LOX10	65
+#define R_SPARC_TLS_LDO_ADD	66
+#define R_SPARC_TLS_IE_HI22	67
+#define R_SPARC_TLS_IE_LO10	68
+#define R_SPARC_TLS_IE_LD	69
+#define R_SPARC_TLS_IE_LDX	70
+#define R_SPARC_TLS_IE_ADD	71
+#define R_SPARC_TLS_LE_HIX22	72
+#define R_SPARC_TLS_LE_LOX10	73
+#define R_SPARC_TLS_DTPMOD32	74
+#define R_SPARC_TLS_DTPMOD64	75
+#define R_SPARC_TLS_DTPOFF32	76
+#define R_SPARC_TLS_DTPOFF64	77
+#define R_SPARC_TLS_TPOFF32	78
+#define R_SPARC_TLS_TPOFF64	79
+#define R_SPARC_GOTDATA_HIX22	80
+#define R_SPARC_GOTDATA_LOX10	81
+#define R_SPARC_GOTDATA_OP_HIX22	82
+#define R_SPARC_GOTDATA_OP_LOX10	83
+#define R_SPARC_GOTDATA_OP	84
+#define R_SPARC_H34		85
+#define R_SPARC_SIZE32		86
+#define R_SPARC_SIZE64		87
+#define R_SPARC_WDISP10		88
+#define R_SPARC_JMP_IREL	248
+#define R_SPARC_IRELATIVE	249
+#define R_SPARC_GNU_VTINHERIT	250
+#define R_SPARC_GNU_VTENTRY	251
+#define R_SPARC_REV32		252
+/* Keep this the last entry.  */
+#define R_SPARC_NUM		253
+
+/* For Sparc64, legal values for d_tag of Elf64_Dyn.  */
+
+#define DT_SPARC_REGISTER	0x70000001
+#define DT_SPARC_NUM		2
+
+/* MIPS R3000 specific definitions.  */
+
+/* Legal values for e_flags field of Elf32_Ehdr.  */
+
+#define EF_MIPS_NOREORDER	1     /* A .noreorder directive was used.  */
+#define EF_MIPS_PIC		2     /* Contains PIC code.  */
+#define EF_MIPS_CPIC		4     /* Uses PIC calling sequence.  */
+#define EF_MIPS_XGOT		8
+#define EF_MIPS_64BIT_WHIRL	16
+#define EF_MIPS_ABI2		32
+#define EF_MIPS_ABI_ON32	64
+#define EF_MIPS_FP64		512  /* Uses FP64 (12 callee-saved).  */
+#define EF_MIPS_NAN2008	1024  /* Uses IEEE 754-2008 NaN encoding.  */
+#define EF_MIPS_ARCH		0xf0000000 /* MIPS architecture level.  */
+
+/* Legal values for MIPS architecture level.  */
+
+#define EF_MIPS_ARCH_1		0x00000000 /* -mips1 code.  */
+#define EF_MIPS_ARCH_2		0x10000000 /* -mips2 code.  */
+#define EF_MIPS_ARCH_3		0x20000000 /* -mips3 code.  */
+#define EF_MIPS_ARCH_4		0x30000000 /* -mips4 code.  */
+#define EF_MIPS_ARCH_5		0x40000000 /* -mips5 code.  */
+#define EF_MIPS_ARCH_32		0x50000000 /* MIPS32 code.  */
+#define EF_MIPS_ARCH_64		0x60000000 /* MIPS64 code.  */
+#define EF_MIPS_ARCH_32R2	0x70000000 /* MIPS32r2 code.  */
+#define EF_MIPS_ARCH_64R2	0x80000000 /* MIPS64r2 code.  */
+
+/* The following are unofficial names and should not be used.  */
+
+#define E_MIPS_ARCH_1		EF_MIPS_ARCH_1
+#define E_MIPS_ARCH_2		EF_MIPS_ARCH_2
+#define E_MIPS_ARCH_3		EF_MIPS_ARCH_3
+#define E_MIPS_ARCH_4		EF_MIPS_ARCH_4
+#define E_MIPS_ARCH_5		EF_MIPS_ARCH_5
+#define E_MIPS_ARCH_32		EF_MIPS_ARCH_32
+#define E_MIPS_ARCH_64		EF_MIPS_ARCH_64
+
+/* Special section indices.  */
+
+#define SHN_MIPS_ACOMMON	0xff00	/* Allocated common symbols.  */
+#define SHN_MIPS_TEXT		0xff01	/* Allocated test symbols.  */
+#define SHN_MIPS_DATA		0xff02	/* Allocated data symbols.  */
+#define SHN_MIPS_SCOMMON 	0xff03	/* Small common symbols.  */
+#define SHN_MIPS_SUNDEFINED	0xff04	/* Small undefined symbols.  */
+
+/* Legal values for sh_type field of Elf32_Shdr.  */
+
+#define SHT_MIPS_LIBLIST	0x70000000 /* Shared objects used in link.  */
+#define SHT_MIPS_MSYM		0x70000001
+#define SHT_MIPS_CONFLICT	0x70000002 /* Conflicting symbols.  */
+#define SHT_MIPS_GPTAB		0x70000003 /* Global data area sizes.  */
+#define SHT_MIPS_UCODE		0x70000004 /* Reserved for SGI/MIPS compilers */
+#define SHT_MIPS_DEBUG		0x70000005 /* MIPS ECOFF debugging info.  */
+#define SHT_MIPS_REGINFO	0x70000006 /* Register usage information.  */
+#define SHT_MIPS_PACKAGE	0x70000007
+#define SHT_MIPS_PACKSYM	0x70000008
+#define SHT_MIPS_RELD		0x70000009
+#define SHT_MIPS_IFACE		0x7000000b
+#define SHT_MIPS_CONTENT	0x7000000c
+#define SHT_MIPS_OPTIONS	0x7000000d /* Miscellaneous options.  */
+#define SHT_MIPS_SHDR		0x70000010
+#define SHT_MIPS_FDESC		0x70000011
+#define SHT_MIPS_EXTSYM		0x70000012
+#define SHT_MIPS_DENSE		0x70000013
+#define SHT_MIPS_PDESC		0x70000014
+#define SHT_MIPS_LOCSYM		0x70000015
+#define SHT_MIPS_AUXSYM		0x70000016
+#define SHT_MIPS_OPTSYM		0x70000017
+#define SHT_MIPS_LOCSTR		0x70000018
+#define SHT_MIPS_LINE		0x70000019
+#define SHT_MIPS_RFDESC		0x7000001a
+#define SHT_MIPS_DELTASYM	0x7000001b
+#define SHT_MIPS_DELTAINST	0x7000001c
+#define SHT_MIPS_DELTACLASS	0x7000001d
+#define SHT_MIPS_DWARF		0x7000001e /* DWARF debugging information.  */
+#define SHT_MIPS_DELTADECL	0x7000001f
+#define SHT_MIPS_SYMBOL_LIB	0x70000020
+#define SHT_MIPS_EVENTS		0x70000021 /* Event section.  */
+#define SHT_MIPS_TRANSLATE	0x70000022
+#define SHT_MIPS_PIXIE		0x70000023
+#define SHT_MIPS_XLATE		0x70000024
+#define SHT_MIPS_XLATE_DEBUG	0x70000025
+#define SHT_MIPS_WHIRL		0x70000026
+#define SHT_MIPS_EH_REGION	0x70000027
+#define SHT_MIPS_XLATE_OLD	0x70000028
+#define SHT_MIPS_PDR_EXCEPTION	0x70000029
+
+/* Legal values for sh_flags field of Elf32_Shdr.  */
+
+#define SHF_MIPS_GPREL		0x10000000 /* Must be in global data area.  */
+#define SHF_MIPS_MERGE		0x20000000
+#define SHF_MIPS_ADDR		0x40000000
+#define SHF_MIPS_STRINGS	0x80000000
+#define SHF_MIPS_NOSTRIP	0x08000000
+#define SHF_MIPS_LOCAL		0x04000000
+#define SHF_MIPS_NAMES		0x02000000
+#define SHF_MIPS_NODUPE		0x01000000
+
+
+/* Symbol tables.  */
+
+/* MIPS specific values for `st_other'.  */
+#define STO_MIPS_DEFAULT		0x0
+#define STO_MIPS_INTERNAL		0x1
+#define STO_MIPS_HIDDEN			0x2
+#define STO_MIPS_PROTECTED		0x3
+#define STO_MIPS_PLT			0x8
+#define STO_MIPS_SC_ALIGN_UNUSED	0xff
+
+/* MIPS specific values for `st_info'.  */
+#define STB_MIPS_SPLIT_COMMON		13
+
+/* Entries found in sections of type SHT_MIPS_GPTAB.  */
+
+typedef union
+{
+  struct
+    {
+      Elf32_Word gt_current_g_value;	/* -G value used for compilation.  */
+      Elf32_Word gt_unused;		/* Not used.  */
+    } gt_header;			/* First entry in section.  */
+  struct
+    {
+      Elf32_Word gt_g_value;		/* If this value were used for -G.  */
+      Elf32_Word gt_bytes;		/* This many bytes would be used.  */
+    } gt_entry;				/* Subsequent entries in section.  */
+} Elf32_gptab;
+
+/* Entry found in sections of type SHT_MIPS_REGINFO.  */
+
+typedef struct
+{
+  Elf32_Word ri_gprmask;		/* General registers used.  */
+  Elf32_Word ri_cprmask[4];		/* Coprocessor registers used.  */
+  Elf32_Sword ri_gp_value;		/* $gp register value.  */
+} Elf32_RegInfo;
+
+/* Entries found in sections of type SHT_MIPS_OPTIONS.  */
+
+typedef struct
+{
+  unsigned char kind;		/* Determines interpretation of the
+				   variable part of descriptor.  */
+  unsigned char size;		/* Size of descriptor, including header.  */
+  Elf32_Section section;	/* Section header index of section affected,
+				   0 for global options.  */
+  Elf32_Word info;		/* Kind-specific information.  */
+} Elf_Options;
+
+/* Values for `kind' field in Elf_Options.  */
+
+#define ODK_NULL	0	/* Undefined.  */
+#define ODK_REGINFO	1	/* Register usage information.  */
+#define ODK_EXCEPTIONS	2	/* Exception processing options.  */
+#define ODK_PAD		3	/* Section padding options.  */
+#define ODK_HWPATCH	4	/* Hardware workarounds performed */
+#define ODK_FILL	5	/* record the fill value used by the linker. */
+#define ODK_TAGS	6	/* reserve space for desktop tools to write. */
+#define ODK_HWAND	7	/* HW workarounds.  'AND' bits when merging. */
+#define ODK_HWOR	8	/* HW workarounds.  'OR' bits when merging.  */
+
+/* Values for `info' in Elf_Options for ODK_EXCEPTIONS entries.  */
+
+#define OEX_FPU_MIN	0x1f	/* FPE's which MUST be enabled.  */
+#define OEX_FPU_MAX	0x1f00	/* FPE's which MAY be enabled.  */
+#define OEX_PAGE0	0x10000	/* page zero must be mapped.  */
+#define OEX_SMM		0x20000	/* Force sequential memory mode?  */
+#define OEX_FPDBUG	0x40000	/* Force floating point debug mode?  */
+#define OEX_PRECISEFP	OEX_FPDBUG
+#define OEX_DISMISS	0x80000	/* Dismiss invalid address faults?  */
+
+#define OEX_FPU_INVAL	0x10
+#define OEX_FPU_DIV0	0x08
+#define OEX_FPU_OFLO	0x04
+#define OEX_FPU_UFLO	0x02
+#define OEX_FPU_INEX	0x01
+
+/* Masks for `info' in Elf_Options for an ODK_HWPATCH entry.  */
+
+#define OHW_R4KEOP	0x1	/* R4000 end-of-page patch.  */
+#define OHW_R8KPFETCH	0x2	/* may need R8000 prefetch patch.  */
+#define OHW_R5KEOP	0x4	/* R5000 end-of-page patch.  */
+#define OHW_R5KCVTL	0x8	/* R5000 cvt.[ds].l bug.  clean=1.  */
+
+#define OPAD_PREFIX	0x1
+#define OPAD_POSTFIX	0x2
+#define OPAD_SYMBOL	0x4
+
+/* Entry found in `.options' section.  */
+
+typedef struct
+{
+  Elf32_Word hwp_flags1;	/* Extra flags.  */
+  Elf32_Word hwp_flags2;	/* Extra flags.  */
+} Elf_Options_Hw;
+
+/* Masks for `info' in ElfOptions for ODK_HWAND and ODK_HWOR entries.  */
+
+#define OHWA0_R4KEOP_CHECKED	0x00000001
+#define OHWA1_R4KEOP_CLEAN	0x00000002
+
+/* MIPS relocs.  */
+
+#define R_MIPS_NONE		0	/* No reloc */
+#define R_MIPS_16		1	/* Direct 16 bit */
+#define R_MIPS_32		2	/* Direct 32 bit */
+#define R_MIPS_REL32		3	/* PC relative 32 bit */
+#define R_MIPS_26		4	/* Direct 26 bit shifted */
+#define R_MIPS_HI16		5	/* High 16 bit */
+#define R_MIPS_LO16		6	/* Low 16 bit */
+#define R_MIPS_GPREL16		7	/* GP relative 16 bit */
+#define R_MIPS_LITERAL		8	/* 16 bit literal entry */
+#define R_MIPS_GOT16		9	/* 16 bit GOT entry */
+#define R_MIPS_PC16		10	/* PC relative 16 bit */
+#define R_MIPS_CALL16		11	/* 16 bit GOT entry for function */
+#define R_MIPS_GPREL32		12	/* GP relative 32 bit */
+
+#define R_MIPS_SHIFT5		16
+#define R_MIPS_SHIFT6		17
+#define R_MIPS_64		18
+#define R_MIPS_GOT_DISP		19
+#define R_MIPS_GOT_PAGE		20
+#define R_MIPS_GOT_OFST		21
+#define R_MIPS_GOT_HI16		22
+#define R_MIPS_GOT_LO16		23
+#define R_MIPS_SUB		24
+#define R_MIPS_INSERT_A		25
+#define R_MIPS_INSERT_B		26
+#define R_MIPS_DELETE		27
+#define R_MIPS_HIGHER		28
+#define R_MIPS_HIGHEST		29
+#define R_MIPS_CALL_HI16	30
+#define R_MIPS_CALL_LO16	31
+#define R_MIPS_SCN_DISP		32
+#define R_MIPS_REL16		33
+#define R_MIPS_ADD_IMMEDIATE	34
+#define R_MIPS_PJUMP		35
+#define R_MIPS_RELGOT		36
+#define R_MIPS_JALR		37
+#define R_MIPS_TLS_DTPMOD32	38	/* Module number 32 bit */
+#define R_MIPS_TLS_DTPREL32	39	/* Module-relative offset 32 bit */
+#define R_MIPS_TLS_DTPMOD64	40	/* Module number 64 bit */
+#define R_MIPS_TLS_DTPREL64	41	/* Module-relative offset 64 bit */
+#define R_MIPS_TLS_GD		42	/* 16 bit GOT offset for GD */
+#define R_MIPS_TLS_LDM		43	/* 16 bit GOT offset for LDM */
+#define R_MIPS_TLS_DTPREL_HI16	44	/* Module-relative offset, high 16 bits */
+#define R_MIPS_TLS_DTPREL_LO16	45	/* Module-relative offset, low 16 bits */
+#define R_MIPS_TLS_GOTTPREL	46	/* 16 bit GOT offset for IE */
+#define R_MIPS_TLS_TPREL32	47	/* TP-relative offset, 32 bit */
+#define R_MIPS_TLS_TPREL64	48	/* TP-relative offset, 64 bit */
+#define R_MIPS_TLS_TPREL_HI16	49	/* TP-relative offset, high 16 bits */
+#define R_MIPS_TLS_TPREL_LO16	50	/* TP-relative offset, low 16 bits */
+#define R_MIPS_GLOB_DAT		51
+#define R_MIPS_COPY		126
+#define R_MIPS_JUMP_SLOT        127
+/* Keep this the last entry.  */
+#define R_MIPS_NUM		128
+
+/* Legal values for p_type field of Elf32_Phdr.  */
+
+#define PT_MIPS_REGINFO	  0x70000000	/* Register usage information. */
+#define PT_MIPS_RTPROC	  0x70000001	/* Runtime procedure table. */
+#define PT_MIPS_OPTIONS	  0x70000002
+#define PT_MIPS_ABIFLAGS  0x70000003	/* FP mode requirement. */
+
+/* Special program header types.  */
+
+#define PF_MIPS_LOCAL	0x10000000
+
+/* Legal values for d_tag field of Elf32_Dyn.  */
+
+#define DT_MIPS_RLD_VERSION  0x70000001	/* Runtime linker interface version */
+#define DT_MIPS_TIME_STAMP   0x70000002	/* Timestamp */
+#define DT_MIPS_ICHECKSUM    0x70000003	/* Checksum */
+#define DT_MIPS_IVERSION     0x70000004	/* Version string (string tbl index) */
+#define DT_MIPS_FLAGS	     0x70000005	/* Flags */
+#define DT_MIPS_BASE_ADDRESS 0x70000006	/* Base address */
+#define DT_MIPS_MSYM	     0x70000007
+#define DT_MIPS_CONFLICT     0x70000008	/* Address of CONFLICT section */
+#define DT_MIPS_LIBLIST	     0x70000009	/* Address of LIBLIST section */
+#define DT_MIPS_LOCAL_GOTNO  0x7000000a	/* Number of local GOT entries */
+#define DT_MIPS_CONFLICTNO   0x7000000b	/* Number of CONFLICT entries */
+#define DT_MIPS_LIBLISTNO    0x70000010	/* Number of LIBLIST entries */
+#define DT_MIPS_SYMTABNO     0x70000011	/* Number of DYNSYM entries */
+#define DT_MIPS_UNREFEXTNO   0x70000012	/* First external DYNSYM */
+#define DT_MIPS_GOTSYM	     0x70000013	/* First GOT entry in DYNSYM */
+#define DT_MIPS_HIPAGENO     0x70000014	/* Number of GOT page table entries */
+#define DT_MIPS_RLD_MAP	     0x70000016	/* Address of run time loader map.  */
+#define DT_MIPS_DELTA_CLASS  0x70000017	/* Delta C++ class definition.  */
+#define DT_MIPS_DELTA_CLASS_NO    0x70000018 /* Number of entries in
+						DT_MIPS_DELTA_CLASS.  */
+#define DT_MIPS_DELTA_INSTANCE    0x70000019 /* Delta C++ class instances.  */
+#define DT_MIPS_DELTA_INSTANCE_NO 0x7000001a /* Number of entries in
+						DT_MIPS_DELTA_INSTANCE.  */
+#define DT_MIPS_DELTA_RELOC  0x7000001b /* Delta relocations.  */
+#define DT_MIPS_DELTA_RELOC_NO 0x7000001c /* Number of entries in
+					     DT_MIPS_DELTA_RELOC.  */
+#define DT_MIPS_DELTA_SYM    0x7000001d /* Delta symbols that Delta
+					   relocations refer to.  */
+#define DT_MIPS_DELTA_SYM_NO 0x7000001e /* Number of entries in
+					   DT_MIPS_DELTA_SYM.  */
+#define DT_MIPS_DELTA_CLASSSYM 0x70000020 /* Delta symbols that hold the
+					     class declaration.  */
+#define DT_MIPS_DELTA_CLASSSYM_NO 0x70000021 /* Number of entries in
+						DT_MIPS_DELTA_CLASSSYM.  */
+#define DT_MIPS_CXX_FLAGS    0x70000022 /* Flags indicating for C++ flavor.  */
+#define DT_MIPS_PIXIE_INIT   0x70000023
+#define DT_MIPS_SYMBOL_LIB   0x70000024
+#define DT_MIPS_LOCALPAGE_GOTIDX 0x70000025
+#define DT_MIPS_LOCAL_GOTIDX 0x70000026
+#define DT_MIPS_HIDDEN_GOTIDX 0x70000027
+#define DT_MIPS_PROTECTED_GOTIDX 0x70000028
+#define DT_MIPS_OPTIONS	     0x70000029 /* Address of .options.  */
+#define DT_MIPS_INTERFACE    0x7000002a /* Address of .interface.  */
+#define DT_MIPS_DYNSTR_ALIGN 0x7000002b
+#define DT_MIPS_INTERFACE_SIZE 0x7000002c /* Size of the .interface section. */
+#define DT_MIPS_RLD_TEXT_RESOLVE_ADDR 0x7000002d /* Address of rld_text_rsolve
+						    function stored in GOT.  */
+#define DT_MIPS_PERF_SUFFIX  0x7000002e /* Default suffix of dso to be added
+					   by rld on dlopen() calls.  */
+#define DT_MIPS_COMPACT_SIZE 0x7000002f /* (O32)Size of compact rel section. */
+#define DT_MIPS_GP_VALUE     0x70000030 /* GP value for aux GOTs.  */
+#define DT_MIPS_AUX_DYNAMIC  0x70000031 /* Address of aux .dynamic.  */
+/* The address of .got.plt in an executable using the new non-PIC ABI.  */
+#define DT_MIPS_PLTGOT	     0x70000032
+/* The base of the PLT in an executable using the new non-PIC ABI if that
+   PLT is writable.  For a non-writable PLT, this is omitted or has a zero
+   value.  */
+#define DT_MIPS_RWPLT        0x70000034
+/* An alternative description of the classic MIPS RLD_MAP that is usable
+   in a PIE as it stores a relative offset from the address of the tag
+   rather than an absolute address.  */
+#define DT_MIPS_RLD_MAP_REL  0x70000035
+#define DT_MIPS_NUM	     0x36
+
+/* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry.  */
+
+#define RHF_NONE		   0		/* No flags */
+#define RHF_QUICKSTART		   (1 << 0)	/* Use quickstart */
+#define RHF_NOTPOT		   (1 << 1)	/* Hash size not power of 2 */
+#define RHF_NO_LIBRARY_REPLACEMENT (1 << 2)	/* Ignore LD_LIBRARY_PATH */
+#define RHF_NO_MOVE		   (1 << 3)
+#define RHF_SGI_ONLY		   (1 << 4)
+#define RHF_GUARANTEE_INIT	   (1 << 5)
+#define RHF_DELTA_C_PLUS_PLUS	   (1 << 6)
+#define RHF_GUARANTEE_START_INIT   (1 << 7)
+#define RHF_PIXIE		   (1 << 8)
+#define RHF_DEFAULT_DELAY_LOAD	   (1 << 9)
+#define RHF_REQUICKSTART	   (1 << 10)
+#define RHF_REQUICKSTARTED	   (1 << 11)
+#define RHF_CORD		   (1 << 12)
+#define RHF_NO_UNRES_UNDEF	   (1 << 13)
+#define RHF_RLD_ORDER_SAFE	   (1 << 14)
+
+/* Entries found in sections of type SHT_MIPS_LIBLIST.  */
+
+typedef struct
+{
+  Elf32_Word l_name;		/* Name (string table index) */
+  Elf32_Word l_time_stamp;	/* Timestamp */
+  Elf32_Word l_checksum;	/* Checksum */
+  Elf32_Word l_version;		/* Interface version */
+  Elf32_Word l_flags;		/* Flags */
+} Elf32_Lib;
+
+typedef struct
+{
+  Elf64_Word l_name;		/* Name (string table index) */
+  Elf64_Word l_time_stamp;	/* Timestamp */
+  Elf64_Word l_checksum;	/* Checksum */
+  Elf64_Word l_version;		/* Interface version */
+  Elf64_Word l_flags;		/* Flags */
+} Elf64_Lib;
+
+
+/* Legal values for l_flags.  */
+
+#define LL_NONE		  0
+#define LL_EXACT_MATCH	  (1 << 0)	/* Require exact match */
+#define LL_IGNORE_INT_VER (1 << 1)	/* Ignore interface version */
+#define LL_REQUIRE_MINOR  (1 << 2)
+#define LL_EXPORTS	  (1 << 3)
+#define LL_DELAY_LOAD	  (1 << 4)
+#define LL_DELTA	  (1 << 5)
+
+/* Entries found in sections of type SHT_MIPS_CONFLICT.  */
+
+typedef Elf32_Addr Elf32_Conflict;
+
+typedef struct
+{
+  /* Version of flags structure.  */
+  Elf32_Half version;
+  /* The level of the ISA: 1-5, 32, 64.  */
+  unsigned char isa_level;
+  /* The revision of ISA: 0 for MIPS V and below, 1-n otherwise.  */
+  unsigned char isa_rev;
+  /* The size of general purpose registers.  */
+  unsigned char gpr_size;
+  /* The size of co-processor 1 registers.  */
+  unsigned char cpr1_size;
+  /* The size of co-processor 2 registers.  */
+  unsigned char cpr2_size;
+  /* The floating-point ABI.  */
+  unsigned char fp_abi;
+  /* Processor-specific extension.  */
+  Elf32_Word isa_ext;
+  /* Mask of ASEs used.  */
+  Elf32_Word ases;
+  /* Mask of general flags.  */
+  Elf32_Word flags1;
+  Elf32_Word flags2;
+} Elf_MIPS_ABIFlags_v0;
+
+/* Values for the register size bytes of an abi flags structure.  */
+
+#define MIPS_AFL_REG_NONE	0x00	 /* No registers.  */
+#define MIPS_AFL_REG_32		0x01	 /* 32-bit registers.  */
+#define MIPS_AFL_REG_64		0x02	 /* 64-bit registers.  */
+#define MIPS_AFL_REG_128	0x03	 /* 128-bit registers.  */
+
+/* Masks for the ases word of an ABI flags structure.  */
+
+#define MIPS_AFL_ASE_DSP	0x00000001 /* DSP ASE.  */
+#define MIPS_AFL_ASE_DSPR2	0x00000002 /* DSP R2 ASE.  */
+#define MIPS_AFL_ASE_EVA	0x00000004 /* Enhanced VA Scheme.  */
+#define MIPS_AFL_ASE_MCU	0x00000008 /* MCU (MicroController) ASE.  */
+#define MIPS_AFL_ASE_MDMX	0x00000010 /* MDMX ASE.  */
+#define MIPS_AFL_ASE_MIPS3D	0x00000020 /* MIPS-3D ASE.  */
+#define MIPS_AFL_ASE_MT		0x00000040 /* MT ASE.  */
+#define MIPS_AFL_ASE_SMARTMIPS	0x00000080 /* SmartMIPS ASE.  */
+#define MIPS_AFL_ASE_VIRT	0x00000100 /* VZ ASE.  */
+#define MIPS_AFL_ASE_MSA	0x00000200 /* MSA ASE.  */
+#define MIPS_AFL_ASE_MIPS16	0x00000400 /* MIPS16 ASE.  */
+#define MIPS_AFL_ASE_MICROMIPS	0x00000800 /* MICROMIPS ASE.  */
+#define MIPS_AFL_ASE_XPA	0x00001000 /* XPA ASE.  */
+#define MIPS_AFL_ASE_MASK	0x00001fff /* All ASEs.  */
+
+/* Values for the isa_ext word of an ABI flags structure.  */
+
+#define MIPS_AFL_EXT_XLR	  1   /* RMI Xlr instruction.  */
+#define MIPS_AFL_EXT_OCTEON2	  2   /* Cavium Networks Octeon2.  */
+#define MIPS_AFL_EXT_OCTEONP	  3   /* Cavium Networks OcteonP.  */
+#define MIPS_AFL_EXT_LOONGSON_3A  4   /* Loongson 3A.  */
+#define MIPS_AFL_EXT_OCTEON	  5   /* Cavium Networks Octeon.  */
+#define MIPS_AFL_EXT_5900	  6   /* MIPS R5900 instruction.  */
+#define MIPS_AFL_EXT_4650	  7   /* MIPS R4650 instruction.  */
+#define MIPS_AFL_EXT_4010	  8   /* LSI R4010 instruction.  */
+#define MIPS_AFL_EXT_4100	  9   /* NEC VR4100 instruction.  */
+#define MIPS_AFL_EXT_3900	  10  /* Toshiba R3900 instruction.  */
+#define MIPS_AFL_EXT_10000	  11  /* MIPS R10000 instruction.  */
+#define MIPS_AFL_EXT_SB1	  12  /* Broadcom SB-1 instruction.  */
+#define MIPS_AFL_EXT_4111	  13  /* NEC VR4111/VR4181 instruction.  */
+#define MIPS_AFL_EXT_4120	  14  /* NEC VR4120 instruction.  */
+#define MIPS_AFL_EXT_5400	  15  /* NEC VR5400 instruction.  */
+#define MIPS_AFL_EXT_5500	  16  /* NEC VR5500 instruction.  */
+#define MIPS_AFL_EXT_LOONGSON_2E  17  /* ST Microelectronics Loongson 2E.  */
+#define MIPS_AFL_EXT_LOONGSON_2F  18  /* ST Microelectronics Loongson 2F.  */
+
+/* Masks for the flags1 word of an ABI flags structure.  */
+#define MIPS_AFL_FLAGS1_ODDSPREG  1  /* Uses odd single-precision registers.  */
+
+/* Object attribute values.  */
+enum
+{
+  /* Not tagged or not using any ABIs affected by the differences.  */
+  Val_GNU_MIPS_ABI_FP_ANY = 0,
+  /* Using hard-float -mdouble-float.  */
+  Val_GNU_MIPS_ABI_FP_DOUBLE = 1,
+  /* Using hard-float -msingle-float.  */
+  Val_GNU_MIPS_ABI_FP_SINGLE = 2,
+  /* Using soft-float.  */
+  Val_GNU_MIPS_ABI_FP_SOFT = 3,
+  /* Using -mips32r2 -mfp64.  */
+  Val_GNU_MIPS_ABI_FP_OLD_64 = 4,
+  /* Using -mfpxx.  */
+  Val_GNU_MIPS_ABI_FP_XX = 5,
+  /* Using -mips32r2 -mfp64.  */
+  Val_GNU_MIPS_ABI_FP_64 = 6,
+  /* Using -mips32r2 -mfp64 -mno-odd-spreg.  */
+  Val_GNU_MIPS_ABI_FP_64A = 7,
+  /* Maximum allocated FP ABI value.  */
+  Val_GNU_MIPS_ABI_FP_MAX = 7
+};
+
+/* HPPA specific definitions.  */
+
+/* Legal values for e_flags field of Elf32_Ehdr.  */
+
+#define EF_PARISC_TRAPNIL	0x00010000 /* Trap nil pointer dereference.  */
+#define EF_PARISC_EXT		0x00020000 /* Program uses arch. extensions. */
+#define EF_PARISC_LSB		0x00040000 /* Program expects little endian. */
+#define EF_PARISC_WIDE		0x00080000 /* Program expects wide mode.  */
+#define EF_PARISC_NO_KABP	0x00100000 /* No kernel assisted branch
+					      prediction.  */
+#define EF_PARISC_LAZYSWAP	0x00400000 /* Allow lazy swapping.  */
+#define EF_PARISC_ARCH		0x0000ffff /* Architecture version.  */
+
+/* Defined values for `e_flags & EF_PARISC_ARCH' are:  */
+
+#define EFA_PARISC_1_0		    0x020b /* PA-RISC 1.0 big-endian.  */
+#define EFA_PARISC_1_1		    0x0210 /* PA-RISC 1.1 big-endian.  */
+#define EFA_PARISC_2_0		    0x0214 /* PA-RISC 2.0 big-endian.  */
+
+/* Additional section indeces.  */
+
+#define SHN_PARISC_ANSI_COMMON	0xff00	   /* Section for tenatively declared
+					      symbols in ANSI C.  */
+#define SHN_PARISC_HUGE_COMMON	0xff01	   /* Common blocks in huge model.  */
+
+/* Legal values for sh_type field of Elf32_Shdr.  */
+
+#define SHT_PARISC_EXT		0x70000000 /* Contains product specific ext. */
+#define SHT_PARISC_UNWIND	0x70000001 /* Unwind information.  */
+#define SHT_PARISC_DOC		0x70000002 /* Debug info for optimized code. */
+
+/* Legal values for sh_flags field of Elf32_Shdr.  */
+
+#define SHF_PARISC_SHORT	0x20000000 /* Section with short addressing. */
+#define SHF_PARISC_HUGE		0x40000000 /* Section far from gp.  */
+#define SHF_PARISC_SBP		0x80000000 /* Static branch prediction code. */
+
+/* Legal values for ST_TYPE subfield of st_info (symbol type).  */
+
+#define STT_PARISC_MILLICODE	13	/* Millicode function entry point.  */
+
+#define STT_HP_OPAQUE		(STT_LOOS + 0x1)
+#define STT_HP_STUB		(STT_LOOS + 0x2)
+
+/* HPPA relocs.  */
+
+#define R_PARISC_NONE		0	/* No reloc.  */
+#define R_PARISC_DIR32		1	/* Direct 32-bit reference.  */
+#define R_PARISC_DIR21L		2	/* Left 21 bits of eff. address.  */
+#define R_PARISC_DIR17R		3	/* Right 17 bits of eff. address.  */
+#define R_PARISC_DIR17F		4	/* 17 bits of eff. address.  */
+#define R_PARISC_DIR14R		6	/* Right 14 bits of eff. address.  */
+#define R_PARISC_PCREL32	9	/* 32-bit rel. address.  */
+#define R_PARISC_PCREL21L	10	/* Left 21 bits of rel. address.  */
+#define R_PARISC_PCREL17R	11	/* Right 17 bits of rel. address.  */
+#define R_PARISC_PCREL17F	12	/* 17 bits of rel. address.  */
+#define R_PARISC_PCREL14R	14	/* Right 14 bits of rel. address.  */
+#define R_PARISC_DPREL21L	18	/* Left 21 bits of rel. address.  */
+#define R_PARISC_DPREL14R	22	/* Right 14 bits of rel. address.  */
+#define R_PARISC_GPREL21L	26	/* GP-relative, left 21 bits.  */
+#define R_PARISC_GPREL14R	30	/* GP-relative, right 14 bits.  */
+#define R_PARISC_LTOFF21L	34	/* LT-relative, left 21 bits.  */
+#define R_PARISC_LTOFF14R	38	/* LT-relative, right 14 bits.  */
+#define R_PARISC_SECREL32	41	/* 32 bits section rel. address.  */
+#define R_PARISC_SEGBASE	48	/* No relocation, set segment base.  */
+#define R_PARISC_SEGREL32	49	/* 32 bits segment rel. address.  */
+#define R_PARISC_PLTOFF21L	50	/* PLT rel. address, left 21 bits.  */
+#define R_PARISC_PLTOFF14R	54	/* PLT rel. address, right 14 bits.  */
+#define R_PARISC_LTOFF_FPTR32	57	/* 32 bits LT-rel. function pointer. */
+#define R_PARISC_LTOFF_FPTR21L	58	/* LT-rel. fct ptr, left 21 bits. */
+#define R_PARISC_LTOFF_FPTR14R	62	/* LT-rel. fct ptr, right 14 bits. */
+#define R_PARISC_FPTR64		64	/* 64 bits function address.  */
+#define R_PARISC_PLABEL32	65	/* 32 bits function address.  */
+#define R_PARISC_PLABEL21L	66	/* Left 21 bits of fdesc address.  */
+#define R_PARISC_PLABEL14R	70	/* Right 14 bits of fdesc address.  */
+#define R_PARISC_PCREL64	72	/* 64 bits PC-rel. address.  */
+#define R_PARISC_PCREL22F	74	/* 22 bits PC-rel. address.  */
+#define R_PARISC_PCREL14WR	75	/* PC-rel. address, right 14 bits.  */
+#define R_PARISC_PCREL14DR	76	/* PC rel. address, right 14 bits.  */
+#define R_PARISC_PCREL16F	77	/* 16 bits PC-rel. address.  */
+#define R_PARISC_PCREL16WF	78	/* 16 bits PC-rel. address.  */
+#define R_PARISC_PCREL16DF	79	/* 16 bits PC-rel. address.  */
+#define R_PARISC_DIR64		80	/* 64 bits of eff. address.  */
+#define R_PARISC_DIR14WR	83	/* 14 bits of eff. address.  */
+#define R_PARISC_DIR14DR	84	/* 14 bits of eff. address.  */
+#define R_PARISC_DIR16F		85	/* 16 bits of eff. address.  */
+#define R_PARISC_DIR16WF	86	/* 16 bits of eff. address.  */
+#define R_PARISC_DIR16DF	87	/* 16 bits of eff. address.  */
+#define R_PARISC_GPREL64	88	/* 64 bits of GP-rel. address.  */
+#define R_PARISC_GPREL14WR	91	/* GP-rel. address, right 14 bits.  */
+#define R_PARISC_GPREL14DR	92	/* GP-rel. address, right 14 bits.  */
+#define R_PARISC_GPREL16F	93	/* 16 bits GP-rel. address.  */
+#define R_PARISC_GPREL16WF	94	/* 16 bits GP-rel. address.  */
+#define R_PARISC_GPREL16DF	95	/* 16 bits GP-rel. address.  */
+#define R_PARISC_LTOFF64	96	/* 64 bits LT-rel. address.  */
+#define R_PARISC_LTOFF14WR	99	/* LT-rel. address, right 14 bits.  */
+#define R_PARISC_LTOFF14DR	100	/* LT-rel. address, right 14 bits.  */
+#define R_PARISC_LTOFF16F	101	/* 16 bits LT-rel. address.  */
+#define R_PARISC_LTOFF16WF	102	/* 16 bits LT-rel. address.  */
+#define R_PARISC_LTOFF16DF	103	/* 16 bits LT-rel. address.  */
+#define R_PARISC_SECREL64	104	/* 64 bits section rel. address.  */
+#define R_PARISC_SEGREL64	112	/* 64 bits segment rel. address.  */
+#define R_PARISC_PLTOFF14WR	115	/* PLT-rel. address, right 14 bits.  */
+#define R_PARISC_PLTOFF14DR	116	/* PLT-rel. address, right 14 bits.  */
+#define R_PARISC_PLTOFF16F	117	/* 16 bits LT-rel. address.  */
+#define R_PARISC_PLTOFF16WF	118	/* 16 bits PLT-rel. address.  */
+#define R_PARISC_PLTOFF16DF	119	/* 16 bits PLT-rel. address.  */
+#define R_PARISC_LTOFF_FPTR64	120	/* 64 bits LT-rel. function ptr.  */
+#define R_PARISC_LTOFF_FPTR14WR	123	/* LT-rel. fct. ptr., right 14 bits. */
+#define R_PARISC_LTOFF_FPTR14DR	124	/* LT-rel. fct. ptr., right 14 bits. */
+#define R_PARISC_LTOFF_FPTR16F	125	/* 16 bits LT-rel. function ptr.  */
+#define R_PARISC_LTOFF_FPTR16WF	126	/* 16 bits LT-rel. function ptr.  */
+#define R_PARISC_LTOFF_FPTR16DF	127	/* 16 bits LT-rel. function ptr.  */
+#define R_PARISC_LORESERVE	128
+#define R_PARISC_COPY		128	/* Copy relocation.  */
+#define R_PARISC_IPLT		129	/* Dynamic reloc, imported PLT */
+#define R_PARISC_EPLT		130	/* Dynamic reloc, exported PLT */
+#define R_PARISC_TPREL32	153	/* 32 bits TP-rel. address.  */
+#define R_PARISC_TPREL21L	154	/* TP-rel. address, left 21 bits.  */
+#define R_PARISC_TPREL14R	158	/* TP-rel. address, right 14 bits.  */
+#define R_PARISC_LTOFF_TP21L	162	/* LT-TP-rel. address, left 21 bits. */
+#define R_PARISC_LTOFF_TP14R	166	/* LT-TP-rel. address, right 14 bits.*/
+#define R_PARISC_LTOFF_TP14F	167	/* 14 bits LT-TP-rel. address.  */
+#define R_PARISC_TPREL64	216	/* 64 bits TP-rel. address.  */
+#define R_PARISC_TPREL14WR	219	/* TP-rel. address, right 14 bits.  */
+#define R_PARISC_TPREL14DR	220	/* TP-rel. address, right 14 bits.  */
+#define R_PARISC_TPREL16F	221	/* 16 bits TP-rel. address.  */
+#define R_PARISC_TPREL16WF	222	/* 16 bits TP-rel. address.  */
+#define R_PARISC_TPREL16DF	223	/* 16 bits TP-rel. address.  */
+#define R_PARISC_LTOFF_TP64	224	/* 64 bits LT-TP-rel. address.  */
+#define R_PARISC_LTOFF_TP14WR	227	/* LT-TP-rel. address, right 14 bits.*/
+#define R_PARISC_LTOFF_TP14DR	228	/* LT-TP-rel. address, right 14 bits.*/
+#define R_PARISC_LTOFF_TP16F	229	/* 16 bits LT-TP-rel. address.  */
+#define R_PARISC_LTOFF_TP16WF	230	/* 16 bits LT-TP-rel. address.  */
+#define R_PARISC_LTOFF_TP16DF	231	/* 16 bits LT-TP-rel. address.  */
+#define R_PARISC_GNU_VTENTRY	232
+#define R_PARISC_GNU_VTINHERIT	233
+#define R_PARISC_TLS_GD21L	234	/* GD 21-bit left.  */
+#define R_PARISC_TLS_GD14R	235	/* GD 14-bit right.  */
+#define R_PARISC_TLS_GDCALL	236	/* GD call to __t_g_a.  */
+#define R_PARISC_TLS_LDM21L	237	/* LD module 21-bit left.  */
+#define R_PARISC_TLS_LDM14R	238	/* LD module 14-bit right.  */
+#define R_PARISC_TLS_LDMCALL	239	/* LD module call to __t_g_a.  */
+#define R_PARISC_TLS_LDO21L	240	/* LD offset 21-bit left.  */
+#define R_PARISC_TLS_LDO14R	241	/* LD offset 14-bit right.  */
+#define R_PARISC_TLS_DTPMOD32	242	/* DTP module 32-bit.  */
+#define R_PARISC_TLS_DTPMOD64	243	/* DTP module 64-bit.  */
+#define R_PARISC_TLS_DTPOFF32	244	/* DTP offset 32-bit.  */
+#define R_PARISC_TLS_DTPOFF64	245	/* DTP offset 32-bit.  */
+#define R_PARISC_TLS_LE21L	R_PARISC_TPREL21L
+#define R_PARISC_TLS_LE14R	R_PARISC_TPREL14R
+#define R_PARISC_TLS_IE21L	R_PARISC_LTOFF_TP21L
+#define R_PARISC_TLS_IE14R	R_PARISC_LTOFF_TP14R
+#define R_PARISC_TLS_TPREL32	R_PARISC_TPREL32
+#define R_PARISC_TLS_TPREL64	R_PARISC_TPREL64
+#define R_PARISC_HIRESERVE	255
+
+/* Legal values for p_type field of Elf32_Phdr/Elf64_Phdr.  */
+
+#define PT_HP_TLS		(PT_LOOS + 0x0)
+#define PT_HP_CORE_NONE		(PT_LOOS + 0x1)
+#define PT_HP_CORE_VERSION	(PT_LOOS + 0x2)
+#define PT_HP_CORE_KERNEL	(PT_LOOS + 0x3)
+#define PT_HP_CORE_COMM		(PT_LOOS + 0x4)
+#define PT_HP_CORE_PROC		(PT_LOOS + 0x5)
+#define PT_HP_CORE_LOADABLE	(PT_LOOS + 0x6)
+#define PT_HP_CORE_STACK	(PT_LOOS + 0x7)
+#define PT_HP_CORE_SHM		(PT_LOOS + 0x8)
+#define PT_HP_CORE_MMF		(PT_LOOS + 0x9)
+#define PT_HP_PARALLEL		(PT_LOOS + 0x10)
+#define PT_HP_FASTBIND		(PT_LOOS + 0x11)
+#define PT_HP_OPT_ANNOT		(PT_LOOS + 0x12)
+#define PT_HP_HSL_ANNOT		(PT_LOOS + 0x13)
+#define PT_HP_STACK		(PT_LOOS + 0x14)
+
+#define PT_PARISC_ARCHEXT	0x70000000
+#define PT_PARISC_UNWIND	0x70000001
+
+/* Legal values for p_flags field of Elf32_Phdr/Elf64_Phdr.  */
+
+#define PF_PARISC_SBP		0x08000000
+
+#define PF_HP_PAGE_SIZE		0x00100000
+#define PF_HP_FAR_SHARED	0x00200000
+#define PF_HP_NEAR_SHARED	0x00400000
+#define PF_HP_CODE		0x01000000
+#define PF_HP_MODIFY		0x02000000
+#define PF_HP_LAZYSWAP		0x04000000
+#define PF_HP_SBP		0x08000000
+
+
+/* Alpha specific definitions.  */
+
+/* Legal values for e_flags field of Elf64_Ehdr.  */
+
+#define EF_ALPHA_32BIT		1	/* All addresses must be < 2GB.  */
+#define EF_ALPHA_CANRELAX	2	/* Relocations for relaxing exist.  */
+
+/* Legal values for sh_type field of Elf64_Shdr.  */
+
+/* These two are primerily concerned with ECOFF debugging info.  */
+#define SHT_ALPHA_DEBUG		0x70000001
+#define SHT_ALPHA_REGINFO	0x70000002
+
+/* Legal values for sh_flags field of Elf64_Shdr.  */
+
+#define SHF_ALPHA_GPREL		0x10000000
+
+/* Legal values for st_other field of Elf64_Sym.  */
+#define STO_ALPHA_NOPV		0x80	/* No PV required.  */
+#define STO_ALPHA_STD_GPLOAD	0x88	/* PV only used for initial ldgp.  */
+
+/* Alpha relocs.  */
+
+#define R_ALPHA_NONE		0	/* No reloc */
+#define R_ALPHA_REFLONG		1	/* Direct 32 bit */
+#define R_ALPHA_REFQUAD		2	/* Direct 64 bit */
+#define R_ALPHA_GPREL32		3	/* GP relative 32 bit */
+#define R_ALPHA_LITERAL		4	/* GP relative 16 bit w/optimization */
+#define R_ALPHA_LITUSE		5	/* Optimization hint for LITERAL */
+#define R_ALPHA_GPDISP		6	/* Add displacement to GP */
+#define R_ALPHA_BRADDR		7	/* PC+4 relative 23 bit shifted */
+#define R_ALPHA_HINT		8	/* PC+4 relative 16 bit shifted */
+#define R_ALPHA_SREL16		9	/* PC relative 16 bit */
+#define R_ALPHA_SREL32		10	/* PC relative 32 bit */
+#define R_ALPHA_SREL64		11	/* PC relative 64 bit */
+#define R_ALPHA_GPRELHIGH	17	/* GP relative 32 bit, high 16 bits */
+#define R_ALPHA_GPRELLOW	18	/* GP relative 32 bit, low 16 bits */
+#define R_ALPHA_GPREL16		19	/* GP relative 16 bit */
+#define R_ALPHA_COPY		24	/* Copy symbol at runtime */
+#define R_ALPHA_GLOB_DAT	25	/* Create GOT entry */
+#define R_ALPHA_JMP_SLOT	26	/* Create PLT entry */
+#define R_ALPHA_RELATIVE	27	/* Adjust by program base */
+#define R_ALPHA_TLS_GD_HI	28
+#define R_ALPHA_TLSGD		29
+#define R_ALPHA_TLS_LDM		30
+#define R_ALPHA_DTPMOD64	31
+#define R_ALPHA_GOTDTPREL	32
+#define R_ALPHA_DTPREL64	33
+#define R_ALPHA_DTPRELHI	34
+#define R_ALPHA_DTPRELLO	35
+#define R_ALPHA_DTPREL16	36
+#define R_ALPHA_GOTTPREL	37
+#define R_ALPHA_TPREL64		38
+#define R_ALPHA_TPRELHI		39
+#define R_ALPHA_TPRELLO		40
+#define R_ALPHA_TPREL16		41
+/* Keep this the last entry.  */
+#define R_ALPHA_NUM		46
+
+/* Magic values of the LITUSE relocation addend.  */
+#define LITUSE_ALPHA_ADDR	0
+#define LITUSE_ALPHA_BASE	1
+#define LITUSE_ALPHA_BYTOFF	2
+#define LITUSE_ALPHA_JSR	3
+#define LITUSE_ALPHA_TLS_GD	4
+#define LITUSE_ALPHA_TLS_LDM	5
+
+/* Legal values for d_tag of Elf64_Dyn.  */
+#define DT_ALPHA_PLTRO		(DT_LOPROC + 0)
+#define DT_ALPHA_NUM		1
+
+/* PowerPC specific declarations */
+
+/* Values for Elf32/64_Ehdr.e_flags.  */
+#define EF_PPC_EMB		0x80000000	/* PowerPC embedded flag */
+
+/* Cygnus local bits below */
+#define EF_PPC_RELOCATABLE	0x00010000	/* PowerPC -mrelocatable flag*/
+#define EF_PPC_RELOCATABLE_LIB	0x00008000	/* PowerPC -mrelocatable-lib
+						   flag */
+
+/* PowerPC relocations defined by the ABIs */
+#define R_PPC_NONE		0
+#define R_PPC_ADDR32		1	/* 32bit absolute address */
+#define R_PPC_ADDR24		2	/* 26bit address, 2 bits ignored.  */
+#define R_PPC_ADDR16		3	/* 16bit absolute address */
+#define R_PPC_ADDR16_LO		4	/* lower 16bit of absolute address */
+#define R_PPC_ADDR16_HI		5	/* high 16bit of absolute address */
+#define R_PPC_ADDR16_HA		6	/* adjusted high 16bit */
+#define R_PPC_ADDR14		7	/* 16bit address, 2 bits ignored */
+#define R_PPC_ADDR14_BRTAKEN	8
+#define R_PPC_ADDR14_BRNTAKEN	9
+#define R_PPC_REL24		10	/* PC relative 26 bit */
+#define R_PPC_REL14		11	/* PC relative 16 bit */
+#define R_PPC_REL14_BRTAKEN	12
+#define R_PPC_REL14_BRNTAKEN	13
+#define R_PPC_GOT16		14
+#define R_PPC_GOT16_LO		15
+#define R_PPC_GOT16_HI		16
+#define R_PPC_GOT16_HA		17
+#define R_PPC_PLTREL24		18
+#define R_PPC_COPY		19
+#define R_PPC_GLOB_DAT		20
+#define R_PPC_JMP_SLOT		21
+#define R_PPC_RELATIVE		22
+#define R_PPC_LOCAL24PC		23
+#define R_PPC_UADDR32		24
+#define R_PPC_UADDR16		25
+#define R_PPC_REL32		26
+#define R_PPC_PLT32		27
+#define R_PPC_PLTREL32		28
+#define R_PPC_PLT16_LO		29
+#define R_PPC_PLT16_HI		30
+#define R_PPC_PLT16_HA		31
+#define R_PPC_SDAREL16		32
+#define R_PPC_SECTOFF		33
+#define R_PPC_SECTOFF_LO	34
+#define R_PPC_SECTOFF_HI	35
+#define R_PPC_SECTOFF_HA	36
+
+/* PowerPC relocations defined for the TLS access ABI.  */
+#define R_PPC_TLS		67 /* none	(sym+add)@tls */
+#define R_PPC_DTPMOD32		68 /* word32	(sym+add)@dtpmod */
+#define R_PPC_TPREL16		69 /* half16*	(sym+add)@tprel */
+#define R_PPC_TPREL16_LO	70 /* half16	(sym+add)@tprel@l */
+#define R_PPC_TPREL16_HI	71 /* half16	(sym+add)@tprel@h */
+#define R_PPC_TPREL16_HA	72 /* half16	(sym+add)@tprel@ha */
+#define R_PPC_TPREL32		73 /* word32	(sym+add)@tprel */
+#define R_PPC_DTPREL16		74 /* half16*	(sym+add)@dtprel */
+#define R_PPC_DTPREL16_LO	75 /* half16	(sym+add)@dtprel@l */
+#define R_PPC_DTPREL16_HI	76 /* half16	(sym+add)@dtprel@h */
+#define R_PPC_DTPREL16_HA	77 /* half16	(sym+add)@dtprel@ha */
+#define R_PPC_DTPREL32		78 /* word32	(sym+add)@dtprel */
+#define R_PPC_GOT_TLSGD16	79 /* half16*	(sym+add)@got@tlsgd */
+#define R_PPC_GOT_TLSGD16_LO	80 /* half16	(sym+add)@got@tlsgd@l */
+#define R_PPC_GOT_TLSGD16_HI	81 /* half16	(sym+add)@got@tlsgd@h */
+#define R_PPC_GOT_TLSGD16_HA	82 /* half16	(sym+add)@got@tlsgd@ha */
+#define R_PPC_GOT_TLSLD16	83 /* half16*	(sym+add)@got@tlsld */
+#define R_PPC_GOT_TLSLD16_LO	84 /* half16	(sym+add)@got@tlsld@l */
+#define R_PPC_GOT_TLSLD16_HI	85 /* half16	(sym+add)@got@tlsld@h */
+#define R_PPC_GOT_TLSLD16_HA	86 /* half16	(sym+add)@got@tlsld@ha */
+#define R_PPC_GOT_TPREL16	87 /* half16*	(sym+add)@got@tprel */
+#define R_PPC_GOT_TPREL16_LO	88 /* half16	(sym+add)@got@tprel@l */
+#define R_PPC_GOT_TPREL16_HI	89 /* half16	(sym+add)@got@tprel@h */
+#define R_PPC_GOT_TPREL16_HA	90 /* half16	(sym+add)@got@tprel@ha */
+#define R_PPC_GOT_DTPREL16	91 /* half16*	(sym+add)@got@dtprel */
+#define R_PPC_GOT_DTPREL16_LO	92 /* half16*	(sym+add)@got@dtprel@l */
+#define R_PPC_GOT_DTPREL16_HI	93 /* half16*	(sym+add)@got@dtprel@h */
+#define R_PPC_GOT_DTPREL16_HA	94 /* half16*	(sym+add)@got@dtprel@ha */
+#define R_PPC_TLSGD		95 /* none	(sym+add)@tlsgd */
+#define R_PPC_TLSLD		96 /* none	(sym+add)@tlsld */
+
+/* The remaining relocs are from the Embedded ELF ABI, and are not
+   in the SVR4 ELF ABI.  */
+#define R_PPC_EMB_NADDR32	101
+#define R_PPC_EMB_NADDR16	102
+#define R_PPC_EMB_NADDR16_LO	103
+#define R_PPC_EMB_NADDR16_HI	104
+#define R_PPC_EMB_NADDR16_HA	105
+#define R_PPC_EMB_SDAI16	106
+#define R_PPC_EMB_SDA2I16	107
+#define R_PPC_EMB_SDA2REL	108
+#define R_PPC_EMB_SDA21		109	/* 16 bit offset in SDA */
+#define R_PPC_EMB_MRKREF	110
+#define R_PPC_EMB_RELSEC16	111
+#define R_PPC_EMB_RELST_LO	112
+#define R_PPC_EMB_RELST_HI	113
+#define R_PPC_EMB_RELST_HA	114
+#define R_PPC_EMB_BIT_FLD	115
+#define R_PPC_EMB_RELSDA	116	/* 16 bit relative offset in SDA */
+
+/* Diab tool relocations.  */
+#define R_PPC_DIAB_SDA21_LO	180	/* like EMB_SDA21, but lower 16 bit */
+#define R_PPC_DIAB_SDA21_HI	181	/* like EMB_SDA21, but high 16 bit */
+#define R_PPC_DIAB_SDA21_HA	182	/* like EMB_SDA21, adjusted high 16 */
+#define R_PPC_DIAB_RELSDA_LO	183	/* like EMB_RELSDA, but lower 16 bit */
+#define R_PPC_DIAB_RELSDA_HI	184	/* like EMB_RELSDA, but high 16 bit */
+#define R_PPC_DIAB_RELSDA_HA	185	/* like EMB_RELSDA, adjusted high 16 */
+
+/* GNU extension to support local ifunc.  */
+#define R_PPC_IRELATIVE		248
+
+/* GNU relocs used in PIC code sequences.  */
+#define R_PPC_REL16		249	/* half16   (sym+add-.) */
+#define R_PPC_REL16_LO		250	/* half16   (sym+add-.)@l */
+#define R_PPC_REL16_HI		251	/* half16   (sym+add-.)@h */
+#define R_PPC_REL16_HA		252	/* half16   (sym+add-.)@ha */
+
+/* This is a phony reloc to handle any old fashioned TOC16 references
+   that may still be in object files.  */
+#define R_PPC_TOC16		255
+
+/* PowerPC specific values for the Dyn d_tag field.  */
+#define DT_PPC_GOT		(DT_LOPROC + 0)
+#define DT_PPC_OPT		(DT_LOPROC + 1)
+#define DT_PPC_NUM		2
+
+/* PowerPC specific values for the DT_PPC_OPT Dyn entry.  */
+#define PPC_OPT_TLS		1
+
+/* PowerPC64 relocations defined by the ABIs */
+#define R_PPC64_NONE		R_PPC_NONE
+#define R_PPC64_ADDR32		R_PPC_ADDR32 /* 32bit absolute address */
+#define R_PPC64_ADDR24		R_PPC_ADDR24 /* 26bit address, word aligned */
+#define R_PPC64_ADDR16		R_PPC_ADDR16 /* 16bit absolute address */
+#define R_PPC64_ADDR16_LO	R_PPC_ADDR16_LO	/* lower 16bits of address */
+#define R_PPC64_ADDR16_HI	R_PPC_ADDR16_HI	/* high 16bits of address. */
+#define R_PPC64_ADDR16_HA	R_PPC_ADDR16_HA /* adjusted high 16bits.  */
+#define R_PPC64_ADDR14		R_PPC_ADDR14 /* 16bit address, word aligned */
+#define R_PPC64_ADDR14_BRTAKEN	R_PPC_ADDR14_BRTAKEN
+#define R_PPC64_ADDR14_BRNTAKEN	R_PPC_ADDR14_BRNTAKEN
+#define R_PPC64_REL24		R_PPC_REL24 /* PC-rel. 26 bit, word aligned */
+#define R_PPC64_REL14		R_PPC_REL14 /* PC relative 16 bit */
+#define R_PPC64_REL14_BRTAKEN	R_PPC_REL14_BRTAKEN
+#define R_PPC64_REL14_BRNTAKEN	R_PPC_REL14_BRNTAKEN
+#define R_PPC64_GOT16		R_PPC_GOT16
+#define R_PPC64_GOT16_LO	R_PPC_GOT16_LO
+#define R_PPC64_GOT16_HI	R_PPC_GOT16_HI
+#define R_PPC64_GOT16_HA	R_PPC_GOT16_HA
+
+#define R_PPC64_COPY		R_PPC_COPY
+#define R_PPC64_GLOB_DAT	R_PPC_GLOB_DAT
+#define R_PPC64_JMP_SLOT	R_PPC_JMP_SLOT
+#define R_PPC64_RELATIVE	R_PPC_RELATIVE
+
+#define R_PPC64_UADDR32		R_PPC_UADDR32
+#define R_PPC64_UADDR16		R_PPC_UADDR16
+#define R_PPC64_REL32		R_PPC_REL32
+#define R_PPC64_PLT32		R_PPC_PLT32
+#define R_PPC64_PLTREL32	R_PPC_PLTREL32
+#define R_PPC64_PLT16_LO	R_PPC_PLT16_LO
+#define R_PPC64_PLT16_HI	R_PPC_PLT16_HI
+#define R_PPC64_PLT16_HA	R_PPC_PLT16_HA
+
+#define R_PPC64_SECTOFF		R_PPC_SECTOFF
+#define R_PPC64_SECTOFF_LO	R_PPC_SECTOFF_LO
+#define R_PPC64_SECTOFF_HI	R_PPC_SECTOFF_HI
+#define R_PPC64_SECTOFF_HA	R_PPC_SECTOFF_HA
+#define R_PPC64_ADDR30		37 /* word30 (S + A - P) >> 2 */
+#define R_PPC64_ADDR64		38 /* doubleword64 S + A */
+#define R_PPC64_ADDR16_HIGHER	39 /* half16 #higher(S + A) */
+#define R_PPC64_ADDR16_HIGHERA	40 /* half16 #highera(S + A) */
+#define R_PPC64_ADDR16_HIGHEST	41 /* half16 #highest(S + A) */
+#define R_PPC64_ADDR16_HIGHESTA	42 /* half16 #highesta(S + A) */
+#define R_PPC64_UADDR64		43 /* doubleword64 S + A */
+#define R_PPC64_REL64		44 /* doubleword64 S + A - P */
+#define R_PPC64_PLT64		45 /* doubleword64 L + A */
+#define R_PPC64_PLTREL64	46 /* doubleword64 L + A - P */
+#define R_PPC64_TOC16		47 /* half16* S + A - .TOC */
+#define R_PPC64_TOC16_LO	48 /* half16 #lo(S + A - .TOC.) */
+#define R_PPC64_TOC16_HI	49 /* half16 #hi(S + A - .TOC.) */
+#define R_PPC64_TOC16_HA	50 /* half16 #ha(S + A - .TOC.) */
+#define R_PPC64_TOC		51 /* doubleword64 .TOC */
+#define R_PPC64_PLTGOT16	52 /* half16* M + A */
+#define R_PPC64_PLTGOT16_LO	53 /* half16 #lo(M + A) */
+#define R_PPC64_PLTGOT16_HI	54 /* half16 #hi(M + A) */
+#define R_PPC64_PLTGOT16_HA	55 /* half16 #ha(M + A) */
+
+#define R_PPC64_ADDR16_DS	56 /* half16ds* (S + A) >> 2 */
+#define R_PPC64_ADDR16_LO_DS	57 /* half16ds  #lo(S + A) >> 2 */
+#define R_PPC64_GOT16_DS	58 /* half16ds* (G + A) >> 2 */
+#define R_PPC64_GOT16_LO_DS	59 /* half16ds  #lo(G + A) >> 2 */
+#define R_PPC64_PLT16_LO_DS	60 /* half16ds  #lo(L + A) >> 2 */
+#define R_PPC64_SECTOFF_DS	61 /* half16ds* (R + A) >> 2 */
+#define R_PPC64_SECTOFF_LO_DS	62 /* half16ds  #lo(R + A) >> 2 */
+#define R_PPC64_TOC16_DS	63 /* half16ds* (S + A - .TOC.) >> 2 */
+#define R_PPC64_TOC16_LO_DS	64 /* half16ds  #lo(S + A - .TOC.) >> 2 */
+#define R_PPC64_PLTGOT16_DS	65 /* half16ds* (M + A) >> 2 */
+#define R_PPC64_PLTGOT16_LO_DS	66 /* half16ds  #lo(M + A) >> 2 */
+
+/* PowerPC64 relocations defined for the TLS access ABI.  */
+#define R_PPC64_TLS		67 /* none	(sym+add)@tls */
+#define R_PPC64_DTPMOD64	68 /* doubleword64 (sym+add)@dtpmod */
+#define R_PPC64_TPREL16		69 /* half16*	(sym+add)@tprel */
+#define R_PPC64_TPREL16_LO	70 /* half16	(sym+add)@tprel@l */
+#define R_PPC64_TPREL16_HI	71 /* half16	(sym+add)@tprel@h */
+#define R_PPC64_TPREL16_HA	72 /* half16	(sym+add)@tprel@ha */
+#define R_PPC64_TPREL64		73 /* doubleword64 (sym+add)@tprel */
+#define R_PPC64_DTPREL16	74 /* half16*	(sym+add)@dtprel */
+#define R_PPC64_DTPREL16_LO	75 /* half16	(sym+add)@dtprel@l */
+#define R_PPC64_DTPREL16_HI	76 /* half16	(sym+add)@dtprel@h */
+#define R_PPC64_DTPREL16_HA	77 /* half16	(sym+add)@dtprel@ha */
+#define R_PPC64_DTPREL64	78 /* doubleword64 (sym+add)@dtprel */
+#define R_PPC64_GOT_TLSGD16	79 /* half16*	(sym+add)@got@tlsgd */
+#define R_PPC64_GOT_TLSGD16_LO	80 /* half16	(sym+add)@got@tlsgd@l */
+#define R_PPC64_GOT_TLSGD16_HI	81 /* half16	(sym+add)@got@tlsgd@h */
+#define R_PPC64_GOT_TLSGD16_HA	82 /* half16	(sym+add)@got@tlsgd@ha */
+#define R_PPC64_GOT_TLSLD16	83 /* half16*	(sym+add)@got@tlsld */
+#define R_PPC64_GOT_TLSLD16_LO	84 /* half16	(sym+add)@got@tlsld@l */
+#define R_PPC64_GOT_TLSLD16_HI	85 /* half16	(sym+add)@got@tlsld@h */
+#define R_PPC64_GOT_TLSLD16_HA	86 /* half16	(sym+add)@got@tlsld@ha */
+#define R_PPC64_GOT_TPREL16_DS	87 /* half16ds*	(sym+add)@got@tprel */
+#define R_PPC64_GOT_TPREL16_LO_DS 88 /* half16ds (sym+add)@got@tprel@l */
+#define R_PPC64_GOT_TPREL16_HI	89 /* half16	(sym+add)@got@tprel@h */
+#define R_PPC64_GOT_TPREL16_HA	90 /* half16	(sym+add)@got@tprel@ha */
+#define R_PPC64_GOT_DTPREL16_DS	91 /* half16ds*	(sym+add)@got@dtprel */
+#define R_PPC64_GOT_DTPREL16_LO_DS 92 /* half16ds (sym+add)@got@dtprel@l */
+#define R_PPC64_GOT_DTPREL16_HI	93 /* half16	(sym+add)@got@dtprel@h */
+#define R_PPC64_GOT_DTPREL16_HA	94 /* half16	(sym+add)@got@dtprel@ha */
+#define R_PPC64_TPREL16_DS	95 /* half16ds*	(sym+add)@tprel */
+#define R_PPC64_TPREL16_LO_DS	96 /* half16ds	(sym+add)@tprel@l */
+#define R_PPC64_TPREL16_HIGHER	97 /* half16	(sym+add)@tprel@higher */
+#define R_PPC64_TPREL16_HIGHERA	98 /* half16	(sym+add)@tprel@highera */
+#define R_PPC64_TPREL16_HIGHEST	99 /* half16	(sym+add)@tprel@highest */
+#define R_PPC64_TPREL16_HIGHESTA 100 /* half16	(sym+add)@tprel@highesta */
+#define R_PPC64_DTPREL16_DS	101 /* half16ds* (sym+add)@dtprel */
+#define R_PPC64_DTPREL16_LO_DS	102 /* half16ds	(sym+add)@dtprel@l */
+#define R_PPC64_DTPREL16_HIGHER	103 /* half16	(sym+add)@dtprel@higher */
+#define R_PPC64_DTPREL16_HIGHERA 104 /* half16	(sym+add)@dtprel@highera */
+#define R_PPC64_DTPREL16_HIGHEST 105 /* half16	(sym+add)@dtprel@highest */
+#define R_PPC64_DTPREL16_HIGHESTA 106 /* half16	(sym+add)@dtprel@highesta */
+#define R_PPC64_TLSGD		107 /* none	(sym+add)@tlsgd */
+#define R_PPC64_TLSLD		108 /* none	(sym+add)@tlsld */
+#define R_PPC64_TOCSAVE		109 /* none */
+
+/* Added when HA and HI relocs were changed to report overflows.  */
+#define R_PPC64_ADDR16_HIGH	110
+#define R_PPC64_ADDR16_HIGHA	111
+#define R_PPC64_TPREL16_HIGH	112
+#define R_PPC64_TPREL16_HIGHA	113
+#define R_PPC64_DTPREL16_HIGH	114
+#define R_PPC64_DTPREL16_HIGHA	115
+
+/* GNU extension to support local ifunc.  */
+#define R_PPC64_JMP_IREL	247
+#define R_PPC64_IRELATIVE	248
+#define R_PPC64_REL16		249	/* half16   (sym+add-.) */
+#define R_PPC64_REL16_LO	250	/* half16   (sym+add-.)@l */
+#define R_PPC64_REL16_HI	251	/* half16   (sym+add-.)@h */
+#define R_PPC64_REL16_HA	252	/* half16   (sym+add-.)@ha */
+
+/* e_flags bits specifying ABI.
+   1 for original function descriptor using ABI,
+   2 for revised ABI without function descriptors,
+   0 for unspecified or not using any features affected by the differences.  */
+#define EF_PPC64_ABI	3
+
+/* PowerPC64 specific values for the Dyn d_tag field.  */
+#define DT_PPC64_GLINK  (DT_LOPROC + 0)
+#define DT_PPC64_OPD	(DT_LOPROC + 1)
+#define DT_PPC64_OPDSZ	(DT_LOPROC + 2)
+#define DT_PPC64_OPT	(DT_LOPROC + 3)
+#define DT_PPC64_NUM    4
+
+/* PowerPC64 specific bits in the DT_PPC64_OPT Dyn entry.  */
+#define PPC64_OPT_TLS		1
+#define PPC64_OPT_MULTI_TOC	2
+#define PPC64_OPT_LOCALENTRY	4
+
+/* PowerPC64 specific values for the Elf64_Sym st_other field.  */
+#define STO_PPC64_LOCAL_BIT	5
+#define STO_PPC64_LOCAL_MASK	(7 << STO_PPC64_LOCAL_BIT)
+#define PPC64_LOCAL_ENTRY_OFFSET(other)				\
+ (((1 << (((other) & STO_PPC64_LOCAL_MASK) >> STO_PPC64_LOCAL_BIT)) >> 2) << 2)
+
+
+/* ARM specific declarations */
+
+/* Processor specific flags for the ELF header e_flags field.  */
+#define EF_ARM_RELEXEC		0x01
+#define EF_ARM_HASENTRY		0x02
+#define EF_ARM_INTERWORK	0x04
+#define EF_ARM_APCS_26		0x08
+#define EF_ARM_APCS_FLOAT	0x10
+#define EF_ARM_PIC		0x20
+#define EF_ARM_ALIGN8		0x40 /* 8-bit structure alignment is in use */
+#define EF_ARM_NEW_ABI		0x80
+#define EF_ARM_OLD_ABI		0x100
+#define EF_ARM_SOFT_FLOAT	0x200
+#define EF_ARM_VFP_FLOAT	0x400
+#define EF_ARM_MAVERICK_FLOAT	0x800
+
+#define EF_ARM_ABI_FLOAT_SOFT	0x200   /* NB conflicts with EF_ARM_SOFT_FLOAT */
+#define EF_ARM_ABI_FLOAT_HARD	0x400   /* NB conflicts with EF_ARM_VFP_FLOAT */
+
+
+/* Other constants defined in the ARM ELF spec. version B-01.  */
+/* NB. These conflict with values defined above.  */
+#define EF_ARM_SYMSARESORTED	0x04
+#define EF_ARM_DYNSYMSUSESEGIDX	0x08
+#define EF_ARM_MAPSYMSFIRST	0x10
+#define EF_ARM_EABIMASK		0XFF000000
+
+/* Constants defined in AAELF.  */
+#define EF_ARM_BE8	    0x00800000
+#define EF_ARM_LE8	    0x00400000
+
+#define EF_ARM_EABI_VERSION(flags)	((flags) & EF_ARM_EABIMASK)
+#define EF_ARM_EABI_UNKNOWN	0x00000000
+#define EF_ARM_EABI_VER1	0x01000000
+#define EF_ARM_EABI_VER2	0x02000000
+#define EF_ARM_EABI_VER3	0x03000000
+#define EF_ARM_EABI_VER4	0x04000000
+#define EF_ARM_EABI_VER5	0x05000000
+
+/* Additional symbol types for Thumb.  */
+#define STT_ARM_TFUNC		STT_LOPROC /* A Thumb function.  */
+#define STT_ARM_16BIT		STT_HIPROC /* A Thumb label.  */
+
+/* ARM-specific values for sh_flags */
+#define SHF_ARM_ENTRYSECT	0x10000000 /* Section contains an entry point */
+#define SHF_ARM_COMDEF		0x80000000 /* Section may be multiply defined
+					      in the input to a link step.  */
+
+/* ARM-specific program header flags */
+#define PF_ARM_SB		0x10000000 /* Segment contains the location
+					      addressed by the static base. */
+#define PF_ARM_PI		0x20000000 /* Position-independent segment.  */
+#define PF_ARM_ABS		0x40000000 /* Absolute segment.  */
+
+/* Processor specific values for the Phdr p_type field.  */
+#define PT_ARM_EXIDX		(PT_LOPROC + 1)	/* ARM unwind segment.  */
+
+/* Processor specific values for the Shdr sh_type field.  */
+#define SHT_ARM_EXIDX		(SHT_LOPROC + 1) /* ARM unwind section.  */
+#define SHT_ARM_PREEMPTMAP	(SHT_LOPROC + 2) /* Preemption details.  */
+#define SHT_ARM_ATTRIBUTES	(SHT_LOPROC + 3) /* ARM attributes section.  */
+
+
+/* AArch64 relocs.  */
+
+#define R_AARCH64_NONE            0	/* No relocation.  */
+
+/* ILP32 AArch64 relocs.  */
+#define R_AARCH64_P32_ABS32		  1	/* Direct 32 bit.  */
+#define R_AARCH64_P32_COPY		180	/* Copy symbol at runtime.  */
+#define R_AARCH64_P32_GLOB_DAT		181	/* Create GOT entry.  */
+#define R_AARCH64_P32_JUMP_SLOT		182	/* Create PLT entry.  */
+#define R_AARCH64_P32_RELATIVE		183	/* Adjust by program base.  */
+#define R_AARCH64_P32_TLS_DTPMOD	184	/* Module number, 32 bit.  */
+#define R_AARCH64_P32_TLS_DTPREL	185	/* Module-relative offset, 32 bit.  */
+#define R_AARCH64_P32_TLS_TPREL		186	/* TP-relative offset, 32 bit.  */
+#define R_AARCH64_P32_TLSDESC		187	/* TLS Descriptor.  */
+#define R_AARCH64_P32_IRELATIVE		188	/* STT_GNU_IFUNC relocation. */
+
+/* LP64 AArch64 relocs.  */
+#define R_AARCH64_ABS64         257	/* Direct 64 bit. */
+#define R_AARCH64_ABS32         258	/* Direct 32 bit.  */
+#define R_AARCH64_ABS16		259	/* Direct 16-bit.  */
+#define R_AARCH64_PREL64	260	/* PC-relative 64-bit.	*/
+#define R_AARCH64_PREL32	261	/* PC-relative 32-bit.	*/
+#define R_AARCH64_PREL16	262	/* PC-relative 16-bit.	*/
+#define R_AARCH64_MOVW_UABS_G0	263	/* Dir. MOVZ imm. from bits 15:0.  */
+#define R_AARCH64_MOVW_UABS_G0_NC 264	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_UABS_G1	265	/* Dir. MOVZ imm. from bits 31:16.  */
+#define R_AARCH64_MOVW_UABS_G1_NC 266	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_UABS_G2	267	/* Dir. MOVZ imm. from bits 47:32.  */
+#define R_AARCH64_MOVW_UABS_G2_NC 268	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_UABS_G3	269	/* Dir. MOV{K,Z} imm. from 63:48.  */
+#define R_AARCH64_MOVW_SABS_G0	270	/* Dir. MOV{N,Z} imm. from 15:0.  */
+#define R_AARCH64_MOVW_SABS_G1	271	/* Dir. MOV{N,Z} imm. from 31:16.  */
+#define R_AARCH64_MOVW_SABS_G2	272	/* Dir. MOV{N,Z} imm. from 47:32.  */
+#define R_AARCH64_LD_PREL_LO19	273	/* PC-rel. LD imm. from bits 20:2.  */
+#define R_AARCH64_ADR_PREL_LO21	274	/* PC-rel. ADR imm. from bits 20:0.  */
+#define R_AARCH64_ADR_PREL_PG_HI21 275	/* Page-rel. ADRP imm. from 32:12.  */
+#define R_AARCH64_ADR_PREL_PG_HI21_NC 276 /* Likewise; no overflow check.  */
+#define R_AARCH64_ADD_ABS_LO12_NC 277	/* Dir. ADD imm. from bits 11:0.  */
+#define R_AARCH64_LDST8_ABS_LO12_NC 278	/* Likewise for LD/ST; no check. */
+#define R_AARCH64_TSTBR14	279	/* PC-rel. TBZ/TBNZ imm. from 15:2.  */
+#define R_AARCH64_CONDBR19	280	/* PC-rel. cond. br. imm. from 20:2. */
+#define R_AARCH64_JUMP26	282	/* PC-rel. B imm. from bits 27:2.  */
+#define R_AARCH64_CALL26	283	/* Likewise for CALL.  */
+#define R_AARCH64_LDST16_ABS_LO12_NC 284 /* Dir. ADD imm. from bits 11:1.  */
+#define R_AARCH64_LDST32_ABS_LO12_NC 285 /* Likewise for bits 11:2.  */
+#define R_AARCH64_LDST64_ABS_LO12_NC 286 /* Likewise for bits 11:3.  */
+#define R_AARCH64_MOVW_PREL_G0	287	/* PC-rel. MOV{N,Z} imm. from 15:0.  */
+#define R_AARCH64_MOVW_PREL_G0_NC 288	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_PREL_G1	289	/* PC-rel. MOV{N,Z} imm. from 31:16. */
+#define R_AARCH64_MOVW_PREL_G1_NC 290	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_PREL_G2	291	/* PC-rel. MOV{N,Z} imm. from 47:32. */
+#define R_AARCH64_MOVW_PREL_G2_NC 292	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_PREL_G3	293	/* PC-rel. MOV{N,Z} imm. from 63:48. */
+#define R_AARCH64_LDST128_ABS_LO12_NC 299 /* Dir. ADD imm. from bits 11:4.  */
+#define R_AARCH64_MOVW_GOTOFF_G0 300	/* GOT-rel. off. MOV{N,Z} imm. 15:0. */
+#define R_AARCH64_MOVW_GOTOFF_G0_NC 301	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_GOTOFF_G1 302	/* GOT-rel. o. MOV{N,Z} imm. 31:16.  */
+#define R_AARCH64_MOVW_GOTOFF_G1_NC 303	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_GOTOFF_G2 304	/* GOT-rel. o. MOV{N,Z} imm. 47:32.  */
+#define R_AARCH64_MOVW_GOTOFF_G2_NC 305	/* Likewise for MOVK; no check.  */
+#define R_AARCH64_MOVW_GOTOFF_G3 306	/* GOT-rel. o. MOV{N,Z} imm. 63:48.  */
+#define R_AARCH64_GOTREL64	307	/* GOT-relative 64-bit.  */
+#define R_AARCH64_GOTREL32	308	/* GOT-relative 32-bit.  */
+#define R_AARCH64_GOT_LD_PREL19	309	/* PC-rel. GOT off. load imm. 20:2.  */
+#define R_AARCH64_LD64_GOTOFF_LO15 310	/* GOT-rel. off. LD/ST imm. 14:3.  */
+#define R_AARCH64_ADR_GOT_PAGE	311	/* P-page-rel. GOT off. ADRP 32:12.  */
+#define R_AARCH64_LD64_GOT_LO12_NC 312	/* Dir. GOT off. LD/ST imm. 11:3.  */
+#define R_AARCH64_LD64_GOTPAGE_LO15 313	/* GOT-page-rel. GOT off. LD/ST 14:3 */
+#define R_AARCH64_TLSGD_ADR_PREL21 512	/* PC-relative ADR imm. 20:0.  */
+#define R_AARCH64_TLSGD_ADR_PAGE21 513	/* page-rel. ADRP imm. 32:12.  */
+#define R_AARCH64_TLSGD_ADD_LO12_NC 514	/* direct ADD imm. from 11:0.  */
+#define R_AARCH64_TLSGD_MOVW_G1	515	/* GOT-rel. MOV{N,Z} 31:16.  */
+#define R_AARCH64_TLSGD_MOVW_G0_NC 516	/* GOT-rel. MOVK imm. 15:0.  */
+#define R_AARCH64_TLSLD_ADR_PREL21 517	/* Like 512; local dynamic model.  */
+#define R_AARCH64_TLSLD_ADR_PAGE21 518	/* Like 513; local dynamic model.  */
+#define R_AARCH64_TLSLD_ADD_LO12_NC 519	/* Like 514; local dynamic model.  */
+#define R_AARCH64_TLSLD_MOVW_G1	520	/* Like 515; local dynamic model.  */
+#define R_AARCH64_TLSLD_MOVW_G0_NC 521	/* Like 516; local dynamic model.  */
+#define R_AARCH64_TLSLD_LD_PREL19 522	/* TLS PC-rel. load imm. 20:2.  */
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G2 523 /* TLS DTP-rel. MOV{N,Z} 47:32.  */
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G1 524 /* TLS DTP-rel. MOV{N,Z} 31:16.  */
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC 525 /* Likewise; MOVK; no check.  */
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G0 526 /* TLS DTP-rel. MOV{N,Z} 15:0.  */
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC 527 /* Likewise; MOVK; no check.  */
+#define R_AARCH64_TLSLD_ADD_DTPREL_HI12 528 /* DTP-rel. ADD imm. from 23:12. */
+#define R_AARCH64_TLSLD_ADD_DTPREL_LO12 529 /* DTP-rel. ADD imm. from 11:0.  */
+#define R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC 530 /* Likewise; no ovfl. check.  */
+#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12 531 /* DTP-rel. LD/ST imm. 11:0.  */
+#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC 532 /* Likewise; no check.  */
+#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12 533 /* DTP-rel. LD/ST imm. 11:1.  */
+#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC 534 /* Likewise; no check.  */
+#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12 535 /* DTP-rel. LD/ST imm. 11:2.  */
+#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC 536 /* Likewise; no check.  */
+#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12 537 /* DTP-rel. LD/ST imm. 11:3.  */
+#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC 538 /* Likewise; no check.  */
+#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 539 /* GOT-rel. MOV{N,Z} 31:16.  */
+#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC 540 /* GOT-rel. MOVK 15:0.  */
+#define R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 541 /* Page-rel. ADRP 32:12.  */
+#define R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC 542 /* Direct LD off. 11:3.  */
+#define R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 543 /* PC-rel. load imm. 20:2.  */
+#define R_AARCH64_TLSLE_MOVW_TPREL_G2 544 /* TLS TP-rel. MOV{N,Z} 47:32.  */
+#define R_AARCH64_TLSLE_MOVW_TPREL_G1 545 /* TLS TP-rel. MOV{N,Z} 31:16.  */
+#define R_AARCH64_TLSLE_MOVW_TPREL_G1_NC 546 /* Likewise; MOVK; no check.  */
+#define R_AARCH64_TLSLE_MOVW_TPREL_G0 547 /* TLS TP-rel. MOV{N,Z} 15:0.  */
+#define R_AARCH64_TLSLE_MOVW_TPREL_G0_NC 548 /* Likewise; MOVK; no check.  */
+#define R_AARCH64_TLSLE_ADD_TPREL_HI12 549 /* TP-rel. ADD imm. 23:12.  */
+#define R_AARCH64_TLSLE_ADD_TPREL_LO12 550 /* TP-rel. ADD imm. 11:0.  */
+#define R_AARCH64_TLSLE_ADD_TPREL_LO12_NC 551 /* Likewise; no ovfl. check.  */
+#define R_AARCH64_TLSLE_LDST8_TPREL_LO12 552 /* TP-rel. LD/ST off. 11:0.  */
+#define R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC 553 /* Likewise; no ovfl. check. */
+#define R_AARCH64_TLSLE_LDST16_TPREL_LO12 554 /* TP-rel. LD/ST off. 11:1.  */
+#define R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC 555 /* Likewise; no check.  */
+#define R_AARCH64_TLSLE_LDST32_TPREL_LO12 556 /* TP-rel. LD/ST off. 11:2.  */
+#define R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC 557 /* Likewise; no check.  */
+#define R_AARCH64_TLSLE_LDST64_TPREL_LO12 558 /* TP-rel. LD/ST off. 11:3.  */
+#define R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC 559 /* Likewise; no check.  */
+#define R_AARCH64_TLSDESC_LD_PREL19 560	/* PC-rel. load immediate 20:2.  */
+#define R_AARCH64_TLSDESC_ADR_PREL21 561 /* PC-rel. ADR immediate 20:0.  */
+#define R_AARCH64_TLSDESC_ADR_PAGE21 562 /* Page-rel. ADRP imm. 32:12.  */
+#define R_AARCH64_TLSDESC_LD64_LO12 563	/* Direct LD off. from 11:3.  */
+#define R_AARCH64_TLSDESC_ADD_LO12 564	/* Direct ADD imm. from 11:0.  */
+#define R_AARCH64_TLSDESC_OFF_G1 565	/* GOT-rel. MOV{N,Z} imm. 31:16.  */
+#define R_AARCH64_TLSDESC_OFF_G0_NC 566	/* GOT-rel. MOVK imm. 15:0; no ck.  */
+#define R_AARCH64_TLSDESC_LDR	567	/* Relax LDR.  */
+#define R_AARCH64_TLSDESC_ADD	568	/* Relax ADD.  */
+#define R_AARCH64_TLSDESC_CALL	569	/* Relax BLR.  */
+#define R_AARCH64_TLSLE_LDST128_TPREL_LO12 570 /* TP-rel. LD/ST off. 11:4.  */
+#define R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC 571 /* Likewise; no check.  */
+#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12 572 /* DTP-rel. LD/ST imm. 11:4. */
+#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC 573 /* Likewise; no check.  */
+#define R_AARCH64_COPY         1024	/* Copy symbol at runtime.  */
+#define R_AARCH64_GLOB_DAT     1025	/* Create GOT entry.  */
+#define R_AARCH64_JUMP_SLOT    1026	/* Create PLT entry.  */
+#define R_AARCH64_RELATIVE     1027	/* Adjust by program base.  */
+#define R_AARCH64_TLS_DTPMOD   1028	/* Module number, 64 bit.  */
+#define R_AARCH64_TLS_DTPREL   1029	/* Module-relative offset, 64 bit.  */
+#define R_AARCH64_TLS_TPREL    1030	/* TP-relative offset, 64 bit.  */
+#define R_AARCH64_TLSDESC      1031	/* TLS Descriptor.  */
+#define R_AARCH64_IRELATIVE	1032	/* STT_GNU_IFUNC relocation.  */
+
+/* ARM relocs.  */
+
+#define R_ARM_NONE		0	/* No reloc */
+#define R_ARM_PC24		1	/* Deprecated PC relative 26
+					   bit branch.  */
+#define R_ARM_ABS32		2	/* Direct 32 bit  */
+#define R_ARM_REL32		3	/* PC relative 32 bit */
+#define R_ARM_PC13		4
+#define R_ARM_ABS16		5	/* Direct 16 bit */
+#define R_ARM_ABS12		6	/* Direct 12 bit */
+#define R_ARM_THM_ABS5		7	/* Direct & 0x7C (LDR, STR).  */
+#define R_ARM_ABS8		8	/* Direct 8 bit */
+#define R_ARM_SBREL32		9
+#define R_ARM_THM_PC22		10	/* PC relative 24 bit (Thumb32 BL).  */
+#define R_ARM_THM_PC8		11	/* PC relative & 0x3FC
+					   (Thumb16 LDR, ADD, ADR).  */
+#define R_ARM_AMP_VCALL9	12
+#define R_ARM_SWI24		13	/* Obsolete static relocation.  */
+#define R_ARM_TLS_DESC		13      /* Dynamic relocation.  */
+#define R_ARM_THM_SWI8		14	/* Reserved.  */
+#define R_ARM_XPC25		15	/* Reserved.  */
+#define R_ARM_THM_XPC22		16	/* Reserved.  */
+#define R_ARM_TLS_DTPMOD32	17	/* ID of module containing symbol */
+#define R_ARM_TLS_DTPOFF32	18	/* Offset in TLS block */
+#define R_ARM_TLS_TPOFF32	19	/* Offset in static TLS block */
+#define R_ARM_COPY		20	/* Copy symbol at runtime */
+#define R_ARM_GLOB_DAT		21	/* Create GOT entry */
+#define R_ARM_JUMP_SLOT		22	/* Create PLT entry */
+#define R_ARM_RELATIVE		23	/* Adjust by program base */
+#define R_ARM_GOTOFF		24	/* 32 bit offset to GOT */
+#define R_ARM_GOTPC		25	/* 32 bit PC relative offset to GOT */
+#define R_ARM_GOT32		26	/* 32 bit GOT entry */
+#define R_ARM_PLT32		27	/* Deprecated, 32 bit PLT address.  */
+#define R_ARM_CALL		28	/* PC relative 24 bit (BL, BLX).  */
+#define R_ARM_JUMP24		29	/* PC relative 24 bit
+					   (B, BL<cond>).  */
+#define R_ARM_THM_JUMP24	30	/* PC relative 24 bit (Thumb32 B.W).  */
+#define R_ARM_BASE_ABS		31	/* Adjust by program base.  */
+#define R_ARM_ALU_PCREL_7_0	32	/* Obsolete.  */
+#define R_ARM_ALU_PCREL_15_8	33	/* Obsolete.  */
+#define R_ARM_ALU_PCREL_23_15	34	/* Obsolete.  */
+#define R_ARM_LDR_SBREL_11_0	35	/* Deprecated, prog. base relative.  */
+#define R_ARM_ALU_SBREL_19_12	36	/* Deprecated, prog. base relative.  */
+#define R_ARM_ALU_SBREL_27_20	37	/* Deprecated, prog. base relative.  */
+#define R_ARM_TARGET1		38
+#define R_ARM_SBREL31		39	/* Program base relative.  */
+#define R_ARM_V4BX		40
+#define R_ARM_TARGET2		41
+#define R_ARM_PREL31		42	/* 32 bit PC relative.  */
+#define R_ARM_MOVW_ABS_NC	43	/* Direct 16-bit (MOVW).  */
+#define R_ARM_MOVT_ABS		44	/* Direct high 16-bit (MOVT).  */
+#define R_ARM_MOVW_PREL_NC	45	/* PC relative 16-bit (MOVW).  */
+#define R_ARM_MOVT_PREL		46	/* PC relative (MOVT).  */
+#define R_ARM_THM_MOVW_ABS_NC	47	/* Direct 16 bit (Thumb32 MOVW).  */
+#define R_ARM_THM_MOVT_ABS	48	/* Direct high 16 bit
+					   (Thumb32 MOVT).  */
+#define R_ARM_THM_MOVW_PREL_NC	49	/* PC relative 16 bit
+					   (Thumb32 MOVW).  */
+#define R_ARM_THM_MOVT_PREL	50	/* PC relative high 16 bit
+					   (Thumb32 MOVT).  */
+#define R_ARM_THM_JUMP19	51	/* PC relative 20 bit
+					   (Thumb32 B<cond>.W).  */
+#define R_ARM_THM_JUMP6		52	/* PC relative X & 0x7E
+					   (Thumb16 CBZ, CBNZ).  */
+#define R_ARM_THM_ALU_PREL_11_0	53	/* PC relative 12 bit
+					   (Thumb32 ADR.W).  */
+#define R_ARM_THM_PC12		54	/* PC relative 12 bit
+					   (Thumb32 LDR{D,SB,H,SH}).  */
+#define R_ARM_ABS32_NOI		55	/* Direct 32-bit.  */
+#define R_ARM_REL32_NOI		56	/* PC relative 32-bit.  */
+#define R_ARM_ALU_PC_G0_NC	57	/* PC relative (ADD, SUB).  */
+#define R_ARM_ALU_PC_G0		58	/* PC relative (ADD, SUB).  */
+#define R_ARM_ALU_PC_G1_NC	59	/* PC relative (ADD, SUB).  */
+#define R_ARM_ALU_PC_G1		60	/* PC relative (ADD, SUB).  */
+#define R_ARM_ALU_PC_G2		61	/* PC relative (ADD, SUB).  */
+#define R_ARM_LDR_PC_G1		62	/* PC relative (LDR,STR,LDRB,STRB).  */
+#define R_ARM_LDR_PC_G2		63	/* PC relative (LDR,STR,LDRB,STRB).  */
+#define R_ARM_LDRS_PC_G0	64	/* PC relative (STR{D,H},
+					   LDR{D,SB,H,SH}).  */
+#define R_ARM_LDRS_PC_G1	65	/* PC relative (STR{D,H},
+					   LDR{D,SB,H,SH}).  */
+#define R_ARM_LDRS_PC_G2	66	/* PC relative (STR{D,H},
+					   LDR{D,SB,H,SH}).  */
+#define R_ARM_LDC_PC_G0		67	/* PC relative (LDC, STC).  */
+#define R_ARM_LDC_PC_G1		68	/* PC relative (LDC, STC).  */
+#define R_ARM_LDC_PC_G2		69	/* PC relative (LDC, STC).  */
+#define R_ARM_ALU_SB_G0_NC	70	/* Program base relative (ADD,SUB).  */
+#define R_ARM_ALU_SB_G0		71	/* Program base relative (ADD,SUB).  */
+#define R_ARM_ALU_SB_G1_NC	72	/* Program base relative (ADD,SUB).  */
+#define R_ARM_ALU_SB_G1		73	/* Program base relative (ADD,SUB).  */
+#define R_ARM_ALU_SB_G2		74	/* Program base relative (ADD,SUB).  */
+#define R_ARM_LDR_SB_G0		75	/* Program base relative (LDR,
+					   STR, LDRB, STRB).  */
+#define R_ARM_LDR_SB_G1		76	/* Program base relative
+					   (LDR, STR, LDRB, STRB).  */
+#define R_ARM_LDR_SB_G2		77	/* Program base relative
+					   (LDR, STR, LDRB, STRB).  */
+#define R_ARM_LDRS_SB_G0	78	/* Program base relative
+					   (LDR, STR, LDRB, STRB).  */
+#define R_ARM_LDRS_SB_G1	79	/* Program base relative
+					   (LDR, STR, LDRB, STRB).  */
+#define R_ARM_LDRS_SB_G2	80	/* Program base relative
+					   (LDR, STR, LDRB, STRB).  */
+#define R_ARM_LDC_SB_G0		81	/* Program base relative (LDC,STC).  */
+#define R_ARM_LDC_SB_G1		82	/* Program base relative (LDC,STC).  */
+#define R_ARM_LDC_SB_G2		83	/* Program base relative (LDC,STC).  */
+#define R_ARM_MOVW_BREL_NC	84	/* Program base relative 16
+					   bit (MOVW).  */
+#define R_ARM_MOVT_BREL		85	/* Program base relative high
+					   16 bit (MOVT).  */
+#define R_ARM_MOVW_BREL		86	/* Program base relative 16
+					   bit (MOVW).  */
+#define R_ARM_THM_MOVW_BREL_NC	87	/* Program base relative 16
+					   bit (Thumb32 MOVW).  */
+#define R_ARM_THM_MOVT_BREL	88	/* Program base relative high
+					   16 bit (Thumb32 MOVT).  */
+#define R_ARM_THM_MOVW_BREL	89	/* Program base relative 16
+					   bit (Thumb32 MOVW).  */
+#define R_ARM_TLS_GOTDESC	90
+#define R_ARM_TLS_CALL		91
+#define R_ARM_TLS_DESCSEQ	92	/* TLS relaxation.  */
+#define R_ARM_THM_TLS_CALL	93
+#define R_ARM_PLT32_ABS		94
+#define R_ARM_GOT_ABS		95	/* GOT entry.  */
+#define R_ARM_GOT_PREL		96	/* PC relative GOT entry.  */
+#define R_ARM_GOT_BREL12	97	/* GOT entry relative to GOT
+					   origin (LDR).  */
+#define R_ARM_GOTOFF12		98	/* 12 bit, GOT entry relative
+					   to GOT origin (LDR, STR).  */
+#define R_ARM_GOTRELAX		99
+#define R_ARM_GNU_VTENTRY	100
+#define R_ARM_GNU_VTINHERIT	101
+#define R_ARM_THM_PC11		102	/* PC relative & 0xFFE (Thumb16 B).  */
+#define R_ARM_THM_PC9		103	/* PC relative & 0x1FE
+					   (Thumb16 B/B<cond>).  */
+#define R_ARM_TLS_GD32		104	/* PC-rel 32 bit for global dynamic
+					   thread local data */
+#define R_ARM_TLS_LDM32		105	/* PC-rel 32 bit for local dynamic
+					   thread local data */
+#define R_ARM_TLS_LDO32		106	/* 32 bit offset relative to TLS
+					   block */
+#define R_ARM_TLS_IE32		107	/* PC-rel 32 bit for GOT entry of
+					   static TLS block offset */
+#define R_ARM_TLS_LE32		108	/* 32 bit offset relative to static
+					   TLS block */
+#define R_ARM_TLS_LDO12		109	/* 12 bit relative to TLS
+					   block (LDR, STR).  */
+#define R_ARM_TLS_LE12		110	/* 12 bit relative to static
+					   TLS block (LDR, STR).  */
+#define R_ARM_TLS_IE12GP	111	/* 12 bit GOT entry relative
+					   to GOT origin (LDR).  */
+#define R_ARM_ME_TOO		128	/* Obsolete.  */
+#define R_ARM_THM_TLS_DESCSEQ	129
+#define R_ARM_THM_TLS_DESCSEQ16	129
+#define R_ARM_THM_TLS_DESCSEQ32	130
+#define R_ARM_THM_GOT_BREL12	131	/* GOT entry relative to GOT
+					   origin, 12 bit (Thumb32 LDR).  */
+#define R_ARM_IRELATIVE		160
+#define R_ARM_RXPC25		249
+#define R_ARM_RSBREL32		250
+#define R_ARM_THM_RPC22		251
+#define R_ARM_RREL32		252
+#define R_ARM_RABS22		253
+#define R_ARM_RPC24		254
+#define R_ARM_RBASE		255
+/* Keep this the last entry.  */
+#define R_ARM_NUM		256
+
+/* csky */
+#define R_CKCORE_NONE               0	/* no reloc */
+#define R_CKCORE_ADDR32             1	/* direct 32 bit (S + A) */
+#define R_CKCORE_PCRELIMM8BY4       2	/* disp ((S + A - P) >> 2) & 0xff   */
+#define R_CKCORE_PCRELIMM11BY2      3	/* disp ((S + A - P) >> 1) & 0x7ff  */
+#define R_CKCORE_PCREL32            5	/* 32-bit rel (S + A - P)           */
+#define R_CKCORE_PCRELJSR_IMM11BY2  6	/* disp ((S + A - P) >>1) & 0x7ff   */
+#define R_CKCORE_RELATIVE           9	/* 32 bit adjust program base(B + A)*/
+#define R_CKCORE_COPY               10	/* 32 bit adjust by program base    */
+#define R_CKCORE_GLOB_DAT           11	/* off between got and sym (S)      */
+#define R_CKCORE_JUMP_SLOT          12	/* PLT entry (S) */
+#define R_CKCORE_GOTOFF             13	/* offset to GOT (S + A - GOT)      */
+#define R_CKCORE_GOTPC              14	/* PC offset to GOT (GOT + A - P)   */
+#define R_CKCORE_GOT32              15	/* 32 bit GOT entry (G) */
+#define R_CKCORE_PLT32              16	/* 32 bit PLT entry (G) */
+#define R_CKCORE_ADDRGOT            17	/* GOT entry in GLOB_DAT (GOT + G)  */
+#define R_CKCORE_ADDRPLT            18	/* PLT entry in GLOB_DAT (GOT + G)  */
+#define R_CKCORE_PCREL_IMM26BY2     19	/* ((S + A - P) >> 1) & 0x3ffffff   */
+#define R_CKCORE_PCREL_IMM16BY2     20	/* disp ((S + A - P) >> 1) & 0xffff */
+#define R_CKCORE_PCREL_IMM16BY4     21	/* disp ((S + A - P) >> 2) & 0xffff */
+#define R_CKCORE_PCREL_IMM10BY2     22	/* disp ((S + A - P) >> 1) & 0x3ff  */
+#define R_CKCORE_PCREL_IMM10BY4     23	/* disp ((S + A - P) >> 2) & 0x3ff  */
+#define R_CKCORE_ADDR_HI16          24	/* high & low 16 bit ADDR */
+                                        /* ((S + A) >> 16) & 0xffff */
+#define R_CKCORE_ADDR_LO16          25	/* (S + A) & 0xffff */
+#define R_CKCORE_GOTPC_HI16         26	/* high & low 16 bit GOTPC */
+                                        /* ((GOT + A - P) >> 16) & 0xffff */
+#define R_CKCORE_GOTPC_LO16         27	/* (GOT + A - P) & 0xffff */
+#define R_CKCORE_GOTOFF_HI16        28	/* high & low 16 bit GOTOFF */
+                                        /* ((S + A - GOT) >> 16) & 0xffff */
+#define R_CKCORE_GOTOFF_LO16        29	/* (S + A - GOT) & 0xffff */
+#define R_CKCORE_GOT12              30	/* 12 bit disp GOT entry (G) */
+#define R_CKCORE_GOT_HI16           31	/* high & low 16 bit GOT */
+                                        /* (G >> 16) & 0xffff */
+#define R_CKCORE_GOT_LO16           32	/* (G & 0xffff) */
+#define R_CKCORE_PLT12              33	/* 12 bit disp PLT entry (G) */
+#define R_CKCORE_PLT_HI16           34	/* high & low 16 bit PLT */
+                                        /* (G >> 16) & 0xffff */
+#define R_CKCORE_PLT_LO16           35	/* G & 0xffff */
+#define R_CKCORE_ADDRGOT_HI16       36	/* high & low 16 bit ADDRGOT */
+                                        /* (GOT + G * 4) & 0xffff */
+#define R_CKCORE_ADDRGOT_LO16       37	/* (GOT + G * 4) & 0xffff */
+#define R_CKCORE_ADDRPLT_HI16       38	/* high & low 16 bit ADDRPLT */
+                                        /* ((GOT + G * 4) >> 16) & 0xFFFF */
+#define R_CKCORE_ADDRPLT_LO16       39	/* (GOT+G*4) & 0xffff */
+#define R_CKCORE_PCREL_JSR_IMM26BY2 40	/* disp ((S+A-P) >>1) & x3ffffff */
+#define R_CKCORE_TOFFSET_LO16       41	/* (S+A-BTEXT) & 0xffff */
+#define R_CKCORE_DOFFSET_LO16       42	/* (S+A-BTEXT) & 0xffff */
+#define R_CKCORE_PCREL_IMM18BY2     43	/* disp ((S+A-P) >>1) & 0x3ffff */
+#define R_CKCORE_DOFFSET_IMM18      44	/* disp (S+A-BDATA) & 0x3ffff */
+#define R_CKCORE_DOFFSET_IMM18BY2   45	/* disp ((S+A-BDATA)>>1) & 0x3ffff */
+#define R_CKCORE_DOFFSET_IMM18BY4   46	/* disp ((S+A-BDATA)>>2) & 0x3ffff */
+#define R_CKCORE_GOT_IMM18BY4       48	/* disp (G >> 2) */
+#define R_CKCORE_PLT_IMM18BY4       49	/* disp (G >> 2) */
+#define R_CKCORE_PCREL_IMM7BY4      50	/* disp ((S+A-P) >>2) & 0x7f */
+#define R_CKCORE_TLS_LE32           51	/* 32 bit offset to TLS block */
+#define R_CKCORE_TLS_IE32           52
+#define R_CKCORE_TLS_GD32           53
+#define R_CKCORE_TLS_LDM32          54
+#define R_CKCORE_TLS_LDO32          55
+#define R_CKCORE_TLS_DTPMOD32       56
+#define R_CKCORE_TLS_DTPOFF32       57
+#define R_CKCORE_TLS_TPOFF32        58
+
+/* IA-64 specific declarations.  */
+
+/* Processor specific flags for the Ehdr e_flags field.  */
+#define EF_IA_64_MASKOS		0x0000000f	/* os-specific flags */
+#define EF_IA_64_ABI64		0x00000010	/* 64-bit ABI */
+#define EF_IA_64_ARCH		0xff000000	/* arch. version mask */
+
+/* Processor specific values for the Phdr p_type field.  */
+#define PT_IA_64_ARCHEXT	(PT_LOPROC + 0)	/* arch extension bits */
+#define PT_IA_64_UNWIND		(PT_LOPROC + 1)	/* ia64 unwind bits */
+#define PT_IA_64_HP_OPT_ANOT	(PT_LOOS + 0x12)
+#define PT_IA_64_HP_HSL_ANOT	(PT_LOOS + 0x13)
+#define PT_IA_64_HP_STACK	(PT_LOOS + 0x14)
+
+/* Processor specific flags for the Phdr p_flags field.  */
+#define PF_IA_64_NORECOV	0x80000000	/* spec insns w/o recovery */
+
+/* Processor specific values for the Shdr sh_type field.  */
+#define SHT_IA_64_EXT		(SHT_LOPROC + 0) /* extension bits */
+#define SHT_IA_64_UNWIND	(SHT_LOPROC + 1) /* unwind bits */
+
+/* Processor specific flags for the Shdr sh_flags field.  */
+#define SHF_IA_64_SHORT		0x10000000	/* section near gp */
+#define SHF_IA_64_NORECOV	0x20000000	/* spec insns w/o recovery */
+
+/* Processor specific values for the Dyn d_tag field.  */
+#define DT_IA_64_PLT_RESERVE	(DT_LOPROC + 0)
+#define DT_IA_64_NUM		1
+
+/* IA-64 relocations.  */
+#define R_IA64_NONE		0x00	/* none */
+#define R_IA64_IMM14		0x21	/* symbol + addend, add imm14 */
+#define R_IA64_IMM22		0x22	/* symbol + addend, add imm22 */
+#define R_IA64_IMM64		0x23	/* symbol + addend, mov imm64 */
+#define R_IA64_DIR32MSB		0x24	/* symbol + addend, data4 MSB */
+#define R_IA64_DIR32LSB		0x25	/* symbol + addend, data4 LSB */
+#define R_IA64_DIR64MSB		0x26	/* symbol + addend, data8 MSB */
+#define R_IA64_DIR64LSB		0x27	/* symbol + addend, data8 LSB */
+#define R_IA64_GPREL22		0x2a	/* @gprel(sym + add), add imm22 */
+#define R_IA64_GPREL64I		0x2b	/* @gprel(sym + add), mov imm64 */
+#define R_IA64_GPREL32MSB	0x2c	/* @gprel(sym + add), data4 MSB */
+#define R_IA64_GPREL32LSB	0x2d	/* @gprel(sym + add), data4 LSB */
+#define R_IA64_GPREL64MSB	0x2e	/* @gprel(sym + add), data8 MSB */
+#define R_IA64_GPREL64LSB	0x2f	/* @gprel(sym + add), data8 LSB */
+#define R_IA64_LTOFF22		0x32	/* @ltoff(sym + add), add imm22 */
+#define R_IA64_LTOFF64I		0x33	/* @ltoff(sym + add), mov imm64 */
+#define R_IA64_PLTOFF22		0x3a	/* @pltoff(sym + add), add imm22 */
+#define R_IA64_PLTOFF64I	0x3b	/* @pltoff(sym + add), mov imm64 */
+#define R_IA64_PLTOFF64MSB	0x3e	/* @pltoff(sym + add), data8 MSB */
+#define R_IA64_PLTOFF64LSB	0x3f	/* @pltoff(sym + add), data8 LSB */
+#define R_IA64_FPTR64I		0x43	/* @fptr(sym + add), mov imm64 */
+#define R_IA64_FPTR32MSB	0x44	/* @fptr(sym + add), data4 MSB */
+#define R_IA64_FPTR32LSB	0x45	/* @fptr(sym + add), data4 LSB */
+#define R_IA64_FPTR64MSB	0x46	/* @fptr(sym + add), data8 MSB */
+#define R_IA64_FPTR64LSB	0x47	/* @fptr(sym + add), data8 LSB */
+#define R_IA64_PCREL60B		0x48	/* @pcrel(sym + add), brl */
+#define R_IA64_PCREL21B		0x49	/* @pcrel(sym + add), ptb, call */
+#define R_IA64_PCREL21M		0x4a	/* @pcrel(sym + add), chk.s */
+#define R_IA64_PCREL21F		0x4b	/* @pcrel(sym + add), fchkf */
+#define R_IA64_PCREL32MSB	0x4c	/* @pcrel(sym + add), data4 MSB */
+#define R_IA64_PCREL32LSB	0x4d	/* @pcrel(sym + add), data4 LSB */
+#define R_IA64_PCREL64MSB	0x4e	/* @pcrel(sym + add), data8 MSB */
+#define R_IA64_PCREL64LSB	0x4f	/* @pcrel(sym + add), data8 LSB */
+#define R_IA64_LTOFF_FPTR22	0x52	/* @ltoff(@fptr(s+a)), imm22 */
+#define R_IA64_LTOFF_FPTR64I	0x53	/* @ltoff(@fptr(s+a)), imm64 */
+#define R_IA64_LTOFF_FPTR32MSB	0x54	/* @ltoff(@fptr(s+a)), data4 MSB */
+#define R_IA64_LTOFF_FPTR32LSB	0x55	/* @ltoff(@fptr(s+a)), data4 LSB */
+#define R_IA64_LTOFF_FPTR64MSB	0x56	/* @ltoff(@fptr(s+a)), data8 MSB */
+#define R_IA64_LTOFF_FPTR64LSB	0x57	/* @ltoff(@fptr(s+a)), data8 LSB */
+#define R_IA64_SEGREL32MSB	0x5c	/* @segrel(sym + add), data4 MSB */
+#define R_IA64_SEGREL32LSB	0x5d	/* @segrel(sym + add), data4 LSB */
+#define R_IA64_SEGREL64MSB	0x5e	/* @segrel(sym + add), data8 MSB */
+#define R_IA64_SEGREL64LSB	0x5f	/* @segrel(sym + add), data8 LSB */
+#define R_IA64_SECREL32MSB	0x64	/* @secrel(sym + add), data4 MSB */
+#define R_IA64_SECREL32LSB	0x65	/* @secrel(sym + add), data4 LSB */
+#define R_IA64_SECREL64MSB	0x66	/* @secrel(sym + add), data8 MSB */
+#define R_IA64_SECREL64LSB	0x67	/* @secrel(sym + add), data8 LSB */
+#define R_IA64_REL32MSB		0x6c	/* data 4 + REL */
+#define R_IA64_REL32LSB		0x6d	/* data 4 + REL */
+#define R_IA64_REL64MSB		0x6e	/* data 8 + REL */
+#define R_IA64_REL64LSB		0x6f	/* data 8 + REL */
+#define R_IA64_LTV32MSB		0x74	/* symbol + addend, data4 MSB */
+#define R_IA64_LTV32LSB		0x75	/* symbol + addend, data4 LSB */
+#define R_IA64_LTV64MSB		0x76	/* symbol + addend, data8 MSB */
+#define R_IA64_LTV64LSB		0x77	/* symbol + addend, data8 LSB */
+#define R_IA64_PCREL21BI	0x79	/* @pcrel(sym + add), 21bit inst */
+#define R_IA64_PCREL22		0x7a	/* @pcrel(sym + add), 22bit inst */
+#define R_IA64_PCREL64I		0x7b	/* @pcrel(sym + add), 64bit inst */
+#define R_IA64_IPLTMSB		0x80	/* dynamic reloc, imported PLT, MSB */
+#define R_IA64_IPLTLSB		0x81	/* dynamic reloc, imported PLT, LSB */
+#define R_IA64_COPY		0x84	/* copy relocation */
+#define R_IA64_SUB		0x85	/* Addend and symbol difference */
+#define R_IA64_LTOFF22X		0x86	/* LTOFF22, relaxable.  */
+#define R_IA64_LDXMOV		0x87	/* Use of LTOFF22X.  */
+#define R_IA64_TPREL14		0x91	/* @tprel(sym + add), imm14 */
+#define R_IA64_TPREL22		0x92	/* @tprel(sym + add), imm22 */
+#define R_IA64_TPREL64I		0x93	/* @tprel(sym + add), imm64 */
+#define R_IA64_TPREL64MSB	0x96	/* @tprel(sym + add), data8 MSB */
+#define R_IA64_TPREL64LSB	0x97	/* @tprel(sym + add), data8 LSB */
+#define R_IA64_LTOFF_TPREL22	0x9a	/* @ltoff(@tprel(s+a)), imm2 */
+#define R_IA64_DTPMOD64MSB	0xa6	/* @dtpmod(sym + add), data8 MSB */
+#define R_IA64_DTPMOD64LSB	0xa7	/* @dtpmod(sym + add), data8 LSB */
+#define R_IA64_LTOFF_DTPMOD22	0xaa	/* @ltoff(@dtpmod(sym + add)), imm22 */
+#define R_IA64_DTPREL14		0xb1	/* @dtprel(sym + add), imm14 */
+#define R_IA64_DTPREL22		0xb2	/* @dtprel(sym + add), imm22 */
+#define R_IA64_DTPREL64I	0xb3	/* @dtprel(sym + add), imm64 */
+#define R_IA64_DTPREL32MSB	0xb4	/* @dtprel(sym + add), data4 MSB */
+#define R_IA64_DTPREL32LSB	0xb5	/* @dtprel(sym + add), data4 LSB */
+#define R_IA64_DTPREL64MSB	0xb6	/* @dtprel(sym + add), data8 MSB */
+#define R_IA64_DTPREL64LSB	0xb7	/* @dtprel(sym + add), data8 LSB */
+#define R_IA64_LTOFF_DTPREL22	0xba	/* @ltoff(@dtprel(s+a)), imm22 */
+
+/* SH specific declarations */
+
+/* Processor specific flags for the ELF header e_flags field.  */
+#define EF_SH_MACH_MASK		0x1f
+#define EF_SH_UNKNOWN		0x0
+#define EF_SH1			0x1
+#define EF_SH2			0x2
+#define EF_SH3			0x3
+#define EF_SH_DSP		0x4
+#define EF_SH3_DSP		0x5
+#define EF_SH4AL_DSP		0x6
+#define EF_SH3E			0x8
+#define EF_SH4			0x9
+#define EF_SH2E			0xb
+#define EF_SH4A			0xc
+#define EF_SH2A			0xd
+#define EF_SH4_NOFPU		0x10
+#define EF_SH4A_NOFPU		0x11
+#define EF_SH4_NOMMU_NOFPU	0x12
+#define EF_SH2A_NOFPU		0x13
+#define EF_SH3_NOMMU		0x14
+#define EF_SH2A_SH4_NOFPU	0x15
+#define EF_SH2A_SH3_NOFPU	0x16
+#define EF_SH2A_SH4		0x17
+#define EF_SH2A_SH3E		0x18
+
+/* SH relocs.  */
+#define	R_SH_NONE		0
+#define	R_SH_DIR32		1
+#define	R_SH_REL32		2
+#define	R_SH_DIR8WPN		3
+#define	R_SH_IND12W		4
+#define	R_SH_DIR8WPL		5
+#define	R_SH_DIR8WPZ		6
+#define	R_SH_DIR8BP		7
+#define	R_SH_DIR8W		8
+#define	R_SH_DIR8L		9
+#define	R_SH_SWITCH16		25
+#define	R_SH_SWITCH32		26
+#define	R_SH_USES		27
+#define	R_SH_COUNT		28
+#define	R_SH_ALIGN		29
+#define	R_SH_CODE		30
+#define	R_SH_DATA		31
+#define	R_SH_LABEL		32
+#define	R_SH_SWITCH8		33
+#define	R_SH_GNU_VTINHERIT	34
+#define	R_SH_GNU_VTENTRY	35
+#define	R_SH_TLS_GD_32		144
+#define	R_SH_TLS_LD_32		145
+#define	R_SH_TLS_LDO_32		146
+#define	R_SH_TLS_IE_32		147
+#define	R_SH_TLS_LE_32		148
+#define	R_SH_TLS_DTPMOD32	149
+#define	R_SH_TLS_DTPOFF32	150
+#define	R_SH_TLS_TPOFF32	151
+#define	R_SH_GOT32		160
+#define	R_SH_PLT32		161
+#define	R_SH_COPY		162
+#define	R_SH_GLOB_DAT		163
+#define	R_SH_JMP_SLOT		164
+#define	R_SH_RELATIVE		165
+#define	R_SH_GOTOFF		166
+#define	R_SH_GOTPC		167
+/* Keep this the last entry.  */
+#define	R_SH_NUM		256
+
+/* S/390 specific definitions.  */
+
+/* Valid values for the e_flags field.  */
+
+#define EF_S390_HIGH_GPRS    0x00000001  /* High GPRs kernel facility needed.  */
+
+/* Additional s390 relocs */
+
+#define R_390_NONE		0	/* No reloc.  */
+#define R_390_8			1	/* Direct 8 bit.  */
+#define R_390_12		2	/* Direct 12 bit.  */
+#define R_390_16		3	/* Direct 16 bit.  */
+#define R_390_32		4	/* Direct 32 bit.  */
+#define R_390_PC32		5	/* PC relative 32 bit.	*/
+#define R_390_GOT12		6	/* 12 bit GOT offset.  */
+#define R_390_GOT32		7	/* 32 bit GOT offset.  */
+#define R_390_PLT32		8	/* 32 bit PC relative PLT address.  */
+#define R_390_COPY		9	/* Copy symbol at runtime.  */
+#define R_390_GLOB_DAT		10	/* Create GOT entry.  */
+#define R_390_JMP_SLOT		11	/* Create PLT entry.  */
+#define R_390_RELATIVE		12	/* Adjust by program base.  */
+#define R_390_GOTOFF32		13	/* 32 bit offset to GOT.	 */
+#define R_390_GOTPC		14	/* 32 bit PC relative offset to GOT.  */
+#define R_390_GOT16		15	/* 16 bit GOT offset.  */
+#define R_390_PC16		16	/* PC relative 16 bit.	*/
+#define R_390_PC16DBL		17	/* PC relative 16 bit shifted by 1.  */
+#define R_390_PLT16DBL		18	/* 16 bit PC rel. PLT shifted by 1.  */
+#define R_390_PC32DBL		19	/* PC relative 32 bit shifted by 1.  */
+#define R_390_PLT32DBL		20	/* 32 bit PC rel. PLT shifted by 1.  */
+#define R_390_GOTPCDBL		21	/* 32 bit PC rel. GOT shifted by 1.  */
+#define R_390_64		22	/* Direct 64 bit.  */
+#define R_390_PC64		23	/* PC relative 64 bit.	*/
+#define R_390_GOT64		24	/* 64 bit GOT offset.  */
+#define R_390_PLT64		25	/* 64 bit PC relative PLT address.  */
+#define R_390_GOTENT		26	/* 32 bit PC rel. to GOT entry >> 1. */
+#define R_390_GOTOFF16		27	/* 16 bit offset to GOT. */
+#define R_390_GOTOFF64		28	/* 64 bit offset to GOT. */
+#define R_390_GOTPLT12		29	/* 12 bit offset to jump slot.	*/
+#define R_390_GOTPLT16		30	/* 16 bit offset to jump slot.	*/
+#define R_390_GOTPLT32		31	/* 32 bit offset to jump slot.	*/
+#define R_390_GOTPLT64		32	/* 64 bit offset to jump slot.	*/
+#define R_390_GOTPLTENT		33	/* 32 bit rel. offset to jump slot.  */
+#define R_390_PLTOFF16		34	/* 16 bit offset from GOT to PLT. */
+#define R_390_PLTOFF32		35	/* 32 bit offset from GOT to PLT. */
+#define R_390_PLTOFF64		36	/* 16 bit offset from GOT to PLT. */
+#define R_390_TLS_LOAD		37	/* Tag for load insn in TLS code.  */
+#define R_390_TLS_GDCALL	38	/* Tag for function call in general
+					   dynamic TLS code. */
+#define R_390_TLS_LDCALL	39	/* Tag for function call in local
+					   dynamic TLS code. */
+#define R_390_TLS_GD32		40	/* Direct 32 bit for general dynamic
+					   thread local data.  */
+#define R_390_TLS_GD64		41	/* Direct 64 bit for general dynamic
+					  thread local data.  */
+#define R_390_TLS_GOTIE12	42	/* 12 bit GOT offset for static TLS
+					   block offset.  */
+#define R_390_TLS_GOTIE32	43	/* 32 bit GOT offset for static TLS
+					   block offset.  */
+#define R_390_TLS_GOTIE64	44	/* 64 bit GOT offset for static TLS
+					   block offset. */
+#define R_390_TLS_LDM32		45	/* Direct 32 bit for local dynamic
+					   thread local data in LE code.  */
+#define R_390_TLS_LDM64		46	/* Direct 64 bit for local dynamic
+					   thread local data in LE code.  */
+#define R_390_TLS_IE32		47	/* 32 bit address of GOT entry for
+					   negated static TLS block offset.  */
+#define R_390_TLS_IE64		48	/* 64 bit address of GOT entry for
+					   negated static TLS block offset.  */
+#define R_390_TLS_IEENT		49	/* 32 bit rel. offset to GOT entry for
+					   negated static TLS block offset.  */
+#define R_390_TLS_LE32		50	/* 32 bit negated offset relative to
+					   static TLS block.  */
+#define R_390_TLS_LE64		51	/* 64 bit negated offset relative to
+					   static TLS block.  */
+#define R_390_TLS_LDO32		52	/* 32 bit offset relative to TLS
+					   block.  */
+#define R_390_TLS_LDO64		53	/* 64 bit offset relative to TLS
+					   block.  */
+#define R_390_TLS_DTPMOD	54	/* ID of module containing symbol.  */
+#define R_390_TLS_DTPOFF	55	/* Offset in TLS block.	 */
+#define R_390_TLS_TPOFF		56	/* Negated offset in static TLS
+					   block.  */
+#define R_390_20		57	/* Direct 20 bit.  */
+#define R_390_GOT20		58	/* 20 bit GOT offset.  */
+#define R_390_GOTPLT20		59	/* 20 bit offset to jump slot.  */
+#define R_390_TLS_GOTIE20	60	/* 20 bit GOT offset for static TLS
+					   block offset.  */
+#define R_390_IRELATIVE         61      /* STT_GNU_IFUNC relocation.  */
+/* Keep this the last entry.  */
+#define R_390_NUM		62
+
+
+/* CRIS relocations.  */
+#define R_CRIS_NONE		0
+#define R_CRIS_8		1
+#define R_CRIS_16		2
+#define R_CRIS_32		3
+#define R_CRIS_8_PCREL		4
+#define R_CRIS_16_PCREL		5
+#define R_CRIS_32_PCREL		6
+#define R_CRIS_GNU_VTINHERIT	7
+#define R_CRIS_GNU_VTENTRY	8
+#define R_CRIS_COPY		9
+#define R_CRIS_GLOB_DAT		10
+#define R_CRIS_JUMP_SLOT	11
+#define R_CRIS_RELATIVE		12
+#define R_CRIS_16_GOT		13
+#define R_CRIS_32_GOT		14
+#define R_CRIS_16_GOTPLT	15
+#define R_CRIS_32_GOTPLT	16
+#define R_CRIS_32_GOTREL	17
+#define R_CRIS_32_PLT_GOTREL	18
+#define R_CRIS_32_PLT_PCREL	19
+
+#define R_CRIS_NUM		20
+
+
+/* AMD x86-64 relocations.  */
+#define R_X86_64_NONE		0	/* No reloc */
+#define R_X86_64_64		1	/* Direct 64 bit  */
+#define R_X86_64_PC32		2	/* PC relative 32 bit signed */
+#define R_X86_64_GOT32		3	/* 32 bit GOT entry */
+#define R_X86_64_PLT32		4	/* 32 bit PLT address */
+#define R_X86_64_COPY		5	/* Copy symbol at runtime */
+#define R_X86_64_GLOB_DAT	6	/* Create GOT entry */
+#define R_X86_64_JUMP_SLOT	7	/* Create PLT entry */
+#define R_X86_64_RELATIVE	8	/* Adjust by program base */
+#define R_X86_64_GOTPCREL	9	/* 32 bit signed PC relative
+					   offset to GOT */
+#define R_X86_64_32		10	/* Direct 32 bit zero extended */
+#define R_X86_64_32S		11	/* Direct 32 bit sign extended */
+#define R_X86_64_16		12	/* Direct 16 bit zero extended */
+#define R_X86_64_PC16		13	/* 16 bit sign extended pc relative */
+#define R_X86_64_8		14	/* Direct 8 bit sign extended  */
+#define R_X86_64_PC8		15	/* 8 bit sign extended pc relative */
+#define R_X86_64_DTPMOD64	16	/* ID of module containing symbol */
+#define R_X86_64_DTPOFF64	17	/* Offset in module's TLS block */
+#define R_X86_64_TPOFF64	18	/* Offset in initial TLS block */
+#define R_X86_64_TLSGD		19	/* 32 bit signed PC relative offset
+					   to two GOT entries for GD symbol */
+#define R_X86_64_TLSLD		20	/* 32 bit signed PC relative offset
+					   to two GOT entries for LD symbol */
+#define R_X86_64_DTPOFF32	21	/* Offset in TLS block */
+#define R_X86_64_GOTTPOFF	22	/* 32 bit signed PC relative offset
+					   to GOT entry for IE symbol */
+#define R_X86_64_TPOFF32	23	/* Offset in initial TLS block */
+#define R_X86_64_PC64		24	/* PC relative 64 bit */
+#define R_X86_64_GOTOFF64	25	/* 64 bit offset to GOT */
+#define R_X86_64_GOTPC32	26	/* 32 bit signed pc relative
+					   offset to GOT */
+#define R_X86_64_GOT64		27	/* 64-bit GOT entry offset */
+#define R_X86_64_GOTPCREL64	28	/* 64-bit PC relative offset
+					   to GOT entry */
+#define R_X86_64_GOTPC64	29	/* 64-bit PC relative offset to GOT */
+#define R_X86_64_GOTPLT64	30 	/* like GOT64, says PLT entry needed */
+#define R_X86_64_PLTOFF64	31	/* 64-bit GOT relative offset
+					   to PLT entry */
+#define R_X86_64_SIZE32		32	/* Size of symbol plus 32-bit addend */
+#define R_X86_64_SIZE64		33	/* Size of symbol plus 64-bit addend */
+#define R_X86_64_GOTPC32_TLSDESC 34	/* GOT offset for TLS descriptor.  */
+#define R_X86_64_TLSDESC_CALL   35	/* Marker for call through TLS
+					   descriptor.  */
+#define R_X86_64_TLSDESC        36	/* TLS descriptor.  */
+#define R_X86_64_IRELATIVE	37	/* Adjust indirectly by program base */
+#define R_X86_64_RELATIVE64	38	/* 64-bit adjust by program base */
+					/* 39 Reserved was R_X86_64_PC32_BND */
+					/* 40 Reserved was R_X86_64_PLT32_BND */
+#define R_X86_64_GOTPCRELX	41	/* Load from 32 bit signed pc relative
+					   offset to GOT entry without REX
+					   prefix, relaxable.  */
+#define R_X86_64_REX_GOTPCRELX	42	/* Load from 32 bit signed pc relative
+					   offset to GOT entry with REX prefix,
+					   relaxable.  */
+#define R_X86_64_NUM		43
+
+/* x86-64 sh_type values.  */
+#define SHT_X86_64_UNWIND	0x70000001 /* Unwind information.  */
+
+
+/* AM33 relocations.  */
+#define R_MN10300_NONE		0	/* No reloc.  */
+#define R_MN10300_32		1	/* Direct 32 bit.  */
+#define R_MN10300_16		2	/* Direct 16 bit.  */
+#define R_MN10300_8		3	/* Direct 8 bit.  */
+#define R_MN10300_PCREL32	4	/* PC-relative 32-bit.  */
+#define R_MN10300_PCREL16	5	/* PC-relative 16-bit signed.  */
+#define R_MN10300_PCREL8	6	/* PC-relative 8-bit signed.  */
+#define R_MN10300_GNU_VTINHERIT	7	/* Ancient C++ vtable garbage... */
+#define R_MN10300_GNU_VTENTRY	8	/* ... collection annotation.  */
+#define R_MN10300_24		9	/* Direct 24 bit.  */
+#define R_MN10300_GOTPC32	10	/* 32-bit PCrel offset to GOT.  */
+#define R_MN10300_GOTPC16	11	/* 16-bit PCrel offset to GOT.  */
+#define R_MN10300_GOTOFF32	12	/* 32-bit offset from GOT.  */
+#define R_MN10300_GOTOFF24	13	/* 24-bit offset from GOT.  */
+#define R_MN10300_GOTOFF16	14	/* 16-bit offset from GOT.  */
+#define R_MN10300_PLT32		15	/* 32-bit PCrel to PLT entry.  */
+#define R_MN10300_PLT16		16	/* 16-bit PCrel to PLT entry.  */
+#define R_MN10300_GOT32		17	/* 32-bit offset to GOT entry.  */
+#define R_MN10300_GOT24		18	/* 24-bit offset to GOT entry.  */
+#define R_MN10300_GOT16		19	/* 16-bit offset to GOT entry.  */
+#define R_MN10300_COPY		20	/* Copy symbol at runtime.  */
+#define R_MN10300_GLOB_DAT	21	/* Create GOT entry.  */
+#define R_MN10300_JMP_SLOT	22	/* Create PLT entry.  */
+#define R_MN10300_RELATIVE	23	/* Adjust by program base.  */
+#define R_MN10300_TLS_GD	24	/* 32-bit offset for global dynamic.  */
+#define R_MN10300_TLS_LD	25	/* 32-bit offset for local dynamic.  */
+#define R_MN10300_TLS_LDO	26	/* Module-relative offset.  */
+#define R_MN10300_TLS_GOTIE	27	/* GOT offset for static TLS block
+					   offset.  */
+#define R_MN10300_TLS_IE	28	/* GOT address for static TLS block
+					   offset.  */
+#define R_MN10300_TLS_LE	29	/* Offset relative to static TLS
+					   block.  */
+#define R_MN10300_TLS_DTPMOD	30	/* ID of module containing symbol.  */
+#define R_MN10300_TLS_DTPOFF	31	/* Offset in module TLS block.  */
+#define R_MN10300_TLS_TPOFF	32	/* Offset in static TLS block.  */
+#define R_MN10300_SYM_DIFF	33	/* Adjustment for next reloc as needed
+					   by linker relaxation.  */
+#define R_MN10300_ALIGN		34	/* Alignment requirement for linker
+					   relaxation.  */
+#define R_MN10300_NUM		35
+
+
+/* M32R relocs.  */
+#define R_M32R_NONE		0	/* No reloc. */
+#define R_M32R_16		1	/* Direct 16 bit. */
+#define R_M32R_32		2	/* Direct 32 bit. */
+#define R_M32R_24		3	/* Direct 24 bit. */
+#define R_M32R_10_PCREL		4	/* PC relative 10 bit shifted. */
+#define R_M32R_18_PCREL		5	/* PC relative 18 bit shifted. */
+#define R_M32R_26_PCREL		6	/* PC relative 26 bit shifted. */
+#define R_M32R_HI16_ULO		7	/* High 16 bit with unsigned low. */
+#define R_M32R_HI16_SLO		8	/* High 16 bit with signed low. */
+#define R_M32R_LO16		9	/* Low 16 bit. */
+#define R_M32R_SDA16		10	/* 16 bit offset in SDA. */
+#define R_M32R_GNU_VTINHERIT	11
+#define R_M32R_GNU_VTENTRY	12
+/* M32R relocs use SHT_RELA.  */
+#define R_M32R_16_RELA		33	/* Direct 16 bit. */
+#define R_M32R_32_RELA		34	/* Direct 32 bit. */
+#define R_M32R_24_RELA		35	/* Direct 24 bit. */
+#define R_M32R_10_PCREL_RELA	36	/* PC relative 10 bit shifted. */
+#define R_M32R_18_PCREL_RELA	37	/* PC relative 18 bit shifted. */
+#define R_M32R_26_PCREL_RELA	38	/* PC relative 26 bit shifted. */
+#define R_M32R_HI16_ULO_RELA	39	/* High 16 bit with unsigned low */
+#define R_M32R_HI16_SLO_RELA	40	/* High 16 bit with signed low */
+#define R_M32R_LO16_RELA	41	/* Low 16 bit */
+#define R_M32R_SDA16_RELA	42	/* 16 bit offset in SDA */
+#define R_M32R_RELA_GNU_VTINHERIT	43
+#define R_M32R_RELA_GNU_VTENTRY	44
+#define R_M32R_REL32		45	/* PC relative 32 bit.  */
+
+#define R_M32R_GOT24		48	/* 24 bit GOT entry */
+#define R_M32R_26_PLTREL	49	/* 26 bit PC relative to PLT shifted */
+#define R_M32R_COPY		50	/* Copy symbol at runtime */
+#define R_M32R_GLOB_DAT		51	/* Create GOT entry */
+#define R_M32R_JMP_SLOT		52	/* Create PLT entry */
+#define R_M32R_RELATIVE		53	/* Adjust by program base */
+#define R_M32R_GOTOFF		54	/* 24 bit offset to GOT */
+#define R_M32R_GOTPC24		55	/* 24 bit PC relative offset to GOT */
+#define R_M32R_GOT16_HI_ULO	56	/* High 16 bit GOT entry with unsigned
+					   low */
+#define R_M32R_GOT16_HI_SLO	57	/* High 16 bit GOT entry with signed
+					   low */
+#define R_M32R_GOT16_LO		58	/* Low 16 bit GOT entry */
+#define R_M32R_GOTPC_HI_ULO	59	/* High 16 bit PC relative offset to
+					   GOT with unsigned low */
+#define R_M32R_GOTPC_HI_SLO	60	/* High 16 bit PC relative offset to
+					   GOT with signed low */
+#define R_M32R_GOTPC_LO		61	/* Low 16 bit PC relative offset to
+					   GOT */
+#define R_M32R_GOTOFF_HI_ULO	62	/* High 16 bit offset to GOT
+					   with unsigned low */
+#define R_M32R_GOTOFF_HI_SLO	63	/* High 16 bit offset to GOT
+					   with signed low */
+#define R_M32R_GOTOFF_LO	64	/* Low 16 bit offset to GOT */
+#define R_M32R_NUM		256	/* Keep this the last entry. */
+
+/* MicroBlaze relocations */
+#define R_MICROBLAZE_NONE		0	/* No reloc. */
+#define R_MICROBLAZE_32 		1	/* Direct 32 bit. */
+#define R_MICROBLAZE_32_PCREL		2	/* PC relative 32 bit. */
+#define R_MICROBLAZE_64_PCREL		3	/* PC relative 64 bit. */
+#define R_MICROBLAZE_32_PCREL_LO	4	/* Low 16 bits of PCREL32. */
+#define R_MICROBLAZE_64 		5	/* Direct 64 bit. */
+#define R_MICROBLAZE_32_LO		6	/* Low 16 bit. */
+#define R_MICROBLAZE_SRO32		7	/* Read-only small data area. */
+#define R_MICROBLAZE_SRW32		8	/* Read-write small data area. */
+#define R_MICROBLAZE_64_NONE		9	/* No reloc. */
+#define R_MICROBLAZE_32_SYM_OP_SYM	10	/* Symbol Op Symbol relocation. */
+#define R_MICROBLAZE_GNU_VTINHERIT	11	/* GNU C++ vtable hierarchy. */
+#define R_MICROBLAZE_GNU_VTENTRY	12	/* GNU C++ vtable member usage. */
+#define R_MICROBLAZE_GOTPC_64		13	/* PC-relative GOT offset.  */
+#define R_MICROBLAZE_GOT_64		14	/* GOT entry offset.  */
+#define R_MICROBLAZE_PLT_64		15	/* PLT offset (PC-relative).  */
+#define R_MICROBLAZE_REL		16	/* Adjust by program base.  */
+#define R_MICROBLAZE_JUMP_SLOT		17	/* Create PLT entry.  */
+#define R_MICROBLAZE_GLOB_DAT		18	/* Create GOT entry.  */
+#define R_MICROBLAZE_GOTOFF_64		19	/* 64 bit offset to GOT. */
+#define R_MICROBLAZE_GOTOFF_32		20	/* 32 bit offset to GOT. */
+#define R_MICROBLAZE_COPY		21	/* Runtime copy.  */
+#define R_MICROBLAZE_TLS		22	/* TLS Reloc. */
+#define R_MICROBLAZE_TLSGD		23	/* TLS General Dynamic. */
+#define R_MICROBLAZE_TLSLD		24	/* TLS Local Dynamic. */
+#define R_MICROBLAZE_TLSDTPMOD32	25	/* TLS Module ID. */
+#define R_MICROBLAZE_TLSDTPREL32	26	/* TLS Offset Within TLS Block. */
+#define R_MICROBLAZE_TLSDTPREL64	27	/* TLS Offset Within TLS Block. */
+#define R_MICROBLAZE_TLSGOTTPREL32	28	/* TLS Offset From Thread Pointer. */
+#define R_MICROBLAZE_TLSTPREL32 	29	/* TLS Offset From Thread Pointer. */
+
+/* Legal values for d_tag (dynamic entry type).  */
+#define DT_NIOS2_GP             0x70000002 /* Address of _gp.  */
+
+/* Nios II relocations.  */
+#define R_NIOS2_NONE		0	/* No reloc.  */
+#define R_NIOS2_S16		1	/* Direct signed 16 bit.  */
+#define R_NIOS2_U16		2	/* Direct unsigned 16 bit.  */
+#define R_NIOS2_PCREL16		3	/* PC relative 16 bit.  */
+#define R_NIOS2_CALL26		4	/* Direct call.  */
+#define R_NIOS2_IMM5		5	/* 5 bit constant expression.  */
+#define R_NIOS2_CACHE_OPX	6	/* 5 bit expression, shift 22.  */
+#define R_NIOS2_IMM6		7	/* 6 bit constant expression.  */
+#define R_NIOS2_IMM8		8	/* 8 bit constant expression.  */
+#define R_NIOS2_HI16		9	/* High 16 bit.  */
+#define R_NIOS2_LO16		10	/* Low 16 bit.  */
+#define R_NIOS2_HIADJ16		11	/* High 16 bit, adjusted.  */
+#define R_NIOS2_BFD_RELOC_32	12	/* 32 bit symbol value + addend.  */
+#define R_NIOS2_BFD_RELOC_16	13	/* 16 bit symbol value + addend.  */
+#define R_NIOS2_BFD_RELOC_8	14	/* 8 bit symbol value + addend.  */
+#define R_NIOS2_GPREL		15	/* 16 bit GP pointer offset.  */
+#define R_NIOS2_GNU_VTINHERIT	16	/* GNU C++ vtable hierarchy.  */
+#define R_NIOS2_GNU_VTENTRY	17	/* GNU C++ vtable member usage.  */
+#define R_NIOS2_UJMP		18	/* Unconditional branch.  */
+#define R_NIOS2_CJMP		19	/* Conditional branch.  */
+#define R_NIOS2_CALLR		20	/* Indirect call through register.  */
+#define R_NIOS2_ALIGN		21	/* Alignment requirement for
+					   linker relaxation.  */
+#define R_NIOS2_GOT16		22	/* 16 bit GOT entry.  */
+#define R_NIOS2_CALL16		23	/* 16 bit GOT entry for function.  */
+#define R_NIOS2_GOTOFF_LO	24	/* %lo of offset to GOT pointer.  */
+#define R_NIOS2_GOTOFF_HA	25	/* %hiadj of offset to GOT pointer.  */
+#define R_NIOS2_PCREL_LO	26	/* %lo of PC relative offset.  */
+#define R_NIOS2_PCREL_HA	27	/* %hiadj of PC relative offset.  */
+#define R_NIOS2_TLS_GD16	28	/* 16 bit GOT offset for TLS GD.  */
+#define R_NIOS2_TLS_LDM16	29	/* 16 bit GOT offset for TLS LDM.  */
+#define R_NIOS2_TLS_LDO16	30	/* 16 bit module relative offset.  */
+#define R_NIOS2_TLS_IE16	31	/* 16 bit GOT offset for TLS IE.  */
+#define R_NIOS2_TLS_LE16	32	/* 16 bit LE TP-relative offset.  */
+#define R_NIOS2_TLS_DTPMOD	33	/* Module number.  */
+#define R_NIOS2_TLS_DTPREL	34	/* Module-relative offset.  */
+#define R_NIOS2_TLS_TPREL	35	/* TP-relative offset.  */
+#define R_NIOS2_COPY		36	/* Copy symbol at runtime.  */
+#define R_NIOS2_GLOB_DAT	37	/* Create GOT entry.  */
+#define R_NIOS2_JUMP_SLOT	38	/* Create PLT entry.  */
+#define R_NIOS2_RELATIVE	39	/* Adjust by program base.  */
+#define R_NIOS2_GOTOFF		40	/* 16 bit offset to GOT pointer.  */
+#define R_NIOS2_CALL26_NOAT	41	/* Direct call in .noat section.  */
+#define R_NIOS2_GOT_LO		42	/* %lo() of GOT entry.  */
+#define R_NIOS2_GOT_HA		43	/* %hiadj() of GOT entry.  */
+#define R_NIOS2_CALL_LO		44	/* %lo() of function GOT entry.  */
+#define R_NIOS2_CALL_HA		45	/* %hiadj() of function GOT entry.  */
+
+/* TILEPro relocations.  */
+#define R_TILEPRO_NONE		0	/* No reloc */
+#define R_TILEPRO_32		1	/* Direct 32 bit */
+#define R_TILEPRO_16		2	/* Direct 16 bit */
+#define R_TILEPRO_8		3	/* Direct 8 bit */
+#define R_TILEPRO_32_PCREL	4	/* PC relative 32 bit */
+#define R_TILEPRO_16_PCREL	5	/* PC relative 16 bit */
+#define R_TILEPRO_8_PCREL	6	/* PC relative 8 bit */
+#define R_TILEPRO_LO16		7	/* Low 16 bit */
+#define R_TILEPRO_HI16		8	/* High 16 bit */
+#define R_TILEPRO_HA16		9	/* High 16 bit, adjusted */
+#define R_TILEPRO_COPY		10	/* Copy relocation */
+#define R_TILEPRO_GLOB_DAT	11	/* Create GOT entry */
+#define R_TILEPRO_JMP_SLOT	12	/* Create PLT entry */
+#define R_TILEPRO_RELATIVE	13	/* Adjust by program base */
+#define R_TILEPRO_BROFF_X1	14	/* X1 pipe branch offset */
+#define R_TILEPRO_JOFFLONG_X1	15	/* X1 pipe jump offset */
+#define R_TILEPRO_JOFFLONG_X1_PLT 16	/* X1 pipe jump offset to PLT */
+#define R_TILEPRO_IMM8_X0	17	/* X0 pipe 8-bit */
+#define R_TILEPRO_IMM8_Y0	18	/* Y0 pipe 8-bit */
+#define R_TILEPRO_IMM8_X1	19	/* X1 pipe 8-bit */
+#define R_TILEPRO_IMM8_Y1	20	/* Y1 pipe 8-bit */
+#define R_TILEPRO_MT_IMM15_X1	21	/* X1 pipe mtspr */
+#define R_TILEPRO_MF_IMM15_X1	22	/* X1 pipe mfspr */
+#define R_TILEPRO_IMM16_X0	23	/* X0 pipe 16-bit */
+#define R_TILEPRO_IMM16_X1	24	/* X1 pipe 16-bit */
+#define R_TILEPRO_IMM16_X0_LO	25	/* X0 pipe low 16-bit */
+#define R_TILEPRO_IMM16_X1_LO	26	/* X1 pipe low 16-bit */
+#define R_TILEPRO_IMM16_X0_HI	27	/* X0 pipe high 16-bit */
+#define R_TILEPRO_IMM16_X1_HI	28	/* X1 pipe high 16-bit */
+#define R_TILEPRO_IMM16_X0_HA	29	/* X0 pipe high 16-bit, adjusted */
+#define R_TILEPRO_IMM16_X1_HA	30	/* X1 pipe high 16-bit, adjusted */
+#define R_TILEPRO_IMM16_X0_PCREL 31	/* X0 pipe PC relative 16 bit */
+#define R_TILEPRO_IMM16_X1_PCREL 32	/* X1 pipe PC relative 16 bit */
+#define R_TILEPRO_IMM16_X0_LO_PCREL 33	/* X0 pipe PC relative low 16 bit */
+#define R_TILEPRO_IMM16_X1_LO_PCREL 34	/* X1 pipe PC relative low 16 bit */
+#define R_TILEPRO_IMM16_X0_HI_PCREL 35	/* X0 pipe PC relative high 16 bit */
+#define R_TILEPRO_IMM16_X1_HI_PCREL 36	/* X1 pipe PC relative high 16 bit */
+#define R_TILEPRO_IMM16_X0_HA_PCREL 37	/* X0 pipe PC relative ha() 16 bit */
+#define R_TILEPRO_IMM16_X1_HA_PCREL 38	/* X1 pipe PC relative ha() 16 bit */
+#define R_TILEPRO_IMM16_X0_GOT	39	/* X0 pipe 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X1_GOT	40	/* X1 pipe 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X0_GOT_LO 41	/* X0 pipe low 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X1_GOT_LO 42	/* X1 pipe low 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X0_GOT_HI 43	/* X0 pipe high 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X1_GOT_HI 44	/* X1 pipe high 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X0_GOT_HA 45	/* X0 pipe ha() 16-bit GOT offset */
+#define R_TILEPRO_IMM16_X1_GOT_HA 46	/* X1 pipe ha() 16-bit GOT offset */
+#define R_TILEPRO_MMSTART_X0	47	/* X0 pipe mm "start" */
+#define R_TILEPRO_MMEND_X0	48	/* X0 pipe mm "end" */
+#define R_TILEPRO_MMSTART_X1	49	/* X1 pipe mm "start" */
+#define R_TILEPRO_MMEND_X1	50	/* X1 pipe mm "end" */
+#define R_TILEPRO_SHAMT_X0	51	/* X0 pipe shift amount */
+#define R_TILEPRO_SHAMT_X1	52	/* X1 pipe shift amount */
+#define R_TILEPRO_SHAMT_Y0	53	/* Y0 pipe shift amount */
+#define R_TILEPRO_SHAMT_Y1	54	/* Y1 pipe shift amount */
+#define R_TILEPRO_DEST_IMM8_X1	55	/* X1 pipe destination 8-bit */
+/* Relocs 56-59 are currently not defined.  */
+#define R_TILEPRO_TLS_GD_CALL	60	/* "jal" for TLS GD */
+#define R_TILEPRO_IMM8_X0_TLS_GD_ADD 61	/* X0 pipe "addi" for TLS GD */
+#define R_TILEPRO_IMM8_X1_TLS_GD_ADD 62	/* X1 pipe "addi" for TLS GD */
+#define R_TILEPRO_IMM8_Y0_TLS_GD_ADD 63	/* Y0 pipe "addi" for TLS GD */
+#define R_TILEPRO_IMM8_Y1_TLS_GD_ADD 64	/* Y1 pipe "addi" for TLS GD */
+#define R_TILEPRO_TLS_IE_LOAD	65	/* "lw_tls" for TLS IE */
+#define R_TILEPRO_IMM16_X0_TLS_GD 66	/* X0 pipe 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X1_TLS_GD 67	/* X1 pipe 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X0_TLS_GD_LO 68	/* X0 pipe low 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X1_TLS_GD_LO 69	/* X1 pipe low 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X0_TLS_GD_HI 70	/* X0 pipe high 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X1_TLS_GD_HI 71	/* X1 pipe high 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X0_TLS_GD_HA 72	/* X0 pipe ha() 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X1_TLS_GD_HA 73	/* X1 pipe ha() 16-bit TLS GD offset */
+#define R_TILEPRO_IMM16_X0_TLS_IE 74	/* X0 pipe 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X1_TLS_IE 75	/* X1 pipe 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X0_TLS_IE_LO 76	/* X0 pipe low 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X1_TLS_IE_LO 77	/* X1 pipe low 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X0_TLS_IE_HI 78	/* X0 pipe high 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X1_TLS_IE_HI 79	/* X1 pipe high 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X0_TLS_IE_HA 80	/* X0 pipe ha() 16-bit TLS IE offset */
+#define R_TILEPRO_IMM16_X1_TLS_IE_HA 81	/* X1 pipe ha() 16-bit TLS IE offset */
+#define R_TILEPRO_TLS_DTPMOD32	82	/* ID of module containing symbol */
+#define R_TILEPRO_TLS_DTPOFF32	83	/* Offset in TLS block */
+#define R_TILEPRO_TLS_TPOFF32	84	/* Offset in static TLS block */
+#define R_TILEPRO_IMM16_X0_TLS_LE 85	/* X0 pipe 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X1_TLS_LE 86	/* X1 pipe 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X0_TLS_LE_LO 87	/* X0 pipe low 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X1_TLS_LE_LO 88	/* X1 pipe low 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X0_TLS_LE_HI 89	/* X0 pipe high 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X1_TLS_LE_HI 90	/* X1 pipe high 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X0_TLS_LE_HA 91	/* X0 pipe ha() 16-bit TLS LE offset */
+#define R_TILEPRO_IMM16_X1_TLS_LE_HA 92	/* X1 pipe ha() 16-bit TLS LE offset */
+
+#define R_TILEPRO_GNU_VTINHERIT	128	/* GNU C++ vtable hierarchy */
+#define R_TILEPRO_GNU_VTENTRY	129	/* GNU C++ vtable member usage */
+
+#define R_TILEPRO_NUM		130
+
+
+/* TILE-Gx relocations.  */
+#define R_TILEGX_NONE		0	/* No reloc */
+#define R_TILEGX_64		1	/* Direct 64 bit */
+#define R_TILEGX_32		2	/* Direct 32 bit */
+#define R_TILEGX_16		3	/* Direct 16 bit */
+#define R_TILEGX_8		4	/* Direct 8 bit */
+#define R_TILEGX_64_PCREL	5	/* PC relative 64 bit */
+#define R_TILEGX_32_PCREL	6	/* PC relative 32 bit */
+#define R_TILEGX_16_PCREL	7	/* PC relative 16 bit */
+#define R_TILEGX_8_PCREL	8	/* PC relative 8 bit */
+#define R_TILEGX_HW0		9	/* hword 0 16-bit */
+#define R_TILEGX_HW1		10	/* hword 1 16-bit */
+#define R_TILEGX_HW2		11	/* hword 2 16-bit */
+#define R_TILEGX_HW3		12	/* hword 3 16-bit */
+#define R_TILEGX_HW0_LAST	13	/* last hword 0 16-bit */
+#define R_TILEGX_HW1_LAST	14	/* last hword 1 16-bit */
+#define R_TILEGX_HW2_LAST	15	/* last hword 2 16-bit */
+#define R_TILEGX_COPY		16	/* Copy relocation */
+#define R_TILEGX_GLOB_DAT	17	/* Create GOT entry */
+#define R_TILEGX_JMP_SLOT	18	/* Create PLT entry */
+#define R_TILEGX_RELATIVE	19	/* Adjust by program base */
+#define R_TILEGX_BROFF_X1	20	/* X1 pipe branch offset */
+#define R_TILEGX_JUMPOFF_X1	21	/* X1 pipe jump offset */
+#define R_TILEGX_JUMPOFF_X1_PLT	22	/* X1 pipe jump offset to PLT */
+#define R_TILEGX_IMM8_X0	23	/* X0 pipe 8-bit */
+#define R_TILEGX_IMM8_Y0	24	/* Y0 pipe 8-bit */
+#define R_TILEGX_IMM8_X1	25	/* X1 pipe 8-bit */
+#define R_TILEGX_IMM8_Y1	26	/* Y1 pipe 8-bit */
+#define R_TILEGX_DEST_IMM8_X1	27	/* X1 pipe destination 8-bit */
+#define R_TILEGX_MT_IMM14_X1	28	/* X1 pipe mtspr */
+#define R_TILEGX_MF_IMM14_X1	29	/* X1 pipe mfspr */
+#define R_TILEGX_MMSTART_X0	30	/* X0 pipe mm "start" */
+#define R_TILEGX_MMEND_X0	31	/* X0 pipe mm "end" */
+#define R_TILEGX_SHAMT_X0	32	/* X0 pipe shift amount */
+#define R_TILEGX_SHAMT_X1	33	/* X1 pipe shift amount */
+#define R_TILEGX_SHAMT_Y0	34	/* Y0 pipe shift amount */
+#define R_TILEGX_SHAMT_Y1	35	/* Y1 pipe shift amount */
+#define R_TILEGX_IMM16_X0_HW0	36	/* X0 pipe hword 0 */
+#define R_TILEGX_IMM16_X1_HW0	37	/* X1 pipe hword 0 */
+#define R_TILEGX_IMM16_X0_HW1	38	/* X0 pipe hword 1 */
+#define R_TILEGX_IMM16_X1_HW1	39	/* X1 pipe hword 1 */
+#define R_TILEGX_IMM16_X0_HW2	40	/* X0 pipe hword 2 */
+#define R_TILEGX_IMM16_X1_HW2	41	/* X1 pipe hword 2 */
+#define R_TILEGX_IMM16_X0_HW3	42	/* X0 pipe hword 3 */
+#define R_TILEGX_IMM16_X1_HW3	43	/* X1 pipe hword 3 */
+#define R_TILEGX_IMM16_X0_HW0_LAST 44	/* X0 pipe last hword 0 */
+#define R_TILEGX_IMM16_X1_HW0_LAST 45	/* X1 pipe last hword 0 */
+#define R_TILEGX_IMM16_X0_HW1_LAST 46	/* X0 pipe last hword 1 */
+#define R_TILEGX_IMM16_X1_HW1_LAST 47	/* X1 pipe last hword 1 */
+#define R_TILEGX_IMM16_X0_HW2_LAST 48	/* X0 pipe last hword 2 */
+#define R_TILEGX_IMM16_X1_HW2_LAST 49	/* X1 pipe last hword 2 */
+#define R_TILEGX_IMM16_X0_HW0_PCREL 50	/* X0 pipe PC relative hword 0 */
+#define R_TILEGX_IMM16_X1_HW0_PCREL 51	/* X1 pipe PC relative hword 0 */
+#define R_TILEGX_IMM16_X0_HW1_PCREL 52	/* X0 pipe PC relative hword 1 */
+#define R_TILEGX_IMM16_X1_HW1_PCREL 53	/* X1 pipe PC relative hword 1 */
+#define R_TILEGX_IMM16_X0_HW2_PCREL 54	/* X0 pipe PC relative hword 2 */
+#define R_TILEGX_IMM16_X1_HW2_PCREL 55	/* X1 pipe PC relative hword 2 */
+#define R_TILEGX_IMM16_X0_HW3_PCREL 56	/* X0 pipe PC relative hword 3 */
+#define R_TILEGX_IMM16_X1_HW3_PCREL 57	/* X1 pipe PC relative hword 3 */
+#define R_TILEGX_IMM16_X0_HW0_LAST_PCREL 58 /* X0 pipe PC-rel last hword 0 */
+#define R_TILEGX_IMM16_X1_HW0_LAST_PCREL 59 /* X1 pipe PC-rel last hword 0 */
+#define R_TILEGX_IMM16_X0_HW1_LAST_PCREL 60 /* X0 pipe PC-rel last hword 1 */
+#define R_TILEGX_IMM16_X1_HW1_LAST_PCREL 61 /* X1 pipe PC-rel last hword 1 */
+#define R_TILEGX_IMM16_X0_HW2_LAST_PCREL 62 /* X0 pipe PC-rel last hword 2 */
+#define R_TILEGX_IMM16_X1_HW2_LAST_PCREL 63 /* X1 pipe PC-rel last hword 2 */
+#define R_TILEGX_IMM16_X0_HW0_GOT 64	/* X0 pipe hword 0 GOT offset */
+#define R_TILEGX_IMM16_X1_HW0_GOT 65	/* X1 pipe hword 0 GOT offset */
+#define R_TILEGX_IMM16_X0_HW0_PLT_PCREL 66 /* X0 pipe PC-rel PLT hword 0 */
+#define R_TILEGX_IMM16_X1_HW0_PLT_PCREL 67 /* X1 pipe PC-rel PLT hword 0 */
+#define R_TILEGX_IMM16_X0_HW1_PLT_PCREL 68 /* X0 pipe PC-rel PLT hword 1 */
+#define R_TILEGX_IMM16_X1_HW1_PLT_PCREL 69 /* X1 pipe PC-rel PLT hword 1 */
+#define R_TILEGX_IMM16_X0_HW2_PLT_PCREL 70 /* X0 pipe PC-rel PLT hword 2 */
+#define R_TILEGX_IMM16_X1_HW2_PLT_PCREL 71 /* X1 pipe PC-rel PLT hword 2 */
+#define R_TILEGX_IMM16_X0_HW0_LAST_GOT 72 /* X0 pipe last hword 0 GOT offset */
+#define R_TILEGX_IMM16_X1_HW0_LAST_GOT 73 /* X1 pipe last hword 0 GOT offset */
+#define R_TILEGX_IMM16_X0_HW1_LAST_GOT 74 /* X0 pipe last hword 1 GOT offset */
+#define R_TILEGX_IMM16_X1_HW1_LAST_GOT 75 /* X1 pipe last hword 1 GOT offset */
+#define R_TILEGX_IMM16_X0_HW3_PLT_PCREL 76 /* X0 pipe PC-rel PLT hword 3 */
+#define R_TILEGX_IMM16_X1_HW3_PLT_PCREL 77 /* X1 pipe PC-rel PLT hword 3 */
+#define R_TILEGX_IMM16_X0_HW0_TLS_GD 78	/* X0 pipe hword 0 TLS GD offset */
+#define R_TILEGX_IMM16_X1_HW0_TLS_GD 79	/* X1 pipe hword 0 TLS GD offset */
+#define R_TILEGX_IMM16_X0_HW0_TLS_LE 80	/* X0 pipe hword 0 TLS LE offset */
+#define R_TILEGX_IMM16_X1_HW0_TLS_LE 81	/* X1 pipe hword 0 TLS LE offset */
+#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_LE 82 /* X0 pipe last hword 0 LE off */
+#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_LE 83 /* X1 pipe last hword 0 LE off */
+#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_LE 84 /* X0 pipe last hword 1 LE off */
+#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_LE 85 /* X1 pipe last hword 1 LE off */
+#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_GD 86 /* X0 pipe last hword 0 GD off */
+#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_GD 87 /* X1 pipe last hword 0 GD off */
+#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_GD 88 /* X0 pipe last hword 1 GD off */
+#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_GD 89 /* X1 pipe last hword 1 GD off */
+/* Relocs 90-91 are currently not defined.  */
+#define R_TILEGX_IMM16_X0_HW0_TLS_IE 92	/* X0 pipe hword 0 TLS IE offset */
+#define R_TILEGX_IMM16_X1_HW0_TLS_IE 93	/* X1 pipe hword 0 TLS IE offset */
+#define R_TILEGX_IMM16_X0_HW0_LAST_PLT_PCREL 94 /* X0 pipe PC-rel PLT last hword 0 */
+#define R_TILEGX_IMM16_X1_HW0_LAST_PLT_PCREL 95 /* X1 pipe PC-rel PLT last hword 0 */
+#define R_TILEGX_IMM16_X0_HW1_LAST_PLT_PCREL 96 /* X0 pipe PC-rel PLT last hword 1 */
+#define R_TILEGX_IMM16_X1_HW1_LAST_PLT_PCREL 97 /* X1 pipe PC-rel PLT last hword 1 */
+#define R_TILEGX_IMM16_X0_HW2_LAST_PLT_PCREL 98 /* X0 pipe PC-rel PLT last hword 2 */
+#define R_TILEGX_IMM16_X1_HW2_LAST_PLT_PCREL 99 /* X1 pipe PC-rel PLT last hword 2 */
+#define R_TILEGX_IMM16_X0_HW0_LAST_TLS_IE 100 /* X0 pipe last hword 0 IE off */
+#define R_TILEGX_IMM16_X1_HW0_LAST_TLS_IE 101 /* X1 pipe last hword 0 IE off */
+#define R_TILEGX_IMM16_X0_HW1_LAST_TLS_IE 102 /* X0 pipe last hword 1 IE off */
+#define R_TILEGX_IMM16_X1_HW1_LAST_TLS_IE 103 /* X1 pipe last hword 1 IE off */
+/* Relocs 104-105 are currently not defined.  */
+#define R_TILEGX_TLS_DTPMOD64	106	/* 64-bit ID of symbol's module */
+#define R_TILEGX_TLS_DTPOFF64	107	/* 64-bit offset in TLS block */
+#define R_TILEGX_TLS_TPOFF64	108	/* 64-bit offset in static TLS block */
+#define R_TILEGX_TLS_DTPMOD32	109	/* 32-bit ID of symbol's module */
+#define R_TILEGX_TLS_DTPOFF32	110	/* 32-bit offset in TLS block */
+#define R_TILEGX_TLS_TPOFF32	111	/* 32-bit offset in static TLS block */
+#define R_TILEGX_TLS_GD_CALL	112	/* "jal" for TLS GD */
+#define R_TILEGX_IMM8_X0_TLS_GD_ADD 113	/* X0 pipe "addi" for TLS GD */
+#define R_TILEGX_IMM8_X1_TLS_GD_ADD 114	/* X1 pipe "addi" for TLS GD */
+#define R_TILEGX_IMM8_Y0_TLS_GD_ADD 115	/* Y0 pipe "addi" for TLS GD */
+#define R_TILEGX_IMM8_Y1_TLS_GD_ADD 116	/* Y1 pipe "addi" for TLS GD */
+#define R_TILEGX_TLS_IE_LOAD	117	/* "ld_tls" for TLS IE */
+#define R_TILEGX_IMM8_X0_TLS_ADD 118	/* X0 pipe "addi" for TLS GD/IE */
+#define R_TILEGX_IMM8_X1_TLS_ADD 119	/* X1 pipe "addi" for TLS GD/IE */
+#define R_TILEGX_IMM8_Y0_TLS_ADD 120	/* Y0 pipe "addi" for TLS GD/IE */
+#define R_TILEGX_IMM8_Y1_TLS_ADD 121	/* Y1 pipe "addi" for TLS GD/IE */
+
+#define R_TILEGX_GNU_VTINHERIT	128	/* GNU C++ vtable hierarchy */
+#define R_TILEGX_GNU_VTENTRY	129	/* GNU C++ vtable member usage */
+
+#define R_TILEGX_NUM		130
+
+/* RISC-V ELF Flags */
+#define EF_RISCV_RVC 			0x0001
+#define EF_RISCV_FLOAT_ABI 		0x0006
+#define EF_RISCV_FLOAT_ABI_SOFT 	0x0000
+#define EF_RISCV_FLOAT_ABI_SINGLE 	0x0002
+#define EF_RISCV_FLOAT_ABI_DOUBLE 	0x0004
+#define EF_RISCV_FLOAT_ABI_QUAD 	0x0006
+
+/* RISC-V relocations.  */
+#define R_RISCV_NONE		 0
+#define R_RISCV_32		 1
+#define R_RISCV_64		 2
+#define R_RISCV_RELATIVE	 3
+#define R_RISCV_COPY		 4
+#define R_RISCV_JUMP_SLOT	 5
+#define R_RISCV_TLS_DTPMOD32	 6
+#define R_RISCV_TLS_DTPMOD64	 7
+#define R_RISCV_TLS_DTPREL32	 8
+#define R_RISCV_TLS_DTPREL64	 9
+#define R_RISCV_TLS_TPREL32	10
+#define R_RISCV_TLS_TPREL64	11
+#define R_RISCV_BRANCH		16
+#define R_RISCV_JAL		17
+#define R_RISCV_CALL		18
+#define R_RISCV_CALL_PLT	19
+#define R_RISCV_GOT_HI20	20
+#define R_RISCV_TLS_GOT_HI20	21
+#define R_RISCV_TLS_GD_HI20	22
+#define R_RISCV_PCREL_HI20	23
+#define R_RISCV_PCREL_LO12_I	24
+#define R_RISCV_PCREL_LO12_S	25
+#define R_RISCV_HI20		26
+#define R_RISCV_LO12_I		27
+#define R_RISCV_LO12_S		28
+#define R_RISCV_TPREL_HI20	29
+#define R_RISCV_TPREL_LO12_I	30
+#define R_RISCV_TPREL_LO12_S	31
+#define R_RISCV_TPREL_ADD	32
+#define R_RISCV_ADD8		33
+#define R_RISCV_ADD16		34
+#define R_RISCV_ADD32		35
+#define R_RISCV_ADD64		36
+#define R_RISCV_SUB8		37
+#define R_RISCV_SUB16		38
+#define R_RISCV_SUB32		39
+#define R_RISCV_SUB64		40
+#define R_RISCV_GNU_VTINHERIT	41
+#define R_RISCV_GNU_VTENTRY	42
+#define R_RISCV_ALIGN		43
+#define R_RISCV_RVC_BRANCH	44
+#define R_RISCV_RVC_JUMP	45
+#define R_RISCV_RVC_LUI		46
+#define R_RISCV_GPREL_I		47
+#define R_RISCV_GPREL_S		48
+#define R_RISCV_TPREL_I		49
+#define R_RISCV_TPREL_S		50
+#define R_RISCV_RELAX		51
+#define R_RISCV_SUB6		52
+#define R_RISCV_SET6		53
+#define R_RISCV_SET8		54
+#define R_RISCV_SET16		55
+#define R_RISCV_SET32		56
+#define R_RISCV_32_PCREL	57
+
+#define R_RISCV_NUM		58
+
+/* BPF specific declarations.  */
+
+#define R_BPF_NONE		0	/* No reloc */
+#define R_BPF_64_64		1
+#define R_BPF_64_32		10
+
+/* Imagination Meta specific relocations. */
+
+#define R_METAG_HIADDR16	0
+#define R_METAG_LOADDR16	1
+#define R_METAG_ADDR32		2	/* 32bit absolute address */
+#define R_METAG_NONE		3	/* No reloc */
+#define R_METAG_RELBRANCH	4
+#define R_METAG_GETSETOFF	5
+
+/* Backward compatability */
+#define R_METAG_REG32OP1	6
+#define R_METAG_REG32OP2	7
+#define R_METAG_REG32OP3	8
+#define R_METAG_REG16OP1	9
+#define R_METAG_REG16OP2	10
+#define R_METAG_REG16OP3	11
+#define R_METAG_REG32OP4	12
+
+#define R_METAG_HIOG		13
+#define R_METAG_LOOG		14
+
+#define R_METAG_REL8		15
+#define R_METAG_REL16		16
+
+/* GNU */
+#define R_METAG_GNU_VTINHERIT	30
+#define R_METAG_GNU_VTENTRY	31
+
+/* PIC relocations */
+#define R_METAG_HI16_GOTOFF	32
+#define R_METAG_LO16_GOTOFF	33
+#define R_METAG_GETSET_GOTOFF	34
+#define R_METAG_GETSET_GOT	35
+#define R_METAG_HI16_GOTPC	36
+#define R_METAG_LO16_GOTPC	37
+#define R_METAG_HI16_PLT	38
+#define R_METAG_LO16_PLT	39
+#define R_METAG_RELBRANCH_PLT	40
+#define R_METAG_GOTOFF		41
+#define R_METAG_PLT		42
+#define R_METAG_COPY		43
+#define R_METAG_JMP_SLOT	44
+#define R_METAG_RELATIVE	45
+#define R_METAG_GLOB_DAT	46
+
+/* TLS relocations */
+#define R_METAG_TLS_GD		47
+#define R_METAG_TLS_LDM		48
+#define R_METAG_TLS_LDO_HI16	49
+#define R_METAG_TLS_LDO_LO16	50
+#define R_METAG_TLS_LDO		51
+#define R_METAG_TLS_IE		52
+#define R_METAG_TLS_IENONPIC	53
+#define R_METAG_TLS_IENONPIC_HI16 54
+#define R_METAG_TLS_IENONPIC_LO16 55
+#define R_METAG_TLS_TPOFF	56
+#define R_METAG_TLS_DTPMOD	57
+#define R_METAG_TLS_DTPOFF	58
+#define R_METAG_TLS_LE		59
+#define R_METAG_TLS_LE_HI16	60
+#define R_METAG_TLS_LE_LO16	61
+
+/* NDS32 relocations.  */
+#define R_NDS32_NONE		0
+#define R_NDS32_32_RELA 	20
+#define R_NDS32_COPY		39
+#define R_NDS32_GLOB_DAT	40
+#define R_NDS32_JMP_SLOT	41
+#define R_NDS32_RELATIVE	42
+#define R_NDS32_TLS_TPOFF	102
+#define R_NDS32_TLS_DESC	119
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif	/* elf.h */
diff --git a/third_party/gtest/BUILD.gn b/third_party/gtest/BUILD.gn
index db5f4a5..4764604 100644
--- a/third_party/gtest/BUILD.gn
+++ b/third_party/gtest/BUILD.gn
@@ -28,12 +28,13 @@
       "//testing/gmock",
     ]
   }
-} else if (crashpad_is_in_fuchsia) {
+} else if (crashpad_is_in_dart || crashpad_is_in_fuchsia) {
   group("gtest") {
     testonly = true
     public_deps = [
       "//third_party/googletest:gtest",
     ]
+    public_configs = [ "../..:disable_ubsan" ]
   }
   group("gmock") {
     testonly = true
@@ -56,6 +57,7 @@
     testonly = true
     sources = [
       "gtest/googletest/include/gtest/gtest-death-test.h",
+      "gtest/googletest/include/gtest/gtest-matchers.h",
       "gtest/googletest/include/gtest/gtest-message.h",
       "gtest/googletest/include/gtest/gtest-param-test.h",
       "gtest/googletest/include/gtest/gtest-printers.h",
@@ -71,18 +73,16 @@
       "gtest/googletest/include/gtest/internal/gtest-death-test-internal.h",
       "gtest/googletest/include/gtest/internal/gtest-filepath.h",
       "gtest/googletest/include/gtest/internal/gtest-internal.h",
-      "gtest/googletest/include/gtest/internal/gtest-linked_ptr.h",
-      "gtest/googletest/include/gtest/internal/gtest-param-util-generated.h",
       "gtest/googletest/include/gtest/internal/gtest-param-util.h",
       "gtest/googletest/include/gtest/internal/gtest-port-arch.h",
       "gtest/googletest/include/gtest/internal/gtest-port.h",
       "gtest/googletest/include/gtest/internal/gtest-string.h",
-      "gtest/googletest/include/gtest/internal/gtest-tuple.h",
       "gtest/googletest/include/gtest/internal/gtest-type-util.h",
       "gtest/googletest/src/gtest-all.cc",
       "gtest/googletest/src/gtest-death-test.cc",
       "gtest/googletest/src/gtest-filepath.cc",
       "gtest/googletest/src/gtest-internal-inl.h",
+      "gtest/googletest/src/gtest-matchers.cc",
       "gtest/googletest/src/gtest-port.cc",
       "gtest/googletest/src/gtest-printers.cc",
       "gtest/googletest/src/gtest-test-part.cc",
@@ -95,6 +95,11 @@
       "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors",
     ]
     configs += [ ":gtest_private_config" ]
+    if (crashpad_is_fuchsia) {
+      deps = [
+        "../fuchsia",
+      ]
+    }
   }
 
   static_library("gtest_main") {
@@ -112,20 +117,20 @@
 
   test("gtest_all_test") {
     sources = [
-      "gtest/googletest/test/gtest-death-test_test.cc",
-      "gtest/googletest/test/gtest-filepath_test.cc",
-      "gtest/googletest/test/gtest-linked_ptr_test.cc",
-      "gtest/googletest/test/gtest-message_test.cc",
-      "gtest/googletest/test/gtest-options_test.cc",
-      "gtest/googletest/test/gtest-port_test.cc",
-      "gtest/googletest/test/gtest-printers_test.cc",
-      "gtest/googletest/test/gtest-test-part_test.cc",
+      "gtest/googletest/test/googletest-death-test-test.cc",
+      "gtest/googletest/test/googletest-filepath-test.cc",
+      "gtest/googletest/test/googletest-message-test.cc",
+      "gtest/googletest/test/googletest-options-test.cc",
+      "gtest/googletest/test/googletest-port-test.cc",
+      "gtest/googletest/test/googletest-printers-test.cc",
+      "gtest/googletest/test/googletest-test-part-test.cc",
       "gtest/googletest/test/gtest-typed-test2_test.cc",
       "gtest/googletest/test/gtest-typed-test_test.cc",
       "gtest/googletest/test/gtest-typed-test_test.h",
       "gtest/googletest/test/gtest_main_unittest.cc",
       "gtest/googletest/test/gtest_pred_impl_unittest.cc",
       "gtest/googletest/test/gtest_prod_test.cc",
+      "gtest/googletest/test/gtest_skip_test.cc",
       "gtest/googletest/test/gtest_unittest.cc",
       "gtest/googletest/test/production.cc",
       "gtest/googletest/test/production.h",
@@ -156,7 +161,7 @@
 
   test("gtest_listener_test") {
     sources = [
-      "gtest/googletest/test/gtest-listener_test.cc",
+      "gtest/googletest/test/googletest-listener-test.cc",
     ]
     deps = [
       ":gtest",
@@ -174,9 +179,9 @@
 
   test("gtest_param_test") {
     sources = [
-      "gtest/googletest/test/gtest-param-test2_test.cc",
-      "gtest/googletest/test/gtest-param-test_test.cc",
-      "gtest/googletest/test/gtest-param-test_test.h",
+      "gtest/googletest/test/googletest-param-test-test.cc",
+      "gtest/googletest/test/googletest-param-test-test.h",
+      "gtest/googletest/test/googletest-param-test2-test.cc",
     ]
     configs -= [
       "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors",
@@ -284,21 +289,20 @@
     sources = [
       "gtest/googlemock/include/gmock/gmock-actions.h",
       "gtest/googlemock/include/gmock/gmock-cardinalities.h",
+      "gtest/googlemock/include/gmock/gmock-function-mocker.h",
       "gtest/googlemock/include/gmock/gmock-generated-actions.h",
-      "gtest/googlemock/include/gmock/gmock-generated-function-mockers.h",
-      "gtest/googlemock/include/gmock/gmock-generated-matchers.h",
-      "gtest/googlemock/include/gmock/gmock-generated-nice-strict.h",
       "gtest/googlemock/include/gmock/gmock-matchers.h",
       "gtest/googlemock/include/gmock/gmock-more-actions.h",
       "gtest/googlemock/include/gmock/gmock-more-matchers.h",
+      "gtest/googlemock/include/gmock/gmock-nice-strict.h",
       "gtest/googlemock/include/gmock/gmock-spec-builders.h",
       "gtest/googlemock/include/gmock/gmock.h",
       "gtest/googlemock/include/gmock/internal/custom/gmock-generated-actions.h",
       "gtest/googlemock/include/gmock/internal/custom/gmock-matchers.h",
       "gtest/googlemock/include/gmock/internal/custom/gmock-port.h",
-      "gtest/googlemock/include/gmock/internal/gmock-generated-internal-utils.h",
       "gtest/googlemock/include/gmock/internal/gmock-internal-utils.h",
       "gtest/googlemock/include/gmock/internal/gmock-port.h",
+      "gtest/googlemock/include/gmock/internal/gmock-pp.h",
       "gtest/googlemock/src/gmock-all.cc",
       "gtest/googlemock/src/gmock-cardinalities.cc",
       "gtest/googlemock/src/gmock-internal-utils.cc",
@@ -334,15 +338,16 @@
     sources = [
       "gtest/googlemock/test/gmock-actions_test.cc",
       "gtest/googlemock/test/gmock-cardinalities_test.cc",
+      "gtest/googlemock/test/gmock-function-mocker_test.cc",
       "gtest/googlemock/test/gmock-generated-actions_test.cc",
-      "gtest/googlemock/test/gmock-generated-function-mockers_test.cc",
-      "gtest/googlemock/test/gmock-generated-internal-utils_test.cc",
       "gtest/googlemock/test/gmock-generated-matchers_test.cc",
       "gtest/googlemock/test/gmock-internal-utils_test.cc",
       "gtest/googlemock/test/gmock-matchers_test.cc",
       "gtest/googlemock/test/gmock-more-actions_test.cc",
       "gtest/googlemock/test/gmock-nice-strict_test.cc",
       "gtest/googlemock/test/gmock-port_test.cc",
+      "gtest/googlemock/test/gmock-pp-string_test.cc",
+      "gtest/googlemock/test/gmock-pp_test.cc",
       "gtest/googlemock/test/gmock-spec-builders_test.cc",
       "gtest/googlemock/test/gmock_test.cc",
     ]
diff --git a/third_party/gtest/gmock.gyp b/third_party/gtest/gmock.gyp
index d53c925..10c0879 100644
--- a/third_party/gtest/gmock.gyp
+++ b/third_party/gtest/gmock.gyp
@@ -56,18 +56,18 @@
       'sources': [
         '<(gmock_dir)/include/gmock/gmock-actions.h',
         '<(gmock_dir)/include/gmock/gmock-cardinalities.h',
+        '<(gmock_dir)/include/gmock/gmock-function-mocker.h',
         '<(gmock_dir)/include/gmock/gmock-generated-actions.h',
-        '<(gmock_dir)/include/gmock/gmock-generated-function-mockers.h',
-        '<(gmock_dir)/include/gmock/gmock-generated-matchers.h',
-        '<(gmock_dir)/include/gmock/gmock-generated-nice-strict.h',
         '<(gmock_dir)/include/gmock/gmock-matchers.h',
         '<(gmock_dir)/include/gmock/gmock-more-actions.h',
         '<(gmock_dir)/include/gmock/gmock-more-matchers.h',
+        '<(gmock_dir)/include/gmock/gmock-nice-strict.h',
         '<(gmock_dir)/include/gmock/gmock-spec-builders.h',
         '<(gmock_dir)/include/gmock/gmock.h',
         '<(gmock_dir)/include/gmock/internal/custom/gmock-generated-actions.h',
         '<(gmock_dir)/include/gmock/internal/custom/gmock-matchers.h',
         '<(gmock_dir)/include/gmock/internal/custom/gmock-port.h',
+        '<(gmock_dir)/include/gmock/internal/custom/gmock-pp.h',
         '<(gmock_dir)/include/gmock/internal/gmock-generated-internal-utils.h',
         '<(gmock_dir)/include/gmock/internal/gmock-internal-utils.h',
         '<(gmock_dir)/include/gmock/internal/gmock-port.h',
@@ -156,15 +156,16 @@
       'sources': [
         '<(gmock_dir)/test/gmock-actions_test.cc',
         '<(gmock_dir)/test/gmock-cardinalities_test.cc',
+        '<(gmock_dir)/test/gmock-function-mocker_test.cc',
         '<(gmock_dir)/test/gmock-generated-actions_test.cc',
-        '<(gmock_dir)/test/gmock-generated-function-mockers_test.cc',
-        '<(gmock_dir)/test/gmock-generated-internal-utils_test.cc',
         '<(gmock_dir)/test/gmock-generated-matchers_test.cc',
         '<(gmock_dir)/test/gmock-internal-utils_test.cc',
         '<(gmock_dir)/test/gmock-matchers_test.cc',
         '<(gmock_dir)/test/gmock-more-actions_test.cc',
         '<(gmock_dir)/test/gmock-nice-strict_test.cc',
         '<(gmock_dir)/test/gmock-port_test.cc',
+        '<(gmock_dir)/test/gmock-pp-string_test.cc',
+        '<(gmock_dir)/test/gmock-pp_test.cc',
         '<(gmock_dir)/test/gmock-spec-builders_test.cc',
         '<(gmock_dir)/test/gmock_test.cc',
       ],
diff --git a/third_party/gtest/gtest.gyp b/third_party/gtest/gtest.gyp
index 5d93feb..aa6399b 100644
--- a/third_party/gtest/gtest.gyp
+++ b/third_party/gtest/gtest.gyp
@@ -67,6 +67,7 @@
       ],
       'sources': [
         '<(gtest_dir)/include/gtest/gtest-death-test.h',
+        '<(gtest_dir)/include/gtest/gtest-matchers.h',
         '<(gtest_dir)/include/gtest/gtest-message.h',
         '<(gtest_dir)/include/gtest/gtest-param-test.h',
         '<(gtest_dir)/include/gtest/gtest-printers.h',
@@ -82,18 +83,17 @@
         '<(gtest_dir)/include/gtest/internal/gtest-death-test-internal.h',
         '<(gtest_dir)/include/gtest/internal/gtest-filepath.h',
         '<(gtest_dir)/include/gtest/internal/gtest-internal.h',
-        '<(gtest_dir)/include/gtest/internal/gtest-linked_ptr.h',
         '<(gtest_dir)/include/gtest/internal/gtest-param-util-generated.h',
         '<(gtest_dir)/include/gtest/internal/gtest-param-util.h',
         '<(gtest_dir)/include/gtest/internal/gtest-port-arch.h',
         '<(gtest_dir)/include/gtest/internal/gtest-port.h',
         '<(gtest_dir)/include/gtest/internal/gtest-string.h',
-        '<(gtest_dir)/include/gtest/internal/gtest-tuple.h',
         '<(gtest_dir)/include/gtest/internal/gtest-type-util.h',
         '<(gtest_dir)/src/gtest-all.cc',
         '<(gtest_dir)/src/gtest-death-test.cc',
         '<(gtest_dir)/src/gtest-filepath.cc',
         '<(gtest_dir)/src/gtest-internal-inl.h',
+        '<(gtest_dir)/src/gtest-matchers.cc',
         '<(gtest_dir)/src/gtest-port.cc',
         '<(gtest_dir)/src/gtest-printers.cc',
         '<(gtest_dir)/src/gtest-test-part.cc',
@@ -174,6 +174,7 @@
         '<(gtest_dir)/test/gtest_main_unittest.cc',
         '<(gtest_dir)/test/gtest_pred_impl_unittest.cc',
         '<(gtest_dir)/test/gtest_prod_test.cc',
+        '<(gtest_dir)/test/gtest_skip_test.cc',
         '<(gtest_dir)/test/gtest_unittest.cc',
         '<(gtest_dir)/test/production.cc',
         '<(gtest_dir)/test/production.h',
diff --git a/third_party/lss/BUILD.gn b/third_party/lss/BUILD.gn
new file mode 100644
index 0000000..c0652cd
--- /dev/null
+++ b/third_party/lss/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2019 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.
+
+import("../../build/crashpad_buildconfig.gni")
+
+config("lss_config") {
+  if (crashpad_is_in_chromium) {
+    defines = [ "CRASHPAD_LSS_SOURCE_EXTERNAL" ]
+  } else {
+    defines = [ "CRASHPAD_LSS_SOURCE_EMBEDDED" ]
+  }
+}
+
+source_set("lss") {
+  public_configs = [ ":lss_config" ]
+
+  sources = [ "lss.h" ]
+}
diff --git a/third_party/lss/README.crashpad b/third_party/lss/README.crashpad
new file mode 100644
index 0000000..c036ae0
--- /dev/null
+++ b/third_party/lss/README.crashpad
@@ -0,0 +1,16 @@
+Name: linux-syscall-support
+Short Name: lss
+URL: https://chromium.googlesource.com/linux-syscall-support/
+Revision: See DEPS
+License: BSD 3-clause
+License File: lss/linux-syscall-support.h
+Security Critical: yes
+
+Description:
+Every so often, projects need to directly embed Linux system calls instead of
+calling the implementations in the system runtime library. This project
+provides a header file that can be included into your application whenever you
+need to make direct system calls.
+
+Local Modifications:
+None.
diff --git a/third_party/lss/lss.gyp b/third_party/lss/lss.gyp
new file mode 100644
index 0000000..63b9b3a
--- /dev/null
+++ b/third_party/lss/lss.gyp
@@ -0,0 +1,27 @@
+# Copyright 2019 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'lss',
+      'type': 'none',
+      'sources': [ 'lss.h' ],
+      'direct_dependent_settings': {
+        # gyp is only used in circumstances when the embedded lss is used.
+        'defines': [ 'CRASHPAD_LSS_SOURCE_EMBEDDED' ]
+      },
+    }
+  ]
+}
diff --git a/third_party/lss/lss.h b/third_party/lss/lss.h
new file mode 100644
index 0000000..11209ff
--- /dev/null
+++ b/third_party/lss/lss.h
@@ -0,0 +1,26 @@
+// Copyright 2019 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_THIRD_PARTY_LSS_LSS_H_
+#define CRASHPAD_THIRD_PARTY_LSS_LSS_H_
+
+#if defined(CRASHPAD_LSS_SOURCE_EXTERNAL)
+#include "third_party/lss/linux_syscall_support.h"
+#elif defined(CRASHPAD_LSS_SOURCE_EMBEDDED)
+#include "third_party/lss/lss/linux_syscall_support.h"
+#else
+#error Unknown lss source
+#endif
+
+#endif  // CRASHPAD_THIRD_PARTY_LSS_LSS_H_
diff --git a/third_party/mini_chromium/BUILD.gn b/third_party/mini_chromium/BUILD.gn
index e11d011..50d1513 100644
--- a/third_party/mini_chromium/BUILD.gn
+++ b/third_party/mini_chromium/BUILD.gn
@@ -16,13 +16,11 @@
 
 group("base") {
   if (crashpad_is_in_chromium) {
-    public_deps = [
-      "//base",
-    ]
+    public_deps = [ "//base" ]
   } else if (crashpad_is_standalone || crashpad_is_in_fuchsia) {
-    public_deps = [
-      "mini_chromium/base",
-    ]
+    public_deps = [ "mini_chromium/base" ]
+  } else if (crashpad_is_in_dart) {
+    public_deps = [ "//third_party/mini_chromium/mini_chromium/base" ]
   }
 }
 
@@ -30,8 +28,6 @@
   testonly = true
 
   if (crashpad_is_in_chromium) {
-    public_deps = [
-      "//base/test:test_support",
-    ]
+    public_deps = [ "//base/test:test_support" ]
   }
 }
diff --git a/third_party/xnu/APPLE_LICENSE b/third_party/xnu/APPLE_LICENSE
new file mode 100644
index 0000000..fe81a60
--- /dev/null
+++ b/third_party/xnu/APPLE_LICENSE
@@ -0,0 +1,367 @@
+APPLE PUBLIC SOURCE LICENSE
+Version 2.0 - August 6, 2003
+
+Please read this License carefully before downloading this software.
+By downloading or using this software, you are agreeing to be bound by
+the terms of this License. If you do not or cannot agree to the terms
+of this License, please do not download or use the software.
+
+1. General; Definitions. This License applies to any program or other
+work which Apple Computer, Inc. ("Apple") makes publicly available and
+which contains a notice placed by Apple identifying such program or
+work as "Original Code" and stating that it is subject to the terms of
+this Apple Public Source License version 2.0 ("License"). As used in
+this License:
+
+1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is
+the grantor of rights, (i) claims of patents that are now or hereafter
+acquired, owned by or assigned to Apple and (ii) that cover subject
+matter contained in the Original Code, but only to the extent
+necessary to use, reproduce and/or distribute the Original Code
+without infringement; and (b) in the case where You are the grantor of
+rights, (i) claims of patents that are now or hereafter acquired,
+owned by or assigned to You and (ii) that cover subject matter in Your
+Modifications, taken alone or in combination with Original Code.
+
+1.2 "Contributor" means any person or entity that creates or
+contributes to the creation of Modifications.
+
+1.3 "Covered Code" means the Original Code, Modifications, the
+combination of Original Code and any Modifications, and/or any
+respective portions thereof.
+
+1.4 "Externally Deploy" means: (a) to sublicense, distribute or
+otherwise make Covered Code available, directly or indirectly, to
+anyone other than You; and/or (b) to use Covered Code, alone or as
+part of a Larger Work, in any way to provide a service, including but
+not limited to delivery of content, through electronic communication
+with a client other than You.
+
+1.5 "Larger Work" means a work which combines Covered Code or portions
+thereof with code not governed by the terms of this License.
+
+1.6 "Modifications" mean any addition to, deletion from, and/or change
+to, the substance and/or structure of the Original Code, any previous
+Modifications, the combination of Original Code and any previous
+Modifications, and/or any respective portions thereof. When code is
+released as a series of files, a Modification is: (a) any addition to
+or deletion from the contents of a file containing Covered Code;
+and/or (b) any new file or other representation of computer program
+statements that contains any part of Covered Code.
+
+1.7 "Original Code" means (a) the Source Code of a program or other
+work as originally made available by Apple under this License,
+including the Source Code of any updates or upgrades to such programs
+or works made available by Apple under this License, and that has been
+expressly identified by Apple as such in the header file(s) of such
+work; and (b) the object code compiled from such Source Code and
+originally made available by Apple under this License.
+
+1.8 "Source Code" means the human readable form of a program or other
+work that is suitable for making modifications to it, including all
+modules it contains, plus any associated interface definition files,
+scripts used to control compilation and installation of an executable
+(object code).
+
+1.9 "You" or "Your" means an individual or a legal entity exercising
+rights under this License. For legal entities, "You" or "Your"
+includes any entity which controls, is controlled by, or is under
+common control with, You, where "control" means (a) the power, direct
+or indirect, to cause the direction or management of such entity,
+whether by contract or otherwise, or (b) ownership of fifty percent
+(50%) or more of the outstanding shares or beneficial ownership of
+such entity.
+
+2. Permitted Uses; Conditions & Restrictions. Subject to the terms
+and conditions of this License, Apple hereby grants You, effective on
+the date You accept this License and download the Original Code, a
+world-wide, royalty-free, non-exclusive license, to the extent of
+Apple's Applicable Patent Rights and copyrights covering the Original
+Code, to do the following:
+
+2.1 Unmodified Code. You may use, reproduce, display, perform,
+internally distribute within Your organization, and Externally Deploy
+verbatim, unmodified copies of the Original Code, for commercial or
+non-commercial purposes, provided that in each instance:
+
+(a) You must retain and reproduce in all copies of Original Code the
+copyright and other proprietary notices and disclaimers of Apple as
+they appear in the Original Code, and keep intact all notices in the
+Original Code that refer to this License; and
+
+(b) You must include a copy of this License with every copy of Source
+Code of Covered Code and documentation You distribute or Externally
+Deploy, and You may not offer or impose any terms on such Source Code
+that alter or restrict this License or the recipients' rights
+hereunder, except as permitted under Section 6.
+
+2.2 Modified Code. You may modify Covered Code and use, reproduce,
+display, perform, internally distribute within Your organization, and
+Externally Deploy Your Modifications and Covered Code, for commercial
+or non-commercial purposes, provided that in each instance You also
+meet all of these conditions:
+
+(a) You must satisfy all the conditions of Section 2.1 with respect to
+the Source Code of the Covered Code;
+
+(b) You must duplicate, to the extent it does not already exist, the
+notice in Exhibit A in each file of the Source Code of all Your
+Modifications, and cause the modified files to carry prominent notices
+stating that You changed the files and the date of any change; and
+
+(c) If You Externally Deploy Your Modifications, You must make
+Source Code of all Your Externally Deployed Modifications either
+available to those to whom You have Externally Deployed Your
+Modifications, or publicly available. Source Code of Your Externally
+Deployed Modifications must be released under the terms set forth in
+this License, including the license grants set forth in Section 3
+below, for as long as you Externally Deploy the Covered Code or twelve
+(12) months from the date of initial External Deployment, whichever is
+longer. You should preferably distribute the Source Code of Your
+Externally Deployed Modifications electronically (e.g. download from a
+web site).
+
+2.3 Distribution of Executable Versions. In addition, if You
+Externally Deploy Covered Code (Original Code and/or Modifications) in
+object code, executable form only, You must include a prominent
+notice, in the code itself as well as in related documentation,
+stating that Source Code of the Covered Code is available under the
+terms of this License with information on how and where to obtain such
+Source Code.
+
+2.4 Third Party Rights. You expressly acknowledge and agree that
+although Apple and each Contributor grants the licenses to their
+respective portions of the Covered Code set forth herein, no
+assurances are provided by Apple or any Contributor that the Covered
+Code does not infringe the patent or other intellectual property
+rights of any other entity. Apple and each Contributor disclaim any
+liability to You for claims brought by any other entity based on
+infringement of intellectual property rights or otherwise. As a
+condition to exercising the rights and licenses granted hereunder, You
+hereby assume sole responsibility to secure any other intellectual
+property rights needed, if any. For example, if a third party patent
+license is required to allow You to distribute the Covered Code, it is
+Your responsibility to acquire that license before distributing the
+Covered Code.
+
+3. Your Grants. In consideration of, and as a condition to, the
+licenses granted to You under this License, You hereby grant to any
+person or entity receiving or distributing Covered Code under this
+License a non-exclusive, royalty-free, perpetual, irrevocable license,
+under Your Applicable Patent Rights and other intellectual property
+rights (other than patent) owned or controlled by You, to use,
+reproduce, display, perform, modify, sublicense, distribute and
+Externally Deploy Your Modifications of the same scope and extent as
+Apple's licenses under Sections 2.1 and 2.2 above.
+
+4. Larger Works. You may create a Larger Work by combining Covered
+Code with other code not governed by the terms of this License and
+distribute the Larger Work as a single product. In each such instance,
+You must make sure the requirements of this License are fulfilled for
+the Covered Code or any portion thereof.
+
+5. Limitations on Patent License. Except as expressly stated in
+Section 2, no other patent rights, express or implied, are granted by
+Apple herein. Modifications and/or Larger Works may require additional
+patent licenses from Apple which Apple may grant in its sole
+discretion.
+
+6. Additional Terms. You may choose to offer, and to charge a fee for,
+warranty, support, indemnity or liability obligations and/or other
+rights consistent with the scope of the license granted herein
+("Additional Terms") to one or more recipients of Covered Code.
+However, You may do so only on Your own behalf and as Your sole
+responsibility, and not on behalf of Apple or any Contributor. You
+must obtain the recipient's agreement that any such Additional Terms
+are offered by You alone, and You hereby agree to indemnify, defend
+and hold Apple and every Contributor harmless for any liability
+incurred by or claims asserted against Apple or such Contributor by
+reason of any such Additional Terms.
+
+7. Versions of the License. Apple may publish revised and/or new
+versions of this License from time to time. Each version will be given
+a distinguishing version number. Once Original Code has been published
+under a particular version of this License, You may continue to use it
+under the terms of that version. You may also choose to use such
+Original Code under the terms of any subsequent version of this
+License published by Apple. No one other than Apple has the right to
+modify the terms applicable to Covered Code created under this
+License.
+
+8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in
+part pre-release, untested, or not fully tested works. The Covered
+Code may contain errors that could cause failures or loss of data, and
+may be incomplete or contain inaccuracies. You expressly acknowledge
+and agree that use of the Covered Code, or any portion thereof, is at
+Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND
+WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND
+APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE
+PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM
+ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF
+MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR
+PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD
+PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST
+INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE
+FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS,
+THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR
+ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO
+ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE
+AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY.
+You acknowledge that the Covered Code is not intended for use in the
+operation of nuclear facilities, aircraft navigation, communication
+systems, or air traffic control machines in which case the failure of
+the Covered Code could lead to death, personal injury, or severe
+physical or environmental damage.
+
+9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO
+EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL,
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING
+TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR
+ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY,
+TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF
+APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY
+REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF
+INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY
+TO YOU. In no event shall Apple's total liability to You for all
+damages (other than as may be required by applicable law) under this
+License exceed the amount of fifty dollars ($50.00).
+
+10. Trademarks. This License does not grant any rights to use the
+trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS",
+"QuickTime", "QuickTime Streaming Server" or any other trademarks,
+service marks, logos or trade names belonging to Apple (collectively
+"Apple Marks") or to any trademark, service mark, logo or trade name
+belonging to any Contributor. You agree not to use any Apple Marks in
+or as part of the name of products derived from the Original Code or
+to endorse or promote products derived from the Original Code other
+than as expressly permitted by and in strict compliance at all times
+with Apple's third party trademark usage guidelines which are posted
+at http://www.apple.com/legal/guidelinesfor3rdparties.html.
+
+11. Ownership. Subject to the licenses granted under this License,
+each Contributor retains all rights, title and interest in and to any
+Modifications made by such Contributor. Apple retains all rights,
+title and interest in and to the Original Code and any Modifications
+made by or on behalf of Apple ("Apple Modifications"), and such Apple
+Modifications will not be automatically subject to this License. Apple
+may, at its sole discretion, choose to license such Apple
+Modifications under this License, or on different terms from those
+contained in this License or may choose not to license them at all.
+
+12. Termination.
+
+12.1 Termination. This License and the rights granted hereunder will
+terminate:
+
+(a) automatically without notice from Apple if You fail to comply with
+any term(s) of this License and fail to cure such breach within 30
+days of becoming aware of such breach;
+
+(b) immediately in the event of the circumstances described in Section
+13.5(b); or
+
+(c) automatically without notice from Apple if You, at any time during
+the term of this License, commence an action for patent infringement
+against Apple; provided that Apple did not first commence
+an action for patent infringement against You in that instance.
+
+12.2 Effect of Termination. Upon termination, You agree to immediately
+stop any further use, reproduction, modification, sublicensing and
+distribution of the Covered Code. All sublicenses to the Covered Code
+which have been properly granted prior to termination shall survive
+any termination of this License. Provisions which, by their nature,
+should remain in effect beyond the termination of this License shall
+survive, including but not limited to Sections 3, 5, 8, 9, 10, 11,
+12.2 and 13. No party will be liable to any other for compensation,
+indemnity or damages of any sort solely as a result of terminating
+this License in accordance with its terms, and termination of this
+License will be without prejudice to any other right or remedy of
+any party.
+
+13. Miscellaneous.
+
+13.1 Government End Users. The Covered Code is a "commercial item" as
+defined in FAR 2.101. Government software and technical data rights in
+the Covered Code include only those rights customarily provided to the
+public as defined in this License. This customary commercial license
+in technical data and software is provided in accordance with FAR
+12.211 (Technical Data) and 12.212 (Computer Software) and, for
+Department of Defense purchases, DFAR 252.227-7015 (Technical Data --
+Commercial Items) and 227.7202-3 (Rights in Commercial Computer
+Software or Computer Software Documentation). Accordingly, all U.S.
+Government End Users acquire Covered Code with only those rights set
+forth herein.
+
+13.2 Relationship of Parties. This License will not be construed as
+creating an agency, partnership, joint venture or any other form of
+legal association between or among You, Apple or any Contributor, and
+You will not represent to the contrary, whether expressly, by
+implication, appearance or otherwise.
+
+13.3 Independent Development. Nothing in this License will impair
+Apple's right to acquire, license, develop, have others develop for
+it, market and/or distribute technology or products that perform the
+same or similar functions as, or otherwise compete with,
+Modifications, Larger Works, technology or products that You may
+develop, produce, market or distribute.
+
+13.4 Waiver; Construction. Failure by Apple or any Contributor to
+enforce any provision of this License will not be deemed a waiver of
+future enforcement of that or any other provision. Any law or
+regulation which provides that the language of a contract shall be
+construed against the drafter will not apply to this License.
+
+13.5 Severability. (a) If for any reason a court of competent
+jurisdiction finds any provision of this License, or portion thereof,
+to be unenforceable, that provision of the License will be enforced to
+the maximum extent permissible so as to effect the economic benefits
+and intent of the parties, and the remainder of this License will
+continue in full force and effect. (b) Notwithstanding the foregoing,
+if applicable law prohibits or restricts You from fully and/or
+specifically complying with Sections 2 and/or 3 or prevents the
+enforceability of either of those Sections, this License will
+immediately terminate and You must immediately discontinue any use of
+the Covered Code and destroy all copies of it that are in your
+possession or control.
+
+13.6 Dispute Resolution. Any litigation or other dispute resolution
+between You and Apple relating to this License shall take place in the
+Northern District of California, and You and Apple hereby consent to
+the personal jurisdiction of, and venue in, the state and federal
+courts within that District with respect to this License. The
+application of the United Nations Convention on Contracts for the
+International Sale of Goods is expressly excluded.
+
+13.7 Entire Agreement; Governing Law. This License constitutes the
+entire agreement between the parties with respect to the subject
+matter hereof. This License shall be governed by the laws of the
+United States and the State of California, except that body of
+California law concerning conflicts of law.
+
+Where You are located in the province of Quebec, Canada, the following
+clause applies: The parties hereby confirm that they have requested
+that this License and all related documents be drafted in English. Les
+parties ont exige que le present contrat et tous les documents
+connexes soient rediges en anglais.
+
+EXHIBIT A.
+
+"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
+Reserved.
+
+This file contains Original Code and/or Modifications of Original Code
+as defined in and that are subject to the Apple Public Source License
+Version 2.0 (the 'License'). You may not use this file except in
+compliance with the License. Please obtain a copy of the License at
+http://www.opensource.apple.com/apsl/ and read it before using this
+file.
+
+The Original Code and all software distributed under the License are
+distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+Please see the License for the specific language governing rights and
+limitations under the License."
diff --git a/third_party/xnu/BUILD.gn b/third_party/xnu/BUILD.gn
new file mode 100644
index 0000000..2079ecf
--- /dev/null
+++ b/third_party/xnu/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2019 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.
+
+source_set("xnu") {
+  sources = [ "EXTERNAL_HEADERS/mach-o/loader.h" ]
+}
diff --git a/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h b/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h
new file mode 100644
index 0000000..72e8c39
--- /dev/null
+++ b/third_party/xnu/EXTERNAL_HEADERS/mach-o/loader.h
@@ -0,0 +1,1574 @@
+/*
+ * Copyright (c) 1999-2010 Apple Inc.  All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+#ifndef _MACHO_LOADER_H_
+#define _MACHO_LOADER_H_
+
+/*
+ * This file describes the format of mach object files.
+ */
+#include <stdint.h>
+
+/*
+ * <mach/machine.h> is needed here for the cpu_type_t and cpu_subtype_t types
+ * and contains the constants for the possible values of these types.
+ */
+#include <mach/machine.h>
+
+/*
+ * <mach/vm_prot.h> is needed here for the vm_prot_t type and contains the 
+ * constants that are or'ed together for the possible values of this type.
+ */
+#include <mach/vm_prot.h>
+
+/*
+ * <machine/thread_status.h> is expected to define the flavors of the thread
+ * states and the structures of those flavors for each machine.
+#include <mach/machine/thread_status.h>
+#include <architecture/byte_order.h>
+ */
+
+/*
+ * The 32-bit mach header appears at the very beginning of the object file for
+ * 32-bit architectures.
+ */
+struct mach_header {
+	uint32_t	magic;		/* mach magic number identifier */
+	cpu_type_t	cputype;	/* cpu specifier */
+	cpu_subtype_t	cpusubtype;	/* machine specifier */
+	uint32_t	filetype;	/* type of file */
+	uint32_t	ncmds;		/* number of load commands */
+	uint32_t	sizeofcmds;	/* the size of all the load commands */
+	uint32_t	flags;		/* flags */
+};
+
+/* Constant for the magic field of the mach_header (32-bit architectures) */
+#define	MH_MAGIC	0xfeedface	/* the mach magic number */
+#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */
+
+/*
+ * The 64-bit mach header appears at the very beginning of object files for
+ * 64-bit architectures.
+ */
+struct mach_header_64 {
+	uint32_t	magic;		/* mach magic number identifier */
+	cpu_type_t	cputype;	/* cpu specifier */
+	cpu_subtype_t	cpusubtype;	/* machine specifier */
+	uint32_t	filetype;	/* type of file */
+	uint32_t	ncmds;		/* number of load commands */
+	uint32_t	sizeofcmds;	/* the size of all the load commands */
+	uint32_t	flags;		/* flags */
+	uint32_t	reserved;	/* reserved */
+};
+
+/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
+#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
+#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
+
+/*
+ * The layout of the file depends on the filetype.  For all but the MH_OBJECT
+ * file type the segments are padded out and aligned on a segment alignment
+ * boundary for efficient demand pageing.  The MH_EXECUTE, MH_FVMLIB, MH_DYLIB,
+ * MH_DYLINKER and MH_BUNDLE file types also have the headers included as part
+ * of their first segment.
+ * 
+ * The file type MH_OBJECT is a compact format intended as output of the
+ * assembler and input (and possibly output) of the link editor (the .o
+ * format).  All sections are in one unnamed segment with no segment padding. 
+ * This format is used as an executable format when the file is so small the
+ * segment padding greatly increases its size.
+ *
+ * The file type MH_PRELOAD is an executable format intended for things that
+ * are not executed under the kernel (proms, stand alones, kernels, etc).  The
+ * format can be executed under the kernel but may demand paged it and not
+ * preload it before execution.
+ *
+ * A core file is in MH_CORE format and can be any in an arbritray legal
+ * Mach-O file.
+ *
+ * Constants for the filetype field of the mach_header
+ */
+#define	MH_OBJECT	0x1		/* relocatable object file */
+#define	MH_EXECUTE	0x2		/* demand paged executable file */
+#define	MH_FVMLIB	0x3		/* fixed VM shared library file */
+#define	MH_CORE		0x4		/* core file */
+#define	MH_PRELOAD	0x5		/* preloaded executable file */
+#define	MH_DYLIB	0x6		/* dynamically bound shared library */
+#define	MH_DYLINKER	0x7		/* dynamic link editor */
+#define	MH_BUNDLE	0x8		/* dynamically bound bundle file */
+#define	MH_DYLIB_STUB	0x9		/* shared library stub for static */
+					/*  linking only, no section contents */
+#define	MH_DSYM		0xa		/* companion file with only debug */
+					/*  sections */
+#define	MH_KEXT_BUNDLE	0xb		/* x86_64 kexts */
+
+/* Constants for the flags field of the mach_header */
+#define	MH_NOUNDEFS	0x1		/* the object file has no undefined
+					   references */
+#define	MH_INCRLINK	0x2		/* the object file is the output of an
+					   incremental link against a base file
+					   and can't be link edited again */
+#define MH_DYLDLINK	0x4		/* the object file is input for the
+					   dynamic linker and can't be staticly
+					   link edited again */
+#define MH_BINDATLOAD	0x8		/* the object file's undefined
+					   references are bound by the dynamic
+					   linker when loaded. */
+#define MH_PREBOUND	0x10		/* the file has its dynamic undefined
+					   references prebound. */
+#define MH_SPLIT_SEGS	0x20		/* the file has its read-only and
+					   read-write segments split */
+#define MH_LAZY_INIT	0x40		/* the shared library init routine is
+					   to be run lazily via catching memory
+					   faults to its writeable segments
+					   (obsolete) */
+#define MH_TWOLEVEL	0x80		/* the image is using two-level name
+					   space bindings */
+#define MH_FORCE_FLAT	0x100		/* the executable is forcing all images
+					   to use flat name space bindings */
+#define MH_NOMULTIDEFS	0x200		/* this umbrella guarantees no multiple
+					   defintions of symbols in its
+					   sub-images so the two-level namespace
+					   hints can always be used. */
+#define MH_NOFIXPREBINDING 0x400	/* do not have dyld notify the
+					   prebinding agent about this
+					   executable */
+#define MH_PREBINDABLE  0x800           /* the binary is not prebound but can
+					   have its prebinding redone. only used
+                                           when MH_PREBOUND is not set. */
+#define MH_ALLMODSBOUND 0x1000		/* indicates that this binary binds to
+                                           all two-level namespace modules of
+					   its dependent libraries. only used
+					   when MH_PREBINDABLE and MH_TWOLEVEL
+					   are both set. */ 
+#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000/* safe to divide up the sections into
+					    sub-sections via symbols for dead
+					    code stripping */
+#define MH_CANONICAL    0x4000		/* the binary has been canonicalized
+					   via the unprebind operation */
+#define MH_WEAK_DEFINES	0x8000		/* the final linked image contains
+					   external weak symbols */
+#define MH_BINDS_TO_WEAK 0x10000	/* the final linked image uses
+					   weak symbols */
+
+#define MH_ALLOW_STACK_EXECUTION 0x20000/* When this bit is set, all stacks 
+					   in the task will be given stack
+					   execution privilege.  Only used in
+					   MH_EXECUTE filetypes. */
+#define MH_ROOT_SAFE 0x40000           /* When this bit is set, the binary 
+					  declares it is safe for use in
+					  processes with uid zero */
+                                         
+#define MH_SETUID_SAFE 0x80000         /* When this bit is set, the binary 
+					  declares it is safe for use in
+					  processes when issetugid() is true */
+
+#define MH_NO_REEXPORTED_DYLIBS 0x100000 /* When this bit is set on a dylib, 
+					  the static linker does not need to
+					  examine dependent dylibs to see
+					  if any are re-exported */
+#define	MH_PIE 0x200000			/* When this bit is set, the OS will
+					   load the main executable at a
+					   random address.  Only used in
+					   MH_EXECUTE filetypes. */
+#define	MH_DEAD_STRIPPABLE_DYLIB 0x400000 /* Only for use on dylibs.  When
+					     linking against a dylib that
+					     has this bit set, the static linker
+					     will automatically not create a
+					     LC_LOAD_DYLIB load command to the
+					     dylib if no symbols are being
+					     referenced from the dylib. */
+#define MH_HAS_TLV_DESCRIPTORS 0x800000 /* Contains a section of type 
+					    S_THREAD_LOCAL_VARIABLES */
+
+#define MH_NO_HEAP_EXECUTION 0x1000000	/* When this bit is set, the OS will
+					   run the main executable with
+					   a non-executable heap even on
+					   platforms (e.g. i386) that don't
+					   require it. Only used in MH_EXECUTE
+					   filetypes. */
+
+#define MH_APP_EXTENSION_SAFE 0x02000000 /* The code was linked for use in an
+					    application extension. */
+
+#define	MH_NLIST_OUTOFSYNC_WITH_DYLDINFO 0x04000000 /* The external symbols
+					   listed in the nlist symbol table do
+					   not include all the symbols listed in
+					   the dyld info. */
+
+#define	MH_SIM_SUPPORT 0x08000000	/* Allow LC_MIN_VERSION_MACOS and
+					   LC_BUILD_VERSION load commands with
+					   the platforms macOS, iOSMac,
+					   iOSSimulator, tvOSSimulator and
+					   watchOSSimulator. */
+
+#define MH_DYLIB_IN_CACHE 0x80000000	/* Only for use on dylibs. When this bit
+					   is set, the dylib is part of the dyld
+					   shared cache, rather than loose in
+					   the filesystem. */
+
+/*
+ * The load commands directly follow the mach_header.  The total size of all
+ * of the commands is given by the sizeofcmds field in the mach_header.  All
+ * load commands must have as their first two fields cmd and cmdsize.  The cmd
+ * field is filled in with a constant for that command type.  Each command type
+ * has a structure specifically for it.  The cmdsize field is the size in bytes
+ * of the particular load command structure plus anything that follows it that
+ * is a part of the load command (i.e. section structures, strings, etc.).  To
+ * advance to the next load command the cmdsize can be added to the offset or
+ * pointer of the current load command.  The cmdsize for 32-bit architectures
+ * MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
+ * of 8 bytes (these are forever the maximum alignment of any load commands).
+ * The padded bytes must be zero.  All tables in the object file must also
+ * follow these rules so the file can be memory mapped.  Otherwise the pointers
+ * to these tables will not work well or at all on some machines.  With all
+ * padding zeroed like objects will compare byte for byte.
+ */
+struct load_command {
+	uint32_t cmd;		/* type of load command */
+	uint32_t cmdsize;	/* total size of command in bytes */
+};
+
+/*
+ * After MacOS X 10.1 when a new load command is added that is required to be
+ * understood by the dynamic linker for the image to execute properly the
+ * LC_REQ_DYLD bit will be or'ed into the load command constant.  If the dynamic
+ * linker sees such a load command it it does not understand will issue a
+ * "unknown load command required for execution" error and refuse to use the
+ * image.  Other load commands without this bit that are not understood will
+ * simply be ignored.
+ */
+#define LC_REQ_DYLD 0x80000000
+
+/* Constants for the cmd field of all load commands, the type */
+#define	LC_SEGMENT	0x1	/* segment of this file to be mapped */
+#define	LC_SYMTAB	0x2	/* link-edit stab symbol table info */
+#define	LC_SYMSEG	0x3	/* link-edit gdb symbol table info (obsolete) */
+#define	LC_THREAD	0x4	/* thread */
+#define	LC_UNIXTHREAD	0x5	/* unix thread (includes a stack) */
+#define	LC_LOADFVMLIB	0x6	/* load a specified fixed VM shared library */
+#define	LC_IDFVMLIB	0x7	/* fixed VM shared library identification */
+#define	LC_IDENT	0x8	/* object identification info (obsolete) */
+#define LC_FVMFILE	0x9	/* fixed VM file inclusion (internal use) */
+#define LC_PREPAGE      0xa     /* prepage command (internal use) */
+#define	LC_DYSYMTAB	0xb	/* dynamic link-edit symbol table info */
+#define	LC_LOAD_DYLIB	0xc	/* load a dynamically linked shared library */
+#define	LC_ID_DYLIB	0xd	/* dynamically linked shared lib ident */
+#define LC_LOAD_DYLINKER 0xe	/* load a dynamic linker */
+#define LC_ID_DYLINKER	0xf	/* dynamic linker identification */
+#define	LC_PREBOUND_DYLIB 0x10	/* modules prebound for a dynamically */
+				/*  linked shared library */
+#define	LC_ROUTINES	0x11	/* image routines */
+#define	LC_SUB_FRAMEWORK 0x12	/* sub framework */
+#define	LC_SUB_UMBRELLA 0x13	/* sub umbrella */
+#define	LC_SUB_CLIENT	0x14	/* sub client */
+#define	LC_SUB_LIBRARY  0x15	/* sub library */
+#define	LC_TWOLEVEL_HINTS 0x16	/* two-level namespace lookup hints */
+#define	LC_PREBIND_CKSUM  0x17	/* prebind checksum */
+
+/*
+ * load a dynamically linked shared library that is allowed to be missing
+ * (all symbols are weak imported).
+ */
+#define	LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)
+
+#define	LC_SEGMENT_64	0x19	/* 64-bit segment of this file to be
+				   mapped */
+#define	LC_ROUTINES_64	0x1a	/* 64-bit image routines */
+#define LC_UUID		0x1b	/* the uuid */
+#define LC_RPATH       (0x1c | LC_REQ_DYLD)    /* runpath additions */
+#define LC_CODE_SIGNATURE 0x1d	/* local of code signature */
+#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
+#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
+#define	LC_LAZY_LOAD_DYLIB 0x20	/* delay load of dylib until first use */
+#define	LC_ENCRYPTION_INFO 0x21	/* encrypted segment information */
+#define	LC_DYLD_INFO 	0x22	/* compressed dyld information */
+#define	LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD)	/* compressed dyld information only */
+#define	LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
+#define LC_VERSION_MIN_MACOSX 0x24   /* build for MacOSX min OS version */
+#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
+#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
+#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat
+				    like environment variable */
+#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
+#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
+#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
+#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
+#define	LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
+#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
+#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
+#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
+#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
+#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
+#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */
+#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */
+#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */
+
+/*
+ * A variable length string in a load command is represented by an lc_str
+ * union.  The strings are stored just after the load command structure and
+ * the offset is from the start of the load command structure.  The size
+ * of the string is reflected in the cmdsize field of the load command.
+ * Once again any padded bytes to bring the cmdsize field to a multiple
+ * of 4 bytes must be zero.
+ */
+union lc_str {
+	uint32_t	offset;	/* offset to the string */
+#ifndef __LP64__
+	char		*ptr;	/* pointer to the string */
+#endif 
+};
+
+/*
+ * The segment load command indicates that a part of this file is to be
+ * mapped into the task's address space.  The size of this segment in memory,
+ * vmsize, maybe equal to or larger than the amount to map from this file,
+ * filesize.  The file is mapped starting at fileoff to the beginning of
+ * the segment in memory, vmaddr.  The rest of the memory of the segment,
+ * if any, is allocated zero fill on demand.  The segment's maximum virtual
+ * memory protection and initial virtual memory protection are specified
+ * by the maxprot and initprot fields.  If the segment has sections then the
+ * section structures directly follow the segment command and their size is
+ * reflected in cmdsize.
+ */
+struct segment_command { /* for 32-bit architectures */
+	uint32_t	cmd;		/* LC_SEGMENT */
+	uint32_t	cmdsize;	/* includes sizeof section structs */
+	char		segname[16];	/* segment name */
+	uint32_t	vmaddr;		/* memory address of this segment */
+	uint32_t	vmsize;		/* memory size of this segment */
+	uint32_t	fileoff;	/* file offset of this segment */
+	uint32_t	filesize;	/* amount to map from the file */
+	vm_prot_t	maxprot;	/* maximum VM protection */
+	vm_prot_t	initprot;	/* initial VM protection */
+	uint32_t	nsects;		/* number of sections in segment */
+	uint32_t	flags;		/* flags */
+};
+
+/*
+ * The 64-bit segment load command indicates that a part of this file is to be
+ * mapped into a 64-bit task's address space.  If the 64-bit segment has
+ * sections then section_64 structures directly follow the 64-bit segment
+ * command and their size is reflected in cmdsize.
+ */
+struct segment_command_64 { /* for 64-bit architectures */
+	uint32_t	cmd;		/* LC_SEGMENT_64 */
+	uint32_t	cmdsize;	/* includes sizeof section_64 structs */
+	char		segname[16];	/* segment name */
+	uint64_t	vmaddr;		/* memory address of this segment */
+	uint64_t	vmsize;		/* memory size of this segment */
+	uint64_t	fileoff;	/* file offset of this segment */
+	uint64_t	filesize;	/* amount to map from the file */
+	vm_prot_t	maxprot;	/* maximum VM protection */
+	vm_prot_t	initprot;	/* initial VM protection */
+	uint32_t	nsects;		/* number of sections in segment */
+	uint32_t	flags;		/* flags */
+};
+
+/* Constants for the flags field of the segment_command */
+#define	SG_HIGHVM	0x1	/* the file contents for this segment is for
+				   the high part of the VM space, the low part
+				   is zero filled (for stacks in core files) */
+#define	SG_FVMLIB	0x2	/* this segment is the VM that is allocated by
+				   a fixed VM library, for overlap checking in
+				   the link editor */
+#define	SG_NORELOC	0x4	/* this segment has nothing that was relocated
+				   in it and nothing relocated to it, that is
+				   it maybe safely replaced without relocation*/
+#define SG_PROTECTED_VERSION_1	0x8 /* This segment is protected.  If the
+				       segment starts at file offset 0, the
+				       first page of the segment is not
+				       protected.  All other pages of the
+				       segment are protected. */
+#define SG_READ_ONLY    0x10 /* This segment is made read-only after fixups */
+
+
+
+/*
+ * A segment is made up of zero or more sections.  Non-MH_OBJECT files have
+ * all of their segments with the proper sections in each, and padded to the
+ * specified segment alignment when produced by the link editor.  The first
+ * segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header
+ * and load commands of the object file before its first section.  The zero
+ * fill sections are always last in their segment (in all formats).  This
+ * allows the zeroed segment padding to be mapped into memory where zero fill
+ * sections might be. The gigabyte zero fill sections, those with the section
+ * type S_GB_ZEROFILL, can only be in a segment with sections of this type.
+ * These segments are then placed after all other segments.
+ *
+ * The MH_OBJECT format has all of its sections in one segment for
+ * compactness.  There is no padding to a specified segment boundary and the
+ * mach_header and load commands are not part of the segment.
+ *
+ * Sections with the same section name, sectname, going into the same segment,
+ * segname, are combined by the link editor.  The resulting section is aligned
+ * to the maximum alignment of the combined sections and is the new section's
+ * alignment.  The combined sections are aligned to their original alignment in
+ * the combined section.  Any padded bytes to get the specified alignment are
+ * zeroed.
+ *
+ * The format of the relocation entries referenced by the reloff and nreloc
+ * fields of the section structure for mach object files is described in the
+ * header file <reloc.h>.
+ */
+struct section { /* for 32-bit architectures */
+	char		sectname[16];	/* name of this section */
+	char		segname[16];	/* segment this section goes in */
+	uint32_t	addr;		/* memory address of this section */
+	uint32_t	size;		/* size in bytes of this section */
+	uint32_t	offset;		/* file offset of this section */
+	uint32_t	align;		/* section alignment (power of 2) */
+	uint32_t	reloff;		/* file offset of relocation entries */
+	uint32_t	nreloc;		/* number of relocation entries */
+	uint32_t	flags;		/* flags (section type and attributes)*/
+	uint32_t	reserved1;	/* reserved (for offset or index) */
+	uint32_t	reserved2;	/* reserved (for count or sizeof) */
+};
+
+struct section_64 { /* for 64-bit architectures */
+	char		sectname[16];	/* name of this section */
+	char		segname[16];	/* segment this section goes in */
+	uint64_t	addr;		/* memory address of this section */
+	uint64_t	size;		/* size in bytes of this section */
+	uint32_t	offset;		/* file offset of this section */
+	uint32_t	align;		/* section alignment (power of 2) */
+	uint32_t	reloff;		/* file offset of relocation entries */
+	uint32_t	nreloc;		/* number of relocation entries */
+	uint32_t	flags;		/* flags (section type and attributes)*/
+	uint32_t	reserved1;	/* reserved (for offset or index) */
+	uint32_t	reserved2;	/* reserved (for count or sizeof) */
+	uint32_t	reserved3;	/* reserved */
+};
+
+/*
+ * The flags field of a section structure is separated into two parts a section
+ * type and section attributes.  The section types are mutually exclusive (it
+ * can only have one type) but the section attributes are not (it may have more
+ * than one attribute).
+ */
+#define SECTION_TYPE		 0x000000ff	/* 256 section types */
+#define SECTION_ATTRIBUTES	 0xffffff00	/*  24 section attributes */
+
+/* Constants for the type of a section */
+#define	S_REGULAR		0x0	/* regular section */
+#define	S_ZEROFILL		0x1	/* zero fill on demand section */
+#define	S_CSTRING_LITERALS	0x2	/* section with only literal C strings*/
+#define	S_4BYTE_LITERALS	0x3	/* section with only 4 byte literals */
+#define	S_8BYTE_LITERALS	0x4	/* section with only 8 byte literals */
+#define	S_LITERAL_POINTERS	0x5	/* section with only pointers to */
+					/*  literals */
+/*
+ * For the two types of symbol pointers sections and the symbol stubs section
+ * they have indirect symbol table entries.  For each of the entries in the
+ * section the indirect symbol table entries, in corresponding order in the
+ * indirect symbol table, start at the index stored in the reserved1 field
+ * of the section structure.  Since the indirect symbol table entries
+ * correspond to the entries in the section the number of indirect symbol table
+ * entries is inferred from the size of the section divided by the size of the
+ * entries in the section.  For symbol pointers sections the size of the entries
+ * in the section is 4 bytes and for symbol stubs sections the byte size of the
+ * stubs is stored in the reserved2 field of the section structure.
+ */
+#define	S_NON_LAZY_SYMBOL_POINTERS	0x6	/* section with only non-lazy
+						   symbol pointers */
+#define	S_LAZY_SYMBOL_POINTERS		0x7	/* section with only lazy symbol
+						   pointers */
+#define	S_SYMBOL_STUBS			0x8	/* section with only symbol
+						   stubs, byte size of stub in
+						   the reserved2 field */
+#define	S_MOD_INIT_FUNC_POINTERS	0x9	/* section with only function
+						   pointers for initialization*/
+#define	S_MOD_TERM_FUNC_POINTERS	0xa	/* section with only function
+						   pointers for termination */
+#define	S_COALESCED			0xb	/* section contains symbols that
+						   are to be coalesced */
+#define	S_GB_ZEROFILL			0xc	/* zero fill on demand section
+						   (that can be larger than 4
+						   gigabytes) */
+#define	S_INTERPOSING			0xd	/* section with only pairs of
+						   function pointers for
+						   interposing */
+#define	S_16BYTE_LITERALS		0xe	/* section with only 16 byte
+						   literals */
+#define	S_DTRACE_DOF			0xf	/* section contains 
+						   DTrace Object Format */
+#define	S_LAZY_DYLIB_SYMBOL_POINTERS	0x10	/* section with only lazy
+						   symbol pointers to lazy
+						   loaded dylibs */
+/*
+ * Section types to support thread local variables
+ */
+#define S_THREAD_LOCAL_REGULAR                   0x11  /* template of initial 
+							  values for TLVs */
+#define S_THREAD_LOCAL_ZEROFILL                  0x12  /* template of initial 
+							  values for TLVs */
+#define S_THREAD_LOCAL_VARIABLES                 0x13  /* TLV descriptors */
+#define S_THREAD_LOCAL_VARIABLE_POINTERS         0x14  /* pointers to TLV 
+                                                          descriptors */
+#define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS    0x15  /* functions to call
+							  to initialize TLV
+							  values */
+#define S_INIT_FUNC_OFFSETS                      0x16  /* 32-bit offsets to
+							  initializers */
+
+/*
+ * Constants for the section attributes part of the flags field of a section
+ * structure.
+ */
+#define SECTION_ATTRIBUTES_USR	 0xff000000	/* User setable attributes */
+#define S_ATTR_PURE_INSTRUCTIONS 0x80000000	/* section contains only true
+						   machine instructions */
+#define S_ATTR_NO_TOC 		 0x40000000	/* section contains coalesced
+						   symbols that are not to be
+						   in a ranlib table of
+						   contents */
+#define S_ATTR_STRIP_STATIC_SYMS 0x20000000	/* ok to strip static symbols
+						   in this section in files
+						   with the MH_DYLDLINK flag */
+#define S_ATTR_NO_DEAD_STRIP	 0x10000000	/* no dead stripping */
+#define S_ATTR_LIVE_SUPPORT	 0x08000000	/* blocks are live if they
+						   reference live blocks */
+#define S_ATTR_SELF_MODIFYING_CODE 0x04000000	/* Used with i386 code stubs
+						   written on by dyld */
+/*
+ * If a segment contains any sections marked with S_ATTR_DEBUG then all
+ * sections in that segment must have this attribute.  No section other than
+ * a section marked with this attribute may reference the contents of this
+ * section.  A section with this attribute may contain no symbols and must have
+ * a section type S_REGULAR.  The static linker will not copy section contents
+ * from sections with this attribute into its output file.  These sections
+ * generally contain DWARF debugging info.
+ */ 
+#define	S_ATTR_DEBUG		 0x02000000	/* a debug section */
+#define SECTION_ATTRIBUTES_SYS	 0x00ffff00	/* system setable attributes */
+#define S_ATTR_SOME_INSTRUCTIONS 0x00000400	/* section contains some
+						   machine instructions */
+#define S_ATTR_EXT_RELOC	 0x00000200	/* section has external
+						   relocation entries */
+#define S_ATTR_LOC_RELOC	 0x00000100	/* section has local
+						   relocation entries */
+
+
+/*
+ * The names of segments and sections in them are mostly meaningless to the
+ * link-editor.  But there are few things to support traditional UNIX
+ * executables that require the link-editor and assembler to use some names
+ * agreed upon by convention.
+ *
+ * The initial protection of the "__TEXT" segment has write protection turned
+ * off (not writeable).
+ *
+ * The link-editor will allocate common symbols at the end of the "__common"
+ * section in the "__DATA" segment.  It will create the section and segment
+ * if needed.
+ */
+
+/* The currently known segment names and the section names in those segments */
+
+#define	SEG_PAGEZERO	"__PAGEZERO"	/* the pagezero segment which has no */
+					/* protections and catches NULL */
+					/* references for MH_EXECUTE files */
+
+
+#define	SEG_TEXT	"__TEXT"	/* the tradition UNIX text segment */
+#define	SECT_TEXT	"__text"	/* the real text part of the text */
+					/* section no headers, and no padding */
+#define SECT_FVMLIB_INIT0 "__fvmlib_init0"	/* the fvmlib initialization */
+						/*  section */
+#define SECT_FVMLIB_INIT1 "__fvmlib_init1"	/* the section following the */
+					        /*  fvmlib initialization */
+						/*  section */
+
+#define	SEG_DATA	"__DATA"	/* the tradition UNIX data segment */
+#define	SECT_DATA	"__data"	/* the real initialized data section */
+					/* no padding, no bss overlap */
+#define	SECT_BSS	"__bss"		/* the real uninitialized data section*/
+					/* no padding */
+#define SECT_COMMON	"__common"	/* the section common symbols are */
+					/* allocated in by the link editor */
+
+#define	SEG_OBJC	"__OBJC"	/* objective-C runtime segment */
+#define SECT_OBJC_SYMBOLS "__symbol_table"	/* symbol table */
+#define SECT_OBJC_MODULES "__module_info"	/* module information */
+#define SECT_OBJC_STRINGS "__selector_strs"	/* string table */
+#define SECT_OBJC_REFS "__selector_refs"	/* string table */
+
+#define	SEG_ICON	 "__ICON"	/* the icon segment */
+#define	SECT_ICON_HEADER "__header"	/* the icon headers */
+#define	SECT_ICON_TIFF   "__tiff"	/* the icons in tiff format */
+
+#define	SEG_LINKEDIT	"__LINKEDIT"	/* the segment containing all structs */
+					/* created and maintained by the link */
+					/* editor.  Created with -seglinkedit */
+					/* option to ld(1) for MH_EXECUTE and */
+					/* FVMLIB file types only */
+
+#define SEG_UNIXSTACK	"__UNIXSTACK"	/* the unix stack segment */
+
+#define SEG_IMPORT	"__IMPORT"	/* the segment for the self (dyld) */
+					/* modifing code stubs that has read, */
+					/* write and execute permissions */
+
+/*
+ * Fixed virtual memory shared libraries are identified by two things.  The
+ * target pathname (the name of the library as found for execution), and the
+ * minor version number.  The address of where the headers are loaded is in
+ * header_addr. (THIS IS OBSOLETE and no longer supported).
+ */
+struct fvmlib {
+	union lc_str	name;		/* library's target pathname */
+	uint32_t	minor_version;	/* library's minor version number */
+	uint32_t	header_addr;	/* library's header address */
+};
+
+/*
+ * A fixed virtual shared library (filetype == MH_FVMLIB in the mach header)
+ * contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library.
+ * An object that uses a fixed virtual shared library also contains a
+ * fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses.
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+struct fvmlib_command {
+	uint32_t	cmd;		/* LC_IDFVMLIB or LC_LOADFVMLIB */
+	uint32_t	cmdsize;	/* includes pathname string */
+	struct fvmlib	fvmlib;		/* the library identification */
+};
+
+/*
+ * Dynamicly linked shared libraries are identified by two things.  The
+ * pathname (the name of the library as found for execution), and the
+ * compatibility version number.  The pathname must match and the compatibility
+ * number in the user of the library must be greater than or equal to the
+ * library being used.  The time stamp is used to record the time a library was
+ * built and copied into user so it can be use to determined if the library used
+ * at runtime is exactly the same as used to built the program.
+ */
+struct dylib {
+    union lc_str  name;			/* library's path name */
+    uint32_t timestamp;			/* library's build time stamp */
+    uint32_t current_version;		/* library's current version number */
+    uint32_t compatibility_version;	/* library's compatibility vers number*/
+};
+
+/*
+ * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
+ * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
+ * An object that uses a dynamically linked shared library also contains a
+ * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
+ * LC_REEXPORT_DYLIB) for each library it uses.
+ */
+struct dylib_command {
+	uint32_t	cmd;		/* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
+					   LC_REEXPORT_DYLIB */
+	uint32_t	cmdsize;	/* includes pathname string */
+	struct dylib	dylib;		/* the library identification */
+};
+
+/*
+ * A dynamically linked shared library may be a subframework of an umbrella
+ * framework.  If so it will be linked with "-umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the umbrella framework. A subframework
+ * can only be linked against by its umbrella framework or other subframeworks
+ * that are part of the same umbrella framework.  Otherwise the static link
+ * editor produces an error and states to link against the umbrella framework.
+ * The name of the umbrella framework for subframeworks is recorded in the
+ * following structure.
+ */
+struct sub_framework_command {
+	uint32_t	cmd;		/* LC_SUB_FRAMEWORK */
+	uint32_t	cmdsize;	/* includes umbrella string */
+	union lc_str 	umbrella;	/* the umbrella framework name */
+};
+
+/*
+ * For dynamically linked shared libraries that are subframework of an umbrella
+ * framework they can allow clients other than the umbrella framework or other
+ * subframeworks in the same umbrella framework.  To do this the subframework
+ * is built with "-allowable_client client_name" and an LC_SUB_CLIENT load
+ * command is created for each -allowable_client flag.  The client_name is
+ * usually a framework name.  It can also be a name used for bundles clients
+ * where the bundle is built with "-client_name client_name".
+ */
+struct sub_client_command {
+	uint32_t	cmd;		/* LC_SUB_CLIENT */
+	uint32_t	cmdsize;	/* includes client string */
+	union lc_str 	client;		/* the client name */
+};
+
+/*
+ * A dynamically linked shared library may be a sub_umbrella of an umbrella
+ * framework.  If so it will be linked with "-sub_umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the sub_umbrella framework.  When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace 
+ * umbrella framework will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks to be implicited linked in.  Any other
+ * dependent dynamic libraries will not be linked it when -twolevel_namespace
+ * is in effect.  The primary library recorded by the static linker when
+ * resolving a symbol in these libraries will be the umbrella framework.
+ * Zero or more sub_umbrella frameworks may be use by an umbrella framework.
+ * The name of a sub_umbrella framework is recorded in the following structure.
+ */
+struct sub_umbrella_command {
+	uint32_t	cmd;		/* LC_SUB_UMBRELLA */
+	uint32_t	cmdsize;	/* includes sub_umbrella string */
+	union lc_str 	sub_umbrella;	/* the sub_umbrella framework name */
+};
+
+/*
+ * A dynamically linked shared library may be a sub_library of another shared
+ * library.  If so it will be linked with "-sub_library library_name" where
+ * Where "library_name" is the name of the sub_library shared library.  When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace 
+ * shared library will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks and libraries listed as sub_libraries to
+ * be implicited linked in.  Any other dependent dynamic libraries will not be
+ * linked it when -twolevel_namespace is in effect.  The primary library
+ * recorded by the static linker when resolving a symbol in these libraries
+ * will be the umbrella framework (or dynamic library). Zero or more sub_library
+ * shared libraries may be use by an umbrella framework or (or dynamic library).
+ * The name of a sub_library framework is recorded in the following structure.
+ * For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc".
+ */
+struct sub_library_command {
+	uint32_t	cmd;		/* LC_SUB_LIBRARY */
+	uint32_t	cmdsize;	/* includes sub_library string */
+	union lc_str 	sub_library;	/* the sub_library name */
+};
+
+/*
+ * A program (filetype == MH_EXECUTE) that is
+ * prebound to its dynamic libraries has one of these for each library that
+ * the static linker used in prebinding.  It contains a bit vector for the
+ * modules in the library.  The bits indicate which modules are bound (1) and
+ * which are not (0) from the library.  The bit for module 0 is the low bit
+ * of the first byte.  So the bit for the Nth module is:
+ * (linked_modules[N/8] >> N%8) & 1
+ */
+struct prebound_dylib_command {
+	uint32_t	cmd;		/* LC_PREBOUND_DYLIB */
+	uint32_t	cmdsize;	/* includes strings */
+	union lc_str	name;		/* library's path name */
+	uint32_t	nmodules;	/* number of modules in library */
+	union lc_str	linked_modules;	/* bit vector of linked modules */
+};
+
+/*
+ * A program that uses a dynamic linker contains a dylinker_command to identify
+ * the name of the dynamic linker (LC_LOAD_DYLINKER).  And a dynamic linker
+ * contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
+ * A file can have at most one of these.
+ * This struct is also used for the LC_DYLD_ENVIRONMENT load command and
+ * contains string for dyld to treat like environment variable.
+ */
+struct dylinker_command {
+	uint32_t	cmd;		/* LC_ID_DYLINKER, LC_LOAD_DYLINKER or
+					   LC_DYLD_ENVIRONMENT */
+	uint32_t	cmdsize;	/* includes pathname string */
+	union lc_str    name;		/* dynamic linker's path name */
+};
+
+/*
+ * Thread commands contain machine-specific data structures suitable for
+ * use in the thread state primitives.  The machine specific data structures
+ * follow the struct thread_command as follows.
+ * Each flavor of machine specific data structure is preceded by an uint32_t
+ * constant for the flavor of that data structure, an uint32_t that is the
+ * count of uint32_t's of the size of the state data structure and then
+ * the state data structure follows.  This triple may be repeated for many
+ * flavors.  The constants for the flavors, counts and state data structure
+ * definitions are expected to be in the header file <machine/thread_status.h>.
+ * These machine specific data structures sizes must be multiples of
+ * 4 bytes.  The cmdsize reflects the total size of the thread_command
+ * and all of the sizes of the constants for the flavors, counts and state
+ * data structures.
+ *
+ * For executable objects that are unix processes there will be one
+ * thread_command (cmd == LC_UNIXTHREAD) created for it by the link-editor.
+ * This is the same as a LC_THREAD, except that a stack is automatically
+ * created (based on the shell's limit for the stack size).  Command arguments
+ * and environment variables are copied onto that stack.
+ */
+struct thread_command {
+	uint32_t	cmd;		/* LC_THREAD or  LC_UNIXTHREAD */
+	uint32_t	cmdsize;	/* total size of this command */
+	/* uint32_t flavor		   flavor of thread state */
+	/* uint32_t count		   count of uint32_t's in thread state */
+	/* struct XXX_thread_state state   thread state for this flavor */
+	/* ... */
+};
+
+/*
+ * The routines command contains the address of the dynamic shared library 
+ * initialization routine and an index into the module table for the module
+ * that defines the routine.  Before any modules are used from the library the
+ * dynamic linker fully binds the module that defines the initialization routine
+ * and then calls it.  This gets called before any module initialization
+ * routines (used for C++ static constructors) in the library.
+ */
+struct routines_command { /* for 32-bit architectures */
+	uint32_t	cmd;		/* LC_ROUTINES */
+	uint32_t	cmdsize;	/* total size of this command */
+	uint32_t	init_address;	/* address of initialization routine */
+	uint32_t	init_module;	/* index into the module table that */
+				        /*  the init routine is defined in */
+	uint32_t	reserved1;
+	uint32_t	reserved2;
+	uint32_t	reserved3;
+	uint32_t	reserved4;
+	uint32_t	reserved5;
+	uint32_t	reserved6;
+};
+
+/*
+ * The 64-bit routines command.  Same use as above.
+ */
+struct routines_command_64 { /* for 64-bit architectures */
+	uint32_t	cmd;		/* LC_ROUTINES_64 */
+	uint32_t	cmdsize;	/* total size of this command */
+	uint64_t	init_address;	/* address of initialization routine */
+	uint64_t	init_module;	/* index into the module table that */
+					/*  the init routine is defined in */
+	uint64_t	reserved1;
+	uint64_t	reserved2;
+	uint64_t	reserved3;
+	uint64_t	reserved4;
+	uint64_t	reserved5;
+	uint64_t	reserved6;
+};
+
+/*
+ * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
+ * "stab" style symbol table information as described in the header files
+ * <nlist.h> and <stab.h>.
+ */
+struct symtab_command {
+	uint32_t	cmd;		/* LC_SYMTAB */
+	uint32_t	cmdsize;	/* sizeof(struct symtab_command) */
+	uint32_t	symoff;		/* symbol table offset */
+	uint32_t	nsyms;		/* number of symbol table entries */
+	uint32_t	stroff;		/* string table offset */
+	uint32_t	strsize;	/* string table size in bytes */
+};
+
+/*
+ * This is the second set of the symbolic information which is used to support
+ * the data structures for the dynamically link editor.
+ *
+ * The original set of symbolic information in the symtab_command which contains
+ * the symbol and string tables must also be present when this load command is
+ * present.  When this load command is present the symbol table is organized
+ * into three groups of symbols:
+ *	local symbols (static and debugging symbols) - grouped by module
+ *	defined external symbols - grouped by module (sorted by name if not lib)
+ *	undefined external symbols (sorted by name if MH_BINDATLOAD is not set,
+ *	     			    and in order the were seen by the static
+ *				    linker if MH_BINDATLOAD is set)
+ * In this load command there are offsets and counts to each of the three groups
+ * of symbols.
+ *
+ * This load command contains a the offsets and sizes of the following new
+ * symbolic information tables:
+ *	table of contents
+ *	module table
+ *	reference symbol table
+ *	indirect symbol table
+ * The first three tables above (the table of contents, module table and
+ * reference symbol table) are only present if the file is a dynamically linked
+ * shared library.  For executable and object modules, which are files
+ * containing only one module, the information that would be in these three
+ * tables is determined as follows:
+ * 	table of contents - the defined external symbols are sorted by name
+ *	module table - the file contains only one module so everything in the
+ *		       file is part of the module.
+ *	reference symbol table - is the defined and undefined external symbols
+ *
+ * For dynamically linked shared library files this load command also contains
+ * offsets and sizes to the pool of relocation entries for all sections
+ * separated into two groups:
+ *	external relocation entries
+ *	local relocation entries
+ * For executable and object modules the relocation entries continue to hang
+ * off the section structures.
+ */
+struct dysymtab_command {
+    uint32_t cmd;	/* LC_DYSYMTAB */
+    uint32_t cmdsize;	/* sizeof(struct dysymtab_command) */
+
+    /*
+     * The symbols indicated by symoff and nsyms of the LC_SYMTAB load command
+     * are grouped into the following three groups:
+     *    local symbols (further grouped by the module they are from)
+     *    defined external symbols (further grouped by the module they are from)
+     *    undefined symbols
+     *
+     * The local symbols are used only for debugging.  The dynamic binding
+     * process may have to use them to indicate to the debugger the local
+     * symbols for a module that is being bound.
+     *
+     * The last two groups are used by the dynamic binding process to do the
+     * binding (indirectly through the module table and the reference symbol
+     * table when this is a dynamically linked shared library file).
+     */
+    uint32_t ilocalsym;	/* index to local symbols */
+    uint32_t nlocalsym;	/* number of local symbols */
+
+    uint32_t iextdefsym;/* index to externally defined symbols */
+    uint32_t nextdefsym;/* number of externally defined symbols */
+
+    uint32_t iundefsym;	/* index to undefined symbols */
+    uint32_t nundefsym;	/* number of undefined symbols */
+
+    /*
+     * For the for the dynamic binding process to find which module a symbol
+     * is defined in the table of contents is used (analogous to the ranlib
+     * structure in an archive) which maps defined external symbols to modules
+     * they are defined in.  This exists only in a dynamically linked shared
+     * library file.  For executable and object modules the defined external
+     * symbols are sorted by name and is use as the table of contents.
+     */
+    uint32_t tocoff;	/* file offset to table of contents */
+    uint32_t ntoc;	/* number of entries in table of contents */
+
+    /*
+     * To support dynamic binding of "modules" (whole object files) the symbol
+     * table must reflect the modules that the file was created from.  This is
+     * done by having a module table that has indexes and counts into the merged
+     * tables for each module.  The module structure that these two entries
+     * refer to is described below.  This exists only in a dynamically linked
+     * shared library file.  For executable and object modules the file only
+     * contains one module so everything in the file belongs to the module.
+     */
+    uint32_t modtaboff;	/* file offset to module table */
+    uint32_t nmodtab;	/* number of module table entries */
+
+    /*
+     * To support dynamic module binding the module structure for each module
+     * indicates the external references (defined and undefined) each module
+     * makes.  For each module there is an offset and a count into the
+     * reference symbol table for the symbols that the module references.
+     * This exists only in a dynamically linked shared library file.  For
+     * executable and object modules the defined external symbols and the
+     * undefined external symbols indicates the external references.
+     */
+    uint32_t extrefsymoff;	/* offset to referenced symbol table */
+    uint32_t nextrefsyms;	/* number of referenced symbol table entries */
+
+    /*
+     * The sections that contain "symbol pointers" and "routine stubs" have
+     * indexes and (implied counts based on the size of the section and fixed
+     * size of the entry) into the "indirect symbol" table for each pointer
+     * and stub.  For every section of these two types the index into the
+     * indirect symbol table is stored in the section header in the field
+     * reserved1.  An indirect symbol table entry is simply a 32bit index into
+     * the symbol table to the symbol that the pointer or stub is referring to.
+     * The indirect symbol table is ordered to match the entries in the section.
+     */
+    uint32_t indirectsymoff; /* file offset to the indirect symbol table */
+    uint32_t nindirectsyms;  /* number of indirect symbol table entries */
+
+    /*
+     * To support relocating an individual module in a library file quickly the
+     * external relocation entries for each module in the library need to be
+     * accessed efficiently.  Since the relocation entries can't be accessed
+     * through the section headers for a library file they are separated into
+     * groups of local and external entries further grouped by module.  In this
+     * case the presents of this load command who's extreloff, nextrel,
+     * locreloff and nlocrel fields are non-zero indicates that the relocation
+     * entries of non-merged sections are not referenced through the section
+     * structures (and the reloff and nreloc fields in the section headers are
+     * set to zero).
+     *
+     * Since the relocation entries are not accessed through the section headers
+     * this requires the r_address field to be something other than a section
+     * offset to identify the item to be relocated.  In this case r_address is
+     * set to the offset from the vmaddr of the first LC_SEGMENT command.
+     * For MH_SPLIT_SEGS images r_address is set to the the offset from the
+     * vmaddr of the first read-write LC_SEGMENT command.
+     *
+     * The relocation entries are grouped by module and the module table
+     * entries have indexes and counts into them for the group of external
+     * relocation entries for that the module.
+     *
+     * For sections that are merged across modules there must not be any
+     * remaining external relocation entries for them (for merged sections
+     * remaining relocation entries must be local).
+     */
+    uint32_t extreloff;	/* offset to external relocation entries */
+    uint32_t nextrel;	/* number of external relocation entries */
+
+    /*
+     * All the local relocation entries are grouped together (they are not
+     * grouped by their module since they are only used if the object is moved
+     * from it staticly link edited address).
+     */
+    uint32_t locreloff;	/* offset to local relocation entries */
+    uint32_t nlocrel;	/* number of local relocation entries */
+
+};	
+
+/*
+ * An indirect symbol table entry is simply a 32bit index into the symbol table 
+ * to the symbol that the pointer or stub is refering to.  Unless it is for a
+ * non-lazy symbol pointer section for a defined symbol which strip(1) as 
+ * removed.  In which case it has the value INDIRECT_SYMBOL_LOCAL.  If the
+ * symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that.
+ */
+#define INDIRECT_SYMBOL_LOCAL	0x80000000
+#define INDIRECT_SYMBOL_ABS	0x40000000
+
+
+/* a table of contents entry */
+struct dylib_table_of_contents {
+    uint32_t symbol_index;	/* the defined external symbol
+				   (index into the symbol table) */
+    uint32_t module_index;	/* index into the module table this symbol
+				   is defined in */
+};	
+
+/* a module table entry */
+struct dylib_module {
+    uint32_t module_name;	/* the module name (index into string table) */
+
+    uint32_t iextdefsym;	/* index into externally defined symbols */
+    uint32_t nextdefsym;	/* number of externally defined symbols */
+    uint32_t irefsym;		/* index into reference symbol table */
+    uint32_t nrefsym;		/* number of reference symbol table entries */
+    uint32_t ilocalsym;		/* index into symbols for local symbols */
+    uint32_t nlocalsym;		/* number of local symbols */
+
+    uint32_t iextrel;		/* index into external relocation entries */
+    uint32_t nextrel;		/* number of external relocation entries */
+
+    uint32_t iinit_iterm;	/* low 16 bits are the index into the init
+				   section, high 16 bits are the index into
+			           the term section */
+    uint32_t ninit_nterm;	/* low 16 bits are the number of init section
+				   entries, high 16 bits are the number of
+				   term section entries */
+
+    uint32_t			/* for this module address of the start of */
+	objc_module_info_addr;  /*  the (__OBJC,__module_info) section */
+    uint32_t			/* for this module size of */
+	objc_module_info_size;	/*  the (__OBJC,__module_info) section */
+};	
+
+/* a 64-bit module table entry */
+struct dylib_module_64 {
+    uint32_t module_name;	/* the module name (index into string table) */
+
+    uint32_t iextdefsym;	/* index into externally defined symbols */
+    uint32_t nextdefsym;	/* number of externally defined symbols */
+    uint32_t irefsym;		/* index into reference symbol table */
+    uint32_t nrefsym;		/* number of reference symbol table entries */
+    uint32_t ilocalsym;		/* index into symbols for local symbols */
+    uint32_t nlocalsym;		/* number of local symbols */
+
+    uint32_t iextrel;		/* index into external relocation entries */
+    uint32_t nextrel;		/* number of external relocation entries */
+
+    uint32_t iinit_iterm;	/* low 16 bits are the index into the init
+				   section, high 16 bits are the index into
+				   the term section */
+    uint32_t ninit_nterm;      /* low 16 bits are the number of init section
+				  entries, high 16 bits are the number of
+				  term section entries */
+
+    uint32_t			/* for this module size of */
+        objc_module_info_size;	/*  the (__OBJC,__module_info) section */
+    uint64_t			/* for this module address of the start of */
+        objc_module_info_addr;	/*  the (__OBJC,__module_info) section */
+};
+
+/* 
+ * The entries in the reference symbol table are used when loading the module
+ * (both by the static and dynamic link editors) and if the module is unloaded
+ * or replaced.  Therefore all external symbols (defined and undefined) are
+ * listed in the module's reference table.  The flags describe the type of
+ * reference that is being made.  The constants for the flags are defined in
+ * <mach-o/nlist.h> as they are also used for symbol table entries.
+ */
+struct dylib_reference {
+    uint32_t isym:24,		/* index into the symbol table */
+    		  flags:8;	/* flags to indicate the type of reference */
+};
+
+/*
+ * The twolevel_hints_command contains the offset and number of hints in the
+ * two-level namespace lookup hints table.
+ */
+struct twolevel_hints_command {
+    uint32_t cmd;	/* LC_TWOLEVEL_HINTS */
+    uint32_t cmdsize;	/* sizeof(struct twolevel_hints_command) */
+    uint32_t offset;	/* offset to the hint table */
+    uint32_t nhints;	/* number of hints in the hint table */
+};
+
+/*
+ * The entries in the two-level namespace lookup hints table are twolevel_hint
+ * structs.  These provide hints to the dynamic link editor where to start
+ * looking for an undefined symbol in a two-level namespace image.  The
+ * isub_image field is an index into the sub-images (sub-frameworks and
+ * sub-umbrellas list) that made up the two-level image that the undefined
+ * symbol was found in when it was built by the static link editor.  If
+ * isub-image is 0 the the symbol is expected to be defined in library and not
+ * in the sub-images.  If isub-image is non-zero it is an index into the array
+ * of sub-images for the umbrella with the first index in the sub-images being
+ * 1. The array of sub-images is the ordered list of sub-images of the umbrella
+ * that would be searched for a symbol that has the umbrella recorded as its
+ * primary library.  The table of contents index is an index into the
+ * library's table of contents.  This is used as the starting point of the
+ * binary search or a directed linear search.
+ */
+struct twolevel_hint {
+    uint32_t 
+	isub_image:8,	/* index into the sub images */
+	itoc:24;	/* index into the table of contents */
+};
+
+/*
+ * The prebind_cksum_command contains the value of the original check sum for
+ * prebound files or zero.  When a prebound file is first created or modified
+ * for other than updating its prebinding information the value of the check sum
+ * is set to zero.  When the file has it prebinding re-done and if the value of
+ * the check sum is zero the original check sum is calculated and stored in
+ * cksum field of this load command in the output file.  If when the prebinding
+ * is re-done and the cksum field is non-zero it is left unchanged from the
+ * input file.
+ */
+struct prebind_cksum_command {
+    uint32_t cmd;	/* LC_PREBIND_CKSUM */
+    uint32_t cmdsize;	/* sizeof(struct prebind_cksum_command) */
+    uint32_t cksum;	/* the check sum or zero */
+};
+
+/*
+ * The uuid load command contains a single 128-bit unique random number that
+ * identifies an object produced by the static link editor.
+ */
+struct uuid_command {
+    uint32_t	cmd;		/* LC_UUID */
+    uint32_t	cmdsize;	/* sizeof(struct uuid_command) */
+    uint8_t	uuid[16];	/* the 128-bit uuid */
+};
+
+/*
+ * The rpath_command contains a path which at runtime should be added to
+ * the current run path used to find @rpath prefixed dylibs.
+ */
+struct rpath_command {
+    uint32_t	 cmd;		/* LC_RPATH */
+    uint32_t	 cmdsize;	/* includes string */
+    union lc_str path;		/* path to add to run path */
+};
+
+/*
+ * The linkedit_data_command contains the offsets and sizes of a blob
+ * of data in the __LINKEDIT segment.  
+ */
+struct linkedit_data_command {
+    uint32_t	cmd;		/* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO,
+                                   LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
+				   LC_DYLIB_CODE_SIGN_DRS,
+				   LC_LINKER_OPTIMIZATION_HINT,
+				   LC_DYLD_EXPORTS_TRIE, or
+				   LC_DYLD_CHAINED_FIXUPS. */
+    uint32_t	cmdsize;	/* sizeof(struct linkedit_data_command) */
+    uint32_t	dataoff;	/* file offset of data in __LINKEDIT segment */
+    uint32_t	datasize;	/* file size of data in __LINKEDIT segment  */
+};
+
+/*
+ * The encryption_info_command contains the file offset and size of an
+ * of an encrypted segment.
+ */
+struct encryption_info_command {
+   uint32_t	cmd;		/* LC_ENCRYPTION_INFO */
+   uint32_t	cmdsize;	/* sizeof(struct encryption_info_command) */
+   uint32_t	cryptoff;	/* file offset of encrypted range */
+   uint32_t	cryptsize;	/* file size of encrypted range */
+   uint32_t	cryptid;	/* which enryption system,
+				   0 means not-encrypted yet */
+};
+
+/*
+ * The encryption_info_command_64 contains the file offset and size of an
+ * of an encrypted segment (for use in x86_64 targets).
+ */
+struct encryption_info_command_64 {
+   uint32_t	cmd;		/* LC_ENCRYPTION_INFO_64 */
+   uint32_t	cmdsize;	/* sizeof(struct encryption_info_command_64) */
+   uint32_t	cryptoff;	/* file offset of encrypted range */
+   uint32_t	cryptsize;	/* file size of encrypted range */
+   uint32_t	cryptid;	/* which enryption system,
+				   0 means not-encrypted yet */
+   uint32_t	pad;		/* padding to make this struct's size a multiple
+				   of 8 bytes */
+};
+
+/*
+ * The version_min_command contains the min OS version on which this 
+ * binary was built to run.
+ */
+struct version_min_command {
+    uint32_t	cmd;		/* LC_VERSION_MIN_MACOSX or
+				   LC_VERSION_MIN_IPHONEOS or
+				   LC_VERSION_MIN_WATCHOS or
+				   LC_VERSION_MIN_TVOS */
+    uint32_t	cmdsize;	/* sizeof(struct min_version_command) */
+    uint32_t	version;	/* X.Y.Z is encoded in nibbles xxxx.yy.zz */
+    uint32_t	sdk;		/* X.Y.Z is encoded in nibbles xxxx.yy.zz */
+};
+
+/*
+ * The build_version_command contains the min OS version on which this
+ * binary was built to run for its platform.  The list of known platforms and
+ * tool values following it.
+ */
+struct build_version_command {
+    uint32_t	cmd;		/* LC_BUILD_VERSION */
+    uint32_t	cmdsize;	/* sizeof(struct build_version_command) plus */
+                                /* ntools * sizeof(struct build_tool_version) */
+    uint32_t	platform;	/* platform */
+    uint32_t	minos;		/* X.Y.Z is encoded in nibbles xxxx.yy.zz */
+    uint32_t	sdk;		/* X.Y.Z is encoded in nibbles xxxx.yy.zz */
+    uint32_t	ntools;		/* number of tool entries following this */
+};
+
+struct build_tool_version {
+    uint32_t	tool;		/* enum for the tool */
+    uint32_t	version;	/* version number of the tool */
+};
+
+/* Known values for the platform field above. */
+#define PLATFORM_MACOS 1
+#define PLATFORM_IOS 2
+#define PLATFORM_TVOS 3
+#define PLATFORM_WATCHOS 4
+#define PLATFORM_BRIDGEOS 5
+#define PLATFORM_IOSMAC 6
+#define PLATFORM_IOSSIMULATOR 7
+#define PLATFORM_TVOSSIMULATOR 8
+#define PLATFORM_WATCHOSSIMULATOR 9
+#define PLATFORM_DRIVERKIT 10
+
+/* Known values for the tool field above. */
+#define TOOL_CLANG 1
+#define TOOL_SWIFT 2
+#define TOOL_LD	3
+
+/*
+ * The dyld_info_command contains the file offsets and sizes of 
+ * the new compressed form of the information dyld needs to 
+ * load the image.  This information is used by dyld on Mac OS X
+ * 10.6 and later.  All information pointed to by this command
+ * is encoded using byte streams, so no endian swapping is needed
+ * to interpret it. 
+ */
+struct dyld_info_command {
+   uint32_t   cmd;		/* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
+   uint32_t   cmdsize;		/* sizeof(struct dyld_info_command) */
+
+    /*
+     * Dyld rebases an image whenever dyld loads it at an address different
+     * from its preferred address.  The rebase information is a stream
+     * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_.
+     * Conceptually the rebase information is a table of tuples:
+     *    <seg-index, seg-offset, type>
+     * The opcodes are a compressed way to encode the table by only
+     * encoding when a column changes.  In addition simple patterns
+     * like "every n'th offset for m times" can be encoded in a few
+     * bytes.
+     */
+    uint32_t   rebase_off;	/* file offset to rebase info  */
+    uint32_t   rebase_size;	/* size of rebase info   */
+    
+    /*
+     * Dyld binds an image during the loading process, if the image
+     * requires any pointers to be initialized to symbols in other images.  
+     * The bind information is a stream of byte sized 
+     * opcodes whose symbolic names start with BIND_OPCODE_.
+     * Conceptually the bind information is a table of tuples:
+     *    <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
+     * The opcodes are a compressed way to encode the table by only
+     * encoding when a column changes.  In addition simple patterns
+     * like for runs of pointers initialzed to the same value can be 
+     * encoded in a few bytes.
+     */
+    uint32_t   bind_off;	/* file offset to binding info   */
+    uint32_t   bind_size;	/* size of binding info  */
+        
+    /*
+     * Some C++ programs require dyld to unique symbols so that all
+     * images in the process use the same copy of some code/data.
+     * This step is done after binding. The content of the weak_bind
+     * info is an opcode stream like the bind_info.  But it is sorted
+     * alphabetically by symbol name.  This enable dyld to walk 
+     * all images with weak binding information in order and look
+     * for collisions.  If there are no collisions, dyld does
+     * no updating.  That means that some fixups are also encoded
+     * in the bind_info.  For instance, all calls to "operator new"
+     * are first bound to libstdc++.dylib using the information
+     * in bind_info.  Then if some image overrides operator new
+     * that is detected when the weak_bind information is processed
+     * and the call to operator new is then rebound.
+     */
+    uint32_t   weak_bind_off;	/* file offset to weak binding info   */
+    uint32_t   weak_bind_size;  /* size of weak binding info  */
+    
+    /*
+     * Some uses of external symbols do not need to be bound immediately.
+     * Instead they can be lazily bound on first use.  The lazy_bind
+     * are contains a stream of BIND opcodes to bind all lazy symbols.
+     * Normal use is that dyld ignores the lazy_bind section when
+     * loading an image.  Instead the static linker arranged for the
+     * lazy pointer to initially point to a helper function which 
+     * pushes the offset into the lazy_bind area for the symbol
+     * needing to be bound, then jumps to dyld which simply adds
+     * the offset to lazy_bind_off to get the information on what 
+     * to bind.  
+     */
+    uint32_t   lazy_bind_off;	/* file offset to lazy binding info */
+    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
+    
+    /*
+     * The symbols exported by a dylib are encoded in a trie.  This
+     * is a compact representation that factors out common prefixes.
+     * It also reduces LINKEDIT pages in RAM because it encodes all  
+     * information (name, address, flags) in one small, contiguous range.
+     * The export area is a stream of nodes.  The first node sequentially
+     * is the start node for the trie.  
+     *
+     * Nodes for a symbol start with a uleb128 that is the length of
+     * the exported symbol information for the string so far.
+     * If there is no exported symbol, the node starts with a zero byte. 
+     * If there is exported info, it follows the length.  
+	 *
+	 * First is a uleb128 containing flags. Normally, it is followed by
+     * a uleb128 encoded offset which is location of the content named
+     * by the symbol from the mach_header for the image.  If the flags
+     * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is
+     * a uleb128 encoded library ordinal, then a zero terminated
+     * UTF8 string.  If the string is zero length, then the symbol
+     * is re-export from the specified dylib with the same name.
+	 * If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following
+	 * the flags is two uleb128s: the stub offset and the resolver offset.
+	 * The stub is used by non-lazy pointers.  The resolver is used
+	 * by lazy pointers and must be called to get the actual address to use.
+     *
+     * After the optional exported symbol information is a byte of
+     * how many edges (0-255) that this node has leaving it, 
+     * followed by each edge.
+     * Each edge is a zero terminated UTF8 of the addition chars
+     * in the symbol, followed by a uleb128 offset for the node that
+     * edge points to.
+     *  
+     */
+    uint32_t   export_off;	/* file offset to lazy binding info */
+    uint32_t   export_size;	/* size of lazy binding infs */
+};
+
+/*
+ * The following are used to encode rebasing information
+ */
+#define REBASE_TYPE_POINTER					1
+#define REBASE_TYPE_TEXT_ABSOLUTE32				2
+#define REBASE_TYPE_TEXT_PCREL32				3
+
+#define REBASE_OPCODE_MASK					0xF0
+#define REBASE_IMMEDIATE_MASK					0x0F
+#define REBASE_OPCODE_DONE					0x00
+#define REBASE_OPCODE_SET_TYPE_IMM				0x10
+#define REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB		0x20
+#define REBASE_OPCODE_ADD_ADDR_ULEB				0x30
+#define REBASE_OPCODE_ADD_ADDR_IMM_SCALED			0x40
+#define REBASE_OPCODE_DO_REBASE_IMM_TIMES			0x50
+#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES			0x60
+#define REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB			0x70
+#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB	0x80
+
+
+/*
+ * The following are used to encode binding information
+ */
+#define BIND_TYPE_POINTER					1
+#define BIND_TYPE_TEXT_ABSOLUTE32				2
+#define BIND_TYPE_TEXT_PCREL32					3
+
+#define BIND_SPECIAL_DYLIB_SELF					 0
+#define BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE			-1
+#define BIND_SPECIAL_DYLIB_FLAT_LOOKUP				-2
+#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP				-3
+
+#define BIND_SYMBOL_FLAGS_WEAK_IMPORT				0x1
+#define BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION			0x8
+
+#define BIND_OPCODE_MASK					0xF0
+#define BIND_IMMEDIATE_MASK					0x0F
+#define BIND_OPCODE_DONE					0x00
+#define BIND_OPCODE_SET_DYLIB_ORDINAL_IMM			0x10
+#define BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB			0x20
+#define BIND_OPCODE_SET_DYLIB_SPECIAL_IMM			0x30
+#define BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM		0x40
+#define BIND_OPCODE_SET_TYPE_IMM				0x50
+#define BIND_OPCODE_SET_ADDEND_SLEB				0x60
+#define BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB			0x70
+#define BIND_OPCODE_ADD_ADDR_ULEB				0x80
+#define BIND_OPCODE_DO_BIND					0x90
+#define BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB			0xA0
+#define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED			0xB0
+#define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB		0xC0
+#define	BIND_OPCODE_THREADED					0xD0
+#define	BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00
+#define	BIND_SUBOPCODE_THREADED_APPLY				 0x01
+
+
+/*
+ * The following are used on the flags byte of a terminal node
+ * in the export information.
+ */
+#define EXPORT_SYMBOL_FLAGS_KIND_MASK				0x03
+#define EXPORT_SYMBOL_FLAGS_KIND_REGULAR			0x00
+#define EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL			0x01
+#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE			0x02
+#define EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION			0x04
+#define EXPORT_SYMBOL_FLAGS_REEXPORT				0x08
+#define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER			0x10
+
+/*
+ * The linker_option_command contains linker options embedded in object files.
+ */
+struct linker_option_command {
+    uint32_t  cmd;	/* LC_LINKER_OPTION only used in MH_OBJECT filetypes */
+    uint32_t  cmdsize;
+    uint32_t  count;	/* number of strings */
+    /* concatenation of zero terminated UTF8 strings.
+       Zero filled at end to align */
+};
+
+/*
+ * The symseg_command contains the offset and size of the GNU style
+ * symbol table information as described in the header file <symseg.h>.
+ * The symbol roots of the symbol segments must also be aligned properly
+ * in the file.  So the requirement of keeping the offsets aligned to a
+ * multiple of a 4 bytes translates to the length field of the symbol
+ * roots also being a multiple of a long.  Also the padding must again be
+ * zeroed. (THIS IS OBSOLETE and no longer supported).
+ */
+struct symseg_command {
+	uint32_t	cmd;		/* LC_SYMSEG */
+	uint32_t	cmdsize;	/* sizeof(struct symseg_command) */
+	uint32_t	offset;		/* symbol segment offset */
+	uint32_t	size;		/* symbol segment size in bytes */
+};
+
+/*
+ * The ident_command contains a free format string table following the
+ * ident_command structure.  The strings are null terminated and the size of
+ * the command is padded out with zero bytes to a multiple of 4 bytes/
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+struct ident_command {
+	uint32_t cmd;		/* LC_IDENT */
+	uint32_t cmdsize;	/* strings that follow this command */
+};
+
+/*
+ * The fvmfile_command contains a reference to a file to be loaded at the
+ * specified virtual address.  (Presently, this command is reserved for
+ * internal use.  The kernel ignores this command when loading a program into
+ * memory).
+ */
+struct fvmfile_command {
+	uint32_t cmd;			/* LC_FVMFILE */
+	uint32_t cmdsize;		/* includes pathname string */
+	union lc_str	name;		/* files pathname */
+	uint32_t	header_addr;	/* files virtual address */
+};
+
+
+/*
+ * The entry_point_command is a replacement for thread_command.
+ * It is used for main executables to specify the location (file offset)
+ * of main().  If -stack_size was used at link time, the stacksize
+ * field will contain the stack size need for the main thread.
+ */
+struct entry_point_command {
+    uint32_t  cmd;	/* LC_MAIN only used in MH_EXECUTE filetypes */
+    uint32_t  cmdsize;	/* 24 */
+    uint64_t  entryoff;	/* file (__TEXT) offset of main() */
+    uint64_t  stacksize;/* if not zero, initial stack size */
+};
+
+
+/*
+ * The source_version_command is an optional load command containing
+ * the version of the sources used to build the binary.
+ */
+struct source_version_command {
+    uint32_t  cmd;	/* LC_SOURCE_VERSION */
+    uint32_t  cmdsize;	/* 16 */
+    uint64_t  version;	/* A.B.C.D.E packed as a24.b10.c10.d10.e10 */
+};
+
+
+/*
+ * The LC_DATA_IN_CODE load commands uses a linkedit_data_command 
+ * to point to an array of data_in_code_entry entries. Each entry
+ * describes a range of data in a code section.
+ */
+struct data_in_code_entry {
+    uint32_t	offset;  /* from mach_header to start of data range*/
+    uint16_t	length;  /* number of bytes in data range */
+    uint16_t	kind;    /* a DICE_KIND_* value  */
+};
+#define DICE_KIND_DATA              0x0001
+#define DICE_KIND_JUMP_TABLE8       0x0002
+#define DICE_KIND_JUMP_TABLE16      0x0003
+#define DICE_KIND_JUMP_TABLE32      0x0004
+#define DICE_KIND_ABS_JUMP_TABLE32  0x0005
+
+
+
+/*
+ * Sections of type S_THREAD_LOCAL_VARIABLES contain an array 
+ * of tlv_descriptor structures.
+ */
+struct tlv_descriptor
+{
+	void*		(*thunk)(struct tlv_descriptor*);
+	unsigned long	key;
+	unsigned long	offset;
+};
+
+/*
+ * LC_NOTE commands describe a region of arbitrary data included in a Mach-O
+ * file.  Its initial use is to record extra data in MH_CORE files.
+ */
+struct note_command {
+    uint32_t	cmd;		/* LC_NOTE */
+    uint32_t	cmdsize;	/* sizeof(struct note_command) */
+    char	data_owner[16];	/* owner name for this LC_NOTE */
+    uint64_t	offset;		/* file offset of this data */
+    uint64_t	size;		/* length of data region */
+};
+
+#endif /* _MACHO_LOADER_H_ */
diff --git a/third_party/xnu/README.crashpad b/third_party/xnu/README.crashpad
new file mode 100644
index 0000000..f8471d3
--- /dev/null
+++ b/third_party/xnu/README.crashpad
@@ -0,0 +1,25 @@
+Name: XNU
+Short Name: xnu
+URL: https://opensource.apple.com/source/xnu/
+URL: https://opensource.apple.com/tarballs/xnu/
+Version: 6153.11.26 (from macOS 10.15.0)
+License: APSL 2.0
+License File: APPLE_LICENSE
+Security Critical: no
+
+Description:
+XNU is the operating system kernel used on macOS and other Apple systems.
+
+Local Modifications:
+ - EXTERNAL_HEADERS/mach-o/loader.h is present. Its #includes of
+   <mach/machine/thread_status.h> and <architecture/byte_order.h> have been
+   commented out as unnecessary. Note that its #includes of <mach/machine.h> and
+   <mach/vm_prot.h> have been retained but these headers have not been provided.
+   External headers must be made available to provide the cpu_type_t,
+   cpu_subtype_t, and vm_prot_t types.
+ - osfmk/mach/exc.defs and osfmk/mach/mach_exc.defs are present, to fill in
+   for <mach/exc.defs> and <mach/mach_exc.defs> on iOS, where they are missing.
+   The .defs files they depend on, <mach/mach_types.defs>,
+   <mach/machine/machine_types.defs>, and <mach/std_types.defs> are also
+   included.
+ - Anything not listed above is omitted.
diff --git a/third_party/xnu/osfmk/mach/exc.defs b/third_party/xnu/osfmk/mach/exc.defs
new file mode 100644
index 0000000..734e740
--- /dev/null
+++ b/third_party/xnu/osfmk/mach/exc.defs
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/*
+ * @OSF_COPYRIGHT@
+ */
+/* 
+ * Mach Operating System
+ * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University
+ * All Rights Reserved.
+ * 
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ * 
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ * 
+ * Carnegie Mellon requests users of this software to return to
+ * 
+ *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
+ *  School of Computer Science
+ *  Carnegie Mellon University
+ *  Pittsburgh PA 15213-3890
+ * 
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+/*
+ */
+/*
+ * Abstract:
+ *	MiG definitions file for Mach exception interface.
+ */
+
+subsystem
+#if	KERNEL_SERVER
+	  KernelServer
+#endif	/* KERNEL_SERVER */
+
+#if	KERNEL_USER
+	  KernelUser
+#endif
+		     exc 2401;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+
+ServerPrefix catch_;
+
+type exception_data_t		= array[*:2] of integer_t;
+type exception_type_t		= int;
+
+routine		exception_raise(
+			exception_port	: mach_port_t;
+			thread		: mach_port_t;
+			task		: mach_port_t;
+			exception	: exception_type_t;
+			code		: exception_data_t
+#if EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+routine		exception_raise_state(
+			exception_port	: mach_port_t;
+			exception	: exception_type_t;
+			code		: exception_data_t, const;
+		  inout flavor		: int;
+			old_state	: thread_state_t, const;
+		    out new_state	: thread_state_t
+#if EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+routine		exception_raise_state_identity(
+			exception_port  : mach_port_t;
+			thread          : mach_port_t;
+			task            : mach_port_t;
+			exception       : exception_type_t;
+			code            : exception_data_t;
+		  inout flavor          : int;
+			old_state       : thread_state_t;
+		    out new_state       : thread_state_t
+#if EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+/* vim: set ft=c : */
diff --git a/third_party/xnu/osfmk/mach/mach_exc.defs b/third_party/xnu/osfmk/mach/mach_exc.defs
new file mode 100644
index 0000000..5ce6427
--- /dev/null
+++ b/third_party/xnu/osfmk/mach/mach_exc.defs
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/*
+ * @OSF_COPYRIGHT@
+ */
+/* 
+ * Mach Operating System
+ * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University
+ * All Rights Reserved.
+ * 
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ * 
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ * 
+ * Carnegie Mellon requests users of this software to return to
+ * 
+ *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
+ *  School of Computer Science
+ *  Carnegie Mellon University
+ *  Pittsburgh PA 15213-3890
+ * 
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+/*
+ */
+/*
+ * Abstract:
+ *	MiG definitions file for Mach exception interface.
+ */
+
+subsystem
+#if	KERNEL_USER
+	  KernelUser
+#endif
+#if	KERNEL_SERVER
+	  KernelServer
+#endif	/* KERNEL_SERVER */
+
+		     mach_exc 2405;
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+
+ServerPrefix catch_;
+
+type mach_exception_data_t	= array[*:2] of int64_t;
+type exception_type_t		= int;
+
+routine		mach_exception_raise(
+			exception_port	: mach_port_t;
+			thread		: mach_port_t;
+			task		: mach_port_t;
+			exception	: exception_type_t;
+			code		: mach_exception_data_t
+#if MACH_EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if MACH_EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+routine		mach_exception_raise_state(
+			exception_port	: mach_port_t;
+			exception	: exception_type_t;
+			code		: mach_exception_data_t, const;
+		  inout flavor		: int;
+			old_state	: thread_state_t, const;
+		    out new_state	: thread_state_t
+#if MACH_EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if MACH_EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+routine		mach_exception_raise_state_identity(
+			exception_port  : mach_port_t;
+			thread          : mach_port_t;
+			task            : mach_port_t;
+			exception       : exception_type_t;
+			code            : mach_exception_data_t;
+		  inout flavor          : int;
+			old_state       : thread_state_t;
+		    out new_state       : thread_state_t
+#if MACH_EXC_SERVER_SECTOKEN
+ ;
+ ServerSecToken stoken	: security_token_t
+#endif
+#if MACH_EXC_SERVER_AUDITTOKEN
+ ;
+ ServerAuditToken atoken: audit_token_t
+#endif
+			);
+
+/* vim: set ft=c : */
diff --git a/third_party/xnu/osfmk/mach/mach_types.defs b/third_party/xnu/osfmk/mach/mach_types.defs
new file mode 100644
index 0000000..d2e9fb0
--- /dev/null
+++ b/third_party/xnu/osfmk/mach/mach_types.defs
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2000-2016 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/*
+ * @OSF_COPYRIGHT@
+ */
+/* 
+ * Mach Operating System
+ * Copyright (c) 1991,1990,1989,1988 Carnegie Mellon University
+ * All Rights Reserved.
+ * 
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ * 
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ * 
+ * Carnegie Mellon requests users of this software to return to
+ * 
+ *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
+ *  School of Computer Science
+ *  Carnegie Mellon University
+ *  Pittsburgh PA 15213-3890
+ * 
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+/*
+ * NOTICE: This file was modified by McAfee Research in 2004 to introduce
+ * support for mandatory and extensible security protections.  This notice
+ * is included in support of clause 2.2 (b) of the Apple Public License,
+ * Version 2.0.
+ */
+/*
+ */
+/*
+ *	Mach kernel interface type declarations
+ */
+
+#ifndef	_MACH_MACH_TYPES_DEFS_
+#define _MACH_MACH_TYPES_DEFS_
+
+
+#include <mach/std_types.defs>
+
+type memory_object_offset_t 	= uint64_t;
+type memory_object_size_t 	= uint64_t;
+type memory_object_cluster_size_t = uint32_t;
+type memory_object_fault_info_t = array[16] of integer_t;
+
+#ifdef KERNEL_PRIVATE
+
+/* Universal Page Lists - restricted to (in-kernel) pagers for now */
+type upl_size_t			= uint32_t;
+type upl_offset_t			= uint32_t;
+type upl_page_info_t		= struct[2] of integer_t;
+type upl_page_info_array_t	= array[*:256] of upl_page_info_t;
+
+type upl_t = mach_port_t
+		intran: upl_t convert_port_to_upl(mach_port_t)
+		outtran: mach_port_t convert_upl_to_port(upl_t)
+		destructor: upl_deallocate(upl_t)
+		;
+
+#endif /* KERNEL_PRIVATE */
+
+type mach_port_status_t = struct[10] of integer_t;	/* obsolete */
+type mach_port_info_ext_t = struct[17] of integer_t;
+
+		/* mach_port_info_t: can hold either a
+		 * mach_port_status_t (9 ints) or a
+		 * mach_port_limits_t (1 int) or a 
+		 * mach_port_info_ext_t (17 ints). If new flavors of
+		 * mach_port_{get,set}_attributes are added, the size of
+		 * this array may have to be increased. (See mach/port.h)
+		 */
+type mach_port_flavor_t 	= int;
+type mach_port_info_t		= array[*:17] of integer_t;
+
+     		/*
+		 * mach_msg_max_trailer_t: can hold
+		 * mach_msg_trailer_type_t (1 int)
+		 * mach_msg_trailer_size_t (1 int)
+		 * mach_port_seqno_t (1 int)
+		 * security_token_t (2 ints)
+		 * audit_token_t (8 ints)
+		 * mach_port_context_t (2 ints)
+		 * msgh_ad (1 int)
+		 * msg_labels_t (1 int)
+		 */
+type mach_msg_trailer_type_t = int;
+type mach_msg_trailer_info_t	= array[*:68] of char;
+
+type task_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: task_t convert_port_to_task(mach_port_t)
+		outtran: mach_port_t convert_task_to_port(task_t)
+		destructor: task_deallocate(task_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type task_name_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: task_name_t convert_port_to_task_name(mach_port_t)
+		outtran: mach_port_t convert_task_name_to_port(task_name_t)
+		destructor: task_name_deallocate(task_name_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type task_inspect_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: task_inspect_t convert_port_to_task_inspect(mach_port_t)
+		outtran: mach_port_t convert_task_inspect_to_port(task_inspect_t)
+		destructor: task_inspect_deallocate(task_inspect_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type thread_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: thread_t convert_port_to_thread(mach_port_t)
+		outtran: mach_port_t convert_thread_to_port(thread_t)
+		destructor: thread_deallocate(thread_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type thread_inspect_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: thread_inspect_t convert_port_to_thread_inspect(mach_port_t)
+		outtran: mach_port_t convert_thread_inspect_to_port(thread_inspect_t)
+		destructor: thread_inspect_deallocate(thread_inspect_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type thread_act_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: thread_act_t convert_port_to_thread(mach_port_t)
+		outtran: mach_port_t convert_thread_to_port(thread_act_t)
+		destructor: thread_deallocate(thread_act_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type thread_act_consume_ref_t = mach_port_move_send_t
+		cusertype: thread_act_t
+#if	KERNEL_SERVER
+		intran: thread_act_t convert_port_to_thread(mach_port_t)
+		destructor: thread_deallocate(thread_act_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+		/* thread_state_t: This inline array can hold
+		 * a machine-dependent amount of data, defined in
+		 * mach/machine/???? (currently THREAD_STATE_MAX,
+		 * in mach/thread_state.h)
+		 */
+#include <mach/machine/thread_state.h>
+type thread_state_flavor_t	= int;
+type thread_state_t		= array[*:THREAD_STATE_MAX] of natural_t;
+
+type task_array_t = ^array[] of task_t;
+type thread_array_t = ^array[] of thread_t;
+type thread_act_array_t = ^array[] of thread_act_t;
+type act_params_t = array[6] of int;
+
+type vm_map_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: vm_map_t convert_port_to_map(mach_port_t)
+		destructor: vm_map_deallocate(vm_map_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type vm_task_entry_t = mach_port_t
+		cusertype: vm_map_t
+#if	KERNEL_SERVER
+		intran: vm_map_t convert_port_entry_to_map(mach_port_t)
+		destructor: vm_map_deallocate(vm_map_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type ipc_space_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: ipc_space_t convert_port_to_space(mach_port_t)
+		destructor: space_deallocate(ipc_space_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type ipc_space_inspect_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: ipc_space_inspect_t convert_port_to_space_inspect(mach_port_t)
+		destructor: space_inspect_deallocate(ipc_space_inspect_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type arcade_register_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: arcade_register_t convert_port_to_arcade_register(mach_port_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type vm_prot_t = int;
+type vm_inherit_t = int;
+type vm_purgable_t = int;
+type xxx_vm_statistics_data_t = struct[13] of integer_t;
+type vm_behavior_t = int;
+type vm_statistics_data_t = struct[15] of integer_t;
+type vm_machine_attribute_t = int;
+type vm_machine_attribute_val_t = int;
+type vm_sync_t = int;
+
+		/* thread_info_t: this inline array can hold any of:
+		 * thread_basic_info_t (10 ints)
+		 * policy_timeshare_info_t (5 ints)
+		 * policy_fifo_info_t (4 ints)
+		 * policy_rr_info_t (5 ints)
+		 * thread_extended_info (12 ints + 64 chars)
+		 * if other thread_info flavors are added, this
+		 * definition may need to be changed. (See
+		 * mach/thread_info.h and mach/policy.h) */
+type thread_flavor_t		= int;
+type thread_info_t		= array[*:32] of integer_t;
+
+type thread_policy_flavor_t	= natural_t;
+type thread_policy_t		= array[*:16] of integer_t;
+
+		/* task_info_t: this inline array can hold any of:
+		 * task_basic_info_32_t (8 ints)
+		 * task_basic_info_64_t (10 ints)
+		 * task_events_info_t (8 ints)
+		 * task_thread_times_info_t (4 ints)
+		 * policy_timeshare_info_t (5 ints)
+		 * policy_fifo_info_t (4 ints)
+		 * policy_rr_info_t (5 ints)
+		 * task security token (2 ints)
+		 * task audit token (8 ints)
+		 * dyld info (2 64-bit ints and 1 int)
+		 * task_extmod_info_t (8 64-bit ints)
+		 * task_basic_info_64_2_t
+		 * mach_task_basic_info_t (12 ints)
+		 * task_power_info_t (18 ints)
+		 * task_vm_info_t (87 ints)
+		 * If other task_info flavors are added, this
+		 * definition may need to be changed. (See
+		 * mach/task_info.h and mach/policy.h) */
+type task_flavor_t		= int;
+type task_info_t		= array[*:87] of integer_t;
+
+type task_purgable_info_t	= struct[68] of integer_t;
+
+type task_policy_flavor_t	= natural_t;
+type task_policy_t		= array[*:16] of integer_t;
+
+type task_inspect_flavor_t = natural_t;
+type task_inspect_info_t = array[*:4] of integer_t;
+
+type task_exc_guard_behavior_t = uint32_t;
+
+type mem_entry_name_port_t = mach_port_t
+#if     KERNEL_SERVER
+		intran: mem_entry_name_port_t null_conversion(mach_port_t)
+		outtran: mach_port_t null_conversion(mem_entry_name_port_t)
+#endif  /* KERNEL_SERVER */
+		;
+
+type mem_entry_name_port_move_send_t = mach_port_move_send_t
+	cusertype: mem_entry_name_port_t
+#if     KERNEL_SERVER
+		intran: mem_entry_name_port_t null_conversion(mach_port_t)
+		outtran: mach_port_t null_conversion(mem_entry_name_port_t)
+#endif  /* KERNEL_SERVER */
+		;
+
+type memory_object_default_t = mach_port_t
+#if	KERNEL_PRIVATE
+		intran: memory_object_default_t null_conversion(mach_port_t)
+		outtran: mach_port_t null_conversion(memory_object_default_t)
+#endif	/* KERNEL_PRIVATE */
+		;
+ 
+type memory_object_t = mach_port_t
+#if	KERNEL_PRIVATE
+		intran: memory_object_t convert_port_to_memory_object(mach_port_t)
+		outtran: mach_port_t convert_memory_object_to_port(memory_object_t)
+#endif	/* KERNEL_PRIVATE */
+		;
+
+
+type memory_object_control_t = mach_port_t
+#if	KERNEL_PRIVATE
+		intran: memory_object_control_t convert_port_to_mo_control(mach_port_t)
+		outtran: mach_port_t convert_mo_control_to_port(memory_object_control_t)
+		destructor: memory_object_control_deallocate(memory_object_control_t)
+#endif	/* KERNEL_PRIVATE */
+		;
+
+type memory_object_name_t = mach_port_t
+		ctype: mach_port_t
+		;
+
+
+type memory_object_copy_strategy_t = int;
+type memory_object_return_t = int;
+
+type machine_info_data_t = struct[5] of integer_t;
+type machine_slot_data_t = struct[8] of integer_t;
+
+type host_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: host_t convert_port_to_host(mach_port_t)
+		outtran: mach_port_t convert_host_to_port(host_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type host_priv_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: host_priv_t convert_port_to_host_priv(mach_port_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type host_security_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: host_security_t convert_port_to_host_security(mach_port_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+		/* 
+		 * host_info_t: variable-sized inline array that can contain:
+		 * 
+		 * 	host_basic_info_old_t (5 ints)
+		 * 	host_basic_info_t (12 ints)
+		 * 	host_sched_info_t (2 ints)
+		 * 	kernel_resource_sizes_t (5 ints)
+		 * 	host_load_info_t (6 ints)
+		 * 	vm_statistics32_t (15 ints)
+		 * 	host_purgable_info_t (68 ints)
+		 *	host_expired_task_info uses a task_power_info (18 ints)
+		 * 
+		 * If other host_info flavors are added, this definition may
+		 * need to be changed. (See mach/{host_info,vm_statistics}.h)
+		 */
+type host_flavor_t		= int;
+type host_info_t 		= array[*:68] of integer_t;
+		/* 
+		 * host_info64_t: variable-sized inline array that can contain:
+		 * 
+		 *	vm_statistics_t (6 ints and 9 longs)
+		 *	vm_extmod_statistics_t (6 64-bit ints)
+		 */
+type host_info64_t		= array[*:256] of integer_t;
+
+type processor_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: processor_t convert_port_to_processor(mach_port_t)
+		outtran: mach_port_t convert_processor_to_port(processor_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type processor_array_t = ^array[] of processor_t;
+
+		/*
+		 * processor_info_t: variable-sized inline array that can
+		 * contain:
+		 *
+		 * - processor_basic_info_t:    (5 ints)
+		 * - processor_cpu_load_info_t: (4 ints)
+		 * - processor_machine_info_t:  (12 ints)
+		 * - processor_cpu_stat_t:      (10 ints)
+		 * - processor_cpu_stat64_t:    (20 ints)
+		 *
+		 * If other processor_info flavors are added, this definition
+		 * may need to be changed.
+		 *
+		 * See mach/processor_info.h and mach/arm/processor_info.h.
+		 */
+
+type processor_flavor_t     = int;
+type processor_info_t       = array[*:20] of integer_t;
+type processor_info_array_t = ^array[] of integer_t;
+
+type processor_set_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: processor_set_t convert_port_to_pset(mach_port_t)
+		outtran: mach_port_t convert_pset_to_port(processor_set_t)
+		destructor: pset_deallocate(processor_set_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type processor_set_array_t = ^array[] of processor_set_t;
+
+type processor_set_name_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: processor_set_name_t convert_port_to_pset_name(mach_port_t)
+		outtran: mach_port_t convert_pset_name_to_port(processor_set_name_t)
+		destructor: pset_deallocate(processor_set_name_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type processor_set_name_array_t = ^array[] of processor_set_name_t;
+
+		/* processor_set_info_t: variable-size inline array
+		 * that can hold:
+		 * processor_set_basic_info (5 ints)
+		 * processor_set_load_info (4 ints)
+		 * policy_timeshare_base_t (1 int)
+		 * policy_fifo_base_t (1 int)
+		 * policy_rr_base_t (1 int)
+		 * policy_timeshare_base_t (1 int)
+		 * policy_fifo_base_t (1 int)
+		 * policy_rr_base_t (1 int)
+		 * policy_t (1 int)
+		 * If other flavors are added, this definition may
+		 * need to be changed. (see mach/processor.h) */
+type processor_set_flavor_t	= int;
+type processor_set_info_t	= array[*:5] of integer_t;	
+
+type bootstrap_t = mach_port_t;
+
+type kernel_version_t           = c_string[*:512];
+type kernel_boot_info_t         = c_string[*:4096];
+
+type time_value_t = struct[2] of integer_t;
+
+type mach_port_qos_t = struct[2] of integer_t;
+
+type mach_port_options_t = struct[3] of uint64_t;
+type mach_port_options_ptr_t = ^ mach_port_options_t;
+
+type emulation_vector_t		= ^array[] of vm_offset_t;
+
+type inline_existence_map_t	= array[*:512] of char;
+
+type policy_t			= int;
+		/* policy_info_t: variable-size inline array. Can hold:
+		 * policy_timeshare_info_t (5 ints)
+		 * policy_fifo_info_t (4 ints)
+		 * policy_rr_info_t (5 ints) */
+type policy_base_t		= array[*:5] of integer_t;
+type policy_info_t		= array[*:2] of integer_t;
+type policy_limit_t		= array[*:1] of integer_t;
+
+type ledger_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: ledger_t convert_port_to_ledger(mach_port_t)
+		outtran: mach_port_t convert_ledger_to_port(ledger_t)
+#endif	/* KERNEL_SERVER */
+                ;
+
+type ledger_array_t	 	= ^array[] of ledger_t;
+type ledger_item_t		= integer_t;
+     				  /* DEPRECATED */
+
+type ledger_amount_t		= int64_t;
+
+type security_token_t		= struct[2] of uint32_t;
+type audit_token_t		= struct[8] of uint32_t;
+
+type msg_labels_t = mach_port_t;
+
+		/* memory_object_info_t: variable-size inline array:
+		 * memory_object_attr_info_t (5 ints)
+		 * XXX actually it's 6 ints temporarily (object_ready!)
+		 * memory_object_behave_info_t (4 ints)
+		 * memory_object_perf_info_t (2 ints)
+		 * old_memory_object_attr_info_t (3 ints)
+		 * If other flavors are added, this definition may
+		 * need to be changed. (see mach/memory_object.h) */
+type memory_object_flavor_t	= int;
+type memory_object_info_t	= array[*:6] of int;
+
+		/* vm_region_info_t: variable-size inline array that can hold:
+		 * vm_region_basic_info_t (8 ints)
+		 * If other flavors are added, this definition may
+		 * need to be changed. (see mach/vm_region.h) */
+type vm_region_flavor_t		= int;
+type vm_region_info_t		= array[*:10] of int;
+type vm_region_recurse_info_t	= array[*:19] of int;
+
+type vm_page_info_flavor_t	= int;
+type vm_page_info_t		= array[*:32] of int;
+
+type mach_vm_read_entry_t = array[512] of mach_vm_offset_t;
+type vm_read_entry_t = array[512] of vm_offset_t;
+#ifdef VM32_SUPPORT
+type vm32_read_entry_t = array[512] of vm32_offset_t;
+#endif
+
+type exception_mask_t		= int;
+type exception_behavior_t	= int;
+
+type    exception_handler_t = mach_port_t;
+
+type	exception_handler_array_t	=
+			array[*:32] of exception_handler_t;
+
+type	exception_behavior_array_t	=
+			array[*:32] of exception_behavior_t;
+
+type	exception_flavor_array_t	=
+			array[*:32] of thread_state_flavor_t;
+
+type	exception_mask_array_t	=
+			array[*:32] of exception_mask_t;
+
+type semaphore_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: semaphore_t convert_port_to_semaphore(mach_port_t)
+		outtran: mach_port_t convert_semaphore_to_port(semaphore_t)
+		destructor: semaphore_dereference(semaphore_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type semaphore_consume_ref_t = mach_port_move_send_t
+		cusertype: semaphore_t
+#if	KERNEL_SERVER
+		intran: semaphore_t convert_port_to_semaphore(mach_port_t)
+		outtran: mach_port_t convert_semaphore_to_port(semaphore_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type lock_set_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: lock_set_t convert_port_to_lock_set(mach_port_t)
+		outtran: mach_port_t convert_lock_set_to_port(lock_set_t)
+		destructor: lock_set_dereference(lock_set_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type task_suspension_token_t = mach_port_move_send_once_t
+#if	KERNEL_SERVER
+		intran: task_suspension_token_t convert_port_to_task_suspension_token(mach_port_t)
+		outtran: mach_port_t convert_task_suspension_token_to_port(task_suspension_token_t)
+#endif	/* KERNEL_SERVER */
+		;
+
+type vfs_path_t = c_string[4096];
+type nspace_path_t = c_string[1024];	/* 1024 == PATH_MAX */
+
+/* public voucher types */
+
+/* Mach voucher object */
+type mach_voucher_t = mach_port_t;
+type mach_voucher_name_t = mach_port_name_t;
+
+type mach_voucher_attr_manager_t = mach_port_t;
+type mach_voucher_attr_control_t = mach_port_t;
+
+/* IPC voucher internal object */
+type ipc_voucher_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: ipc_voucher_t convert_port_to_voucher(mach_port_t)
+		outtran: mach_port_t convert_voucher_to_port(ipc_voucher_t)
+		destructor: ipc_voucher_release(ipc_voucher_t)
+#endif	/* KERNEL_SERVER */
+	        ;
+
+/* IPC voucher attribute control internal object */
+type ipc_voucher_attr_control_t = mach_port_t
+#if	KERNEL_SERVER
+		intran: ipc_voucher_attr_control_t convert_port_to_voucher_attr_control(mach_port_t)
+		outtran: mach_port_t convert_voucher_attr_control_to_port(ipc_voucher_attr_control_t)
+		destructor: ipc_voucher_attr_control_release(ipc_voucher_attr_control_t)
+#endif	/* KERNEL_SERVER */
+                ;
+
+type mach_voucher_attr_key_t = uint32_t;
+
+type mach_voucher_attr_command_t = uint32_t;
+type mach_voucher_attr_recipe_command_t = uint32_t;
+
+type mach_voucher_attr_content_size_t = uint32_t;
+type mach_voucher_attr_content_t = array[*:4096] of uint8_t;
+type mach_voucher_attr_content_array_t = array[*:5120] of uint8_t;
+
+type mach_voucher_attr_raw_recipe_size_t = uint32_t;
+type mach_voucher_attr_raw_recipe_t = array[*:4096] of uint8_t;
+type mach_voucher_attr_raw_recipe_array_t = array[*:5120] of uint8_t;
+
+type mach_voucher_selector_t = uint32_t;
+
+type mach_voucher_attr_value_handle_t = uint64_t;
+type mach_voucher_attr_value_handle_array_t = array[*:4] of mach_voucher_attr_value_handle_t;
+type mach_voucher_attr_value_reference_t = uint32_t;
+
+/* kernel module loader */
+type kmod_t = int;
+type kmod_control_flavor_t = int;
+
+type kmod_args_t = ^array[] of MACH_MSG_TYPE_BYTE
+	ctype: kmod_args_t;
+
+type io_master_t = mach_port_t;
+type UNDServerRef = mach_port_t;
+
+/* These must be kept in sync with definitions in osfmk/mach/dyld_kernel.h */
+type dyld_kernel_image_info_t = struct[40] of MACH_MSG_TYPE_BYTE;
+type dyld_kernel_image_info_array_t = ^array[] of dyld_kernel_image_info_t;
+type dyld_kernel_process_info_t = struct[64] of MACH_MSG_TYPE_BYTE;
+
+#if KERNEL_SERVER
+#ifdef	MACH_KERNEL_PRIVATE
+simport <ipc/ipc_voucher.h>;	/* for voucher conversions */
+simport <kern/ipc_kobject.h>;	/* for null conversion */
+simport <kern/ipc_tt.h>;	/* for task/thread conversion */
+simport <kern/ipc_host.h>;	/* for host/processor/pset conversions */
+simport <kern/ipc_sync.h>;      /* for lock_set and semaphore conversions */
+simport <kern/ledger.h>;	/* for ledger conversions */
+simport <kern/processor.h>;	/* for processor conversions */
+simport <kern/sync_lock.h>;	/* for lock-set conversions */
+simport <kern/sync_sema.h>;	/* for semaphore conversions */
+simport <vm/memory_object.h>;	/* for memory object type conversions */
+simport <vm/vm_map.h>;		/* for vm_map conversions */
+#if CONFIG_ARCADE
+simport <kern/arcade.h>;    /* for arcade_register conversions */
+#endif
+#endif	/* MACH_KERNEL_PRIVATE */
+
+simport <kern/ipc_mig.h>;	/* pick up kernel-specific MIG things */
+
+#endif /* KERNEL_SERVER */
+
+import <mach/mig.h>;
+import <mach/mach_types.h>;
+
+#endif	/* _MACH_MACH_TYPES_DEFS_ */
+
+/* vim: set ft=c : */
diff --git a/third_party/xnu/osfmk/mach/machine/machine_types.defs b/third_party/xnu/osfmk/mach/machine/machine_types.defs
new file mode 100644
index 0000000..f481394
--- /dev/null
+++ b/third_party/xnu/osfmk/mach/machine/machine_types.defs
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2000-2007 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/*
+ * @OSF_COPYRIGHT@
+ */
+
+/*
+ *	Header file for basic, machine-dependent data types.  arm+i386 version.
+ */
+ 
+#ifndef _MACH_MACHINE_MACHNINE_TYPES_DEFS
+#define _MACH_MACHINE_MACHNINE_TYPES_DEFS
+
+type short = int16_t;
+type int = int32_t;
+type unsigned = uint32_t;
+
+type float = MACH_MSG_TYPE_REAL_32;
+type double = MACH_MSG_TYPE_REAL_64;
+
+
+/* from ISO/IEC 988:1999 spec */
+/* 7.18.1.4 Integer types capable of hgolding object pointers */
+/*
+ * The [u]intptr_t types for the native
+ * integer type, e.g. 32 or 64 or.. whatever
+ * register size the machine has.  They are
+ * used for entities that might be either
+ * [unsigned] integers or pointers, and for
+ * type-casting between the two.
+ *
+ * For instance, the IPC system represents
+ * a port in user space as an integer and
+ * in kernel space as a pointer.
+ */
+#if defined(__LP64__)
+type uintptr_t = uint64_t;
+type intptr_t = int64_t;
+#else
+type uintptr_t = uint32_t;
+type intptr_t = int32_t;
+#endif
+
+/*
+ * These are the legacy Mach types that are
+ * the [rough] equivalents of the standards above.
+ * They were defined in terms of int, not
+ * long int, so they remain separate.
+ */
+#if defined(__LP64__)
+type register_t = int64_t;
+#else
+type register_t = int32_t;
+#endif
+type integer_t = int32_t;
+type natural_t = uint32_t;
+
+/*
+ * These are the VM types that scale with the address
+ * space size of a given process.
+ */
+
+#if defined(__LP64__)
+type vm_address_t = uint64_t;
+type vm_offset_t = uint64_t;
+type vm_size_t = uint64_t;
+#else
+type vm_address_t = natural_t;
+type vm_offset_t = natural_t;
+type vm_size_t = natural_t;
+#endif
+
+/* This is a bit of a hack for arm.  We implement the backend with a wide type, but present a native-sized type to callers */
+type mach_port_context_t = uint64_t;
+
+/*
+ * The mach_vm_xxx_t types are sized to hold the
+ * maximum pointer, offset, etc... supported on the
+ * platform.
+ */
+type mach_vm_address_t = uint64_t;
+type mach_vm_offset_t = uint64_t;
+type mach_vm_size_t = uint64_t;
+
+#if	MACH_IPC_COMPAT
+/*
+ * For the old IPC interface
+ */
+#define	MSG_TYPE_PORT_NAME	natural_t
+
+#endif	/* MACH_IPC_COMPAT */
+
+/*
+ * These are types used internal to Mach to implement the
+ * legacy 32-bit VM APIs published by the kernel.
+ */
+#define	VM32_SUPPORT	1
+
+type vm32_address_t = uint32_t;
+type vm32_offset_t = uint32_t;
+type vm32_size_t = uint32_t;
+
+#endif /* _MACH_MACHINE_MACHNINE_TYPES_DEFS */
+
+/* vim: set ft=c : */
diff --git a/third_party/xnu/osfmk/mach/std_types.defs b/third_party/xnu/osfmk/mach/std_types.defs
new file mode 100644
index 0000000..0b48383
--- /dev/null
+++ b/third_party/xnu/osfmk/mach/std_types.defs
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2002,2000 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/*
+ * @OSF_COPYRIGHT@
+ */
+/* 
+ * Mach Operating System
+ * Copyright (c) 1991,1990,1989,1988 Carnegie Mellon University
+ * All Rights Reserved.
+ * 
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ * 
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ * 
+ * Carnegie Mellon requests users of this software to return to
+ * 
+ *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
+ *  School of Computer Science
+ *  Carnegie Mellon University
+ *  Pittsburgh PA 15213-3890
+ * 
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+/*
+ */
+/*
+ *	Mach kernel standard interface type declarations
+ */
+
+#ifndef	_MACH_STD_TYPES_DEFS_
+#define _MACH_STD_TYPES_DEFS_
+
+/* from ISO/IEC 988:1999 spec */
+/* 7.18.1.1 Exact-width integer types */
+
+type int8_t = MACH_MSG_TYPE_INTEGER_8;
+type uint8_t = MACH_MSG_TYPE_INTEGER_8;
+type int16_t = MACH_MSG_TYPE_INTEGER_16;
+type uint16_t = MACH_MSG_TYPE_INTEGER_16;
+type int32_t = MACH_MSG_TYPE_INTEGER_32;
+type uint32_t = MACH_MSG_TYPE_INTEGER_32;
+type int64_t = MACH_MSG_TYPE_INTEGER_64;
+type uint64_t = MACH_MSG_TYPE_INTEGER_64;
+
+/*
+ * Legacy fixed-length Mach types which should
+ * be replaced with the Standard types from above.
+ */
+type int32 = int32_t;
+type unsigned32 = uint32_t;
+type int64 = int64_t;
+type unsigned64 = uint64_t;
+
+/*
+ * Other fixed length Mach types.
+ */
+type char = MACH_MSG_TYPE_CHAR;
+type boolean_t = MACH_MSG_TYPE_BOOLEAN;
+
+#include <mach/machine/machine_types.defs>
+
+type kern_return_t = int;
+
+type pointer_t = ^array[] of MACH_MSG_TYPE_BYTE
+	ctype: vm_offset_t;
+
+
+type mach_port_t = MACH_MSG_TYPE_COPY_SEND;
+type mach_port_array_t = array[] of mach_port_t;
+
+type mach_port_name_t = MACH_MSG_TYPE_PORT_NAME;
+type mach_port_name_array_t = array[] of mach_port_name_t;
+
+type mach_port_right_t = natural_t;
+
+type mach_port_type_t = natural_t;
+type mach_port_type_array_t = array[] of mach_port_type_t;
+
+type mach_port_urefs_t = natural_t;
+type mach_port_delta_t = integer_t;
+type mach_port_seqno_t = natural_t;
+type mach_port_mscount_t = unsigned;
+type mach_port_msgcount_t = unsigned;
+type mach_port_rights_t = unsigned;
+type mach_msg_id_t = integer_t;
+type mach_msg_size_t = natural_t;
+type mach_msg_type_name_t = unsigned;
+type mach_msg_options_t = integer_t;
+
+type mach_port_move_receive_t =		MACH_MSG_TYPE_MOVE_RECEIVE
+	ctype: mach_port_t;
+type mach_port_copy_send_t =		MACH_MSG_TYPE_COPY_SEND
+	ctype: mach_port_t;
+type mach_port_make_send_t =		MACH_MSG_TYPE_MAKE_SEND
+	ctype: mach_port_t;
+type mach_port_move_send_t =		MACH_MSG_TYPE_MOVE_SEND
+	ctype: mach_port_t;
+type mach_port_make_send_once_t =	MACH_MSG_TYPE_MAKE_SEND_ONCE
+	ctype: mach_port_t;
+type mach_port_move_send_once_t =	MACH_MSG_TYPE_MOVE_SEND_ONCE
+	ctype: mach_port_t;
+
+type mach_port_receive_t =		MACH_MSG_TYPE_PORT_RECEIVE
+	ctype: mach_port_t;
+type mach_port_send_t =			MACH_MSG_TYPE_PORT_SEND
+	ctype: mach_port_t;
+type mach_port_send_once_t =		MACH_MSG_TYPE_PORT_SEND_ONCE
+	ctype: mach_port_t;
+
+type mach_port_poly_t = polymorphic
+	ctype: mach_port_t;
+
+import <mach/std_types.h>;
+import <mach/mig.h>;
+
+#endif	/* _MACH_STD_TYPES_DEFS_ */
+
+/* vim: set ft=c : */
diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn
index 0723ba3..e5a2ad3 100644
--- a/third_party/zlib/BUILD.gn
+++ b/third_party/zlib/BUILD.gn
@@ -14,7 +14,7 @@
 
 import("../../build/crashpad_buildconfig.gni")
 
-if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
+if (crashpad_is_in_chromium || crashpad_is_in_fuchsia || crashpad_is_in_dart) {
   zlib_source = "external"
 } else if (!crashpad_is_win && !crashpad_is_fuchsia) {
   zlib_source = "system"
@@ -36,9 +36,7 @@
 if (zlib_source == "external") {
   group("zlib") {
     public_configs = [ ":zlib_config" ]
-    public_deps = [
-      "//third_party/zlib",
-    ]
+    public_deps = [ "//third_party/zlib" ]
   }
 } else if (zlib_source == "system") {
   source_set("zlib") {
@@ -73,9 +71,9 @@
       "zlib/uncompr.c",
       "zlib/zconf.h",
       "zlib/zlib.h",
-      "zlib/zlib_crashpad.h",
       "zlib/zutil.c",
       "zlib/zutil.h",
+      "zlib_crashpad.h",
     ]
 
     cflags = []
@@ -91,6 +89,12 @@
         "/wd4324",  # structure was padded due to alignment specifier
         "/wd4702",  # unreachable code
       ]
+      if (current_cpu == "arm64" && !crashpad_is_clang) {
+        # Select code path for clang in zlib to avoid using MSVC x86/x64
+        # intrinsics for Windows ARM64.
+        # TODO: https://crashpad.chromium.org/bug/267
+        defines += [ "__clang__" ]
+      }
     } else {
       defines += [
         "HAVE_HIDDEN",
@@ -98,6 +102,10 @@
       ]
     }
 
+    configs -= [
+      "//third_party/mini_chromium/mini_chromium/build:Wimplicit_fallthrough",
+    ]
+
     if (current_cpu == "x86" || current_cpu == "x64") {
       sources += [
         "zlib/crc_folding.c",
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index cd1e95f..5e0228b 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -22,80 +22,83 @@
 
   public_configs = [ "..:crashpad_config" ]
 
-  deps = [
-    "../third_party/mini_chromium:base",
-  ]
+  deps = [ "../third_party/mini_chromium:base" ]
 }
 
-crashpad_executable("crashpad_database_util") {
-  sources = [
-    "crashpad_database_util.cc",
-  ]
+if (!crashpad_is_ios) {
+  crashpad_executable("crashpad_database_util") {
+    sources = [ "crashpad_database_util.cc" ]
 
-  deps = [
-    ":tool_support",
-    "../build:default_exe_manifest_win",
-    "../client",
-    "../compat",
-    "../third_party/mini_chromium:base",
-    "../util",
-  ]
-}
-
-crashpad_executable("crashpad_http_upload") {
-  sources = [
-    "crashpad_http_upload.cc",
-  ]
-
-  deps = [
-    ":tool_support",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../third_party/mini_chromium:base",
-    "../util",
-  ]
-}
-
-crashpad_executable("generate_dump") {
-  sources = [
-    "generate_dump.cc",
-  ]
-
-  deps = [
-    ":tool_support",
-    "../build:default_exe_manifest_win",
-    "../compat",
-    "../minidump",
-    "../snapshot",
-    "../third_party/mini_chromium:base",
-    "../util",
-  ]
-
-  if (crashpad_is_mac) {
-    # This would be better as a config so that it could be shared with
-    # exception_port_tool, but configs can’t alter “inputs”.
-    # https://crbug.com/781858.
-    inputs = [
-      "mac/sectaskaccess_info.plist",
-    ]
-    ldflags = [
-      "-sectcreate",
-      "__TEXT",
-      "__info_plist",
-      rebase_path(inputs[0], root_build_dir),
+    deps = [
+      ":tool_support",
+      "../build:default_exe_manifest_win",
+      "../client",
+      "../compat",
+      "../third_party/mini_chromium:base",
+      "../util",
     ]
   }
 
-  if (crashpad_is_win) {
-    cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
+  crashpad_executable("crashpad_http_upload") {
+    sources = [ "crashpad_http_upload.cc" ]
+
+    deps = [
+      ":tool_support",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../third_party/mini_chromium:base",
+      "../util",
+    ]
+  }
+}
+
+crashpad_executable("base94_encoder") {
+  sources = [ "base94_encoder.cc" ]
+  deps = [
+    ":tool_support",
+    "../build:default_exe_manifest_win",
+    "../third_party/mini_chromium:base",
+    "../util",
+  ]
+}
+
+if (!crashpad_is_fuchsia && !crashpad_is_ios) {
+  crashpad_executable("generate_dump") {
+    sources = [ "generate_dump.cc" ]
+
+    deps = [
+      ":tool_support",
+      "../build:default_exe_manifest_win",
+      "../compat",
+      "../minidump",
+      "../snapshot",
+      "../third_party/mini_chromium:base",
+      "../util",
+    ]
+
+    if (crashpad_is_mac) {
+      # This would be better as a config so that it could be shared with
+      # exception_port_tool, but configs can’t alter “inputs”.
+      # https://crbug.com/781858.
+      inputs = [ "mac/sectaskaccess_info.plist" ]
+      ldflags = [
+        "-sectcreate",
+        "__TEXT",
+        "__info_plist",
+        rebase_path(inputs[0], root_build_dir),
+      ]
+    }
+
+    if (crashpad_is_win) {
+      cflags =
+          [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
+    }
   }
 }
 
 if (crashpad_is_mac || crashpad_is_fuchsia) {
   crashpad_executable("run_with_crashpad") {
-    sources = [
-      "run_with_crashpad.cc",
-    ]
+    sources = [ "run_with_crashpad.cc" ]
 
     deps = [
       ":tool_support",
@@ -109,9 +112,7 @@
 
 if (crashpad_is_mac) {
   crashpad_executable("catch_exception_tool") {
-    sources = [
-      "mac/catch_exception_tool.cc",
-    ]
+    sources = [ "mac/catch_exception_tool.cc" ]
 
     deps = [
       ":tool_support",
@@ -122,15 +123,11 @@
   }
 
   crashpad_executable("exception_port_tool") {
-    sources = [
-      "mac/exception_port_tool.cc",
-    ]
+    sources = [ "mac/exception_port_tool.cc" ]
 
     # This would be better as a config so that it could be shared with
     # generate_dump, but configs can’t alter “inputs”. https://crbug.com/781858.
-    inputs = [
-      "mac/sectaskaccess_info.plist",
-    ]
+    inputs = [ "mac/sectaskaccess_info.plist" ]
     ldflags = [
       "-sectcreate",
       "__TEXT",
@@ -147,9 +144,7 @@
   }
 
   crashpad_executable("on_demand_service_tool") {
-    sources = [
-      "mac/on_demand_service_tool.mm",
-    ]
+    sources = [ "mac/on_demand_service_tool.mm" ]
 
     libs = [
       "CoreFoundation.framework",
diff --git a/tools/base94_encoder.cc b/tools/base94_encoder.cc
new file mode 100644
index 0000000..1b184e9
--- /dev/null
+++ b/tools/base94_encoder.cc
@@ -0,0 +1,128 @@
+// Copyright 2019 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.
+
+#include <getopt.h>
+#include <stdio.h>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "tools/tool_support.h"
+#include "util/stream/file_encoder.h"
+
+namespace crashpad {
+namespace {
+
+void Usage(const base::FilePath& me) {
+  fprintf(stderr,
+"Usage: %" PRFilePath " [options] <input-file> <output-file>\n"
+"Encode/Decode the given file\n"
+"\n"
+"  -e, --encode   compress and encode the input file to a base94 encoded"
+                  " file\n"
+"  -d, --decode   decode and decompress a base94 encoded file\n"
+"      --help     display this help and exit\n"
+"      --version  output version information and exit\n",
+          me.value().c_str());
+  ToolSupport::UsageTail(me);
+}
+
+int Base94EncoderMain(int argc, char* argv[]) {
+  const base::FilePath argv0(
+      ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
+  const base::FilePath me(argv0.BaseName());
+
+  enum OptionFlags {
+    // “Short” (single-character) options.
+    kOptionEncode = 'e',
+    kOptionDecode = 'd',
+
+    // Standard options.
+    kOptionHelp = -2,
+    kOptionVersion = -3,
+  };
+
+  struct Options {
+    bool encoding;
+    base::FilePath input_file;
+    base::FilePath output_file;
+  } options = {};
+
+  static constexpr option long_options[] = {
+      {"encode", no_argument, nullptr, kOptionEncode},
+      {"decode", no_argument, nullptr, kOptionDecode},
+      {"help", no_argument, nullptr, kOptionHelp},
+      {"version", no_argument, nullptr, kOptionVersion},
+      {nullptr, 0, nullptr, 0},
+  };
+
+  bool encoding_valid = false;
+  int opt;
+  while ((opt = getopt_long(argc, argv, "de", long_options, nullptr)) != -1) {
+    switch (opt) {
+      case kOptionEncode:
+        options.encoding = true;
+        encoding_valid = true;
+        break;
+      case kOptionDecode:
+        options.encoding = false;
+        encoding_valid = true;
+        break;
+      case kOptionHelp:
+        Usage(me);
+        return EXIT_SUCCESS;
+      case kOptionVersion:
+        ToolSupport::Version(me);
+        return EXIT_SUCCESS;
+      default:
+        ToolSupport::UsageHint(me, nullptr);
+        return EXIT_FAILURE;
+    }
+  }
+
+  if (!encoding_valid) {
+    ToolSupport::UsageHint(me, "Either -e or -d required");
+    return EXIT_FAILURE;
+  }
+
+  argc -= optind;
+  argv += optind;
+  if (argc != 2) {
+    ToolSupport::UsageHint(me, "Both input-file and output-file required");
+    return EXIT_FAILURE;
+  }
+
+  options.input_file = base::FilePath(
+      ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
+  options.output_file = base::FilePath(
+      ToolSupport::CommandLineArgumentToFilePathStringType(argv[1]));
+
+  FileEncoder encoder(options.encoding ? crashpad::FileEncoder::Mode::kEncode
+                                       : crashpad::FileEncoder::Mode::kDecode,
+                      options.input_file,
+                      options.output_file);
+  return encoder.Process() ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+}  // namespace
+}  // namespace crashpad
+
+#if defined(OS_POSIX)
+int main(int argc, char* argv[]) {
+  return crashpad::Base94EncoderMain(argc, argv);
+}
+#elif defined(OS_WIN)
+int wmain(int argc, wchar_t* argv[]) {
+  return crashpad::ToolSupport::Wmain(argc, argv, crashpad::Base94EncoderMain);
+}
+#endif  // OS_POSIX
diff --git a/tools/base94_encoder.md b/tools/base94_encoder.md
new file mode 100644
index 0000000..292c7a6
--- /dev/null
+++ b/tools/base94_encoder.md
@@ -0,0 +1,100 @@
+<!--
+Copyright 2020 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.
+-->
+
+# base94_encoder(1)
+
+## Name
+
+base94_encoder—Encode/Decode the given file
+
+## Synopsis
+
+**base94_encoder** [_OPTION…_] input-file output-file
+
+## Description
+
+Encodes a file for printing safely by compressing and base94 encoding it.
+
+The base94_encoder can decode the input file by base94 decoding and
+uncompressing it.
+
+## Options
+
+ * **-e**, **--encode**
+
+   Compress and encode the input file to a base94 encoded file.
+
+ * **-d**, **--decode**
+
+   Decode and decompress a base94 encoded file.
+
+ * **--help**
+
+   Display help and exit.
+
+ * **--version**
+
+   Output version information and exit.
+
+## Examples
+
+Encode file a to b:
+
+```
+$ base94_encoder --encode a b
+```
+
+Decode file b to a
+
+```
+$ base94_encoder --decode b a
+```
+
+## Exit Status
+
+ * **0**
+
+   Success.
+
+ * **1**
+
+   Failure, with a message printed to the standard error stream.
+
+
+## Resources
+
+Crashpad home page: https://crashpad.chromium.org/.
+
+Report bugs at https://crashpad.chromium.org/bug/new.
+
+## Copyright
+
+Copyright 2020 [The Crashpad
+Authors](https://chromium.googlesource.com/crashpad/crashpad/+/master/AUTHORS).
+
+## License
+
+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.
diff --git a/tools/crashpad_database_util.cc b/tools/crashpad_database_util.cc
index b4c2a15..a003616 100644
--- a/tools/crashpad_database_util.cc
+++ b/tools/crashpad_database_util.cc
@@ -28,8 +28,8 @@
 
 #include "base/files/file_path.h"
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "client/crash_report_database.h"
@@ -109,14 +109,14 @@
       "set",
   };
 
-  for (size_t index = 0; index < arraysize(kFalseWords); ++index) {
+  for (size_t index = 0; index < base::size(kFalseWords); ++index) {
     if (strcasecmp(string, kFalseWords[index]) == 0) {
       *boolean = false;
       return true;
     }
   }
 
-  for (size_t index = 0; index < arraysize(kTrueWords); ++index) {
+  for (size_t index = 0; index < base::size(kTrueWords); ++index) {
     if (strcasecmp(string, kTrueWords[index]) == 0) {
       *boolean = true;
       return true;
@@ -159,7 +159,7 @@
       "%+",
   };
 
-  for (size_t index = 0; index < arraysize(kFormats); ++index) {
+  for (size_t index = 0; index < base::size(kFormats); ++index) {
     tm time_tm;
     const char* strptime_result = strptime(string, kFormats[index], &time_tm);
     if (strptime_result == end) {
@@ -214,7 +214,7 @@
 
   char string[64];
   CHECK_NE(
-      strftime(string, arraysize(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm),
+      strftime(string, base::size(string), "%Y-%m-%d %H:%M:%S %Z", &time_tm),
       0u);
 
   return std::string(string);
@@ -561,7 +561,7 @@
   }
 
   bool used_stdin = false;
-  for (const base::FilePath new_report_path : options.new_report_paths) {
+  for (const base::FilePath& new_report_path : options.new_report_paths) {
     std::unique_ptr<FileReaderInterface> file_reader;
 
     if (new_report_path.value() == FILE_PATH_LITERAL("-")) {
diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc
index 2cdaed2..2113a1a 100644
--- a/tools/generate_dump.cc
+++ b/tools/generate_dump.cc
@@ -26,11 +26,13 @@
 #include "minidump/minidump_file_writer.h"
 #include "tools/tool_support.h"
 #include "util/file/file_writer.h"
-#include "util/posix/drop_privileges.h"
+#include "util/process/process_id.h"
 #include "util/stdlib/string_number_conversion.h"
 
 #if defined(OS_POSIX)
 #include <unistd.h>
+
+#include "util/posix/drop_privileges.h"
 #endif
 
 #if defined(OS_MACOSX)
@@ -45,11 +47,6 @@
 #include "snapshot/win/process_snapshot_win.h"
 #include "util/win/scoped_process_suspend.h"
 #include "util/win/xp_compat.h"
-#elif defined(OS_FUCHSIA)
-#include "base/fuchsia/scoped_zx_handle.h"
-#include "snapshot/fuchsia/process_snapshot_fuchsia.h"
-#include "util/fuchsia/koid_utilities.h"
-#include "util/fuchsia/scoped_task_suspend.h"
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
 #include "snapshot/linux/process_snapshot_linux.h"
 #endif  // OS_MACOSX
@@ -90,7 +87,7 @@
 
   struct {
     std::string dump_path;
-    pid_t pid;
+    ProcessID pid;
     bool suspend;
   } options = {};
   options.suspend = true;
@@ -164,16 +161,11 @@
     PLOG(ERROR) << "could not open process " << options.pid;
     return EXIT_FAILURE;
   }
-#elif defined(OS_FUCHSIA)
-  base::ScopedZxHandle task = GetProcessFromKoid(options.pid);
-  if (!task.is_valid()) {
-    LOG(ERROR) << "could not open process " << options.pid;
-    return EXIT_FAILURE;
-  }
 #endif  // OS_MACOSX
 
   if (options.dump_path.empty()) {
-    options.dump_path = base::StringPrintf("minidump.%d", options.pid);
+    options.dump_path = base::StringPrintf("minidump.%" PRI_PROCESS_ID,
+                                           options.pid);
   }
 
   {
@@ -187,11 +179,6 @@
     if (options.suspend) {
       suspend.reset(new ScopedProcessSuspend(process.get()));
     }
-#elif defined(OS_FUCHSIA)
-    std::unique_ptr<ScopedTaskSuspend> suspend;
-    if (options.suspend) {
-      suspend.reset(new ScopedTaskSuspend(task.get()));
-    }
 #endif  // OS_MACOSX
 
 #if defined(OS_MACOSX)
@@ -209,11 +196,6 @@
                                      0)) {
       return EXIT_FAILURE;
     }
-#elif defined(OS_FUCHSIA)
-    ProcessSnapshotFuchsia process_snapshot;
-    if (!process_snapshot.Initialize(task.get())) {
-      return EXIT_FAILURE;
-    }
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
     // TODO(jperaza): https://crashpad.chromium.org/bug/30.
     ProcessSnapshotLinux process_snapshot;
diff --git a/tools/mac/catch_exception_tool.cc b/tools/mac/catch_exception_tool.cc
index d4dc3c1..4f40372 100644
--- a/tools/mac/catch_exception_tool.cc
+++ b/tools/mac/catch_exception_tool.cc
@@ -28,6 +28,7 @@
 #include "base/logging.h"
 #include "base/mac/mach_logging.h"
 #include "tools/tool_support.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/exc_server_variants.h"
 #include "util/mach/exception_behaviors.h"
 #include "util/mach/exception_types.h"
diff --git a/tools/mac/exception_port_tool.cc b/tools/mac/exception_port_tool.cc
index a047e96..b9d3fdd 100644
--- a/tools/mac/exception_port_tool.cc
+++ b/tools/mac/exception_port_tool.cc
@@ -30,6 +30,7 @@
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
 #include "tools/tool_support.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/exception_ports.h"
 #include "util/mach/mach_extensions.h"
 #include "util/mach/symbolic_constants_mach.h"
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 03220c9..d32863a 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -13,13 +13,17 @@
 # limitations under the License.
 
 import("../build/crashpad_buildconfig.gni")
+import("net/tls.gni")
 
-declare_args() {
-  use_boringssl_for_http_transport_socket = crashpad_is_fuchsia
+if (crashpad_is_in_chromium) {
+  import("//build/config/sanitizers/sanitizers.gni")
+
+  # Prevent Chromium source assignment filters from being inherited.
+  set_sources_assignment_filter([])
 }
 
-if (crashpad_is_mac) {
-  if (crashpad_is_in_chromium) {
+if (crashpad_is_mac || crashpad_is_ios) {
+  if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
     import("//build/config/sysroot.gni")
   } else {
     import("//third_party/mini_chromium/mini_chromium/build/sysroot.gni")
@@ -27,14 +31,26 @@
 
   action_foreach("mig") {
     script = "mach/mig.py"
-
-    sources = [
-      "$sysroot/usr/include/mach/exc.defs",
-      "$sysroot/usr/include/mach/mach_exc.defs",
-      "$sysroot/usr/include/mach/notify.defs",
-      "mach/child_port.defs",
+    inputs = [
+      "mach/mig_fix.py",
+      "mach/mig_gen.py",
     ]
 
+    if (crashpad_is_mac) {
+      sources = [
+        "$sysroot/usr/include/mach/exc.defs",
+        "$sysroot/usr/include/mach/mach_exc.defs",
+        "$sysroot/usr/include/mach/notify.defs",
+        "mach/child_port.defs",
+      ]
+    } else if (crashpad_is_ios) {
+      sources = [
+        # The iOS SDK doesn’t have any .defs files. Get them directly from xnu.
+        "../third_party/xnu/osfmk/mach/exc.defs",
+        "../third_party/xnu/osfmk/mach/mach_exc.defs",
+      ]
+    }
+
     outputs = [
       "$target_gen_dir/mach/{{source_name_part}}User.c",
       "$target_gen_dir/mach/{{source_name_part}}Server.c",
@@ -46,9 +62,20 @@
     args += rebase_path(outputs, root_build_dir)
     if (crashpad_is_in_chromium) {
       if (!use_system_xcode) {
+        import("//build/config/clang/clang.gni")
+        import("//build/config/mac/mac_sdk.gni")
+        clang_path =
+            rebase_path("$clang_base_path/bin/", root_build_dir) + "clang"
+        mig_path = "$mac_bin_path" + "mig"
+        migcom_path = "$mac_bin_path" + "../libexec/migcom"
+
         args += [
-          "--developer-dir",
-          hermetic_xcode_path,
+          "--clang-path",
+          clang_path,
+          "--mig-path",
+          mig_path,
+          "--migcom-path",
+          migcom_path,
         ]
       }
     }
@@ -60,8 +87,48 @@
     }
     args += [
       "--include",
+      rebase_path("..", root_build_dir),
+      "--include",
       rebase_path("../compat/mac", root_build_dir),
     ]
+    if (crashpad_is_ios) {
+      args += [
+        "--include",
+        rebase_path("../compat/ios", root_build_dir),
+      ]
+    }
+    if (current_cpu == "x86") {
+      args += [
+        "--arch",
+        "i386",
+      ]
+    } else if (current_cpu == "x64") {
+      args += [
+        "--arch",
+        "x86_64",
+      ]
+    } else if (current_cpu == "arm") {
+      args += [
+        "--arch",
+        "armv7",
+      ]
+    } else if (current_cpu == "arm64") {
+      args += [
+        "--arch",
+        "arm64",
+      ]
+    }
+  }
+
+  static_library("mig_output") {
+    deps = [ ":mig" ]
+    sources = get_target_outputs(":mig")
+    if (crashpad_is_in_chromium) {
+      # mig output contains unreachable code, which irks -Wunreachable-code.
+      configs -= [ "//build/config/compiler:chromium_code" ]
+      configs += [ "//build/config/compiler:no_chromium_code" ]
+    }
+    public_configs = [ "..:crashpad_config" ]
   }
 }
 
@@ -79,13 +146,15 @@
     "file/file_writer.cc",
     "file/file_writer.h",
     "file/filesystem.h",
+    "file/output_stream_file_writer.cc",
+    "file/output_stream_file_writer.h",
     "file/scoped_remove_file.cc",
     "file/scoped_remove_file.h",
     "file/string_file.cc",
     "file/string_file.h",
     "misc/address_sanitizer.h",
     "misc/address_types.h",
-    "misc/arraysize_unsafe.h",
+    "misc/arraysize.h",
     "misc/as_underlying_type.h",
     "misc/capture_context.h",
     "misc/clock.h",
@@ -97,6 +166,7 @@
     "misc/initialization_state_dcheck.h",
     "misc/lexing.cc",
     "misc/lexing.h",
+    "misc/memory_sanitizer.h",
     "misc/metrics.cc",
     "misc/metrics.h",
     "misc/paths.h",
@@ -136,6 +206,12 @@
     "numeric/in_range_cast.h",
     "numeric/int128.h",
     "numeric/safe_assignment.h",
+    "process/process_id.h",
+    "process/process_memory.cc",
+    "process/process_memory.h",
+    "process/process_memory_native.h",
+    "process/process_memory_range.cc",
+    "process/process_memory_range.h",
     "stdlib/aligned_allocator.cc",
     "stdlib/aligned_allocator.h",
     "stdlib/map_insert.h",
@@ -147,6 +223,17 @@
     "stdlib/strnlen.cc",
     "stdlib/strnlen.h",
     "stdlib/thread_safe_vector.h",
+    "stream/base94_output_stream.cc",
+    "stream/base94_output_stream.h",
+    "stream/file_encoder.cc",
+    "stream/file_encoder.h",
+    "stream/file_output_stream.cc",
+    "stream/file_output_stream.h",
+    "stream/log_output_stream.cc",
+    "stream/log_output_stream.h",
+    "stream/output_stream_interface.h",
+    "stream/zlib_output_stream.cc",
+    "stream/zlib_output_stream.h",
     "string/split_string.cc",
     "string/split_string.h",
     "synchronization/semaphore.h",
@@ -159,6 +246,8 @@
     "thread/worker_thread.h",
   ]
 
+  defines = [ "ZLIB_CONST" ]
+
   if (crashpad_is_posix || crashpad_is_fuchsia) {
     sources += [
       "file/directory_reader_posix.cc",
@@ -196,22 +285,10 @@
     }
   }
 
-  if (crashpad_is_mac) {
+  if (crashpad_is_mac || crashpad_is_ios) {
     sources += [
-      "mac/checked_mach_address_range.h",
-      "mac/launchd.h",
-      "mac/launchd.mm",
-      "mac/mac_util.cc",
-      "mac/mac_util.h",
-      "mac/service_management.cc",
-      "mac/service_management.h",
       "mac/xattr.cc",
       "mac/xattr.h",
-      "mach/child_port_handshake.cc",
-      "mach/child_port_handshake.h",
-      "mach/child_port_server.cc",
-      "mach/child_port_server.h",
-      "mach/child_port_types.h",
       "mach/composite_mach_message_server.cc",
       "mach/composite_mach_message_server.h",
       "mach/exc_client_variants.cc",
@@ -222,14 +299,37 @@
       "mach/exception_behaviors.h",
       "mach/exception_ports.cc",
       "mach/exception_ports.h",
-      "mach/exception_types.cc",
-      "mach/exception_types.h",
       "mach/mach_extensions.cc",
       "mach/mach_extensions.h",
       "mach/mach_message.cc",
       "mach/mach_message.h",
       "mach/mach_message_server.cc",
       "mach/mach_message_server.h",
+      "misc/capture_context_mac.S",
+      "misc/clock_mac.cc",
+      "misc/paths_mac.cc",
+      "synchronization/semaphore_mac.cc",
+    ]
+  }
+
+  if (crashpad_is_mac) {
+    sources += [
+      "mac/checked_mach_address_range.h",
+      "mac/launchd.h",
+      "mac/launchd.mm",
+      "mac/mac_util.cc",
+      "mac/mac_util.h",
+      "mac/service_management.cc",
+      "mac/service_management.h",
+      "mach/bootstrap.cc",
+      "mach/bootstrap.h",
+      "mach/child_port_handshake.cc",
+      "mach/child_port_handshake.h",
+      "mach/child_port_server.cc",
+      "mach/child_port_server.h",
+      "mach/child_port_types.h",
+      "mach/exception_types.cc",
+      "mach/exception_types.h",
       "mach/notify_server.cc",
       "mach/notify_server.h",
       "mach/scoped_task_suspend.cc",
@@ -238,26 +338,30 @@
       "mach/symbolic_constants_mach.h",
       "mach/task_for_pid.cc",
       "mach/task_for_pid.h",
-      "mach/task_memory.cc",
-      "mach/task_memory.h",
-      "misc/capture_context_mac.S",
-      "misc/clock_mac.cc",
-      "misc/paths_mac.cc",
       "net/http_transport_mac.mm",
       "posix/process_info_mac.cc",
-      "synchronization/semaphore_mac.cc",
+      "process/process_memory_mac.cc",
+      "process/process_memory_mac.h",
     ]
-    sources += get_target_outputs(":mig")
+  }
+
+  if (crashpad_is_ios) {
+    sources += [
+      "ios/exception_processor.h",
+      "ios/exception_processor.mm",
+      "ios/ios_system_data_collector.h",
+      "ios/ios_system_data_collector.mm",
+    ]
   }
 
   deps = []
 
-  if (crashpad_is_linux || crashpad_is_fuchsia) {
+  if (crashpad_is_linux || crashpad_is_fuchsia || crashpad_is_android) {
     sources += [ "net/http_transport_socket.cc" ]
-    if (use_boringssl_for_http_transport_socket) {
-      defines = [ "CRASHPAD_USE_BORINGSSL" ]
+    if (crashpad_use_boringssl_for_http_transport_socket) {
+      defines += [ "CRASHPAD_USE_BORINGSSL" ]
 
-      if (crashpad_is_in_fuchsia) {
+      if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
         deps += [ "//third_party/boringssl" ]
       } else {
         libs = [
@@ -266,12 +370,16 @@
         ]
       }
     }
-  } else if (crashpad_is_android) {
-    sources += [ "net/http_transport_none.cc" ]
+  }
+
+  if (crashpad_is_android) {
+    sources += [
+      "linux/initial_signal_dispositions.cc",
+      "linux/initial_signal_dispositions.h",
+    ]
   }
 
   if (crashpad_is_linux || crashpad_is_android) {
-    set_sources_assignment_filter([])
     sources += [
       "linux/address_types.h",
       "linux/auxiliary_vector.cc",
@@ -288,6 +396,8 @@
       "linux/memory_map.h",
       "linux/proc_stat_reader.cc",
       "linux/proc_stat_reader.h",
+      "linux/proc_task_reader.cc",
+      "linux/proc_task_reader.h",
       "linux/ptrace_broker.cc",
       "linux/ptrace_broker.h",
       "linux/ptrace_client.cc",
@@ -295,30 +405,25 @@
       "linux/ptrace_connection.h",
       "linux/ptracer.cc",
       "linux/ptracer.h",
+      "linux/scoped_pr_set_dumpable.cc",
+      "linux/scoped_pr_set_dumpable.h",
       "linux/scoped_pr_set_ptracer.cc",
       "linux/scoped_pr_set_ptracer.h",
       "linux/scoped_ptrace_attach.cc",
       "linux/scoped_ptrace_attach.h",
+      "linux/socket.cc",
+      "linux/socket.h",
       "linux/thread_info.cc",
       "linux/thread_info.h",
       "linux/traits.h",
       "misc/capture_context_linux.S",
       "misc/paths_linux.cc",
+      "misc/time_linux.cc",
       "posix/process_info_linux.cc",
       "process/process_memory_linux.cc",
       "process/process_memory_linux.h",
-    ]
-  }
-
-  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
-    sources += [
-      "process/process_memory.cc",
-      "process/process_memory.h",
-      "process/process_memory_native.h",
-
-      # TODO: Port to all platforms.
-      "process/process_memory_range.cc",
-      "process/process_memory_range.h",
+      "process/process_memory_sanitized.cc",
+      "process/process_memory_sanitized.h",
     ]
   }
 
@@ -331,12 +436,15 @@
       "misc/paths_win.cc",
       "misc/time_win.cc",
       "net/http_transport_win.cc",
+      "process/process_memory_win.cc",
+      "process/process_memory_win.h",
       "synchronization/semaphore_win.cc",
       "thread/thread_win.cc",
       "win/address_types.h",
       "win/checked_win_address_range.h",
       "win/command_line.cc",
       "win/command_line.h",
+      "win/context_wrappers.h",
       "win/critical_section_with_debug_info.cc",
       "win/critical_section_with_debug_info.h",
       "win/exception_handler_server.cc",
@@ -349,6 +457,8 @@
       "win/handle.h",
       "win/initial_client_data.cc",
       "win/initial_client_data.h",
+      "win/loader_lock.cc",
+      "win/loader_lock.h",
       "win/module_version.cc",
       "win/module_version.h",
       "win/nt_internals.cc",
@@ -386,10 +496,24 @@
     # TODO(thakis): Use the .asm file in cross builds somehow,
     # https://crbug.com/762167.
     if (host_os == "win") {
-      sources += [
-        "misc/capture_context_win.asm",
-        "win/safe_terminate_process.asm",
-      ]
+      if (current_cpu != "arm64") {
+        sources += [
+          "misc/capture_context_win.asm",
+          "win/safe_terminate_process.asm",
+        ]
+      } else {
+        # Most Crashpad builds use Microsoft's armasm64.exe macro assembler for
+        # .asm source files. When building in Chromium, clang-cl is used as the
+        # assembler instead. Since the two assemblers recognize different
+        # assembly dialects, the same .asm file can't be used for each. As a
+        # workaround, use a prebuilt .obj file when the Microsoft-dialect
+        # assembler isn't available.
+        if (crashpad_is_in_chromium) {
+          sources += [ "misc/capture_context_win_arm64.obj" ]
+        } else {
+          sources += [ "misc/capture_context_win_arm64.asm" ]
+        }
+      }
     } else {
       sources += [
         "misc/capture_context_broken.cc",
@@ -404,7 +528,6 @@
       "fuchsia/koid_utilities.h",
       "fuchsia/scoped_task_suspend.cc",
       "fuchsia/scoped_task_suspend.h",
-      "fuchsia/system_exception_port_key.h",
       "misc/capture_context_fuchsia.S",
       "misc/paths_fuchsia.cc",
       "process/process_memory_fuchsia.cc",
@@ -415,17 +538,24 @@
   public_configs = [ "..:crashpad_config" ]
 
   # Include generated files starting with "util".
-  include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ]
+  if (crashpad_is_in_fuchsia) {
+    include_dirs = [ "$root_gen_dir/third_party/crashpad" ]
+  } else {
+    include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ]
+  }
 
   public_deps = [
     "../compat",
-  ]
-
-  deps += [
-    "../third_party/mini_chromium:base",
     "../third_party/zlib",
   ]
 
+  deps += [ "../third_party/mini_chromium:base" ]
+
+  if (crashpad_is_mac || crashpad_is_ios) {
+    include_dirs += [ "$root_build_dir/gen" ]
+    deps += [ ":mig_output" ]
+  }
+
   if (crashpad_is_mac) {
     libs = [
       "bsm",
@@ -433,8 +563,7 @@
       "Foundation.framework",
       "IOKit.framework",
     ]
-    deps += [ ":mig" ]
-    include_dirs += [ "$root_build_dir/gen" ]
+    deps += [ "../third_party/apple_cf:apple_cf" ]
   }
 
   if (crashpad_is_win) {
@@ -454,25 +583,22 @@
       asmflags = [ "/safeseh" ]
     }
   }
-}
 
-if (use_boringssl_for_http_transport_socket) {
-  action("generate_test_server_key") {
-    script = "net/generate_test_server_key.py"
-    outputs = [
-      "$root_out_dir/crashpad_util_test_cert.pem",
-      "$root_out_dir/crashpad_util_test_key.pem",
-    ]
-    data = outputs
+  if (crashpad_is_fuchsia) {
+    public_deps += [ "../third_party/fuchsia" ]
   }
+
+  if (crashpad_is_android || crashpad_is_linux) {
+    deps += [ "../third_party/lss" ]
+  }
+
+  configs += [ "..:disable_ubsan" ]
 }
 
-if (!crashpad_is_android) {
+if (!crashpad_is_android && !crashpad_is_ios) {
   crashpad_executable("http_transport_test_server") {
     testonly = true
-    sources = [
-      "net/http_transport_test_server.cc",
-    ]
+    sources = [ "net/http_transport_test_server.cc" ]
 
     deps = [
       ":util",
@@ -490,13 +616,10 @@
       libs = [ "ws2_32.lib" ]
     }
 
-    if (use_boringssl_for_http_transport_socket) {
-      data_deps = [
-        ":generate_test_server_key",
-      ]
+    if (crashpad_use_boringssl_for_http_transport_socket) {
       defines = [ "CRASHPAD_USE_BORINGSSL" ]
 
-      if (crashpad_is_in_fuchsia) {
+      if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
         deps += [ "//third_party/boringssl" ]
       } else {
         libs = [
@@ -518,7 +641,7 @@
     "file/file_reader_test.cc",
     "file/filesystem_test.cc",
     "file/string_file_test.cc",
-    "misc/arraysize_unsafe_test.cc",
+    "misc/arraysize_test.cc",
     "misc/capture_context_test.cc",
     "misc/capture_context_test_util.h",
     "misc/clock_test.cc",
@@ -542,12 +665,20 @@
     "numeric/checked_range_test.cc",
     "numeric/in_range_cast_test.cc",
     "numeric/int128_test.cc",
+    "process/process_memory_range_test.cc",
+    "process/process_memory_test.cc",
     "stdlib/aligned_allocator_test.cc",
     "stdlib/map_insert_test.cc",
     "stdlib/string_number_conversion_test.cc",
     "stdlib/strlcpy_test.cc",
     "stdlib/strnlen_test.cc",
     "stdlib/thread_safe_vector_test.cc",
+    "stream/base94_output_stream_test.cc",
+    "stream/file_encoder_test.cc",
+    "stream/log_output_stream_test.cc",
+    "stream/test_output_stream.cc",
+    "stream/test_output_stream.h",
+    "stream/zlib_output_stream_test.cc",
     "string/split_string_test.cc",
     "synchronization/semaphore_test.cc",
     "thread/thread_log_messages_test.cc",
@@ -555,13 +686,13 @@
     "thread/worker_thread_test.cc",
   ]
 
-  if (!crashpad_is_android) {
+  if (!crashpad_is_android && !crashpad_is_ios) {
     # Android requires an HTTPTransport implementation.
     sources += [ "net/http_transport_test.cc" ]
   }
 
   if (crashpad_is_posix || crashpad_is_fuchsia) {
-    if (!crashpad_is_fuchsia) {
+    if (!crashpad_is_fuchsia && !crashpad_is_ios) {
       sources += [
         "posix/process_info_test.cc",
         "posix/signals_test.cc",
@@ -571,41 +702,59 @@
     sources += [ "posix/scoped_mmap_test.cc" ]
   }
 
+  if (crashpad_is_mac || crashpad_is_ios) {
+    sources += [
+      "mac/xattr_test.cc",
+      "mach/composite_mach_message_server_test.cc",
+      "mach/exc_server_variants_test.cc",
+      "mach/exception_behaviors_test.cc",
+      "mach/mach_extensions_test.cc",
+      "mach/mach_message_test.cc",
+    ]
+  }
+
   if (crashpad_is_mac) {
     sources += [
       "mac/launchd_test.mm",
       "mac/mac_util_test.mm",
       "mac/service_management_test.mm",
-      "mac/xattr_test.cc",
+      "mach/bootstrap_test.cc",
       "mach/child_port_handshake_test.cc",
       "mach/child_port_server_test.cc",
-      "mach/composite_mach_message_server_test.cc",
       "mach/exc_client_variants_test.cc",
-      "mach/exc_server_variants_test.cc",
-      "mach/exception_behaviors_test.cc",
       "mach/exception_ports_test.cc",
       "mach/exception_types_test.cc",
-      "mach/mach_extensions_test.cc",
       "mach/mach_message_server_test.cc",
-      "mach/mach_message_test.cc",
       "mach/notify_server_test.cc",
       "mach/scoped_task_suspend_test.cc",
       "mach/symbolic_constants_mach_test.cc",
-      "mach/task_memory_test.cc",
       "misc/capture_context_test_util_mac.cc",
+      "process/process_memory_mac_test.cc",
+    ]
+  }
+
+  if (crashpad_is_ios) {
+    sources += [ "ios/exception_processor_test.mm" ]
+
+    sources -= [
+      "misc/capture_context_test.cc",
+      "process/process_memory_range_test.cc",
+      "process/process_memory_test.cc",
     ]
   }
 
   if (crashpad_is_linux || crashpad_is_android) {
-    set_sources_assignment_filter([])
     sources += [
       "linux/auxiliary_vector_test.cc",
       "linux/memory_map_test.cc",
       "linux/proc_stat_reader_test.cc",
+      "linux/proc_task_reader_test.cc",
       "linux/ptrace_broker_test.cc",
       "linux/ptracer_test.cc",
       "linux/scoped_ptrace_attach_test.cc",
+      "linux/socket_test.cc",
       "misc/capture_context_test_util_linux.cc",
+      "process/process_memory_sanitized_test.cc",
     ]
   }
 
@@ -613,14 +762,6 @@
     sources += [ "misc/capture_context_test_util_fuchsia.cc" ]
   }
 
-  if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) {
-    sources += [
-      # TODO: Port to all platforms.
-      "process/process_memory_range_test.cc",
-      "process/process_memory_test.cc",
-    ]
-  }
-
   if (crashpad_is_win) {
     sources += [
       "misc/capture_context_test_util_win.cc",
@@ -630,16 +771,25 @@
       "win/get_function_test.cc",
       "win/handle_test.cc",
       "win/initial_client_data_test.cc",
+      "win/loader_lock_test.cc",
       "win/process_info_test.cc",
       "win/registration_protocol_win_test.cc",
       "win/safe_terminate_process_test.cc",
       "win/scoped_process_suspend_test.cc",
       "win/session_end_watcher_test.cc",
     ]
+
+    if (crashpad_is_in_chromium && is_asan && is_component_build) {
+      # TODO(crbug.com/856174): Re-enable these once Windows ASan is fixed.
+      sources -= [ "stdlib/string_number_conversion_test.cc" ]
+    }
   }
 
   data = [
-    "net/testdata/",
+    "net/testdata/ascii_http_body.txt",
+    "net/testdata/binary_http_body.dat",
+    "net/testdata/crashpad_util_test_cert.pem",
+    "net/testdata/crashpad_util_test_key.pem",
   ]
 
   deps = [
@@ -653,12 +803,10 @@
     "../third_party/zlib",
   ]
 
-  if (!crashpad_is_android) {
-    data_deps = [
-      ":http_transport_test_server",
-    ]
+  if (!crashpad_is_android && !crashpad_is_ios) {
+    data_deps = [ ":http_transport_test_server" ]
 
-    if (use_boringssl_for_http_transport_socket) {
+    if (crashpad_use_boringssl_for_http_transport_socket) {
       defines = [ "CRASHPAD_USE_BORINGSSL" ]
     }
   }
@@ -667,30 +815,55 @@
     libs = [ "Foundation.framework" ]
   }
 
+  if (crashpad_is_ios) {
+    deps += [ ":util_test_bundle_data" ]
+  }
+
+  if (crashpad_is_android || crashpad_is_linux) {
+    deps += [ "../third_party/lss" ]
+  }
+
   if (crashpad_is_win) {
     libs = [
       "rpcrt4.lib",
       "dbghelp.lib",
     ]
     data_deps += [
+      ":crashpad_util_test_loader_lock_test",
       ":crashpad_util_test_process_info_test_child",
       ":crashpad_util_test_safe_terminate_process_test_child",
     ]
   }
 }
 
+if (crashpad_is_ios) {
+  bundle_data("util_test_bundle_data") {
+    testonly = true
+
+    sources = [
+      "net/testdata/ascii_http_body.txt",
+      "net/testdata/binary_http_body.dat",
+    ]
+
+    outputs = [ "{{bundle_resources_dir}}/crashpad_test_data/" +
+                "{{source_root_relative_dir}}/{{source_file_part}}" ]
+  }
+}
+
 if (crashpad_is_win) {
   crashpad_executable("crashpad_util_test_process_info_test_child") {
     testonly = true
-    sources = [
-      "win/process_info_test_child.cc",
-    ]
+    sources = [ "win/process_info_test_child.cc" ]
   }
 
   crashpad_executable("crashpad_util_test_safe_terminate_process_test_child") {
     testonly = true
-    sources = [
-      "win/safe_terminate_process_test_child.cc",
-    ]
+    sources = [ "win/safe_terminate_process_test_child.cc" ]
+  }
+
+  crashpad_loadable_module("crashpad_util_test_loader_lock_test") {
+    testonly = true
+    sources = [ "win/loader_lock_test_dll.cc" ]
+    deps = [ ":util" ]
   }
 }
diff --git a/util/file/delimited_file_reader.cc b/util/file/delimited_file_reader.cc
index a55c2a7..2275ade 100644
--- a/util/file/delimited_file_reader.cc
+++ b/util/file/delimited_file_reader.cc
@@ -21,6 +21,7 @@
 
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
 
 namespace crashpad {
 
@@ -75,7 +76,7 @@
         return Result::kEndOfFile;
       }
 
-      DCHECK_LE(static_cast<size_t>(read_result), arraysize(buf_));
+      DCHECK_LE(static_cast<size_t>(read_result), base::size(buf_));
       DCHECK(
           base::IsValueInRangeForNumericType<decltype(buf_len_)>(read_result));
       buf_len_ = static_cast<decltype(buf_len_)>(read_result);
diff --git a/util/file/delimited_file_reader_test.cc b/util/file/delimited_file_reader_test.cc
index 79e331f..a2fd7d5 100644
--- a/util/file/delimited_file_reader_test.cc
+++ b/util/file/delimited_file_reader_test.cc
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "util/file/string_file.h"
@@ -259,7 +260,7 @@
 TEST(DelimitedFileReader, EmbeddedNUL) {
   static constexpr char kString[] = "embedded\0NUL\n";
   StringFile string_file;
-  string_file.SetString(std::string(kString, arraysize(kString) - 1));
+  string_file.SetString(std::string(kString, base::size(kString) - 1));
   DelimitedFileReader delimited_file_reader(&string_file);
 
   std::string line;
@@ -277,7 +278,7 @@
 TEST(DelimitedFileReader, NULDelimiter) {
   static constexpr char kString[] = "aa\0b\0ccc\0";
   StringFile string_file;
-  string_file.SetString(std::string(kString, arraysize(kString) - 1));
+  string_file.SetString(std::string(kString, base::size(kString) - 1));
   DelimitedFileReader delimited_file_reader(&string_file);
 
   std::string field;
@@ -301,7 +302,7 @@
 TEST(DelimitedFileReader, EdgeCases) {
   static constexpr size_t kSizes[] =
       {4094, 4095, 4096, 4097, 8190, 8191, 8192, 8193};
-  for (size_t index = 0; index < arraysize(kSizes); ++index) {
+  for (size_t index = 0; index < base::size(kSizes); ++index) {
     size_t size = kSizes[index];
     SCOPED_TRACE(
         base::StringPrintf("index %" PRIuS ", size %" PRIuS, index, size));
diff --git a/util/file/directory_reader_posix.cc b/util/file/directory_reader_posix.cc
index b33e19b..475b0b1 100644
--- a/util/file/directory_reader_posix.cc
+++ b/util/file/directory_reader_posix.cc
@@ -39,7 +39,7 @@
 bool DirectoryReader::Open(const base::FilePath& path) {
   dir_.reset(HANDLE_EINTR_IF_EQ(opendir(path.value().c_str()), nullptr));
   if (!dir_.is_valid()) {
-    PLOG(ERROR) << "opendir";
+    PLOG(ERROR) << "opendir " << path.value();
     return false;
   }
   return true;
@@ -52,7 +52,7 @@
   dirent* entry = HANDLE_EINTR_IF_EQ(readdir(dir_.get()), nullptr);
   if (!entry) {
     if (errno) {
-      PLOG(ERROR) << "readdir";
+      PLOG(ERROR) << "readdir " << filename->value();
       return Result::kError;
     } else {
       return Result::kNoMoreFiles;
diff --git a/util/file/directory_reader_test.cc b/util/file/directory_reader_test.cc
index f03669e..812deaf 100644
--- a/util/file/directory_reader_test.cc
+++ b/util/file/directory_reader_test.cc
@@ -22,7 +22,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
 #include "test/filesystem.h"
-#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
@@ -48,7 +47,7 @@
 
 TEST(DirectoryReader, BadPaths_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -144,7 +143,7 @@
 
 TEST(DirectoryReader, FilesAndDirectories_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestFilesAndDirectories(true);
diff --git a/util/file/file_io.h b/util/file/file_io.h
index 797db68..6fa0f96 100644
--- a/util/file/file_io.h
+++ b/util/file/file_io.h
@@ -398,6 +398,30 @@
                                    FileWriteMode mode,
                                    FilePermissions permissions);
 
+#if defined(OS_LINUX)
+//! \brief Opens an in-memory file for input and output.
+//!
+//! This function first attempts to open the file with `memfd_create()`. If
+//! `memfd_create()` isn't supported by the kernel, this function next attempts
+//! to open a file using `O_TMPFILE`. If `O_TMPFILE` isn't supported, this
+//! function finally falls back to creating a file with a randomized name in
+//! `/tmp` and immediately `unlink()`ing it.
+//!
+//! Unlike other file open operations, this function doesn't set `O_CLOEXEC`.
+//!
+//! \param name A name associated with the file. This name does not indicate any
+//!     exact path and may not be used at all, depending on the strategy used to
+//!     create the file. The name should not contain any '/' characters.
+//! \return The newly opened FileHandle, or an invalid FileHandle on failure,
+//!     with a message logged.
+//!
+//! \sa ScopedFileHandle
+//! \sa LoggingOpenFileForRead
+//! \sa LoggingOpenFileForWrite
+//! \sa LoggingOpenFileForReadAndWrite
+FileHandle LoggingOpenMemoryFileForReadAndWrite(const base::FilePath& name);
+#endif  // OS_LINUX
+
 //! \brief Wraps OpenFileForReadAndWrite(), logging an error if the operation
 //!     fails.
 //!
diff --git a/util/file/file_io_posix.cc b/util/file/file_io_posix.cc
index f311616..91b252a 100644
--- a/util/file/file_io_posix.cc
+++ b/util/file/file_io_posix.cc
@@ -14,8 +14,10 @@
 
 #include "util/file/file_io.h"
 
+#include <errno.h>
 #include <fcntl.h>
 #include <sys/file.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -25,7 +27,9 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/strings/stringprintf.h"
 #include "build/build_config.h"
+#include "util/misc/random_string.h"
 
 namespace crashpad {
 
@@ -97,7 +101,6 @@
            flags,
            permissions == FilePermissions::kWorldReadable ? 0644 : 0600));
 }
-
 }  // namespace
 
 namespace internal {
@@ -149,6 +152,53 @@
   return fd;
 }
 
+#if defined(OS_LINUX)
+FileHandle LoggingOpenMemoryFileForReadAndWrite(const base::FilePath& name) {
+  DCHECK(name.value().find('/') == std::string::npos);
+
+  int result = HANDLE_EINTR(memfd_create(name.value().c_str(), 0));
+  if (result >= 0 || errno != ENOSYS) {
+    PLOG_IF(ERROR, result < 0) << "memfd_create";
+    return result;
+  }
+
+  const char* tmp = getenv("TMPDIR");
+  tmp = tmp ? tmp : "/tmp";
+
+  result = HANDLE_EINTR(open(tmp, O_RDWR | O_EXCL | O_TMPFILE, 0600));
+  if (result >= 0 ||
+      // These are the expected possible error codes indicating that O_TMPFILE
+      // doesn't have kernel or filesystem support. O_TMPFILE was added in Linux
+      // 3.11. Experimentation confirms that at least Linux 2.6.29 and Linux
+      // 3.10 set errno to EISDIR. EOPNOTSUPP is returned when the filesystem
+      // doesn't support O_TMPFILE. The man pages also mention ENOENT as an
+      // error code to check, but the language implies it would only occur when
+      // |tmp| is also an invalid directory. EINVAL is mentioned as a possible
+      // error code for any invalid values in flags, but O_TMPFILE isn't
+      // mentioned explicitly in this context and hasn't been observed in
+      // practice.
+      (errno != EISDIR && errno != EOPNOTSUPP)) {
+    PLOG_IF(ERROR, result < 0) << "open";
+    return result;
+  }
+
+  std::string path = base::StringPrintf("%s/%s.%d.%s",
+                                        tmp,
+                                        name.value().c_str(),
+                                        getpid(),
+                                        RandomString().c_str());
+  result = HANDLE_EINTR(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
+  if (result < 0) {
+    PLOG(ERROR) << "open";
+    return result;
+  }
+  if (unlink(path.c_str()) != 0) {
+    PLOG(WARNING) << "unlink";
+  }
+  return result;
+}
+#endif
+
 FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path,
                                           FileWriteMode mode,
                                           FilePermissions permissions) {
diff --git a/util/file/file_io_test.cc b/util/file/file_io_test.cc
index 4446c32..0efded8 100644
--- a/util/file/file_io_test.cc
+++ b/util/file/file_io_test.cc
@@ -22,6 +22,7 @@
 #include "base/atomicops.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
@@ -472,6 +473,23 @@
   TestOpenFileForWrite(LoggingOpenFileForReadAndWrite);
 }
 
+#if defined(OS_LINUX)
+TEST(FileIO, LoggingOpenMemoryFileForReadAndWrite) {
+  ScopedFileHandle handle(
+      LoggingOpenMemoryFileForReadAndWrite(base::FilePath("memfile")));
+  ASSERT_TRUE(handle.is_valid());
+
+  static constexpr char kTestData[] = "somedata";
+  ASSERT_TRUE(LoggingWriteFile(handle.get(), kTestData, sizeof(kTestData)));
+
+  ASSERT_EQ(LoggingSeekFile(handle.get(), 0, SEEK_SET), 0);
+
+  char buffer[sizeof(kTestData)];
+  ASSERT_TRUE(LoggingReadFileExactly(handle.get(), buffer, sizeof(buffer)));
+  EXPECT_EQ(memcmp(buffer, kTestData, sizeof(buffer)), 0);
+}
+#endif  // OS_LINUX
+
 enum class ReadOrWrite : bool {
   kRead,
   kWrite,
@@ -612,7 +630,7 @@
 
   LockingTestThread threads[20];
   int expected_iterations = 0;
-  for (size_t index = 0; index < arraysize(threads); ++index) {
+  for (size_t index = 0; index < base::size(threads); ++index) {
     int iterations_for_this_thread = static_cast<int>(index * 10);
     threads[index].Init(
         (other_locks == FileLocking::kShared)
diff --git a/util/file/file_io_win.cc b/util/file/file_io_win.cc
index 7428034..111a8da 100644
--- a/util/file/file_io_win.cc
+++ b/util/file/file_io_win.cc
@@ -28,7 +28,7 @@
     // FILE_TYPE_PIPE means that it's a socket, a named pipe, or an anonymous
     // pipe. If we are unable to retrieve the pipe information, we know it's a
     // socket.
-    return !GetNamedPipeInfo(file, NULL, NULL, NULL, NULL);
+    return !GetNamedPipeInfo(file, nullptr, nullptr, nullptr, nullptr);
   }
   return false;
 }
diff --git a/util/file/file_writer.cc b/util/file/file_writer.cc
index 13ac6cf..6dff975 100644
--- a/util/file/file_writer.cc
+++ b/util/file/file_writer.cc
@@ -171,6 +171,23 @@
   return true;
 }
 
+#if defined(OS_LINUX)
+bool FileWriter::OpenMemfd(const base::FilePath& path) {
+  CHECK(!file_.is_valid());
+  file_.reset(LoggingOpenMemoryFileForReadAndWrite(path));
+  if (!file_.is_valid()) {
+    return false;
+  }
+
+  weak_file_handle_file_writer_.set_file_handle(file_.get());
+  return true;
+}
+
+int FileWriter::fd() {
+  return file_.get();
+}
+#endif
+
 void FileWriter::Close() {
   CHECK(file_.is_valid());
 
diff --git a/util/file/file_writer.h b/util/file/file_writer.h
index ed261ec..4b99b37 100644
--- a/util/file/file_writer.h
+++ b/util/file/file_writer.h
@@ -131,6 +131,22 @@
             FileWriteMode write_mode,
             FilePermissions permissions);
 
+#if defined(OS_LINUX)
+  //! \brief Wraps LoggingOpenMemoryFileForWrite().
+  //!
+  //! \return `true` if the operation succeeded, `false` if it failed, with an
+  //!     error message logged.
+  //!
+  //! \note After a successful call, this method or Open() cannot be called
+  //      again until after Close().
+  bool OpenMemfd(const base::FilePath& path);
+
+  //! \brief Returns the underlying file descriptor.
+  //!
+  //! \note This is used when this writes to a Memfd.
+  int fd();
+#endif
+
   //! \brief Wraps CheckedCloseHandle().
   //!
   //! \note It is only valid to call this method on an object that has had a
diff --git a/util/file/filesystem.h b/util/file/filesystem.h
index 27a4d1d..e12f127 100644
--- a/util/file/filesystem.h
+++ b/util/file/filesystem.h
@@ -73,7 +73,8 @@
 //!     failure.
 //!
 //! On POSIX, this function returns `true` if \a path refers to a file that is
-//! not a symbolic link, directory, or other kind of special file.
+//! not a symbolic link, directory, or other kind of special file. If this
+//! function fails because \a path does not exist, then no message is logged.
 //!
 //! On Windows, this function returns `true` if \a path refers to a file that
 //! is not a symbolic link or directory.
@@ -85,6 +86,9 @@
 //! \brief Determines if a path refers to a directory, logging a message on
 //!     failure.
 //!
+//! On POSIX, if this function fails because \a path does not exist, then no
+//! message is logged.
+//!
 //! \param[in] path The path to check.
 //! \param[in] allow_symlinks Whether to allow the final component in the path
 //!     to be a symbolic link to a directory.
diff --git a/util/file/filesystem_posix.cc b/util/file/filesystem_posix.cc
index c2234da..bb51a9c 100644
--- a/util/file/filesystem_posix.cc
+++ b/util/file/filesystem_posix.cc
@@ -65,14 +65,6 @@
 
 bool MoveFileOrDirectory(const base::FilePath& source,
                          const base::FilePath& dest) {
-#if defined(OS_FUCHSIA)
-  // Fuchsia fails and sets errno to EINVAL if source and dest are the same.
-  // Upstream bug is ZX-1729.
-  if (!source.empty() && source == dest) {
-    return true;
-  }
-#endif  // OS_FUCHSIA
-
   if (rename(source.value().c_str(), dest.value().c_str()) != 0) {
     PLOG(ERROR) << "rename " << source.value().c_str() << ", "
                 << dest.value().c_str();
@@ -84,7 +76,7 @@
 bool IsRegularFile(const base::FilePath& path) {
   struct stat st;
   if (lstat(path.value().c_str(), &st) != 0) {
-    PLOG(ERROR) << "stat " << path.value();
+    PLOG_IF(ERROR, errno != ENOENT) << "stat " << path.value();
     return false;
   }
   return S_ISREG(st.st_mode);
@@ -94,11 +86,11 @@
   struct stat st;
   if (allow_symlinks) {
     if (stat(path.value().c_str(), &st) != 0) {
-      PLOG(ERROR) << "stat " << path.value();
+      PLOG_IF(ERROR, errno != ENOENT) << "stat " << path.value();
       return false;
     }
   } else if (lstat(path.value().c_str(), &st) != 0) {
-    PLOG(ERROR) << "lstat " << path.value();
+    PLOG_IF(ERROR, errno != ENOENT) << "lstat " << path.value();
     return false;
   }
   return S_ISDIR(st.st_mode);
diff --git a/util/file/filesystem_test.cc b/util/file/filesystem_test.cc
index 3430c3f..37a9290 100644
--- a/util/file/filesystem_test.cc
+++ b/util/file/filesystem_test.cc
@@ -21,7 +21,6 @@
 #include "gtest/gtest.h"
 #include "test/errors.h"
 #include "test/filesystem.h"
-#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/misc/time.h"
 
@@ -93,7 +92,7 @@
 
 TEST(Filesystem, FileModificationTime_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -224,7 +223,7 @@
 
 TEST(Filesystem, MoveFileOrDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -302,7 +301,7 @@
 
 TEST(Filesystem, IsRegularFile_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -344,7 +343,7 @@
 
 TEST(Filesystem, IsDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -393,7 +392,7 @@
 
 TEST(Filesystem, RemoveFile_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
@@ -450,7 +449,7 @@
 
 TEST(Filesystem, RemoveDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   ScopedTempDir temp_dir;
diff --git a/util/file/output_stream_file_writer.cc b/util/file/output_stream_file_writer.cc
new file mode 100644
index 0000000..1172019
--- /dev/null
+++ b/util/file/output_stream_file_writer.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 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.
+
+#include "util/file/output_stream_file_writer.h"
+
+#include "base/logging.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+
+OutputStreamFileWriter::OutputStreamFileWriter(
+    std::unique_ptr<OutputStreamInterface> output_stream)
+    : output_stream_(std::move(output_stream)),
+      flush_needed_(false),
+      flushed_(false) {}
+
+OutputStreamFileWriter::~OutputStreamFileWriter() {
+  DCHECK(!flush_needed_);
+}
+
+bool OutputStreamFileWriter::Write(const void* data, size_t size) {
+  DCHECK(!flushed_);
+  flush_needed_ =
+      output_stream_->Write(static_cast<const uint8_t*>(data), size);
+  return flush_needed_;
+}
+
+bool OutputStreamFileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
+  DCHECK(!flushed_);
+  flush_needed_ = true;
+  if (iovecs->empty()) {
+    LOG(ERROR) << "no iovecs";
+    flush_needed_ = false;
+    return false;
+  }
+  for (const WritableIoVec& iov : *iovecs) {
+    if (!output_stream_->Write(static_cast<const uint8_t*>(iov.iov_base),
+                               iov.iov_len)) {
+      flush_needed_ = false;
+      return false;
+    }
+  }
+  return true;
+}
+
+FileOffset OutputStreamFileWriter::Seek(FileOffset offset, int whence) {
+  NOTREACHED();
+  return -1;
+}
+
+bool OutputStreamFileWriter::Flush() {
+  flush_needed_ = false;
+  flushed_ = true;
+  return output_stream_->Flush();
+}
+
+}  // namespace crashpad
diff --git a/util/file/output_stream_file_writer.h b/util/file/output_stream_file_writer.h
new file mode 100644
index 0000000..6bf1c2c
--- /dev/null
+++ b/util/file/output_stream_file_writer.h
@@ -0,0 +1,62 @@
+// Copyright 2020 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_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_
+#define CRASHPAD_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "util/file/file_writer.h"
+
+namespace crashpad {
+
+class OutputStreamInterface;
+
+//! \brief A file writer backed by a OutputSteamInterface.
+//! \note The \a Seek related methods don't work and shouldn't be invoked.
+class OutputStreamFileWriter : public FileWriterInterface {
+ public:
+  //! \param[in] output_stream The output stream that this object writes to.
+  explicit OutputStreamFileWriter(
+      std::unique_ptr<OutputStreamInterface> output_stream);
+  ~OutputStreamFileWriter() override;
+
+  // FileWriterInterface:
+  bool Write(const void* data, size_t size) override;
+  bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
+
+  // FileSeekerInterface:
+
+  //! \copydoc FileWriterInterface::Seek()
+  //!
+  //! \note This method doesn't work and shouldn't be invoked.
+  FileOffset Seek(FileOffset offset, int whence) override;
+
+  //! \brief  Flush data to output_stream.
+  //!
+  //! Either \a Write() or \a WriteIoVec() can't be called afterwards.
+  bool Flush();
+
+ private:
+  std::unique_ptr<OutputStreamInterface> output_stream_;
+  bool flush_needed_;
+  bool flushed_;
+
+  DISALLOW_COPY_AND_ASSIGN(OutputStreamFileWriter);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_FILE_OUTPUT_STREAM_FILE_WRITER_H_
diff --git a/util/fuchsia/koid_utilities.cc b/util/fuchsia/koid_utilities.cc
index 0d44f4a..f0377dc 100644
--- a/util/fuchsia/koid_utilities.cc
+++ b/util/fuchsia/koid_utilities.cc
@@ -14,7 +14,10 @@
 
 #include "util/fuchsia/koid_utilities.h"
 
-#include <zircon/device/sysinfo.h>
+#include <lib/fdio/fdio.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/job.h>
+#include <lib/zx/process.h>
 
 #include <vector>
 
@@ -26,62 +29,59 @@
 
 namespace {
 
-base::ScopedZxHandle GetRootJob() {
-  ScopedFileHandle sysinfo(
-      LoggingOpenFileForRead(base::FilePath("/dev/misc/sysinfo")));
-  if (!sysinfo.is_valid())
-    return base::ScopedZxHandle();
-
-  zx_handle_t root_job;
-  size_t n = ioctl_sysinfo_get_root_job(sysinfo.get(), &root_job);
-  if (n != sizeof(root_job)) {
-    LOG(ERROR) << "unexpected root job size";
-    return base::ScopedZxHandle();
+// Casts |handle| into a container of type T, returning a null handle if the
+// actual handle type does not match that of T.
+template <typename T>
+T CastHandle(zx::handle handle) {
+  zx_info_handle_basic_t actual = {};
+  zx_status_t status = handle.get_info(
+      ZX_INFO_HANDLE_BASIC, &actual, sizeof(actual), nullptr, nullptr);
+  if (status != ZX_OK) {
+    ZX_LOG(ERROR, status) << "zx_object_get_info";
+    return T();
   }
-  return base::ScopedZxHandle(root_job);
+  if (actual.type != T::TYPE) {
+    LOG(ERROR) << "Wrong type: " << actual.type << ", expected " << T::TYPE;
+    return T();
+  }
+  return T(std::move(handle));
 }
 
-bool FindProcess(const base::ScopedZxHandle& job,
-                 zx_koid_t koid,
-                 base::ScopedZxHandle* out) {
-  for (auto& proc : GetChildHandles(job.get(), ZX_INFO_JOB_PROCESSES)) {
-    if (GetKoidForHandle(proc.get()) == koid) {
-      *out = std::move(proc);
-      return true;
-    }
+// Returns null handle if |koid| is not found or an error occurs. If |was_found|
+// is non-null then it will be set, to distinguish not-found from other errors.
+template <typename T, typename U>
+T GetChildHandleByKoid(const U& parent, zx_koid_t child_koid, bool* was_found) {
+  zx::handle handle;
+  zx_status_t status =
+      parent.get_child(child_koid, ZX_RIGHT_SAME_RIGHTS, &handle);
+  if (was_found)
+    *was_found = (status != ZX_ERR_NOT_FOUND);
+  if (status != ZX_OK) {
+    ZX_LOG(ERROR, status) << "zx_object_get_child";
+    return T();
   }
 
-  // TODO(scottmg): As this is recursing down the job tree all the handles are
-  // kept open, so this could be very expensive in terms of number of open
-  // handles. This function should be replaced by a syscall in the
-  // not-too-distant future, so hopefully OK for now.
-  for (const auto& child_job :
-       GetChildHandles(job.get(), ZX_INFO_JOB_CHILDREN)) {
-    if (FindProcess(child_job, koid, out))
-      return true;
-  }
-
-  return false;
+  return CastHandle<T>(std::move(handle));
 }
 
 }  // namespace
 
-std::vector<zx_koid_t> GetChildKoids(zx_handle_t parent,
+std::vector<zx_koid_t> GetChildKoids(const zx::object_base& parent_object,
                                      zx_object_info_topic_t child_kind) {
   size_t actual = 0;
   size_t available = 0;
   std::vector<zx_koid_t> result(100);
+  zx::unowned_handle parent(parent_object.get());
 
   // This is inherently racy. Better if the process is suspended, but there's
   // still no guarantee that a thread isn't externally created. As a result,
   // must be in a retry loop.
   for (;;) {
-    zx_status_t status = zx_object_get_info(parent,
-                                            child_kind,
-                                            result.data(),
-                                            result.size() * sizeof(zx_koid_t),
-                                            &actual,
-                                            &available);
+    zx_status_t status = parent->get_info(child_kind,
+                                          result.data(),
+                                          result.size() * sizeof(zx_koid_t),
+                                          &actual,
+                                          &available);
     // If the buffer is too small (even zero), the result is still ZX_OK, not
     // ZX_ERR_BUFFER_TOO_SMALL.
     if (status != ZX_OK) {
@@ -102,56 +102,40 @@
   return result;
 }
 
-std::vector<base::ScopedZxHandle> GetChildHandles(zx_handle_t parent,
-                                                  zx_object_info_topic_t type) {
-  auto koids = GetChildKoids(parent, type);
-  return GetHandlesForChildKoids(parent, koids);
+std::vector<zx::thread> GetThreadHandles(const zx::process& parent) {
+  auto koids = GetChildKoids(parent, ZX_INFO_PROCESS_THREADS);
+  return GetHandlesForThreadKoids(parent, koids);
 }
 
-std::vector<base::ScopedZxHandle> GetHandlesForChildKoids(
-    zx_handle_t parent,
+std::vector<zx::thread> GetHandlesForThreadKoids(
+    const zx::process& parent,
     const std::vector<zx_koid_t>& koids) {
-  std::vector<base::ScopedZxHandle> result;
+  std::vector<zx::thread> result;
   result.reserve(koids.size());
   for (zx_koid_t koid : koids) {
-    result.emplace_back(GetChildHandleByKoid(parent, koid));
+    result.emplace_back(GetThreadHandleByKoid(parent, koid));
   }
   return result;
 }
 
-base::ScopedZxHandle GetChildHandleByKoid(zx_handle_t parent,
-                                          zx_koid_t child_koid) {
-  zx_handle_t handle;
-  zx_status_t status =
-      zx_object_get_child(parent, child_koid, ZX_RIGHT_SAME_RIGHTS, &handle);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "zx_object_get_child";
-    return base::ScopedZxHandle();
-  }
-  return base::ScopedZxHandle(handle);
+zx::thread GetThreadHandleByKoid(const zx::process& parent,
+                                 zx_koid_t child_koid) {
+  return GetChildHandleByKoid<zx::thread>(parent, child_koid, nullptr);
 }
 
-zx_koid_t GetKoidForHandle(zx_handle_t object) {
+zx_koid_t GetKoidForHandle(const zx::object_base& object) {
   zx_info_handle_basic_t info;
-  zx_status_t status = zx_object_get_info(
-      object, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
+  zx_status_t status = zx_object_get_info(object.get(),
+                                          ZX_INFO_HANDLE_BASIC,
+                                          &info,
+                                          sizeof(info),
+                                          nullptr,
+                                          nullptr);
   if (status != ZX_OK) {
     ZX_LOG(ERROR, status) << "zx_object_get_info";
-    return ZX_HANDLE_INVALID;
+    return ZX_KOID_INVALID;
   }
   return info.koid;
 }
 
-// TODO(scottmg): This implementation uses some debug/temporary/hacky APIs and
-// ioctls that are currently the only way to go from pid to handle. This should
-// hopefully eventually be replaced by more or less a single
-// zx_debug_something() syscall.
-base::ScopedZxHandle GetProcessFromKoid(zx_koid_t koid) {
-  base::ScopedZxHandle result;
-  if (!FindProcess(GetRootJob(), koid, &result)) {
-    LOG(ERROR) << "process " << koid << " not found";
-  }
-  return result;
-}
-
 }  // namespace crashpad
diff --git a/util/fuchsia/koid_utilities.h b/util/fuchsia/koid_utilities.h
index 5bcea10..8859bc5 100644
--- a/util/fuchsia/koid_utilities.h
+++ b/util/fuchsia/koid_utilities.h
@@ -15,13 +15,14 @@
 #ifndef CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_
 #define CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_
 
+#include <lib/zx/object.h>
+#include <lib/zx/process.h>
+#include <lib/zx/thread.h>
 #include <zircon/syscalls/object.h>
 #include <zircon/types.h>
 
 #include <vector>
 
-#include "base/fuchsia/scoped_zx_handle.h"
-
 namespace crashpad {
 
 //! \brief Get a list of child koids for a parent handle.
@@ -38,57 +39,43 @@
 //! \return A vector of the koids representing the child objects.
 //!
 //! \sa GetChildHandles
-std::vector<zx_koid_t> GetChildKoids(zx_handle_t parent,
+std::vector<zx_koid_t> GetChildKoids(const zx::object_base& parent,
                                      zx_object_info_topic_t child_kind);
 
 //! \brief Get handles representing a list of child objects of a given parent.
 //!
 //! \param[in] parent The handle to the parent object.
-//! \param[in] child_kind The type of children to retrieve from \a parent. Valid
-//!     values depend on the type of \a parent, but include
-//!     `ZX_INFO_JOB_CHILDREN` (child jobs of a job), `ZX_INFO_JOB_PROCESSES`
-//!     (child processes of a job), and `ZX_INFO_PROCESS_THREADS` (child threads
-//!     of a process).
 //! \return The resulting list of handles corresponding to the child objects.
 //!
 //! \sa GetChildKoids
-std::vector<base::ScopedZxHandle> GetChildHandles(
-    zx_handle_t parent,
-    zx_object_info_topic_t child_kind);
+std::vector<zx::thread> GetThreadHandles(const zx::process& parent);
 
-//! \brief Convert a list of koids that are all children of a particular object
-//!     into handles.
+//! \brief Convert a list of koids that are all children of a particular process
+//!     into thread handles.
 //!
 //! \param[in] parent The parent object to which the koids belong.
 //! \param[in] koids The list of koids.
 //! \return The resulting list of handles corresponding to the koids. If an
 //!     element of \a koids is invalid or can't be retrieved, there will be a
 //!     corresponding `ZX_HANDLE_INVALID` entry in the return.
-std::vector<base::ScopedZxHandle> GetHandlesForChildKoids(
-    zx_handle_t parent,
+std::vector<zx::thread> GetHandlesForThreadKoids(
+    const zx::process& parent,
     const std::vector<zx_koid_t>& koids);
 
-//! \brief Retrieve the child of a parent handle, based on koid.
+//! \brief Retrieve the handle of a process' thread, based on koid.
 //!
 //! \param[in] parent The parent object to which the child belongs.
 //! \param[in] child_koid The koid of the child to retrieve.
 //! \return A handle representing \a child_koid, or `ZX_HANDLE_INVALID` if the
 //!     handle could not be retrieved, in which case an error will be logged.
-base::ScopedZxHandle GetChildHandleByKoid(zx_handle_t parent,
-                                          zx_koid_t child_koid);
-
-//! \brief Gets a process handle given the process' koid.
-//!
-//! \param[in] koid The process id.
-//! \return A zx_handle_t (owned by a base::ScopedZxHandle) for the process. If
-//!     the handle is invalid, an error will have been logged.
-base::ScopedZxHandle GetProcessFromKoid(zx_koid_t koid);
+zx::thread GetThreadHandleByKoid(const zx::process& parent,
+                                 zx_koid_t child_koid);
 
 //! \brief Retrieves the koid for a given object handle.
 //!
 //! \param[in] object The handle for which the koid is to be retrieved.
 //! \return The koid of \a handle, or `ZX_HANDLE_INVALID` with an error logged.
-zx_koid_t GetKoidForHandle(zx_handle_t object);
+zx_koid_t GetKoidForHandle(const zx::object_base& object);
 
 }  // namespace crashpad
 
diff --git a/util/fuchsia/scoped_task_suspend.cc b/util/fuchsia/scoped_task_suspend.cc
index ca0fd56..8591d55 100644
--- a/util/fuchsia/scoped_task_suspend.cc
+++ b/util/fuchsia/scoped_task_suspend.cc
@@ -14,68 +14,55 @@
 
 #include "util/fuchsia/scoped_task_suspend.h"
 
-#include <zircon/process.h>
-#include <zircon/syscalls.h>
-#include <zircon/syscalls/debug.h>
+#include <lib/zx/time.h>
+#include <zircon/errors.h>
+#include <zircon/status.h>
+#include <zircon/syscalls/object.h>
 
 #include <vector>
 
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/scoped_zx_handle.h"
 #include "base/logging.h"
 #include "util/fuchsia/koid_utilities.h"
 
 namespace crashpad {
 
-namespace {
+ScopedTaskSuspend::ScopedTaskSuspend(const zx::process& process) {
+  DCHECK_NE(process.get(), zx::process::self()->get());
 
-zx_obj_type_t GetHandleType(zx_handle_t handle) {
-  zx_info_handle_basic_t basic;
-  zx_status_t status = zx_object_get_info(
-      handle, ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "zx_object_get_info";
-    return ZX_OBJ_TYPE_NONE;
-  }
-  return basic.type;
-}
-
-// Returns the suspend token of the suspended thread. This function attempts
-// to wait a short time for the thread to actually suspend before returning
-// but this is not guaranteed.
-base::ScopedZxHandle SuspendThread(zx_handle_t thread) {
-  zx_handle_t token = ZX_HANDLE_INVALID;
-  zx_status_t status = zx_task_suspend_token(thread, &token);
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status) << "zx_task_suspend";
-    base::ScopedZxHandle();
+  const zx_status_t suspend_status = process.suspend(&suspend_token_);
+  if (suspend_status != ZX_OK) {
+    ZX_LOG(ERROR, suspend_status) << "zx_task_suspend";
+    return;
   }
 
-  zx_signals_t observed = 0u;
-  if (zx_object_wait_one(thread, ZX_THREAD_SUSPENDED,
-                         zx_deadline_after(ZX_MSEC(50)), &observed) != ZX_OK) {
-    LOG(ERROR) << "thread failed to suspend";
-  }
-  return base::ScopedZxHandle(token);
-}
+  // suspend() is asynchronous so we now check that each thread is indeed
+  // suspended, up to some deadline.
+  for (const auto& thread : GetThreadHandles(process)) {
+    // We omit the crashed thread (blocked in an exception) as it is technically
+    // not suspended, cf. ZX-3772.
+    zx_info_thread_t info;
+    if (thread.get_info(
+            ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr) == ZX_OK) {
+      if (info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION) {
+        continue;
+      }
+    }
 
-}  // namespace
-
-ScopedTaskSuspend::ScopedTaskSuspend(zx_handle_t task) {
-  DCHECK_NE(task, zx_process_self());
-  DCHECK_NE(task, zx_thread_self());
-
-  zx_obj_type_t type = GetHandleType(task);
-  if (type == ZX_OBJ_TYPE_THREAD) {
-    suspend_tokens_.push_back(SuspendThread(task));
-  } else if (type == ZX_OBJ_TYPE_PROCESS) {
-    for (const auto& thread : GetChildHandles(task, ZX_INFO_PROCESS_THREADS))
-      suspend_tokens_.push_back(SuspendThread(thread.get()));
-  } else {
-    LOG(ERROR) << "unexpected handle type";
+    zx_signals_t observed = 0u;
+    const zx_status_t wait_status = thread.wait_one(
+        ZX_THREAD_SUSPENDED, zx::deadline_after(zx::msec(50)), &observed);
+    if (wait_status != ZX_OK) {
+      zx_info_thread_t info = {};
+      zx_status_t info_status = thread.get_info(
+          ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
+      ZX_LOG(ERROR, wait_status) << "thread failed to suspend";
+      LOG(ERROR) << "Thread info status " << info_status;
+      if (info_status == ZX_OK) {
+        LOG(ERROR) << "Thread state " << info.state;
+      }
+    }
   }
 }
 
-ScopedTaskSuspend::~ScopedTaskSuspend() = default;
-
 }  // namespace crashpad
diff --git a/util/fuchsia/scoped_task_suspend.h b/util/fuchsia/scoped_task_suspend.h
index f817364..afe497b 100644
--- a/util/fuchsia/scoped_task_suspend.h
+++ b/util/fuchsia/scoped_task_suspend.h
@@ -15,37 +15,31 @@
 #ifndef CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_
 #define CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_
 
-#include <zircon/types.h>
+#include <lib/zx/process.h>
+#include <lib/zx/suspend_token.h>
+#include <lib/zx/thread.h>
 
 #include <vector>
 
-#include "base/fuchsia/scoped_zx_handle.h"
 #include "base/macros.h"
 
 namespace crashpad {
 
 //! \brief Manages the suspension of another task.
 //!
-//! The underlying API only supports suspending threads (despite its name) not
-//! entire tasks. As a result, it's possible some threads may not be correctly
-//! suspended/resumed as their creation might race enumeration.
-//!
-//! Additionally, suspending a thread is asynchronous and may take an
-//! arbitrary amount of time.
-//!
-//! Because of these limitations, this class is limited to being a best-effort,
-//! and correct suspension/resumption cannot be relied upon.
+//! Suspending a process is asynchronous, and may take an arbitrary amount of
+//! time. As a result, this class is limited to being a best-effort, and
+//! correct suspension/resumption cannot be relied upon.
 //!
 //! Callers should not attempt to suspend the current task as obtained via
 //! `zx_process_self()`.
 class ScopedTaskSuspend {
  public:
-  explicit ScopedTaskSuspend(zx_handle_t task);
-  ~ScopedTaskSuspend();
+  explicit ScopedTaskSuspend(const zx::process& process);
+  ~ScopedTaskSuspend() = default;
 
  private:
-  // Could be one (for a thread) or many (for every process in a thread).
-  std::vector<base::ScopedZxHandle> suspend_tokens_;
+  zx::suspend_token suspend_token_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspend);
 };
diff --git a/util/fuchsia/system_exception_port_key.h b/util/fuchsia/system_exception_port_key.h
deleted file mode 100644
index 0bbae69..0000000
--- a/util/fuchsia/system_exception_port_key.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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_UTIL_FUCHSIA_EXCEPTION_PORT_KEY_H_
-#define CRASHPAD_UTIL_FUCHSIA_EXCEPTION_PORT_KEY_H_
-
-namespace crashpad {
-
-//! \brief The key used in `zx_task_bind_exception_port()` and packet
-//!     processing. This matches the value that Zircon's `devmgr` and
-//!     `crashlogger` use for interoperability, for now.
-constexpr uint64_t kSystemExceptionPortKey = 1166444u;
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_UTIL_FUCHSIA_EXCEPTION_PORT_KEY_H_
diff --git a/util/ios/exception_processor.h b/util/ios/exception_processor.h
new file mode 100644
index 0000000..76a8c8b
--- /dev/null
+++ b/util/ios/exception_processor.h
@@ -0,0 +1,37 @@
+// Copyright 2020 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_UTIL_IOS_EXCEPTION_PROCESSOR_H_
+#define CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_
+
+namespace crashpad {
+
+//! \brief Installs the Objective-C exception preprocessor.
+//!
+//! When code raises an Objective-C exception, unwind the stack looking for
+//! any exception handlers. If an exception handler is encountered, test to
+//! see if it is a function known to be a catch-and-rethrow 'sinkhole' exception
+//! handler. Various routines in UIKit do this, and they obscure the
+//! crashing stack, since the original throw location is no longer present
+//! on the stack (just the re-throw) when Crashpad captures the crash
+//! report. In the case of sinkholes, trigger an immediate exception to
+//! capture the original stack.
+//!
+//! This should be installed at the same time the CrashpadClient installs the
+//! signal handler. It should only be installed once.
+void InstallObjcExceptionPreprocessor();
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_EXCEPTION_PROCESSOR_H_
diff --git a/util/ios/exception_processor.mm b/util/ios/exception_processor.mm
new file mode 100644
index 0000000..139a851
--- /dev/null
+++ b/util/ios/exception_processor.mm
@@ -0,0 +1,337 @@
+// Copyright 2020 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.
+
+#include "util/ios/exception_processor.h"
+
+#import <Foundation/Foundation.h>
+#include <TargetConditionals.h>
+#include <cxxabi.h>
+#include <dlfcn.h>
+#include <libunwind.h>
+#include <mach-o/loader.h>
+#include <objc/message.h>
+#include <objc/objc-exception.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unwind.h>
+
+#include <exception>
+#include <type_traits>
+#include <typeinfo>
+
+#include "base/bit_cast.h"
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "build/build_config.h"
+
+namespace {
+
+// From 10.15.0 objc4-779.1/runtime/objc-exception.mm.
+struct objc_typeinfo {
+  const void* const* vtable;
+  const char* name;
+  Class cls_unremapped;
+};
+struct objc_exception {
+  id obj;
+  objc_typeinfo tinfo;
+};
+
+// From 10.15.0 objc4-779.1/runtime/objc-abi.h.
+extern "C" const void* const objc_ehtype_vtable[];
+
+// https://github.com/llvm/llvm-project/blob/09dc884eb2e4/libcxxabi/src/cxa_exception.h
+static const uint64_t kOurExceptionClass = 0x434c4e47432b2b00;
+struct __cxa_exception {
+#if defined(ARCH_CPU_64_BITS)
+  void* reserve;
+  size_t referenceCount;
+#endif
+  std::type_info* exceptionType;
+  void (*exceptionDestructor)(void*);
+  std::unexpected_handler unexpectedHandler;
+  std::terminate_handler terminateHandler;
+  __cxa_exception* nextException;
+  int handlerCount;
+  int handlerSwitchValue;
+  const unsigned char* actionRecord;
+  const unsigned char* languageSpecificData;
+  void* catchTemp;
+  void* adjustedPtr;
+#if !defined(ARCH_CPU_64_BITS)
+  size_t referenceCount;
+#endif
+  _Unwind_Exception unwindHeader;
+};
+
+objc_exception_preprocessor g_next_preprocessor;
+bool g_exception_preprocessor_installed;
+
+void TerminatingFromUncaughtNSException(id exception, const char* sinkhole) {
+  // TODO(justincohen): This is incomplete, as the signal handler will not have
+  // access to the exception name and reason.  Pass that along somehow here.
+  NSString* exception_message_ns = [NSString
+      stringWithFormat:@"%@: %@", [exception name], [exception reason]];
+  std::string exception_message = base::SysNSStringToUTF8(exception_message_ns);
+  LOG(INFO) << "Terminating from Objective-C exception: " << exception_message
+            << " with sinkhole: " << sinkhole;
+  // TODO(justincohen): This is temporary, as crashpad can capture this
+  // exception directly instead.
+  std::terminate();
+}
+
+// Returns true if |path| equals |sinkhole| on device. Simulator paths prepend
+// much of Xcode's internal structure, so check that |path| ends with |sinkhole|
+// for simulator.
+bool ModulePathMatchesSinkhole(const char* path, const char* sinkhole) {
+#if TARGET_OS_SIMULATOR
+  size_t path_length = strlen(path);
+  size_t sinkhole_length = strlen(sinkhole);
+  if (sinkhole_length > path_length)
+    return false;
+  return strncmp(path + path_length - sinkhole_length,
+                 sinkhole,
+                 sinkhole_length) == 0;
+#else
+  return strcmp(path, sinkhole) == 0;
+#endif
+}
+
+int LoggingUnwStep(unw_cursor_t* cursor) {
+  int rv = unw_step(cursor);
+  if (rv < 0) {
+    LOG(ERROR) << "unw_step: " << rv;
+  }
+  return rv;
+}
+
+id ObjcExceptionPreprocessor(id exception) {
+  // Unwind the stack looking for any exception handlers. If an exception
+  // handler is encountered, test to see if it is a function known to catch-
+  // and-rethrow as a "top-level" exception handler. Various routines in
+  // Cocoa/UIKit do this, and it obscures the crashing stack, since the original
+  // throw location is no longer present on the stack (just the re-throw) when
+  // Crashpad captures the crash report.
+  unw_context_t context;
+  unw_getcontext(&context);
+
+  unw_cursor_t cursor;
+  unw_init_local(&cursor, &context);
+
+  static const void* this_base_address = []() -> const void* {
+    Dl_info dl_info;
+    if (!dladdr(reinterpret_cast<const void*>(&ObjcExceptionPreprocessor),
+                &dl_info)) {
+      LOG(ERROR) << "dladdr: " << dlerror();
+      return nullptr;
+    }
+    return dl_info.dli_fbase;
+  }();
+
+  // Generate an exception_header for the __personality_routine.
+  // From 10.15.0 objc4-779.1/runtime/objc-exception.mm objc_exception_throw.
+  objc_exception* exception_objc = reinterpret_cast<objc_exception*>(
+      __cxxabiv1::__cxa_allocate_exception(sizeof(objc_exception)));
+  exception_objc->obj = exception;
+  exception_objc->tinfo.vtable = objc_ehtype_vtable + 2;
+  exception_objc->tinfo.name = object_getClassName(exception);
+  exception_objc->tinfo.cls_unremapped = object_getClass(exception);
+
+  // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp
+  // __cxa_throw
+  __cxa_exception* exception_header =
+      reinterpret_cast<__cxa_exception*>(exception_objc) - 1;
+  exception_header->unexpectedHandler = std::get_unexpected();
+  exception_header->terminateHandler = std::get_terminate();
+  exception_header->exceptionType =
+      reinterpret_cast<std::type_info*>(&exception_objc->tinfo);
+  exception_header->unwindHeader.exception_class = kOurExceptionClass;
+
+  bool handler_found = false;
+  while (LoggingUnwStep(&cursor) > 0) {
+    unw_proc_info_t frame_info;
+    if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) {
+      continue;
+    }
+
+    if (frame_info.handler == 0) {
+      continue;
+    }
+
+    // Check to see if the handler is really an exception handler.
+    __personality_routine p =
+        reinterpret_cast<__personality_routine>(frame_info.handler);
+
+    // From 10.15.0 libunwind-35.4/src/UnwindLevel1.c.
+    _Unwind_Reason_Code personalityResult = (*p)(
+        1,
+        _UA_SEARCH_PHASE,
+        exception_header->unwindHeader.exception_class,
+        reinterpret_cast<_Unwind_Exception*>(&exception_header->unwindHeader),
+        reinterpret_cast<_Unwind_Context*>(&cursor));
+    switch (personalityResult) {
+      case _URC_HANDLER_FOUND:
+        break;
+      case _URC_CONTINUE_UNWIND:
+        continue;
+      default:
+        break;
+    }
+
+    char proc_name[512];
+    unw_word_t offset;
+    if (unw_get_proc_name(&cursor, proc_name, sizeof(proc_name), &offset) !=
+        UNW_ESUCCESS) {
+      // The symbol has no name, so see if it belongs to the same image as
+      // this function.
+      Dl_info dl_info;
+      if (dladdr(reinterpret_cast<const void*>(frame_info.start_ip),
+                 &dl_info)) {
+        if (dl_info.dli_fbase == this_base_address) {
+          // This is a handler in our image, so allow it to run.
+          handler_found = true;
+          break;
+        }
+      }
+
+      // This handler does not belong to us, so continue the search.
+      continue;
+    }
+
+    // Check if the function is one that is known to obscure (by way of
+    // catch-and-rethrow) exception stack traces. If it is, sinkhole it
+    // by crashing here at the point of throw.
+    constexpr const char* kExceptionSymbolNameSinkholes[] = {
+        // The two CF symbol names will also be captured by the CoreFoundation
+        // library path check below, but for completeness they are listed here,
+        // since they appear unredacted.
+        "CFRunLoopRunSpecific",
+        "_CFXNotificationPost",
+        "__NSFireDelayedPerform",
+    };
+    for (const char* sinkhole : kExceptionSymbolNameSinkholes) {
+      if (strcmp(sinkhole, proc_name) == 0) {
+        TerminatingFromUncaughtNSException(exception, sinkhole);
+      }
+    }
+
+    // On iOS, function names are often reported as "<redacted>", although they
+    // do appear when attached to the debugger.  When this happens, use the path
+    // of the image to determine if the handler is an exception sinkhole.
+    constexpr const char* kExceptionLibraryPathSinkholes[] = {
+        // Everything in this library is a sinkhole, specifically
+        // _dispatch_client_callout.  Both are needed here depending on whether
+        // the debugger is attached (introspection only appears when a simulator
+        // is attached to a debugger).
+        "/usr/lib/system/introspection/libdispatch.dylib",
+        "/usr/lib/system/libdispatch.dylib",
+
+        // __CFRunLoopDoTimers and __CFRunLoopRun are sinkholes. Consider also
+        // checking that a few frames up is CFRunLoopRunSpecific().
+        "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
+    };
+
+    Dl_info dl_info;
+    if (dladdr(reinterpret_cast<const void*>(frame_info.start_ip), &dl_info) !=
+        0) {
+      for (const char* sinkhole : kExceptionLibraryPathSinkholes) {
+        if (ModulePathMatchesSinkhole(dl_info.dli_fname, sinkhole)) {
+          TerminatingFromUncaughtNSException(exception, sinkhole);
+        }
+      }
+    }
+
+    // Some <redacted> sinkholes are harder to find. _UIGestureEnvironmentUpdate
+    // in UIKitCore is an example. UIKitCore can't be added to
+    // kExceptionLibraryPathSinkholes because it uses Objective-C exceptions
+    // internally and also has has non-sinkhole handlers. Since
+    // _UIGestureEnvironmentUpdate is always called from
+    // -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:],
+    // inspect the caller frame info to match the sinkhole.
+    constexpr const char* kUIKitCorePath =
+        "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore";
+    if (ModulePathMatchesSinkhole(dl_info.dli_fname, kUIKitCorePath)) {
+      unw_proc_info_t caller_frame_info;
+      if (LoggingUnwStep(&cursor) > 0 &&
+          unw_get_proc_info(&cursor, &caller_frame_info) == UNW_ESUCCESS) {
+        static IMP uigesture_deliver_event_imp = [] {
+          IMP imp = class_getMethodImplementation(
+              NSClassFromString(@"UIGestureEnvironment"),
+              NSSelectorFromString(
+                  @"_deliverEvent:toGestureRecognizers:usingBlock:"));
+
+          // From 10.15.0 objc4-779.1/runtime/objc-class.mm
+          // class_getMethodImplementation returns nil or _objc_msgForward on
+          // failure.
+          if (!imp || imp == _objc_msgForward) {
+            LOG(WARNING) << "Unable to find -[UIGestureEnvironment "
+                            "_deliverEvent:toGestureRecognizers:usingBlock:]";
+            return bit_cast<IMP>(nullptr);  // IMP is a function pointer type.
+          }
+          return imp;
+        }();
+
+        if (uigesture_deliver_event_imp ==
+            reinterpret_cast<IMP>(caller_frame_info.start_ip)) {
+          TerminatingFromUncaughtNSException(exception,
+                                             "_UIGestureEnvironmentUpdate");
+        }
+      }
+    }
+
+    handler_found = true;
+
+    break;
+  }
+
+  // If no handler is found, __cxa_throw would call failed_throw and terminate.
+  // See:
+  // https://github.com/llvm/llvm-project/blob/c5d2746fbea7/libcxxabi/src/cxa_exception.cpp
+  // __cxa_throw. Instead, terminate via TerminatingFromUncaughtNSException so
+  // the exception name and reason are properly recorded.
+  if (!handler_found) {
+    TerminatingFromUncaughtNSException(exception, "__cxa_throw");
+  }
+
+  // Forward to the next preprocessor.
+  if (g_next_preprocessor)
+    return g_next_preprocessor(exception);
+
+  return exception;
+}
+
+}  // namespace
+
+namespace crashpad {
+
+void InstallObjcExceptionPreprocessor() {
+  DCHECK(!g_exception_preprocessor_installed);
+
+  g_next_preprocessor =
+      objc_setExceptionPreprocessor(&ObjcExceptionPreprocessor);
+  g_exception_preprocessor_installed = true;
+}
+
+void UninstallObjcExceptionPreprocessor() {
+  DCHECK(g_exception_preprocessor_installed);
+
+  objc_setExceptionPreprocessor(g_next_preprocessor);
+  g_next_preprocessor = nullptr;
+  g_exception_preprocessor_installed = false;
+}
+
+}  // namespace crashpad
diff --git a/util/ios/exception_processor_test.mm b/util/ios/exception_processor_test.mm
new file mode 100644
index 0000000..c9aa716
--- /dev/null
+++ b/util/ios/exception_processor_test.mm
@@ -0,0 +1,41 @@
+// Copyright 2020 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.
+
+#import <Foundation/Foundation.h>
+#include <objc/message.h>
+#include <objc/runtime.h>
+
+#include "gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using IOSExceptionProcessor = PlatformTest;
+
+TEST_F(IOSExceptionProcessor, SelectorExists) {
+  IMP uigesture_deliver_event_imp = class_getMethodImplementation(
+      NSClassFromString(@"UIGestureEnvironment"),
+      NSSelectorFromString(@"_deliverEvent:toGestureRecognizers:usingBlock:"));
+
+  // From 10.15.0 objc4-779.1/runtime/objc-class.mm
+  // class_getMethodImplementation returns nil or _objc_msgForward on failure.
+  ASSERT_TRUE(uigesture_deliver_event_imp);
+  ASSERT_NE(uigesture_deliver_event_imp, _objc_msgForward);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/ios/ios_system_data_collector.h b/util/ios/ios_system_data_collector.h
new file mode 100644
index 0000000..45837c6
--- /dev/null
+++ b/util/ios/ios_system_data_collector.h
@@ -0,0 +1,81 @@
+// Copyright 2020 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_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_
+#define CRASHPAD_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#include <string>
+
+namespace crashpad {
+
+//! \brief Used to collect system level data before a crash occurs.
+class IOSSystemDataCollector {
+ public:
+  IOSSystemDataCollector();
+  ~IOSSystemDataCollector();
+
+  void OSVersion(int* major, int* minor, int* bugfix, std::string* build) const;
+  std::string MachineDescription() const { return machine_description_; }
+  int ProcessorCount() const { return processor_count_; }
+  std::string CPUVendor() const { return cpu_vendor_; }
+  bool HasDaylightSavingTime() const { return has_next_daylight_saving_time_; }
+  bool IsDaylightSavingTime() const { return is_daylight_saving_time_; }
+  int StandardOffsetSeconds() const { return standard_offset_seconds_; }
+  int DaylightOffsetSeconds() const { return daylight_offset_seconds_; }
+  std::string StandardName() const { return standard_name_; }
+  std::string DaylightName() const { return daylight_name_; }
+
+  // Currently unused by minidump.
+  int Orientation() const { return orientation_; }
+
+ private:
+  // Notification handlers.
+  void InstallHandlers();
+  static void SystemTimeZoneDidChangeNotificationHandler(
+      CFNotificationCenterRef center,
+      void* observer,
+      CFStringRef name,
+      const void* object,
+      CFDictionaryRef userInfo);
+  void SystemTimeZoneDidChangeNotification();
+
+  static void OrientationDidChangeNotificationHandler(
+      CFNotificationCenterRef center,
+      void* observer,
+      CFStringRef name,
+      const void* object,
+      CFDictionaryRef userInfo);
+  void OrientationDidChangeNotification();
+
+  int major_version_;
+  int minor_version_;
+  int patch_version_;
+  std::string build_;
+  std::string machine_description_;
+  int orientation_;
+  int processor_count_;
+  std::string cpu_vendor_;
+  bool has_next_daylight_saving_time_;
+  bool is_daylight_saving_time_;
+  int standard_offset_seconds_;
+  int daylight_offset_seconds_;
+  std::string standard_name_;
+  std::string daylight_name_;
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_IOS_IOS_SYSTEM_DATA_COLLECTOR_H_
diff --git a/util/ios/ios_system_data_collector.mm b/util/ios/ios_system_data_collector.mm
new file mode 100644
index 0000000..c81fe3f
--- /dev/null
+++ b/util/ios/ios_system_data_collector.mm
@@ -0,0 +1,209 @@
+// Copyright 2020 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.
+
+#include "util/ios/ios_system_data_collector.h"
+
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+#import <Foundation/Foundation.h>
+#include <TargetConditionals.h>
+#import <UIKit/UIKit.h>
+
+#include "base/mac/mach_logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/sys_string_conversions.h"
+#include "build/build_config.h"
+
+namespace {
+
+std::string ReadStringSysctlByName(const char* name) {
+  size_t buf_len;
+  if (sysctlbyname(name, nullptr, &buf_len, nullptr, 0) != 0) {
+    PLOG(WARNING) << "sysctlbyname (size) " << name;
+    return std::string();
+  }
+
+  if (buf_len == 0) {
+    return std::string();
+  }
+
+  std::string value(buf_len - 1, '\0');
+  if (sysctlbyname(name, &value[0], &buf_len, nullptr, 0) != 0) {
+    PLOG(WARNING) << "sysctlbyname " << name;
+    return std::string();
+  }
+
+  return value;
+}
+
+}  // namespace
+
+namespace crashpad {
+
+IOSSystemDataCollector::IOSSystemDataCollector()
+    : major_version_(0),
+      minor_version_(0),
+      patch_version_(0),
+      build_(),
+      machine_description_(),
+      orientation_(0),
+      processor_count_(0),
+      cpu_vendor_(),
+      has_next_daylight_saving_time_(false),
+      is_daylight_saving_time_(false),
+      standard_offset_seconds_(0),
+      daylight_offset_seconds_(0),
+      standard_name_(),
+      daylight_name_() {
+  NSOperatingSystemVersion version =
+      [[NSProcessInfo processInfo] operatingSystemVersion];
+  major_version_ = base::saturated_cast<int>(version.majorVersion);
+  minor_version_ = base::saturated_cast<int>(version.minorVersion);
+  patch_version_ = base::saturated_cast<int>(version.patchVersion);
+  processor_count_ =
+      base::saturated_cast<int>([[NSProcessInfo processInfo] processorCount]);
+  build_ = ReadStringSysctlByName("kern.osversion");
+
+#if defined(ARCH_CPU_X86_64)
+  cpu_vendor_ = ReadStringSysctlByName("machdep.cpu.vendor");
+#endif
+
+#if TARGET_OS_SIMULATOR
+  // TODO(justincohen): Consider adding board and model information to
+  // |machine_description| as well (similar to MacModelAndBoard in
+  // util/mac/mac_util.cc).
+  switch (UI_USER_INTERFACE_IDIOM()) {
+    case UIUserInterfaceIdiomPhone:
+      machine_description_ = "iOS Simulator (iPhone)";
+      break;
+    case UIUserInterfaceIdiomPad:
+      machine_description_ = "iOS Simulator (iPad)";
+      break;
+    default:
+      machine_description_ = "iOS Simulator (Unknown)";
+      break;
+  }
+#elif TARGET_OS_IPHONE
+  utsname uts;
+  if (uname(&uts) == 0) {
+    machine_description_ = uts.machine;
+  }
+#else
+#error "Unexpected target type OS."
+#endif
+
+  InstallHandlers();
+}
+
+IOSSystemDataCollector::~IOSSystemDataCollector() {
+  CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetLocalCenter(),
+                                          this);
+}
+
+void IOSSystemDataCollector::OSVersion(int* major,
+                                       int* minor,
+                                       int* bugfix,
+                                       std::string* build) const {
+  *major = major_version_;
+  *minor = minor_version_;
+  *bugfix = patch_version_;
+  build->assign(build_);
+}
+
+void IOSSystemDataCollector::InstallHandlers() {
+  // Timezone.
+  CFNotificationCenterAddObserver(
+      CFNotificationCenterGetLocalCenter(),
+      this,
+      IOSSystemDataCollector::SystemTimeZoneDidChangeNotificationHandler,
+      reinterpret_cast<CFStringRef>(NSSystemTimeZoneDidChangeNotification),
+      nullptr,
+      CFNotificationSuspensionBehaviorDeliverImmediately);
+  SystemTimeZoneDidChangeNotification();
+
+  // Orientation.
+  CFNotificationCenterAddObserver(
+      CFNotificationCenterGetLocalCenter(),
+      this,
+      IOSSystemDataCollector::OrientationDidChangeNotificationHandler,
+      reinterpret_cast<CFStringRef>(UIDeviceOrientationDidChangeNotification),
+      nullptr,
+      CFNotificationSuspensionBehaviorDeliverImmediately);
+  OrientationDidChangeNotification();
+}
+
+// static
+void IOSSystemDataCollector::SystemTimeZoneDidChangeNotificationHandler(
+    CFNotificationCenterRef center,
+    void* observer,
+    CFStringRef name,
+    const void* object,
+    CFDictionaryRef userInfo) {
+  static_cast<IOSSystemDataCollector*>(observer)
+      ->SystemTimeZoneDidChangeNotification();
+}
+
+void IOSSystemDataCollector::SystemTimeZoneDidChangeNotification() {
+  NSTimeZone* time_zone = NSTimeZone.localTimeZone;
+  NSDate* transition =
+      [time_zone nextDaylightSavingTimeTransitionAfterDate:[NSDate date]];
+  if (transition == nil) {
+    has_next_daylight_saving_time_ = false;
+    is_daylight_saving_time_ = false;
+    standard_offset_seconds_ =
+        base::saturated_cast<int>([time_zone secondsFromGMTForDate:transition]);
+    standard_name_ = base::SysNSStringToUTF8([time_zone abbreviation]);
+    daylight_offset_seconds_ = standard_offset_seconds_;
+    daylight_name_ = standard_name_;
+  } else {
+    has_next_daylight_saving_time_ = true;
+    is_daylight_saving_time_ = time_zone.isDaylightSavingTime;
+    if (time_zone.isDaylightSavingTime) {
+      standard_offset_seconds_ = base::saturated_cast<int>(
+          [time_zone secondsFromGMTForDate:transition]);
+      standard_name_ =
+          base::SysNSStringToUTF8([time_zone abbreviationForDate:transition]);
+      daylight_offset_seconds_ =
+          base::saturated_cast<int>([time_zone secondsFromGMT]);
+      daylight_name_ = base::SysNSStringToUTF8([time_zone abbreviation]);
+    } else {
+      standard_offset_seconds_ =
+          base::saturated_cast<int>([time_zone secondsFromGMT]);
+      standard_name_ = base::SysNSStringToUTF8([time_zone abbreviation]);
+      daylight_offset_seconds_ = base::saturated_cast<int>(
+          [time_zone secondsFromGMTForDate:transition]);
+      daylight_name_ =
+          base::SysNSStringToUTF8([time_zone abbreviationForDate:transition]);
+    }
+  }
+}
+
+// static
+void IOSSystemDataCollector::OrientationDidChangeNotificationHandler(
+    CFNotificationCenterRef center,
+    void* observer,
+    CFStringRef name,
+    const void* object,
+    CFDictionaryRef userInfo) {
+  static_cast<IOSSystemDataCollector*>(observer)
+      ->OrientationDidChangeNotification();
+}
+
+void IOSSystemDataCollector::OrientationDidChangeNotification() {
+  orientation_ =
+      base::saturated_cast<int>([[UIDevice currentDevice] orientation]);
+}
+
+}  // namespace crashpad
diff --git a/util/linux/auxiliary_vector_test.cc b/util/linux/auxiliary_vector_test.cc
index d4cfcb7..00af5ab 100644
--- a/util/linux/auxiliary_vector_test.cc
+++ b/util/linux/auxiliary_vector_test.cc
@@ -120,8 +120,9 @@
 
 #if defined(AT_SYSINFO_EHDR)
   LinuxVMAddress vdso_addr;
-  ASSERT_TRUE(aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr));
-  EXPECT_TRUE(mappings.FindMapping(vdso_addr));
+  if (aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr)) {
+    EXPECT_TRUE(mappings.FindMapping(vdso_addr));
+  }
 #endif  // AT_SYSINFO_EHDR
 
 #if defined(AT_EXECFN)
diff --git a/util/linux/direct_ptrace_connection.cc b/util/linux/direct_ptrace_connection.cc
index a3df425..8b55205 100644
--- a/util/linux/direct_ptrace_connection.cc
+++ b/util/linux/direct_ptrace_connection.cc
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "util/file/file_io.h"
+#include "util/linux/proc_task_reader.h"
 
 namespace crashpad {
 
@@ -81,4 +82,9 @@
   return &memory_;
 }
 
+bool DirectPtraceConnection::Threads(std::vector<pid_t>* threads) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return ReadThreadIDs(pid_, threads);
+}
+
 }  // namespace crashpad
diff --git a/util/linux/direct_ptrace_connection.h b/util/linux/direct_ptrace_connection.h
index 53594ef..10f8370 100644
--- a/util/linux/direct_ptrace_connection.h
+++ b/util/linux/direct_ptrace_connection.h
@@ -56,6 +56,7 @@
   bool ReadFileContents(const base::FilePath& path,
                         std::string* contents) override;
   ProcessMemory* Memory() override;
+  bool Threads(std::vector<pid_t>* threads) override;
 
  private:
   std::vector<std::unique_ptr<ScopedPtraceAttach>> attachments_;
diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc
index 6333351..98edb30 100644
--- a/util/linux/exception_handler_client.cc
+++ b/util/linux/exception_handler_client.cc
@@ -15,27 +15,84 @@
 #include "util/linux/exception_handler_client.h"
 
 #include <errno.h>
+#include <signal.h>
 #include <sys/prctl.h>
-#include <sys/socket.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
 #include "build/build_config.h"
+#include "third_party/lss/lss.h"
 #include "util/file/file_io.h"
 #include "util/linux/ptrace_broker.h"
+#include "util/linux/socket.h"
+#include "util/misc/from_pointer_cast.h"
 #include "util/posix/signals.h"
 
+#if defined(OS_ANDROID)
+#include <android/api-level.h>
+#endif
+
 namespace crashpad {
 
-ExceptionHandlerClient::ExceptionHandlerClient(int sock)
-    : server_sock_(sock), ptracer_(-1), can_set_ptracer_(true) {}
+namespace {
+
+class ScopedSigprocmaskRestore {
+ public:
+  explicit ScopedSigprocmaskRestore(const kernel_sigset_t& set_to_block)
+      : orig_mask_(), mask_is_set_(false) {
+    mask_is_set_ = sys_sigprocmask(SIG_BLOCK, &set_to_block, &orig_mask_) == 0;
+    DPLOG_IF(ERROR, !mask_is_set_) << "sigprocmask";
+  }
+
+  ~ScopedSigprocmaskRestore() {
+    if (mask_is_set_ &&
+        sys_sigprocmask(SIG_SETMASK, &orig_mask_, nullptr) != 0) {
+      DPLOG(ERROR) << "sigprocmask";
+    }
+  }
+
+ private:
+  kernel_sigset_t orig_mask_;
+  bool mask_is_set_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedSigprocmaskRestore);
+};
+
+}  // namespace
+
+ExceptionHandlerClient::ExceptionHandlerClient(int sock, bool multiple_clients)
+    : server_sock_(sock),
+      ptracer_(-1),
+      can_set_ptracer_(true),
+      multiple_clients_(multiple_clients) {}
 
 ExceptionHandlerClient::~ExceptionHandlerClient() = default;
 
-int ExceptionHandlerClient::RequestCrashDump(const ClientInformation& info) {
-  int status = SendCrashDumpRequest(info);
+bool ExceptionHandlerClient::GetHandlerCredentials(ucred* creds) {
+  ExceptionHandlerProtocol::ClientToServerMessage message = {};
+  message.type =
+      ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials;
+  if (UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)) !=
+      0) {
+    return false;
+  }
+
+  ExceptionHandlerProtocol::ServerToClientMessage response;
+  return UnixCredentialSocket::RecvMsg(
+      server_sock_, &response, sizeof(response), creds);
+}
+
+int ExceptionHandlerClient::RequestCrashDump(
+    const ExceptionHandlerProtocol::ClientInformation& info) {
+  VMAddress sp = FromPointerCast<VMAddress>(&sp);
+
+  if (multiple_clients_) {
+    return SignalCrashDump(info, sp);
+  }
+
+  int status = SendCrashDumpRequest(info, sp);
   if (status != 0) {
     return status;
   }
@@ -61,59 +118,56 @@
   can_set_ptracer_ = can_set_ptracer;
 }
 
-int ExceptionHandlerClient::SendCrashDumpRequest(
-    const ClientInformation& info) {
-  ClientToServerMessage message;
-  message.type = ClientToServerMessage::kCrashDumpRequest;
-  message.client_info = info;
+int ExceptionHandlerClient::SignalCrashDump(
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    VMAddress stack_pointer) {
+  kernel_sigset_t dump_done_sigset;
+  sys_sigemptyset(&dump_done_sigset);
+  sys_sigaddset(&dump_done_sigset, ExceptionHandlerProtocol::kDumpDoneSignal);
+  ScopedSigprocmaskRestore scoped_block(dump_done_sigset);
 
-  iovec iov;
-  iov.iov_base = &message;
-  iov.iov_len = sizeof(message);
+  int status = SendCrashDumpRequest(info, stack_pointer);
+  if (status != 0) {
+    return status;
+  }
 
-  msghdr msg;
-  msg.msg_name = nullptr;
-  msg.msg_namelen = 0;
-  msg.msg_iov = &iov;
-  msg.msg_iovlen = 1;
-
-  ucred creds;
-  creds.pid = getpid();
-  creds.uid = geteuid();
-  creds.gid = getegid();
-
-  char cmsg_buf[CMSG_SPACE(sizeof(creds))];
-  msg.msg_control = cmsg_buf;
-  msg.msg_controllen = sizeof(cmsg_buf);
-
-  cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
-  cmsg->cmsg_level = SOL_SOCKET;
-  cmsg->cmsg_type = SCM_CREDENTIALS;
-  cmsg->cmsg_len = CMSG_LEN(sizeof(creds));
-  *reinterpret_cast<ucred*>(CMSG_DATA(cmsg)) = creds;
-
-  if (HANDLE_EINTR(sendmsg(server_sock_, &msg, MSG_NOSIGNAL)) < 0) {
-    PLOG(ERROR) << "sendmsg";
+  siginfo_t siginfo = {};
+  timespec timeout;
+  timeout.tv_sec = 5;
+  timeout.tv_nsec = 0;
+  if (HANDLE_EINTR(sys_sigtimedwait(&dump_done_sigset, &siginfo, &timeout)) <
+      0) {
     return errno;
   }
 
   return 0;
 }
 
+int ExceptionHandlerClient::SendCrashDumpRequest(
+    const ExceptionHandlerProtocol::ClientInformation& info,
+    VMAddress stack_pointer) {
+  ExceptionHandlerProtocol::ClientToServerMessage message;
+  message.type =
+      ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest;
+  message.requesting_thread_stack_address = stack_pointer;
+  message.client_info = info;
+  return UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message));
+}
+
 int ExceptionHandlerClient::WaitForCrashDumpComplete() {
-  ServerToClientMessage message;
+  ExceptionHandlerProtocol::ServerToClientMessage message;
 
   // If the server hangs up, ReadFileExactly will return false without setting
   // errno.
   errno = 0;
   while (ReadFileExactly(server_sock_, &message, sizeof(message))) {
     switch (message.type) {
-      case ServerToClientMessage::kTypeForkBroker: {
+      case ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker: {
         Signals::InstallDefaultHandler(SIGCHLD);
 
         pid_t pid = fork();
         if (pid <= 0) {
-          Errno error = pid < 0 ? errno : 0;
+          ExceptionHandlerProtocol::Errno error = pid < 0 ? errno : 0;
           if (!WriteFile(server_sock_, &error, sizeof(error))) {
             return errno;
           }
@@ -144,16 +198,22 @@
         continue;
       }
 
-      case ServerToClientMessage::kTypeSetPtracer: {
-        Errno result = SetPtracer(message.pid);
+      case ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer: {
+        ExceptionHandlerProtocol::Errno result = SetPtracer(message.pid);
         if (!WriteFile(server_sock_, &result, sizeof(result))) {
           return errno;
         }
         continue;
       }
 
-      case ServerToClientMessage::kTypeCrashDumpComplete:
-      case ServerToClientMessage::kTypeCrashDumpFailed:
+      case ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials:
+        DCHECK(false);
+        continue;
+
+      case ExceptionHandlerProtocol::ServerToClientMessage::
+          kTypeCrashDumpComplete:
+      case ExceptionHandlerProtocol::ServerToClientMessage::
+          kTypeCrashDumpFailed:
         return 0;
     }
 
diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h
index a60b065..4e10fa6 100644
--- a/util/linux/exception_handler_client.h
+++ b/util/linux/exception_handler_client.h
@@ -15,6 +15,7 @@
 #ifndef CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_
 #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_
 
+#include <sys/socket.h>
 #include <sys/types.h>
 
 #include "base/macros.h"
@@ -28,17 +29,30 @@
   //! \brief Constructs this object.
   //!
   //! \param[in] sock A socket connected to an ExceptionHandlerServer.
-  explicit ExceptionHandlerClient(int sock);
+  //! \param[in] multiple_clients `true` if this socket may be used by multiple
+  //!     clients.
+  ExceptionHandlerClient(int sock, bool multiple_clients);
 
   ~ExceptionHandlerClient();
 
+  //! \brief Communicates with the handler to determine its credentials.
+  //!
+  //! If using a multi-client socket, this method should be called before
+  //! sharing the client socket end, or the handler's response may not be
+  //! received.
+  //!
+  //! \param[out] creds The handler process' credentials, valid if this method
+  //!     returns `true`.
+  //! \return `true` on success. Otherwise, `false` with a message logged.
+  bool GetHandlerCredentials(ucred* creds);
+
   //! \brief Request a crash dump from the ExceptionHandlerServer.
   //!
   //! This method blocks until the crash dump is complete.
   //!
   //! \param[in] info Information about this client.
   //! \return 0 on success or an error code on failure.
-  int RequestCrashDump(const ClientInformation& info);
+  int RequestCrashDump(const ExceptionHandlerProtocol::ClientInformation& info);
 
   //! \brief Uses `prctl(PR_SET_PTRACER, ...)` to set the process with
   //!     process ID \a pid as the ptracer for this process.
@@ -53,12 +67,17 @@
   void SetCanSetPtracer(bool can_set_ptracer);
 
  private:
-  int SendCrashDumpRequest(const ClientInformation& info);
+  int SendCrashDumpRequest(
+      const ExceptionHandlerProtocol::ClientInformation& info,
+      VMAddress stack_pointer);
+  int SignalCrashDump(const ExceptionHandlerProtocol::ClientInformation& info,
+                      VMAddress stack_pointer);
   int WaitForCrashDumpComplete();
 
   int server_sock_;
   pid_t ptracer_;
   bool can_set_ptracer_;
+  bool multiple_clients_;
 
   DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerClient);
 };
diff --git a/util/linux/exception_handler_protocol.cc b/util/linux/exception_handler_protocol.cc
index 3feca69..45590c8 100644
--- a/util/linux/exception_handler_protocol.cc
+++ b/util/linux/exception_handler_protocol.cc
@@ -16,10 +16,18 @@
 
 namespace crashpad {
 
-ClientInformation::ClientInformation()
-    : exception_information_address(0), sanitization_information_address(0) {}
+ExceptionHandlerProtocol::ClientInformation::ClientInformation()
+    : exception_information_address(0),
+      sanitization_information_address(0)
+#if defined(OS_LINUX)
+      , crash_loop_before_time(0)
+#endif  // OS_LINUX
+{}
 
-ClientToServerMessage::ClientToServerMessage()
-    : version(kVersion), type(kCrashDumpRequest), client_info() {}
+ExceptionHandlerProtocol::ClientToServerMessage::ClientToServerMessage()
+    : version(kVersion),
+      type(kTypeCrashDumpRequest),
+      requesting_thread_stack_address(0),
+      client_info() {}
 
 }  // namespace crashpad
diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h
index 4ba1b5d..7312b9d 100644
--- a/util/linux/exception_handler_protocol.h
+++ b/util/linux/exception_handler_protocol.h
@@ -16,81 +16,119 @@
 #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_
 
 #include <errno.h>
+#include <signal.h>
 #include <stdint.h>
 #include <sys/types.h>
 
+#include "base/macros.h"
+#include "build/build_config.h"
 #include "util/file/file_io.h"
 #include "util/misc/address_types.h"
 
 namespace crashpad {
 
+class ExceptionHandlerProtocol {
+ public:
 #pragma pack(push, 1)
 
-//! \brief The type used for error reporting.
-using Errno = int32_t;
-static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small");
+  //! \brief The type used for error reporting.
+  using Errno = int32_t;
+  static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small");
 
-//! \brief A boolean status suitable for communication between processes.
-enum Bool : char { kBoolFalse, kBoolTrue };
+  //! \brief A boolean status suitable for communication between processes.
+  enum Bool : char { kBoolFalse, kBoolTrue };
 
-//! \brief Information about a client registered with an ExceptionHandlerServer.
-struct ClientInformation {
-  //! \brief Constructs this object.
-  ClientInformation();
+  //! \brief Information about a client registered with an
+  //!     ExceptionHandlerServer.
+  struct ClientInformation {
+    //! \brief Constructs this object.
+    ClientInformation();
 
-  //! \brief The address in the client's address space of an
-  //!     ExceptionInformation struct.
-  VMAddress exception_information_address;
+    //! \brief The address in the client's address space of an
+    //!     ExceptionInformation struct.
+    VMAddress exception_information_address;
 
-  //! \brief The address in the client's address space of a
-  //!     SanitizationInformation struct, or 0 if there is no such struct.
-  VMAddress sanitization_information_address;
-};
+    //! \brief The address in the client's address space of a
+    //!     SanitizationInformation struct, or 0 if there is no such struct.
+    VMAddress sanitization_information_address;
 
-//! \brief The message passed from client to server.
-struct ClientToServerMessage {
-  static constexpr int32_t kVersion = 1;
-
-  //! \brief Constructs this object.
-  ClientToServerMessage();
-
-  //! \brief Indicates what message version is being used.
-  int32_t version;
-
-  enum Type : uint32_t {
-    //! \brief Used to request a crash dump for the sending client.
-    kCrashDumpRequest
-  } type;
-
-  union {
-    //! \brief Valid for type == kCrashDumpRequest
-    ClientInformation client_info;
+#if defined(OS_LINUX)
+    //! \brief Indicates that the client is likely in a crash loop if a crash
+    //!     occurs before this timestamp. This value is only used by ChromeOS's
+    //!     `/sbin/crash_reporter`.
+    uint64_t crash_loop_before_time;
+#endif
   };
-};
 
-//! \brief The message passed from server to client.
-struct ServerToClientMessage {
-  enum Type : uint32_t {
-    //! \brief Indicates that the client should fork a PtraceBroker process.
-    kTypeForkBroker,
+  //! \brief The signal used to indicate a crash dump is complete.
+  //!
+  //! When multiple clients share a single socket connection with the handler,
+  //! the handler sends this signal to the dump requestor to indicate when the
+  //! the dump is either done or has failed and the client may continue.
+  static constexpr int kDumpDoneSignal = SIGCONT;
 
-    //! \brief Inidicates that the client should set allow the handler to trace
-    //!     it using PR_SET_PTRACER.
-    kTypeSetPtracer,
+  //! \brief The message passed from client to server.
+  struct ClientToServerMessage {
+    static constexpr int32_t kVersion = 1;
 
-    //! \brief Indicates that the handler has completed a requested crash dump.
-    kTypeCrashDumpComplete,
+    //! \brief Constructs this object.
+    ClientToServerMessage();
 
-    //! \brief Indicicates that the handler was unable to produce a crash dump.
-    kTypeCrashDumpFailed
-  } type;
+    //! \brief Indicates what message version is being used.
+    int32_t version;
 
-  //! \brief The handler's process ID. Valid for kTypeSetPtracer.
-  pid_t pid;
-};
+    enum Type : uint32_t {
+      //! \brief Request that the server respond with its credentials.
+      kTypeCheckCredentials,
+
+      //! \brief Used to request a crash dump for the sending client.
+      kTypeCrashDumpRequest
+    };
+
+    Type type;
+
+    //! \brief A stack address of the thread sending the message.
+    VMAddress requesting_thread_stack_address;
+
+    union {
+      //! \brief Valid for type == kCrashDumpRequest
+      ClientInformation client_info;
+    };
+  };
+
+  //! \brief The message passed from server to client.
+  struct ServerToClientMessage {
+    enum Type : uint32_t {
+      //! \brief Used to pass credentials with `SCM_CREDENTIALS`.
+      kTypeCredentials,
+
+      //! \brief Indicates that the client should fork a PtraceBroker process.
+      kTypeForkBroker,
+
+      //! \brief Inidicates that the client should set allow the handler to
+      //!     trace it using PR_SET_PTRACER.
+      kTypeSetPtracer,
+
+      //! \brief Indicates that the handler has completed a requested crash
+      //!     dump.
+      kTypeCrashDumpComplete,
+
+      //! \brief Indicicates that the handler was unable to produce a crash
+      //!     dump.
+      kTypeCrashDumpFailed
+    };
+
+    Type type;
+
+    //! \brief The handler's process ID. Valid for kTypeSetPtracer.
+    pid_t pid;
+  };
 
 #pragma pack(pop)
 
+  DISALLOW_IMPLICIT_CONSTRUCTORS(ExceptionHandlerProtocol);
+};
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_
diff --git a/util/linux/initial_signal_dispositions.cc b/util/linux/initial_signal_dispositions.cc
new file mode 100644
index 0000000..b72b247
--- /dev/null
+++ b/util/linux/initial_signal_dispositions.cc
@@ -0,0 +1,78 @@
+// Copyright 2020 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.
+
+#include "util/linux/initial_signal_dispositions.h"
+
+#include <android/api-level.h>
+#include <signal.h>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if __ANDROID_API__ <= 23
+
+namespace crashpad {
+namespace {
+bool LoggingSignal(int signum, sighandler_t handler) {
+  sighandler_t previous = signal(signum, handler);
+  PLOG_IF(ERROR, previous == SIG_ERR) << "signal " << signum;
+  return previous != SIG_ERR;
+}
+}  // namespace
+
+#endif  // __ANDROID_API__ <= 23
+
+bool InitializeSignalDispositions() {
+#if __ANDROID_API__ <= 23
+  const int api_level = android_get_device_api_level();
+  if (api_level < 0) {
+    LOG(WARNING) << "bad api level";
+    return false;
+  }
+
+  // Bionic installs signal handlers which request crash dumps from Android's
+  // debuggerd, but there are errors in how signals which aren't automatically
+  // re-raised are handled on Marshmallow (API 23).
+  //
+  // Before requesting a dump, Bionic acquires a lock to communicate with
+  // debuggerd and expecting imminent death, never releases it.
+  //
+  // While handling the dump request, debuggerd allows the dying process to
+  // continue before ptrace-detaching it. So, when Bionic manually re-raises a
+  // signal, it is intercepted by debuggerd and the dying process is allowed to
+  // live.
+  //
+  // Bionic restores SIG_DFL for the signal it's just handled, but if a
+  // different crash signal is later recieved, Bionic attempts to reacquire the
+  // lock to communicate with debuggerd and blocks forever.
+  //
+  // Disable Bionic's signal handlers for these signals on Marshmallow.
+  bool success = true;
+  if (api_level == 23) {
+    success = LoggingSignal(SIGABRT, SIG_DFL);
+    success = LoggingSignal(SIGFPE, SIG_DFL) && success;
+    success = LoggingSignal(SIGPIPE, SIG_DFL) && success;
+#if defined(SIGSTKFLT)
+    success = LoggingSignal(SIGSTKFLT, SIG_DFL) && success;
+#endif
+    success = LoggingSignal(SIGTRAP, SIG_DFL) && success;
+  }
+
+  return success;
+#else
+  return true;
+#endif  // __ANDROID_API__ <= 23
+}
+
+}  // namespace crashpad
diff --git a/util/linux/initial_signal_dispositions.h b/util/linux/initial_signal_dispositions.h
new file mode 100644
index 0000000..b67083c
--- /dev/null
+++ b/util/linux/initial_signal_dispositions.h
@@ -0,0 +1,41 @@
+// Copyright 2020 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_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H
+#define CRASHPAD_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H
+
+namespace crashpad {
+
+//! \brief Establishes signal dispositions for a process based on the platform.
+//!
+//! Default signal dispositions are normally configured by the kernel, but
+//! additional signal handlers might be installed by dependent or preloaded
+//! libraries, e.g. Bionic normally installs signal handlers which log stack
+//! traces to Android's logcat.
+//!
+//! This function initializes signal dispositions when the default dispositions
+//! provided by the platform are broken. This function must be called before any
+//! application level signal handlers have been installed and should be called
+//! early in the process lifetime to reduce the chance of any broken signal
+//! handlers being triggered.
+//!
+//! When running on Android M (API 23), this function installs `SIG_DFL` for
+//! signals: `SIGABRT`, `SIGFPE`, `SIGPIPE`, `SIGSTKFLT`, and `SIGTRAP`.
+//!
+//! \return `true` on success. Otherwise `false` with a message logged.
+bool InitializeSignalDispositions();
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_LINUX_INITIAL_SIGNAL_DISPOSITIONS_H
diff --git a/util/linux/memory_map.cc b/util/linux/memory_map.cc
index 345f63c..0890cd6 100644
--- a/util/linux/memory_map.cc
+++ b/util/linux/memory_map.cc
@@ -18,6 +18,7 @@
 #include <string.h>
 #include <sys/sysmacros.h>
 
+#include "base/bit_cast.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "build/build_config.h"
@@ -30,31 +31,9 @@
 
 namespace {
 
-// This function is used in this file specfically for signed or unsigned longs.
-// longs are typically either int or int64 sized, but pointers to longs are not
-// automatically coerced to pointers to ints when they are the same size.
-// Simply adding a StringToNumber for longs doesn't work since sometimes long
-// and int64_t are actually the same type, resulting in a redefinition error.
-template <typename Type>
-bool LocalStringToNumber(const std::string& string, Type* number) {
-  static_assert(sizeof(Type) == sizeof(int) || sizeof(Type) == sizeof(int64_t),
-                "Unexpected Type size");
-
-  if (sizeof(Type) == sizeof(int)) {
-    return std::numeric_limits<Type>::is_signed
-               ? StringToNumber(string, reinterpret_cast<int*>(number))
-               : StringToNumber(string,
-                                reinterpret_cast<unsigned int*>(number));
-  } else {
-    return std::numeric_limits<Type>::is_signed
-               ? StringToNumber(string, reinterpret_cast<int64_t*>(number))
-               : StringToNumber(string, reinterpret_cast<uint64_t*>(number));
-  }
-}
-
 template <typename Type>
 bool HexStringToNumber(const std::string& string, Type* number) {
-  return LocalStringToNumber("0x" + string, number);
+  return StringToNumber("0x" + string, number);
 }
 
 // The result from parsing a line from the maps file.
@@ -179,7 +158,7 @@
 
   if (maps_file_reader->GetDelim(' ', &field) !=
           DelimitedFileReader::Result::kSuccess ||
-      (field.pop_back(), !LocalStringToNumber(field, &mapping.inode))) {
+      (field.pop_back(), !StringToNumber(field, &mapping.inode))) {
     LOG(ERROR) << "format error";
     return ParseResult::kError;
   }
@@ -204,6 +183,48 @@
   return ParseResult::kSuccess;
 }
 
+class SparseReverseIterator : public MemoryMap::Iterator {
+ public:
+  SparseReverseIterator(const std::vector<const MemoryMap::Mapping*>& mappings)
+      : mappings_(mappings), riter_(mappings_.rbegin()) {}
+
+  SparseReverseIterator() : mappings_(), riter_(mappings_.rend()) {}
+
+  // Iterator:
+  const MemoryMap::Mapping* Next() override {
+    return riter_ == mappings_.rend() ? nullptr : *(riter_++);
+  }
+
+  unsigned int Count() override { return mappings_.rend() - riter_; }
+
+ private:
+  std::vector<const MemoryMap::Mapping*> mappings_;
+  std::vector<const MemoryMap::Mapping*>::reverse_iterator riter_;
+
+  DISALLOW_COPY_AND_ASSIGN(SparseReverseIterator);
+};
+
+class FullReverseIterator : public MemoryMap::Iterator {
+ public:
+  FullReverseIterator(
+      std::vector<MemoryMap::Mapping>::const_reverse_iterator rbegin,
+      std::vector<MemoryMap::Mapping>::const_reverse_iterator rend)
+      : riter_(rbegin), rend_(rend) {}
+
+  // Iterator:
+  const MemoryMap::Mapping* Next() override {
+    return riter_ == rend_ ? nullptr : &*riter_++;
+  }
+
+  unsigned int Count() override { return rend_ - riter_; }
+
+ private:
+  std::vector<MemoryMap::Mapping>::const_reverse_iterator riter_;
+  std::vector<MemoryMap::Mapping>::const_reverse_iterator rend_;
+
+  DISALLOW_COPY_AND_ASSIGN(FullReverseIterator);
+};
+
 }  // namespace
 
 MemoryMap::Mapping::Mapping()
@@ -296,7 +317,7 @@
   return nullptr;
 }
 
-std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
+std::unique_ptr<MemoryMap::Iterator> MemoryMap::FindFilePossibleMmapStarts(
     const Mapping& mapping) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
@@ -308,27 +329,74 @@
     for (const auto& candidate : mappings_) {
       if (mapping.Equals(candidate)) {
         possible_starts.push_back(&candidate);
-        return possible_starts;
+        return std::make_unique<SparseReverseIterator>(possible_starts);
       }
     }
 
     LOG(ERROR) << "mapping not found";
-    return std::vector<const Mapping*>();
+    return std::make_unique<SparseReverseIterator>();
   }
 
+#if defined(OS_ANDROID)
+  // The Android Chromium linker uses ashmem to share RELRO segments between
+  // processes. The original RELRO segment has been unmapped and replaced with a
+  // mapping named "/dev/ashmem/RELRO:<libname>" where <libname> is the base
+  // library name (e.g. libchrome.so) sans any preceding path that may be
+  // present in other mappings for the library.
+  // https://crashpad.chromium.org/bug/253
+  static constexpr char kRelro[] = "/dev/ashmem/RELRO:";
+  if (mapping.name.compare(0, strlen(kRelro), kRelro, 0, strlen(kRelro)) == 0) {
+    // The kernel appends "(deleted)" to ashmem mappings because there isn't
+    // any corresponding file on the filesystem.
+    static constexpr char kDeleted[] = " (deleted)";
+    size_t libname_end = mapping.name.rfind(kDeleted);
+    DCHECK_NE(libname_end, std::string::npos);
+    if (libname_end == std::string::npos) {
+      libname_end = mapping.name.size();
+    }
+
+    std::string libname =
+        mapping.name.substr(strlen(kRelro), libname_end - strlen(kRelro));
+    for (const auto& candidate : mappings_) {
+      if (candidate.name.rfind(libname) != std::string::npos) {
+        possible_starts.push_back(&candidate);
+      }
+      if (mapping.Equals(candidate)) {
+        return std::make_unique<SparseReverseIterator>(possible_starts);
+      }
+    }
+  }
+#endif  // OS_ANDROID
+
   for (const auto& candidate : mappings_) {
     if (candidate.device == mapping.device &&
-        candidate.inode == mapping.inode &&
-        candidate.offset == 0) {
+        candidate.inode == mapping.inode
+#if !defined(OS_ANDROID)
+        // Libraries on Android may be mapped from zipfiles (APKs), in which
+        // case the offset is not 0.
+        && candidate.offset == 0
+#endif  // !defined(OS_ANDROID)
+        ) {
       possible_starts.push_back(&candidate);
     }
     if (mapping.Equals(candidate)) {
-      return possible_starts;
+      return std::make_unique<SparseReverseIterator>(possible_starts);
     }
   }
 
   LOG(ERROR) << "mapping not found";
-  return std::vector<const Mapping*>();
+  return std::make_unique<SparseReverseIterator>();
+}
+
+std::unique_ptr<MemoryMap::Iterator> MemoryMap::ReverseIteratorFrom(
+    const Mapping& target) const {
+  for (auto riter = mappings_.crbegin(); riter != mappings_.rend(); ++riter) {
+    if (riter->Equals(target)) {
+      return std::make_unique<FullReverseIterator>(riter, mappings_.rend());
+    }
+  }
+  return std::make_unique<FullReverseIterator>(mappings_.rend(),
+                                               mappings_.rend());
 }
 
 }  // namespace crashpad
diff --git a/util/linux/memory_map.h b/util/linux/memory_map.h
index af194e9..d43b7af 100644
--- a/util/linux/memory_map.h
+++ b/util/linux/memory_map.h
@@ -17,6 +17,7 @@
 
 #include <sys/types.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -42,7 +43,7 @@
 
     std::string name;
     CheckedLinuxAddressRange range;
-    off_t offset;
+    off64_t offset;
     dev_t device;
     ino_t inode;
     bool readable;
@@ -76,14 +77,37 @@
   //!     it was obtained from.
   const Mapping* FindMappingWithName(const std::string& name) const;
 
-  //! \brief Find Mappings that share a Mapping's file, mapped from offset 0.
+  //! \brief An abstract base class for iterating over ordered sets of mappings
+  //!   in a MemoryMap.
+  class Iterator {
+   public:
+    virtual ~Iterator() = default;
+
+    //! \return the mapping pointed to by the iterator and advance the iterator
+    //!     to the next mapping. If there are no more mappings, this method
+    //!     returns `nullptr` on all subsequent invocations.
+    virtual const Mapping* Next() = 0;
+
+    //! \return the number of mappings remaining.
+    virtual unsigned int Count() = 0;
+
+   protected:
+    Iterator() = default;
+  };
+
+  //! \brief Find possible initial mappings of files mapped over several
+  //!     segments.
   //!
   //! Executables and libaries are typically loaded into several mappings with
   //! varying permissions for different segments. Portions of an ELF file may
   //! be mapped multiple times as part of loading the file, for example, when
-  //! initializing GNU_RELRO segments. This method searches for mappings at or
-  //! below \a mapping in memory that are mapped from the same file as \a
-  //! mapping from offset 0.
+  //! initializing GNU_RELRO segments.
+  //!
+  //! This method searches for mappings at or below \a mapping in memory that
+  //! are mapped from the same file as \a mapping from offset 0.
+  //!
+  //! On Android, ELF modules may be loaded from within a zipfile, so this
+  //! method may return mappings whose offset is not 0.
   //!
   //! This method is intended to help identify the possible base address for
   //! loaded modules, but it is the caller's responsibility to determine which
@@ -94,10 +118,15 @@
   //! map a file, \a mapping is returned in \a possible_starts.
   //!
   //! \param[in] mapping A Mapping whose series to find the start of.
-  //! \return a vector of the possible mapping starts.
-  std::vector<const Mapping*> FindFilePossibleMmapStarts(
+  //! \return a reverse iterator over the possible mapping starts, starting from
+  //!     the mapping with highest base address.
+  std::unique_ptr<Iterator> FindFilePossibleMmapStarts(
       const Mapping& mapping) const;
 
+  //! \return A reverse iterator over all mappings in the MemoryMap from \a
+  //!     mapping to the start of the MemoryMap.
+  std::unique_ptr<Iterator> ReverseIteratorFrom(const Mapping& mapping) const;
+
  private:
   std::vector<Mapping> mappings_;
   InitializationStateDcheck initialized_;
diff --git a/util/linux/memory_map_test.cc b/util/linux/memory_map_test.cc
index 645a0b4..0ee9080 100644
--- a/util/linux/memory_map_test.cc
+++ b/util/linux/memory_map_test.cc
@@ -29,7 +29,9 @@
 #include "test/linux/fake_ptrace_connection.h"
 #include "test/multiprocess.h"
 #include "test/scoped_temp_dir.h"
+#include "third_party/lss/lss.h"
 #include "util/file/file_io.h"
+#include "util/file/scoped_remove_file.h"
 #include "util/linux/direct_ptrace_connection.h"
 #include "util/misc/clock.h"
 #include "util/misc/from_pointer_cast.h"
@@ -39,6 +41,38 @@
 namespace test {
 namespace {
 
+TEST(MemoryMap, SelfLargeFiles) {
+  // This test is meant to test the handler's ability to understand files
+  // mapped from large offsets, even if the handler wasn't built with
+  // _FILE_OFFSET_BITS=64. ScopedTempDir needs to stat files to determine
+  // whether to recurse into directories, which may will fail without large file
+  // support. ScopedRemoveFile doesn't have that restriction.
+  ScopedTempDir dir;
+  ScopedRemoveFile large_file_path(dir.path().Append("crashpad_test_file"));
+  ScopedFileHandle handle(
+      LoggingOpenFileForReadAndWrite(large_file_path.get(),
+                                     FileWriteMode::kCreateOrFail,
+                                     FilePermissions::kWorldReadable));
+  ASSERT_TRUE(handle.is_valid());
+
+  // sys_fallocate supports large files as long as the kernel supports them,
+  // regardless of _FILE_OFFSET_BITS.
+  off64_t off = 1llu + UINT32_MAX;
+  ASSERT_EQ(sys_fallocate(handle.get(), 0, off, getpagesize()), 0)
+      << ErrnoMessage("fallocate");
+
+  ScopedMmap mapping;
+  void* addr = sys_mmap(
+      nullptr, getpagesize(), PROT_READ, MAP_SHARED, handle.get(), off);
+  ASSERT_TRUE(addr);
+  ASSERT_TRUE(mapping.ResetAddrLen(addr, getpagesize()));
+
+  FakePtraceConnection connection;
+  ASSERT_TRUE(connection.Initialize(getpid()));
+  MemoryMap map;
+  ASSERT_TRUE(map.Initialize(&connection));
+}
+
 TEST(MemoryMap, SelfBasic) {
   ScopedMmap mmapping;
   ASSERT_TRUE(mmapping.ResetMmap(nullptr,
@@ -67,7 +101,10 @@
   ASSERT_TRUE(mapping);
   EXPECT_GE(code_address, mapping->range.Base());
   EXPECT_LT(code_address, mapping->range.End());
+#if !defined(OS_ANDROID)
+  // Android Q+ supports execute only memory.
   EXPECT_TRUE(mapping->readable);
+#endif
   EXPECT_FALSE(mapping->writable);
   EXPECT_TRUE(mapping->executable);
 
@@ -133,7 +170,10 @@
     ASSERT_TRUE(mapping);
     EXPECT_GE(code_address, mapping->range.Base());
     EXPECT_LT(code_address, mapping->range.End());
+#if !defined(OS_ANDROID)
+    // Android Q+ supports execute only memory.
     EXPECT_TRUE(mapping->readable);
+#endif
     EXPECT_TRUE(mapping->executable);
     EXPECT_FALSE(mapping->writable);
 
@@ -373,19 +413,21 @@
   ASSERT_NE(mapping1, mapping2);
   ASSERT_NE(mapping2, mapping3);
 
-  std::vector<const MemoryMap::Mapping*> mappings;
-
-  mappings = map.FindFilePossibleMmapStarts(*mapping1);
-  ASSERT_EQ(mappings.size(), 1u);
-  EXPECT_EQ(mappings[0], mapping1);
+  auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
+  ASSERT_EQ(mappings->Count(), 1u);
+  EXPECT_EQ(mappings->Next(), mapping1);
 
   mappings = map.FindFilePossibleMmapStarts(*mapping2);
-  ASSERT_EQ(mappings.size(), 1u);
-  EXPECT_EQ(mappings[0], mapping2);
+  ASSERT_EQ(mappings->Count(), 1u);
+  EXPECT_EQ(mappings->Next(), mapping2);
 
   mappings = map.FindFilePossibleMmapStarts(*mapping3);
-  ASSERT_EQ(mappings.size(), 1u);
-  EXPECT_EQ(mappings[0], mapping1);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(mappings->Count(), 2u);
+#else
+  ASSERT_EQ(mappings->Count(), 1u);
+  EXPECT_EQ(mappings->Next(), mapping1);
+#endif
 }
 
 TEST(MemoryMap, FindFilePossibleMmapStarts) {
@@ -428,19 +470,31 @@
     ASSERT_NE(mapping1, mapping2);
     ASSERT_NE(mapping2, mapping3);
 
-    std::vector<const MemoryMap::Mapping*> mappings;
-
-    mappings = map.FindFilePossibleMmapStarts(*mapping1);
-    ASSERT_EQ(mappings.size(), 1u);
-    EXPECT_EQ(mappings[0], mapping1);
+#if defined(OS_ANDROID)
+    auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
+    EXPECT_EQ(mappings->Count(), 1u);
+    EXPECT_EQ(mappings->Next(), mapping1);
+    EXPECT_EQ(mappings->Next(), nullptr);
 
     mappings = map.FindFilePossibleMmapStarts(*mapping2);
-    ASSERT_EQ(mappings.size(), 1u);
-    EXPECT_EQ(mappings[0], mapping1);
+    EXPECT_EQ(mappings->Count(), 2u);
 
     mappings = map.FindFilePossibleMmapStarts(*mapping3);
-    ASSERT_EQ(mappings.size(), 1u);
-    EXPECT_EQ(mappings[0], mapping1);
+    EXPECT_EQ(mappings->Count(), 3u);
+#else
+    auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
+    ASSERT_EQ(mappings->Count(), 1u);
+    EXPECT_EQ(mappings->Next(), mapping1);
+    EXPECT_EQ(mappings->Next(), nullptr);
+
+    mappings = map.FindFilePossibleMmapStarts(*mapping2);
+    ASSERT_EQ(mappings->Count(), 1u);
+    EXPECT_EQ(mappings->Next(), mapping1);
+
+    mappings = map.FindFilePossibleMmapStarts(*mapping3);
+    ASSERT_EQ(mappings->Count(), 1u);
+    EXPECT_EQ(mappings->Next(), mapping1);
+#endif
 
 #if defined(ARCH_CPU_64_BITS)
     constexpr bool is_64_bit = true;
@@ -449,7 +503,9 @@
 #endif
     MemoryMap::Mapping bad_mapping;
     bad_mapping.range.SetRange(is_64_bit, 0, 1);
-    EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u);
+    mappings = map.FindFilePossibleMmapStarts(bad_mapping);
+    EXPECT_EQ(mappings->Count(), 0u);
+    EXPECT_EQ(mappings->Next(), nullptr);
   }
 
   // Make the second page an anonymous mapping
@@ -562,27 +618,47 @@
   auto mapping = map.FindMapping(file_mapping0.addr_as<VMAddress>());
   ASSERT_TRUE(mapping);
   auto possible_starts = map.FindFilePossibleMmapStarts(*mapping);
-  EXPECT_EQ(possible_starts.size(), 0u);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(possible_starts->Count(), 1u);
+#else
+  EXPECT_EQ(possible_starts->Count(), 0u);
+#endif
 
   mapping = map.FindMapping(file_mapping1.addr_as<VMAddress>());
   ASSERT_TRUE(mapping);
   possible_starts = map.FindFilePossibleMmapStarts(*mapping);
-  EXPECT_EQ(possible_starts.size(), 1u);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(possible_starts->Count(), 2u);
+#else
+  EXPECT_EQ(possible_starts->Count(), 1u);
+#endif
 
   mapping = map.FindMapping(file_mapping2.addr_as<VMAddress>());
   ASSERT_TRUE(mapping);
   possible_starts = map.FindFilePossibleMmapStarts(*mapping);
-  EXPECT_EQ(possible_starts.size(), 2u);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(possible_starts->Count(), 3u);
+#else
+  EXPECT_EQ(possible_starts->Count(), 2u);
+#endif
 
   mapping = map.FindMapping(file_mapping3.addr_as<VMAddress>());
   ASSERT_TRUE(mapping);
   possible_starts = map.FindFilePossibleMmapStarts(*mapping);
-  EXPECT_EQ(possible_starts.size(), 3u);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(possible_starts->Count(), 4u);
+#else
+  EXPECT_EQ(possible_starts->Count(), 3u);
+#endif
 
   mapping = map.FindMapping(file_mapping4.addr_as<VMAddress>());
   ASSERT_TRUE(mapping);
   possible_starts = map.FindFilePossibleMmapStarts(*mapping);
-  EXPECT_EQ(possible_starts.size(), 4u);
+#if defined(OS_ANDROID)
+  EXPECT_EQ(possible_starts->Count(), 5u);
+#else
+  EXPECT_EQ(possible_starts->Count(), 4u);
+#endif
 }
 
 }  // namespace
diff --git a/util/linux/proc_stat_reader.cc b/util/linux/proc_stat_reader.cc
index b1dfe94..dd663d6 100644
--- a/util/linux/proc_stat_reader.cc
+++ b/util/linux/proc_stat_reader.cc
@@ -20,6 +20,7 @@
 
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "util/file/file_io.h"
 #include "util/misc/lexing.h"
 #include "util/misc/time.h"
@@ -43,9 +44,12 @@
 
 ProcStatReader::~ProcStatReader() {}
 
-bool ProcStatReader::Initialize(pid_t tid) {
+bool ProcStatReader::Initialize(PtraceConnection* connection, pid_t tid) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-  if (!ReadFile(tid)) {
+
+  char path[32];
+  snprintf(path, base::size(path), "/proc/%d/stat", tid);
+  if (!connection->ReadFileContents(base::FilePath(path), &contents_)) {
     return false;
   }
 
@@ -81,7 +85,8 @@
   return ReadTimeAtIndex(14, system_time);
 }
 
-bool ProcStatReader::StartTime(timeval* start_time) const {
+bool ProcStatReader::StartTime(const timeval& boot_time,
+                               timeval* start_time) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
   timeval time_after_boot;
@@ -89,33 +94,7 @@
     return false;
   }
 
-  timespec uptime;
-  if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) {
-    PLOG(ERROR) << "clock_gettime";
-    return false;
-  }
-
-  timespec current_time;
-  if (clock_gettime(CLOCK_REALTIME, &current_time) != 0) {
-    PLOG(ERROR) << "clock_gettime";
-    return false;
-  }
-
-  timespec boot_time_ts;
-  SubtractTimespec(current_time, uptime, &boot_time_ts);
-  timeval boot_time_tv;
-  TimespecToTimeval(boot_time_ts, &boot_time_tv);
-  timeradd(&boot_time_tv, &time_after_boot, start_time);
-
-  return true;
-}
-
-bool ProcStatReader::ReadFile(pid_t tid) {
-  char path[32];
-  snprintf(path, arraysize(path), "/proc/%d/stat", tid);
-  if (!LoggingReadEntireFile(base::FilePath(path), &contents_)) {
-    return false;
-  }
+  timeradd(&boot_time, &time_after_boot, start_time);
   return true;
 }
 
diff --git a/util/linux/proc_stat_reader.h b/util/linux/proc_stat_reader.h
index 4cad97c..6eae8fb 100644
--- a/util/linux/proc_stat_reader.h
+++ b/util/linux/proc_stat_reader.h
@@ -22,6 +22,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "util/linux/ptrace_connection.h"
 #include "util/misc/initialization_state_dcheck.h"
 
 namespace crashpad {
@@ -36,8 +37,10 @@
   //!
   //! This method must be successfully called before calling any other.
   //!
+  //! \param[in] connection A connection to the process to which the target
+  //!     thread belongs.
   //! \param[in] tid The thread ID to read the stat file for.
-  bool Initialize(pid_t tid);
+  bool Initialize(PtraceConnection* connection, pid_t tid);
 
   //! \brief Determines the time the thread has spent executing in user mode.
   //!
@@ -57,14 +60,14 @@
 
   //! \brief Determines the target thread’s start time.
   //!
+  //! \param[in] boot_time The kernel boot time.
   //! \param[out] start_time The time that the thread started.
   //!
   //! \return `true` on success, with \a start_time set. Otherwise, `false` with
   //!     a message logged.
-  bool StartTime(timeval* start_time) const;
+  bool StartTime(const timeval& boot_time, timeval* start_time) const;
 
  private:
-  bool ReadFile(pid_t tid);
   bool FindColumn(int index, const char** column) const;
   bool ReadTimeAtIndex(int index, timeval* time_val) const;
 
diff --git a/util/linux/proc_stat_reader_test.cc b/util/linux/proc_stat_reader_test.cc
index 8189c2e..01b9afd 100644
--- a/util/linux/proc_stat_reader_test.cc
+++ b/util/linux/proc_stat_reader_test.cc
@@ -20,6 +20,8 @@
 
 #include "base/logging.h"
 #include "gtest/gtest.h"
+#include "test/linux/fake_ptrace_connection.h"
+#include "util/misc/time.h"
 #include "util/thread/thread.h"
 
 namespace crashpad {
@@ -27,11 +29,19 @@
 namespace {
 
 TEST(ProcStatReader, Basic) {
+  FakePtraceConnection connection;
+  ASSERT_TRUE(connection.Initialize(getpid()));
+
   ProcStatReader stat;
-  ASSERT_TRUE(stat.Initialize(getpid()));
+  ASSERT_TRUE(stat.Initialize(&connection, getpid()));
+
+  timespec boot_time_ts;
+  ASSERT_TRUE(GetBootTime(&boot_time_ts));
+  timeval boot_time;
+  TimespecToTimeval(boot_time_ts, &boot_time);
 
   timeval start_time;
-  ASSERT_TRUE(stat.StartTime(&start_time));
+  ASSERT_TRUE(stat.StartTime(boot_time, &start_time));
 
   time_t now;
   time(&now);
@@ -52,27 +62,38 @@
   return syscall(SYS_gettid);
 }
 
-void GetStartTime(timeval* start_time) {
+void GetStartTime(const timeval& boot_time, timeval* start_time) {
+  FakePtraceConnection connection;
+  ASSERT_TRUE(connection.Initialize(getpid()));
+
   ProcStatReader stat;
-  ASSERT_TRUE(stat.Initialize(gettid()));
-  ASSERT_TRUE(stat.StartTime(start_time));
+  ASSERT_TRUE(stat.Initialize(&connection, gettid()));
+  ASSERT_TRUE(stat.StartTime(boot_time, start_time));
 }
 
 class StatTimeThread : public Thread {
  public:
-  StatTimeThread(timeval* start_time) : start_time_(start_time) {}
+  StatTimeThread(const timeval& boot_time, timeval* start_time)
+      : boot_time_(boot_time), start_time_(start_time) {}
 
  private:
-  void ThreadMain() override { GetStartTime(start_time_); }
+  void ThreadMain() override { GetStartTime(boot_time_, start_time_); }
+
+  const timeval& boot_time_;
   timeval* start_time_;
 };
 
 TEST(ProcStatReader, Threads) {
+  timespec boot_time_ts;
+  ASSERT_TRUE(GetBootTime(&boot_time_ts));
+  timeval boot_time;
+  TimespecToTimeval(boot_time_ts, &boot_time);
+
   timeval main_time;
-  ASSERT_NO_FATAL_FAILURE(GetStartTime(&main_time));
+  ASSERT_NO_FATAL_FAILURE(GetStartTime(boot_time, &main_time));
 
   timeval thread_time;
-  StatTimeThread thread(&thread_time);
+  StatTimeThread thread(boot_time, &thread_time);
   thread.Start();
   ASSERT_NO_FATAL_FAILURE(thread.Join());
 
@@ -82,7 +103,7 @@
          time_t thread_sec,
          suseconds_t thread_usec) {
         return (thread_sec > main_sec) ||
-               (thread_sec == main_sec && thread_usec > main_usec);
+               (thread_sec == main_sec && thread_usec >= main_usec);
       },
       main_time.tv_sec,
       main_time.tv_usec,
diff --git a/util/linux/proc_task_reader.cc b/util/linux/proc_task_reader.cc
new file mode 100644
index 0000000..360f83a
--- /dev/null
+++ b/util/linux/proc_task_reader.cc
@@ -0,0 +1,59 @@
+// Copyright 2019 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.
+
+#include "util/linux/proc_task_reader.h"
+
+#include <stdio.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "util/file/directory_reader.h"
+#include "util/misc/as_underlying_type.h"
+
+namespace crashpad {
+
+bool ReadThreadIDs(pid_t pid, std::vector<pid_t>* tids) {
+  DCHECK(tids->empty());
+
+  char path[32];
+  snprintf(path, base::size(path), "/proc/%d/task", pid);
+  DirectoryReader reader;
+  if (!reader.Open(base::FilePath(path))) {
+    return false;
+  }
+
+  std::vector<pid_t> local_tids;
+  base::FilePath tid_str;
+  DirectoryReader::Result result;
+  while ((result = reader.NextFile(&tid_str)) ==
+         DirectoryReader::Result::kSuccess) {
+    pid_t tid;
+    if (!base::StringToInt(tid_str.value(), &tid)) {
+      LOG(ERROR) << "format error";
+      continue;
+    }
+
+    local_tids.push_back(tid);
+  }
+  DCHECK_EQ(AsUnderlyingType(result),
+            AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles));
+  DCHECK(!local_tids.empty());
+
+  tids->swap(local_tids);
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/linux/proc_task_reader.h b/util/linux/proc_task_reader.h
new file mode 100644
index 0000000..9b95f12
--- /dev/null
+++ b/util/linux/proc_task_reader.h
@@ -0,0 +1,35 @@
+// Copyright 2019 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_UTIL_LINUX_PROC_TASK_READER_H_
+#define CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_
+
+#include <sys/types.h>
+
+#include <vector>
+
+namespace crashpad {
+
+//! \brief Enumerates the thread IDs of a process by reading
+//!     <code>/proc/<i>pid</i>/task</code>.
+//!
+//! \param[in] pid The process ID for which to read thread IDs.
+//! \param[out] tids The read thread IDs.
+//! \return `true` if the task directory was successfully read. Format errors
+//!     are logged, but won't cause this function to return `false`.
+bool ReadThreadIDs(pid_t pid, std::vector<pid_t>* tids);
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_
diff --git a/util/linux/proc_task_reader_test.cc b/util/linux/proc_task_reader_test.cc
new file mode 100644
index 0000000..911f6d3
--- /dev/null
+++ b/util/linux/proc_task_reader_test.cc
@@ -0,0 +1,162 @@
+// Copyright 2019 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.
+
+#include "util/linux/proc_task_reader.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "test/multiprocess_exec.h"
+#include "third_party/lss/lss.h"
+#include "util/synchronization/semaphore.h"
+#include "util/thread/thread.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+bool FindThreadID(pid_t tid, const std::vector<pid_t>& threads) {
+  for (const auto& thread : threads) {
+    if (thread == tid) {
+      return true;
+    }
+  }
+  return false;
+}
+
+class ScopedBlockingThread : public Thread {
+ public:
+  ScopedBlockingThread() : tid_sem_(0), join_sem_(0), tid_(-1) {}
+
+  ~ScopedBlockingThread() {
+    join_sem_.Signal();
+    Join();
+  }
+
+  pid_t ThreadID() {
+    tid_sem_.Wait();
+    return tid_;
+  }
+
+ private:
+  void ThreadMain() override {
+    tid_ = sys_gettid();
+    tid_sem_.Signal();
+    join_sem_.Wait();
+  }
+
+  Semaphore tid_sem_;
+  Semaphore join_sem_;
+  pid_t tid_;
+};
+
+TEST(ProcTaskReader, Self) {
+  std::vector<pid_t> tids;
+  ASSERT_TRUE(ReadThreadIDs(getpid(), &tids));
+  EXPECT_TRUE(FindThreadID(getpid(), tids));
+  EXPECT_TRUE(FindThreadID(sys_gettid(), tids));
+
+  ScopedBlockingThread thread1;
+  thread1.Start();
+
+  ScopedBlockingThread thread2;
+  thread2.Start();
+
+  pid_t thread1_tid = thread1.ThreadID();
+  pid_t thread2_tid = thread2.ThreadID();
+
+  tids.clear();
+  ASSERT_TRUE(ReadThreadIDs(getpid(), &tids));
+  EXPECT_TRUE(FindThreadID(getpid(), tids));
+  EXPECT_TRUE(FindThreadID(thread1_tid, tids));
+  EXPECT_TRUE(FindThreadID(thread2_tid, tids));
+}
+
+TEST(ProcTaskReader, BadPID) {
+  std::vector<pid_t> tids;
+  EXPECT_FALSE(ReadThreadIDs(-1, &tids));
+
+  tids.clear();
+  EXPECT_FALSE(ReadThreadIDs(0, &tids));
+}
+
+CRASHPAD_CHILD_TEST_MAIN(ProcTaskTestChild) {
+  FileHandle in = StdioFileHandle(StdioStream::kStandardInput);
+  FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
+
+  pid_t tid = getpid();
+  CheckedWriteFile(out, &tid, sizeof(tid));
+
+  tid = sys_gettid();
+  CheckedWriteFile(out, &tid, sizeof(tid));
+
+  ScopedBlockingThread thread1;
+  thread1.Start();
+
+  ScopedBlockingThread thread2;
+  thread2.Start();
+
+  tid = thread1.ThreadID();
+  CheckedWriteFile(out, &tid, sizeof(tid));
+
+  tid = thread2.ThreadID();
+  CheckedWriteFile(out, &tid, sizeof(tid));
+
+  CheckedReadFileAtEOF(in);
+  return 0;
+}
+
+class ProcTaskTest : public MultiprocessExec {
+ public:
+  ProcTaskTest() : MultiprocessExec() {
+    SetChildTestMainFunction("ProcTaskTestChild");
+  }
+
+ private:
+  bool ReadIDFromChild(std::vector<pid_t>* threads) {
+    pid_t tid;
+    if (!LoggingReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid))) {
+      return false;
+    }
+    threads->push_back(tid);
+    return true;
+  }
+
+  void MultiprocessParent() override {
+    std::vector<pid_t> ids_to_find;
+    for (size_t id_count = 0; id_count < 4; ++id_count) {
+      ASSERT_TRUE(ReadIDFromChild(&ids_to_find));
+    }
+
+    std::vector<pid_t> threads;
+    ASSERT_TRUE(ReadThreadIDs(ChildPID(), &threads));
+    for (size_t index = 0; index < ids_to_find.size(); ++index) {
+      SCOPED_TRACE(
+          base::StringPrintf("index %zd, tid %d", index, ids_to_find[index]));
+      EXPECT_TRUE(FindThreadID(ids_to_find[index], threads));
+    }
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ProcTaskTest);
+};
+
+TEST(ProcTaskReader, ReadChild) {
+  ProcTaskTest test;
+  test.Run();
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/linux/ptrace_broker.cc b/util/linux/ptrace_broker.cc
index e9d3068..155a1e0 100644
--- a/util/linux/ptrace_broker.cc
+++ b/util/linux/ptrace_broker.cc
@@ -17,12 +17,14 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <string.h>
+#include <syscall.h>
 #include <unistd.h>
 
 #include <algorithm>
 
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
+#include "util/misc/memory_sanitizer.h"
 
 namespace crashpad {
 
@@ -138,9 +140,10 @@
           attach_on_stack = true;
         }
 
-        Bool status = kBoolFalse;
+        ExceptionHandlerProtocol::Bool status =
+            ExceptionHandlerProtocol::kBoolFalse;
         if (attach->ResetAttach(request.tid)) {
-          status = kBoolTrue;
+          status = ExceptionHandlerProtocol::kBoolTrue;
           if (!attach_on_stack) {
             ++attach_count_;
           }
@@ -150,21 +153,23 @@
           return errno;
         }
 
-        if (status == kBoolFalse) {
-          Errno error = errno;
+        if (status == ExceptionHandlerProtocol::kBoolFalse) {
+          ExceptionHandlerProtocol::Errno error = errno;
           if (!WriteFile(sock_, &error, sizeof(error))) {
             return errno;
           }
         }
 
-        if (attach_on_stack && status == kBoolTrue) {
+        if (attach_on_stack && status == ExceptionHandlerProtocol::kBoolTrue) {
           return RunImpl();
         }
         continue;
       }
 
       case Request::kTypeIs64Bit: {
-        Bool is_64_bit = ptracer_.Is64Bit() ? kBoolTrue : kBoolFalse;
+        ExceptionHandlerProtocol::Bool is_64_bit =
+            ptracer_.Is64Bit() ? ExceptionHandlerProtocol::kBoolTrue
+                               : ExceptionHandlerProtocol::kBoolFalse;
         if (!WriteFile(sock_, &is_64_bit, sizeof(is_64_bit))) {
           return errno;
         }
@@ -174,15 +179,15 @@
       case Request::kTypeGetThreadInfo: {
         GetThreadInfoResponse response;
         response.success = ptracer_.GetThreadInfo(request.tid, &response.info)
-                               ? kBoolTrue
-                               : kBoolFalse;
+                               ? ExceptionHandlerProtocol::kBoolTrue
+                               : ExceptionHandlerProtocol::kBoolFalse;
 
         if (!WriteFile(sock_, &response, sizeof(response))) {
           return errno;
         }
 
-        if (response.success == kBoolFalse) {
-          Errno error = errno;
+        if (response.success == ExceptionHandlerProtocol::kBoolFalse) {
+          ExceptionHandlerProtocol::Errno error = errno;
           if (!WriteFile(sock_, &error, sizeof(error))) {
             return errno;
           }
@@ -192,7 +197,9 @@
 
       case Request::kTypeReadFile: {
         ScopedFileHandle handle;
-        int result = ReceiveAndOpenFilePath(request.path.path_length, &handle);
+        int result = ReceiveAndOpenFilePath(request.path.path_length,
+                                            /* is_directory= */ false,
+                                            &handle);
         if (result != 0) {
           return result;
         }
@@ -217,6 +224,26 @@
         continue;
       }
 
+      case Request::kTypeListDirectory: {
+        ScopedFileHandle handle;
+        int result = ReceiveAndOpenFilePath(request.path.path_length,
+                                            /* is_directory= */ true,
+                                            &handle);
+        if (result != 0) {
+          return result;
+        }
+
+        if (!handle.is_valid()) {
+          continue;
+        }
+
+        result = SendDirectory(handle.get());
+        if (result != 0) {
+          return result;
+        }
+        continue;
+      }
+
       case Request::kTypeExit:
         return 0;
     }
@@ -226,7 +253,7 @@
   }
 }
 
-int PtraceBroker::SendError(Errno err) {
+int PtraceBroker::SendError(ExceptionHandlerProtocol::Errno err) {
   return WriteFile(sock_, &err, sizeof(err)) ? 0 : errno;
 }
 
@@ -329,7 +356,36 @@
   return 0;
 }
 
+#if defined(MEMORY_SANITIZER)
+// MSan doesn't intercept syscall() and doesn't see that buffer is initialized.
+__attribute__((no_sanitize("memory")))
+#endif  // defined(MEMORY_SANITIZER)
+int PtraceBroker::SendDirectory(FileHandle handle) {
+  char buffer[4096];
+  int rv;
+  do {
+    rv = syscall(SYS_getdents64, handle, buffer, sizeof(buffer));
+
+    if (rv < 0) {
+      return SendReadError(static_cast<ReadError>(errno));
+    }
+
+    if (!WriteFile(sock_, &rv, sizeof(rv))) {
+      return errno;
+    }
+
+    if (rv > 0) {
+      if (!WriteFile(sock_, buffer, static_cast<size_t>(rv))) {
+        return errno;
+      }
+    }
+  } while (rv > 0);
+
+  return 0;
+}
+
 int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length,
+                                         bool is_directory,
                                          ScopedFileHandle* handle) {
   char path[std::max(4096, PATH_MAX)];
 
@@ -346,8 +402,11 @@
     return SendOpenResult(kOpenResultAccessDenied);
   }
 
-  ScopedFileHandle local_handle(
-      HANDLE_EINTR(open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY)));
+  int flags = O_RDONLY | O_CLOEXEC | O_NOCTTY;
+  if (is_directory) {
+    flags |= O_DIRECTORY;
+  }
+  ScopedFileHandle local_handle(HANDLE_EINTR(open(path, flags)));
   if (!local_handle.is_valid()) {
     return SendOpenResult(static_cast<OpenResult>(errno));
   }
diff --git a/util/linux/ptrace_broker.h b/util/linux/ptrace_broker.h
index 2e5decd..6a7bfb7 100644
--- a/util/linux/ptrace_broker.h
+++ b/util/linux/ptrace_broker.h
@@ -75,6 +75,16 @@
       //!     errors, followed by an Errno. On success, the bytes read follow.
       kTypeReadFile,
 
+      //! \brief Reads the contents of a directory. The data is returned in a
+      //!     series of messages. The first message is an OpenResult, indicating
+      //!     the validity of the received file path. If the OpenResult is
+      //!     kOpenResultSuccess, the subsequent messages return the contents of
+      //!     the directory as a dirent stream, as read by `getdents64()`. Each
+      //!     subsequent message begins with an int32_t indicating the number of
+      //!     bytes read, 0 for end-of-file, or -1 for errors, followed by an
+      //!     Errno. On success, the bytes read follow.
+      kTypeListDirectory,
+
       //! \brief Causes the broker to return from Run(), detaching all attached
       //!     threads. Does not respond.
       kTypeExit
@@ -136,7 +146,7 @@
     ThreadInfo info;
 
     //! \brief Specifies the success or failure of this call.
-    Bool success;
+    ExceptionHandlerProtocol::Bool success;
   };
 #pragma pack(pop)
 
@@ -186,13 +196,16 @@
   bool AllocateAttachments();
   void ReleaseAttachments();
   int RunImpl();
-  int SendError(Errno err);
+  int SendError(ExceptionHandlerProtocol::Errno err);
   int SendReadError(ReadError err);
   int SendOpenResult(OpenResult result);
   int SendFileContents(FileHandle handle);
+  int SendDirectory(FileHandle handle);
   void TryOpeningMemFile();
   int SendMemory(pid_t pid, VMAddress address, VMSize size);
-  int ReceiveAndOpenFilePath(VMSize path_length, ScopedFileHandle* handle);
+  int ReceiveAndOpenFilePath(VMSize path_length,
+                             bool is_directory,
+                             ScopedFileHandle* handle);
 
   char file_root_buffer_[32];
   Ptracer ptracer_;
diff --git a/util/linux/ptrace_broker_test.cc b/util/linux/ptrace_broker_test.cc
index 9ed1973..341f2c8 100644
--- a/util/linux/ptrace_broker_test.cc
+++ b/util/linux/ptrace_broker_test.cc
@@ -155,6 +155,17 @@
         client_sock.get(), ChildPID(), /* try_direct_memory= */ false));
 
     EXPECT_EQ(client.GetProcessID(), ChildPID());
+
+    std::vector<pid_t> threads;
+    ASSERT_TRUE(client.Threads(&threads));
+    EXPECT_EQ(threads.size(), 2u);
+    if (threads[0] == ChildPID()) {
+      EXPECT_EQ(threads[1], child2_tid);
+    } else {
+      EXPECT_EQ(threads[0], child2_tid);
+      EXPECT_EQ(threads[1], ChildPID());
+    }
+
     EXPECT_TRUE(client.Attach(child2_tid));
     EXPECT_EQ(client.Is64Bit(), am_64_bit);
 
diff --git a/util/linux/ptrace_client.cc b/util/linux/ptrace_client.cc
index b5cceba..f097ad9 100644
--- a/util/linux/ptrace_client.cc
+++ b/util/linux/ptrace_client.cc
@@ -20,6 +20,8 @@
 #include <string>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "util/file/file_io.h"
 #include "util/linux/ptrace_broker.h"
 #include "util/process/process_memory_linux.h"
@@ -29,7 +31,7 @@
 namespace {
 
 bool ReceiveAndLogError(int sock, const std::string& operation) {
-  Errno error;
+  ExceptionHandlerProtocol::Errno error;
   if (!LoggingReadFileExactly(sock, &error, sizeof(error))) {
     return false;
   }
@@ -60,19 +62,19 @@
 }
 
 bool AttachImpl(int sock, pid_t tid) {
-  PtraceBroker::Request request;
+  PtraceBroker::Request request = {};
   request.type = PtraceBroker::Request::kTypeAttach;
   request.tid = tid;
   if (!LoggingWriteFile(sock, &request, sizeof(request))) {
     return false;
   }
 
-  Bool success;
+  ExceptionHandlerProtocol::Bool success;
   if (!LoggingReadFileExactly(sock, &success, sizeof(success))) {
     return false;
   }
 
-  if (success != kBoolTrue) {
+  if (success != ExceptionHandlerProtocol::kBoolTrue) {
     ReceiveAndLogError(sock, "PtraceBroker Attach");
     return false;
   }
@@ -80,6 +82,48 @@
   return true;
 }
 
+struct Dirent64 {
+  ino64_t d_ino;
+  off64_t d_off;
+  unsigned short d_reclen;
+  unsigned char d_type;
+  char d_name[];
+};
+
+void ReadDentsAsThreadIDs(char* buffer,
+                          size_t size,
+                          std::vector<pid_t>* threads) {
+  while (size > offsetof(Dirent64, d_name)) {
+    auto dirent = reinterpret_cast<Dirent64*>(buffer);
+    if (size < dirent->d_reclen) {
+      LOG(ERROR) << "short dirent";
+      break;
+    }
+    buffer += dirent->d_reclen;
+    size -= dirent->d_reclen;
+
+    const size_t max_name_length =
+        dirent->d_reclen - offsetof(Dirent64, d_name);
+    size_t name_len = strnlen(dirent->d_name, max_name_length);
+    if (name_len >= max_name_length) {
+      LOG(ERROR) << "format error";
+      break;
+    }
+
+    if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) {
+      continue;
+    }
+
+    pid_t tid;
+    if (!base::StringToInt(dirent->d_name, &tid)) {
+      LOG(ERROR) << "format error";
+      continue;
+    }
+    threads->push_back(tid);
+  }
+  DCHECK_EQ(size, 0u);
+}
+
 }  // namespace
 
 PtraceClient::PtraceClient()
@@ -92,7 +136,7 @@
 
 PtraceClient::~PtraceClient() {
   if (sock_ != kInvalidFileHandle) {
-    PtraceBroker::Request request;
+    PtraceBroker::Request request = {};
     request.type = PtraceBroker::Request::kTypeExit;
     LoggingWriteFile(sock_, &request, sizeof(request));
   }
@@ -107,7 +151,7 @@
     return false;
   }
 
-  PtraceBroker::Request request;
+  PtraceBroker::Request request = {};
   request.type = PtraceBroker::Request::kTypeIs64Bit;
   request.tid = pid_;
 
@@ -115,11 +159,11 @@
     return false;
   }
 
-  Bool is_64_bit;
+  ExceptionHandlerProtocol::Bool is_64_bit;
   if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) {
     return false;
   }
-  is_64_bit_ = is_64_bit == kBoolTrue;
+  is_64_bit_ = is_64_bit == ExceptionHandlerProtocol::kBoolTrue;
 
   if (try_direct_memory) {
     auto direct_mem = std::make_unique<ProcessMemoryLinux>();
@@ -153,7 +197,7 @@
 bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
-  PtraceBroker::Request request;
+  PtraceBroker::Request request = {};
   request.type = PtraceBroker::Request::kTypeGetThreadInfo;
   request.tid = tid;
   if (!LoggingWriteFile(sock_, &request, sizeof(request))) {
@@ -165,7 +209,7 @@
     return false;
   }
 
-  if (response.success == kBoolTrue) {
+  if (response.success == ExceptionHandlerProtocol::kBoolTrue) {
     *info = response.info;
     return true;
   }
@@ -178,7 +222,7 @@
                                     std::string* contents) {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
 
-  PtraceBroker::Request request;
+  PtraceBroker::Request request = {};
   request.type = PtraceBroker::Request::kTypeReadFile;
   request.path.path_length = path.value().size();
 
@@ -218,6 +262,51 @@
   return memory_.get();
 }
 
+bool PtraceClient::Threads(std::vector<pid_t>* threads) {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK(threads->empty());
+
+  // If the broker is unable to read thread IDs, fall-back to just the main
+  // thread's ID.
+  threads->push_back(pid_);
+
+  char path[32];
+  snprintf(path, base::size(path), "/proc/%d/task", pid_);
+
+  PtraceBroker::Request request = {};
+  request.type = PtraceBroker::Request::kTypeListDirectory;
+  request.path.path_length = strlen(path);
+
+  if (!LoggingWriteFile(sock_, &request, sizeof(request)) ||
+      !SendFilePath(path, request.path.path_length)) {
+    return false;
+  }
+
+  std::vector<pid_t> local_threads;
+  int32_t read_result;
+  do {
+    if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) {
+      return false;
+    }
+
+    if (read_result < 0) {
+      return ReceiveAndLogReadError(sock_, "Threads");
+    }
+
+    if (read_result > 0) {
+      auto buffer = std::make_unique<char[]>(read_result);
+      if (!LoggingReadFileExactly(sock_, buffer.get(), read_result)) {
+        return false;
+      }
+
+      ReadDentsAsThreadIDs(buffer.get(), read_result, &local_threads);
+    }
+  } while (read_result > 0);
+
+  threads->swap(local_threads);
+  return true;
+}
+
 PtraceClient::BrokeredMemory::BrokeredMemory(PtraceClient* client)
     : ProcessMemory(), client_(client) {}
 
@@ -235,7 +324,7 @@
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   char* buffer_c = reinterpret_cast<char*>(buffer);
 
-  PtraceBroker::Request request;
+  PtraceBroker::Request request = {};
   request.type = PtraceBroker::Request::kTypeReadMemory;
   request.tid = pid_;
   request.iov.base = address;
diff --git a/util/linux/ptrace_client.h b/util/linux/ptrace_client.h
index a7abc0c..b4f27ae 100644
--- a/util/linux/ptrace_client.h
+++ b/util/linux/ptrace_client.h
@@ -61,6 +61,7 @@
   bool ReadFileContents(const base::FilePath& path,
                         std::string* contents) override;
   ProcessMemory* Memory() override;
+  bool Threads(std::vector<pid_t>* threads) override;
 
  private:
   class BrokeredMemory : public ProcessMemory {
diff --git a/util/linux/ptrace_connection.h b/util/linux/ptrace_connection.h
index 86dc2dd..2111747 100644
--- a/util/linux/ptrace_connection.h
+++ b/util/linux/ptrace_connection.h
@@ -18,6 +18,7 @@
 #include <sys/types.h>
 
 #include <string>
+#include <vector>
 
 #include "base/files/file_path.h"
 #include "util/linux/thread_info.h"
@@ -64,6 +65,14 @@
   //! The caller does not take ownership of the reader. The reader is valid for
   //! the lifetime of the PtraceConnection that created it.
   virtual ProcessMemory* Memory() = 0;
+
+  //! \brief Determines the thread IDs of the threads in the connected process.
+  //!
+  //! \param[out] threads The list of thread IDs.
+  //! \return `true` on success, `false` on failure with a message logged. If
+  //!     this method returns `false`, \a threads may contain a partial list of
+  //!     thread IDs.
+  virtual bool Threads(std::vector<pid_t>* threads) = 0;
 };
 
 }  // namespace crashpad
diff --git a/util/linux/ptracer.cc b/util/linux/ptracer.cc
index c6c9229..557e0d3 100644
--- a/util/linux/ptracer.cc
+++ b/util/linux/ptracer.cc
@@ -44,7 +44,8 @@
     return false;
   }
   if (iov.iov_len != sizeof(*dest)) {
-    LOG_IF(ERROR, can_log) << "Unexpected registers size";
+    LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
+                           << " != " << sizeof(*dest);
     return false;
   }
   return true;
@@ -176,7 +177,8 @@
     }
   } else {
     if (iov.iov_len != sizeof(context->f32.fpregs)) {
-      LOG_IF(ERROR, can_log) << "Unexpected registers size";
+      LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
+                             << " != " << sizeof(context->f32.fpregs);
       return false;
     }
     context->f32.have_fpregs = true;
@@ -197,7 +199,8 @@
     }
   } else {
     if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) {
-      LOG_IF(ERROR, can_log) << "Unexpected registers size";
+      LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
+                             << " != " << sizeof(context->f32.vfp);
       return false;
     }
     context->f32.have_vfp = true;
@@ -223,7 +226,8 @@
     return false;
   }
   if (iov.iov_len != sizeof(context->f64)) {
-    LOG_IF(ERROR, can_log) << "Unexpected registers size";
+    LOG_IF(ERROR, can_log) << "Unexpected registers size " << iov.iov_len
+                           << " != " << sizeof(context->f64);
     return false;
   }
   return true;
@@ -425,9 +429,10 @@
 bool GetGeneralPurposeRegisters32(pid_t tid,
                                   ThreadContext* context,
                                   bool can_log) {
-  if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) !=
-      sizeof(context->t32)) {
-    LOG_IF(ERROR, can_log) << "Unexpected registers size";
+  size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log);
+  if (length != sizeof(context->t32)) {
+    LOG_IF(ERROR, can_log) << "Unexpected registers size " << length
+                           << " != " << sizeof(context->t32);
     return false;
   }
   return true;
@@ -436,9 +441,10 @@
 bool GetGeneralPurposeRegisters64(pid_t tid,
                                   ThreadContext* context,
                                   bool can_log) {
-  if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) !=
-      sizeof(context->t64)) {
-    LOG_IF(ERROR, can_log) << "Unexpected registers size";
+  size_t length = GetGeneralPurposeRegistersAndLength(tid, context, can_log);
+  if (length != sizeof(context->t64)) {
+    LOG_IF(ERROR, can_log) << "Unexpected registers size " << length
+                           << " != " << sizeof(context->t64);
     return false;
   }
   return true;
@@ -467,7 +473,9 @@
   } else if (length == sizeof(context.t32)) {
     is_64_bit_ = false;
   } else {
-    LOG_IF(ERROR, can_log_) << "Unexpected registers size";
+    LOG_IF(ERROR, can_log_)
+        << "Unexpected registers size " << length
+        << " != " << sizeof(context.t64) << ", " << sizeof(context.t32);
     return false;
   }
 
diff --git a/util/linux/scoped_pr_set_dumpable.cc b/util/linux/scoped_pr_set_dumpable.cc
new file mode 100644
index 0000000..cbec009
--- /dev/null
+++ b/util/linux/scoped_pr_set_dumpable.cc
@@ -0,0 +1,41 @@
+// 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.
+
+#include "util/linux/scoped_pr_set_dumpable.h"
+
+#include <sys/prctl.h>
+
+#include "base/logging.h"
+
+namespace crashpad {
+
+ScopedPrSetDumpable::ScopedPrSetDumpable(bool may_log) : may_log_(may_log) {
+  int result = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0);
+  PLOG_IF(ERROR, result < 0 && may_log_) << "prctl";
+  was_dumpable_ = result > 0;
+
+  if (!was_dumpable_) {
+    result = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+    PLOG_IF(ERROR, result != 0 && may_log_) << "prctl";
+  }
+}
+
+ScopedPrSetDumpable::~ScopedPrSetDumpable() {
+  if (!was_dumpable_) {
+    int result = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
+    PLOG_IF(ERROR, result != 0 && may_log_) << "prctl";
+  }
+}
+
+}  // namespace crashpad
diff --git a/util/linux/scoped_pr_set_dumpable.h b/util/linux/scoped_pr_set_dumpable.h
new file mode 100644
index 0000000..1681930
--- /dev/null
+++ b/util/linux/scoped_pr_set_dumpable.h
@@ -0,0 +1,44 @@
+// 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_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_
+#define CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_
+
+#include "base/macros.h"
+
+namespace crashpad {
+
+class ScopedPrSetDumpable {
+ public:
+  //! \brief Uses `PR_SET_DUMPABLE` to make the current process dumpable.
+  //!
+  //! Restores the dumpable flag to its original value on destruction. If the
+  //! original value couldn't be determined, the destructor attempts to restore
+  //! the flag to 0 (non-dumpable).
+  //!
+  //! \param[in] may_log `true` if this object may log error messages.
+  explicit ScopedPrSetDumpable(bool may_log);
+
+  ~ScopedPrSetDumpable();
+
+ private:
+  bool was_dumpable_;
+  bool may_log_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedPrSetDumpable);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_DUMPABLE_H_
diff --git a/util/linux/socket.cc b/util/linux/socket.cc
new file mode 100644
index 0000000..f56eacf
--- /dev/null
+++ b/util/linux/socket.cc
@@ -0,0 +1,192 @@
+// Copyright 2019 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.
+
+#include "util/linux/socket.h"
+
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "third_party/lss/lss.h"
+
+namespace crashpad {
+
+// static
+bool UnixCredentialSocket::CreateCredentialSocketpair(ScopedFileHandle* sock1,
+                                                      ScopedFileHandle* sock2) {
+  int socks[2];
+  if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socks) != 0) {
+    PLOG(ERROR) << "socketpair";
+    return false;
+  }
+  ScopedFileHandle local_sock1(socks[0]);
+  ScopedFileHandle local_sock2(socks[1]);
+
+  int optval = 1;
+  socklen_t optlen = sizeof(optval);
+  if (setsockopt(local_sock1.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
+          0 ||
+      setsockopt(local_sock2.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
+          0) {
+    PLOG(ERROR) << "setsockopt";
+    return false;
+  }
+
+  sock1->swap(local_sock1);
+  sock2->swap(local_sock2);
+  return true;
+}
+
+constexpr size_t UnixCredentialSocket::kMaxSendRecvMsgFDs = 4;
+
+// static
+int UnixCredentialSocket::SendMsg(int fd,
+                                  const void* buf,
+                                  size_t buf_size,
+                                  const int* fds,
+                                  size_t fd_count) {
+  // This function is intended to be used after a crash. fds is an integer
+  // array instead of a vector to avoid forcing callers to provide a vector,
+  // which they would have to create prior to the crash.
+  if (fds && fd_count > kMaxSendRecvMsgFDs) {
+    DLOG(ERROR) << "too many fds " << fd_count;
+    return EINVAL;
+  }
+
+  iovec iov;
+  iov.iov_base = const_cast<void*>(buf);
+  iov.iov_len = buf_size;
+
+  msghdr msg = {};
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
+  char cmsg_buf[CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)];
+  if (fds) {
+    msg.msg_control = cmsg_buf;
+    msg.msg_controllen = CMSG_SPACE(sizeof(int) * fd_count);
+
+    cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+    DCHECK(cmsg);
+
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fd_count);
+    memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * fd_count);
+  }
+
+  // TODO(jperaza): Use sys_sendmsg when lss has macros for maniuplating control
+  // messages. https://crbug.com/crashpad/265
+  if (HANDLE_EINTR(sendmsg(fd, &msg, MSG_NOSIGNAL)) < 0) {
+    DPLOG(ERROR) << "sendmsg";
+    return errno;
+  }
+  return 0;
+}
+
+// static
+bool UnixCredentialSocket::RecvMsg(int fd,
+                                   void* buf,
+                                   size_t buf_size,
+                                   ucred* creds,
+                                   std::vector<ScopedFileHandle>* fds) {
+  iovec iov;
+  iov.iov_base = buf;
+  iov.iov_len = buf_size;
+
+  msghdr msg = {};
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
+  char cmsg_buf[CMSG_SPACE(sizeof(ucred)) +
+                CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)];
+  msg.msg_control = cmsg_buf;
+  msg.msg_controllen = sizeof(cmsg_buf);
+
+  int res = HANDLE_EINTR(recvmsg(fd, &msg, 0));
+  if (res < 0) {
+    PLOG(ERROR) << "recvmsg";
+    return false;
+  }
+
+  ucred* local_creds = nullptr;
+  std::vector<ScopedFileHandle> local_fds;
+  bool unhandled_cmsgs = false;
+
+  for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+       cmsg;
+       cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+      int* fdp = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+      size_t fd_count = (reinterpret_cast<char*>(cmsg) + cmsg->cmsg_len -
+                         reinterpret_cast<char*>(fdp)) /
+                        sizeof(int);
+      DCHECK_LE(fd_count, kMaxSendRecvMsgFDs);
+      for (size_t index = 0; index < fd_count; ++index) {
+        if (fds) {
+          local_fds.emplace_back(fdp[index]);
+        } else if (IGNORE_EINTR(close(fdp[index])) != 0) {
+          PLOG(ERROR) << "close";
+        }
+      }
+      continue;
+    }
+
+    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) {
+      DCHECK(!local_creds);
+      local_creds = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
+      continue;
+    }
+
+    LOG(ERROR) << "unhandled cmsg " << cmsg->cmsg_level << ", "
+               << cmsg->cmsg_type;
+    unhandled_cmsgs = true;
+  }
+
+  if (unhandled_cmsgs) {
+    return false;
+  }
+
+  if (msg.msg_name != nullptr || msg.msg_namelen != 0) {
+    LOG(ERROR) << "unexpected msg name";
+    return false;
+  }
+
+  if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) {
+    LOG(ERROR) << "truncated msg";
+    return false;
+  }
+
+  // Credentials are missing from the message either when the recv socket wasn't
+  // configured with SO_PASSCRED or when all sending sockets have been closed.
+  // In the latter case, res == 0. This case is also indistinguishable from an
+  // empty message sent to a recv socket which hasn't set SO_PASSCRED.
+  if (!local_creds) {
+    LOG_IF(ERROR, res != 0) << "missing credentials";
+    return false;
+  }
+
+  if (static_cast<size_t>(res) != buf_size) {
+    LOG(ERROR) << "incorrect payload size " << res;
+    return false;
+  }
+
+  *creds = *local_creds;
+  if (fds) {
+    fds->swap(local_fds);
+  }
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/linux/socket.h b/util/linux/socket.h
new file mode 100644
index 0000000..85860f7
--- /dev/null
+++ b/util/linux/socket.h
@@ -0,0 +1,96 @@
+// Copyright 2019 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_UTIL_LINUX_SOCKET_H_
+#define CRASHPAD_UTIL_LINUX_SOCKET_H_
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "util/file/file_io.h"
+
+namespace crashpad {
+
+//! \brief Utilities for communicating over `SO_PASSCRED` enabled `AF_UNIX`
+//!     sockets.
+class UnixCredentialSocket {
+ public:
+  //! \brief Creates an `AF_UNIX` family socket pair with `SO_PASSCRED` set on
+  //!     each socket.
+  //!
+  //! \param[out] s1 One end of the connected pair.
+  //! \param[out] s2 The other end of the connected pair.
+  //! \return `true` on success. Otherwise, `false` with a message logged.
+  static bool CreateCredentialSocketpair(ScopedFileHandle* s1,
+                                         ScopedFileHandle* s2);
+
+  //! \brief The maximum number of file descriptors that may be sent/received
+  //!     with `SendMsg()` or `RecvMsg()`.
+  static const size_t kMaxSendRecvMsgFDs;
+
+  //! \brief Wraps `sendmsg()` to send a message with file descriptors.
+  //!
+  //! This function is intended for use with `AF_UNIX` family sockets and
+  //! passes file descriptors with `SCM_RIGHTS`.
+  //!
+  //! This function may be used in a compromised context.
+  //!
+  //! \param[in] fd The file descriptor to write the message to.
+  //! \param[in] buf The buffer containing the message.
+  //! \param[in] buf_size The size of the message.
+  //! \param[in] fds An array of at most `kMaxSendRecvMsgFDs` file descriptors.
+  //!     Optional.
+  //! \param[in] fd_count The number of file descriptors in \a fds. Required
+  //!     only if \a fds was set.
+  //! \return 0 on success or an error code on failure.
+  static int SendMsg(int fd,
+                     const void* buf,
+                     size_t buf_size,
+                     const int* fds = nullptr,
+                     size_t fd_count = 0);
+
+  //! \brief Wraps `recvmsg()` to receive a message with file descriptors and
+  //!     credentials.
+  //!
+  //! This function is intended to be used with `AF_UNIX` family sockets. Up to
+  //! `kMaxSendRecvMsgFDs` file descriptors may be received (via `SCM_RIGHTS`).
+  //! The socket must have `SO_PASSCRED` set.
+  //!
+  //! \param[in] fd The file descriptor to receive the message on.
+  //! \param[out] buf The buffer to fill with the message.
+  //! \param[in] buf_size The size of the message.
+  //! \param[out] creds The credentials of the sender.
+  //! \param[out] fds The recieved file descriptors. Optional. If `nullptr`, all
+  //!     received file descriptors will be closed.
+  //! \return `true` on success. Otherwise, `false`, with a message logged. No
+  //!     message will be logged if the message was detected to be an EOF
+  //!     condition triggered by all clients disconnecting. This case is
+  //!     indistinguishable from misuses of this interface that haven't set
+  //!     `SO_PASSCRED` on \a fd.
+  static bool RecvMsg(int fd,
+                      void* buf,
+                      size_t buf_size,
+                      ucred* creds,
+                      std::vector<ScopedFileHandle>* fds = nullptr);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(UnixCredentialSocket);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_LINUX_SOCKET_H_
diff --git a/util/linux/socket_test.cc b/util/linux/socket_test.cc
new file mode 100644
index 0000000..4e583ae
--- /dev/null
+++ b/util/linux/socket_test.cc
@@ -0,0 +1,139 @@
+// Copyright 2019 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.
+
+#include "util/linux/socket.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "gtest/gtest.h"
+#include "util/linux/socket.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(Socket, Credentials) {
+  ScopedFileHandle send_sock, recv_sock;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock));
+
+  char msg = 42;
+  ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), &msg, sizeof(msg)),
+            0);
+
+  char recv_msg = 0;
+  ucred creds;
+  ASSERT_TRUE(UnixCredentialSocket::RecvMsg(
+      recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds));
+  EXPECT_EQ(recv_msg, msg);
+  EXPECT_EQ(creds.pid, getpid());
+  EXPECT_EQ(creds.uid, geteuid());
+  EXPECT_EQ(creds.gid, getegid());
+}
+
+TEST(Socket, EmptyMessages) {
+  ScopedFileHandle send_sock, recv_sock;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock));
+
+  ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), nullptr, 0), 0);
+
+  ucred creds;
+  ASSERT_TRUE(
+      UnixCredentialSocket::RecvMsg(recv_sock.get(), nullptr, 0, &creds));
+  EXPECT_EQ(creds.pid, getpid());
+  EXPECT_EQ(creds.uid, geteuid());
+  EXPECT_EQ(creds.gid, getegid());
+}
+
+TEST(Socket, Hangup) {
+  ScopedFileHandle send_sock, recv_sock;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock));
+
+  send_sock.reset();
+
+  char recv_msg = 0;
+  ucred creds;
+  EXPECT_FALSE(UnixCredentialSocket::RecvMsg(
+      recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds));
+}
+
+TEST(Socket, FileDescriptors) {
+  ScopedFileHandle send_sock, recv_sock;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock));
+
+  ScopedFileHandle test_fd1, test_fd2;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&test_fd1, &test_fd2));
+
+  char msg = 42;
+  ASSERT_EQ(UnixCredentialSocket::SendMsg(
+                send_sock.get(), &msg, sizeof(msg), &test_fd1.get(), 1),
+            0);
+
+  char recv_msg = 0;
+  ucred creds;
+  std::vector<ScopedFileHandle> fds;
+  ASSERT_TRUE(UnixCredentialSocket::RecvMsg(
+      recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds, &fds));
+  ASSERT_EQ(fds.size(), 1u);
+}
+
+TEST(Socket, RecvClosesFileDescriptors) {
+  ScopedFileHandle send_sock, recv_sock;
+  ASSERT_TRUE(
+      UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock));
+
+  ScopedFileHandle send_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs];
+  ScopedFileHandle recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs];
+  int raw_recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs];
+  for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs;
+       ++index) {
+    ASSERT_TRUE(UnixCredentialSocket::CreateCredentialSocketpair(
+        &send_fds[index], &recv_fds[index]));
+    raw_recv_fds[index] = recv_fds[index].get();
+  }
+
+  char msg = 42;
+  ASSERT_EQ(
+      UnixCredentialSocket::SendMsg(send_sock.get(),
+                                    &msg,
+                                    sizeof(msg),
+                                    raw_recv_fds,
+                                    UnixCredentialSocket::kMaxSendRecvMsgFDs),
+      0);
+
+  char recv_msg = 0;
+  ucred creds;
+  ASSERT_TRUE(UnixCredentialSocket::RecvMsg(
+      recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds));
+  EXPECT_EQ(creds.pid, getpid());
+
+  for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs;
+       ++index) {
+    recv_fds[index].reset();
+    char c;
+    EXPECT_EQ(
+        HANDLE_EINTR(send(send_fds[index].get(), &c, sizeof(c), MSG_NOSIGNAL)),
+        -1);
+    EXPECT_EQ(errno, EPIPE);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/mac/checked_mach_address_range_test.cc b/util/mac/checked_mach_address_range_test.cc
index 089f681..8531379 100644
--- a/util/mac/checked_mach_address_range_test.cc
+++ b/util/mac/checked_mach_address_range_test.cc
@@ -19,7 +19,7 @@
 
 #include <limits>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -116,7 +116,7 @@
       {0xffffffffffffffff, 1, kInvalid},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx",
                                     index,
@@ -166,7 +166,7 @@
   CheckedMachAddressRange parent_range_32(false, 0x2000, 0x1000);
   ASSERT_TRUE(parent_range_32.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(
         base::StringPrintf("index %zu, value 0x%llx", index, testcase.value));
@@ -223,7 +223,7 @@
   CheckedMachAddressRange parent_range_32(false, 0x2000, 0x1000);
   ASSERT_TRUE(parent_range_32.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx",
                                     index,
diff --git a/util/mac/launchd_test.mm b/util/mac/launchd_test.mm
index 12ed832..b793a9b 100644
--- a/util/mac/launchd_test.mm
+++ b/util/mac/launchd_test.mm
@@ -23,7 +23,7 @@
 #include <limits>
 
 #include "base/mac/scoped_launch_data.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "util/stdlib/objc.h"
 
@@ -58,7 +58,7 @@
       @0xfedcba9876543210,
     };
 
-    for (size_t index = 0; index < arraysize(integer_nses); ++index) {
+    for (size_t index = 0; index < base::size(integer_nses); ++index) {
       NSNumber* integer_ns = integer_nses[index];
       launch_data.reset(CFPropertyToLaunchData(integer_ns));
       ASSERT_TRUE(launch_data.get());
@@ -88,7 +88,7 @@
       [NSNumber numberWithDouble:std::numeric_limits<double>::signaling_NaN()],
     };
 
-    for (size_t index = 0; index < arraysize(double_nses); ++index) {
+    for (size_t index = 0; index < base::size(double_nses); ++index) {
       NSNumber* double_ns = double_nses[index];
       launch_data.reset(CFPropertyToLaunchData(double_ns));
       ASSERT_TRUE(launch_data.get());
@@ -114,7 +114,7 @@
       @YES,
     };
 
-    for (size_t index = 0; index < arraysize(bool_nses); ++index) {
+    for (size_t index = 0; index < base::size(bool_nses); ++index) {
       NSNumber* bool_ns = bool_nses[index];
       launch_data.reset(CFPropertyToLaunchData(bool_ns));
       ASSERT_TRUE(launch_data.get());
@@ -138,7 +138,7 @@
       @"Üñîçø∂é",
     };
 
-    for (size_t index = 0; index < arraysize(string_nses); ++index) {
+    for (size_t index = 0; index < base::size(string_nses); ++index) {
       NSString* string_ns = string_nses[index];
       launch_data.reset(CFPropertyToLaunchData(string_ns));
       ASSERT_TRUE(launch_data.get());
diff --git a/util/mac/xattr.cc b/util/mac/xattr.cc
index 75d7938..c63fc87 100644
--- a/util/mac/xattr.cc
+++ b/util/mac/xattr.cc
@@ -20,7 +20,6 @@
 #include <sys/xattr.h>
 
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
diff --git a/util/mach/bootstrap.cc b/util/mach/bootstrap.cc
new file mode 100644
index 0000000..f553437
--- /dev/null
+++ b/util/mach/bootstrap.cc
@@ -0,0 +1,109 @@
+// Copyright 2020 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.
+
+#include "util/mach/bootstrap.h"
+
+#include <mach/mach.h>
+#include <servers/bootstrap.h>
+
+#include "base/mac/mach_logging.h"
+
+namespace {
+
+// This forms the internal implementation for BootstrapCheckIn() and
+// BootstrapLookUp(), which follow the same logic aside from the routine called
+// and the right type returned.
+
+struct BootstrapCheckInTraits {
+  using Type = base::mac::ScopedMachReceiveRight;
+  static kern_return_t Call(mach_port_t bootstrap_port,
+                            const char* service_name,
+                            mach_port_t* service_port) {
+    return bootstrap_check_in(bootstrap_port, service_name, service_port);
+  }
+  static constexpr char kName[] = "bootstrap_check_in";
+};
+constexpr char BootstrapCheckInTraits::kName[];
+
+struct BootstrapLookUpTraits {
+  using Type = base::mac::ScopedMachSendRight;
+  static kern_return_t Call(mach_port_t bootstrap_port,
+                            const char* service_name,
+                            mach_port_t* service_port) {
+    return bootstrap_look_up(bootstrap_port, service_name, service_port);
+  }
+  static constexpr char kName[] = "bootstrap_look_up";
+};
+constexpr char BootstrapLookUpTraits::kName[];
+
+template <typename Traits>
+typename Traits::Type BootstrapCheckInOrLookUp(
+    const std::string& service_name) {
+  // bootstrap_check_in() and bootstrap_look_up() silently truncate service
+  // names longer than BOOTSTRAP_MAX_NAME_LEN. This check ensures that the name
+  // will not be truncated.
+  if (service_name.size() >= BOOTSTRAP_MAX_NAME_LEN) {
+    LOG(ERROR) << Traits::kName << " " << service_name << ": name too long";
+    return typename Traits::Type(MACH_PORT_NULL);
+  }
+
+  mach_port_t service_port;
+  kern_return_t kr =
+      Traits::Call(bootstrap_port, service_name.c_str(), &service_port);
+  if (kr != BOOTSTRAP_SUCCESS) {
+    BOOTSTRAP_LOG(ERROR, kr) << Traits::kName << " " << service_name;
+    service_port = MACH_PORT_NULL;
+  }
+
+  return typename Traits::Type(service_port);
+}
+
+}  // namespace
+
+namespace crashpad {
+
+base::mac::ScopedMachReceiveRight BootstrapCheckIn(
+    const std::string& service_name) {
+  return BootstrapCheckInOrLookUp<BootstrapCheckInTraits>(service_name);
+}
+
+base::mac::ScopedMachSendRight BootstrapLookUp(
+    const std::string& service_name) {
+  base::mac::ScopedMachSendRight send(
+      BootstrapCheckInOrLookUp<BootstrapLookUpTraits>(service_name));
+
+  // It’s possible to race the bootstrap server when the receive right
+  // corresponding to the looked-up send right is destroyed immediately before
+  // the bootstrap_look_up() call. If the bootstrap server believes that
+  // |service_name| is still registered before processing the port-destroyed
+  // notification sent to it by the kernel, it will respond to a
+  // bootstrap_look_up() request with a send right that has become a dead name,
+  // which will be returned to the bootstrap_look_up() caller, translated into
+  // the caller’s IPC port name space, as the special MACH_PORT_DEAD port name.
+  // Check for that and return MACH_PORT_NULL in its place, as though the
+  // bootstrap server had fully processed the port-destroyed notification before
+  // responding to bootstrap_look_up().
+  if (send.get() == MACH_PORT_DEAD) {
+    LOG(ERROR) << "bootstrap_look_up " << service_name << ": service is dead";
+    send.reset();
+  }
+
+  return send;
+}
+
+base::mac::ScopedMachSendRight SystemCrashReporterHandler() {
+  return BootstrapLookUp("com.apple.ReportCrash");
+}
+
+}  // namespace crashpad
diff --git a/util/mach/bootstrap.h b/util/mach/bootstrap.h
new file mode 100644
index 0000000..680f5b9
--- /dev/null
+++ b/util/mach/bootstrap.h
@@ -0,0 +1,63 @@
+// Copyright 2020 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_UTIL_MACH_BOOTSTRAP_H_
+#define CRASHPAD_UTIL_MACH_BOOTSTRAP_H_
+
+#include <string>
+
+#include "base/mac/scoped_mach_port.h"
+
+namespace crashpad {
+
+//! \brief Makes a `boostrap_check_in()` call to the process’ bootstrap server.
+//!
+//! This function is provided to make it easier to call `bootstrap_check_in()`
+//! while avoiding accidental leaks of the returned receive right.
+//!
+//! \param[in] service_name The service name to check in.
+//!
+//! \return On success, a receive right to the service port. On failure,
+//!     `MACH_PORT_NULL` with a message logged.
+base::mac::ScopedMachReceiveRight BootstrapCheckIn(
+    const std::string& service_name);
+
+//! \brief Makes a `boostrap_look_up()` call to the process’ bootstrap server.
+//!
+//! This function is provided to make it easier to call `bootstrap_look_up()`
+//! while avoiding accidental leaks of the returned send right.
+//!
+//! \param[in] service_name The service name to look up.
+//!
+//! \return On success, a send right to the service port. On failure,
+//!     `MACH_PORT_NULL` with a message logged.
+base::mac::ScopedMachSendRight BootstrapLookUp(const std::string& service_name);
+
+//! \brief Obtains the system’s default Mach exception handler for crash-type
+//!     exceptions.
+//!
+//! This is obtained by looking up `"com.apple.ReportCrash"` with the bootstrap
+//! server. The service name comes from the first launch agent loaded by
+//! `launchd` with a `MachServices` entry having `ExceptionServer` set. This
+//! launch agent is normally loaded from
+//! `/System/Library/LaunchAgents/com.apple.ReportCrash.plist`.
+//!
+//! \return On success, a send right to an `exception_handler_t` corresponding
+//!     to the system’s default crash reporter. On failure, `MACH_PORT_NULL`,
+//!     with a message logged.
+base::mac::ScopedMachSendRight SystemCrashReporterHandler();
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_MACH_BOOTSTRAP_H_
diff --git a/util/mach/bootstrap_test.cc b/util/mach/bootstrap_test.cc
new file mode 100644
index 0000000..44587d0
--- /dev/null
+++ b/util/mach/bootstrap_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 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.
+
+#include "util/mach/bootstrap.h"
+
+#include "base/mac/scoped_mach_port.h"
+#include "gtest/gtest.h"
+#include "util/mach/mach_extensions.h"
+#include "util/misc/random_string.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(Bootstrap, BootstrapCheckInAndLookUp) {
+  // This should always exist.
+  base::mac::ScopedMachSendRight report_crash(
+      BootstrapLookUp("com.apple.ReportCrash"));
+  EXPECT_NE(report_crash, kMachPortNull);
+
+  std::string service_name = "org.chromium.crashpad.test.bootstrap_check_in.";
+  service_name.append(RandomString());
+
+  {
+    // The new service hasn’t checked in yet, so this should fail.
+    base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name));
+    EXPECT_EQ(send, kMachPortNull);
+
+    // Check it in.
+    base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name));
+    EXPECT_NE(receive, kMachPortNull);
+
+    // Now it should be possible to look up the new service.
+    send = BootstrapLookUp(service_name);
+    EXPECT_NE(send, kMachPortNull);
+
+    // It shouldn’t be possible to check the service in while it’s active.
+    base::mac::ScopedMachReceiveRight receive_2(BootstrapCheckIn(service_name));
+    EXPECT_EQ(receive_2, kMachPortNull);
+  }
+
+  // The new service should be gone now.
+  base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name));
+  EXPECT_EQ(send, kMachPortNull);
+
+  // It should be possible to check it in again.
+  base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name));
+  EXPECT_NE(receive, kMachPortNull);
+}
+
+TEST(Bootstrap, SystemCrashReporterHandler) {
+  base::mac::ScopedMachSendRight system_crash_reporter_handler(
+      SystemCrashReporterHandler());
+  EXPECT_TRUE(system_crash_reporter_handler.is_valid());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/mach/child_port_handshake.cc b/util/mach/child_port_handshake.cc
index 750d3cb..3ff9828 100644
--- a/util/mach/child_port_handshake.cc
+++ b/util/mach/child_port_handshake.cc
@@ -31,8 +31,10 @@
 #include "base/mac/scoped_mach_port.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/rand_util.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "util/file/file_io.h"
+#include "util/mach/bootstrap.h"
 #include "util/mach/child_port.h"
 #include "util/mach/child_port_server.h"
 #include "util/mach/mach_extensions.h"
@@ -125,8 +127,12 @@
     return MACH_PORT_NULL;
   }
 
-  // A kqueue cannot monitor a raw Mach receive right with EVFILT_MACHPORT. It
-  // requires a port set. Create a new port set and add the receive right to it.
+  // Prior to macOS 10.12, a kqueue cannot monitor a raw Mach receive right with
+  // EVFILT_MACHPORT. It requires a port set. Compare 10.11.6
+  // xnu-3248.60.10/osfmk/ipc/ipc_pset.c filt_machportattach(), which requires
+  // MACH_PORT_RIGHT_PORT_SET, to 10.12.0 xnu-3789.1.32/osfmk/ipc/ipc_pset.c
+  // filt_machportattach(), which also handles MACH_PORT_TYPE_RECEIVE. Create a
+  // new port set and add the receive right to it.
   base::mac::ScopedMachPortSet server_port_set(
       NewMachPort(MACH_PORT_RIGHT_PORT_SET));
   CHECK(server_port_set.is_valid());
@@ -157,8 +163,8 @@
          0,
          0,
          nullptr);
-  int rv = HANDLE_EINTR(
-      kevent(kq.get(), changelist, arraysize(changelist), nullptr, 0, nullptr));
+  int rv = HANDLE_EINTR(kevent(
+      kq.get(), changelist, base::size(changelist), nullptr, 0, nullptr));
   PCHECK(rv != -1) << "kevent";
 
   ChildPortServer child_port_server(this);
diff --git a/util/mach/child_port_server.cc b/util/mach/child_port_server.cc
index a322302..aa7a1b8 100644
--- a/util/mach/child_port_server.cc
+++ b/util/mach/child_port_server.cc
@@ -15,6 +15,7 @@
 #include "util/mach/child_port_server.h"
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "util/mach/child_portServer.h"
 #include "util/mach/mach_message.h"
 
@@ -90,7 +91,7 @@
   static constexpr mach_msg_id_t request_ids[] =
       {kMachMessageIDChildPortCheckIn};
   return std::set<mach_msg_id_t>(&request_ids[0],
-                                 &request_ids[arraysize(request_ids)]);
+                                 &request_ids[base::size(request_ids)]);
 }
 
 mach_msg_size_t ChildPortServer::MachMessageServerRequestSize() {
diff --git a/util/mach/composite_mach_message_server_test.cc b/util/mach/composite_mach_message_server_test.cc
index d45eca0..87242be 100644
--- a/util/mach/composite_mach_message_server_test.cc
+++ b/util/mach/composite_mach_message_server_test.cc
@@ -16,6 +16,7 @@
 
 #include <sys/types.h>
 
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "test/gtest_death.h"
@@ -196,7 +197,7 @@
   TestMachMessageHandler handlers[3];
   std::set<mach_msg_id_t> expect_request_ids;
 
-  for (size_t index = 0; index < arraysize(kRequestIDs0); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs0); ++index) {
     const mach_msg_id_t request_id = kRequestIDs0[index];
     handlers[0].AddRequestID(request_id);
     expect_request_ids.insert(request_id);
@@ -205,7 +206,7 @@
   handlers[0].SetReplySize(sizeof(mig_reply_error_t));
   handlers[0].SetReturnCodes(true, kReturnCode0, false);
 
-  for (size_t index = 0; index < arraysize(kRequestIDs1); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs1); ++index) {
     const mach_msg_id_t request_id = kRequestIDs1[index];
     handlers[1].AddRequestID(request_id);
     expect_request_ids.insert(request_id);
@@ -214,7 +215,7 @@
   handlers[1].SetReplySize(200);
   handlers[1].SetReturnCodes(false, kReturnCode1, true);
 
-  for (size_t index = 0; index < arraysize(kRequestIDs2); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs2); ++index) {
     const mach_msg_id_t request_id = kRequestIDs2[index];
     handlers[2].AddRequestID(request_id);
     expect_request_ids.insert(request_id);
@@ -252,7 +253,7 @@
 
   // Send messages with known request IDs.
 
-  for (size_t index = 0; index < arraysize(kRequestIDs0); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs0); ++index) {
     request.header.msgh_id = kRequestIDs0[index];
     SCOPED_TRACE(base::StringPrintf(
         "handler 0, index %zu, id %d", index, request.header.msgh_id));
@@ -263,7 +264,7 @@
     EXPECT_FALSE(destroy_complex_request);
   }
 
-  for (size_t index = 0; index < arraysize(kRequestIDs1); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs1); ++index) {
     request.header.msgh_id = kRequestIDs1[index];
     SCOPED_TRACE(base::StringPrintf(
         "handler 1, index %zu, id %d", index, request.header.msgh_id));
@@ -274,7 +275,7 @@
     EXPECT_TRUE(destroy_complex_request);
   }
 
-  for (size_t index = 0; index < arraysize(kRequestIDs2); ++index) {
+  for (size_t index = 0; index < base::size(kRequestIDs2); ++index) {
     request.header.msgh_id = kRequestIDs2[index];
     SCOPED_TRACE(base::StringPrintf(
         "handler 2, index %zu, id %d", index, request.header.msgh_id));
diff --git a/util/mach/exc_client_variants_test.cc b/util/mach/exc_client_variants_test.cc
index b728856..007442a 100644
--- a/util/mach/exc_client_variants_test.cc
+++ b/util/mach/exc_client_variants_test.cc
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "test/mac/mach_errors.h"
@@ -181,11 +182,11 @@
       // These aren’t real flavors, it’s just for testing.
       flavor = exception_ + 10;
       flavor_p = &flavor;
-      for (size_t index = 0; index < arraysize(old_state); ++index) {
+      for (size_t index = 0; index < base::size(old_state); ++index) {
         old_state[index] = index;
       }
       old_state_p = reinterpret_cast<thread_state_t>(&old_state);
-      old_state_count = arraysize(old_state);
+      old_state_count = base::size(old_state);
 
       // new_state and new_state_count are out parameters that the server should
       // never see or use, so set them to bogus values. The call to the server
@@ -202,7 +203,7 @@
                                       task,
                                       exception,
                                       code,
-                                      arraysize(code),
+                                      base::size(code),
                                       flavor_p,
                                       old_state_p,
                                       old_state_count,
@@ -273,7 +274,7 @@
       kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
   };
 
-  for (size_t index = 0; index < arraysize(kBehaviors); ++index) {
+  for (size_t index = 0; index < base::size(kBehaviors); ++index) {
     exception_behavior_t behavior = kBehaviors[index];
     SCOPED_TRACE(base::StringPrintf("index %zu, behavior %d", index, behavior));
 
diff --git a/util/mach/exc_server_variants.cc b/util/mach/exc_server_variants.cc
index e7174ac..c272ae7 100644
--- a/util/mach/exc_server_variants.cc
+++ b/util/mach/exc_server_variants.cc
@@ -21,6 +21,8 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
+#include "build/build_config.h"
 #include "util/mac/mac_util.h"
 #include "util/mach/composite_mach_message_server.h"
 #include "util/mach/exc.h"
@@ -242,7 +244,7 @@
         Traits::kMachMessageIDExceptionRaiseStateIdentity,
     };
     return std::set<mach_msg_id_t>(&request_ids[0],
-                                   &request_ids[arraysize(request_ids)]);
+                                   &request_ids[base::size(request_ids)]);
   }
 
   mach_msg_size_t MachMessageServerRequestSize() override {
@@ -319,7 +321,7 @@
       using Reply = typename Traits::ExceptionRaiseStateReply;
       Reply* out_reply = reinterpret_cast<Reply*>(out_header);
       out_reply->flavor = in_request_1->flavor;
-      out_reply->new_stateCnt = arraysize(out_reply->new_state);
+      out_reply->new_stateCnt = base::size(out_reply->new_state);
       out_reply->RetCode =
           interface_->CatchExceptionRaiseState(in_header->msgh_local_port,
                                                in_request->exception,
@@ -362,7 +364,7 @@
       using Reply = typename Traits::ExceptionRaiseStateIdentityReply;
       Reply* out_reply = reinterpret_cast<Reply*>(out_header);
       out_reply->flavor = in_request_1->flavor;
-      out_reply->new_stateCnt = arraysize(out_reply->new_state);
+      out_reply->new_stateCnt = base::size(out_reply->new_state);
       out_reply->RetCode = interface_->CatchExceptionRaiseStateIdentity(
           in_header->msgh_local_port,
           in_request->thread.name,
@@ -681,7 +683,7 @@
                                              exception_behavior_t behavior,
                                              bool set_thread_state) {
   if (exception == EXC_CRASH
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
+#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
       && MacOSXMinorVersion() >= 11
 #endif
      ) {
diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc
index 5e67bfe..bea3d76 100644
--- a/util/mach/exc_server_variants_test.cc
+++ b/util/mach/exc_server_variants_test.cc
@@ -19,18 +19,22 @@
 #include <string.h>
 #include <sys/types.h>
 
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "test/mac/mach_errors.h"
-#include "test/mac/mach_multiprocess.h"
 #include "util/mac/mac_util.h"
 #include "util/mach/exception_behaviors.h"
 #include "util/mach/exception_types.h"
 #include "util/mach/mach_message.h"
 #include "util/misc/implicit_cast.h"
 
+#if !defined(OS_IOS)
+#include "test/mac/mach_multiprocess.h"
+#endif  // !OS_IOS
+
 namespace crashpad {
 namespace test {
 namespace {
@@ -228,7 +232,7 @@
     EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0);
     EXPECT_EQ(RetCode, KERN_SUCCESS);
     EXPECT_EQ(flavor, kThreadStateFlavor);
-    EXPECT_EQ(new_stateCnt, arraysize(new_state));
+    EXPECT_EQ(new_stateCnt, base::size(new_state));
   }
 
   mach_msg_header_t Head;
@@ -659,7 +663,7 @@
           AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]),
           Pointee(Eq(kThreadStateFlavor)),
           IsThreadStateAndCount(kThreadStateFlavorCount),
-          IsThreadStateAndCount(arraysize(reply.new_state)),
+          IsThreadStateAndCount(base::size(reply.new_state)),
           Eq(request.Trailer())))
       .WillOnce(Return(KERN_SUCCESS))
       .RetiresOnSaturation();
@@ -708,7 +712,7 @@
           AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]),
           Pointee(Eq(kThreadStateFlavor)),
           IsThreadStateAndCount(kThreadStateFlavorCount),
-          IsThreadStateAndCount(arraysize(reply.new_state)),
+          IsThreadStateAndCount(base::size(reply.new_state)),
           Eq(request.Trailer())))
       .WillOnce(Return(KERN_SUCCESS))
       .RetiresOnSaturation();
@@ -802,7 +806,7 @@
                                                kTestMachExceptionCodes[1]),
                              Pointee(Eq(kThreadStateFlavor)),
                              IsThreadStateAndCount(kThreadStateFlavorCount),
-                             IsThreadStateAndCount(arraysize(reply.new_state)),
+                             IsThreadStateAndCount(base::size(reply.new_state)),
                              Eq(request.Trailer())))
       .WillOnce(Return(KERN_SUCCESS))
       .RetiresOnSaturation();
@@ -852,7 +856,7 @@
                                                kTestMachExceptionCodes[1]),
                              Pointee(Eq(kThreadStateFlavor)),
                              IsThreadStateAndCount(kThreadStateFlavorCount),
-                             IsThreadStateAndCount(arraysize(reply.new_state)),
+                             IsThreadStateAndCount(base::size(reply.new_state)),
                              Eq(request.Trailer())))
       .WillOnce(Return(KERN_SUCCESS))
       .RetiresOnSaturation();
@@ -906,7 +910,7 @@
       2508,
   };
 
-  for (size_t index = 0; index < arraysize(unknown_ids); ++index) {
+  for (size_t index = 0; index < base::size(unknown_ids); ++index) {
     mach_msg_id_t id = unknown_ids[index];
 
     SCOPED_TRACE(base::StringPrintf("unknown id %d", id));
@@ -957,6 +961,8 @@
             expect_request_ids);
 }
 
+#if !defined(OS_IOS)
+
 class TestExcServerVariants : public MachMultiprocess,
                               public UniversalMachExcServer::Interface {
  public:
@@ -1179,7 +1185,7 @@
 #endif
   };
 
-  for (size_t index = 0; index < arraysize(test_data); ++index) {
+  for (size_t index = 0; index < base::size(test_data); ++index) {
     const auto& test = test_data[index];
     SCOPED_TRACE(
         base::StringPrintf("index %zu, flavor %d", index, test.flavor));
@@ -1192,9 +1198,16 @@
   }
 }
 
+#endif  // !OS_IOS
+
 TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) {
+#if defined(OS_IOS)
+  // iOS 9 ≅ OS X 10.11.
+  const kern_return_t prefer_not_set_thread_state = KERN_SUCCESS;
+#else
   const kern_return_t prefer_not_set_thread_state =
       MacOSXMinorVersion() < 11 ? MACH_RCV_PORT_DIED : KERN_SUCCESS;
+#endif
 
   const struct {
     exception_type_t exception;
@@ -1252,7 +1265,7 @@
        KERN_SUCCESS},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& test_data = kTestData[index];
     SCOPED_TRACE(
         base::StringPrintf("index %zu, behavior %d, set_thread_state %s",
@@ -1271,8 +1284,8 @@
   static constexpr natural_t old_state[] = {1, 2, 3, 4, 5};
   natural_t new_state[10] = {};
 
-  constexpr mach_msg_type_number_t old_state_count = arraysize(old_state);
-  mach_msg_type_number_t new_state_count = arraysize(new_state);
+  constexpr mach_msg_type_number_t old_state_count = base::size(old_state);
+  mach_msg_type_number_t new_state_count = base::size(new_state);
 
   // EXCEPTION_DEFAULT (with or without MACH_EXCEPTION_CODES) is not
   // state-carrying. new_state and new_state_count should be untouched.
@@ -1281,8 +1294,8 @@
                      old_state_count,
                      new_state,
                      &new_state_count);
-  EXPECT_EQ(new_state_count, arraysize(new_state));
-  for (size_t i = 0; i < arraysize(new_state); ++i) {
+  EXPECT_EQ(new_state_count, base::size(new_state));
+  for (size_t i = 0; i < base::size(new_state); ++i) {
     EXPECT_EQ(new_state[i], 0u) << "i " << i;
   }
 
@@ -1291,8 +1304,8 @@
                      old_state_count,
                      new_state,
                      &new_state_count);
-  EXPECT_EQ(new_state_count, arraysize(new_state));
-  for (size_t i = 0; i < arraysize(new_state); ++i) {
+  EXPECT_EQ(new_state_count, base::size(new_state));
+  for (size_t i = 0; i < base::size(new_state); ++i) {
     EXPECT_EQ(new_state[i], 0u) << "i " << i;
   }
 
@@ -1304,7 +1317,7 @@
   for (size_t i = 0; i < copy_limit; ++i) {
     EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
   }
-  for (size_t i = copy_limit; i < arraysize(new_state); ++i) {
+  for (size_t i = copy_limit; i < base::size(new_state); ++i) {
     EXPECT_EQ(new_state[i], 0u) << "i " << i;
   }
 
@@ -1320,23 +1333,23 @@
   for (size_t i = 0; i < copy_limit; ++i) {
     EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
   }
-  for (size_t i = copy_limit; i < arraysize(new_state); ++i) {
+  for (size_t i = copy_limit; i < base::size(new_state); ++i) {
     EXPECT_EQ(new_state[i], 0u) << "i " << i;
   }
 
   // This is a state-carrying exception where all of old_state is copied to
   // new_state, which is large enough to receive it and then some.
-  new_state_count = arraysize(new_state);
+  new_state_count = base::size(new_state);
   ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY,
                      old_state,
                      old_state_count,
                      new_state,
                      &new_state_count);
   EXPECT_EQ(new_state_count, old_state_count);
-  for (size_t i = 0; i < arraysize(old_state); ++i) {
+  for (size_t i = 0; i < base::size(old_state); ++i) {
     EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
   }
-  for (size_t i = arraysize(old_state); i < arraysize(new_state); ++i) {
+  for (size_t i = base::size(old_state); i < base::size(new_state); ++i) {
     EXPECT_EQ(new_state[i], 0u) << "i " << i;
   }
 }
diff --git a/util/mach/exception_behaviors_test.cc b/util/mach/exception_behaviors_test.cc
index d45110d..bdbf673 100644
--- a/util/mach/exception_behaviors_test.cc
+++ b/util/mach/exception_behaviors_test.cc
@@ -16,7 +16,7 @@
 
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "util/mach/mach_extensions.h"
@@ -53,7 +53,7 @@
        EXCEPTION_STATE_IDENTITY},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& test_data = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %zu, behavior %d", index, test_data.behavior));
diff --git a/util/mach/exception_types_test.cc b/util/mach/exception_types_test.cc
index c24428a..da2c822 100644
--- a/util/mach/exception_types_test.cc
+++ b/util/mach/exception_types_test.cc
@@ -20,7 +20,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "util/mac/mac_util.h"
@@ -67,7 +67,7 @@
       {0, 0, 0, 0},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& test_data = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %zu, code_0 0x%llx", index, test_data.code_0));
@@ -84,7 +84,7 @@
 
   // Now make sure that ExcCrashRecoverOriginalException() properly ignores
   // optional arguments.
-  static_assert(arraysize(kTestData) >= 1, "must have something to test");
+  static_assert(base::size(kTestData) >= 1, "must have something to test");
   const auto& test_data = kTestData[0];
   EXPECT_EQ(
       ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr),
@@ -238,7 +238,7 @@
       {0x00010000, 0x00010000, static_cast<int32_t>(0xffffffff)},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& test_data = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %zu, exception 0x%x, code_0 0x%llx",
                                     index,
diff --git a/util/mach/mach_extensions.cc b/util/mach/mach_extensions.cc
index 5450ab2..cefb9bf 100644
--- a/util/mach/mach_extensions.cc
+++ b/util/mach/mach_extensions.cc
@@ -14,66 +14,14 @@
 
 #include "util/mach/mach_extensions.h"
 
+#include <Availability.h>
 #include <AvailabilityMacros.h>
 #include <pthread.h>
-#include <servers/bootstrap.h>
 
 #include "base/mac/mach_logging.h"
+#include "build/build_config.h"
 #include "util/mac/mac_util.h"
 
-namespace {
-
-// This forms the internal implementation for BootstrapCheckIn() and
-// BootstrapLookUp(), which follow the same logic aside from the routine called
-// and the right type returned.
-
-struct BootstrapCheckInTraits {
-  using Type = base::mac::ScopedMachReceiveRight;
-  static kern_return_t Call(mach_port_t bootstrap_port,
-                            const char* service_name,
-                            mach_port_t* service_port) {
-    return bootstrap_check_in(bootstrap_port, service_name, service_port);
-  }
-  static constexpr char kName[] = "bootstrap_check_in";
-};
-constexpr char BootstrapCheckInTraits::kName[];
-
-struct BootstrapLookUpTraits {
-  using Type = base::mac::ScopedMachSendRight;
-  static kern_return_t Call(mach_port_t bootstrap_port,
-                            const char* service_name,
-                            mach_port_t* service_port) {
-    return bootstrap_look_up(bootstrap_port, service_name, service_port);
-  }
-  static constexpr char kName[] = "bootstrap_look_up";
-};
-constexpr char BootstrapLookUpTraits::kName[];
-
-template <typename Traits>
-typename Traits::Type BootstrapCheckInOrLookUp(
-    const std::string& service_name) {
-  // bootstrap_check_in() and bootstrap_look_up() silently truncate service
-  // names longer than BOOTSTRAP_MAX_NAME_LEN. This check ensures that the name
-  // will not be truncated.
-  if (service_name.size() >= BOOTSTRAP_MAX_NAME_LEN) {
-    LOG(ERROR) << Traits::kName << " " << service_name << ": name too long";
-    return typename Traits::Type(MACH_PORT_NULL);
-  }
-
-  mach_port_t service_port;
-  kern_return_t kr = Traits::Call(bootstrap_port,
-                                  service_name.c_str(),
-                                  &service_port);
-  if (kr != BOOTSTRAP_SUCCESS) {
-    BOOTSTRAP_LOG(ERROR, kr) << Traits::kName << " " << service_name;
-    service_port = MACH_PORT_NULL;
-  }
-
-  return typename Traits::Type(service_port);
-}
-
-}  // namespace
-
 namespace crashpad {
 
 thread_t MachThreadSelf() {
@@ -97,7 +45,12 @@
   // 10.9.4 xnu-2422.110.17/osfmk/mach/ipc_host.c and
   // xnu-2422.110.17/osfmk/mach/ipc_tt.c.
 
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
+#if defined(OS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
+// iOS 7 ≅ OS X 10.9.
+#error This code was not ported to iOS versions older than 7
+#endif
+
+#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
   const int mac_os_x_minor_version = MacOSXMinorVersion();
 #endif
 
@@ -114,7 +67,7 @@
       EXC_MASK_MACH_SYSCALL |
       EXC_MASK_RPC_ALERT |
       EXC_MASK_MACHINE;
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
+#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
   if (mac_os_x_minor_version < 8) {
     return kExcMaskAll_10_6;
   }
@@ -124,7 +77,7 @@
   // xnu-2050.48.11/osfmk/mach/exception_types.h.
   constexpr exception_mask_t kExcMaskAll_10_8 =
       kExcMaskAll_10_6 | EXC_MASK_RESOURCE;
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
+#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
   if (mac_os_x_minor_version < 9) {
     return kExcMaskAll_10_8;
   }
@@ -139,7 +92,12 @@
 
 exception_mask_t ExcMaskValid() {
   const exception_mask_t kExcMaskValid_10_6 = ExcMaskAll() | EXC_MASK_CRASH;
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
+#if defined(OS_IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+// iOS 9 ≅ OS X 10.11.
+#error This code was not ported to iOS versions older than 9
+#endif
+
+#if !defined(OS_IOS) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
   if (MacOSXMinorVersion() < 11) {
     return kExcMaskValid_10_6;
   }
@@ -151,37 +109,4 @@
   return kExcMaskValid_10_11;
 }
 
-base::mac::ScopedMachReceiveRight BootstrapCheckIn(
-    const std::string& service_name) {
-  return BootstrapCheckInOrLookUp<BootstrapCheckInTraits>(service_name);
-}
-
-base::mac::ScopedMachSendRight BootstrapLookUp(
-    const std::string& service_name) {
-  base::mac::ScopedMachSendRight send(
-      BootstrapCheckInOrLookUp<BootstrapLookUpTraits>(service_name));
-
-  // It’s possible to race the bootstrap server when the receive right
-  // corresponding to the looked-up send right is destroyed immediately before
-  // the bootstrap_look_up() call. If the bootstrap server believes that
-  // |service_name| is still registered before processing the port-destroyed
-  // notification sent to it by the kernel, it will respond to a
-  // bootstrap_look_up() request with a send right that has become a dead name,
-  // which will be returned to the bootstrap_look_up() caller, translated into
-  // the caller’s IPC port name space, as the special MACH_PORT_DEAD port name.
-  // Check for that and return MACH_PORT_NULL in its place, as though the
-  // bootstrap server had fully processed the port-destroyed notification before
-  // responding to bootstrap_look_up().
-  if (send.get() == MACH_PORT_DEAD) {
-    LOG(ERROR) << "bootstrap_look_up " << service_name << ": service is dead";
-    send.reset();
-  }
-
-  return send;
-}
-
-base::mac::ScopedMachSendRight SystemCrashReporterHandler() {
-  return BootstrapLookUp("com.apple.ReportCrash");
-}
-
 }  // namespace crashpad
diff --git a/util/mach/mach_extensions.h b/util/mach/mach_extensions.h
index 25e401c..9247ce9 100644
--- a/util/mach/mach_extensions.h
+++ b/util/mach/mach_extensions.h
@@ -17,10 +17,6 @@
 
 #include <mach/mach.h>
 
-#include <string>
-
-#include "base/mac/scoped_mach_port.h"
-
 namespace crashpad {
 
 //! \brief `MACH_PORT_NULL` with the correct type for a Mach port,
@@ -119,43 +115,6 @@
 //! support is present.
 exception_mask_t ExcMaskValid();
 
-//! \brief Makes a `boostrap_check_in()` call to the process’ bootstrap server.
-//!
-//! This function is provided to make it easier to call `bootstrap_check_in()`
-//! while avoiding accidental leaks of the returned receive right.
-//!
-//! \param[in] service_name The service name to check in.
-//!
-//! \return On success, a receive right to the service port. On failure,
-//!     `MACH_PORT_NULL` with a message logged.
-base::mac::ScopedMachReceiveRight BootstrapCheckIn(
-    const std::string& service_name);
-
-//! \brief Makes a `boostrap_look_up()` call to the process’ bootstrap server.
-//!
-//! This function is provided to make it easier to call `bootstrap_look_up()`
-//! while avoiding accidental leaks of the returned send right.
-//!
-//! \param[in] service_name The service name to look up.
-//!
-//! \return On success, a send right to the service port. On failure,
-//!     `MACH_PORT_NULL` with a message logged.
-base::mac::ScopedMachSendRight BootstrapLookUp(const std::string& service_name);
-
-//! \brief Obtains the system’s default Mach exception handler for crash-type
-//!     exceptions.
-//!
-//! This is obtained by looking up `"com.apple.ReportCrash"` with the bootstrap
-//! server. The service name comes from the first launch agent loaded by
-//! `launchd` with a `MachServices` entry having `ExceptionServer` set. This
-//! launch agent is normally loaded from
-//! `/System/Library/LaunchAgents/com.apple.ReportCrash.plist`.
-//!
-//! \return On success, a send right to an `exception_handler_t` corresponding
-//!     to the system’s default crash reporter. On failure, `MACH_PORT_NULL`,
-//!     with a message logged.
-base::mac::ScopedMachSendRight SystemCrashReporterHandler();
-
 }  // namespace crashpad
 
 #endif  // CRASHPAD_UTIL_MACH_MACH_EXTENSIONS_H_
diff --git a/util/mach/mach_extensions_test.cc b/util/mach/mach_extensions_test.cc
index 1c5b368..9fd4940 100644
--- a/util/mach/mach_extensions_test.cc
+++ b/util/mach/mach_extensions_test.cc
@@ -15,10 +15,10 @@
 #include "util/mach/mach_extensions.h"
 
 #include "base/mac/scoped_mach_port.h"
+#include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/mac/mach_errors.h"
 #include "util/mac/mac_util.h"
-#include "util/misc/random_string.h"
 
 namespace crashpad {
 namespace test {
@@ -80,6 +80,11 @@
   EXPECT_FALSE(exc_mask_all & EXC_MASK_CRASH);
   EXPECT_FALSE(exc_mask_all & EXC_MASK_CORPSE_NOTIFY);
 
+#if defined(OS_IOS)
+  // Assume at least iOS 7 (≅ OS X 10.9).
+  EXPECT_TRUE(exc_mask_all & EXC_MASK_RESOURCE);
+  EXPECT_TRUE(exc_mask_all & EXC_MASK_GUARD);
+#else  // OS_IOS
   const int mac_os_x_minor_version = MacOSXMinorVersion();
   if (mac_os_x_minor_version >= 8) {
     EXPECT_TRUE(exc_mask_all & EXC_MASK_RESOURCE);
@@ -92,6 +97,7 @@
   } else {
     EXPECT_FALSE(exc_mask_all & EXC_MASK_GUARD);
   }
+#endif  // OS_IOS
 
   // Bit 0 should not be set.
   EXPECT_FALSE(ExcMaskAll() & 1);
@@ -106,6 +112,12 @@
 
   EXPECT_TRUE(exc_mask_valid & EXC_MASK_CRASH);
 
+#if defined(OS_IOS)
+  // Assume at least iOS 9 (≅ OS X 10.11).
+  EXPECT_TRUE(exc_mask_valid & EXC_MASK_RESOURCE);
+  EXPECT_TRUE(exc_mask_valid & EXC_MASK_GUARD);
+  EXPECT_TRUE(exc_mask_valid & EXC_MASK_CORPSE_NOTIFY);
+#else  // OS_IOS
   const int mac_os_x_minor_version = MacOSXMinorVersion();
   if (mac_os_x_minor_version >= 8) {
     EXPECT_TRUE(exc_mask_valid & EXC_MASK_RESOURCE);
@@ -124,6 +136,7 @@
   } else {
     EXPECT_FALSE(exc_mask_valid & EXC_MASK_CORPSE_NOTIFY);
   }
+#endif  // OS_IOS
 
   // Bit 0 should not be set.
   EXPECT_FALSE(ExcMaskValid() & 1);
@@ -132,48 +145,6 @@
   EXPECT_TRUE(ExcMaskValid() & ~ExcMaskAll());
 }
 
-TEST(MachExtensions, BootstrapCheckInAndLookUp) {
-  // This should always exist.
-  base::mac::ScopedMachSendRight
-      report_crash(BootstrapLookUp("com.apple.ReportCrash"));
-  EXPECT_NE(report_crash, kMachPortNull);
-
-  std::string service_name = "org.chromium.crashpad.test.bootstrap_check_in.";
-  service_name.append(RandomString());
-
-  {
-    // The new service hasn’t checked in yet, so this should fail.
-    base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name));
-    EXPECT_EQ(send, kMachPortNull);
-
-    // Check it in.
-    base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name));
-    EXPECT_NE(receive, kMachPortNull);
-
-    // Now it should be possible to look up the new service.
-    send = BootstrapLookUp(service_name);
-    EXPECT_NE(send, kMachPortNull);
-
-    // It shouldn’t be possible to check the service in while it’s active.
-    base::mac::ScopedMachReceiveRight receive_2(BootstrapCheckIn(service_name));
-    EXPECT_EQ(receive_2, kMachPortNull);
-  }
-
-  // The new service should be gone now.
-  base::mac::ScopedMachSendRight send(BootstrapLookUp(service_name));
-  EXPECT_EQ(send, kMachPortNull);
-
-  // It should be possible to check it in again.
-  base::mac::ScopedMachReceiveRight receive(BootstrapCheckIn(service_name));
-  EXPECT_NE(receive, kMachPortNull);
-}
-
-TEST(MachExtensions, SystemCrashReporterHandler) {
-  base::mac::ScopedMachSendRight
-      system_crash_reporter_handler(SystemCrashReporterHandler());
-  EXPECT_TRUE(system_crash_reporter_handler.is_valid());
-}
-
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/util/mach/mach_message.cc b/util/mach/mach_message.cc
index 5294c03..72d71e3 100644
--- a/util/mach/mach_message.cc
+++ b/util/mach/mach_message.cc
@@ -15,7 +15,6 @@
 #include "util/mach/mach_message.h"
 
 #include <AvailabilityMacros.h>
-#include <bsm/libbsm.h>
 
 #include <limits>
 
@@ -24,6 +23,10 @@
 #include "util/misc/clock.h"
 #include "util/misc/implicit_cast.h"
 
+#if !defined(OS_IOS)
+#include <bsm/libbsm.h>
+#endif  // !OS_IOS
+
 namespace crashpad {
 
 namespace {
@@ -217,38 +220,6 @@
   return reinterpret_cast<const mach_msg_trailer_t*>(trailer_address);
 }
 
-pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) {
-  if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) {
-    LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type;
-    return -1;
-  }
-  if (trailer->msgh_trailer_size <
-      REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) {
-    LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size;
-    return -1;
-  }
-
-  const mach_msg_audit_trailer_t* audit_trailer =
-      reinterpret_cast<const mach_msg_audit_trailer_t*>(trailer);
-
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
-  pid_t audit_pid;
-  audit_token_to_au32(audit_trailer->msgh_audit,
-                      nullptr,
-                      nullptr,
-                      nullptr,
-                      nullptr,
-                      nullptr,
-                      &audit_pid,
-                      nullptr,
-                      nullptr);
-#else
-  pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit);
-#endif
-
-  return audit_pid;
-}
-
 bool MachMessageDestroyReceivedPort(mach_port_t port,
                                     mach_msg_type_name_t port_right_type) {
   // This implements a subset of 10.10.5
@@ -282,4 +253,40 @@
   }
 }
 
+#if !defined(OS_IOS)
+
+pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) {
+  if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) {
+    LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type;
+    return -1;
+  }
+  if (trailer->msgh_trailer_size <
+      REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) {
+    LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size;
+    return -1;
+  }
+
+  const mach_msg_audit_trailer_t* audit_trailer =
+      reinterpret_cast<const mach_msg_audit_trailer_t*>(trailer);
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
+  pid_t audit_pid;
+  audit_token_to_au32(audit_trailer->msgh_audit,
+                      nullptr,
+                      nullptr,
+                      nullptr,
+                      nullptr,
+                      nullptr,
+                      &audit_pid,
+                      nullptr,
+                      nullptr);
+#else
+  pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit);
+#endif
+
+  return audit_pid;
+}
+
+#endif  // !OS_IOS
+
 }  // namespace crashpad
diff --git a/util/mach/mach_message.h b/util/mach/mach_message.h
index e0fc6f6..fd8d3d5 100644
--- a/util/mach/mach_message.h
+++ b/util/mach/mach_message.h
@@ -19,6 +19,8 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include "build/build_config.h"
+
 namespace crashpad {
 
 //! \brief A Mach message option specifying that an audit trailer should be
@@ -166,6 +168,23 @@
 const mach_msg_trailer_t* MachMessageTrailerFromHeader(
     const mach_msg_header_t* header);
 
+//! \brief Destroys or deallocates a Mach port received in a Mach message.
+//!
+//! This function disposes of port rights received in a Mach message. Receive
+//! rights will be destroyed with `mach_port_mod_refs()`. Send and send-once
+//! rights will be deallocated with `mach_port_deallocate()`.
+//!
+//! \param[in] port The port to destroy or deallocate.
+//! \param[in] port_right_type The right type held for \a port:
+//!     `MACH_MSG_TYPE_PORT_RECEIVE`, `MACH_MSG_TYPE_PORT_SEND`, or
+//!     `MACH_MSG_TYPE_PORT_SEND_ONCE`.
+//!
+//! \return `true` on success, or `false` on failure with a message logged.
+bool MachMessageDestroyReceivedPort(mach_port_t port,
+                                    mach_msg_type_name_t port_right_type);
+
+#if !defined(OS_IOS) || DOXYGEN
+
 //! \brief Returns the process ID of a Mach message’s sender from its audit
 //!     trailer.
 //!
@@ -182,20 +201,7 @@
 //!     audit information.
 pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer);
 
-//! \brief Destroys or deallocates a Mach port received in a Mach message.
-//!
-//! This function disposes of port rights received in a Mach message. Receive
-//! rights will be destroyed with `mach_port_mod_refs()`. Send and send-once
-//! rights will be deallocated with `mach_port_deallocate()`.
-//!
-//! \param[in] port The port to destroy or deallocate.
-//! \param[in] port_right_type The right type held for \a port:
-//!     `MACH_MSG_TYPE_PORT_RECEIVE`, `MACH_MSG_TYPE_PORT_SEND`, or
-//!     `MACH_MSG_TYPE_PORT_SEND_ONCE`.
-//!
-//! \return `true` on success, or `false` on failure with a message logged.
-bool MachMessageDestroyReceivedPort(mach_port_t port,
-                                    mach_msg_type_name_t port_right_type);
+#endif  // !OS_IOS
 
 }  // namespace crashpad
 
diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc
index 16ba8f2..e3a3f94 100644
--- a/util/mach/mach_message_server_test.cc
+++ b/util/mach/mach_message_server_test.cc
@@ -23,6 +23,7 @@
 
 #include "base/mac/scoped_mach_port.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "test/mac/mach_errors.h"
 #include "test/mac/mach_multiprocess.h"
@@ -281,7 +282,7 @@
   std::set<mach_msg_id_t> MachMessageServerRequestIDs() override {
     static constexpr mach_msg_id_t request_ids[] = {kRequestMessageID};
     return std::set<mach_msg_id_t>(&request_ids[0],
-                                   &request_ids[arraysize(request_ids)]);
+                                   &request_ids[base::size(request_ids)]);
   }
 
   mach_msg_size_t MachMessageServerRequestSize() override {
diff --git a/util/mach/mach_message_test.cc b/util/mach/mach_message_test.cc
index d88ca85..729e3a3 100644
--- a/util/mach/mach_message_test.cc
+++ b/util/mach/mach_message_test.cc
@@ -17,6 +17,7 @@
 #include <unistd.h>
 
 #include "base/mac/scoped_mach_port.h"
+#include "base/macros.h"
 #include "gtest/gtest.h"
 #include "test/mac/mach_errors.h"
 #include "util/mach/mach_extensions.h"
@@ -108,46 +109,6 @@
   EXPECT_EQ(MachMessageTrailerFromHeader(&test.receive), &test.receive.trailer);
 }
 
-TEST(MachMessage, AuditPIDFromMachMessageTrailer) {
-  base::mac::ScopedMachReceiveRight port(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
-  ASSERT_NE(port, kMachPortNull);
-
-  mach_msg_empty_send_t send = {};
-  send.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0);
-  send.header.msgh_size = sizeof(send);
-  send.header.msgh_remote_port = port.get();
-  mach_msg_return_t mr =
-      MachMessageWithDeadline(&send.header,
-                              MACH_SEND_MSG,
-                              0,
-                              MACH_PORT_NULL,
-                              kMachMessageDeadlineNonblocking,
-                              MACH_PORT_NULL,
-                              false);
-  ASSERT_EQ(mr, MACH_MSG_SUCCESS)
-      << MachErrorMessage(mr, "MachMessageWithDeadline send");
-
-  struct EmptyReceiveMessageWithAuditTrailer : public mach_msg_empty_send_t {
-    union {
-      mach_msg_trailer_t trailer;
-      mach_msg_audit_trailer_t audit_trailer;
-    };
-  };
-
-  EmptyReceiveMessageWithAuditTrailer receive;
-  mr = MachMessageWithDeadline(&receive.header,
-                               MACH_RCV_MSG | kMachMessageReceiveAuditTrailer,
-                               sizeof(receive),
-                               port.get(),
-                               kMachMessageDeadlineNonblocking,
-                               MACH_PORT_NULL,
-                               false);
-  ASSERT_EQ(mr, MACH_MSG_SUCCESS)
-      << MachErrorMessage(mr, "MachMessageWithDeadline receive");
-
-  EXPECT_EQ(AuditPIDFromMachMessageTrailer(&receive.trailer), getpid());
-}
-
 TEST(MachMessage, MachMessageDestroyReceivedPort) {
   mach_port_t port = NewMachPort(MACH_PORT_RIGHT_RECEIVE);
   ASSERT_NE(port, kMachPortNull);
@@ -197,6 +158,50 @@
   EXPECT_TRUE(MachMessageDestroyReceivedPort(port, MACH_MSG_TYPE_PORT_SEND));
 }
 
+#if !defined(OS_IOS)
+
+TEST(MachMessage, AuditPIDFromMachMessageTrailer) {
+  base::mac::ScopedMachReceiveRight port(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
+  ASSERT_NE(port, kMachPortNull);
+
+  mach_msg_empty_send_t send = {};
+  send.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0);
+  send.header.msgh_size = sizeof(send);
+  send.header.msgh_remote_port = port.get();
+  mach_msg_return_t mr =
+      MachMessageWithDeadline(&send.header,
+                              MACH_SEND_MSG,
+                              0,
+                              MACH_PORT_NULL,
+                              kMachMessageDeadlineNonblocking,
+                              MACH_PORT_NULL,
+                              false);
+  ASSERT_EQ(mr, MACH_MSG_SUCCESS)
+      << MachErrorMessage(mr, "MachMessageWithDeadline send");
+
+  struct EmptyReceiveMessageWithAuditTrailer : public mach_msg_empty_send_t {
+    union {
+      mach_msg_trailer_t trailer;
+      mach_msg_audit_trailer_t audit_trailer;
+    };
+  };
+
+  EmptyReceiveMessageWithAuditTrailer receive;
+  mr = MachMessageWithDeadline(&receive.header,
+                               MACH_RCV_MSG | kMachMessageReceiveAuditTrailer,
+                               sizeof(receive),
+                               port.get(),
+                               kMachMessageDeadlineNonblocking,
+                               MACH_PORT_NULL,
+                               false);
+  ASSERT_EQ(mr, MACH_MSG_SUCCESS)
+      << MachErrorMessage(mr, "MachMessageWithDeadline receive");
+
+  EXPECT_EQ(AuditPIDFromMachMessageTrailer(&receive.trailer), getpid());
+}
+
+#endif  // !OS_IOS
+
 }  // namespace
 }  // namespace test
 }  // namespace crashpad
diff --git a/util/mach/mig.py b/util/mach/mig.py
index 9833c8c..fa35e00 100755
--- a/util/mach/mig.py
+++ b/util/mach/mig.py
@@ -1,7 +1,6 @@
 #!/usr/bin/env python
-# coding: utf-8
 
-# Copyright 2014 The Crashpad Authors. All rights reserved.
+# Copyright 2019 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.
@@ -15,143 +14,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import argparse
-import os
-import re
-import subprocess
 import sys
 
-def FixUserImplementation(implementation):
-  """Rewrites a MIG-generated user implementation (.c) file.
+import mig_fix
+import mig_gen
 
-  Rewrites the file at |implementation| by adding “__attribute__((unused))” to
-  the definition of any structure typedefed as “__Reply” by searching for the
-  pattern unique to those structure definitions. These structures are in fact
-  unused in the user implementation file, and this will trigger a
-  -Wunused-local-typedefs warning in gcc unless removed or marked with the
-  “unused” attribute.
-  """
-
-  file = open(implementation, 'r+')
-  contents = file.read()
-
-  pattern = re.compile('^(\t} __Reply);$', re.MULTILINE)
-  contents = pattern.sub(r'\1 __attribute__((unused));', contents)
-
-  file.seek(0)
-  file.truncate()
-  file.write(contents)
-  file.close()
-
-def FixServerImplementation(implementation):
-  """Rewrites a MIG-generated server implementation (.c) file.
-
-  Rewrites the file at |implementation| by replacing “mig_internal” with
-  “mig_external” on functions that begin with “__MIG_check__”. This makes these
-  functions available to other callers outside this file from a linkage
-  perspective. It then returns, as a list of lines, declarations that can be
-  added to a header file, so that other files that include that header file will
-  have access to these declarations from a compilation perspective.
-  """
-
-  file = open(implementation, 'r+')
-  contents = file.read()
-
-  # Find interesting declarations.
-  declaration_pattern = \
-      re.compile('^mig_internal (kern_return_t __MIG_check__.*)$',
-                 re.MULTILINE)
-  declarations = declaration_pattern.findall(contents)
-
-  # Remove “__attribute__((__unused__))” from the declarations, and call them
-  # “mig_external” or “extern” depending on whether “mig_external” is defined.
-  attribute_pattern = re.compile(r'__attribute__\(\(__unused__\)\) ')
-  declarations = ['#ifdef mig_external\nmig_external\n#else\nextern\n#endif\n' +
-                  attribute_pattern.sub('', x) +
-                  ';\n' for x in declarations]
-
-  # Rewrite the declarations in this file as “mig_external”.
-  contents = declaration_pattern.sub(r'mig_external \1', contents);
-
-  # Crashpad never implements the mach_msg_server() MIG callouts. To avoid
-  # needing to provide stub implementations, set KERN_FAILURE as the RetCode
-  # and abort().
-  routine_callout_pattern = re.compile(
-      r'OutP->RetCode = (([a-zA-Z0-9_]+)\(.+\));')
-  routine_callouts = routine_callout_pattern.findall(contents)
-  for routine in routine_callouts:
-    contents = contents.replace(routine[0], 'KERN_FAILURE; abort()')
-
-  # Include the header for abort().
-  contents = '#include <stdlib.h>\n' + contents
-
-  file.seek(0)
-  file.truncate()
-  file.write(contents)
-  file.close()
-  return declarations
-
-def FixHeader(header, declarations=[]):
-  """Rewrites a MIG-generated header (.h) file.
-
-  Rewrites the file at |header| by placing it inside an “extern "C"” block, so
-  that it declares things properly when included by a C++ compilation unit.
-  |declarations| can be a list of additional declarations to place inside the
-  “extern "C"” block after the original contents of |header|.
-  """
-
-  file = open(header, 'r+')
-  contents = file.read()
-  declarations_text = ''.join(declarations)
-  contents = '''\
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-%s
-%s
-#ifdef __cplusplus
-}
-#endif
-''' % (contents, declarations_text)
-  file.seek(0)
-  file.truncate()
-  file.write(contents)
-  file.close()
 
 def main(args):
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--developer-dir', help='Path to Xcode')
-  parser.add_argument('--sdk', help='Path to SDK')
-  parser.add_argument('--include',
-                      default=[],
-                      action='append',
-                      help='Additional include directory')
-  parser.add_argument('defs')
-  parser.add_argument('user_c')
-  parser.add_argument('server_c')
-  parser.add_argument('user_h')
-  parser.add_argument('server_h')
-  parsed = parser.parse_args(args)
+    parsed = mig_gen.parse_args(args)
 
-  command = ['mig',
-             '-user', parsed.user_c,
-             '-server', parsed.server_c,
-             '-header', parsed.user_h,
-             '-sheader', parsed.server_h,
-            ]
-  if parsed.developer_dir is not None:
-    os.environ['DEVELOPER_DIR'] = parsed.developer_dir
-  if parsed.sdk is not None:
-    command.extend(['-isysroot', parsed.sdk])
-  for include in parsed.include:
-    command.extend(['-I' + include])
-  command.append(parsed.defs)
-  subprocess.check_call(command)
-  FixUserImplementation(parsed.user_c)
-  server_declarations = FixServerImplementation(parsed.server_c)
-  FixHeader(parsed.user_h)
-  FixHeader(parsed.server_h, server_declarations)
+    interface = mig_gen.MigInterface(parsed.user_c, parsed.server_c,
+                                     parsed.user_h, parsed.server_h)
+    mig_gen.generate_interface(parsed.defs, interface, parsed.include,
+                               parsed.sdk, parsed.clang_path, parsed.mig_path,
+                               parsed.migcom_path, parsed.arch)
+    mig_fix.fix_interface(interface)
+
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/util/mach/mig_fix.py b/util/mach/mig_fix.py
new file mode 100755
index 0000000..037746f
--- /dev/null
+++ b/util/mach/mig_fix.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# Copyright 2019 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.
+
+import argparse
+import os
+import re
+import sys
+
+from mig_gen import MigInterface
+
+
+def _fix_user_implementation(implementation, fixed_implementation, header,
+                             fixed_header):
+    """Rewrites a MIG-generated user implementation (.c) file.
+
+    Rewrites the file at |implementation| by adding “__attribute__((unused))” to
+    the definition of any structure typedefed as “__Reply” by searching for the
+    pattern unique to those structure definitions. These structures are in fact
+    unused in the user implementation file, and this will trigger a
+    -Wunused-local-typedefs warning in gcc unless removed or marked with the
+    “unused” attribute. Also changes header references to point to the new
+    header filename, if changed.
+
+    If |fixed_implementation| is None, overwrites the original; otherwise, puts
+    the result in the file at |fixed_implementation|.
+    """
+
+    file = open(implementation, 'r+' if fixed_implementation is None else 'r')
+    contents = file.read()
+
+    pattern = re.compile('^(\t} __Reply);$', re.MULTILINE)
+    contents = pattern.sub(r'\1 __attribute__((unused));', contents)
+
+    if fixed_header is not None:
+        contents = contents.replace(
+            '#include "%s"' % os.path.basename(header),
+            '#include "%s"' % os.path.basename(fixed_header))
+
+    if fixed_implementation is None:
+        file.seek(0)
+        file.truncate()
+    else:
+        file.close()
+        file = open(fixed_implementation, 'w')
+    file.write(contents)
+    file.close()
+
+
+def _fix_server_implementation(implementation, fixed_implementation, header,
+                               fixed_header):
+    """Rewrites a MIG-generated server implementation (.c) file.
+
+    Rewrites the file at |implementation| by replacing “mig_internal” with
+    “mig_external” on functions that begin with “__MIG_check__”. This makes
+    these functions available to other callers outside this file from a linkage
+    perspective. It then returns, as a list of lines, declarations that can be
+    added to a header file, so that other files that include that header file
+    will have access to these declarations from a compilation perspective. Also
+    changes header references to point to the new header filename, if changed.
+
+    If |fixed_implementation| is None or not provided, overwrites the original;
+    otherwise, puts the result in the file at |fixed_implementation|.
+    """
+
+    file = open(implementation, 'r+' if fixed_implementation is None else 'r')
+    contents = file.read()
+
+    # Find interesting declarations.
+    declaration_pattern = re.compile(
+        '^mig_internal (kern_return_t __MIG_check__.*)$', re.MULTILINE)
+    declarations = declaration_pattern.findall(contents)
+
+    # Remove “__attribute__((__unused__))” from the declarations, and call them
+    # “mig_external” or “extern” depending on whether “mig_external” is defined.
+    attribute_pattern = re.compile(r'__attribute__\(\(__unused__\)\) ')
+    declarations = [
+        '''\
+#ifdef mig_external
+mig_external
+#else
+extern
+#endif
+''' + attribute_pattern.sub('', x) + ';\n' for x in declarations
+    ]
+
+    # Rewrite the declarations in this file as “mig_external”.
+    contents = declaration_pattern.sub(r'mig_external \1', contents)
+
+    # Crashpad never implements the mach_msg_server() MIG callouts. To avoid
+    # needing to provide stub implementations, set KERN_FAILURE as the RetCode
+    # and abort().
+    routine_callout_pattern = re.compile(
+        r'OutP->RetCode = (([a-zA-Z0-9_]+)\(.+\));')
+    routine_callouts = routine_callout_pattern.findall(contents)
+    for routine in routine_callouts:
+        contents = contents.replace(routine[0], 'KERN_FAILURE; abort()')
+
+    # Include the header for abort().
+    contents = '#include <stdlib.h>\n' + contents
+
+    if fixed_header is not None:
+        contents = contents.replace(
+            '#include "%s"' % os.path.basename(header),
+            '#include "%s"' % os.path.basename(fixed_header))
+
+    if fixed_implementation is None:
+        file.seek(0)
+        file.truncate()
+    else:
+        file.close()
+        file = open(fixed_implementation, 'w')
+    file.write(contents)
+    file.close()
+    return declarations
+
+
+def _fix_header(header, fixed_header, declarations=[]):
+    """Rewrites a MIG-generated header (.h) file.
+
+    Rewrites the file at |header| by placing it inside an “extern "C"” block, so
+    that it declares things properly when included by a C++ compilation unit.
+    |declarations| can be a list of additional declarations to place inside the
+    “extern "C"” block after the original contents of |header|.
+
+    If |fixed_header| is None or not provided, overwrites the original;
+    otherwise, puts the result in the file at |fixed_header|.
+    """
+
+    file = open(header, 'r+' if fixed_header is None else 'r')
+    contents = file.read()
+    declarations_text = ''.join(declarations)
+    contents = '''\
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+%s
+%s
+#ifdef __cplusplus
+}
+#endif
+''' % (contents, declarations_text)
+
+    if fixed_header is None:
+        file.seek(0)
+        file.truncate()
+    else:
+        file.close()
+        file = open(fixed_header, 'w')
+    file.write(contents)
+    file.close()
+
+
+def fix_interface(interface, fixed_interface=None):
+    if fixed_interface is None:
+        fixed_interface = MigInterface(None, None, None, None)
+
+    _fix_user_implementation(interface.user_c, fixed_interface.user_c,
+                             interface.user_h, fixed_interface.user_h)
+    server_declarations = _fix_server_implementation(interface.server_c,
+                                                     fixed_interface.server_c,
+                                                     interface.server_h,
+                                                     fixed_interface.server_h)
+    _fix_header(interface.user_h, fixed_interface.user_h)
+    _fix_header(interface.server_h, fixed_interface.server_h,
+                server_declarations)
+
+
+def main(args):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('user_c')
+    parser.add_argument('--fixed_user_c', default=None)
+    parser.add_argument('server_c')
+    parser.add_argument('--fixed_server_c', default=None)
+    parser.add_argument('user_h')
+    parser.add_argument('--fixed_user_h', default=None)
+    parser.add_argument('server_h')
+    parser.add_argument('--fixed_server_h', default=None)
+    parsed = parser.parse_args(args)
+
+    interface = MigInterface(parsed.user_c, parsed.server_c, parsed.user_h,
+                             parsed.server_h)
+    fixed_interface = MigInterface(parsed.fixed_user_c, parsed.fixed_server_c,
+                                   parsed.fixed_user_h, parsed.fixed_server_h)
+    fix_interface(interface, fixed_interface)
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/util/mach/mig_gen.py b/util/mach/mig_gen.py
new file mode 100755
index 0000000..dcbf829
--- /dev/null
+++ b/util/mach/mig_gen.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+
+# Copyright 2019 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.
+
+import argparse
+import collections
+import os
+import subprocess
+import sys
+
+MigInterface = collections.namedtuple(
+    'MigInterface', ['user_c', 'server_c', 'user_h', 'server_h'])
+
+
+def generate_interface(defs,
+                       interface,
+                       includes=[],
+                       sdk=None,
+                       clang_path=None,
+                       mig_path=None,
+                       migcom_path=None,
+                       arch=None):
+    if mig_path is None:
+        mig_path = 'mig'
+
+    # yapf: disable
+    command = [
+        mig_path,
+        '-user', interface.user_c,
+        '-server', interface.server_c,
+        '-header', interface.user_h,
+        '-sheader', interface.server_h,
+    ]
+    # yapf: enable
+
+    if clang_path is not None:
+        os.environ['MIGCC'] = clang_path
+    if migcom_path is not None:
+        os.environ['MIGCOM'] = migcom_path
+    if arch is not None:
+        command.extend(['-arch', arch])
+    if sdk is not None:
+        command.extend(['-isysroot', sdk])
+    for include in includes:
+        command.extend(['-I' + include])
+    command.append(defs)
+    subprocess.check_call(command)
+
+
+def parse_args(args):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--clang-path', help='Path to Clang')
+    parser.add_argument('--mig-path', help='Path to mig')
+    parser.add_argument('--migcom-path', help='Path to migcom')
+    parser.add_argument('--arch', help='Target architecture')
+    parser.add_argument('--sdk', help='Path to SDK')
+    parser.add_argument('--include',
+                        default=[],
+                        action='append',
+                        help='Additional include directory')
+    parser.add_argument('defs')
+    parser.add_argument('user_c')
+    parser.add_argument('server_c')
+    parser.add_argument('user_h')
+    parser.add_argument('server_h')
+    return parser.parse_args(args)
+
+
+def main(args):
+    parsed = parse_args(args)
+    interface = MigInterface(parsed.user_c, parsed.server_c, parsed.user_h,
+                             parsed.server_h)
+    generate_interface(parsed.defs, interface, parsed.include, parsed.sdk,
+                       parsed.clang_path, parsed.mig_path, parsed.migcom_path,
+                       parsed.arch)
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/util/mach/notify_server.cc b/util/mach/notify_server.cc
index 711529b..b9da795 100644
--- a/util/mach/notify_server.cc
+++ b/util/mach/notify_server.cc
@@ -15,6 +15,7 @@
 #include "util/mach/notify_server.h"
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "util/mach/mach_message.h"
 #include "util/mach/notifyServer.h"
 
@@ -227,7 +228,7 @@
       MACH_NOTIFY_DEAD_NAME,
   };
   return std::set<mach_msg_id_t>(&request_ids[0],
-                                 &request_ids[arraysize(request_ids)]);
+                                 &request_ids[base::size(request_ids)]);
 }
 
 mach_msg_size_t NotifyServer::MachMessageServerRequestSize() {
diff --git a/util/mach/notify_server_test.cc b/util/mach/notify_server_test.cc
index b5e152d..d334e57 100644
--- a/util/mach/notify_server_test.cc
+++ b/util/mach/notify_server_test.cc
@@ -31,6 +31,7 @@
 namespace {
 
 using testing::AllOf;
+using testing::DoAll;
 using testing::Eq;
 using testing::Invoke;
 using testing::Pointee;
diff --git a/util/mach/symbolic_constants_mach.cc b/util/mach/symbolic_constants_mach.cc
index 98a2d20..71fe115 100644
--- a/util/mach/symbolic_constants_mach.cc
+++ b/util/mach/symbolic_constants_mach.cc
@@ -17,7 +17,7 @@
 #include <string.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "util/mach/exception_behaviors.h"
 #include "util/mach/mach_extensions.h"
@@ -45,7 +45,7 @@
     "GUARD",
     "CORPSE_NOTIFY",
 };
-static_assert(arraysize(kExceptionNames) == EXC_TYPES_COUNT,
+static_assert(base::size(kExceptionNames) == EXC_TYPES_COUNT,
               "kExceptionNames length");
 
 constexpr char kExcPrefix[] = "EXC_";
@@ -170,8 +170,7 @@
         {"_STATE32", "32"},
         {"_STATE64", "64"},
     };
-    for (size_t suffix_index = 0;
-         suffix_index < arraysize(kStateSuffixes);
+    for (size_t suffix_index = 0; suffix_index < base::size(kStateSuffixes);
          ++suffix_index) {
       const char* suffix = kStateSuffixes[suffix_index].orig;
       size_t suffix_len = strlen(suffix);
@@ -195,7 +194,7 @@
 std::string ExceptionToString(exception_type_t exception,
                               SymbolicConstantToStringOptions options) {
   const char* exception_name =
-      implicit_cast<size_t>(exception) < arraysize(kExceptionNames)
+      implicit_cast<size_t>(exception) < base::size(kExceptionNames)
           ? kExceptionNames[exception]
           : nullptr;
   if (!exception_name) {
@@ -221,7 +220,7 @@
     base::StringPiece short_string =
         can_match_full ? string.substr(strlen(kExcPrefix)) : string;
     for (exception_type_t index = 0;
-         index < implicit_cast<exception_type_t>(arraysize(kExceptionNames));
+         index < implicit_cast<exception_type_t>(base::size(kExceptionNames));
          ++index) {
       const char* exception_name = kExceptionNames[index];
       if (!exception_name) {
@@ -251,8 +250,7 @@
   exception_mask_t local_exception_mask = exception_mask;
   std::string mask_string;
   bool has_forbidden_or = false;
-  for (size_t exception = 0;
-       exception < arraysize(kExceptionNames);
+  for (size_t exception = 0; exception < base::size(kExceptionNames);
        ++exception) {
     const char* exception_name = kExceptionNames[exception];
     exception_mask_t exception_mask_value = 1 << exception;
@@ -326,7 +324,7 @@
     base::StringPiece short_string =
         can_match_full ? string.substr(strlen(kExcMaskPrefix)) : string;
     for (exception_type_t index = 0;
-         index < implicit_cast<exception_type_t>(arraysize(kExceptionNames));
+         index < implicit_cast<exception_type_t>(base::size(kExceptionNames));
          ++index) {
       const char* exception_name = kExceptionNames[index];
       if (!exception_name) {
@@ -365,7 +363,7 @@
   const exception_behavior_t basic_behavior = ExceptionBehaviorBasic(behavior);
 
   const char* behavior_name =
-      implicit_cast<size_t>(basic_behavior) < arraysize(kBehaviorNames)
+      implicit_cast<size_t>(basic_behavior) < base::size(kBehaviorNames)
           ? kBehaviorNames[basic_behavior]
           : nullptr;
   if (!behavior_name) {
@@ -432,7 +430,8 @@
     base::StringPiece short_string =
         can_match_full ? sp.substr(strlen(kBehaviorPrefix)) : sp;
     for (exception_behavior_t index = 0;
-         index < implicit_cast<exception_behavior_t>(arraysize(kBehaviorNames));
+         index <
+         implicit_cast<exception_behavior_t>(base::size(kBehaviorNames));
          ++index) {
       const char* behavior_name = kBehaviorNames[index];
       if (!behavior_name) {
@@ -468,13 +467,13 @@
 std::string ThreadStateFlavorToString(thread_state_flavor_t flavor,
                                       SymbolicConstantToStringOptions options) {
   const char* flavor_name =
-      implicit_cast<size_t>(flavor) < arraysize(kFlavorNames)
+      implicit_cast<size_t>(flavor) < base::size(kFlavorNames)
           ? kFlavorNames[flavor]
           : nullptr;
 
   if (!flavor_name) {
     for (size_t generic_flavor_index = 0;
-         generic_flavor_index < arraysize(kGenericFlavorNames);
+         generic_flavor_index < base::size(kGenericFlavorNames);
          ++generic_flavor_index) {
       if (flavor == kGenericFlavorNames[generic_flavor_index].flavor) {
         flavor_name = kGenericFlavorNames[generic_flavor_index].name;
@@ -501,7 +500,7 @@
                                thread_state_flavor_t* flavor) {
   if ((options & kAllowFullName) || (options & kAllowShortName)) {
     for (thread_state_flavor_t index = 0;
-         index < implicit_cast<thread_state_flavor_t>(arraysize(kFlavorNames));
+         index < implicit_cast<thread_state_flavor_t>(base::size(kFlavorNames));
          ++index) {
       const char* flavor_name = kFlavorNames[index];
       if (!flavor_name) {
@@ -521,7 +520,7 @@
     }
 
     for (size_t generic_flavor_index = 0;
-         generic_flavor_index < arraysize(kGenericFlavorNames);
+         generic_flavor_index < base::size(kGenericFlavorNames);
          ++generic_flavor_index) {
       const char* flavor_name = kGenericFlavorNames[generic_flavor_index].name;
       thread_state_flavor_t flavor_number =
diff --git a/util/mach/symbolic_constants_mach_test.cc b/util/mach/symbolic_constants_mach_test.cc
index 3bf5abb..e58a9c5 100644
--- a/util/mach/symbolic_constants_mach_test.cc
+++ b/util/mach/symbolic_constants_mach_test.cc
@@ -18,14 +18,15 @@
 #include <string.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "util/mach/mach_extensions.h"
 #include "util/misc/implicit_cast.h"
 
-#define NUL_TEST_DATA(string) { string, arraysize(string) - 1 }
+#define NUL_TEST_DATA(string) \
+  { string, base::size(string) - 1 }
 
 namespace crashpad {
 namespace test {
@@ -159,7 +160,7 @@
 }
 
 TEST(SymbolicConstantsMach, ExceptionToString) {
-  for (size_t index = 0; index < arraysize(kExceptionTestData); ++index) {
+  for (size_t index = 0; index < base::size(kExceptionTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestExceptionToString(kExceptionTestData[index].exception,
                           kExceptionTestData[index].full_name,
@@ -187,12 +188,11 @@
 }
 
 TEST(SymbolicConstantsMach, StringToException) {
-  for (size_t option_index = 0;
-       option_index < arraysize(kNormalOptions);
+  for (size_t option_index = 0; option_index < base::size(kNormalOptions);
        ++option_index) {
     SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
     StringToSymbolicConstantOptions options = kNormalOptions[option_index];
-    for (size_t index = 0; index < arraysize(kExceptionTestData); ++index) {
+    for (size_t index = 0; index < base::size(kExceptionTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       exception_type_t exception = kExceptionTestData[index].exception;
       {
@@ -230,7 +230,7 @@
         "",
     };
 
-    for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNegativeTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       TestStringToException(kNegativeTestData[index], options, false, 0);
     }
@@ -251,7 +251,7 @@
         NUL_TEST_DATA("1\0002"),
     };
 
-    for (size_t index = 0; index < arraysize(kNULTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNULTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       base::StringPiece string(kNULTestData[index].string,
                                kNULTestData[index].length);
@@ -334,7 +334,7 @@
 }
 
 TEST(SymbolicConstantsMach, ExceptionMaskToString) {
-  for (size_t index = 0; index < arraysize(kExceptionMaskTestData); ++index) {
+  for (size_t index = 0; index < base::size(kExceptionMaskTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestExceptionMaskToString(kExceptionMaskTestData[index].exception_mask,
                               kExceptionMaskTestData[index].full_name,
@@ -389,12 +389,12 @@
       kAllowFullName | kAllowShortName | kAllowNumber | kAllowOr,
   };
 
-  for (size_t option_index = 0;
-       option_index < arraysize(kOptions);
+  for (size_t option_index = 0; option_index < base::size(kOptions);
        ++option_index) {
     SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
     StringToSymbolicConstantOptions options = kOptions[option_index];
-    for (size_t index = 0; index < arraysize(kExceptionMaskTestData); ++index) {
+    for (size_t index = 0; index < base::size(kExceptionMaskTestData);
+         ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       exception_mask_t exception_mask =
           kExceptionMaskTestData[index].exception_mask;
@@ -445,7 +445,7 @@
         "",
     };
 
-    for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNegativeTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       TestStringToExceptionMask(kNegativeTestData[index], options, false, 0);
     }
@@ -471,7 +471,7 @@
         NUL_TEST_DATA("ARITHMETIC|\0EMULATION"),
     };
 
-    for (size_t index = 0; index < arraysize(kNULTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNULTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       base::StringPiece string(kNULTestData[index].string,
                                kNULTestData[index].length);
@@ -506,7 +506,7 @@
        EXC_MASK_SYSCALL | 0x100},
     };
 
-  for (size_t index = 0; index < arraysize(kNonCanonicalTestData); ++index) {
+  for (size_t index = 0; index < base::size(kNonCanonicalTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestStringToExceptionMask(kNonCanonicalTestData[index].string,
                               kNonCanonicalTestData[index].options,
@@ -577,8 +577,7 @@
 }
 
 TEST(SymbolicConstantsMach, ExceptionBehaviorToString) {
-  for (size_t index = 0;
-       index < arraysize(kExceptionBehaviorTestData);
+  for (size_t index = 0; index < base::size(kExceptionBehaviorTestData);
        ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestExceptionBehaviorToString(kExceptionBehaviorTestData[index].behavior,
@@ -608,13 +607,11 @@
 }
 
 TEST(SymbolicConstantsMach, StringToExceptionBehavior) {
-  for (size_t option_index = 0;
-       option_index < arraysize(kNormalOptions);
+  for (size_t option_index = 0; option_index < base::size(kNormalOptions);
        ++option_index) {
     SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
     StringToSymbolicConstantOptions options = kNormalOptions[option_index];
-    for (size_t index = 0;
-         index < arraysize(kExceptionBehaviorTestData);
+    for (size_t index = 0; index < base::size(kExceptionBehaviorTestData);
          ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       exception_behavior_t behavior =
@@ -660,7 +657,7 @@
         "",
     };
 
-    for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNegativeTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       TestStringToExceptionBehavior(
           kNegativeTestData[index], options, false, 0);
@@ -686,7 +683,7 @@
         NUL_TEST_DATA("STATE_IDENTITY|\0MACH"),
     };
 
-    for (size_t index = 0; index < arraysize(kNULTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNULTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       base::StringPiece string(kNULTestData[index].string,
                                kNULTestData[index].length);
@@ -723,7 +720,7 @@
        implicit_cast<exception_behavior_t>(MACH_EXCEPTION_CODES | 0x2)},
   };
 
-  for (size_t index = 0; index < arraysize(kNonCanonicalTestData); ++index) {
+  for (size_t index = 0; index < base::size(kNonCanonicalTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestStringToExceptionBehavior(kNonCanonicalTestData[index].string,
                                   kNonCanonicalTestData[index].options,
@@ -840,8 +837,7 @@
 }
 
 TEST(SymbolicConstantsMach, ThreadStateFlavorToString) {
-  for (size_t index = 0;
-       index < arraysize(kThreadStateFlavorTestData);
+  for (size_t index = 0; index < base::size(kThreadStateFlavorTestData);
        ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestThreadStateFlavorToString(kThreadStateFlavorTestData[index].flavor,
@@ -883,13 +879,11 @@
 }
 
 TEST(SymbolicConstantsMach, StringToThreadStateFlavor) {
-  for (size_t option_index = 0;
-       option_index < arraysize(kNormalOptions);
+  for (size_t option_index = 0; option_index < base::size(kNormalOptions);
        ++option_index) {
     SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
     StringToSymbolicConstantOptions options = kNormalOptions[option_index];
-    for (size_t index = 0;
-         index < arraysize(kThreadStateFlavorTestData);
+    for (size_t index = 0; index < base::size(kThreadStateFlavorTestData);
          ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       thread_state_flavor_t flavor = kThreadStateFlavorTestData[index].flavor;
@@ -959,7 +953,7 @@
 #endif
     };
 
-    for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNegativeTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       TestStringToThreadStateFlavor(
           kNegativeTestData[index], options, false, 0);
@@ -1025,7 +1019,7 @@
 #endif
     };
 
-    for (size_t index = 0; index < arraysize(kNULTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNULTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       base::StringPiece string(kNULTestData[index].string,
                                kNULTestData[index].length);
diff --git a/util/mach/task_memory.cc b/util/mach/task_memory.cc
deleted file mode 100644
index 6168434..0000000
--- a/util/mach/task_memory.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2014 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.
-
-#include "util/mach/task_memory.h"
-
-#include <mach/mach_vm.h>
-#include <string.h>
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "base/mac/mach_logging.h"
-#include "base/strings/stringprintf.h"
-#include "util/stdlib/strnlen.h"
-
-namespace crashpad {
-
-TaskMemory::MappedMemory::~MappedMemory() {
-}
-
-bool TaskMemory::MappedMemory::ReadCString(
-    size_t offset, std::string* string) const {
-  if (offset >= user_size_) {
-    LOG(WARNING) << "offset out of range";
-    return false;
-  }
-
-  const char* string_base = reinterpret_cast<const char*>(data_) + offset;
-  size_t max_length = user_size_ - offset;
-  size_t string_length = strnlen(string_base, max_length);
-  if (string_length == max_length) {
-    LOG(WARNING) << "unterminated string";
-    return false;
-  }
-
-  string->assign(string_base, string_length);
-  return true;
-}
-
-TaskMemory::MappedMemory::MappedMemory(vm_address_t vm_address,
-                                       size_t vm_size,
-                                       size_t user_offset,
-                                       size_t user_size)
-    : vm_(vm_address, vm_size),
-      data_(reinterpret_cast<const void*>(vm_address + user_offset)),
-      user_size_(user_size) {
-  vm_address_t vm_end = vm_address + vm_size;
-  vm_address_t user_address = reinterpret_cast<vm_address_t>(data_);
-  vm_address_t user_end = user_address + user_size;
-  DCHECK_GE(user_address, vm_address);
-  DCHECK_LE(user_address, vm_end);
-  DCHECK_GE(user_end, vm_address);
-  DCHECK_LE(user_end, vm_end);
-}
-
-TaskMemory::TaskMemory(task_t task) : task_(task) {
-}
-
-bool TaskMemory::Read(mach_vm_address_t address, size_t size, void* buffer) {
-  std::unique_ptr<MappedMemory> memory = ReadMapped(address, size);
-  if (!memory) {
-    return false;
-  }
-
-  memcpy(buffer, memory->data(), size);
-  return true;
-}
-
-std::unique_ptr<TaskMemory::MappedMemory> TaskMemory::ReadMapped(
-    mach_vm_address_t address,
-    size_t size) {
-  if (size == 0) {
-    return std::unique_ptr<MappedMemory>(new MappedMemory(0, 0, 0, 0));
-  }
-
-  mach_vm_address_t region_address = mach_vm_trunc_page(address);
-  mach_vm_size_t region_size =
-      mach_vm_round_page(address - region_address + size);
-
-  vm_offset_t region;
-  mach_msg_type_number_t region_count;
-  kern_return_t kr =
-      mach_vm_read(task_, region_address, region_size, &region, &region_count);
-  if (kr != KERN_SUCCESS) {
-    MACH_LOG(WARNING, kr) << base::StringPrintf(
-        "mach_vm_read(0x%llx, 0x%llx)", region_address, region_size);
-    return std::unique_ptr<MappedMemory>();
-  }
-
-  DCHECK_EQ(region_count, region_size);
-  return std::unique_ptr<MappedMemory>(
-      new MappedMemory(region, region_size, address - region_address, size));
-}
-
-bool TaskMemory::ReadCString(mach_vm_address_t address, std::string* string) {
-  return ReadCStringInternal(address, false, 0, string);
-}
-
-bool TaskMemory::ReadCStringSizeLimited(mach_vm_address_t address,
-                                        mach_vm_size_t size,
-                                        std::string* string) {
-  return ReadCStringInternal(address, true, size, string);
-}
-
-bool TaskMemory::ReadCStringInternal(mach_vm_address_t address,
-                                     bool has_size,
-                                     mach_vm_size_t size,
-                                     std::string* string) {
-  if (has_size) {
-    if (size == 0)  {
-      string->clear();
-      return true;
-    }
-  } else {
-    size = PAGE_SIZE;
-  }
-
-  std::string local_string;
-  mach_vm_address_t read_address = address;
-  do {
-    mach_vm_size_t read_length =
-        std::min(size, PAGE_SIZE - (read_address % PAGE_SIZE));
-    std::unique_ptr<MappedMemory> read_region =
-        ReadMapped(read_address, read_length);
-    if (!read_region) {
-      return false;
-    }
-
-    const char* read_region_data =
-        reinterpret_cast<const char*>(read_region->data());
-    size_t read_region_data_length = strnlen(read_region_data, read_length);
-    local_string.append(read_region_data, read_region_data_length);
-    if (read_region_data_length < read_length) {
-      string->swap(local_string);
-      return true;
-    }
-
-    if (has_size) {
-      size -= read_length;
-    }
-    read_address = mach_vm_trunc_page(read_address + read_length);
-  } while ((!has_size || size > 0) && read_address > address);
-
-  LOG(WARNING) << base::StringPrintf("unterminated string at 0x%llx", address);
-  return false;
-}
-
-}  // namespace crashpad
diff --git a/util/mach/task_memory.h b/util/mach/task_memory.h
deleted file mode 100644
index c8caf85..0000000
--- a/util/mach/task_memory.h
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2014 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_UTIL_MACH_TASK_MEMORY_H_
-#define CRASHPAD_UTIL_MACH_TASK_MEMORY_H_
-
-#include <mach/mach.h>
-#include <sys/types.h>
-
-#include <memory>
-#include <string>
-
-#include "base/mac/scoped_mach_vm.h"
-#include "base/macros.h"
-
-namespace crashpad {
-
-//! \brief Accesses the memory of another Mach task.
-class TaskMemory {
- public:
-  //! \brief A memory region mapped from another Mach task.
-  //!
-  //! The mapping is maintained until this object is destroyed.
-  class MappedMemory {
-   public:
-    ~MappedMemory();
-
-    //! \brief Returns a pointer to the data requested by the user.
-    //!
-    //! This is the value of the \a vm_address + \a user_offset parameters
-    //! passed to the constructor, casted to `const void*`.
-    const void* data() const { return data_; }
-
-    //! \brief Reads a `NUL`-terminated C string from the mapped region.
-    //!
-    //! This method will read contiguous memory until a `NUL` terminator is
-    //! found.
-    //!
-    //! \param[in] offset The offset into data() of the string to be read.
-    //! \param[out] string The string, whose contents begin at data() and
-    //!     continue up to a `NUL` terminator.
-    //!
-    //! \return `true` on success, with \a string set appropriately. If \a
-    //!     offset is greater than or equal to the \a user_size constructor
-    //!     parameter, or if no `NUL` terminator was found in data() after \a
-    //!     offset, returns `false` with an appropriate warning logged.
-    bool ReadCString(size_t offset, std::string* string) const;
-
-   private:
-    //! \brief Creates an object that owns a memory region mapped from another
-    //!     Mach task.
-    //!
-    //! \param[in] vm_address The address in this process’ address space where
-    //!     the mapping begins. This must be page-aligned.
-    //! \param[in] vm_size The total size of the mapping that begins at \a
-    //!     vm_address. This must be page-aligned.
-    //! \param[in] user_offset The offset into the mapped region where the data
-    //!     requested by the user begins. This accounts for the fact that a
-    //!     mapping must be page-aligned but the user data may not be. This
-    //!     parameter must be equal to or less than \a vm_size.
-    //! \param[in] user_size The size of the data requested by the user. This
-    //!     parameter can be used to compute the end address of user data, which
-    //!     must be within the mapped region.
-    MappedMemory(vm_address_t vm_address,
-                 size_t vm_size,
-                 size_t user_offset,
-                 size_t user_size);
-
-    base::mac::ScopedMachVM vm_;
-    const void* data_;
-    size_t user_size_;
-
-    // The outer class needs to be able to call this class’ private constructor.
-    friend class TaskMemory;
-
-    DISALLOW_COPY_AND_ASSIGN(MappedMemory);
-  };
-
-  //! \param[in] task A send right to the target task’s task port. This object
-  //!     does not take ownership of the send right.
-  explicit TaskMemory(task_t task);
-
-  ~TaskMemory() {}
-
-  //! \brief Copies memory from the target task into a caller-provided buffer in
-  //!     the current task.
-  //!
-  //! \param[in] address The address, in the target task’s address space, of the
-  //!     memory region to copy.
-  //! \param[in] size The size, in bytes, of the memory region to copy. \a
-  //!     buffer must be at least this size.
-  //! \param[out] buffer The buffer into which the contents of the other task’s
-  //!     memory will be copied.
-  //!
-  //! \return `true` on success, with \a buffer filled appropriately. `false` on
-  //!     failure, with a warning logged. Failures can occur, for example, when
-  //!     encountering unmapped or unreadable pages.
-  //!
-  //! \sa ReadMapped()
-  bool Read(mach_vm_address_t address, size_t size, void* buffer);
-
-  //! \brief Maps memory from the target task into the current task.
-  //!
-  //! This interface is an alternative to Read() that does not require the
-  //! caller to provide a buffer to fill. This avoids copying memory, which can
-  //! offer a performance improvement.
-  //!
-  //! \param[in] address The address, in the target task’s address space, of the
-  //!     memory region to map.
-  //! \param[in] size The size, in bytes, of the memory region to map.
-  //!
-  //! \return On success, a MappedMemory object that provides access to the data
-  //!     requested. On faliure, `nullptr`, with a warning logged. Failures can
-  //!     occur, for example, when encountering unmapped or unreadable pages.
-  std::unique_ptr<MappedMemory> ReadMapped(mach_vm_address_t address,
-                                           size_t size);
-
-  //! \brief Reads a `NUL`-terminated C string from the target task into a
-  //!     string in the current task.
-  //!
-  //! The length of the string need not be known ahead of time. This method will
-  //! read contiguous memory until a `NUL` terminator is found.
-  //!
-  //! \param[in] address The address, in the target task’s address space, of the
-  //!     string to copy.
-  //! \param[out] string The string read from the other task.
-  //!
-  //! \return `true` on success, with \a string set appropriately. `false` on
-  //!     failure, with a warning logged. Failures can occur, for example, when
-  //!     encountering unmapped or unreadable pages.
-  //!
-  //! \sa MappedMemory::ReadCString()
-  bool ReadCString(mach_vm_address_t address, std::string* string);
-
-  //! \brief Reads a `NUL`-terminated C string from the target task into a
-  //!     string in the current task.
-  //!
-  //! \param[in] address The address, in the target task’s address space, of the
-  //!     string to copy.
-  //! \param[in] size The maximum number of bytes to read. The string is
-  //!     required to be `NUL`-terminated within this many bytes.
-  //! \param[out] string The string read from the other task.
-  //!
-  //! \return `true` on success, with \a string set appropriately. `false` on
-  //!     failure, with a warning logged. Failures can occur, for example, when
-  //!     a `NUL` terminator is not found within \a size bytes, or when
-  //!     encountering unmapped or unreadable pages.
-  //!
-  //! \sa MappedMemory::ReadCString()
-  bool ReadCStringSizeLimited(mach_vm_address_t address,
-                              mach_vm_size_t size,
-                              std::string* string);
-
- private:
-  // The common internal implementation shared by the ReadCString*() methods.
-  bool ReadCStringInternal(mach_vm_address_t address,
-                           bool has_size,
-                           mach_vm_size_t size,
-                           std::string* string);
-
-  task_t task_;  // weak
-
-  DISALLOW_COPY_AND_ASSIGN(TaskMemory);
-};
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_UTIL_MACH_TASK_MEMORY_H_
diff --git a/util/mach/task_memory_test.cc b/util/mach/task_memory_test.cc
deleted file mode 100644
index d9f0cb7..0000000
--- a/util/mach/task_memory_test.cc
+++ /dev/null
@@ -1,578 +0,0 @@
-// Copyright 2014 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.
-
-#include "util/mach/task_memory.h"
-
-#include <mach/mach.h>
-#include <string.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-
-#include "base/mac/scoped_mach_port.h"
-#include "base/mac/scoped_mach_vm.h"
-#include "gtest/gtest.h"
-#include "test/mac/mach_errors.h"
-#include "util/misc/from_pointer_cast.h"
-
-namespace crashpad {
-namespace test {
-namespace {
-
-TEST(TaskMemory, ReadSelf) {
-  vm_address_t address = 0;
-  constexpr vm_size_t kSize = 4 * PAGE_SIZE;
-  kern_return_t kr =
-      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
-  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
-
-  char* region = reinterpret_cast<char*>(address);
-  for (size_t index = 0; index < kSize; ++index) {
-    region[index] = (index % 256) ^ ((index >> 8) % 256);
-  }
-
-  TaskMemory memory(mach_task_self());
-
-  // This tests using both the Read() and ReadMapped() interfaces.
-  std::string result(kSize, '\0');
-  std::unique_ptr<TaskMemory::MappedMemory> mapped;
-
-  // Ensure that the entire region can be read.
-  ASSERT_TRUE(memory.Read(address, kSize, &result[0]));
-  EXPECT_EQ(memcmp(region, &result[0], kSize), 0);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize)));
-  EXPECT_EQ(memcmp(region, mapped->data(), kSize), 0);
-
-  // Ensure that a read of length 0 succeeds and doesn’t touch the result.
-  result.assign(kSize, '\0');
-  std::string zeroes = result;
-  ASSERT_TRUE(memory.Read(address, 0, &result[0]));
-  EXPECT_EQ(result, zeroes);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address, 0)));
-
-  // Ensure that a read starting at an unaligned address works.
-  ASSERT_TRUE(memory.Read(address + 1, kSize - 1, &result[0]));
-  EXPECT_EQ(memcmp(region + 1, &result[0], kSize - 1), 0);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
-  EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 1), 0);
-
-  // Ensure that a read ending at an unaligned address works.
-  ASSERT_TRUE(memory.Read(address, kSize - 1, &result[0]));
-  EXPECT_EQ(memcmp(region, &result[0], kSize - 1), 0);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize - 1)));
-  EXPECT_EQ(memcmp(region, mapped->data(), kSize - 1), 0);
-
-  // Ensure that a read starting and ending at unaligned addresses works.
-  ASSERT_TRUE(memory.Read(address + 1, kSize - 2, &result[0]));
-  EXPECT_EQ(memcmp(region + 1, &result[0], kSize - 2), 0);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 2)));
-  EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 2), 0);
-
-  // Ensure that a read of exactly one page works.
-  ASSERT_TRUE(memory.Read(address + PAGE_SIZE, PAGE_SIZE, &result[0]));
-  EXPECT_EQ(memcmp(region + PAGE_SIZE, &result[0], PAGE_SIZE), 0);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE, PAGE_SIZE)));
-  EXPECT_EQ(memcmp(region + PAGE_SIZE, mapped->data(), PAGE_SIZE), 0);
-
-  // Ensure that a read of a single byte works.
-  ASSERT_TRUE(memory.Read(address + 2, 1, &result[0]));
-  EXPECT_EQ(result[0], region[2]);
-  ASSERT_TRUE((mapped = memory.ReadMapped(address + 2, 1)));
-  EXPECT_EQ(reinterpret_cast<const char*>(mapped->data())[0], region[2]);
-
-  // Ensure that a read of length zero works and doesn’t touch the data.
-  result[0] = 'M';
-  ASSERT_TRUE(memory.Read(address + 3, 0, &result[0]));
-  EXPECT_EQ(result[0], 'M');
-  ASSERT_TRUE((mapped = memory.ReadMapped(address + 3, 0)));
-}
-
-TEST(TaskMemory, ReadSelfUnmapped) {
-  vm_address_t address = 0;
-  constexpr vm_size_t kSize = 2 * PAGE_SIZE;
-  kern_return_t kr =
-      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
-  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
-
-  char* region = reinterpret_cast<char*>(address);
-  for (size_t index = 0; index < kSize; ++index) {
-    // Don’t include any NUL bytes, because ReadCString stops when it encounters
-    // a NUL.
-    region[index] = (index % 255) + 1;
-  }
-
-  kr = vm_protect(
-      mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect");
-
-  TaskMemory memory(mach_task_self());
-  std::string result(kSize, '\0');
-
-  EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
-  EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
-  EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
-  EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
-  EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
-  EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
-
-  // Do the same thing with the ReadMapped() interface.
-  std::unique_ptr<TaskMemory::MappedMemory> mapped;
-  EXPECT_FALSE((mapped = memory.ReadMapped(address, kSize)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE, 1)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 2)));
-  EXPECT_TRUE((mapped = memory.ReadMapped(address, PAGE_SIZE)));
-  EXPECT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 1)));
-
-  // Repeat the test with an unmapped page instead of an unreadable one. This
-  // portion of the test may be flaky in the presence of other threads, if
-  // another thread maps something in the region that is deallocated here.
-  kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
-  vm_owner.reset(address, PAGE_SIZE);
-
-  EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
-  EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
-  EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
-  EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
-  EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
-  EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
-
-  // Do the same thing with the ReadMapped() interface.
-  EXPECT_FALSE((mapped = memory.ReadMapped(address, kSize)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE, 1)));
-  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 2)));
-  EXPECT_TRUE((mapped = memory.ReadMapped(address, PAGE_SIZE)));
-  EXPECT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 1)));
-}
-
-// This function consolidates the cast from a char* to mach_vm_address_t in one
-// location when reading from the current task.
-bool ReadCStringSelf(TaskMemory* memory,
-                     const char* pointer,
-                     std::string* result) {
-  return memory->ReadCString(FromPointerCast<mach_vm_address_t>(pointer),
-                             result);
-}
-
-TEST(TaskMemory, ReadCStringSelf) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  const char kConstCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharEmpty, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kConstCharEmpty);
-
-  const char kConstCharShort[] = "A short const char[]";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kConstCharShort, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kConstCharShort);
-
-  static const char kStaticConstCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharEmpty, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharEmpty);
-
-  static const char kStaticConstCharShort[] = "A short static const char[]";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstCharShort, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharShort);
-
-  constexpr char kConstexprCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kConstexprCharEmpty, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kConstexprCharEmpty);
-
-  constexpr char kConstexprCharShort[] = "A short constexpr char[]";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kConstexprCharShort, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kConstexprCharShort);
-
-  static constexpr char kStaticConstexprCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstexprCharEmpty, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kStaticConstexprCharEmpty);
-
-  static constexpr char kStaticConstexprCharShort[] =
-      "A short static constexpr char[]";
-  ASSERT_TRUE(ReadCStringSelf(&memory, kStaticConstexprCharShort, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kStaticConstexprCharShort);
-
-  std::string string_short("A short std::string in a function");
-  ASSERT_TRUE(ReadCStringSelf(&memory, &string_short[0], &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, string_short);
-
-  std::string string_long;
-  constexpr size_t kStringLongSize = 4 * PAGE_SIZE;
-  for (size_t index = 0; index < kStringLongSize; ++index) {
-    // Don’t include any NUL bytes, because ReadCString stops when it encounters
-    // a NUL.
-    string_long.append(1, (index % 255) + 1);
-  }
-  ASSERT_EQ(string_long.size(), kStringLongSize);
-  ASSERT_TRUE(ReadCStringSelf(&memory, &string_long[0], &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result.size(), kStringLongSize);
-  EXPECT_EQ(result, string_long);
-}
-
-TEST(TaskMemory, ReadCStringSelfUnmapped) {
-  vm_address_t address = 0;
-  constexpr vm_size_t kSize = 2 * PAGE_SIZE;
-  kern_return_t kr =
-      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
-  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
-
-  char* region = reinterpret_cast<char*>(address);
-  for (size_t index = 0; index < kSize; ++index) {
-    // Don’t include any NUL bytes, because ReadCString stops when it encounters
-    // a NUL.
-    region[index] = (index % 255) + 1;
-  }
-
-  kr = vm_protect(
-      mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect");
-
-  TaskMemory memory(mach_task_self());
-  std::string result;
-  EXPECT_FALSE(memory.ReadCString(address, &result));
-
-  // Make sure that if the string is NUL-terminated within the mapped memory
-  // region, it can be read properly.
-  char terminator_or_not = '\0';
-  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
-  ASSERT_TRUE(memory.ReadCString(address, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result.size(), PAGE_SIZE - 1u);
-  EXPECT_EQ(result, region);
-
-  // Repeat the test with an unmapped page instead of an unreadable one. This
-  // portion of the test may be flaky in the presence of other threads, if
-  // another thread maps something in the region that is deallocated here.
-  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
-  kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
-  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
-  vm_owner.reset(address, PAGE_SIZE);
-
-  EXPECT_FALSE(memory.ReadCString(address, &result));
-
-  // Clear the result before testing that the string can be read. This makes
-  // sure that the result is actually filled in, because it already contains the
-  // expected value from the tests above.
-  result.clear();
-  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
-  ASSERT_TRUE(memory.ReadCString(address, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result.size(), PAGE_SIZE - 1u);
-  EXPECT_EQ(result, region);
-}
-
-// This function consolidates the cast from a char* to mach_vm_address_t in one
-// location when reading from the current task.
-bool ReadCStringSizeLimitedSelf(TaskMemory* memory,
-                                const char* pointer,
-                                size_t size,
-                                std::string* result) {
-  return memory->ReadCStringSizeLimited(
-      FromPointerCast<mach_vm_address_t>(pointer), size, result);
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_ConstCharEmpty) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  static constexpr char kConstCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, kConstCharEmpty, arraysize(kConstCharEmpty), &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kConstCharEmpty);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, kConstCharEmpty, arraysize(kConstCharEmpty) + 1, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kConstCharEmpty);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory, kConstCharEmpty, 0, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kConstCharEmpty);
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_ConstCharShort) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  static constexpr char kConstCharShort[] = "A short const char[]";
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, kConstCharShort, arraysize(kConstCharShort), &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kConstCharShort);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, kConstCharShort, arraysize(kConstCharShort) + 1, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kConstCharShort);
-
-  ASSERT_FALSE(ReadCStringSizeLimitedSelf(
-      &memory, kConstCharShort, arraysize(kConstCharShort) - 1, &result));
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharEmpty) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  static constexpr char kStaticConstCharEmpty[] = "";
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
-                                         kStaticConstCharEmpty,
-                                         arraysize(kStaticConstCharEmpty),
-                                         &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharEmpty);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
-                                         kStaticConstCharEmpty,
-                                         arraysize(kStaticConstCharEmpty) + 1,
-                                         &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharEmpty);
-
-  result.clear();
-  ASSERT_TRUE(
-      ReadCStringSizeLimitedSelf(&memory, kStaticConstCharEmpty, 0, &result));
-  EXPECT_TRUE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharEmpty);
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_StaticConstCharShort) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  static constexpr char kStaticConstCharShort[] =
-      "A short static constexpr char[]";
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
-                                         kStaticConstCharShort,
-                                         arraysize(kStaticConstCharShort),
-                                         &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharShort);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(&memory,
-                                         kStaticConstCharShort,
-                                         arraysize(kStaticConstCharShort) + 1,
-                                         &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, kStaticConstCharShort);
-
-  ASSERT_FALSE(ReadCStringSizeLimitedSelf(&memory,
-                                          kStaticConstCharShort,
-                                          arraysize(kStaticConstCharShort) - 1,
-                                          &result));
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_StringShort) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  std::string string_short("A short std::string in a function");
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, &string_short[0], string_short.size() + 1, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, string_short);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, &string_short[0], string_short.size() + 2, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result, string_short);
-
-  ASSERT_FALSE(ReadCStringSizeLimitedSelf(
-      &memory, &string_short[0], string_short.size(), &result));
-}
-
-TEST(TaskMemory, ReadCStringSizeLimited_StringLong) {
-  TaskMemory memory(mach_task_self());
-  std::string result;
-
-  std::string string_long;
-  constexpr size_t kStringLongSize = 4 * PAGE_SIZE;
-  for (size_t index = 0; index < kStringLongSize; ++index) {
-    // Don’t include any NUL bytes, because ReadCString stops when it encounters
-    // a NUL.
-    string_long.append(1, (index % 255) + 1);
-  }
-  ASSERT_EQ(string_long.size(), kStringLongSize);
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, &string_long[0], string_long.size() + 1, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result.size(), kStringLongSize);
-  EXPECT_EQ(result, string_long);
-
-  result.clear();
-  ASSERT_TRUE(ReadCStringSizeLimitedSelf(
-      &memory, &string_long[0], string_long.size() + 2, &result));
-  EXPECT_FALSE(result.empty());
-  EXPECT_EQ(result.size(), kStringLongSize);
-  EXPECT_EQ(result, string_long);
-
-  ASSERT_FALSE(ReadCStringSizeLimitedSelf(
-      &memory, &string_long[0], string_long.size(), &result));
-}
-
-bool IsAddressMapped(vm_address_t address) {
-  vm_address_t region_address = address;
-  vm_size_t region_size;
-  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
-  vm_region_basic_info_64 info;
-  mach_port_t object;
-  kern_return_t kr = vm_region_64(mach_task_self(),
-                                  &region_address,
-                                  &region_size,
-                                  VM_REGION_BASIC_INFO_64,
-                                  reinterpret_cast<vm_region_info_t>(&info),
-                                  &count,
-                                  &object);
-  if (kr == KERN_SUCCESS) {
-    // |object| will be MACH_PORT_NULL (10.9.4 xnu-2422.110.17/osfmk/vm/vm_map.c
-    // vm_map_region()), but the interface acts as if it might carry a send
-    // right, so treat it as documented.
-    base::mac::ScopedMachSendRight object_owner(object);
-
-    return address >= region_address && address <= region_address + region_size;
-  }
-
-  if (kr == KERN_INVALID_ADDRESS) {
-    return false;
-  }
-
-  ADD_FAILURE() << MachErrorMessage(kr, "vm_region_64");
-  return false;
-}
-
-TEST(TaskMemory, MappedMemoryDeallocates) {
-  // This tests that once a TaskMemory::MappedMemory object is destroyed, it
-  // releases the mapped memory that it owned. Technically, this test is not
-  // valid because after the mapping is released, something else (on another
-  // thread) might wind up mapped in the same address. In the test environment,
-  // hopefully there are either no other threads or they’re all quiescent, so
-  // nothing else should wind up mapped in the address.
-
-  TaskMemory memory(mach_task_self());
-  std::unique_ptr<TaskMemory::MappedMemory> mapped;
-
-  static constexpr char kTestBuffer[] = "hello!";
-  mach_vm_address_t test_address =
-      FromPointerCast<mach_vm_address_t>(&kTestBuffer);
-  ASSERT_TRUE((mapped = memory.ReadMapped(test_address, sizeof(kTestBuffer))));
-  EXPECT_EQ(memcmp(kTestBuffer, mapped->data(), sizeof(kTestBuffer)), 0);
-
-  vm_address_t mapped_address = reinterpret_cast<vm_address_t>(mapped->data());
-  EXPECT_TRUE(IsAddressMapped(mapped_address));
-
-  mapped.reset();
-  EXPECT_FALSE(IsAddressMapped(mapped_address));
-
-  // This is the same but with a big buffer that’s definitely larger than a
-  // single page. This makes sure that the whole mapped region winds up being
-  // deallocated.
-  constexpr size_t kBigSize = 4 * PAGE_SIZE;
-  std::unique_ptr<char[]> big_buffer(new char[kBigSize]);
-  test_address = FromPointerCast<mach_vm_address_t>(&big_buffer[0]);
-  ASSERT_TRUE((mapped = memory.ReadMapped(test_address, kBigSize)));
-
-  mapped_address = reinterpret_cast<vm_address_t>(mapped->data());
-  vm_address_t mapped_last_address = mapped_address + kBigSize - 1;
-  EXPECT_TRUE(IsAddressMapped(mapped_address));
-  EXPECT_TRUE(IsAddressMapped(mapped_address + PAGE_SIZE));
-  EXPECT_TRUE(IsAddressMapped(mapped_last_address));
-
-  mapped.reset();
-  EXPECT_FALSE(IsAddressMapped(mapped_address));
-  EXPECT_FALSE(IsAddressMapped(mapped_address + PAGE_SIZE));
-  EXPECT_FALSE(IsAddressMapped(mapped_last_address));
-}
-
-TEST(TaskMemory, MappedMemoryReadCString) {
-  // This tests the behavior of TaskMemory::MappedMemory::ReadCString().
-  TaskMemory memory(mach_task_self());
-  std::unique_ptr<TaskMemory::MappedMemory> mapped;
-
-  static constexpr char kTestBuffer[] = "0\0" "2\0" "45\0" "789";
-  const mach_vm_address_t kTestAddress =
-      FromPointerCast<mach_vm_address_t>(&kTestBuffer);
-  ASSERT_TRUE((mapped = memory.ReadMapped(kTestAddress, 10)));
-
-  std::string string;
-  ASSERT_TRUE(mapped->ReadCString(0, &string));
-  EXPECT_EQ(string, "0");
-  ASSERT_TRUE(mapped->ReadCString(1, &string));
-  EXPECT_EQ(string, "");
-  ASSERT_TRUE(mapped->ReadCString(2, &string));
-  EXPECT_EQ(string, "2");
-  ASSERT_TRUE(mapped->ReadCString(3, &string));
-  EXPECT_EQ(string, "");
-  ASSERT_TRUE(mapped->ReadCString(4, &string));
-  EXPECT_EQ(string, "45");
-  ASSERT_TRUE(mapped->ReadCString(5, &string));
-  EXPECT_EQ(string, "5");
-  ASSERT_TRUE(mapped->ReadCString(6, &string));
-  EXPECT_EQ(string, "");
-
-  // kTestBuffer’s NUL terminator was not read, so these will see an
-  // unterminated string and fail.
-  EXPECT_FALSE(mapped->ReadCString(7, &string));
-  EXPECT_FALSE(mapped->ReadCString(8, &string));
-  EXPECT_FALSE(mapped->ReadCString(9, &string));
-
-  // This is out of the range of what was read, so it will fail.
-  EXPECT_FALSE(mapped->ReadCString(10, &string));
-  EXPECT_FALSE(mapped->ReadCString(11, &string));
-
-  // Read it again, this time with a length long enough to include the NUL
-  // terminator.
-  ASSERT_TRUE((mapped = memory.ReadMapped(kTestAddress, 11)));
-
-  ASSERT_TRUE(mapped->ReadCString(6, &string));
-  EXPECT_EQ(string, "");
-
-  // These should now succeed.
-  ASSERT_TRUE(mapped->ReadCString(7, &string));
-  EXPECT_EQ(string, "789");
-  ASSERT_TRUE(mapped->ReadCString(8, &string));
-  EXPECT_EQ(string, "89");
-  ASSERT_TRUE(mapped->ReadCString(9, &string));
-  EXPECT_EQ(string, "9");
-  EXPECT_TRUE(mapped->ReadCString(10, &string));
-  EXPECT_EQ(string, "");
-
-  // These are still out of range.
-  EXPECT_FALSE(mapped->ReadCString(11, &string));
-  EXPECT_FALSE(mapped->ReadCString(12, &string));
-}
-
-}  // namespace
-}  // namespace test
-}  // namespace crashpad
diff --git a/util/misc/arraysize.h b/util/misc/arraysize.h
new file mode 100644
index 0000000..93a6388
--- /dev/null
+++ b/util/misc/arraysize.h
@@ -0,0 +1,43 @@
+// Copyright 2019 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_UTIL_MISC_ARRAYSIZE_H_
+#define CRASHPAD_UTIL_MISC_ARRAYSIZE_H_
+
+#include <sys/types.h>  // For size_t.
+
+#include <type_traits>
+
+//! \file
+
+namespace crashpad {
+namespace internal {
+
+//! \brief A helper to implement ArraySize.
+template <typename ArrayType>
+constexpr size_t ArraySizeHelper() noexcept {
+  return std::extent<typename std::remove_reference<ArrayType>::type>::value;
+}
+
+}  // namespace internal
+}  // namespace crashpad
+
+//! \brief A way of computing an array’s size.
+//!
+//! Use this only where `base::size()` or `std::size()` won’t work, such as in
+//! constant expressions (including `static_assert` expressions) that consider
+//! the sizes of non-static data members.
+#define ArraySize(array) crashpad::internal::ArraySizeHelper<decltype(array)>()
+
+#endif  // CRASHPAD_UTIL_MISC_ARRAYSIZE_H_
diff --git a/util/misc/arraysize_unsafe_test.cc b/util/misc/arraysize_test.cc
similarity index 68%
rename from util/misc/arraysize_unsafe_test.cc
rename to util/misc/arraysize_test.cc
index a6660ae..ad8da09 100644
--- a/util/misc/arraysize_unsafe_test.cc
+++ b/util/misc/arraysize_test.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/misc/arraysize_unsafe.h"
+#include "util/misc/arraysize.h"
 
 #include "base/compiler_specific.h"
 #include "gtest/gtest.h"
@@ -21,37 +21,37 @@
 namespace test {
 namespace {
 
-TEST(ArraySizeUnsafe, ArraySizeUnsafe) {
+TEST(ArraySize, ArraySize) {
   char c1[1];
-  static_assert(ARRAYSIZE_UNSAFE(c1) == 1, "c1");
+  static_assert(ArraySize(c1) == 1, "c1");
   ALLOW_UNUSED_LOCAL(c1);
 
   char c2[2];
-  static_assert(ARRAYSIZE_UNSAFE(c2) == 2, "c2");
+  static_assert(ArraySize(c2) == 2, "c2");
   ALLOW_UNUSED_LOCAL(c2);
 
   char c4[4];
-  static_assert(ARRAYSIZE_UNSAFE(c4) == 4, "c4");
+  static_assert(ArraySize(c4) == 4, "c4");
   ALLOW_UNUSED_LOCAL(c4);
 
   int i1[1];
-  static_assert(ARRAYSIZE_UNSAFE(i1) == 1, "i1");
+  static_assert(ArraySize(i1) == 1, "i1");
   ALLOW_UNUSED_LOCAL(i1);
 
   int i2[2];
-  static_assert(ARRAYSIZE_UNSAFE(i2) == 2, "i2");
+  static_assert(ArraySize(i2) == 2, "i2");
   ALLOW_UNUSED_LOCAL(i2);
 
   int i4[4];
-  static_assert(ARRAYSIZE_UNSAFE(i4) == 4, "i4");
+  static_assert(ArraySize(i4) == 4, "i4");
   ALLOW_UNUSED_LOCAL(i4);
 
   long l8[8];
-  static_assert(ARRAYSIZE_UNSAFE(l8) == 8, "l8");
+  static_assert(ArraySize(l8) == 8, "l8");
   ALLOW_UNUSED_LOCAL(l8);
 
   int l9[9];
-  static_assert(ARRAYSIZE_UNSAFE(l9) == 9, "l9");
+  static_assert(ArraySize(l9) == 9, "l9");
   ALLOW_UNUSED_LOCAL(l9);
 
   struct S {
@@ -62,11 +62,11 @@
   };
 
   S s1[1];
-  static_assert(ARRAYSIZE_UNSAFE(s1) == 1, "s1");
+  static_assert(ArraySize(s1) == 1, "s1");
   ALLOW_UNUSED_LOCAL(s1);
 
   S s10[10];
-  static_assert(ARRAYSIZE_UNSAFE(s10) == 10, "s10");
+  static_assert(ArraySize(s10) == 10, "s10");
   ALLOW_UNUSED_LOCAL(s10);
 }
 
diff --git a/util/misc/arraysize_unsafe.h b/util/misc/arraysize_unsafe.h
deleted file mode 100644
index e53c70b..0000000
--- a/util/misc/arraysize_unsafe.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 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_UTIL_MISC_ARRAYSIZE_UNSAFE_H_
-#define CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_
-
-//! \file
-
-//! \brief Not the safest way of computing an array’s size…
-//!
-//! `#%include "base/macros.h"` and use its `arraysize()` instead. This macro
-//! should only be used in rare situations where `arraysize()` does not
-//! function.
-#define ARRAYSIZE_UNSAFE(array) (sizeof(array) / sizeof(array[0]))
-
-#endif  // CRASHPAD_UTIL_MISC_ARRAYSIZE_UNSAFE_H_
diff --git a/util/misc/capture_context.h b/util/misc/capture_context.h
index 541589d..3ff7118 100644
--- a/util/misc/capture_context.h
+++ b/util/misc/capture_context.h
@@ -32,6 +32,8 @@
 #if defined(OS_MACOSX)
 #if defined(ARCH_CPU_X86_FAMILY)
 using NativeCPUContext = x86_thread_state;
+#elif defined(ARCH_CPU_ARM64)
+using NativeCPUContext = arm_unified_thread_state;
 #endif
 #elif defined(OS_WIN)
 using NativeCPUContext = CONTEXT;
diff --git a/util/misc/capture_context_fuchsia.S b/util/misc/capture_context_fuchsia.S
index 8e5c0cb..0ebc7f7 100644
--- a/util/misc/capture_context_fuchsia.S
+++ b/util/misc/capture_context_fuchsia.S
@@ -116,7 +116,7 @@
   movq 0x90(%rdi), %rax
   movq 0x28(%rdi), %r8
 
-  // TODO(scottmg): save floating-point registers.
+  // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers.
 
   popfq
 
@@ -159,14 +159,14 @@
   // The link register holds the return address for this function.
   str LR, [x0, #0x1b8]  // context->uc_mcontext.pc
 
-  // NZCV, pstate, and CPSR are synonyms.
+  // pstate should hold SPSR but NZCV are the only bits we know about.
   mrs x1, NZCV
   str x1, [x0, #0x1c0]  // context->uc_mcontext.pstate
 
   // Restore x1 from the saved context.
   ldr x1, [x0, #0xc0]
 
-  // TODO(scottmg): save floating-point registers.
+  // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers.
 
   ret
 
diff --git a/util/misc/capture_context_linux.S b/util/misc/capture_context_linux.S
index 42999a9..52215ee 100644
--- a/util/misc/capture_context_linux.S
+++ b/util/misc/capture_context_linux.S
@@ -28,7 +28,11 @@
   .globl CAPTURECONTEXT_SYMBOL2
 #if defined(__i386__) || defined(__x86_64__)
   .balign 16, 0x90
-#elif defined(__arm__) || defined(__aarch64__) || defined(__mips__)
+#elif defined(__arm__) || defined(__aarch64__)
+  .balign 4, 0x0
+  .type CAPTURECONTEXT_SYMBOL, %function
+  .type CAPTURECONTEXT_SYMBOL2, %function
+#elif defined(__mips__)
   .balign 4, 0x0
 #endif
 
@@ -257,9 +261,7 @@
   // Restore r0.
   sub r0, r0, #0x24
 
-  // Save named general purpose registers.
-  str FP, [r0, #0x4c]  // context->uc_mcontext.fp
-  str IP, [r0, #0x50]  // context->uc_mcontext.ip
+  // Save SP/r13.
   str SP, [r0, #0x54]  // context->uc_mcontext.sp
 
   // The original LR can't be recovered.
@@ -284,7 +286,7 @@
   // Restore r1.
   ldr r1, [r0, #0x24]
 
-  // TODO(jperaza): save floating-point registers.
+  // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers.
 
   mov PC, LR
 
@@ -312,23 +314,23 @@
   stp x28, x29, [x0, #0x198]
 
   // The original LR can't be recovered.
-  str LR, [x0, #0x1a8]
+  str x30, [x0, #0x1a8]
 
   // Use x1 as a scratch register.
   mov x1, SP
   str x1, [x0, #0x1b0] // context->uc_mcontext.sp
 
   // The link register holds the return address for this function.
-  str LR, [x0, #0x1b8]  // context->uc_mcontext.pc
+  str x30, [x0, #0x1b8]  // context->uc_mcontext.pc
 
-  // NZCV, pstate, and CPSR are synonyms.
+  // pstate should hold SPSR but NZCV are the only bits we know about.
   mrs x1, NZCV
   str x1, [x0, #0x1c0]  // context->uc_mcontext.pstate
 
   // Restore x1 from the saved context.
   ldr x1, [x0, #0xc0]
 
-  // TODO(jperaza): save floating-point registers.
+  // TODO(https://crashpad.chromium.org/bug/300): save floating-point registers.
 
   ret
 #elif defined(__mips__)
diff --git a/util/misc/capture_context_test.cc b/util/misc/capture_context_test.cc
index 7f3890f..cf23c2d 100644
--- a/util/misc/capture_context_test.cc
+++ b/util/misc/capture_context_test.cc
@@ -18,9 +18,11 @@
 
 #include <algorithm>
 
+#include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "util/misc/address_sanitizer.h"
 #include "util/misc/capture_context_test_util.h"
+#include "util/misc/memory_sanitizer.h"
 
 namespace crashpad {
 namespace test {
@@ -33,7 +35,12 @@
 // find an approximately valid stack pointer by comparing locals to the
 // captured one, disable safe-stack for this function.
 __attribute__((no_sanitize("safe-stack")))
-#endif
+#endif  // defined(OS_FUCHSIA)
+
+#if defined(MEMORY_SANITIZER)
+// CaptureContext() calls inline assembly and is incompatible with MSan.
+__attribute__((no_sanitize("memory")))
+#endif  // defined(MEMORY_SANITIZER)
 
 void TestCaptureContext() {
   NativeCPUContext context_1;
@@ -49,16 +56,17 @@
   // reference program counter.
   uintptr_t pc = ProgramCounterFromContext(context_1);
 
-#if !defined(ADDRESS_SANITIZER) && !defined(ARCH_CPU_MIPS_FAMILY)
-  // AddressSanitizer can cause enough code bloat that the “nearby” check would
+#if !defined(ADDRESS_SANITIZER) && !defined(ARCH_CPU_MIPS_FAMILY) && \
+    !defined(MEMORY_SANITIZER)
+  // Sanitizers can cause enough code bloat that the “nearby” check would
   // likely fail.
   const uintptr_t kReferencePC =
       reinterpret_cast<uintptr_t>(TestCaptureContext);
   EXPECT_PRED2([](uintptr_t actual,
-                  uintptr_t reference) { return actual - reference < 64u; },
+                  uintptr_t reference) { return actual - reference < 128u; },
                pc,
                kReferencePC);
-#endif  // !defined(ADDRESS_SANITIZER)
+#endif
 
   const uintptr_t sp = StackPointerFromContext(context_1);
 
@@ -67,9 +75,9 @@
   // value can be the lowest value possible.
   NativeCPUContext context_2;
 
-// AddressSanitizer on Linux causes stack variables to be stored separately from
-// the call stack.
-#if !defined(ADDRESS_SANITIZER) || (!defined(OS_LINUX) && !defined(OS_ANDROID))
+  // AddressSanitizer with use-after-return detection causes stack variables to
+  // be allocated on the heap.
+#if !defined(ADDRESS_SANITIZER)
   // The stack pointer reference value is the lowest address of a local variable
   // in this function. The captured program counter will be slightly less than
   // or equal to the reference stack pointer.
@@ -82,7 +90,7 @@
                   uintptr_t reference) { return reference - actual < 768u; },
                sp,
                kReferenceSP);
-#endif  // !ADDRESS_SANITIZER || (!OS_LINUX && !OS_ANDROID)
+#endif  // !defined(ADDRESS_SANITIZER)
 
   // Capture the context again, expecting that the stack pointer stays the same
   // and the program counter increases. Strictly speaking, there’s no guarantee
diff --git a/util/misc/capture_context_test_util_win.cc b/util/misc/capture_context_test_util_win.cc
index 239beac..16d81b7 100644
--- a/util/misc/capture_context_test_util_win.cc
+++ b/util/misc/capture_context_test_util_win.cc
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 #include "util/misc/capture_context_test_util.h"
+#include "util/win/context_wrappers.h"
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 
 namespace crashpad {
@@ -58,7 +59,7 @@
 
 #if defined(ARCH_CPU_X86)
   // fxsave doesn’t write these bytes.
-  for (size_t i = 464; i < arraysize(context.ExtendedRegisters); ++i) {
+  for (size_t i = 464; i < base::size(context.ExtendedRegisters); ++i) {
     SCOPED_TRACE(i);
     EXPECT_EQ(context.ExtendedRegisters[i], 0);
   }
@@ -68,7 +69,7 @@
   EXPECT_EQ(context.FltSave.MxCsr, context.MxCsr);
 
   // fxsave doesn’t write these bytes.
-  for (size_t i = 0; i < arraysize(context.FltSave.Reserved4); ++i) {
+  for (size_t i = 0; i < base::size(context.FltSave.Reserved4); ++i) {
     SCOPED_TRACE(i);
     EXPECT_EQ(context.FltSave.Reserved4[i], 0);
   }
@@ -80,7 +81,7 @@
   EXPECT_EQ(context.P4Home, 0u);
   EXPECT_EQ(context.P5Home, 0u);
   EXPECT_EQ(context.P6Home, 0u);
-  for (size_t i = 0; i < arraysize(context.VectorRegister); ++i) {
+  for (size_t i = 0; i < base::size(context.VectorRegister); ++i) {
     SCOPED_TRACE(i);
     EXPECT_EQ(context.VectorRegister[i].Low, 0u);
     EXPECT_EQ(context.VectorRegister[i].High, 0u);
@@ -95,11 +96,7 @@
 }
 
 uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) {
-#if defined(ARCH_CPU_X86)
-  return context.Eip;
-#elif defined(ARCH_CPU_X86_64)
-  return context.Rip;
-#endif
+  return reinterpret_cast<uintptr_t>(ProgramCounterFromCONTEXT(&context));
 }
 
 uintptr_t StackPointerFromContext(const NativeCPUContext& context) {
@@ -107,6 +104,8 @@
   return context.Esp;
 #elif defined(ARCH_CPU_X86_64)
   return context.Rsp;
+#elif defined(ARCH_CPU_ARM64)
+  return context.Sp;
 #endif
 }
 
diff --git a/util/misc/capture_context_win_arm64.asm b/util/misc/capture_context_win_arm64.asm
new file mode 100644
index 0000000..5630698
--- /dev/null
+++ b/util/misc/capture_context_win_arm64.asm
@@ -0,0 +1,64 @@
+; Copyright 2019 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.
+
+  EXPORT |?CaptureContext@crashpad@@YAXPEAU_CONTEXT@@@Z|
+  AREA |.text|, CODE
+|?CaptureContext@crashpad@@YAXPEAU_CONTEXT@@@Z| PROC
+  ; Save general purpose registers in context.regs[i].
+  ; The original x0 can't be recovered.
+  stp x0, x1, [x0, #0x008]
+  stp x2, x3, [x0, #0x018]
+  stp x4, x5, [x0, #0x028]
+  stp x6, x7, [x0, #0x038]
+  stp x8, x9, [x0, #0x048]
+  stp x10, x11, [x0, #0x058]
+  stp x12, x13, [x0, #0x068]
+  stp x14, x15, [x0, #0x078]
+  stp x16, x17, [x0, #0x088]
+  stp x18, x19, [x0, #0x098]
+  stp x20, x21, [x0, #0x0a8]
+  stp x22, x23, [x0, #0x0b8]
+  stp x24, x25, [x0, #0x0c8]
+  stp x26, x27, [x0, #0x0d8]
+  stp x28, x29, [x0, #0x0e8]
+
+  ; The original LR can't be recovered.
+  str LR, [x0, #0x0f8]
+
+  ; Use x1 as a scratch register.
+  mov x1, SP
+  str x1, [x0, #0x100] ; context.sp
+
+  ; The link register holds the return address for this function.
+  str LR, [x0, #0x108]  ; context.pc
+
+  ; pstate should hold SPSR but NZCV are the only bits we know about.
+  mrs x1, NZCV
+
+  ; Enable Control flags, such as CONTEXT_ARM64, CONTEXT_CONTROL,
+  ; CONTEXT_INTEGER
+  ldr w1, =0x00400003
+
+  ; Set ControlFlags /0x000/ and pstate /0x004/ at the same time.
+  str x1, [x0, #0x000]
+
+  ; Restore x1 from the saved context.
+  ldr x1, [x0, #0x010]
+
+  ; TODO(https://crashpad.chromium.org/bug/300): save floating-point registers
+
+  ret
+  ENDP
+
+  END
diff --git a/util/misc/capture_context_win_arm64.obj b/util/misc/capture_context_win_arm64.obj
new file mode 100644
index 0000000..11c76a1
--- /dev/null
+++ b/util/misc/capture_context_win_arm64.obj
Binary files differ
diff --git a/util/misc/clock_test.cc b/util/misc/clock_test.cc
index 95330eb..6bfb87b 100644
--- a/util/misc/clock_test.cc
+++ b/util/misc/clock_test.cc
@@ -20,7 +20,7 @@
 
 #include "base/format_macros.h"
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 
@@ -83,7 +83,7 @@
       static_cast<uint64_t>(5E7),  // 50 milliseconds
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const uint64_t nanoseconds = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %zu, nanoseconds %" PRIu64, index, nanoseconds));
diff --git a/util/misc/from_pointer_cast_test.cc b/util/misc/from_pointer_cast_test.cc
index 1a6850f..ce53de5 100644
--- a/util/misc/from_pointer_cast_test.cc
+++ b/util/misc/from_pointer_cast_test.cc
@@ -41,7 +41,7 @@
                                                 volatile SomeType*,
                                                 const volatile SomeType*>;
 
-TYPED_TEST_CASE(FromPointerCastTest, FromPointerCastTestTypes);
+TYPED_TEST_SUITE(FromPointerCastTest, FromPointerCastTestTypes);
 
 TYPED_TEST(FromPointerCastTest, ToSigned) {
   EXPECT_EQ(FromPointerCast<int64_t>(nullptr), 0);
diff --git a/util/misc/lexing.cc b/util/misc/lexing.cc
index 6c31865..5e8fa30 100644
--- a/util/misc/lexing.cc
+++ b/util/misc/lexing.cc
@@ -32,10 +32,10 @@
   bool ConvertStringToNumber(const base::StringPiece& input, type* value) { \
     return function(input, value);                                          \
   }
-MAKE_ADAPTER(int, base::StringToInt);
-MAKE_ADAPTER(unsigned int, base::StringToUint);
-MAKE_ADAPTER(int64_t, base::StringToInt64);
-MAKE_ADAPTER(uint64_t, base::StringToUint64);
+MAKE_ADAPTER(int, base::StringToInt)
+MAKE_ADAPTER(unsigned int, base::StringToUint)
+MAKE_ADAPTER(int64_t, base::StringToInt64)
+MAKE_ADAPTER(uint64_t, base::StringToUint64)
 #undef MAKE_ADAPTER
 
 }  // namespace
diff --git a/util/misc/memory_sanitizer.h b/util/misc/memory_sanitizer.h
new file mode 100644
index 0000000..a3e0d8e
--- /dev/null
+++ b/util/misc/memory_sanitizer.h
@@ -0,0 +1,27 @@
+// Copyright 2019 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_UTIL_MISC_MEMORY_SANITIZER_H_
+#define CRASHPAD_UTIL_MISC_MEMORY_SANITIZER_H_
+
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+
+#if !defined(MEMORY_SANITIZER)
+#if HAS_FEATURE(memory_sanitizer)
+#define MEMORY_SANITIZER 1
+#endif  // HAS_FEATURE(memory_sanitizer)
+#endif  // !defined(MEMORY_SANITIZER)
+
+#endif  // CRASHPAD_UTIL_MISC_MEMORY_SANITIZER_H_
diff --git a/util/misc/metrics.cc b/util/misc/metrics.cc
index 7d191f7..bda9031 100644
--- a/util/misc/metrics.cc
+++ b/util/misc/metrics.cc
@@ -16,6 +16,7 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
 
 #if defined(OS_MACOSX)
@@ -55,43 +56,40 @@
 // static
 void Metrics::CrashReportPending(PendingReportReason reason) {
   UMA_HISTOGRAM_ENUMERATION(
-      "Crashpad.CrashReportPending",
-      static_cast<int32_t>(reason),
-      static_cast<int32_t>(PendingReportReason::kMaxValue));
+      "Crashpad.CrashReportPending", reason, PendingReportReason::kMaxValue);
 }
 
 // static
 void Metrics::CrashReportSize(FileOffset size) {
-  UMA_HISTOGRAM_CUSTOM_COUNTS(
-      "Crashpad.CrashReportSize", size, 0, 20 * 1024 * 1024, 50);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Crashpad.CrashReportSize",
+                              base::saturated_cast<uint32_t>(size),
+                              0,
+                              20 * 1024 * 1024,
+                              50);
 }
 
 // static
 void Metrics::CrashUploadAttempted(bool successful) {
-  UMA_HISTOGRAM_COUNTS("Crashpad.CrashUpload.AttemptSuccessful",
-                       static_cast<int32_t>(successful));
+  UMA_HISTOGRAM_COUNTS("Crashpad.CrashUpload.AttemptSuccessful", successful);
 }
 
 // static
 void Metrics::CrashUploadSkipped(CrashSkippedReason reason) {
   UMA_HISTOGRAM_ENUMERATION(
-      "Crashpad.CrashUpload.Skipped",
-      static_cast<int32_t>(reason),
-      static_cast<int32_t>(CrashSkippedReason::kMaxValue));
+      "Crashpad.CrashUpload.Skipped", reason, CrashSkippedReason::kMaxValue);
 }
 
 // static
 void Metrics::ExceptionCaptureResult(CaptureResult result) {
   ExceptionProcessing(ExceptionProcessingState::kFinished);
-  UMA_HISTOGRAM_ENUMERATION("Crashpad.ExceptionCaptureResult",
-                            static_cast<int32_t>(result),
-                            static_cast<int32_t>(CaptureResult::kMaxValue));
+  UMA_HISTOGRAM_ENUMERATION(
+      "Crashpad.ExceptionCaptureResult", result, CaptureResult::kMaxValue);
 }
 
 // static
 void Metrics::ExceptionCode(uint32_t exception_code) {
   base::UmaHistogramSparse("Crashpad.ExceptionCode." METRICS_OS_NAME,
-                           static_cast<int32_t>(exception_code));
+                           exception_code);
 }
 
 // static
@@ -102,15 +100,14 @@
 // static
 void Metrics::HandlerLifetimeMilestone(LifetimeMilestone milestone) {
   UMA_HISTOGRAM_ENUMERATION("Crashpad.HandlerLifetimeMilestone",
-                            static_cast<int32_t>(milestone),
-                            static_cast<int32_t>(LifetimeMilestone::kMaxValue));
+                            milestone,
+                            LifetimeMilestone::kMaxValue);
 }
 
 // static
 void Metrics::HandlerCrashed(uint32_t exception_code) {
   base::UmaHistogramSparse(
-      "Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME,
-      static_cast<int32_t>(exception_code));
+      "Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME, exception_code);
 }
 
 }  // namespace crashpad
diff --git a/util/misc/metrics.h b/util/misc/metrics.h
index 8046497..d976381 100644
--- a/util/misc/metrics.h
+++ b/util/misc/metrics.h
@@ -139,6 +139,9 @@
     //! \brief Sanitization caused this crash dump to be skipped.
     kSkippedDueToSanitization = 11,
 
+    //! \brief Failure to open a memfd caused this crash dump to be skipped.
+    kOpenMemfdFailed = 12,
+
     //! \brief The number of values in this enumeration; not a valid value.
     kMaxValue
   };
diff --git a/util/misc/paths_fuchsia.cc b/util/misc/paths_fuchsia.cc
index 0908448..6dea633 100644
--- a/util/misc/paths_fuchsia.cc
+++ b/util/misc/paths_fuchsia.cc
@@ -25,7 +25,7 @@
 // static
 bool Paths::Executable(base::FilePath* path) {
   // Assume the environment has been set up following
-  // https://fuchsia.googlesource.com/docs/+/master/namespaces.md#typical-directory-structure
+  // https://fuchsia.googlesource.com/docs/+/master/the-book/namespaces.md#typical-directory-structure
   // .
   *path = base::FilePath("/pkg/bin/app");
   return true;
diff --git a/util/misc/paths_win.cc b/util/misc/paths_win.cc
index 4c402fe..f05bdcf 100644
--- a/util/misc/paths_win.cc
+++ b/util/misc/paths_win.cc
@@ -17,6 +17,7 @@
 #include <windows.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 
 namespace crashpad {
 
@@ -24,11 +25,13 @@
 bool Paths::Executable(base::FilePath* path) {
   wchar_t executable_path[_MAX_PATH];
   unsigned int len =
-      GetModuleFileName(nullptr, executable_path, arraysize(executable_path));
+      GetModuleFileName(nullptr,
+                        executable_path,
+                        static_cast<DWORD>(base::size(executable_path)));
   if (len == 0) {
     PLOG(ERROR) << "GetModuleFileName";
     return false;
-  } else if (len >= arraysize(executable_path)) {
+  } else if (len >= base::size(executable_path)) {
     LOG(ERROR) << "GetModuleFileName";
     return false;
   }
diff --git a/util/misc/pdb_structures.cc b/util/misc/pdb_structures.cc
index c62f11c..e904dc6 100644
--- a/util/misc/pdb_structures.cc
+++ b/util/misc/pdb_structures.cc
@@ -18,5 +18,6 @@
 
 const uint32_t CodeViewRecordPDB20::kSignature;
 const uint32_t CodeViewRecordPDB70::kSignature;
+const uint32_t CodeViewRecordBuildID::kSignature;
 
 }  // namespace crashpad
diff --git a/util/misc/pdb_structures.h b/util/misc/pdb_structures.h
index d0cc9a3..834cfdc 100644
--- a/util/misc/pdb_structures.h
+++ b/util/misc/pdb_structures.h
@@ -90,12 +90,7 @@
   // UUID has a constructor, which makes it non-POD, which makes this structure
   // non-POD. In order for the default constructor to zero-initialize other
   // members, an explicit constructor must be provided.
-  CodeViewRecordPDB70()
-      : signature(),
-        uuid(),
-        age(),
-        pdb_name() {
-  }
+  CodeViewRecordPDB70() : signature(), uuid(), age(), pdb_name() {}
 
   //! \brief The magic number identifying this structure version, stored in
   //!     #signature.
@@ -127,6 +122,27 @@
   uint8_t pdb_name[1];
 };
 
+//! \brief A CodeView record containing an ELF build-id.
+//!
+//! This identifier comes from the ELF section `NT_GNU_BUILD_ID`.
+struct CodeViewRecordBuildID {
+  //! \brief The magic number identifying this structure version, stored in
+  //!     #signature.
+  //!
+  //! In a hex dump, this will appear as “LEpB” when produced by a little-endian
+  //! machine.
+  static const uint32_t kSignature = 'BpEL';
+
+  //! \brief The magic number identifying this structure version, the value of
+  //!     #kSignature.
+  uint32_t signature;
+
+  //! \brief The build ID for this object.
+  //!
+  //! This usually comes from `NT_GNU_BUILD_ID` on ELF objects.
+  uint8_t build_id[1];
+};
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_UTIL_MISC_PDB_STRUCTURES_H_
diff --git a/util/misc/random_string_test.cc b/util/misc/random_string_test.cc
index b0866c0..f5f0f32 100644
--- a/util/misc/random_string_test.cc
+++ b/util/misc/random_string_test.cc
@@ -18,7 +18,7 @@
 
 #include <set>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 
 namespace crashpad {
@@ -33,7 +33,7 @@
   const std::string allowed_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
 
   size_t character_counts[26] = {};
-  ASSERT_EQ(allowed_characters.size(), arraysize(character_counts));
+  ASSERT_EQ(allowed_characters.size(), base::size(character_counts));
 
   std::set<std::string> strings;
 
@@ -61,7 +61,7 @@
   // Make sure every character appears at least once. It is possible, but
   // extremely unlikely, for a character to not appear at all.
   for (size_t character_index = 0;
-       character_index < arraysize(character_counts);
+       character_index < base::size(character_counts);
        ++character_index) {
     EXPECT_GT(character_counts[character_index], 0u)
         << allowed_characters[character_index];
diff --git a/util/misc/time.h b/util/misc/time.h
index aabe8e6..dffe1a8 100644
--- a/util/misc/time.h
+++ b/util/misc/time.h
@@ -69,6 +69,14 @@
 
 #endif  // OS_WIN
 
+#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
+//! \brief Get the kernel boot time. Subsequent calls to this function may
+//!     return different results due to the system clock being changed or
+//!     imprecision in measuring the boot time.
+//! \return `true` on success. Otherwise, `false` with a message logged.
+bool GetBootTime(timespec* ts);
+#endif  // OS_LINUX || OS_ANDROID || DOXYGEN
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_UTIL_MISC_TIME_H_
diff --git a/util/misc/time_linux.cc b/util/misc/time_linux.cc
new file mode 100644
index 0000000..bc73533
--- /dev/null
+++ b/util/misc/time_linux.cc
@@ -0,0 +1,38 @@
+// Copyright 2019 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.
+
+#include "util/misc/time.h"
+
+#include "base/logging.h"
+
+namespace crashpad {
+
+bool GetBootTime(timespec* boot_time) {
+  timespec uptime;
+  if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) {
+    PLOG(ERROR) << "clock_gettime";
+    return false;
+  }
+
+  timespec current_time;
+  if (clock_gettime(CLOCK_REALTIME, &current_time) != 0) {
+    PLOG(ERROR) << "clock_gettime";
+    return false;
+  }
+
+  SubtractTimespec(current_time, uptime, boot_time);
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/misc/uuid_test.cc b/util/misc/uuid_test.cc
index c05c5c1..35f9ecd 100644
--- a/util/misc/uuid_test.cc
+++ b/util/misc/uuid_test.cc
@@ -20,8 +20,8 @@
 #include <string>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
 #include "base/scoped_generic.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 
@@ -95,12 +95,12 @@
   ++uuid.data_3;
   EXPECT_NE(uuid, uuid_2);
   --uuid.data_3;
-  for (size_t index = 0; index < arraysize(uuid.data_4); ++index) {
+  for (size_t index = 0; index < base::size(uuid.data_4); ++index) {
     ++uuid.data_4[index];
     EXPECT_NE(uuid, uuid_2);
     --uuid.data_4[index];
   }
-  for (size_t index = 0; index < arraysize(uuid.data_5); ++index) {
+  for (size_t index = 0; index < base::size(uuid.data_5); ++index) {
     ++uuid.data_5[index];
     EXPECT_NE(uuid, uuid_2);
     --uuid.data_5[index];
@@ -190,7 +190,7 @@
   uuid_zero.InitializeToZero();
   const std::string empty_uuid = uuid_zero.ToString();
 
-  for (size_t index = 0; index < arraysize(kCases); ++index) {
+  for (size_t index = 0; index < base::size(kCases); ++index) {
     const TestCase& test_case = kCases[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ": %s", index, test_case.uuid_string));
@@ -226,7 +226,7 @@
   };
   // clang-format on
   EXPECT_TRUE(uuid.InitializeFromString(
-      base::StringPiece16(kChar16UUID, arraysize(kChar16UUID))));
+      base::StringPiece16(kChar16UUID, base::size(kChar16UUID))));
   EXPECT_EQ(uuid.ToString(), "f32e5bdc-2681-4c73-a4e6-333ffd33b333");
 
 #if defined(OS_WIN)
diff --git a/util/net/generate_test_server_key.py b/util/net/generate_test_server_key.py
index 6e6340e..3db08ad 100755
--- a/util/net/generate_test_server_key.py
+++ b/util/net/generate_test_server_key.py
@@ -16,13 +16,35 @@
 
 import os
 import subprocess
+import sys
 
-# GN requires a Python script for actions, so this just wraps the openssl
-# command needed to generate a test private key and a certificate. These names
-# must correspond to what TestPaths::BuildArtifact() constructs.
-key = 'crashpad_util_test_key.pem'
-cert = 'crashpad_util_test_cert.pem'
-subprocess.check_call(
-    ['openssl', 'req', '-x509', '-nodes', '-subj', '/CN=localhost',
-     '-days', '365', '-newkey', 'rsa:2048', '-keyout', key, '-out', cert],
-    stderr=open(os.devnull, 'w'))
+testdata = os.path.join(os.path.dirname(__file__), 'testdata')
+key = os.path.join(testdata, 'crashpad_util_test_key.pem')
+cert = os.path.join(testdata, 'crashpad_util_test_cert.pem')
+
+with open(cert, 'w') as cert_file, open(key, 'w') as key_file:
+    MESSAGE = ('DO NOT EDIT: This file was auto-generated by ' + __file__ +
+               '\n\n')
+    cert_file.write(MESSAGE)
+    key_file.write(MESSAGE)
+
+    proc = subprocess.Popen([
+        'openssl', 'req', '-x509', '-nodes', '-subj', '/CN=localhost', '-days',
+        '3650', '-newkey', 'rsa:2048', '-keyout', '-'
+    ],
+                            stderr=open(os.devnull, 'w'),
+                            stdout=subprocess.PIPE)
+
+    contents = proc.communicate()[0]
+    dest = sys.stderr
+    for line in contents.splitlines(True):
+        if line.startswith("-----BEGIN PRIVATE KEY-----"):
+            dest = key_file
+        elif line.startswith("-----BEGIN CERTIFICATE-----"):
+            dest = cert_file
+        elif line.startswith("-----END"):
+            dest.write(line)
+            dest = sys.stderr
+            continue
+
+        dest.write(line)
diff --git a/util/net/http_body_test.cc b/util/net/http_body_test.cc
index e48cbf9..1e5a479 100644
--- a/util/net/http_body_test.cc
+++ b/util/net/http_body_test.cc
@@ -207,9 +207,9 @@
   EXPECT_EQ(actual_string, expected_string);
 }
 
-INSTANTIATE_TEST_CASE_P(VariableBufferSize,
-                        CompositeHTTPBodyStreamBufferSize,
-                        testing::Values(1, 2, 9, 16, 31, 128, 1024));
+INSTANTIATE_TEST_SUITE_P(VariableBufferSize,
+                         CompositeHTTPBodyStreamBufferSize,
+                         testing::Values(1, 2, 9, 16, 31, 128, 1024));
 
 }  // namespace
 }  // namespace test
diff --git a/util/net/http_transport.h b/util/net/http_transport.h
index f91a556..acd4e44 100644
--- a/util/net/http_transport.h
+++ b/util/net/http_transport.h
@@ -90,7 +90,7 @@
   //!     if the response body is not required.
   //!
   //! \return Whether or not the request was successful, defined as returning
-  //!     a HTTP status 200 (OK) code.
+  //!     a HTTP status code in the range 200-203 (inclusive).
   virtual bool ExecuteSynchronously(std::string* response_body) = 0;
 
  protected:
diff --git a/util/net/http_transport_mac.mm b/util/net/http_transport_mac.mm
index 8d5f78c..a433bb3 100644
--- a/util/net/http_transport_mac.mm
+++ b/util/net/http_transport_mac.mm
@@ -293,7 +293,7 @@
       return false;
     }
     NSInteger http_status = [http_response statusCode];
-    if (http_status != 200) {
+    if (http_status < 200 || http_status > 203) {
       LOG(ERROR) << base::StringPrintf("HTTP status %ld",
                                        implicit_cast<long>(http_status));
       return false;
diff --git a/util/net/http_transport_socket.cc b/util/net/http_transport_socket.cc
index f0e2dc1..4dd01b6 100644
--- a/util/net/http_transport_socket.cc
+++ b/util/net/http_transport_socket.cc
@@ -24,6 +24,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/scoped_generic.h"
+#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "util/file/file_io.h"
@@ -57,8 +58,7 @@
   static addrinfo* InvalidValue() { return nullptr; }
   static void Free(addrinfo* ai) { freeaddrinfo(ai); }
 };
-using ScopedAddrinfo =
-    base::ScopedGeneric<addrinfo*, ScopedAddrinfoTraits>;
+using ScopedAddrinfo = base::ScopedGeneric<addrinfo*, ScopedAddrinfoTraits>;
 
 class Stream {
  public:
@@ -81,7 +81,7 @@
     return LoggingReadFileExactly(fd_, data, size);
   }
 
-  bool LoggingReadToEOF(std::string* result) override{
+  bool LoggingReadToEOF(std::string* result) override {
     return crashpad::LoggingReadToEOF(fd_, result);
   }
 
@@ -366,7 +366,7 @@
 
   FileOperationResult data_bytes;
   do {
-    constexpr size_t kCRLFSize = arraysize(kCRLFTerminator) - 1;
+    constexpr size_t kCRLFSize = base::size(kCRLFTerminator) - 1;
     struct __attribute__((packed)) {
       char size[8];
       char crlf[2];
@@ -394,11 +394,13 @@
       // placed immediately following the used portion of buf.data, even if
       // buf.data is not full.
 
-      // Not snprintf because non-null terminated is desired.
-      int rv = sprintf(
-          buf.size, "%08x", base::checked_cast<unsigned int>(data_bytes));
-      DCHECK_GE(rv, 0);
+      char tmp[9];
+      int rv = snprintf(tmp,
+                        sizeof(tmp),
+                        "%08x",
+                        base::checked_cast<unsigned int>(data_bytes));
       DCHECK_EQ(static_cast<size_t>(rv), sizeof(buf.size));
+      strncpy(buf.size, tmp, sizeof(buf.size));
       DCHECK_NE(buf.size[sizeof(buf.size) - 1], '\0');
 
       memcpy(&buf.crlf[0], kCRLFTerminator, kCRLFSize);
@@ -457,10 +459,18 @@
     LOG(ERROR) << "ReadLine";
     return false;
   }
-  static constexpr const char kHttp10[] = "HTTP/1.0 200 ";
-  static constexpr const char kHttp11[] = "HTTP/1.1 200 ";
-  return StartsWith(response_line, kHttp10, strlen(kHttp10)) ||
-         StartsWith(response_line, kHttp11, strlen(kHttp11));
+  static constexpr const char kHttp10[] = "HTTP/1.0 ";
+  static constexpr const char kHttp11[] = "HTTP/1.1 ";
+  if (!(StartsWith(response_line, kHttp10, strlen(kHttp10)) ||
+        StartsWith(response_line, kHttp11, strlen(kHttp11))) ||
+      response_line.size() < strlen(kHttp10) + 3 ||
+      response_line.at(strlen(kHttp10) + 3) != ' ') {
+    return false;
+  }
+  unsigned int http_status = 0;
+  return base::StringToUint(response_line.substr(strlen(kHttp10), 3),
+                            &http_status) &&
+         http_status >= 200 && http_status <= 203;
 }
 
 bool ReadResponseHeaders(Stream* stream, HTTPHeaders* headers) {
@@ -537,7 +547,8 @@
   }
 
 #if !defined(CRASHPAD_USE_BORINGSSL)
-  CHECK(scheme == "http");
+  CHECK(scheme == "http") << "Got " << scheme << " for scheme in '" << url()
+                          << "'";
 #endif
 
   base::ScopedFD sock(CreateSocket(hostname, port));
diff --git a/util/net/http_transport_test.cc b/util/net/http_transport_test.cc
index 7b5f41d..d6a7675 100644
--- a/util/net/http_transport_test.cc
+++ b/util/net/http_transport_test.cc
@@ -54,10 +54,10 @@
 
 class HTTPTransportTestFixture : public MultiprocessExec {
  public:
-  using RequestValidator =
-      void(*)(HTTPTransportTestFixture*, const std::string&);
+  using RequestValidator = void (*)(HTTPTransportTestFixture*,
+                                    const std::string&);
 
-  HTTPTransportTestFixture(const base::FilePath::StringType& scheme,
+  HTTPTransportTestFixture(const std::string& scheme,
                            const HTTPHeaders& headers,
                            std::unique_ptr<HTTPBodyStream> body_stream,
                            uint16_t http_response_code,
@@ -74,22 +74,21 @@
 #if defined(OS_WIN)
             FILE_PATH_LITERAL(".exe")
 #endif
-        );
+    );
 
-    if (ToUTF8IfWin(scheme) == "http") {
+    if (scheme == "http") {
       scheme_and_host_ = "http://localhost";
       SetChildCommand(server_path, nullptr);
     } else {
       std::vector<std::string> args;
-      cert_ = TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"),
-                                       FILE_PATH_LITERAL("cert"),
-                                       TestPaths::FileType::kCertificate);
+      cert_ = TestPaths::TestDataRoot().Append(
+          FILE_PATH_LITERAL("util/net/testdata/crashpad_util_test_cert.pem"));
       args.push_back(ToUTF8IfWin(cert_.value()));
-      args.emplace_back(ToUTF8IfWin(
-          TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"),
-                                   FILE_PATH_LITERAL("key"),
-                                   TestPaths::FileType::kCertificate)
-              .value()));
+      args.emplace_back(
+          ToUTF8IfWin(TestPaths::TestDataRoot()
+                          .Append(FILE_PATH_LITERAL(
+                              "util/net/testdata/crashpad_util_test_key.pem"))
+                          .value()));
       SetChildCommand(server_path, &args);
       scheme_and_host_ = "https://localhost";
     }
@@ -117,9 +116,8 @@
     // 200.
     const std::string random_string = RandomString();
 
-    ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(),
-                                 random_string.c_str(),
-                                 random_string.size()));
+    ASSERT_TRUE(LoggingWriteFile(
+        WritePipeHandle(), random_string.c_str(), random_string.size()));
 
     // Now execute the HTTP request.
     std::unique_ptr<HTTPTransport> transport(HTTPTransport::Create());
@@ -137,7 +135,7 @@
 
     std::string response_body;
     bool success = transport->ExecuteSynchronously(&response_body);
-    if (response_code_ == 200) {
+    if (response_code_ >= 200 && response_code_ <= 203) {
       EXPECT_TRUE(success);
       std::string expect_response_body = random_string + "\r\n";
       EXPECT_EQ(response_body, expect_response_body);
@@ -246,8 +244,7 @@
   EXPECT_EQ(request.substr(body_start), expected);
 }
 
-class HTTPTransport
-    : public testing::TestWithParam<base::FilePath::StringType> {};
+class HTTPTransport : public testing::TestWithParam<std::string> {};
 
 TEST_P(HTTPTransport, ValidFormData) {
   HTTPMultipartBuilder builder;
@@ -257,8 +254,8 @@
   HTTPHeaders headers;
   builder.PopulateContentHeaders(&headers);
 
-  HTTPTransportTestFixture test(GetParam(),
-      headers, builder.GetBodyStream(), 200, &ValidFormData);
+  HTTPTransportTestFixture test(
+      GetParam(), headers, builder.GetBodyStream(), 200, &ValidFormData);
   test.Run();
 }
 
@@ -289,8 +286,8 @@
   HTTPMultipartBuilder builder;
   HTTPHeaders headers;
   headers[kContentType] = kTextPlain;
-  HTTPTransportTestFixture test(GetParam(), headers, builder.GetBodyStream(),
-      404, &ErrorResponse);
+  HTTPTransportTestFixture test(
+      GetParam(), headers, builder.GetBodyStream(), 404, &ErrorResponse);
   test.Run();
 }
 
@@ -321,13 +318,12 @@
   headers[kContentType] = kTextPlain;
   headers[kContentLength] = base::StringPrintf("%" PRIuS, strlen(kTextBody));
 
-  HTTPTransportTestFixture test(GetParam(),
-      headers, std::move(body_stream), 200, &UnchunkedPlainText);
+  HTTPTransportTestFixture test(
+      GetParam(), headers, std::move(body_stream), 200, &UnchunkedPlainText);
   test.Run();
 }
 
-void RunUpload33k(const base::FilePath::StringType& scheme,
-                  bool has_content_length) {
+void RunUpload33k(const std::string& scheme, bool has_content_length) {
   // On macOS, NSMutableURLRequest winds up calling into a CFReadStream’s Read()
   // callback with a 32kB buffer. Make sure that it’s able to get everything
   // when enough is available to fill this buffer, requiring more than one
@@ -364,20 +360,29 @@
   RunUpload33k(GetParam(), false);
 }
 
-#if defined(CRASHPAD_USE_BORINGSSL)
+// This should be on for Fuchsia, but DX-382. Debug and re-enabled.
+#if defined(CRASHPAD_USE_BORINGSSL) && !defined(OS_FUCHSIA)
 // The test server requires BoringSSL or OpenSSL, so https in tests can only be
 // enabled where that's readily available. Additionally on Linux, the bots fail
 // lacking libcrypto.so.1.1, so disabled there for now. On Mac, they could also
 // likely be enabled relatively easily, if HTTPTransportMac learned to respect
 // the user-supplied cert.
-INSTANTIATE_TEST_CASE_P(HTTPTransport,
-                        HTTPTransport,
-                        testing::Values(FILE_PATH_LITERAL("http"),
-                                        FILE_PATH_LITERAL("https")));
+//
+// If tests with boringssl are failing because of expired certificates, try
+// re-running generate_test_server_key.py.
+INSTANTIATE_TEST_SUITE_P(HTTPTransport,
+                         HTTPTransport,
+                         testing::Values("http", "https"),
+                         [](const testing::TestParamInfo<std::string>& info) {
+                           return info.param;
+                         });
 #else
-INSTANTIATE_TEST_CASE_P(HTTPTransport,
-                        HTTPTransport,
-                        testing::Values(FILE_PATH_LITERAL("http")));
+INSTANTIATE_TEST_SUITE_P(HTTPTransport,
+                         HTTPTransport,
+                         testing::Values("http"),
+                         [](const testing::TestParamInfo<std::string>& info) {
+                           return info.param;
+                         });
 #endif
 
 }  // namespace
diff --git a/util/net/http_transport_win.cc b/util/net/http_transport_win.cc
index 18d343c..06ecc4f 100644
--- a/util/net/http_transport_win.cc
+++ b/util/net/http_transport_win.cc
@@ -25,14 +25,15 @@
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/scoped_generic.h"
+#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "package.h"
 #include "util/file/file_io.h"
-#include "util/numeric/safe_assignment.h"
 #include "util/net/http_body.h"
+#include "util/numeric/safe_assignment.h"
 #include "util/win/module_version.h"
 
 namespace crashpad {
@@ -65,6 +66,8 @@
     user_agent.append("x86");
 #elif defined(ARCH_CPU_X86_64)
     user_agent.append("x64");
+#elif defined(ARCH_CPU_ARM64)
+    user_agent.append("arm64");
 #else
 #error Port
 #endif
@@ -93,8 +96,8 @@
                              error_code,
                              0,
                              msgbuf,
-                             arraysize(msgbuf),
-                             NULL);
+                             static_cast<DWORD>(base::size(msgbuf)),
+                             nullptr);
   if (!len) {
     return base::StringPrintf("%s: error 0x%lx while retrieving error 0x%lx",
                               extra,
@@ -375,7 +378,7 @@
     return false;
   }
 
-  if (status_code != 200) {
+  if (status_code < 200 || status_code > 203) {
     LOG(ERROR) << base::StringPrintf("HTTP status %lu", status_code);
     return false;
   }
diff --git a/util/net/testdata/crashpad_util_test_cert.pem b/util/net/testdata/crashpad_util_test_cert.pem
new file mode 100644
index 0000000..5a2261e
--- /dev/null
+++ b/util/net/testdata/crashpad_util_test_cert.pem
@@ -0,0 +1,21 @@
+DO NOT EDIT: This file was auto-generated by ./util/net/generate_test_server_key.py
+
+-----BEGIN CERTIFICATE-----
+MIIDCDCCAfCgAwIBAgITF9h5BaPfzje590HrE1UF+f9PVDANBgkqhkiG9w0BAQsF
+ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTkxMDMwMjIyNDE4WhcNMjkxMDI3
+MjIyNDE4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCrT0iXfixTPH6nBxgvCgiKkSTeVTSvfhcoUYA7yPZj/1XH
+VYfECoGyvFN4/5g/GL4c721XZmI92Twg2Oa3HksRY8VlNXGNwqbt+wftSLOz+dOE
+bsJ3+NsBtXuaIphFPA9u0h+yrplBtM/dxLTW5v8e3OuEMBXa+94dp4y78u21UkzM
+uWB66tduT7/f0S9P68XhksYwwwvCn5lu258wHKNM5mb4CTwOcUgyyprlS2FS3Fsx
+pIsg9wDfQy5rHINxcZGZZ8Ht4KOJ7egwhfiP4XEiLK6+5DZoOQtrkEUFfLL4i5sx
++eOeD12GP+Dxs6KHYoejGftNx/7U0E2GVMWwWzstAgMBAAGjUzBRMB0GA1UdDgQW
+BBT8y2bnyty/YWcvzlE9JIAPG7/q4DAfBgNVHSMEGDAWgBT8y2bnyty/YWcvzlE9
+JIAPG7/q4DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCF+R21
+Cd1fgITfhYJE7JAV3jLfact5h/2MP5UwgtODfZt2Idwxs8qqwPB0g7WQqEjrPfTv
+6y90AEwr2L8e+wXGnTKtWelCqzztFGov4Hj+rd158eOmEo0IPGrx8dwqLQbw91uU
+cq4onf5iuHkOt99TmIqVYn2zaOHbOF2YYyU052X+9XE/Z5fhibX5THfEG3w0+XM6
+aKEGIM/MxfVaLA8yDXFxhDXHBrH+QAAXTQaC8exnp+po/psyJARD0sM509MeTdAv
+po9JyIzesNAsLW4I7kL8I5i8e+WN79lX2jgwaWMxPmHadYN3mtoltpmFM40oFN6q
+8wjuU07eN/G79c3B
+-----END CERTIFICATE-----
diff --git a/util/net/testdata/crashpad_util_test_key.pem b/util/net/testdata/crashpad_util_test_key.pem
new file mode 100644
index 0000000..b0d6973
--- /dev/null
+++ b/util/net/testdata/crashpad_util_test_key.pem
@@ -0,0 +1,30 @@
+DO NOT EDIT: This file was auto-generated by ./util/net/generate_test_server_key.py
+
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrT0iXfixTPH6n
+BxgvCgiKkSTeVTSvfhcoUYA7yPZj/1XHVYfECoGyvFN4/5g/GL4c721XZmI92Twg
+2Oa3HksRY8VlNXGNwqbt+wftSLOz+dOEbsJ3+NsBtXuaIphFPA9u0h+yrplBtM/d
+xLTW5v8e3OuEMBXa+94dp4y78u21UkzMuWB66tduT7/f0S9P68XhksYwwwvCn5lu
+258wHKNM5mb4CTwOcUgyyprlS2FS3FsxpIsg9wDfQy5rHINxcZGZZ8Ht4KOJ7egw
+hfiP4XEiLK6+5DZoOQtrkEUFfLL4i5sx+eOeD12GP+Dxs6KHYoejGftNx/7U0E2G
+VMWwWzstAgMBAAECggEANEgJDnrqSijfOlh27/wu6SMszlHQt3JS3PIqoZROZexK
+IICg45qVRJgnHXlb3H3Pn4MOqqrLdraynA+2MdKj9FWvq5io5CuwyFZhb/BNL7Mh
+83veC8E+DYJ2i27da9vNlfO4ys5wZVYqTjM3QZLT73Zaxkfqk59khUZaNA4Kr9hD
+EBs9gaDMATyIAN5eAbYTVxgobZ+7OjCNmQ46x2KOg6Wwg/ZQEXtqFoDB3aCNVfTz
+OxlLb5XWF9PvqW0p1wFpF0XEDipkVtjorjp3mHXRuq0gS9Gev1ChQej1yVqMee9J
+jnVK0k7qZw4WLStxBjHqWlBnYLcXLb4f2c6cssA6YQKBgQDfqtYFjU3HCeoRaAoP
+MWTSW2PX8fitnp0O4e1yHymDgtirepvPL8pa7rTBkHuR6lfXNjY0qD0NPsbr81nG
+kNv/btLOK1YPw6f468S6DMsOzGPIWWE+jkhF/1iUTMUQLvOM3lCLEi0ayZ26yx1T
+F2wO2u1kGc1eJ8wn6TccsYiguQKBgQDEEt7eGQMkxLFV4N5e5iunHBvQg+EgfLZZ
+bnuftwruqafmCht/BZgmKQD+e6B6h3nC/iaKET101zpWL95x7Ayd1+mg6Rmdzl87
+ctftScrNLYUTl36BuhP82Y9HvLgf20xrEwBGivc0QtqhMAz/DJoknEM/29LmWEsZ
+VwvWQxhsFQKBgQCrOcJMT8d6Fznsh2QkC2EutK3ztBb2+xUrPoQjOH30YqfyZpN/
+Agv8nv8bq7sdknQamjLXDvBmAmgQW6SfoWf53OJe2Mgym0stAXkCIScWNhwxVVNf
+q1bi1z79kOPPptHmRo8MWCbVegFY7YOOh8C+gpT3a9VPPlJJP31kZvi8aQKBgDcD
+ZGzEb9FdLrR9x2axBgZ5KIS0u/G1jCRDj4Qcg4C7MVSl+VkGZM4wKws7/KbkZBGF
+5aJPfALQcJnGDI/CPzf6YJ65SGqygJ3ZdyQo1DIFV5VLqD8Vyo3jLQRfuvmVOjfA
+uQ8R5pJPP7CCHuNg0c772RKNxvrCQy/08GlJogyRAoGAK6A/8r/iDRJipJSOZdCl
+MreiJvjFOQrORhy+TeDGn02EHK0lijzj1I7SlgfUDLWVnQpBlk12wV6aHGUj2NlG
+Ctk15MFs6gggPV1Fmt6PeviE1e9E4+SDWX35Jhe8JDIqxdgZ0HW/S9Nl4Xt3owhw
+/DSneMQd7X0Tdq7lesJjd3o=
+-----END PRIVATE KEY-----
diff --git a/util/net/tls.gni b/util/net/tls.gni
new file mode 100644
index 0000000..eb96845
--- /dev/null
+++ b/util/net/tls.gni
@@ -0,0 +1,23 @@
+# 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.
+
+import("../../build/crashpad_buildconfig.gni")
+
+declare_args() {
+  # TODO(scottmg): https://crbug.com/crashpad/266 fuchsia:DX-690: BoringSSL
+  # was removed from the Fuchsia SDK. Re-enable it when we have a way to acquire
+  # a BoringSSL lib again.
+  crashpad_use_boringssl_for_http_transport_socket =
+      crashpad_is_in_fuchsia || (crashpad_is_linux && crashpad_is_in_chromium)
+}
diff --git a/util/numeric/checked_address_range_test.cc b/util/numeric/checked_address_range_test.cc
index e6bd9ec..08bc551 100644
--- a/util/numeric/checked_address_range_test.cc
+++ b/util/numeric/checked_address_range_test.cc
@@ -19,7 +19,7 @@
 #include <limits>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -119,7 +119,7 @@
       {0xffffffffffffffff, 1, kInvalid},
   };
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS
                                     ", base 0x%" PRIx64 ", size 0x%" PRIx64,
@@ -170,7 +170,7 @@
   CheckedAddressRange parent_range_32(false, 0x2000, 0x1000);
   ASSERT_TRUE(parent_range_32.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ", value 0x%" PRIx64, index, testcase.value));
@@ -227,7 +227,7 @@
   CheckedAddressRange parent_range_32(false, 0x2000, 0x1000);
   ASSERT_TRUE(parent_range_32.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS
                                     ", base 0x%" PRIx64 ", size 0x%" PRIx64,
diff --git a/util/numeric/checked_range_test.cc b/util/numeric/checked_range_test.cc
index 04f6bb1..9d611e8 100644
--- a/util/numeric/checked_range_test.cc
+++ b/util/numeric/checked_range_test.cc
@@ -20,7 +20,7 @@
 #include <limits>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 
@@ -78,7 +78,7 @@
       {0xffffffff, 0xffffffff, false},
   };
 
-  for (size_t index = 0; index < arraysize(kUnsignedTestData); ++index) {
+  for (size_t index = 0; index < base::size(kUnsignedTestData); ++index) {
     const auto& testcase = kUnsignedTestData[index];
     SCOPED_TRACE(base::StringPrintf("unsigned index %" PRIuS
                                     ", base 0x%x, size 0x%x",
@@ -140,7 +140,7 @@
       {-1, 0xffffffff, false},
   };
 
-  for (size_t index = 0; index < arraysize(kSignedTestData); ++index) {
+  for (size_t index = 0; index < base::size(kSignedTestData); ++index) {
     const auto& testcase = kSignedTestData[index];
     SCOPED_TRACE(base::StringPrintf("signed index %" PRIuS
                                     ", base 0x%x, size 0x%x",
@@ -186,7 +186,7 @@
   CheckedRange<uint32_t> parent_range(0x2000, 0x1000);
   ASSERT_TRUE(parent_range.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %" PRIuS ", value 0x%x", index, testcase.value));
@@ -234,7 +234,7 @@
   CheckedRange<uint32_t> parent_range(0x2000, 0x1000);
   ASSERT_TRUE(parent_range.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%x, size 0x%x",
                                     index,
@@ -287,7 +287,7 @@
   CheckedRange<uint32_t> first_range(0x2000, 0x1000);
   ASSERT_TRUE(first_range.IsValid());
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto& testcase = kTestData[index];
     SCOPED_TRACE(base::StringPrintf("index %" PRIuS ", base 0x%x, size 0x%x",
                                     index,
diff --git a/util/posix/close_multiple.cc b/util/posix/close_multiple.cc
index 02c8a76..238f158 100644
--- a/util/posix/close_multiple.cc
+++ b/util/posix/close_multiple.cc
@@ -25,6 +25,7 @@
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "build/build_config.h"
 #include "util/file/directory_reader.h"
@@ -152,7 +153,7 @@
   int maxfilesperproc;
   size_t maxfilesperproc_size = sizeof(maxfilesperproc);
   if (sysctl(oid,
-             arraysize(oid),
+             base::size(oid),
              &maxfilesperproc,
              &maxfilesperproc_size,
              nullptr,
diff --git a/util/posix/close_stdio.cc b/util/posix/close_stdio.cc
index cc9cdac..02bd4a9 100644
--- a/util/posix/close_stdio.cc
+++ b/util/posix/close_stdio.cc
@@ -20,6 +20,7 @@
 
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
+#include "base/macros.h"
 #include "base/posix/eintr_wrapper.h"
 
 namespace crashpad {
diff --git a/util/posix/double_fork_and_exec.cc b/util/posix/double_fork_and_exec.cc
index df74f70..e4ad998 100644
--- a/util/posix/double_fork_and_exec.cc
+++ b/util/posix/double_fork_and_exec.cc
@@ -27,9 +27,12 @@
 namespace crashpad {
 
 bool DoubleForkAndExec(const std::vector<std::string>& argv,
+                       const std::vector<std::string>* envp,
                        int preserve_fd,
                        bool use_path,
                        void (*child_function)()) {
+  DCHECK(!envp || !use_path);
+
   // argv_c contains const char* pointers and is terminated by nullptr. This is
   // suitable for passing to execv(). Although argv_c is not used in the parent
   // process, it must be built in the parent process because it’s unsafe to do
@@ -41,6 +44,15 @@
   }
   argv_c.push_back(nullptr);
 
+  std::vector<const char*> envp_c;
+  if (envp) {
+    envp_c.reserve(envp->size() + 1);
+    for (const std::string& variable : *envp) {
+      envp_c.push_back(variable.c_str());
+    }
+    envp_c.push_back(nullptr);
+  }
+
   // Double-fork(). The three processes involved are parent, child, and
   // grandchild. The grandchild will call execv(). The child exits immediately
   // after spawning the grandchild, so the grandchild becomes an orphan and its
@@ -102,6 +114,13 @@
     // const_cast is safe.
     char* const* argv_for_execv = const_cast<char* const*>(&argv_c[0]);
 
+    if (envp) {
+      // This cast is safe for the same reason that the argv_for_execv cast is.
+      char* const* envp_for_execv = const_cast<char* const*>(&envp_c[0]);
+      execve(argv_for_execv[0], argv_for_execv, envp_for_execv);
+      PLOG(FATAL) << "execve " << argv_for_execv[0];
+    }
+
     if (use_path) {
       execvp(argv_for_execv[0], argv_for_execv);
       PLOG(FATAL) << "execvp " << argv_for_execv[0];
diff --git a/util/posix/double_fork_and_exec.h b/util/posix/double_fork_and_exec.h
index df340d0..02fc0f2 100644
--- a/util/posix/double_fork_and_exec.h
+++ b/util/posix/double_fork_and_exec.h
@@ -36,6 +36,9 @@
 //!
 //! \param[in] argv The argument vector to start the grandchild process with.
 //!     `argv[0]` is used as the path to the executable.
+//! \param[in] envp A vector of environment variables of the form `var=value` to
+//!     be passed to `execve()`. If this value is `nullptr`, the current
+//!     environment is used.
 //! \param[in] preserve_fd A file descriptor to be inherited by the grandchild
 //!     process. This file descriptor is inherited in addition to the three file
 //!     descriptors associated with the standard input/output streams. Use `-1`
@@ -49,6 +52,9 @@
 //!     that this function will run in the context of a forked process, and must
 //!     be safe for that purpose.
 //!
+//! Setting both \a envp to a value other than `nullptr` and \a use_path to
+//! `true` is not currently supported.
+//!
 //! \return `true` on success, and `false` on failure with a message logged.
 //!     Only failures that occur in the parent process that indicate a definite
 //!     failure to start the the grandchild are reported in the return value.
@@ -58,6 +64,7 @@
 //!     failures, for example, by observing a failure to perform a successful
 //!     handshake with the grandchild process.
 bool DoubleForkAndExec(const std::vector<std::string>& argv,
+                       const std::vector<std::string>* envp,
                        int preserve_fd,
                        bool use_path,
                        void (*child_function)());
diff --git a/util/posix/process_info.h b/util/posix/process_info.h
index 18a9cd9..7017cc6 100644
--- a/util/posix/process_info.h
+++ b/util/posix/process_info.h
@@ -174,6 +174,7 @@
   // multiple successive calls will always produce the same return value and out
   // parameters. This is necessary for intergration with the Snapshot interface.
   // See https://crashpad.chromium.org/bug/9.
+  PtraceConnection* connection_;
   std::set<gid_t> supplementary_groups_;
   mutable timeval start_time_;
   pid_t pid_;
diff --git a/util/posix/process_info_linux.cc b/util/posix/process_info_linux.cc
index 10c94b3..3ffd96a 100644
--- a/util/posix/process_info_linux.cc
+++ b/util/posix/process_info_linux.cc
@@ -20,13 +20,16 @@
 #include "base/logging.h"
 #include "util/file/delimited_file_reader.h"
 #include "util/file/file_reader.h"
+#include "util/file/string_file.h"
 #include "util/linux/proc_stat_reader.h"
 #include "util/misc/lexing.h"
+#include "util/misc/time.h"
 
 namespace crashpad {
 
 ProcessInfo::ProcessInfo()
-    : supplementary_groups_(),
+    : connection_(),
+      supplementary_groups_(),
       start_time_(),
       pid_(-1),
       ppid_(-1),
@@ -45,16 +48,19 @@
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
   DCHECK(connection);
 
+  connection_ = connection;
   pid_ = connection->GetProcessID();
   is_64_bit_ = connection->Is64Bit();
 
   {
     char path[32];
     snprintf(path, sizeof(path), "/proc/%d/status", pid_);
-    FileReader status_file;
-    if (!status_file.Open(base::FilePath(path))) {
+    std::string contents;
+    if (!connection->ReadFileContents(base::FilePath(path), &contents)) {
       return false;
     }
+    StringFile status_file;
+    status_file.SetString(contents);
 
     DelimitedFileReader status_file_line_reader(&status_file);
 
@@ -230,10 +236,16 @@
   if (start_time_initialized_.is_uninitialized()) {
     start_time_initialized_.set_invalid();
     ProcStatReader reader;
-    if (!reader.Initialize(pid_)) {
+    if (!reader.Initialize(connection_, pid_)) {
       return false;
     }
-    if (!reader.StartTime(&start_time_)) {
+    timespec boot_time_ts;
+    if (!GetBootTime(&boot_time_ts)) {
+      return false;
+    }
+    timeval boot_time;
+    TimespecToTimeval(boot_time_ts, &boot_time);
+    if (!reader.StartTime(boot_time, &start_time_)) {
       return false;
     }
     start_time_initialized_.set_valid();
@@ -252,10 +264,12 @@
 
   char path[32];
   snprintf(path, sizeof(path), "/proc/%d/cmdline", pid_);
-  FileReader cmdline_file;
-  if (!cmdline_file.Open(base::FilePath(path))) {
+  std::string contents;
+  if (!connection_->ReadFileContents(base::FilePath(path), &contents)) {
     return false;
   }
+  StringFile cmdline_file;
+  cmdline_file.SetString(contents);
 
   DelimitedFileReader cmdline_file_field_reader(&cmdline_file);
 
diff --git a/util/posix/process_info_mac.cc b/util/posix/process_info_mac.cc
index fe9fb65..9e86e08 100644
--- a/util/posix/process_info_mac.cc
+++ b/util/posix/process_info_mac.cc
@@ -18,6 +18,7 @@
 
 #include "base/logging.h"
 #include "base/mac/mach_logging.h"
+#include "base/stl_util.h"
 
 namespace crashpad {
 
@@ -32,7 +33,7 @@
 
   int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
   size_t len = sizeof(kern_proc_info_);
-  if (sysctl(mib, arraysize(mib), &kern_proc_info_, &len, nullptr, 0) != 0) {
+  if (sysctl(mib, base::size(mib), &kern_proc_info_, &len, nullptr, 0) != 0) {
     PLOG(ERROR) << "sysctl for pid " << pid;
     return false;
   }
@@ -111,7 +112,7 @@
   const short ngroups = kern_proc_info_.kp_eproc.e_ucred.cr_ngroups;
   DCHECK_GE(ngroups, 0);
   DCHECK_LE(static_cast<size_t>(ngroups),
-            arraysize(kern_proc_info_.kp_eproc.e_ucred.cr_groups));
+            base::size(kern_proc_info_.kp_eproc.e_ucred.cr_groups));
 
   const gid_t* groups = kern_proc_info_.kp_eproc.e_ucred.cr_groups;
   return std::set<gid_t>(&groups[0], &groups[ngroups]);
@@ -168,7 +169,7 @@
   do {
     int mib[] = {CTL_KERN, KERN_PROCARGS2, pid};
     int rv =
-        sysctl(mib, arraysize(mib), nullptr, &args_size_estimate, nullptr, 0);
+        sysctl(mib, base::size(mib), nullptr, &args_size_estimate, nullptr, 0);
     if (rv != 0) {
       PLOG(ERROR) << "sysctl (size) for pid " << pid;
       return false;
@@ -176,7 +177,7 @@
 
     args_size = args_size_estimate + 1;
     args.resize(args_size);
-    rv = sysctl(mib, arraysize(mib), &args[0], &args_size, nullptr, 0);
+    rv = sysctl(mib, base::size(mib), &args[0], &args_size, nullptr, 0);
     if (rv != 0) {
       PLOG(ERROR) << "sysctl (data) for pid " << pid;
       return false;
diff --git a/util/posix/process_info_test.cc b/util/posix/process_info_test.cc
index 6154ef7..69390fa 100644
--- a/util/posix/process_info_test.cc
+++ b/util/posix/process_info_test.cc
@@ -14,6 +14,7 @@
 
 #include "util/posix/process_info.h"
 
+#include <sys/utsname.h>
 #include <time.h>
 
 #include <algorithm>
@@ -21,6 +22,7 @@
 #include <string>
 #include <vector>
 
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -29,6 +31,7 @@
 #include "test/multiprocess.h"
 #include "util/file/file_io.h"
 #include "util/misc/implicit_cast.h"
+#include "util/string/split_string.h"
 
 #if defined(OS_LINUX) || defined(OS_ANDROID)
 #include "util/linux/direct_ptrace_connection.h"
@@ -93,11 +96,39 @@
   time(&now);
   EXPECT_LE(start_time.tv_sec, now);
 
+  const std::vector<std::string>& expect_argv = GetMainArguments();
+
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+  // Prior to Linux 4.2, the kernel only allowed reading a single page from
+  // /proc/<pid>/cmdline, causing any further arguments to be truncated. Disable
+  // testing arguments in this case.
+  // TODO(jperaza): The main arguments are stored on the main thread's stack
+  // (and so should be included in dumps automatically), and
+  // ProcessInfo::Arguments() might be updated to read the arguments directly,
+  // rather than via procfs on older kernels.
+  utsname uts;
+  ASSERT_EQ(uname(&uts), 0) << ErrnoMessage("uname");
+  std::vector<std::string> parts = SplitString(uts.release, '.');
+  ASSERT_GE(parts.size(), 2u);
+
+  int major, minor;
+  ASSERT_TRUE(base::StringToInt(parts[0], &major));
+  ASSERT_TRUE(base::StringToInt(parts[1], &minor));
+
+  size_t argv_size = 0;
+  for (const auto& arg : expect_argv) {
+    argv_size += arg.size() + 1;
+  }
+
+  if ((major < 4 || (major == 4 && minor < 2)) &&
+      argv_size > static_cast<size_t>(getpagesize())) {
+    return;
+  }
+#endif  // OS_ANDROID || OS_LINUX
+
   std::vector<std::string> argv;
   ASSERT_TRUE(process_info.Arguments(&argv));
 
-  const std::vector<std::string>& expect_argv = GetMainArguments();
-
   // expect_argv always contains the initial view of the arguments at the time
   // the program was invoked. argv may contain this view, or it may contain the
   // current view of arguments after gtest argv processing. argv may be a subset
diff --git a/util/posix/scoped_mmap.cc b/util/posix/scoped_mmap.cc
index 4860cec..fbf47fa 100644
--- a/util/posix/scoped_mmap.cc
+++ b/util/posix/scoped_mmap.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/numerics/safe_math.h"
 
 namespace {
@@ -32,6 +33,11 @@
   return true;
 }
 
+size_t RoundPage(size_t size) {
+  const size_t kPageMask = base::checked_cast<size_t>(getpagesize()) - 1;
+  return (size + kPageMask) & ~kPageMask;
+}
+
 }  // namespace
 
 namespace crashpad {
@@ -40,7 +46,7 @@
 
 ScopedMmap::~ScopedMmap() {
   if (is_valid()) {
-    Munmap(reinterpret_cast<uintptr_t>(addr_), len_);
+    Munmap(reinterpret_cast<uintptr_t>(addr_), RoundPage(len_));
   }
 }
 
@@ -50,26 +56,28 @@
 
 bool ScopedMmap::ResetAddrLen(void* addr, size_t len) {
   const uintptr_t new_addr = reinterpret_cast<uintptr_t>(addr);
+  const size_t new_len_round = RoundPage(len);
 
   if (addr == MAP_FAILED) {
     DCHECK_EQ(len, 0u);
   } else {
     DCHECK_NE(len, 0u);
     DCHECK_EQ(new_addr % getpagesize(), 0u);
-    DCHECK_EQ(len % getpagesize(), 0u);
-    DCHECK((base::CheckedNumeric<uintptr_t>(new_addr) + (len - 1)).IsValid());
+    DCHECK((base::CheckedNumeric<uintptr_t>(new_addr) + (new_len_round - 1))
+               .IsValid());
   }
 
   bool result = true;
 
   if (is_valid()) {
     const uintptr_t old_addr = reinterpret_cast<uintptr_t>(addr_);
+    const size_t old_len_round = RoundPage(len_);
     if (old_addr < new_addr) {
-      result &= Munmap(old_addr, std::min(len_, new_addr - old_addr));
+      result &= Munmap(old_addr, std::min(old_len_round, new_addr - old_addr));
     }
-    if (old_addr + len_ > new_addr + len) {
-      uintptr_t unmap_start = std::max(old_addr, new_addr + len);
-      result &= Munmap(unmap_start, old_addr + len_ - unmap_start);
+    if (old_addr + old_len_round > new_addr + new_len_round) {
+      uintptr_t unmap_start = std::max(old_addr, new_addr + new_len_round);
+      result &= Munmap(unmap_start, old_addr + old_len_round - unmap_start);
     }
   }
 
@@ -104,7 +112,7 @@
 }
 
 bool ScopedMmap::Mprotect(int prot) {
-  if (mprotect(addr_, len_, prot) < 0) {
+  if (mprotect(addr_, RoundPage(len_), prot) < 0) {
     PLOG(ERROR) << "mprotect";
     return false;
   }
diff --git a/util/posix/scoped_mmap.h b/util/posix/scoped_mmap.h
index b0ff3dc..9f22372 100644
--- a/util/posix/scoped_mmap.h
+++ b/util/posix/scoped_mmap.h
@@ -91,6 +91,11 @@
   }
 
   //! \brief Returns the size of the memory-mapped region.
+  //!
+  //! This is the value originally passed to ResetAddrLen() or ResetMmap(), or
+  //! after Reset(), `0`. It may not be a round number of pages. Providing the
+  //! passed-in value is intended to ease tracking the intended lengths of
+  //! memory-mapped regions backed by files whose sizes are not whole pages.
   size_t len() const { return len_; }
 
  private:
diff --git a/util/posix/scoped_mmap_test.cc b/util/posix/scoped_mmap_test.cc
index 7312b84..5279fdb 100644
--- a/util/posix/scoped_mmap_test.cc
+++ b/util/posix/scoped_mmap_test.cc
@@ -20,6 +20,7 @@
 
 #include "base/numerics/safe_conversions.h"
 #include "base/rand_util.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "test/gtest_death.h"
@@ -150,7 +151,7 @@
   EXPECT_EQ(mapping.len(), 3 * kPageSize);
 
   TestCookie cookies[3];
-  for (size_t index = 0; index < arraysize(cookies); ++index) {
+  for (size_t index = 0; index < base::size(cookies); ++index) {
     cookies[index].SetUp(reinterpret_cast<uint64_t*>(
         mapping.addr_as<uintptr_t>() + index * kPageSize));
   }
@@ -185,7 +186,7 @@
   EXPECT_EQ(mapping.len(), kPageSize);
 
   TestCookie cookies[3];
-  for (size_t index = 0; index < arraysize(cookies); ++index) {
+  for (size_t index = 0; index < base::size(cookies); ++index) {
     cookies[index].SetUp(reinterpret_cast<uint64_t*>(
         reinterpret_cast<uintptr_t>(pages) + index * kPageSize));
   }
@@ -196,7 +197,7 @@
   EXPECT_EQ(mapping.addr(), pages);
   EXPECT_EQ(mapping.len(), 3 * kPageSize);
 
-  for (size_t index = 0; index < arraysize(cookies); ++index) {
+  for (size_t index = 0; index < base::size(cookies); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     EXPECT_EQ(cookies[index].Observed(), cookies[index].Expected());
   }
@@ -217,7 +218,7 @@
   EXPECT_EQ(mapping.len(), kPageSize);
 
   TestCookie cookies[3];
-  for (size_t index = 0; index < arraysize(cookies); ++index) {
+  for (size_t index = 0; index < base::size(cookies); ++index) {
     cookies[index].SetUp(reinterpret_cast<uint64_t*>(
         reinterpret_cast<uintptr_t>(pages) + index * kPageSize));
   }
@@ -248,7 +249,7 @@
   EXPECT_EQ(mapping.len(), 2 * kPageSize);
 
   TestCookie cookies[3];
-  for (size_t index = 0; index < arraysize(cookies); ++index) {
+  for (size_t index = 0; index < base::size(cookies); ++index) {
     cookies[index].SetUp(reinterpret_cast<uint64_t*>(
         reinterpret_cast<uintptr_t>(pages) + index * kPageSize));
   }
@@ -293,6 +294,94 @@
   EXPECT_DEATH_CRASH(cookie.Check(), "");
 }
 
+TEST(ScopedMmapDeathTest, NotIntegralNumberOfPages) {
+  ScopedMmap mapping;
+  EXPECT_FALSE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), MAP_FAILED);
+  EXPECT_EQ(mapping.len(), 0u);
+
+  ASSERT_TRUE(mapping.Reset());
+  EXPECT_FALSE(mapping.is_valid());
+
+  // Establishing a half-page mapping actually establishes a single page.
+  const size_t kPageSize = base::checked_cast<size_t>(getpagesize());
+  const size_t kHalfPageSize = kPageSize / 2;
+  ASSERT_TRUE(ScopedMmapResetMmap(&mapping, kHalfPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_NE(mapping.addr(), MAP_FAILED);
+  EXPECT_EQ(mapping.len(), kHalfPageSize);
+
+  TestCookie cookie;
+  cookie.SetUp(mapping.addr_as<uint64_t*>());
+
+  // Shrinking a one-page mapping to a half page is a no-op.
+  void* orig_addr = mapping.addr();
+  ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kHalfPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), orig_addr);
+  EXPECT_EQ(mapping.len(), kHalfPageSize);
+
+  EXPECT_EQ(cookie.Observed(), cookie.Expected());
+
+  // Same thing shrinking it to a single byte, or one byte less than a whole
+  // page.
+  ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, 1));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), orig_addr);
+  EXPECT_EQ(mapping.len(), 1u);
+
+  EXPECT_EQ(cookie.Observed(), cookie.Expected());
+
+  ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kPageSize - 1));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), orig_addr);
+  EXPECT_EQ(mapping.len(), kPageSize - 1);
+
+  EXPECT_EQ(cookie.Observed(), cookie.Expected());
+
+  // Shrinking a two-page mapping to a half page frees the second page but
+  // leaves the first alone.
+  ASSERT_TRUE(ScopedMmapResetMmap(&mapping, 2 * kPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_NE(mapping.addr(), MAP_FAILED);
+  EXPECT_EQ(mapping.len(), 2 * kPageSize);
+
+  TestCookie two_cookies[2];
+  for (size_t index = 0; index < base::size(two_cookies); ++index) {
+    two_cookies[index].SetUp(reinterpret_cast<uint64_t*>(
+        mapping.addr_as<uintptr_t>() + index * kPageSize));
+  }
+
+  orig_addr = mapping.addr();
+  ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kHalfPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), orig_addr);
+  EXPECT_EQ(mapping.len(), kHalfPageSize);
+
+  EXPECT_EQ(two_cookies[0].Observed(), two_cookies[0].Expected());
+  EXPECT_DEATH_CRASH(two_cookies[1].Check(), "");
+
+  // Shrinking a two-page mapping to a page and a half is a no-op.
+  ASSERT_TRUE(ScopedMmapResetMmap(&mapping, 2 * kPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_NE(mapping.addr(), MAP_FAILED);
+  EXPECT_EQ(mapping.len(), 2 * kPageSize);
+
+  for (size_t index = 0; index < base::size(two_cookies); ++index) {
+    two_cookies[index].SetUp(reinterpret_cast<uint64_t*>(
+        mapping.addr_as<uintptr_t>() + index * kPageSize));
+  }
+
+  orig_addr = mapping.addr();
+  ASSERT_TRUE(mapping.ResetAddrLen(orig_addr, kPageSize + kHalfPageSize));
+  EXPECT_TRUE(mapping.is_valid());
+  EXPECT_EQ(mapping.addr(), orig_addr);
+  EXPECT_EQ(mapping.len(), kPageSize + kHalfPageSize);
+
+  EXPECT_EQ(two_cookies[0].Observed(), two_cookies[0].Expected());
+  EXPECT_EQ(two_cookies[1].Observed(), two_cookies[1].Expected());
+}
+
 TEST(ScopedMmapDeathTest, Mprotect) {
   ScopedMmap mapping;
 
diff --git a/util/posix/signals.cc b/util/posix/signals.cc
index 63764ab..53c1038 100644
--- a/util/posix/signals.cc
+++ b/util/posix/signals.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 
 namespace crashpad {
 
@@ -92,9 +93,14 @@
 bool InstallHandlers(const std::vector<int>& signals,
                      Signals::Handler handler,
                      int flags,
-                     Signals::OldActions* old_actions) {
+                     Signals::OldActions* old_actions,
+                     const std::set<int>* unhandled_signals) {
   bool success = true;
   for (int sig : signals) {
+    if (unhandled_signals &&
+        unhandled_signals->find(sig) != unhandled_signals->end()) {
+      continue;
+    }
     success &= Signals::InstallHandler(
         sig,
         handler,
@@ -118,7 +124,7 @@
 struct sigaction* Signals::OldActions::ActionForSignal(int sig) {
   DCHECK_GT(sig, 0);
   const size_t slot = sig - 1;
-  DCHECK_LT(slot, arraysize(actions_));
+  DCHECK_LT(slot, base::size(actions_));
   return &actions_[slot];
 }
 
@@ -150,12 +156,15 @@
 // static
 bool Signals::InstallCrashHandlers(Handler handler,
                                    int flags,
-                                   OldActions* old_actions) {
+                                   OldActions* old_actions,
+                                   const std::set<int>* unhandled_signals) {
   return InstallHandlers(
-      std::vector<int>(kCrashSignals, kCrashSignals + arraysize(kCrashSignals)),
+      std::vector<int>(kCrashSignals,
+                       kCrashSignals + base::size(kCrashSignals)),
       handler,
       flags,
-      old_actions);
+      old_actions,
+      unhandled_signals);
 }
 
 // static
@@ -164,10 +173,11 @@
                                        OldActions* old_actions) {
   return InstallHandlers(
       std::vector<int>(kTerminateSignals,
-                       kTerminateSignals + arraysize(kTerminateSignals)),
+                       kTerminateSignals + base::size(kTerminateSignals)),
       handler,
       flags,
-      old_actions);
+      old_actions,
+      nullptr);
 }
 
 // static
@@ -279,12 +289,12 @@
 
 // static
 bool Signals::IsCrashSignal(int sig) {
-  return IsSignalInSet(sig, kCrashSignals, arraysize(kCrashSignals));
+  return IsSignalInSet(sig, kCrashSignals, base::size(kCrashSignals));
 }
 
 // static
 bool Signals::IsTerminateSignal(int sig) {
-  return IsSignalInSet(sig, kTerminateSignals, arraysize(kTerminateSignals));
+  return IsSignalInSet(sig, kTerminateSignals, base::size(kTerminateSignals));
 }
 
 }  // namespace crashpad
diff --git a/util/posix/signals.h b/util/posix/signals.h
index dc55059..368161b 100644
--- a/util/posix/signals.h
+++ b/util/posix/signals.h
@@ -17,6 +17,8 @@
 
 #include <signal.h>
 
+#include <set>
+
 #include "base/macros.h"
 
 namespace crashpad {
@@ -114,15 +116,19 @@
   //!     the new action. May be `nullptr` if not needed. The same \a
   //!     old_actions object may be used for calls to both this function and
   //!     InstallTerminateHandlers().
+  //! \param[in] unhandled_signals Signal handlers will not be installed for
+  //!     signal numbers in this set. Optional.
   //!
   //! \return `true` on success. `false` on failure with a message logged.
   //!
   //! \warning This function may not be called from a signal handler because of
   //!     its use of logging. See RestoreHandlerAndReraiseSignalOnReturn()
   //!     instead.
-  static bool InstallCrashHandlers(Handler handler,
-                                   int flags,
-                                   OldActions* old_actions);
+  static bool InstallCrashHandlers(
+      Handler handler,
+      int flags,
+      OldActions* old_actions,
+      const std::set<int>* unhandled_signals = nullptr);
 
   //! \brief Installs a new signal handler for all signals associated with
   //!     termination.
diff --git a/util/posix/signals_test.cc b/util/posix/signals_test.cc
index 75ff558..58bfa8f 100644
--- a/util/posix/signals_test.cc
+++ b/util/posix/signals_test.cc
@@ -26,6 +26,7 @@
 #include "base/compiler_specific.h"
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
@@ -257,7 +258,12 @@
   void MultiprocessChild() override {
     bool (*install_handlers)(Signals::Handler, int, Signals::OldActions*);
     if (Signals::IsCrashSignal(sig_)) {
-      install_handlers = Signals::InstallCrashHandlers;
+      install_handlers = [](Signals::Handler handler,
+                            int flags,
+                            Signals::OldActions* old_actions) {
+        return Signals::InstallCrashHandlers(
+            handler, flags, old_actions, nullptr);
+      };
     } else if (Signals::IsTerminateSignal(sig_)) {
       install_handlers = Signals::InstallTerminateHandlers;
     } else {
@@ -340,7 +346,7 @@
       {SIGHUP, SEGV_MAPERR, false},
       {SIGINT, SI_USER, false},
   };
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+  for (size_t index = 0; index < base::size(kTestData); ++index) {
     const auto test_data = kTestData[index];
     SCOPED_TRACE(base::StringPrintf(
         "index %zu, sig %d, code %d", index, test_data.sig, test_data.code));
diff --git a/util/posix/symbolic_constants_posix.cc b/util/posix/symbolic_constants_posix.cc
index 8008ffb..5937c57 100644
--- a/util/posix/symbolic_constants_posix.cc
+++ b/util/posix/symbolic_constants_posix.cc
@@ -18,7 +18,7 @@
 #include <string.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "util/misc/implicit_cast.h"
@@ -137,9 +137,9 @@
 };
 #if defined(OS_LINUX) || defined(OS_ANDROID)
 // NSIG is 64 to account for real-time signals.
-static_assert(arraysize(kSignalNames) == 32, "kSignalNames length");
+static_assert(base::size(kSignalNames) == 32, "kSignalNames length");
 #else
-static_assert(arraysize(kSignalNames) == NSIG, "kSignalNames length");
+static_assert(base::size(kSignalNames) == NSIG, "kSignalNames length");
 #endif
 
 constexpr char kSigPrefix[] = "SIG";
@@ -151,7 +151,7 @@
 std::string SignalToString(int signal,
                            SymbolicConstantToStringOptions options) {
   const char* signal_name =
-      implicit_cast<size_t>(signal) < arraysize(kSignalNames)
+      implicit_cast<size_t>(signal) < base::size(kSignalNames)
           ? kSignalNames[signal]
           : nullptr;
   if (!signal_name) {
@@ -176,8 +176,7 @@
         string.substr(0, strlen(kSigPrefix)).compare(kSigPrefix) == 0;
     base::StringPiece short_string =
         can_match_full ? string.substr(strlen(kSigPrefix)) : string;
-    for (int index = 0;
-         index < implicit_cast<int>(arraysize(kSignalNames));
+    for (int index = 0; index < implicit_cast<int>(base::size(kSignalNames));
          ++index) {
       const char* signal_name = kSignalNames[index];
       if (!signal_name) {
diff --git a/util/posix/symbolic_constants_posix_test.cc b/util/posix/symbolic_constants_posix_test.cc
index 32c1d43..c7d62ee 100644
--- a/util/posix/symbolic_constants_posix_test.cc
+++ b/util/posix/symbolic_constants_posix_test.cc
@@ -17,13 +17,14 @@
 #include <signal.h>
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 
-#define NUL_TEST_DATA(string) { string, arraysize(string) - 1 }
+#define NUL_TEST_DATA(string) \
+  { string, base::size(string) - 1 }
 
 namespace crashpad {
 namespace test {
@@ -115,7 +116,7 @@
 }
 
 TEST(SymbolicConstantsPOSIX, SignalToString) {
-  for (size_t index = 0; index < arraysize(kSignalTestData); ++index) {
+  for (size_t index = 0; index < base::size(kSignalTestData); ++index) {
     SCOPED_TRACE(base::StringPrintf("index %zu", index));
     TestSignalToString(kSignalTestData[index].signal,
                        kSignalTestData[index].full_name,
@@ -170,12 +171,11 @@
       kAllowFullName | kAllowShortName | kAllowNumber,
   };
 
-  for (size_t option_index = 0;
-       option_index < arraysize(kOptions);
+  for (size_t option_index = 0; option_index < base::size(kOptions);
        ++option_index) {
     SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index));
     StringToSymbolicConstantOptions options = kOptions[option_index];
-    for (size_t index = 0; index < arraysize(kSignalTestData); ++index) {
+    for (size_t index = 0; index < base::size(kSignalTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       int signal = kSignalTestData[index].signal;
       {
@@ -213,7 +213,7 @@
         "",
     };
 
-    for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNegativeTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       TestStringToSignal(kNegativeTestData[index], options, false, 0);
     }
@@ -234,7 +234,7 @@
         NUL_TEST_DATA("1\0002"),
     };
 
-    for (size_t index = 0; index < arraysize(kNULTestData); ++index) {
+    for (size_t index = 0; index < base::size(kNULTestData); ++index) {
       SCOPED_TRACE(base::StringPrintf("index %zu", index));
       base::StringPiece string(kNULTestData[index].string,
                                kNULTestData[index].length);
diff --git a/util/process/process_id.h b/util/process/process_id.h
new file mode 100644
index 0000000..113f6fc
--- /dev/null
+++ b/util/process/process_id.h
@@ -0,0 +1,54 @@
+// Copyright 2019 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_UTIL_PROCESS_PROCESS_ID_H_
+#define CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_
+
+#include <type_traits>
+
+#include "base/format_macros.h"
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include <sys/types.h>
+#elif defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_FUCHSIA)
+#include <zircon/types.h>
+#endif
+
+namespace crashpad {
+
+#if defined(OS_POSIX) || DOXYGEN
+//! \brief Alias for platform-specific type to represent a process.
+using ProcessID = pid_t;
+constexpr ProcessID kInvalidProcessID = -1;
+static_assert(std::is_same<ProcessID, int>::value, "Port.");
+#define PRI_PROCESS_ID "d"
+#elif defined(OS_WIN)
+using ProcessID = DWORD;
+constexpr ProcessID kInvalidProcessID = 0;
+#define PRI_PROCESS_ID "lu"
+#elif defined(OS_FUCHSIA)
+using ProcessID = zx_koid_t;
+constexpr ProcessID kInvalidProcessID = ZX_KOID_INVALID;
+static_assert(std::is_same<ProcessID, int64_t>::value, "Port.");
+#define PRI_PROCESS_ID PRId64
+#else
+#error Port.
+#endif
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_
diff --git a/util/process/process_memory.cc b/util/process/process_memory.cc
index 6bc0010..ab87b94 100644
--- a/util/process/process_memory.cc
+++ b/util/process/process_memory.cc
@@ -14,14 +14,23 @@
 
 #include "util/process/process_memory.h"
 
+#include <algorithm>
+
 #include "base/logging.h"
+#include "util/numeric/safe_assignment.h"
 
 namespace crashpad {
 
-bool ProcessMemory::Read(VMAddress address, size_t size, void* buffer) const {
+bool ProcessMemory::Read(VMAddress address, VMSize size, void* buffer) const {
+  size_t local_size;
+  if (!AssignIfInRange(&local_size, size)) {
+    LOG(ERROR) << "size " << size << " out of bounds for size_t";
+    return false;
+  }
+
   char* buffer_c = static_cast<char*>(buffer);
-  while (size > 0) {
-    ssize_t bytes_read = ReadUpTo(address, size, buffer_c);
+  while (local_size > 0) {
+    ssize_t bytes_read = ReadUpTo(address, local_size, buffer_c);
     if (bytes_read < 0) {
       return false;
     }
@@ -29,8 +38,8 @@
       LOG(ERROR) << "short read";
       return false;
     }
-    DCHECK_LE(static_cast<size_t>(bytes_read), size);
-    size -= bytes_read;
+    DCHECK_LE(static_cast<size_t>(bytes_read), local_size);
+    local_size -= bytes_read;
     address += bytes_read;
     buffer_c += bytes_read;
   }
@@ -39,15 +48,21 @@
 
 bool ProcessMemory::ReadCStringInternal(VMAddress address,
                                         bool has_size,
-                                        size_t size,
+                                        VMSize size,
                                         std::string* string) const {
+  size_t local_size;
+  if (!AssignIfInRange(&local_size, size)) {
+    LOG(ERROR) << "size " << size << " out of bounds for size_t";
+    return false;
+  }
+
   string->clear();
 
   char buffer[4096];
   do {
     size_t read_size;
     if (has_size) {
-      read_size = std::min(sizeof(buffer), size);
+      read_size = std::min(sizeof(buffer), local_size);
     } else {
       read_size = sizeof(buffer);
     }
@@ -68,8 +83,8 @@
     string->append(buffer, bytes_read);
 
     address += bytes_read;
-    size -= bytes_read;
-  } while (!has_size || size > 0);
+    local_size -= bytes_read;
+  } while (!has_size || local_size > 0);
 
   LOG(ERROR) << "unterminated string";
   return false;
diff --git a/util/process/process_memory.h b/util/process/process_memory.h
index d67c382..eeb78e9 100644
--- a/util/process/process_memory.h
+++ b/util/process/process_memory.h
@@ -19,8 +19,14 @@
 
 #include <string>
 
+#include "build/build_config.h"
 #include "util/misc/address_types.h"
 
+#if defined(OS_WIN)
+#include <basetsd.h>
+typedef SSIZE_T ssize_t;
+#endif  // defined(OS_WIN)
+
 namespace crashpad {
 
 //! \brief Abstract base class for accessing the memory of another process.
@@ -40,7 +46,7 @@
   //!
   //! \return `true` on success, with \a buffer filled appropriately. `false` on
   //!     failure, with a message logged.
-  bool Read(VMAddress address, size_t size, void* buffer) const;
+  bool Read(VMAddress address, VMSize size, void* buffer) const;
 
   //! \brief Reads a `NUL`-terminated C string from the target process into a
   //!     string in the current process.
@@ -73,7 +79,7 @@
   //!     a `NUL` terminator is not found within \a size bytes, or when
   //!     encountering unmapped or unreadable pages.
   bool ReadCStringSizeLimited(VMAddress address,
-                              size_t size,
+                              VMSize size,
                               std::string* string) const {
     return ReadCStringInternal(address, true, size, string);
   }
@@ -118,8 +124,11 @@
   //!     encountering unmapped or unreadable pages.
   virtual bool ReadCStringInternal(VMAddress address,
                                    bool has_size,
-                                   size_t size,
+                                   VMSize size,
                                    std::string* string) const;
+
+  // Allow ProcessMemorySanitized to call ReadUpTo.
+  friend class ProcessMemorySanitized;
 };
 
 }  // namespace crashpad
diff --git a/util/process/process_memory_fuchsia.cc b/util/process/process_memory_fuchsia.cc
index 212e1c6..b9c4a0c 100644
--- a/util/process/process_memory_fuchsia.cc
+++ b/util/process/process_memory_fuchsia.cc
@@ -14,8 +14,6 @@
 
 #include "util/process/process_memory_fuchsia.h"
 
-#include <zircon/syscalls.h>
-
 #include <limits>
 
 #include "base/logging.h"
@@ -28,9 +26,13 @@
 
 ProcessMemoryFuchsia::~ProcessMemoryFuchsia() {}
 
-bool ProcessMemoryFuchsia::Initialize(zx_handle_t process) {
+bool ProcessMemoryFuchsia::Initialize(const zx::unowned_process& process) {
+  return Initialize(*process);
+}
+
+bool ProcessMemoryFuchsia::Initialize(const zx::process& process) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
-  process_ = process;
+  process_ = zx::unowned_process(process);
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
@@ -42,8 +44,7 @@
   DCHECK_LE(size, size_t{std::numeric_limits<ssize_t>::max()});
 
   size_t actual;
-  zx_status_t status =
-      zx_process_read_memory(process_, address, buffer, size, &actual);
+  zx_status_t status = process_->read_memory(address, buffer, size, &actual);
 
   if (status != ZX_OK) {
     ZX_LOG(ERROR, status) << "zx_process_read_memory";
diff --git a/util/process/process_memory_fuchsia.h b/util/process/process_memory_fuchsia.h
index e6cdd3e..6c9ceba 100644
--- a/util/process/process_memory_fuchsia.h
+++ b/util/process/process_memory_fuchsia.h
@@ -15,7 +15,7 @@
 #ifndef CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_
 #define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_
 
-#include <zircon/types.h>
+#include <lib/zx/process.h>
 
 #include <string>
 
@@ -40,12 +40,15 @@
   //! \param[in] process The handle to the target process.
   //!
   //! \return `true` on success, `false` on failure with a message logged.
-  bool Initialize(zx_handle_t process);
+  bool Initialize(const zx::process& process);
+  // TODO(wez): Remove this overload when zx::unowned_process allows implicit
+  // copy.
+  bool Initialize(const zx::unowned_process& process);
 
  private:
   ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
 
-  zx_handle_t process_;
+  zx::unowned_process process_;
   InitializationStateDcheck initialized_;
 
   DISALLOW_COPY_AND_ASSIGN(ProcessMemoryFuchsia);
diff --git a/util/process/process_memory_mac.cc b/util/process/process_memory_mac.cc
new file mode 100644
index 0000000..29357f3
--- /dev/null
+++ b/util/process/process_memory_mac.cc
@@ -0,0 +1,136 @@
+// Copyright 2014 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.
+
+#include "util/process/process_memory_mac.h"
+
+#include <mach/mach_vm.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/strings/stringprintf.h"
+#include "util/stdlib/strnlen.h"
+
+namespace crashpad {
+
+ProcessMemoryMac::MappedMemory::~MappedMemory() {}
+
+bool ProcessMemoryMac::MappedMemory::ReadCString(size_t offset,
+                                                 std::string* string) const {
+  if (offset >= user_size_) {
+    LOG(WARNING) << "offset out of range";
+    return false;
+  }
+
+  const char* string_base = reinterpret_cast<const char*>(data_) + offset;
+  size_t max_length = user_size_ - offset;
+  size_t string_length = strnlen(string_base, max_length);
+  if (string_length == max_length) {
+    LOG(WARNING) << "unterminated string";
+    return false;
+  }
+
+  string->assign(string_base, string_length);
+  return true;
+}
+
+ProcessMemoryMac::MappedMemory::MappedMemory(vm_address_t vm_address,
+                                             size_t vm_size,
+                                             size_t user_offset,
+                                             size_t user_size)
+    : vm_(vm_address, vm_size),
+      data_(reinterpret_cast<const void*>(vm_address + user_offset)),
+      user_size_(user_size) {
+  vm_address_t vm_end = vm_address + vm_size;
+  vm_address_t user_address = reinterpret_cast<vm_address_t>(data_);
+  vm_address_t user_end = user_address + user_size;
+  DCHECK_GE(user_address, vm_address);
+  DCHECK_LE(user_address, vm_end);
+  DCHECK_GE(user_end, vm_address);
+  DCHECK_LE(user_end, vm_end);
+}
+
+ProcessMemoryMac::ProcessMemoryMac() : task_(TASK_NULL), initialized_() {}
+
+bool ProcessMemoryMac::Initialize(task_t task) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  task_ = task;
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+std::unique_ptr<ProcessMemoryMac::MappedMemory> ProcessMemoryMac::ReadMapped(
+    mach_vm_address_t address,
+    size_t size) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  if (size == 0) {
+    return std::unique_ptr<MappedMemory>(new MappedMemory(0, 0, 0, 0));
+  }
+
+  mach_vm_address_t region_address = mach_vm_trunc_page(address);
+  mach_vm_size_t region_size =
+      mach_vm_round_page(address - region_address + size);
+
+  vm_offset_t region;
+  mach_msg_type_number_t region_count;
+  kern_return_t kr =
+      mach_vm_read(task_, region_address, region_size, &region, &region_count);
+  if (kr != KERN_SUCCESS) {
+    MACH_LOG(WARNING, kr) << base::StringPrintf(
+        "mach_vm_read(0x%llx, 0x%llx)", region_address, region_size);
+    return std::unique_ptr<MappedMemory>();
+  }
+  if (region_count != region_size) {
+    LOG(ERROR) << base::StringPrintf(
+        "mach_vm_read() unexpected read: 0x%x != 0x%llx bytes",
+        region_count,
+        region_size);
+    if (region_count)
+      vm_deallocate(mach_task_self(), region, region_count);
+    return std::unique_ptr<MappedMemory>();
+  }
+
+  return std::unique_ptr<MappedMemory>(
+      new MappedMemory(region, region_size, address - region_address, size));
+}
+
+ssize_t ProcessMemoryMac::ReadUpTo(VMAddress address,
+                                   size_t size,
+                                   void* buffer) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
+
+  std::unique_ptr<MappedMemory> memory = ReadMapped(address, size);
+  if (!memory) {
+    // If we can not read the entire mapping, try to perform a short read of the
+    // first page instead. This is necessary to support ReadCString().
+    size_t short_read = PAGE_SIZE - (address % PAGE_SIZE);
+    if (short_read >= size)
+      return -1;
+
+    memory = ReadMapped(address, short_read);
+    if (!memory)
+      return -1;
+
+    size = short_read;
+  }
+
+  memcpy(buffer, memory->data(), size);
+  return static_cast<ssize_t>(size);
+}
+
+}  // namespace crashpad
diff --git a/util/process/process_memory_mac.h b/util/process/process_memory_mac.h
new file mode 100644
index 0000000..214e489
--- /dev/null
+++ b/util/process/process_memory_mac.h
@@ -0,0 +1,135 @@
+// Copyright 2014 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_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_
+#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_
+
+#include <mach/mach.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include "base/mac/scoped_mach_vm.h"
+#include "base/macros.h"
+#include "util/misc/address_types.h"
+#include "util/misc/initialization_state_dcheck.h"
+#include "util/process/process_memory.h"
+
+namespace crashpad {
+
+//! \brief Accesses the memory of another Mach task.
+class ProcessMemoryMac : public ProcessMemory {
+ public:
+  //! \brief A memory region mapped from another Mach task.
+  //!
+  //! The mapping is maintained until this object is destroyed.
+  class MappedMemory {
+   public:
+    ~MappedMemory();
+
+    //! \brief Returns a pointer to the data requested by the user.
+    //!
+    //! This is the value of the \a vm_address + \a user_offset parameters
+    //! passed to the constructor, casted to `const void*`.
+    const void* data() const { return data_; }
+
+    //! \brief Reads a `NUL`-terminated C string from the mapped region.
+    //!
+    //! This method will read contiguous memory until a `NUL` terminator is
+    //! found.
+    //!
+    //! \param[in] offset The offset into data() of the string to be read.
+    //! \param[out] string The string, whose contents begin at data() and
+    //!     continue up to a `NUL` terminator.
+    //!
+    //! \return `true` on success, with \a string set appropriately. If \a
+    //!     offset is greater than or equal to the \a user_size constructor
+    //!     parameter, or if no `NUL` terminator was found in data() after \a
+    //!     offset, returns `false` with an appropriate warning logged.
+    bool ReadCString(size_t offset, std::string* string) const;
+
+   private:
+    //! \brief Creates an object that owns a memory region mapped from another
+    //!     Mach task.
+    //!
+    //! \param[in] vm_address The address in this process’ address space where
+    //!     the mapping begins. This must be page-aligned.
+    //! \param[in] vm_size The total size of the mapping that begins at \a
+    //!     vm_address. This must be page-aligned.
+    //! \param[in] user_offset The offset into the mapped region where the data
+    //!     requested by the user begins. This accounts for the fact that a
+    //!     mapping must be page-aligned but the user data may not be. This
+    //!     parameter must be equal to or less than \a vm_size.
+    //! \param[in] user_size The size of the data requested by the user. This
+    //!     parameter can be used to compute the end address of user data, which
+    //!     must be within the mapped region.
+    MappedMemory(vm_address_t vm_address,
+                 size_t vm_size,
+                 size_t user_offset,
+                 size_t user_size);
+
+    base::mac::ScopedMachVM vm_;
+    const void* data_;
+    size_t user_size_;
+
+    // The outer class needs to be able to call this class’ private constructor.
+    friend class ProcessMemoryMac;
+
+    DISALLOW_COPY_AND_ASSIGN(MappedMemory);
+  };
+
+  ProcessMemoryMac();
+  ~ProcessMemoryMac() {}
+
+  //! \brief Initializes this object to read the memory of a task with the
+  //!     provided task port.
+  //!
+  //! This method must be called successfully prior to calling any other method
+  //! in this class.
+  //!
+  //! \param[in] task A send right to the target task's task port. This object
+  //!     does not take ownership of the send right.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  bool Initialize(task_t task);
+
+  //! \brief Maps memory from the target task into the current task.
+  //!
+  //! This interface is an alternative to Read() that does not require the
+  //! caller to provide a buffer to fill. This avoids copying memory, which can
+  //! offer a performance improvement.
+  //!
+  //! \param[in] address The address, in the target task’s address space, of the
+  //!     memory region to map.
+  //! \param[in] size The size, in bytes, of the memory region to map.
+  //!
+  //! \return On success, a MappedMemory object that provides access to the data
+  //!     requested. On faliure, `nullptr`, with a warning logged. Failures can
+  //!     occur, for example, when encountering unmapped or unreadable pages.
+  std::unique_ptr<MappedMemory> ReadMapped(mach_vm_address_t address,
+                                           size_t size) const;
+
+ private:
+  ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
+
+  task_t task_;  // weak
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProcessMemoryMac);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_MAC_H_
diff --git a/util/process/process_memory_mac_test.cc b/util/process/process_memory_mac_test.cc
new file mode 100644
index 0000000..d801bb1
--- /dev/null
+++ b/util/process/process_memory_mac_test.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 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.
+
+#include "util/process/process_memory_mac.h"
+
+#include <mach/mach.h>
+#include <string.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "base/mac/scoped_mach_port.h"
+#include "base/mac/scoped_mach_vm.h"
+#include "gtest/gtest.h"
+#include "test/mac/mach_errors.h"
+#include "util/misc/from_pointer_cast.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(ProcessMemoryMac, ReadMappedSelf) {
+  vm_address_t address = 0;
+  constexpr vm_size_t kSize = 4 * PAGE_SIZE;
+  kern_return_t kr =
+      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
+  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
+
+  char* region = reinterpret_cast<char*>(address);
+  for (size_t index = 0; index < kSize; ++index) {
+    region[index] = (index % 256) ^ ((index >> 8) % 256);
+  }
+
+  ProcessMemoryMac memory;
+  ASSERT_TRUE(memory.Initialize(mach_task_self()));
+
+  std::string result(kSize, '\0');
+  std::unique_ptr<ProcessMemoryMac::MappedMemory> mapped;
+
+  // Ensure that the entire region can be read.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize)));
+  EXPECT_EQ(memcmp(region, mapped->data(), kSize), 0);
+
+  // Ensure that a read of length 0 succeeds and doesn't touch the result.
+  result.assign(kSize, '\0');
+  std::string zeroes = result;
+  ASSERT_TRUE((mapped = memory.ReadMapped(address, 0)));
+  EXPECT_EQ(result, zeroes);
+
+  // Ensure that a read starting at an unaligned address works.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
+  EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 1), 0);
+
+  // Ensure that a read ending at an unaligned address works.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address, kSize - 1)));
+  EXPECT_EQ(memcmp(region, mapped->data(), kSize - 1), 0);
+
+  // Ensure that a read starting and ending at unaligned addresses works.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address + 1, kSize - 2)));
+  EXPECT_EQ(memcmp(region + 1, mapped->data(), kSize - 2), 0);
+
+  // Ensure that a read of exactly one page works.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE, PAGE_SIZE)));
+  EXPECT_EQ(memcmp(region + PAGE_SIZE, mapped->data(), PAGE_SIZE), 0);
+
+  // Ensure that a read of a single byte works.
+  ASSERT_TRUE((mapped = memory.ReadMapped(address + 2, 1)));
+  EXPECT_EQ(reinterpret_cast<const char*>(mapped->data())[0], region[2]);
+
+  // Ensure that a read of length zero works and doesn't touch the data.
+  result[0] = 'M';
+  ASSERT_TRUE((mapped = memory.ReadMapped(address + 3, 0)));
+  EXPECT_EQ(result[0], 'M');
+}
+
+TEST(ProcessMemoryMac, ReadSelfUnmapped) {
+  vm_address_t address = 0;
+  constexpr vm_size_t kSize = 2 * PAGE_SIZE;
+  kern_return_t kr =
+      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
+  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
+
+  char* region = reinterpret_cast<char*>(address);
+  for (size_t index = 0; index < kSize; ++index) {
+    // Don't include any NUL bytes, because ReadCString stops when it encounters
+    // a NUL.
+    region[index] = (index % 255) + 1;
+  }
+
+  kr = vm_protect(
+      mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect");
+
+  ProcessMemoryMac memory;
+  ASSERT_TRUE(memory.Initialize(mach_task_self()));
+  std::string result(kSize, '\0');
+
+  EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
+  EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
+  EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
+  EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
+  EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
+  EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
+
+  // Do the same thing with the ReadMapped() interface.
+  std::unique_ptr<ProcessMemoryMac::MappedMemory> mapped;
+  EXPECT_FALSE((mapped = memory.ReadMapped(address, kSize)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE, 1)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 2)));
+  EXPECT_TRUE((mapped = memory.ReadMapped(address, PAGE_SIZE)));
+  EXPECT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 1)));
+
+  // Repeat the test with an unmapped page instead of an unreadable one. This
+  // portion of the test may be flaky in the presence of other threads, if
+  // another thread maps something in the region that is deallocated here.
+  kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
+  vm_owner.reset(address, PAGE_SIZE);
+
+  EXPECT_FALSE(memory.Read(address, kSize, &result[0]));
+  EXPECT_FALSE(memory.Read(address + 1, kSize - 1, &result[0]));
+  EXPECT_FALSE(memory.Read(address + PAGE_SIZE, 1, &result[0]));
+  EXPECT_FALSE(memory.Read(address + PAGE_SIZE - 1, 2, &result[0]));
+  EXPECT_TRUE(memory.Read(address, PAGE_SIZE, &result[0]));
+  EXPECT_TRUE(memory.Read(address + PAGE_SIZE - 1, 1, &result[0]));
+
+  // Do the same thing with the ReadMapped() interface.
+  EXPECT_FALSE((mapped = memory.ReadMapped(address, kSize)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + 1, kSize - 1)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE, 1)));
+  EXPECT_FALSE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 2)));
+  EXPECT_TRUE((mapped = memory.ReadMapped(address, PAGE_SIZE)));
+  EXPECT_TRUE((mapped = memory.ReadMapped(address + PAGE_SIZE - 1, 1)));
+}
+
+TEST(ProcessMemoryMac, ReadCStringSelfUnmapped) {
+  vm_address_t address = 0;
+  constexpr vm_size_t kSize = 2 * PAGE_SIZE;
+  kern_return_t kr =
+      vm_allocate(mach_task_self(), &address, kSize, VM_FLAGS_ANYWHERE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_allocate");
+  base::mac::ScopedMachVM vm_owner(address, mach_vm_round_page(kSize));
+
+  char* region = reinterpret_cast<char*>(address);
+  for (size_t index = 0; index < kSize; ++index) {
+    // Don't include any NUL bytes, because ReadCString stops when it encounters
+    // a NUL.
+    region[index] = (index % 255) + 1;
+  }
+
+  kr = vm_protect(
+      mach_task_self(), address + PAGE_SIZE, PAGE_SIZE, FALSE, VM_PROT_NONE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_protect");
+
+  ProcessMemoryMac memory;
+  ASSERT_TRUE(memory.Initialize(mach_task_self()));
+  std::string result;
+  EXPECT_FALSE(memory.ReadCString(address, &result));
+
+  // Make sure that if the string is NUL-terminated within the mapped memory
+  // region, it can be read properly.
+  char terminator_or_not = '\0';
+  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
+  ASSERT_TRUE(memory.ReadCString(address, &result));
+  EXPECT_FALSE(result.empty());
+  EXPECT_EQ(result.size(), PAGE_SIZE - 1u);
+  EXPECT_EQ(result, region);
+
+  // Repeat the test with an unmapped page instead of an unreadable one. This
+  // portion of the test may be flaky in the presence of other threads, if
+  // another thread maps something in the region that is deallocated here.
+  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
+  kr = vm_deallocate(mach_task_self(), address + PAGE_SIZE, PAGE_SIZE);
+  ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "vm_deallocate");
+  vm_owner.reset(address, PAGE_SIZE);
+
+  EXPECT_FALSE(memory.ReadCString(address, &result));
+
+  // Clear the result before testing that the string can be read. This makes
+  // sure that the result is actually filled in, because it already contains the
+  // expected value from the tests above.
+  result.clear();
+  std::swap(region[PAGE_SIZE - 1], terminator_or_not);
+  ASSERT_TRUE(memory.ReadCString(address, &result));
+  EXPECT_FALSE(result.empty());
+  EXPECT_EQ(result.size(), PAGE_SIZE - 1u);
+  EXPECT_EQ(result, region);
+}
+
+bool IsAddressMapped(vm_address_t address) {
+  vm_address_t region_address = address;
+  vm_size_t region_size;
+  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
+  vm_region_basic_info_64 info;
+  mach_port_t object;
+  kern_return_t kr = vm_region_64(mach_task_self(),
+                                  &region_address,
+                                  &region_size,
+                                  VM_REGION_BASIC_INFO_64,
+                                  reinterpret_cast<vm_region_info_t>(&info),
+                                  &count,
+                                  &object);
+  if (kr == KERN_SUCCESS) {
+    // |object| will be MACH_PORT_NULL (10.9.4 xnu-2422.110.17/osfmk/vm/vm_map.c
+    // vm_map_region()), but the interface acts as if it might carry a send
+    // right, so treat it as documented.
+    base::mac::ScopedMachSendRight object_owner(object);
+
+    return address >= region_address && address <= region_address + region_size;
+  }
+
+  if (kr == KERN_INVALID_ADDRESS) {
+    return false;
+  }
+
+  ADD_FAILURE() << MachErrorMessage(kr, "vm_region_64");
+  return false;
+}
+
+TEST(ProcessMemoryMac, MappedMemoryDeallocates) {
+  // This tests that once a ProcessMemoryMac::MappedMemory object is destroyed,
+  // it releases the mapped memory that it owned. Technically, this test is not
+  // valid because after the mapping is released, something else (on another
+  // thread) might wind up mapped in the same address. In the test environment,
+  // hopefully there are either no other threads or they're all quiescent, so
+  // nothing else should wind up mapped in the address.
+
+  ProcessMemoryMac memory;
+  ASSERT_TRUE(memory.Initialize(mach_task_self()));
+  std::unique_ptr<ProcessMemoryMac::MappedMemory> mapped;
+
+  static constexpr char kTestBuffer[] = "hello!";
+  mach_vm_address_t test_address =
+      FromPointerCast<mach_vm_address_t>(&kTestBuffer);
+  ASSERT_TRUE((mapped = memory.ReadMapped(test_address, sizeof(kTestBuffer))));
+  EXPECT_EQ(memcmp(kTestBuffer, mapped->data(), sizeof(kTestBuffer)), 0);
+
+  vm_address_t mapped_address = reinterpret_cast<vm_address_t>(mapped->data());
+  EXPECT_TRUE(IsAddressMapped(mapped_address));
+
+  mapped.reset();
+  EXPECT_FALSE(IsAddressMapped(mapped_address));
+
+  // This is the same but with a big buffer that’s definitely larger than a
+  // single page. This makes sure that the whole mapped region winds up being
+  // deallocated.
+  constexpr size_t kBigSize = 4 * PAGE_SIZE;
+  std::unique_ptr<char[]> big_buffer(new char[kBigSize]);
+  test_address = FromPointerCast<mach_vm_address_t>(&big_buffer[0]);
+  ASSERT_TRUE((mapped = memory.ReadMapped(test_address, kBigSize)));
+
+  mapped_address = reinterpret_cast<vm_address_t>(mapped->data());
+  vm_address_t mapped_last_address = mapped_address + kBigSize - 1;
+  EXPECT_TRUE(IsAddressMapped(mapped_address));
+  EXPECT_TRUE(IsAddressMapped(mapped_address + PAGE_SIZE));
+  EXPECT_TRUE(IsAddressMapped(mapped_last_address));
+
+  mapped.reset();
+  EXPECT_FALSE(IsAddressMapped(mapped_address));
+  EXPECT_FALSE(IsAddressMapped(mapped_address + PAGE_SIZE));
+  EXPECT_FALSE(IsAddressMapped(mapped_last_address));
+}
+
+TEST(ProcessMemoryMac, MappedMemoryReadCString) {
+  // This tests the behavior of ProcessMemoryMac::MappedMemory::ReadCString().
+  ProcessMemoryMac memory;
+  ASSERT_TRUE(memory.Initialize(mach_task_self()));
+  std::unique_ptr<ProcessMemoryMac::MappedMemory> mapped;
+
+  static constexpr char kTestBuffer[] = "0\0" "2\0" "45\0" "789";
+  const mach_vm_address_t kTestAddress =
+      FromPointerCast<mach_vm_address_t>(&kTestBuffer);
+  ASSERT_TRUE((mapped = memory.ReadMapped(kTestAddress, 10)));
+
+  std::string string;
+  ASSERT_TRUE(mapped->ReadCString(0, &string));
+  EXPECT_EQ(string, "0");
+  ASSERT_TRUE(mapped->ReadCString(1, &string));
+  EXPECT_EQ(string, "");
+  ASSERT_TRUE(mapped->ReadCString(2, &string));
+  EXPECT_EQ(string, "2");
+  ASSERT_TRUE(mapped->ReadCString(3, &string));
+  EXPECT_EQ(string, "");
+  ASSERT_TRUE(mapped->ReadCString(4, &string));
+  EXPECT_EQ(string, "45");
+  ASSERT_TRUE(mapped->ReadCString(5, &string));
+  EXPECT_EQ(string, "5");
+  ASSERT_TRUE(mapped->ReadCString(6, &string));
+  EXPECT_EQ(string, "");
+
+  // kTestBuffer’s NUL terminator was not read, so these will see an
+  // unterminated string and fail.
+  EXPECT_FALSE(mapped->ReadCString(7, &string));
+  EXPECT_FALSE(mapped->ReadCString(8, &string));
+  EXPECT_FALSE(mapped->ReadCString(9, &string));
+
+  // This is out of the range of what was read, so it will fail.
+  EXPECT_FALSE(mapped->ReadCString(10, &string));
+  EXPECT_FALSE(mapped->ReadCString(11, &string));
+
+  // Read it again, this time with a length long enough to include the NUL
+  // terminator.
+  ASSERT_TRUE((mapped = memory.ReadMapped(kTestAddress, 11)));
+
+  ASSERT_TRUE(mapped->ReadCString(6, &string));
+  EXPECT_EQ(string, "");
+
+  // These should now succeed.
+  ASSERT_TRUE(mapped->ReadCString(7, &string));
+  EXPECT_EQ(string, "789");
+  ASSERT_TRUE(mapped->ReadCString(8, &string));
+  EXPECT_EQ(string, "89");
+  ASSERT_TRUE(mapped->ReadCString(9, &string));
+  EXPECT_EQ(string, "9");
+  EXPECT_TRUE(mapped->ReadCString(10, &string));
+  EXPECT_EQ(string, "");
+
+  // These are still out of range.
+  EXPECT_FALSE(mapped->ReadCString(11, &string));
+  EXPECT_FALSE(mapped->ReadCString(12, &string));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/process/process_memory_native.h b/util/process/process_memory_native.h
index 19fa805..39bfc89 100644
--- a/util/process/process_memory_native.h
+++ b/util/process/process_memory_native.h
@@ -18,6 +18,10 @@
 #include "util/process/process_memory_fuchsia.h"
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
 #include "util/process/process_memory_linux.h"
+#elif defined(OS_WIN)
+#include "util/process/process_memory_win.h"
+#elif defined(OS_MACOSX)
+#include "util/process/process_memory_mac.h"
 #endif
 
 namespace crashpad {
@@ -27,6 +31,10 @@
 using ProcessMemoryNative = ProcessMemoryFuchsia;
 #elif defined(OS_LINUX) || defined(OS_ANDROID)
 using ProcessMemoryNative = ProcessMemoryLinux;
+#elif defined(OS_WIN)
+using ProcessMemoryNative = ProcessMemoryWin;
+#elif defined(OS_MACOSX)
+using ProcessMemoryNative = ProcessMemoryMac;
 #else
 #error Port.
 #endif
diff --git a/util/process/process_memory_range.cc b/util/process/process_memory_range.cc
index 32d805d..caa4315 100644
--- a/util/process/process_memory_range.cc
+++ b/util/process/process_memory_range.cc
@@ -67,7 +67,7 @@
 }
 
 bool ProcessMemoryRange::Read(VMAddress address,
-                              size_t size,
+                              VMSize size,
                               void* buffer) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   CheckedVMAddressRange read_range(range_.Is64Bit(), address, size);
@@ -79,14 +79,14 @@
 }
 
 bool ProcessMemoryRange::ReadCStringSizeLimited(VMAddress address,
-                                                size_t size,
+                                                VMSize size,
                                                 std::string* string) const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   if (!range_.ContainsValue(address)) {
     LOG(ERROR) << "read out of range";
     return false;
   }
-  size = std::min(static_cast<VMSize>(size), range_.End() - address);
+  size = std::min(size, range_.End() - address);
   return memory_->ReadCStringSizeLimited(address, size, string);
 }
 
diff --git a/util/process/process_memory_range.h b/util/process/process_memory_range.h
index 2b654ba..aabee49 100644
--- a/util/process/process_memory_range.h
+++ b/util/process/process_memory_range.h
@@ -97,7 +97,7 @@
   //!
   //! \return `true` on success, with \a buffer filled appropriately. `false` on
   //!     failure, with a message logged.
-  bool Read(VMAddress address, size_t size, void* buffer) const;
+  bool Read(VMAddress address, VMSize size, void* buffer) const;
 
   //! \brief Reads a `NUL`-terminated C string from the target process into a
   //!     string in the current process.
@@ -113,7 +113,7 @@
   //!     a `NUL` terminator is not found within \a size bytes, or when
   //!     encountering unmapped or unreadable pages.
   bool ReadCStringSizeLimited(VMAddress address,
-                              size_t size,
+                              VMSize size,
                               std::string* string) const;
 
  private:
diff --git a/util/process/process_memory_range_test.cc b/util/process/process_memory_range_test.cc
index 1c78506..2df2b5c 100644
--- a/util/process/process_memory_range_test.cc
+++ b/util/process/process_memory_range_test.cc
@@ -14,22 +14,15 @@
 
 #include "util/process/process_memory_range.h"
 
-#include <unistd.h>
-
 #include <limits>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
+#include "test/process_type.h"
 #include "util/misc/from_pointer_cast.h"
-
-#if defined(OS_FUCHSIA)
-#include <zircon/process.h>
-
-#include "util/process/process_memory_fuchsia.h"
-#else
-#include "util/process/process_memory_linux.h"
-#endif
+#include "util/process/process_memory_native.h"
 
 namespace crashpad {
 namespace test {
@@ -41,21 +34,14 @@
 } kTestObject = {"string1", "string2"};
 
 TEST(ProcessMemoryRange, Basic) {
-#if defined(OS_FUCHSIA)
-  ProcessMemoryFuchsia memory;
-  ASSERT_TRUE(memory.Initialize(zx_process_self()));
-  constexpr bool is_64_bit = true;
-#else
-  pid_t pid = getpid();
 #if defined(ARCH_CPU_64_BITS)
   constexpr bool is_64_bit = true;
 #else
   constexpr bool is_64_bit = false;
 #endif  // ARCH_CPU_64_BITS
 
-  ProcessMemoryLinux memory;
-  ASSERT_TRUE(memory.Initialize(pid));
-#endif  // OS_FUCHSIA
+  ProcessMemoryNative memory;
+  ASSERT_TRUE(memory.Initialize(GetSelfProcess()));
 
   ProcessMemoryRange range;
   ASSERT_TRUE(range.Initialize(&memory, is_64_bit));
@@ -73,28 +59,28 @@
   auto string1_addr = FromPointerCast<VMAddress>(kTestObject.string1);
   auto string2_addr = FromPointerCast<VMAddress>(kTestObject.string2);
   ASSERT_TRUE(range.ReadCStringSizeLimited(
-      string1_addr, arraysize(kTestObject.string1), &string));
+      string1_addr, base::size(kTestObject.string1), &string));
   EXPECT_STREQ(string.c_str(), kTestObject.string1);
 
   ASSERT_TRUE(range.ReadCStringSizeLimited(
-      string2_addr, arraysize(kTestObject.string2), &string));
+      string2_addr, base::size(kTestObject.string2), &string));
   EXPECT_STREQ(string.c_str(), kTestObject.string2);
 
   // Limit the range to remove access to string2.
   ProcessMemoryRange range2;
   ASSERT_TRUE(range2.Initialize(range));
   ASSERT_TRUE(
-      range2.RestrictRange(string1_addr, arraysize(kTestObject.string1)));
+      range2.RestrictRange(string1_addr, base::size(kTestObject.string1)));
   EXPECT_TRUE(range2.ReadCStringSizeLimited(
-      string1_addr, arraysize(kTestObject.string1), &string));
+      string1_addr, base::size(kTestObject.string1), &string));
   EXPECT_FALSE(range2.ReadCStringSizeLimited(
-      string2_addr, arraysize(kTestObject.string2), &string));
+      string2_addr, base::size(kTestObject.string2), &string));
   EXPECT_FALSE(range2.Read(object_addr, sizeof(object), &object));
 
   // String reads fail if the NUL terminator is outside the range.
   ASSERT_TRUE(range2.RestrictRange(string1_addr, strlen(kTestObject.string1)));
   EXPECT_FALSE(range2.ReadCStringSizeLimited(
-      string1_addr, arraysize(kTestObject.string1), &string));
+      string1_addr, base::size(kTestObject.string1), &string));
 
   // New range outside the old range.
   EXPECT_FALSE(range2.RestrictRange(string1_addr - 1, 1));
diff --git a/util/process/process_memory_sanitized.cc b/util/process/process_memory_sanitized.cc
new file mode 100644
index 0000000..cafe5ae
--- /dev/null
+++ b/util/process/process_memory_sanitized.cc
@@ -0,0 +1,65 @@
+// Copyright 2019 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.
+
+#include "util/process/process_memory_sanitized.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+
+namespace crashpad {
+
+ProcessMemorySanitized::ProcessMemorySanitized()
+    : ProcessMemory(), memory_(nullptr), whitelist_() {}
+
+ProcessMemorySanitized::~ProcessMemorySanitized() {}
+
+bool ProcessMemorySanitized::Initialize(
+    const ProcessMemory* memory,
+    const std::vector<std::pair<VMAddress, VMAddress>>* whitelist) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+  memory_ = memory;
+  if (whitelist)
+    whitelist_ = *whitelist;
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+ssize_t ProcessMemorySanitized::ReadUpTo(VMAddress address,
+                                         size_t size,
+                                         void* buffer) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+  VMAddress end = address + size;
+  for (auto&& entry : whitelist_) {
+    if (address >= entry.first && address < entry.second &&
+        end >= entry.first && end <= entry.second) {
+      return memory_->ReadUpTo(address, size, buffer);
+    }
+  }
+
+  DLOG(ERROR)
+      << "ProcessMemorySanitized failed to read unwhitelisted region. address="
+      << address << " size=" << size;
+  return 0;
+}
+
+}  // namespace crashpad
diff --git a/util/process/process_memory_sanitized.h b/util/process/process_memory_sanitized.h
new file mode 100644
index 0000000..4443963
--- /dev/null
+++ b/util/process/process_memory_sanitized.h
@@ -0,0 +1,62 @@
+// Copyright 2019 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_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_
+#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_
+
+#include <sys/types.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "util/misc/address_types.h"
+#include "util/misc/initialization_state_dcheck.h"
+#include "util/process/process_memory.h"
+
+namespace crashpad {
+
+//! \brief Sanitized access to the memory of another process.
+class ProcessMemorySanitized final : public ProcessMemory {
+ public:
+  ProcessMemorySanitized();
+  ~ProcessMemorySanitized();
+
+  //! \brief Initializes this object to read memory from the underlying
+  //!     \a memory object if the memory range is in the provided \a whitelist.
+  //!
+  //! This method must be called successfully prior to calling any other method
+  //! in this class.
+  //!
+  //! \param[in] memory The memory object to read whitelisted regions from.
+  //! \param[in] whitelist A whitelist of memory regions.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  bool Initialize(
+      const ProcessMemory* memory,
+      const std::vector<std::pair<VMAddress, VMAddress>>* whitelist);
+
+ private:
+  ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
+
+  const ProcessMemory* memory_;
+  InitializationStateDcheck initialized_;
+  std::vector<std::pair<VMAddress, VMAddress>> whitelist_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProcessMemorySanitized);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_
diff --git a/util/process/process_memory_sanitized_test.cc b/util/process/process_memory_sanitized_test.cc
new file mode 100644
index 0000000..ff5c944
--- /dev/null
+++ b/util/process/process_memory_sanitized_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2019 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.
+
+#include "util/process/process_memory_sanitized.h"
+
+#include "gtest/gtest.h"
+#include "test/process_type.h"
+#include "util/misc/from_pointer_cast.h"
+#include "util/process/process_memory_native.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(ProcessMemorySanitized, DenyOnEmptyWhitelist) {
+  ProcessMemoryNative memory;
+  ASSERT_TRUE(memory.Initialize(GetSelfProcess()));
+
+  char c = 42;
+  char out;
+
+  ProcessMemorySanitized san_null;
+  san_null.Initialize(&memory, nullptr);
+  EXPECT_FALSE(san_null.Read(FromPointerCast<VMAddress>(&c), 1, &out));
+
+  std::vector<std::pair<VMAddress, VMAddress>> whitelist;
+  ProcessMemorySanitized san_blank;
+  san_blank.Initialize(&memory, &whitelist);
+  EXPECT_FALSE(san_blank.Read(FromPointerCast<VMAddress>(&c), 1, &out));
+}
+
+TEST(ProcessMemorySanitized, WhitelistingWorks) {
+  ProcessMemoryNative memory;
+  ASSERT_TRUE(memory.Initialize(GetSelfProcess()));
+
+  char str[4] = "ABC";
+  char out[4];
+
+  std::vector<std::pair<VMAddress, VMAddress>> whitelist;
+  whitelist.push_back(std::make_pair(FromPointerCast<VMAddress>(str + 1),
+                                     FromPointerCast<VMAddress>(str + 2)));
+
+  ProcessMemorySanitized sanitized;
+  sanitized.Initialize(&memory, &whitelist);
+
+  EXPECT_FALSE(sanitized.Read(FromPointerCast<VMAddress>(str), 1, &out));
+  EXPECT_TRUE(sanitized.Read(FromPointerCast<VMAddress>(str + 1), 1, &out));
+  EXPECT_FALSE(sanitized.Read(FromPointerCast<VMAddress>(str + 2), 1, &out));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/process/process_memory_test.cc b/util/process/process_memory_test.cc
index 741e58d..3f5db24 100644
--- a/util/process/process_memory_test.cc
+++ b/util/process/process_memory_test.cc
@@ -15,28 +15,93 @@
 #include "util/process/process_memory.h"
 
 #include <string.h>
-#include <sys/mman.h>
-#include <unistd.h>
 
 #include <memory>
 
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
 #include "test/multiprocess.h"
 #include "test/multiprocess_exec.h"
 #include "test/process_type.h"
+#include "test/scoped_guarded_page.h"
 #include "util/file/file_io.h"
 #include "util/misc/from_pointer_cast.h"
-#include "util/posix/scoped_mmap.h"
 #include "util/process/process_memory_native.h"
 
+#if defined(OS_MACOSX)
+#include "test/mac/mach_multiprocess.h"
+#endif  // defined(OS_MACOSX)
+
 namespace crashpad {
 namespace test {
 namespace {
 
+// On macOS the ProcessMemoryTests require accessing the child process' task
+// port which requires root or a code signing entitlement. To account for this
+// we implement an adaptor class that wraps MachMultiprocess on macOS, because
+// it shares the child's task port, and makes it behave like MultiprocessExec.
+#if defined(OS_MACOSX)
+class MultiprocessAdaptor : public MachMultiprocess {
+ public:
+  void SetChildTestMainFunction(const std::string& function_name) {
+    test_function_ = function_name;
+  }
+
+  ProcessType ChildProcess() { return ChildTask(); }
+
+  // Helpers to get I/O handles in the child process
+  static FileHandle OutputHandle() {
+    CHECK_NE(write_pipe_handle_, -1);
+    return write_pipe_handle_;
+  }
+
+  static FileHandle InputHandle() {
+    CHECK_NE(read_pipe_handle_, -1);
+    return read_pipe_handle_;
+  }
+
+ private:
+  virtual void Parent() = 0;
+
+  void MachMultiprocessParent() override { Parent(); }
+
+  void MachMultiprocessChild() override {
+    read_pipe_handle_ = ReadPipeHandle();
+    write_pipe_handle_ = WritePipeHandle();
+    internal::CheckedInvokeMultiprocessChild(test_function_);
+  }
+
+  std::string test_function_;
+
+  static FileHandle read_pipe_handle_;
+  static FileHandle write_pipe_handle_;
+};
+
+FileHandle MultiprocessAdaptor::read_pipe_handle_ = -1;
+FileHandle MultiprocessAdaptor::write_pipe_handle_ = -1;
+#else
+class MultiprocessAdaptor : public MultiprocessExec {
+ public:
+  static FileHandle OutputHandle() {
+    return StdioFileHandle(StdioStream::kStandardOutput);
+  }
+
+  static FileHandle InputHandle() {
+    return StdioFileHandle(StdioStream::kStandardInput);
+  }
+
+ private:
+  virtual void Parent() = 0;
+
+  void MultiprocessParent() override { Parent(); }
+};
+#endif  // defined(OS_MACOSX)
+
 void DoChildReadTestSetup(size_t* region_size,
                           std::unique_ptr<char[]>* region) {
-  *region_size = 4 * getpagesize();
+  *region_size = 4 * base::GetPageSize();
   region->reset(new char[*region_size]);
   for (size_t index = 0; index < *region_size; ++index) {
     (*region)[index] = index % 256;
@@ -47,17 +112,17 @@
   size_t region_size;
   std::unique_ptr<char[]> region;
   DoChildReadTestSetup(&region_size, &region);
-  FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
+  FileHandle out = MultiprocessAdaptor::OutputHandle();
   CheckedWriteFile(out, &region_size, sizeof(region_size));
   VMAddress address = FromPointerCast<VMAddress>(region.get());
   CheckedWriteFile(out, &address, sizeof(address));
-  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
+  CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
   return 0;
 }
 
-class ReadTest : public MultiprocessExec {
+class ReadTest : public MultiprocessAdaptor {
  public:
-  ReadTest() : MultiprocessExec() {
+  ReadTest() : MultiprocessAdaptor() {
     SetChildTestMainFunction("ReadTestChild");
   }
 
@@ -73,7 +138,7 @@
   void RunAgainstChild() { Run(); }
 
  private:
-  void MultiprocessParent() override {
+  void Parent() override {
     size_t region_size;
     VMAddress region;
     ASSERT_TRUE(
@@ -120,7 +185,7 @@
     }
 
     // Ensure that a read of exactly one page works.
-    size_t page_size = getpagesize();
+    size_t page_size = base::GetPageSize();
     ASSERT_GE(region_size, page_size + page_size);
     ASSERT_TRUE(memory.Read(address + page_size, page_size, result.get()));
     for (size_t i = 0; i < page_size; ++i) {
@@ -154,7 +219,7 @@
 
 std::string MakeLongString() {
   std::string long_string;
-  const size_t kStringLongSize = 4 * getpagesize();
+  const size_t kStringLongSize = 4 * base::GetPageSize();
   for (size_t index = 0; index < kStringLongSize; ++index) {
     long_string.push_back((index % 255) + 1);
   }
@@ -184,23 +249,22 @@
       &const_empty, &const_short, &local_empty, &local_short, &long_string);
   const auto write_address = [](const char* p) {
     VMAddress address = FromPointerCast<VMAddress>(p);
-    CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
-                     &address,
-                     sizeof(address));
+    CheckedWriteFile(
+        MultiprocessAdaptor::OutputHandle(), &address, sizeof(address));
   };
   write_address(const_empty);
   write_address(const_short);
   write_address(local_empty);
   write_address(local_short);
   write_address(long_string.c_str());
-  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
+  CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
   return 0;
 }
 
-class ReadCStringTest : public MultiprocessExec {
+class ReadCStringTest : public MultiprocessAdaptor {
  public:
   ReadCStringTest(bool limit_size)
-      : MultiprocessExec(), limit_size_(limit_size) {
+      : MultiprocessAdaptor(), limit_size_(limit_size) {
     SetChildTestMainFunction("ReadCStringTestChild");
   }
 
@@ -222,7 +286,7 @@
   void RunAgainstChild() { Run(); }
 
  private:
-  void MultiprocessParent() override {
+  void Parent() override {
 #define DECLARE_AND_READ_ADDRESS(name) \
   VMAddress name;                      \
   ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &name, sizeof(name)));
@@ -241,6 +305,23 @@
            long_string_address);
   }
 
+  void Compare(ProcessMemory& memory, VMAddress address, const char* str) {
+    std::string result;
+    if (limit_size_) {
+      ASSERT_TRUE(
+          memory.ReadCStringSizeLimited(address, strlen(str) + 1, &result));
+      EXPECT_EQ(result, str);
+      ASSERT_TRUE(
+          memory.ReadCStringSizeLimited(address, strlen(str) + 2, &result));
+      EXPECT_EQ(result, str);
+      EXPECT_FALSE(
+          memory.ReadCStringSizeLimited(address, strlen(str), &result));
+    } else {
+      ASSERT_TRUE(memory.ReadCString(address, &result));
+      EXPECT_EQ(result, str);
+    }
+  }
+
   void DoTest(ProcessType process,
               VMAddress const_empty_address,
               VMAddress const_short_address,
@@ -250,51 +331,12 @@
     ProcessMemoryNative memory;
     ASSERT_TRUE(memory.Initialize(process));
 
-    std::string result;
-
-    if (limit_size_) {
-      ASSERT_TRUE(memory.ReadCStringSizeLimited(
-          const_empty_address, arraysize(kConstCharEmpty), &result));
-      EXPECT_EQ(result, kConstCharEmpty);
-
-      ASSERT_TRUE(memory.ReadCStringSizeLimited(
-          const_short_address, arraysize(kConstCharShort), &result));
-      EXPECT_EQ(result, kConstCharShort);
-      EXPECT_FALSE(memory.ReadCStringSizeLimited(
-          const_short_address, arraysize(kConstCharShort) - 1, &result));
-
-      ASSERT_TRUE(
-          memory.ReadCStringSizeLimited(local_empty_address, 1, &result));
-      EXPECT_EQ(result, "");
-
-      ASSERT_TRUE(memory.ReadCStringSizeLimited(
-          local_short_address, strlen(SHORT_LOCAL_STRING) + 1, &result));
-      EXPECT_EQ(result, SHORT_LOCAL_STRING);
-      EXPECT_FALSE(memory.ReadCStringSizeLimited(
-          local_short_address, strlen(SHORT_LOCAL_STRING), &result));
-
-      std::string long_string_for_comparison = MakeLongString();
-      ASSERT_TRUE(memory.ReadCStringSizeLimited(
-          long_string_address, long_string_for_comparison.size() + 1, &result));
-      EXPECT_EQ(result, long_string_for_comparison);
-      EXPECT_FALSE(memory.ReadCStringSizeLimited(
-          long_string_address, long_string_for_comparison.size(), &result));
-    } else {
-      ASSERT_TRUE(memory.ReadCString(const_empty_address, &result));
-      EXPECT_EQ(result, kConstCharEmpty);
-
-      ASSERT_TRUE(memory.ReadCString(const_short_address, &result));
-      EXPECT_EQ(result, kConstCharShort);
-
-      ASSERT_TRUE(memory.ReadCString(local_empty_address, &result));
-      EXPECT_EQ(result, "");
-
-      ASSERT_TRUE(memory.ReadCString(local_short_address, &result));
-      EXPECT_EQ(result, SHORT_LOCAL_STRING);
-
-      ASSERT_TRUE(memory.ReadCString(long_string_address, &result));
-      EXPECT_EQ(result, MakeLongString());
-    }
+    Compare(memory, const_empty_address, kConstCharEmpty);
+    Compare(memory, const_short_address, kConstCharShort);
+    Compare(memory, local_empty_address, "");
+    Compare(memory, local_short_address, SHORT_LOCAL_STRING);
+    std::string long_string_for_comparison = MakeLongString();
+    Compare(memory, long_string_address, long_string_for_comparison.c_str());
   }
 
   const bool limit_size_;
@@ -322,101 +364,60 @@
   test.RunAgainstChild();
 }
 
-void DoReadUnmappedChildMainSetup(ScopedMmap* pages,
-                                  VMAddress* address,
-                                  size_t* page_size,
-                                  size_t* region_size) {
-  *page_size = getpagesize();
-  *region_size = 2 * (*page_size);
-  if (!pages->ResetMmap(nullptr,
-                        *region_size,
-                        PROT_READ | PROT_WRITE,
-                        MAP_PRIVATE | MAP_ANONYMOUS,
-                        -1,
-                        0)) {
-    ADD_FAILURE();
-    return;
-  }
-
-  *address = pages->addr_as<VMAddress>();
-
-  char* region = pages->addr_as<char*>();
-  for (size_t index = 0; index < *region_size; ++index) {
+void DoReadUnmappedChildMainSetup(void* page) {
+  char* region = reinterpret_cast<char*>(page);
+  for (size_t index = 0; index < base::GetPageSize(); ++index) {
     region[index] = index % 256;
   }
-
-  EXPECT_TRUE(pages->ResetAddrLen(region, *page_size));
 }
 
 CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) {
-  ScopedMmap pages;
-  VMAddress address = 0;
-  size_t page_size, region_size;
-  DoReadUnmappedChildMainSetup(&pages, &address, &page_size, &region_size);
-  FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
+  ScopedGuardedPage pages;
+  VMAddress address = reinterpret_cast<VMAddress>(pages.Pointer());
+  DoReadUnmappedChildMainSetup(pages.Pointer());
+  FileHandle out = MultiprocessAdaptor::OutputHandle();
   CheckedWriteFile(out, &address, sizeof(address));
-  CheckedWriteFile(out, &page_size, sizeof(page_size));
-  CheckedWriteFile(out, &region_size, sizeof(region_size));
-  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
+  CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
   return 0;
 }
 
-class ReadUnmappedTest : public MultiprocessExec {
+// This test only supports running against a child process because
+// ScopedGuardedPage is not thread-safe.
+class ReadUnmappedTest : public MultiprocessAdaptor {
  public:
-  ReadUnmappedTest() : MultiprocessExec() {
+  ReadUnmappedTest() : MultiprocessAdaptor() {
     SetChildTestMainFunction("ReadUnmappedChildMain");
   }
 
-  void RunAgainstSelf() {
-    ScopedMmap pages;
-    VMAddress address = 0;
-    size_t page_size, region_size;
-    DoReadUnmappedChildMainSetup(&pages, &address, &page_size, &region_size);
-    DoTest(GetSelfProcess(), address, page_size, region_size);
-  }
-
   void RunAgainstChild() { Run(); }
 
  private:
-  void MultiprocessParent() override {
+  void Parent() override {
     VMAddress address = 0;
-    size_t page_size, region_size;
     ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &address, sizeof(address)));
-    ASSERT_TRUE(
-        ReadFileExactly(ReadPipeHandle(), &page_size, sizeof(page_size)));
-    ASSERT_TRUE(
-        ReadFileExactly(ReadPipeHandle(), &region_size, sizeof(region_size)));
-    DoTest(ChildProcess(), address, page_size, region_size);
+    DoTest(ChildProcess(), address);
   }
 
-  void DoTest(ProcessType process,
-              VMAddress address,
-              size_t page_size,
-              size_t region_size) {
+  void DoTest(ProcessType process, VMAddress address) {
     ProcessMemoryNative memory;
     ASSERT_TRUE(memory.Initialize(process));
 
     VMAddress page_addr1 = address;
-    VMAddress page_addr2 = page_addr1 + page_size;
+    VMAddress page_addr2 = page_addr1 + base::GetPageSize();
 
-    std::unique_ptr<char[]> result(new char[region_size]);
-    EXPECT_TRUE(memory.Read(page_addr1, page_size, result.get()));
+    std::unique_ptr<char[]> result(new char[base::GetPageSize() * 2]);
+    EXPECT_TRUE(memory.Read(page_addr1, base::GetPageSize(), result.get()));
     EXPECT_TRUE(memory.Read(page_addr2 - 1, 1, result.get()));
 
-    EXPECT_FALSE(memory.Read(page_addr1, region_size, result.get()));
-    EXPECT_FALSE(memory.Read(page_addr2, page_size, result.get()));
+    EXPECT_FALSE(
+        memory.Read(page_addr1, base::GetPageSize() * 2, result.get()));
+    EXPECT_FALSE(memory.Read(page_addr2, base::GetPageSize(), result.get()));
     EXPECT_FALSE(memory.Read(page_addr2 - 1, 2, result.get()));
   }
 
   DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest);
 };
 
-TEST(ProcessMemory, ReadUnmappedSelf) {
-  ReadUnmappedTest test;
-  ASSERT_FALSE(testing::Test::HasFailure());
-  test.RunAgainstSelf();
-}
-
 TEST(ProcessMemory, ReadUnmappedChild) {
   ReadUnmappedTest test;
   ASSERT_FALSE(testing::Test::HasFailure());
@@ -428,9 +429,13 @@
 class StringDataInChildProcess {
  public:
   // This constructor only makes sense in the child process.
-  explicit StringDataInChildProcess(const char* cstring)
+  explicit StringDataInChildProcess(const char* cstring, bool valid)
       : address_(FromPointerCast<VMAddress>(cstring)) {
-    memcpy(expected_value_, cstring, kChildProcessStringLength + 1);
+    if (valid) {
+      memcpy(expected_value_, cstring, kChildProcessStringLength + 1);
+    } else {
+      memset(expected_value_, 0xff, kChildProcessStringLength + 1);
+    }
   }
 
   void Write(FileHandle out) {
@@ -457,22 +462,10 @@
 };
 
 void DoCStringUnmappedTestSetup(
-    ScopedMmap* pages,
+    void* page,
     std::vector<StringDataInChildProcess>* strings) {
-  const size_t page_size = getpagesize();
-  const size_t region_size = 2 * page_size;
-  if (!pages->ResetMmap(nullptr,
-                        region_size,
-                        PROT_READ | PROT_WRITE,
-                        MAP_PRIVATE | MAP_ANONYMOUS,
-                        -1,
-                        0)) {
-    ADD_FAILURE();
-    return;
-  }
-
-  char* region = pages->addr_as<char*>();
-  for (size_t index = 0; index < region_size; ++index) {
+  char* region = reinterpret_cast<char*>(page);
+  for (size_t index = 0; index < base::GetPageSize(); ++index) {
     region[index] = 1 + index % 255;
   }
 
@@ -481,63 +474,53 @@
   string1[kChildProcessStringLength] = '\0';
 
   // A string near the end of the mapped region
-  char* string2 = region + page_size - kChildProcessStringLength * 2;
+  char* string2 = region + base::GetPageSize() - kChildProcessStringLength * 2;
   string2[kChildProcessStringLength] = '\0';
 
   // A string that crosses from the mapped into the unmapped region
-  char* string3 = region + page_size - kChildProcessStringLength + 1;
-  string3[kChildProcessStringLength] = '\0';
+  char* string3 = region + base::GetPageSize() - kChildProcessStringLength + 1;
 
   // A string entirely in the unmapped region
-  char* string4 = region + page_size + 10;
-  string4[kChildProcessStringLength] = '\0';
+  char* string4 = region + base::GetPageSize() + 10;
 
-  strings->push_back(StringDataInChildProcess(string1));
-  strings->push_back(StringDataInChildProcess(string2));
-  strings->push_back(StringDataInChildProcess(string3));
-  strings->push_back(StringDataInChildProcess(string4));
-
-  EXPECT_TRUE(pages->ResetAddrLen(region, page_size));
+  strings->push_back(StringDataInChildProcess(string1, true));
+  strings->push_back(StringDataInChildProcess(string2, true));
+  strings->push_back(StringDataInChildProcess(string3, false));
+  strings->push_back(StringDataInChildProcess(string4, false));
 }
 
 CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) {
-  ScopedMmap pages;
+  ScopedGuardedPage pages;
   std::vector<StringDataInChildProcess> strings;
-  DoCStringUnmappedTestSetup(&pages, &strings);
-  FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
+  DoCStringUnmappedTestSetup(pages.Pointer(), &strings);
+  FileHandle out = MultiprocessAdaptor::OutputHandle();
   strings[0].Write(out);
   strings[1].Write(out);
   strings[2].Write(out);
   strings[3].Write(out);
-  CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
+  CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
   return 0;
 }
 
-class ReadCStringUnmappedTest : public MultiprocessExec {
+// This test only supports running against a child process because
+// ScopedGuardedPage is not thread-safe.
+class ReadCStringUnmappedTest : public MultiprocessAdaptor {
  public:
   ReadCStringUnmappedTest(bool limit_size)
-      : MultiprocessExec(), limit_size_(limit_size) {
+      : MultiprocessAdaptor(), limit_size_(limit_size) {
     SetChildTestMainFunction("ReadCStringUnmappedChildMain");
   }
 
-  void RunAgainstSelf() {
-    ScopedMmap pages;
-    std::vector<StringDataInChildProcess> strings;
-    DoCStringUnmappedTestSetup(&pages, &strings);
-    DoTest(GetSelfProcess(), strings);
-  }
-
   void RunAgainstChild() { Run(); }
 
  private:
-  void MultiprocessParent() override {
+  void Parent() override {
     std::vector<StringDataInChildProcess> strings;
     strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
     strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
     strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
     strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
-    ASSERT_NO_FATAL_FAILURE();
-    DoTest(ChildProcess(), strings);
+    ASSERT_NO_FATAL_FAILURE(DoTest(ChildProcess(), strings));
   }
 
   void DoTest(ProcessType process,
@@ -574,24 +557,12 @@
   DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest);
 };
 
-TEST(ProcessMemory, ReadCStringUnmappedSelf) {
-  ReadCStringUnmappedTest test(/* limit_size= */ false);
-  ASSERT_FALSE(testing::Test::HasFailure());
-  test.RunAgainstSelf();
-}
-
 TEST(ProcessMemory, ReadCStringUnmappedChild) {
   ReadCStringUnmappedTest test(/* limit_size= */ false);
   ASSERT_FALSE(testing::Test::HasFailure());
   test.RunAgainstChild();
 }
 
-TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) {
-  ReadCStringUnmappedTest test(/* limit_size= */ true);
-  ASSERT_FALSE(testing::Test::HasFailure());
-  test.RunAgainstSelf();
-}
-
 TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedChild) {
   ReadCStringUnmappedTest test(/* limit_size= */ true);
   ASSERT_FALSE(testing::Test::HasFailure());
diff --git a/util/process/process_memory_win.cc b/util/process/process_memory_win.cc
new file mode 100644
index 0000000..f716102
--- /dev/null
+++ b/util/process/process_memory_win.cc
@@ -0,0 +1,118 @@
+// 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.
+
+#include "util/process/process_memory_win.h"
+
+#include <windows.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/stringprintf.h"
+
+namespace crashpad {
+
+ProcessMemoryWin::ProcessMemoryWin()
+    : ProcessMemory(), handle_(), process_info_(), initialized_() {}
+
+ProcessMemoryWin::~ProcessMemoryWin() {}
+
+bool ProcessMemoryWin::Initialize(HANDLE handle) {
+  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+
+  handle_ = handle;
+  if (!process_info_.Initialize(handle)) {
+    LOG(ERROR) << "Failed to initialize ProcessInfo.";
+    return false;
+  }
+
+  INITIALIZATION_STATE_SET_VALID(initialized_);
+  return true;
+}
+
+ssize_t ProcessMemoryWin::ReadUpTo(VMAddress address,
+                                   size_t size,
+                                   void* buffer) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
+
+  SIZE_T size_out = 0;
+  BOOL success = ReadProcessMemory(
+      handle_, reinterpret_cast<void*>(address), buffer, size, &size_out);
+  if (success)
+    return base::checked_cast<ssize_t>(size_out);
+
+  if (GetLastError() == ERROR_PARTIAL_COPY) {
+    // If we can not read the entire section, perform a short read of the first
+    // page instead. This is necessary to support ReadCString().
+    size_t short_read =
+        base::GetPageSize() - (address & (base::GetPageSize() - 1));
+    success = ReadProcessMemory(handle_,
+                                reinterpret_cast<void*>(address),
+                                buffer,
+                                short_read,
+                                &size_out);
+    if (success)
+      return base::checked_cast<ssize_t>(size_out);
+  }
+
+  PLOG(ERROR) << "ReadMemory at 0x" << std::hex << address << std::dec << " of "
+              << size << " bytes failed";
+  return -1;
+}
+
+size_t ProcessMemoryWin::ReadAvailableMemory(VMAddress address,
+                                             size_t size,
+                                             void* buffer) const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
+
+  if (size == 0)
+    return 0;
+
+  auto ranges = process_info_.GetReadableRanges(
+      CheckedRange<WinVMAddress, WinVMSize>(address, size));
+
+  // We only read up until the first unavailable byte, so we only read from the
+  // first range. If we have no ranges, then no bytes were accessible anywhere
+  // in the range.
+  if (ranges.empty()) {
+    LOG(ERROR) << base::StringPrintf(
+        "range at 0x%llx, size 0x%zx completely inaccessible", address, size);
+    return 0;
+  }
+
+  // If the start address was adjusted, we couldn't read even the first
+  // requested byte.
+  if (ranges.front().base() != address) {
+    LOG(ERROR) << base::StringPrintf(
+        "start of range at 0x%llx, size 0x%zx inaccessible", address, size);
+    return 0;
+  }
+
+  DCHECK_LE(ranges.front().size(), size);
+
+  ssize_t result = ReadUpTo(ranges.front().base(),
+                            base::checked_cast<size_t>(ranges.front().size()),
+                            buffer);
+  if (result < 0)
+    return 0;
+
+  return base::checked_cast<size_t>(result);
+}
+
+}  // namespace crashpad
diff --git a/util/process/process_memory_win.h b/util/process/process_memory_win.h
new file mode 100644
index 0000000..2856900
--- /dev/null
+++ b/util/process/process_memory_win.h
@@ -0,0 +1,66 @@
+// 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_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_
+#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_
+
+#include <windows.h>
+
+#include "base/macros.h"
+#include "util/misc/address_types.h"
+#include "util/misc/initialization_state_dcheck.h"
+#include "util/process/process_memory.h"
+#include "util/win/process_info.h"
+
+namespace crashpad {
+
+//! \brief Accesses the memory of another Windows process.
+class ProcessMemoryWin final : public ProcessMemory {
+ public:
+  ProcessMemoryWin();
+  ~ProcessMemoryWin();
+
+  //! \brief Initializes this object to read the memory of a process with the
+  //!     provided handle.
+  //!
+  //! This method must be called successfully prior to calling any other method
+  //! in this class.
+  //!
+  //! \param[in] handle The HANDLE of a target process.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  bool Initialize(HANDLE handle);
+
+  //! \brief Attempts to read \a size bytes from the target process starting at
+  //!     address \a address into \a buffer. If some of the specified range is
+  //!     not accessible, reads up to the first inaccessible byte.
+  //!
+  //! \return The actual number of bytes read.
+  size_t ReadAvailableMemory(VMAddress address,
+                             size_t num_bytes,
+                             void* buffer) const;
+
+ private:
+  ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
+
+  HANDLE handle_;
+  ProcessInfo process_info_;
+  InitializationStateDcheck initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProcessMemoryWin);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_WIN_H_
diff --git a/util/stdlib/string_number_conversion.cc b/util/stdlib/string_number_conversion.cc
index 859a833..6421184 100644
--- a/util/stdlib/string_number_conversion.cc
+++ b/util/stdlib/string_number_conversion.cc
@@ -97,15 +97,34 @@
   }
 };
 
-struct StringToInt64Traits
-    : public StringToSignedIntegerTraits<int64_t, int64_t> {
+struct StringToLongTraits
+    : public StringToSignedIntegerTraits<long, long long> {
   static LongType Convert(const char* str, char** end, int base) {
     return strtoll(str, end, base);
   }
 };
 
-struct StringToUnsignedInt64Traits
-    : public StringToUnsignedIntegerTraits<uint64_t, uint64_t> {
+struct StringToUnsignedLongTraits
+    : public StringToUnsignedIntegerTraits<unsigned long, unsigned long long> {
+  static LongType Convert(const char* str, char** end, int base) {
+    if (str[0] == '-') {
+      *end = const_cast<char*>(str);
+      return 0;
+    }
+    return strtoull(str, end, base);
+  }
+};
+
+struct StringToLongLongTraits
+    : public StringToSignedIntegerTraits<long long, long long> {
+  static LongType Convert(const char* str, char** end, int base) {
+    return strtoll(str, end, base);
+  }
+};
+
+struct StringToUnsignedLongLongTraits
+    : public StringToUnsignedIntegerTraits<unsigned long long,
+                                           unsigned long long> {
   static LongType Convert(const char* str, char** end, int base) {
     if (str[0] == '-') {
       *end = const_cast<char*>(str);
@@ -136,7 +155,7 @@
       end != string.data() + string.length()) {
     return false;
   }
-  *number = result;
+  *number = static_cast<IntType>(result);
   return true;
 }
 
@@ -152,12 +171,21 @@
   return StringToIntegerInternal<StringToUnsignedIntTraits>(string, number);
 }
 
-bool StringToNumber(const std::string& string, int64_t* number) {
-  return StringToIntegerInternal<StringToInt64Traits>(string, number);
+bool StringToNumber(const std::string& string, long* number) {
+  return StringToIntegerInternal<StringToLongTraits>(string, number);
 }
 
-bool StringToNumber(const std::string& string, uint64_t* number) {
-  return StringToIntegerInternal<StringToUnsignedInt64Traits>(string, number);
+bool StringToNumber(const std::string& string, unsigned long* number) {
+  return StringToIntegerInternal<StringToUnsignedLongTraits>(string, number);
+}
+
+bool StringToNumber(const std::string& string, long long* number) {
+  return StringToIntegerInternal<StringToLongLongTraits>(string, number);
+}
+
+bool StringToNumber(const std::string& string, unsigned long long* number) {
+  return StringToIntegerInternal<StringToUnsignedLongLongTraits>(string,
+                                                                 number);
 }
 
 }  // namespace crashpad
diff --git a/util/stdlib/string_number_conversion.h b/util/stdlib/string_number_conversion.h
index b5f1d44..69e7fdd 100644
--- a/util/stdlib/string_number_conversion.h
+++ b/util/stdlib/string_number_conversion.h
@@ -56,8 +56,10 @@
 //!     where such prefix recognition is desirable.
 bool StringToNumber(const std::string& string, int* number);
 bool StringToNumber(const std::string& string, unsigned int* number);
-bool StringToNumber(const std::string& string, int64_t* number);
-bool StringToNumber(const std::string& string, uint64_t* number);
+bool StringToNumber(const std::string& string, long* number);
+bool StringToNumber(const std::string& string, unsigned long* number);
+bool StringToNumber(const std::string& string, long long* number);
+bool StringToNumber(const std::string& string, unsigned long long* number);
 //! \}
 
 }  // namespace crashpad
diff --git a/util/stdlib/string_number_conversion_test.cc b/util/stdlib/string_number_conversion_test.cc
index d855c8d..760dc4a 100644
--- a/util/stdlib/string_number_conversion_test.cc
+++ b/util/stdlib/string_number_conversion_test.cc
@@ -16,30 +16,39 @@
 
 #include <sys/types.h>
 
+#include <array>
 #include <limits>
+#include <type_traits>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 
-namespace crashpad {
-namespace test {
-namespace {
+#define STRINGIFY(a) STR(a)
+#define STR(a) #a
 
-TEST(StringNumberConversion, StringToInt) {
-  static constexpr struct {
-    const char* string;
-    bool valid;
-    int value;
-  } kTestData[] = {
+template <typename TValueType>
+struct TestSpecification {
+  const char* string;
+  bool valid;
+  TValueType value;
+};
+
+// Signed 32-bit test data
+template <typename TIntType,
+          typename std::enable_if<std::is_integral<TIntType>::value &&
+                                  std::is_signed<TIntType>::value &&
+                                  (sizeof(TIntType) == 4)>::type* = nullptr>
+static constexpr std::array<TestSpecification<TIntType>, 61> kTestDataFunc() {
+  return {{
       {"", false, 0},
       {"0", true, 0},
       {"1", true, 1},
-      {"2147483647", true, std::numeric_limits<int>::max()},
+      {"2147483647", true, std::numeric_limits<TIntType>::max()},
       {"2147483648", false, 0},
       {"4294967295", false, 0},
       {"4294967296", false, 0},
       {"-1", true, -1},
-      {"-2147483648", true, std::numeric_limits<int>::min()},
+      {"-2147483648", true, std::numeric_limits<TIntType>::min()},
       {"-2147483649", false, 0},
       {"00", true, 0},
       {"01", true, 1},
@@ -50,12 +59,12 @@
       {"+0x20", true, 32},
       {"0xf", true, 15},
       {"0xg", false, 0},
-      {"0x7fffffff", true, std::numeric_limits<int>::max()},
-      {"0x7FfFfFfF", true, std::numeric_limits<int>::max()},
+      {"0x7fffffff", true, std::numeric_limits<TIntType>::max()},
+      {"0x7FfFfFfF", true, std::numeric_limits<TIntType>::max()},
       {"0x80000000", false, 0},
       {"0xFFFFFFFF", false, 0},
       {"-0x7fffffff", true, -2147483647},
-      {"-0x80000000", true, std::numeric_limits<int>::min()},
+      {"-0x80000000", true, std::numeric_limits<TIntType>::min()},
       {"-0x80000001", false, 0},
       {"-0xffffffff", false, 0},
       {"0x100000000", false, 0},
@@ -92,45 +101,22 @@
       {"9223372036854775809", false, 0},
       {"18446744073709551615", false, 0},
       {"18446744073709551616", false, 0},
-  };
-
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
-    int value;
-    bool valid = StringToNumber(kTestData[index].string, &value);
-    if (kTestData[index].valid) {
-      EXPECT_TRUE(valid) << "index " << index << ", string "
-                         << kTestData[index].string;
-      if (valid) {
-        EXPECT_EQ(value, kTestData[index].value)
-            << "index " << index << ", string " << kTestData[index].string;
-      }
-    } else {
-      EXPECT_FALSE(valid) << "index " << index << ", string "
-                          << kTestData[index].string << ", value " << value;
-    }
-  }
-
-  // Ensure that embedded NUL characters are treated as bad input. The string
-  // is split to avoid MSVC warning:
-  //   "decimal digit terminates octal escape sequence".
-  static constexpr char input[] = "6\000" "6";
-  std::string input_string(input, arraysize(input) - 1);
-  int output;
-  EXPECT_FALSE(StringToNumber(input_string, &output));
+  }};
 }
 
-TEST(StringNumberConversion, StringToUnsignedInt) {
-  static constexpr struct {
-    const char* string;
-    bool valid;
-    unsigned int value;
-  } kTestData[] = {
+// Unsigned 32-bit test data
+template <typename TIntType,
+          typename std::enable_if<std::is_integral<TIntType>::value &&
+                                  !std::is_signed<TIntType>::value &&
+                                  (sizeof(TIntType) == 4)>::type* = nullptr>
+static constexpr std::array<TestSpecification<TIntType>, 61> kTestDataFunc() {
+  return {{
       {"", false, 0},
       {"0", true, 0},
       {"1", true, 1},
       {"2147483647", true, 2147483647},
       {"2147483648", true, 2147483648},
-      {"4294967295", true, std::numeric_limits<unsigned int>::max()},
+      {"4294967295", true, std::numeric_limits<TIntType>::max()},
       {"4294967296", false, 0},
       {"-1", false, 0},
       {"-2147483648", false, 0},
@@ -186,9 +172,121 @@
       {"9223372036854775809", false, 0},
       {"18446744073709551615", false, 0},
       {"18446744073709551616", false, 0},
-  };
+  }};
+}
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
+// Signed 64-bit test data
+template <typename TIntType,
+          typename std::enable_if<std::is_integral<TIntType>::value &&
+                                  std::is_signed<TIntType>::value &&
+                                  (sizeof(TIntType) == 8)>::type* = nullptr>
+static constexpr std::array<TestSpecification<TIntType>, 24> kTestDataFunc() {
+  return {{
+      {"", false, 0},
+      {"0", true, 0},
+      {"1", true, 1},
+      {"2147483647", true, 2147483647},
+      {"2147483648", true, 2147483648},
+      {"4294967295", true, 4294967295},
+      {"4294967296", true, 4294967296},
+      {"9223372036854775807", true, std::numeric_limits<TIntType>::max()},
+      {"9223372036854775808", false, 0},
+      {"18446744073709551615", false, 0},
+      {"18446744073709551616", false, 0},
+      {"-1", true, -1},
+      {"-2147483648", true, INT64_C(-2147483648)},
+      {"-2147483649", true, INT64_C(-2147483649)},
+      {"-9223372036854775808", true, std::numeric_limits<TIntType>::min()},
+      {"-9223372036854775809", false, 0},
+      {"0x7fffffffffffffff", true, std::numeric_limits<TIntType>::max()},
+      {"0x8000000000000000", false, 0},
+      {"0xffffffffffffffff", false, 0},
+      {"0x10000000000000000", false, 0},
+      {"-0x7fffffffffffffff", true, -9223372036854775807},
+      {"-0x8000000000000000", true, std::numeric_limits<TIntType>::min()},
+      {"-0x8000000000000001", false, 0},
+      {"0x7Fffffffffffffff", true, std::numeric_limits<TIntType>::max()},
+  }};
+}
+
+// Unsigned 64-bit test data
+template <typename TIntType,
+          typename std::enable_if<std::is_integral<TIntType>::value &&
+                                  !std::is_signed<TIntType>::value &&
+                                  (sizeof(TIntType) == 8)>::type* = nullptr>
+static constexpr std::array<TestSpecification<TIntType>, 25> kTestDataFunc() {
+  return {{
+      {"", false, 0},
+      {"0", true, 0},
+      {"1", true, 1},
+      {"2147483647", true, 2147483647},
+      {"2147483648", true, 2147483648},
+      {"4294967295", true, 4294967295},
+      {"4294967296", true, 4294967296},
+      {"9223372036854775807", true, 9223372036854775807},
+      {"9223372036854775808", true, 9223372036854775808u},
+      {"18446744073709551615", true, std::numeric_limits<TIntType>::max()},
+      {"18446744073709551616", false, 0},
+      {"-1", false, 0},
+      {"-2147483648", false, 0},
+      {"-2147483649", false, 0},
+      {"-2147483648", false, 0},
+      {"-9223372036854775808", false, 0},
+      {"-9223372036854775809", false, 0},
+      {"0x7fffffffffffffff", true, 9223372036854775807},
+      {"0x8000000000000000", true, 9223372036854775808u},
+      {"0xffffffffffffffff", true, std::numeric_limits<TIntType>::max()},
+      {"0x10000000000000000", false, 0},
+      {"-0x7fffffffffffffff", false, 0},
+      {"-0x8000000000000000", false, 0},
+      {"-0x8000000000000001", false, 0},
+      {"0xFfffffffffffffff", true, std::numeric_limits<TIntType>::max()},
+  }};
+}
+
+// This string is split to avoid MSVC warning:
+//   "decimal digit terminates octal escape sequence".
+static constexpr char kEmbeddedNullInputRaw[] = "6\000" "6";
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(StringNumberConversion, StringToInt) {
+  static_assert(sizeof(int) == 4, "Test only configured for 32-bit int.");
+  static constexpr auto kTestData = kTestDataFunc<int>();
+
+  for (size_t index = 0; index < kTestData.size(); ++index) {
+    int value;
+    bool valid = StringToNumber(kTestData[index].string, &value);
+    if (kTestData[index].valid) {
+      EXPECT_TRUE(valid) << "index " << index << ", string "
+                         << kTestData[index].string;
+      if (valid) {
+        EXPECT_EQ(value, kTestData[index].value)
+            << "index " << index << ", string " << kTestData[index].string;
+      }
+    } else {
+      EXPECT_FALSE(valid) << "index " << index << ", string "
+                          << kTestData[index].string << ", value " << value;
+    }
+  }
+
+  // Ensure that embedded NUL characters are treated as bad input. The string
+  // is split to avoid MSVC warning:
+  //   "decimal digit terminates octal escape sequence".
+  int output;
+  std::string kEmbeddedNullInput(kEmbeddedNullInputRaw,
+                                 base::size(kEmbeddedNullInputRaw) - 1);
+  EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output));
+}
+
+TEST(StringNumberConversion, StringToUnsignedInt) {
+  static_assert(sizeof(unsigned int) == 4,
+                "Test only configured for 32-bit unsigned int.");
+  static constexpr auto kTestData = kTestDataFunc<unsigned int>();
+
+  for (size_t index = 0; index < kTestData.size(); ++index) {
     unsigned int value;
     bool valid = StringToNumber(kTestData[index].string, &value);
     if (kTestData[index].valid) {
@@ -207,46 +305,20 @@
   // Ensure that embedded NUL characters are treated as bad input. The string
   // is split to avoid MSVC warning:
   //   "decimal digit terminates octal escape sequence".
-  static constexpr char input[] = "6\000" "6";
-  std::string input_string(input, arraysize(input) - 1);
   unsigned int output;
-  EXPECT_FALSE(StringToNumber(input_string, &output));
+  std::string kEmbeddedNullInput(kEmbeddedNullInputRaw,
+                                 base::size(kEmbeddedNullInputRaw) - 1);
+  EXPECT_FALSE(StringToNumber(kEmbeddedNullInput, &output));
 }
 
-TEST(StringNumberConversion, StringToInt64) {
-  static constexpr struct {
-    const char* string;
-    bool valid;
-    int64_t value;
-  } kTestData[] = {
-      {"", false, 0},
-      {"0", true, 0},
-      {"1", true, 1},
-      {"2147483647", true, 2147483647},
-      {"2147483648", true, 2147483648},
-      {"4294967295", true, 4294967295},
-      {"4294967296", true, 4294967296},
-      {"9223372036854775807", true, std::numeric_limits<int64_t>::max()},
-      {"9223372036854775808", false, 0},
-      {"18446744073709551615", false, 0},
-      {"18446744073709551616", false, 0},
-      {"-1", true, -1},
-      {"-2147483648", true, INT64_C(-2147483648)},
-      {"-2147483649", true, INT64_C(-2147483649)},
-      {"-9223372036854775808", true, std::numeric_limits<int64_t>::min()},
-      {"-9223372036854775809", false, 0},
-      {"0x7fffffffffffffff", true, std::numeric_limits<int64_t>::max()},
-      {"0x8000000000000000", false, 0},
-      {"0xffffffffffffffff", false, 0},
-      {"0x10000000000000000", false, 0},
-      {"-0x7fffffffffffffff", true, -9223372036854775807},
-      {"-0x8000000000000000", true, std::numeric_limits<int64_t>::min()},
-      {"-0x8000000000000001", false, 0},
-      {"0x7Fffffffffffffff", true, std::numeric_limits<int64_t>::max()},
-  };
+TEST(StringNumberConversion, StringToLong) {
+  static_assert(
+      sizeof(long) == 4 || sizeof(long) == 8,
+      "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long");
+  static constexpr auto kTestData = kTestDataFunc<long>();
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
-    int64_t value;
+  for (size_t index = 0; index < kTestData.size(); ++index) {
+    long value;
     bool valid = StringToNumber(kTestData[index].string, &value);
     if (kTestData[index].valid) {
       EXPECT_TRUE(valid) << "index " << index << ", string "
@@ -262,41 +334,58 @@
   }
 }
 
-TEST(StringNumberConversion, StringToUnsignedInt64) {
-  static constexpr struct {
-    const char* string;
-    bool valid;
-    uint64_t value;
-  } kTestData[] = {
-      {"", false, 0},
-      {"0", true, 0},
-      {"1", true, 1},
-      {"2147483647", true, 2147483647},
-      {"2147483648", true, 2147483648},
-      {"4294967295", true, 4294967295},
-      {"4294967296", true, 4294967296},
-      {"9223372036854775807", true, 9223372036854775807},
-      {"9223372036854775808", true, 9223372036854775808u},
-      {"18446744073709551615", true, std::numeric_limits<uint64_t>::max()},
-      {"18446744073709551616", false, 0},
-      {"-1", false, 0},
-      {"-2147483648", false, 0},
-      {"-2147483649", false, 0},
-      {"-2147483648", false, 0},
-      {"-9223372036854775808", false, 0},
-      {"-9223372036854775809", false, 0},
-      {"0x7fffffffffffffff", true, 9223372036854775807},
-      {"0x8000000000000000", true, 9223372036854775808u},
-      {"0xffffffffffffffff", true, std::numeric_limits<uint64_t>::max()},
-      {"0x10000000000000000", false, 0},
-      {"-0x7fffffffffffffff", false, 0},
-      {"-0x8000000000000000", false, 0},
-      {"-0x8000000000000001", false, 0},
-      {"0xFfffffffffffffff", true, std::numeric_limits<uint64_t>::max()},
-  };
+TEST(StringNumberConversion, StringToUnsignedLong) {
+  static_assert(
+      sizeof(long) == 4 || sizeof(long) == 8,
+      "Test not configured for " STRINGIFY(__SIZEOF_LONG__) "-byte long");
+  static constexpr auto kTestData = kTestDataFunc<unsigned long>();
 
-  for (size_t index = 0; index < arraysize(kTestData); ++index) {
-    uint64_t value;
+  for (size_t index = 0; index < kTestData.size(); ++index) {
+    unsigned long value;
+    bool valid = StringToNumber(kTestData[index].string, &value);
+    if (kTestData[index].valid) {
+      EXPECT_TRUE(valid) << "index " << index << ", string "
+                         << kTestData[index].string;
+      if (valid) {
+        EXPECT_EQ(value, kTestData[index].value)
+            << "index " << index << ", string " << kTestData[index].string;
+      }
+    } else {
+      EXPECT_FALSE(valid) << "index " << index << ", string "
+                          << kTestData[index].string << ", value " << value;
+    }
+  }
+}
+
+TEST(StringNumberConversion, StringToLongLong) {
+  static_assert(sizeof(long long) == 8,
+                "Test only configured for 64-bit long long.");
+  static constexpr auto kTestData = kTestDataFunc<long long>();
+
+  for (size_t index = 0; index < kTestData.size(); ++index) {
+    long long value;
+    bool valid = StringToNumber(kTestData[index].string, &value);
+    if (kTestData[index].valid) {
+      EXPECT_TRUE(valid) << "index " << index << ", string "
+                         << kTestData[index].string;
+      if (valid) {
+        EXPECT_EQ(value, kTestData[index].value)
+            << "index " << index << ", string " << kTestData[index].string;
+      }
+    } else {
+      EXPECT_FALSE(valid) << "index " << index << ", string "
+                          << kTestData[index].string << ", value " << value;
+    }
+  }
+}
+
+TEST(StringNumberConversion, StringToUnsignedLongLong) {
+  static_assert(sizeof(unsigned long long) == 8,
+                "Test only configured for 64-bit unsigned long long.");
+  static constexpr auto kTestData = kTestDataFunc<unsigned long long>();
+
+  for (size_t index = 0; index < kTestData.size(); ++index) {
+    unsigned long long value;
     bool valid = StringToNumber(kTestData[index].string, &value);
     if (kTestData[index].valid) {
       EXPECT_TRUE(valid) << "index " << index << ", string "
diff --git a/util/stdlib/strlcpy_test.cc b/util/stdlib/strlcpy_test.cc
index 5d20e19..bfb00fe 100644
--- a/util/stdlib/strlcpy_test.cc
+++ b/util/stdlib/strlcpy_test.cc
@@ -20,7 +20,7 @@
 #include <algorithm>
 
 #include "base/format_macros.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
@@ -64,7 +64,7 @@
   static constexpr base::char16 test_characters[] =
       {0x4d, 0xe9, 0x100, 0x151, 0x1e18};
 
-  for (size_t index = 0; index < arraysize(test_characters); ++index) {
+  for (size_t index = 0; index < base::size(test_characters); ++index) {
     base::char16 test_character = test_characters[index];
     SCOPED_TRACE(base::StringPrintf(
         "character index %" PRIuS ", character 0x%x", index, test_character));
@@ -78,13 +78,13 @@
 
       EXPECT_EQ(c16lcpy(destination.data,
                         test_string.c_str(),
-                        arraysize(destination.data)),
+                        base::size(destination.data)),
                 length);
 
       // Make sure that the destination buffer is NUL-terminated, and that as
       // much of the test string was copied as could fit.
       size_t expected_destination_length =
-          std::min(length, arraysize(destination.data) - 1);
+          std::min(length, base::size(destination.data) - 1);
 
       EXPECT_EQ(destination.data[expected_destination_length], '\0');
       EXPECT_EQ(C16Len(destination.data), expected_destination_length);
@@ -97,15 +97,15 @@
       // of the buffer passed to c16lcpy.
       EXPECT_TRUE(C16Memcmp(expected_untouched.lead_guard,
                             destination.lead_guard,
-                            arraysize(destination.lead_guard)) == 0);
+                            base::size(destination.lead_guard)) == 0);
       size_t expected_untouched_length =
-          arraysize(destination.data) - expected_destination_length - 1;
+          base::size(destination.data) - expected_destination_length - 1;
       EXPECT_TRUE(C16Memcmp(expected_untouched.data,
                             &destination.data[expected_destination_length + 1],
                             expected_untouched_length) == 0);
       EXPECT_TRUE(C16Memcmp(expected_untouched.trail_guard,
                             destination.trail_guard,
-                            arraysize(destination.trail_guard)) == 0);
+                            base::size(destination.trail_guard)) == 0);
     }
   }
 }
diff --git a/util/stdlib/strnlen.cc b/util/stdlib/strnlen.cc
index a238728..7ef8d3b 100644
--- a/util/stdlib/strnlen.cc
+++ b/util/stdlib/strnlen.cc
@@ -14,7 +14,8 @@
 
 #include "util/stdlib/strnlen.h"
 
-#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
+#if defined(OS_MACOSX) && !defined(OS_IOS) && \
+    MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
 // Redeclare a method only available on Mac OS X 10.7 and later to suppress a
diff --git a/util/stdlib/strnlen.h b/util/stdlib/strnlen.h
index e85d8c7..1db5f6e 100644
--- a/util/stdlib/strnlen.h
+++ b/util/stdlib/strnlen.h
@@ -20,7 +20,7 @@
 
 #include "build/build_config.h"
 
-#if defined(OS_MACOSX)
+#if defined(OS_MACOSX) && !defined(OS_IOS)
 #include <AvailabilityMacros.h>
 #endif
 
@@ -38,7 +38,7 @@
 //!     and not all systems’ standard libraries provide an implementation.
 size_t strnlen(const char* string, size_t max_length);
 
-#if !defined(OS_MACOSX) || \
+#if !defined(OS_MACOSX) || defined(OS_IOS) || \
     MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7
 inline size_t strnlen(const char* string, size_t max_length) {
   return ::strnlen(string, max_length);
diff --git a/util/stdlib/thread_safe_vector_test.cc b/util/stdlib/thread_safe_vector_test.cc
index 805360f..1d26b09 100644
--- a/util/stdlib/thread_safe_vector_test.cc
+++ b/util/stdlib/thread_safe_vector_test.cc
@@ -14,6 +14,7 @@
 
 #include "util/stdlib/thread_safe_vector.h"
 
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "util/thread/thread.h"
 
@@ -53,12 +54,12 @@
   EXPECT_TRUE(vector.empty());
 
   ThreadSafeVectorTestThread threads[100];
-  for (size_t index = 0; index < arraysize(threads); ++index) {
+  for (size_t index = 0; index < base::size(threads); ++index) {
     threads[index].SetTestParameters(
         &thread_safe_vector, static_cast<int>(index * kElementsPerThread));
   }
 
-  for (size_t index = 0; index < arraysize(threads); ++index) {
+  for (size_t index = 0; index < base::size(threads); ++index) {
     threads[index].Start();
 
     if (index % 10 == 0) {
@@ -75,8 +76,8 @@
 
   std::vector<int> drained = thread_safe_vector.Drain();
   vector.insert(vector.end(), drained.begin(), drained.end());
-  bool found[arraysize(threads) * kElementsPerThread] = {};
-  EXPECT_EQ(vector.size(), arraysize(found));
+  bool found[base::size(threads) * kElementsPerThread] = {};
+  EXPECT_EQ(vector.size(), base::size(found));
   for (int element : vector) {
     EXPECT_FALSE(found[element]) << element;
     found[element] = true;
diff --git a/util/stream/base94_output_stream.cc b/util/stream/base94_output_stream.cc
new file mode 100644
index 0000000..a47d774
--- /dev/null
+++ b/util/stream/base94_output_stream.cc
@@ -0,0 +1,173 @@
+// Copyright 2019 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.
+
+#include "util/stream/base94_output_stream.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+
+namespace crashpad {
+
+namespace {
+// To improve the space efficiency, we can encode 14 bits into two symbols
+// if 14-bit number is less than 94^2 which is total number can be encoded
+// by 2 digits of base94 number, in another word, if 13 bit number is
+// smaller than 643, we could read one more bit, because even if the 14th
+// bit is 1, the 14-bit number doesn’t exceed the max value.
+constexpr uint16_t kMaxValueOf14BitEncoding = (94 * 94 - 1) & 0x1FFF;
+
+constexpr size_t kMaxBuffer = 4096;
+
+inline uint8_t EncodeByte(uint8_t byte) {
+  DCHECK(byte < 94);
+  return (byte >= 94u) ? 0xff : (byte + '!');
+}
+
+inline uint8_t DecodeByte(uint8_t byte) {
+  DCHECK(byte >= '!' && byte <= '~');
+  return std::min(static_cast<uint8_t>(byte - '!'), static_cast<uint8_t>(94));
+}
+
+}  // namespace
+
+Base94OutputStream::Base94OutputStream(
+    Mode mode,
+    std::unique_ptr<OutputStreamInterface> output_stream)
+    : mode_(mode),
+      output_stream_(std::move(output_stream)),
+      bit_buf_(0),
+      bit_count_(0),
+      symbol_buffer_(0),
+      flush_needed_(false),
+      flushed_(false) {
+  buffer_.reserve(kMaxBuffer);
+}
+
+Base94OutputStream::~Base94OutputStream() {
+  DCHECK(!flush_needed_);
+}
+
+bool Base94OutputStream::Write(const uint8_t* data, size_t size) {
+  DCHECK(!flushed_);
+  flush_needed_ = true;
+  return mode_ == Mode::kEncode ? Encode(data, size) : Decode(data, size);
+}
+
+bool Base94OutputStream::Flush() {
+  flushed_ = true;
+  if (flush_needed_) {
+    flush_needed_ = false;
+    if (!((mode_ == Mode::kEncode) ? FinishEncoding() : FinishDecoding()))
+      return false;
+  }
+  return output_stream_->Flush();
+}
+
+bool Base94OutputStream::Encode(const uint8_t* data, size_t size) {
+  const uint8_t* cur = data;
+  while (size--) {
+    bit_buf_ |= *(cur++) << bit_count_;
+    bit_count_ += 8;
+    if (bit_count_ < 14)
+      continue;
+
+    uint16_t block;
+    // Check if 13-bit or 14-bit data should be encoded.
+    if ((bit_buf_ & 0x1FFF) > kMaxValueOf14BitEncoding) {
+      block = bit_buf_ & 0x1FFF;
+      bit_buf_ >>= 13;
+      bit_count_ -= 13;
+    } else {
+      block = bit_buf_ & 0x3FFF;
+      bit_buf_ >>= 14;
+      bit_count_ -= 14;
+    }
+    buffer_.push_back(EncodeByte(block % 94));
+    buffer_.push_back(EncodeByte(base::saturated_cast<uint8_t>(block / 94)));
+
+    if (buffer_.size() > kMaxBuffer - 2 && !WriteOutputStream())
+      return false;
+  }
+  return WriteOutputStream();
+}
+
+bool Base94OutputStream::Decode(const uint8_t* data, size_t size) {
+  const uint8_t* cur = data;
+  while (size--) {
+    if (DecodeByte(*cur) == 94) {
+      LOG(ERROR) << "Decode: invalid input";
+      return false;
+    }
+    if (symbol_buffer_ == 0) {
+      symbol_buffer_ = *cur;
+      cur++;
+      continue;
+    }
+    uint16_t v = DecodeByte(symbol_buffer_) + DecodeByte(*cur) * 94;
+    cur++;
+    symbol_buffer_ = 0;
+    bit_buf_ |= v << bit_count_;
+    bit_count_ += (v & 0x1FFF) > kMaxValueOf14BitEncoding ? 13 : 14;
+    while (bit_count_ > 7) {
+      buffer_.push_back(bit_buf_ & 0xff);
+      bit_buf_ >>= 8;
+      bit_count_ -= 8;
+    }
+    if (buffer_.size() > kMaxBuffer - 2) {
+      if (!WriteOutputStream())
+        return false;
+    }
+  }
+  return WriteOutputStream();
+}
+
+bool Base94OutputStream::FinishEncoding() {
+  if (bit_count_ == 0)
+    return true;
+  // Up to 13 bits data is left over.
+  buffer_.push_back(EncodeByte(bit_buf_ % 94));
+  if (bit_buf_ > 93 || bit_count_ > 8)
+    buffer_.push_back(EncodeByte(base::saturated_cast<uint8_t>(bit_buf_ / 94)));
+  bit_count_ = 0;
+  bit_buf_ = 0;
+  return WriteOutputStream();
+}
+
+bool Base94OutputStream::FinishDecoding() {
+  // The left over bit is padding and all zero, if there is no symbol
+  // unprocessed.
+  if (symbol_buffer_ == 0) {
+    DCHECK(!bit_buf_);
+    return true;
+  }
+  bit_buf_ |= DecodeByte(symbol_buffer_) << bit_count_;
+  buffer_.push_back(bit_buf_ & 0xff);
+  bit_buf_ >>= 8;
+  // The remaining bits are either encode padding or zeros from bit shift.
+  DCHECK(!bit_buf_);
+  return WriteOutputStream();
+}
+
+bool Base94OutputStream::WriteOutputStream() {
+  if (buffer_.empty())
+    return true;
+
+  bool result = output_stream_->Write(buffer_.data(), buffer_.size());
+  buffer_.clear();
+  return result;
+}
+
+}  // namespace crashpad
diff --git a/util/stream/base94_output_stream.h b/util/stream/base94_output_stream.h
new file mode 100644
index 0000000..9d27b2a
--- /dev/null
+++ b/util/stream/base94_output_stream.h
@@ -0,0 +1,79 @@
+// Copyright 2019 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_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_
+#define CRASHPAD_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+
+//! \brief This class implements Base94 encoding/decoding, it uses all
+//! printable characters except space for encoding, and no padding is required.
+//!
+//! This implementation uses two base94 symbols to encoding 13 or 14 bit data,
+//! To maximize encoding efficiency, 14-bit data is encoded into two base94
+//! symbols if its low 13-bit is less than 644 ( = 94^2 - 2^13), otherwise
+//! 13-bit data is encoded.
+class Base94OutputStream : public OutputStreamInterface {
+ public:
+  //! \brief Whether this object is configured to encode or decode data.
+  enum class Mode : bool {
+    //! \brief Data passed through this object is encoded.
+    kEncode = false,
+    //! \brief Data passed through this object is decoded.
+    kDecode = true
+  };
+
+  //! \param[in] mode The work mode of this object.
+  //! \param[in] output_stream The output_stream that this object writes to.
+  Base94OutputStream(Mode mode,
+                     std::unique_ptr<OutputStreamInterface> output_stream);
+  ~Base94OutputStream() override;
+
+  // OutputStreamInterface:
+  bool Write(const uint8_t* data, size_t size) override;
+  bool Flush() override;
+
+ private:
+  bool Encode(const uint8_t* data, size_t size);
+  bool Decode(const uint8_t* data, size_t size);
+  bool FinishEncoding();
+  bool FinishDecoding();
+  // Write encoded/decoded data to |output_stream_| and empty the |buffer_|.
+  bool WriteOutputStream();
+
+  Mode mode_;
+  std::unique_ptr<OutputStreamInterface> output_stream_;
+  std::vector<uint8_t> buffer_;
+  uint32_t bit_buf_;
+  // The number of valid bit in bit_buf_.
+  size_t bit_count_;
+  char symbol_buffer_;
+  bool flush_needed_;
+  bool flushed_;
+
+  DISALLOW_COPY_AND_ASSIGN(Base94OutputStream);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_BASE94_OUTPUT_STREAM_H_
diff --git a/util/stream/base94_output_stream_test.cc b/util/stream/base94_output_stream_test.cc
new file mode 100644
index 0000000..32decbb
--- /dev/null
+++ b/util/stream/base94_output_stream_test.cc
@@ -0,0 +1,290 @@
+// Copyright 2019 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.
+
+#include "util/stream/base94_output_stream.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <sstream>
+
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "util/stream/test_output_stream.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr size_t kLongDataLength = 4096 * 10;
+
+std::string DumpInput(const uint8_t* input, size_t size) {
+  std::stringstream s;
+  size_t index = 0;
+  size_t byte_count = 0;
+  while (index < size) {
+    s << "0x" << std::hex << static_cast<int>(*(input + index++)) << ",";
+    if (byte_count++ > 1024) {
+      s << "\n";
+      byte_count = 0;
+    }
+  }
+  return s.str();
+}
+
+class Base94OutputStreamTest : public testing::Test {
+ public:
+  Base94OutputStreamTest() {}
+
+ protected:
+  void SetUp() override {
+    auto output_stream = std::make_unique<TestOutputStream>();
+    encode_test_output_stream_ = output_stream.get();
+    encoder_ = std::make_unique<Base94OutputStream>(
+        Base94OutputStream::Mode::kEncode, std::move(output_stream));
+    output_stream = std::make_unique<TestOutputStream>();
+    decode_test_output_stream_ = output_stream.get();
+    decoder_ = std::make_unique<Base94OutputStream>(
+        Base94OutputStream::Mode::kDecode, std::move(output_stream));
+    output_stream = std::make_unique<TestOutputStream>();
+    round_trip_test_output_stream_ = output_stream.get();
+    round_trip_ = std::make_unique<Base94OutputStream>(
+        Base94OutputStream::Mode::kEncode,
+        std::make_unique<Base94OutputStream>(Base94OutputStream::Mode::kDecode,
+                                             std::move(output_stream)));
+  }
+
+  const uint8_t* BuildDeterministicInput(size_t size) {
+    deterministic_input_ = std::make_unique<uint8_t[]>(size);
+    uint8_t* deterministic_input_base = deterministic_input_.get();
+    while (size-- > 0)
+      deterministic_input_base[size] = static_cast<uint8_t>(size);
+    return deterministic_input_base;
+  }
+
+  const uint8_t* BuildRandomInput(size_t size) {
+    input_ = std::make_unique<uint8_t[]>(size);
+    base::RandBytes(&input_[0], size);
+    return input_.get();
+  }
+
+  Base94OutputStream* round_trip() const { return round_trip_.get(); }
+  const TestOutputStream& round_trip_test_output_stream() const {
+    return *round_trip_test_output_stream_;
+  }
+
+  static void VerifyEncoding(const TestOutputStream& out,
+                             const std::string& expected) {
+    EXPECT_EQ(out.all_data().size(), expected.size());
+    EXPECT_EQ(memcmp(out.all_data().data(), expected.data(), expected.size()),
+              0);
+  }
+
+  static void VerifyDecoding(const TestOutputStream& out,
+                             const std::vector<uint8_t>& expected) {
+    EXPECT_EQ(out.all_data().size(), expected.size());
+    EXPECT_EQ(memcmp(out.all_data().data(), expected.data(), expected.size()),
+              0);
+  }
+
+  void RunTest(const std::string& text, const std::vector<uint8_t>& binary) {
+    EXPECT_TRUE(encoder_->Write(binary.data(), binary.size()));
+    EXPECT_TRUE(encoder_->Flush());
+    VerifyEncoding(*encode_test_output_stream_, text);
+    EXPECT_TRUE(decoder_->Write(reinterpret_cast<const uint8_t*>(text.data()),
+                                text.size()));
+    EXPECT_TRUE(decoder_->Flush());
+    VerifyDecoding(*decode_test_output_stream_, binary);
+  }
+
+  void VerifyRoundTrip(const std::vector<uint8_t>& expected) {
+    TestOutputStream* out = round_trip_test_output_stream_;
+    EXPECT_EQ(out->all_data().size(), expected.size());
+    EXPECT_EQ(memcmp(out->all_data().data(), expected.data(), expected.size()),
+              0);
+  }
+
+ private:
+  std::unique_ptr<Base94OutputStream> encoder_;
+  std::unique_ptr<Base94OutputStream> decoder_;
+  std::unique_ptr<Base94OutputStream> round_trip_;
+  TestOutputStream* encode_test_output_stream_;
+  TestOutputStream* decode_test_output_stream_;
+  TestOutputStream* round_trip_test_output_stream_;
+  std::unique_ptr<uint8_t[]> input_;
+  std::unique_ptr<uint8_t[]> deterministic_input_;
+
+  DISALLOW_COPY_AND_ASSIGN(Base94OutputStreamTest);
+};
+
+TEST_F(Base94OutputStreamTest, Encoding) {
+  std::vector<uint8_t> binary = {0x0};
+  std::string text("!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding1) {
+  std::vector<uint8_t> binary = {0x0, 0x0};
+  std::string text("!!!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding2) {
+  std::vector<uint8_t> binary = {0x0, 0x0, 0x0};
+  std::string text("!!!!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding3) {
+  std::vector<uint8_t> binary = {0x0, 0x0, 0x0, 0x0};
+  std::string text("!!!!!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding4) {
+  std::vector<uint8_t> binary = {0x0, 0x0, 0x0, 0x0, 0x0};
+  std::string text("!!!!!!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding10) {
+  std::vector<uint8_t> binary = {0xFF};
+  std::string text("d#");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding11) {
+  std::vector<uint8_t> binary = {0xFF, 0xFF};
+  std::string text(".x(");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding12) {
+  std::vector<uint8_t> binary = {0xFF, 0xFF, 0xFF};
+  std::string text(".xj6");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding13) {
+  std::vector<uint8_t> binary = {0xFF, 0xFF, 0xFF, 0xFF};
+  std::string text(".x.x`");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Encoding14) {
+  std::vector<uint8_t> binary = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+  std::string text(".x.x.x\"");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, Printable94) {
+  std::vector<uint8_t> binary = {
+      0x5e, 0x0,  0x47, 0xa0, 0x1d, 0x60, 0xa,  0xab, 0x41, 0x41, 0xa4, 0x9,
+      0x64, 0x71, 0x32, 0xc,  0x47, 0xf9, 0x20, 0x22, 0xa3, 0x44, 0xa0, 0x84,
+      0x15, 0xe0, 0xf2, 0x61, 0xfc, 0x4c, 0xb7, 0xe1, 0x39, 0x9b, 0x47, 0xff,
+      0x64, 0x21, 0x5c, 0x74, 0x91, 0xec, 0x52, 0x75, 0xa2, 0x51, 0x93, 0x4a,
+      0x5e, 0x45, 0x2d, 0xd8, 0xf5, 0xc0, 0xdc, 0x58, 0x33, 0x63, 0x69, 0x8b,
+      0x4d, 0xbd, 0x25, 0x39, 0x54, 0x77, 0xf0, 0xcc, 0x5e, 0xf1, 0x23, 0x81,
+      0x6,  0x21, 0x71, 0x28, 0x28, 0x2};
+  std::string text(
+      "!\"#$%&'()*+,-./"
+      "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
+      "abcdefghijklmnopqrstuvwxyz{|}~!");
+  RunTest(text, binary);
+}
+
+TEST_F(Base94OutputStreamTest, WriteLongDataMultipleTimes) {
+  const uint8_t* input = BuildRandomInput(kLongDataLength);
+  SCOPED_TRACE(base::StringPrintf("Input: %s",
+                                  DumpInput(input, kLongDataLength).c_str()));
+  // Call Write() a random number of times.
+  size_t index = 0;
+  while (index < kLongDataLength) {
+    size_t write_length =
+        std::min(static_cast<size_t>(base::RandInt(0, 4096 * 2)),
+                 kLongDataLength - index);
+    SCOPED_TRACE(
+        base::StringPrintf("index %zu, write_length %zu", index, write_length));
+    EXPECT_TRUE(round_trip()->Write(input + index, write_length));
+    index += write_length;
+  }
+  EXPECT_TRUE(round_trip()->Flush());
+  EXPECT_EQ(round_trip_test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(memcmp(round_trip_test_output_stream().all_data().data(),
+                   input,
+                   kLongDataLength),
+            0);
+}
+
+TEST_F(Base94OutputStreamTest, WriteDeterministicLongDataMultipleTimes) {
+  const uint8_t* input = BuildDeterministicInput(kLongDataLength);
+
+  static constexpr size_t kWriteLengths[] = {
+      4, 96, 40, kLongDataLength - 4 - 96 - 40};
+
+  size_t offset = 0;
+  for (size_t index = 0; index < base::size(kWriteLengths); ++index) {
+    const size_t write_length = kWriteLengths[index];
+    SCOPED_TRACE(base::StringPrintf(
+        "offset %zu, write_length %zu", offset, write_length));
+    EXPECT_TRUE(round_trip()->Write(input + offset, write_length));
+    offset += write_length;
+  }
+  EXPECT_TRUE(round_trip()->Flush());
+  EXPECT_EQ(round_trip_test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(memcmp(round_trip_test_output_stream().all_data().data(),
+                   input,
+                   kLongDataLength),
+            0);
+}
+
+TEST_F(Base94OutputStreamTest, NoWriteOrFlush) {
+  EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u);
+  EXPECT_EQ(round_trip_test_output_stream().flush_count(), 0u);
+  EXPECT_TRUE(round_trip_test_output_stream().all_data().empty());
+}
+
+TEST_F(Base94OutputStreamTest, FlushWithoutWrite) {
+  EXPECT_TRUE(round_trip()->Flush());
+  EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u);
+  EXPECT_EQ(round_trip_test_output_stream().flush_count(), 1u);
+  EXPECT_TRUE(round_trip_test_output_stream().all_data().empty());
+}
+
+TEST_F(Base94OutputStreamTest, WriteEmptyData) {
+  std::vector<uint8_t> empty_data;
+  EXPECT_TRUE(round_trip()->Write(
+      static_cast<const uint8_t*>(empty_data.data()), empty_data.size()));
+  EXPECT_TRUE(round_trip()->Flush());
+  EXPECT_TRUE(round_trip()->Flush());
+  EXPECT_EQ(round_trip_test_output_stream().write_count(), 0u);
+  EXPECT_EQ(round_trip_test_output_stream().flush_count(), 2u);
+  EXPECT_TRUE(round_trip_test_output_stream().all_data().empty());
+}
+
+TEST_F(Base94OutputStreamTest, Process7bitsInFinishDecoding) {
+  std::vector<uint8_t> input = {
+      0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+  EXPECT_TRUE(round_trip()->Write(static_cast<const uint8_t*>(input.data()),
+                                  input.size()));
+  EXPECT_TRUE(round_trip()->Flush());
+  VerifyRoundTrip(input);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/stream/file_encoder.cc b/util/stream/file_encoder.cc
new file mode 100644
index 0000000..bb2c41a
--- /dev/null
+++ b/util/stream/file_encoder.cc
@@ -0,0 +1,85 @@
+// Copyright 2019 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.
+
+#include "util/stream/file_encoder.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "util/file/file_io.h"
+#include "util/file/file_reader.h"
+#include "util/file/scoped_remove_file.h"
+#include "util/stream/base94_output_stream.h"
+#include "util/stream/file_output_stream.h"
+#include "util/stream/output_stream_interface.h"
+#include "util/stream/zlib_output_stream.h"
+
+namespace crashpad {
+
+FileEncoder::FileEncoder(Mode mode,
+                         const base::FilePath& input_path,
+                         const base::FilePath& output_path)
+    : mode_(mode), input_path_(input_path), output_path_(output_path) {}
+
+FileEncoder::~FileEncoder() {}
+
+bool FileEncoder::Process() {
+  ScopedRemoveFile file_remover;
+  ScopedFileHandle write_handle(LoggingOpenFileForWrite(
+      output_path_, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly));
+  if (!write_handle.is_valid())
+    return false;
+
+  // Remove the output file on failure.
+  file_remover.reset(output_path_);
+
+  std::unique_ptr<OutputStreamInterface> output;
+  if (mode_ == Mode::kEncode) {
+    output = std::make_unique<ZlibOutputStream>(
+        ZlibOutputStream::Mode::kCompress,
+        std::make_unique<Base94OutputStream>(
+            Base94OutputStream::Mode::kEncode,
+            std::make_unique<FileOutputStream>(write_handle.get())));
+  } else {
+    output = std::make_unique<Base94OutputStream>(
+        Base94OutputStream::Mode::kDecode,
+        std::make_unique<ZlibOutputStream>(
+            ZlibOutputStream::Mode::kDecompress,
+            std::make_unique<FileOutputStream>(write_handle.get())));
+  }
+
+  FileReader file_reader;
+  if (!file_reader.Open(input_path_))
+    return false;
+
+  FileOperationResult read_result;
+  do {
+    uint8_t buffer[4096];
+    read_result = file_reader.Read(buffer, sizeof(buffer));
+    if (read_result < 0)
+      return false;
+
+    if (read_result > 0 && (!output->Write(buffer, read_result)))
+      return false;
+  } while (read_result > 0);
+
+  if (!output->Flush())
+    return false;
+
+  ignore_result(file_remover.release());
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/stream/file_encoder.h b/util/stream/file_encoder.h
new file mode 100644
index 0000000..2f34b0b
--- /dev/null
+++ b/util/stream/file_encoder.h
@@ -0,0 +1,59 @@
+// Copyright 2019 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_UTIL_STREAM_FILE_ENCODER_H_
+#define CRASHPAD_UTIL_STREAM_FILE_ENCODER_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+
+namespace crashpad {
+
+//! \brief The class is used to compress and base94-encode, or base94-decode
+//! and decompress the given input file to the output file.
+class FileEncoder {
+ public:
+  //! \brief Whether this object is configured to encode or decode data.
+  enum class Mode : bool {
+    //! \brief Data passed through this object is encoded.
+    kEncode = false,
+    //! \brief Data passed through this object is decoded.
+    kDecode = true
+  };
+
+  //! \param[in] mode The work mode of this object.
+  //! \param[in] input_path The input file that this object reads from.
+  //! \param[in] output_path The output file that this object writes to.
+  FileEncoder(Mode mode,
+              const base::FilePath& input_path,
+              const base::FilePath& output_path);
+  ~FileEncoder();
+
+  //! \brief Encode/decode the data from \a input_path_ file according work
+  //! \a mode, and write the result to \a output_path_ on success.
+  //!
+  //! \return `true` on success.
+  bool Process();
+
+ private:
+  Mode mode_;
+  base::FilePath input_path_;
+  base::FilePath output_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileEncoder);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_FILE_ENCODER_H_
diff --git a/util/stream/file_encoder_test.cc b/util/stream/file_encoder_test.cc
new file mode 100644
index 0000000..ff98914
--- /dev/null
+++ b/util/stream/file_encoder_test.cc
@@ -0,0 +1,112 @@
+// Copyright 2019 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.
+
+#include "util/stream/file_encoder.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "gtest/gtest.h"
+#include "test/scoped_temp_dir.h"
+#include "util/file/file_io.h"
+#include "util/stream/file_output_stream.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr size_t kBufferSize = 4096;
+
+class FileEncoderTest : public testing::Test {
+ public:
+  FileEncoderTest() {}
+
+  void Verify(size_t size) {
+    std::string contents;
+    ASSERT_TRUE(LoggingReadEntireFile(decoded_, &contents));
+    ASSERT_EQ(contents.size(), size);
+    EXPECT_EQ(memcmp(deterministic_input_.get(), contents.data(), size), 0);
+  }
+
+  const uint8_t* BuildDeterministicInput(size_t size) {
+    deterministic_input_ = std::make_unique<uint8_t[]>(size);
+    uint8_t* deterministic_input_base = deterministic_input_.get();
+    while (size-- > 0)
+      deterministic_input_base[size] = static_cast<uint8_t>(size);
+    return deterministic_input_base;
+  }
+
+  void GenerateOrigFile(size_t size) {
+    ScopedFileHandle write_handle(OpenFileForWrite(
+        orig_, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly));
+    ASSERT_TRUE(write_handle.is_valid());
+    FileOutputStream out(write_handle.get());
+    const uint8_t* buf = BuildDeterministicInput(size);
+    while (size > 0) {
+      size_t m = std::min(kBufferSize, size);
+      ASSERT_TRUE(out.Write(buf, m));
+      size -= m;
+      buf += m;
+    }
+    ASSERT_TRUE(out.Flush());
+  }
+
+  FileEncoder* encoder() const { return encoder_.get(); }
+
+  FileEncoder* decoder() const { return decoder_.get(); }
+
+ protected:
+  void SetUp() override {
+    temp_dir_ = std::make_unique<ScopedTempDir>();
+    orig_ = base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("orig")));
+    encoded_ =
+        base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("encoded")));
+    decoded_ =
+        base::FilePath(temp_dir_->path().Append(FILE_PATH_LITERAL("decoded")));
+    encoder_ = std::make_unique<FileEncoder>(
+        FileEncoder::Mode::kEncode, orig_, encoded_);
+    decoder_ = std::make_unique<FileEncoder>(
+        FileEncoder::Mode::kDecode, encoded_, decoded_);
+  }
+
+ private:
+  std::unique_ptr<ScopedTempDir> temp_dir_;
+  base::FilePath orig_;
+  base::FilePath encoded_;
+  base::FilePath decoded_;
+  std::unique_ptr<FileEncoder> encoder_;
+  std::unique_ptr<FileEncoder> decoder_;
+  std::unique_ptr<uint8_t[]> deterministic_input_;
+};
+
+TEST_F(FileEncoderTest, ProcessShortFile) {
+  GenerateOrigFile(kBufferSize - 512);
+  EXPECT_TRUE(encoder()->Process());
+  EXPECT_TRUE(decoder()->Process());
+  Verify(kBufferSize - 512);
+}
+
+TEST_F(FileEncoderTest, ProcessLongFile) {
+  GenerateOrigFile(kBufferSize + 512);
+  EXPECT_TRUE(encoder()->Process());
+  EXPECT_TRUE(decoder()->Process());
+  Verify(kBufferSize + 512);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/stream/file_output_stream.cc b/util/stream/file_output_stream.cc
new file mode 100644
index 0000000..022d91f
--- /dev/null
+++ b/util/stream/file_output_stream.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 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.
+
+#include "util/stream/file_output_stream.h"
+
+#include "base/logging.h"
+
+namespace crashpad {
+
+FileOutputStream::FileOutputStream(FileHandle file_handle)
+    : writer_(file_handle), flush_needed_(false), flushed_(false) {}
+
+FileOutputStream::~FileOutputStream() {
+  DCHECK(!flush_needed_);
+}
+
+bool FileOutputStream::Write(const uint8_t* data, size_t size) {
+  DCHECK(!flushed_);
+
+  if (!writer_.Write(data, size)) {
+    LOG(ERROR) << "Write: Failed";
+    return false;
+  }
+  flush_needed_ = true;
+  return true;
+}
+
+bool FileOutputStream::Flush() {
+  flush_needed_ = false;
+  flushed_ = true;
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/stream/file_output_stream.h b/util/stream/file_output_stream.h
new file mode 100644
index 0000000..128ccd5
--- /dev/null
+++ b/util/stream/file_output_stream.h
@@ -0,0 +1,46 @@
+// Copyright 2019 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_UTIL_STREAM_FILE_OUTPUT_STREAM_H_
+#define CRASHPAD_UTIL_STREAM_FILE_OUTPUT_STREAM_H_
+
+#include "base/macros.h"
+#include "util/file/file_io.h"
+#include "util/file/file_writer.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+
+//! \brief The class is used to write data to a file.
+class FileOutputStream : public OutputStreamInterface {
+ public:
+  //! \param[in] file_handle The file that this object writes to.
+  explicit FileOutputStream(FileHandle file_handle);
+  ~FileOutputStream();
+
+  // OutputStream.
+  bool Write(const uint8_t* data, size_t size) override;
+  bool Flush() override;
+
+ private:
+  WeakFileHandleFileWriter writer_;
+  bool flush_needed_;
+  bool flushed_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_FILE_OUTPUT_STREAM_H_
diff --git a/util/stream/log_output_stream.cc b/util/stream/log_output_stream.cc
new file mode 100644
index 0000000..03c0a5a
--- /dev/null
+++ b/util/stream/log_output_stream.cc
@@ -0,0 +1,130 @@
+// Copyright 2019 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.
+
+#include "util/stream/log_output_stream.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+#if defined(OS_ANDROID)
+#include <android/log.h>
+#endif
+
+namespace crashpad {
+
+namespace {
+
+// Most minidumps are expected to be compressed and encoded into less than 128k.
+constexpr size_t kOutputCap = 128 * 1024;
+
+// From Android NDK r20 <android/log.h>, log message text may be truncated to
+// less than an implementation-specific limit (1023 bytes), for sake of safe
+// and being easy to read in logcat, choose 512.
+constexpr size_t kLineBufferSize = 512;
+
+}  // namespace
+
+LogOutputStream::LogOutputStream()
+    : output_count_(0), flush_needed_(false), flushed_(false) {
+  buffer_.reserve(kLineBufferSize);
+}
+
+LogOutputStream::~LogOutputStream() {
+  DCHECK(!flush_needed_);
+}
+
+bool LogOutputStream::Write(const uint8_t* data, size_t size) {
+  DCHECK(!flushed_);
+  flush_needed_ = true;
+  while (size > 0) {
+    size_t m = std::min(kLineBufferSize - buffer_.size(), size);
+    buffer_.append(reinterpret_cast<const char*>(data), m);
+    data += m;
+    size -= m;
+    if (buffer_.size() == kLineBufferSize && !WriteBuffer()) {
+      flush_needed_ = false;
+      LOG(ERROR) << "Write: exceeds cap.";
+      if (output_stream_for_testing_)
+        output_stream_for_testing_->Flush();
+      return false;
+    }
+  }
+  return true;
+}
+
+bool LogOutputStream::WriteBuffer() {
+  if (output_count_ == 0) {
+    if (!WriteToLog("-----BEGIN CRASHPAD MINIDUMP-----"))
+      return false;
+  }
+
+  if (buffer_.empty())
+    return true;
+
+  output_count_ += buffer_.size();
+  if (output_count_ > kOutputCap) {
+    WriteToLog("-----ABORT CRASHPAD MINIDUMP-----");
+    return false;
+  }
+
+  bool result = WriteToLog(buffer_.c_str());
+  buffer_.clear();
+  return result;
+}
+
+bool LogOutputStream::WriteToLog(const char* buf) {
+#if defined(OS_ANDROID)
+  int ret =
+      __android_log_buf_write(LOG_ID_CRASH, ANDROID_LOG_FATAL, "crashpad", buf);
+  if (ret < 0) {
+    errno = -ret;
+    PLOG(ERROR) << "__android_log_buf_write";
+    return false;
+  }
+#endif
+  // For testing.
+  if (output_stream_for_testing_) {
+    return output_stream_for_testing_->Write(
+        reinterpret_cast<const uint8_t*>(buf), strlen(buf));
+  }
+  return true;
+}
+
+bool LogOutputStream::Flush() {
+  flush_needed_ = false;
+  flushed_ = true;
+
+  bool result = true;
+  if (WriteBuffer()) {
+    result = WriteToLog("-----END CRASHPAD MINIDUMP-----");
+  } else {
+    LOG(ERROR) << "Flush: exceeds cap.";
+    result = false;
+  }
+
+  // Since output_stream_for_testing_'s Write() method has been called, its
+  // Flush() shall always be invoked.
+  if (output_stream_for_testing_)
+    output_stream_for_testing_->Flush();
+
+  return result;
+}
+
+void LogOutputStream::SetOutputStreamForTesting(
+    std::unique_ptr<OutputStreamInterface> stream) {
+  output_stream_for_testing_ = std::move(stream);
+}
+
+}  // namespace crashpad
diff --git a/util/stream/log_output_stream.h b/util/stream/log_output_stream.h
new file mode 100644
index 0000000..4a91f5c
--- /dev/null
+++ b/util/stream/log_output_stream.h
@@ -0,0 +1,61 @@
+// Copyright 2019 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_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
+#define CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+
+//! \brief This class output the received data to Android log, NOP in other
+//! platform.
+//!
+//! To avoid overflowing Android log, total 128k log data is allowed, after
+//! that cap, the output is aborted.
+class LogOutputStream : public OutputStreamInterface {
+ public:
+  LogOutputStream();
+  ~LogOutputStream() override;
+
+  // OutputStreamInterface:
+  bool Write(const uint8_t* data, size_t size) override;
+  bool Flush() override;
+
+  void SetOutputStreamForTesting(std::unique_ptr<OutputStreamInterface> stream);
+
+ private:
+  // Flush the |buffer_|, return false if kOutputCap meet.
+  bool WriteBuffer();
+  bool WriteToLog(const char* buf);
+
+  std::string buffer_;
+  size_t output_count_;
+  bool flush_needed_;
+  bool flushed_;
+  std::unique_ptr<OutputStreamInterface> output_stream_for_testing_;
+
+  DISALLOW_COPY_AND_ASSIGN(LogOutputStream);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_LOG_OUTPUT_STREAM_H_
diff --git a/util/stream/log_output_stream_test.cc b/util/stream/log_output_stream_test.cc
new file mode 100644
index 0000000..510df76
--- /dev/null
+++ b/util/stream/log_output_stream_test.cc
@@ -0,0 +1,126 @@
+// Copyright 2019 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.
+
+#include "util/stream/log_output_stream.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "gtest/gtest.h"
+#include "util/stream/test_output_stream.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr size_t kOutputCap = 128 * 1024;
+constexpr size_t kLineBufferSize = 512;
+const char* kBeginGuard = "-----BEGIN CRASHPAD MINIDUMP-----";
+const char* kEndGuard = "-----END CRASHPAD MINIDUMP-----";
+const char* kAbortGuard = "-----ABORT CRASHPAD MINIDUMP-----";
+
+std::string ConvertToString(const std::vector<uint8_t>& src) {
+  return std::string(reinterpret_cast<const char*>(src.data()), src.size());
+}
+
+class LogOutputStreamTest : public testing::Test {
+ public:
+  LogOutputStreamTest() : test_output_stream_(nullptr) {}
+
+ protected:
+  void SetUp() override {
+    std::unique_ptr<TestOutputStream> output_stream =
+        std::make_unique<TestOutputStream>();
+    test_output_stream_ = output_stream.get();
+    log_stream_ = std::make_unique<LogOutputStream>();
+    log_stream_->SetOutputStreamForTesting(std::move(output_stream));
+  }
+
+  const uint8_t* BuildDeterministicInput(size_t size) {
+    deterministic_input_ = std::make_unique<uint8_t[]>(size);
+    uint8_t* deterministic_input_base = deterministic_input_.get();
+    while (size-- > 0)
+      deterministic_input_base[size] = static_cast<uint8_t>('a');
+    return deterministic_input_base;
+  }
+
+  TestOutputStream* test_output_stream() const { return test_output_stream_; }
+
+  LogOutputStream* log_stream() const { return log_stream_.get(); }
+
+ private:
+  std::unique_ptr<LogOutputStream> log_stream_;
+  TestOutputStream* test_output_stream_;
+  std::unique_ptr<uint8_t[]> deterministic_input_;
+
+  DISALLOW_COPY_AND_ASSIGN(LogOutputStreamTest);
+};
+
+TEST_F(LogOutputStreamTest, VerifyGuards) {
+  log_stream()->Flush();
+  // Verify OutputStream wrote 2 guards.
+  EXPECT_EQ(test_output_stream()->write_count(), 2u);
+  EXPECT_EQ(test_output_stream()->flush_count(), 1u);
+  EXPECT_FALSE(test_output_stream()->all_data().empty());
+  EXPECT_EQ(ConvertToString(test_output_stream()->all_data()),
+            std::string(kBeginGuard).append(kEndGuard));
+}
+
+TEST_F(LogOutputStreamTest, WriteShortLog) {
+  const uint8_t* input = BuildDeterministicInput(2);
+  EXPECT_TRUE(log_stream()->Write(input, 2));
+  EXPECT_TRUE(log_stream()->Flush());
+  // Verify OutputStream wrote 2 guards and data.
+  EXPECT_EQ(test_output_stream()->write_count(), 3u);
+  EXPECT_EQ(test_output_stream()->flush_count(), 1u);
+  EXPECT_FALSE(test_output_stream()->all_data().empty());
+  EXPECT_EQ(ConvertToString(test_output_stream()->all_data()),
+            std::string(kBeginGuard).append("aa").append(kEndGuard));
+}
+
+TEST_F(LogOutputStreamTest, WriteLongLog) {
+  size_t input_length = kLineBufferSize + kLineBufferSize / 2;
+  const uint8_t* input = BuildDeterministicInput(input_length);
+  // Verify OutputStream wrote 2 guards and data.
+  EXPECT_TRUE(log_stream()->Write(input, input_length));
+  EXPECT_TRUE(log_stream()->Flush());
+  EXPECT_EQ(test_output_stream()->write_count(),
+            2 + input_length / kLineBufferSize + 1);
+  EXPECT_EQ(test_output_stream()->flush_count(), 1u);
+  EXPECT_EQ(test_output_stream()->all_data().size(),
+            strlen(kBeginGuard) + strlen(kEndGuard) + input_length);
+}
+
+TEST_F(LogOutputStreamTest, WriteAbort) {
+  size_t input_length = kOutputCap + kLineBufferSize;
+  const uint8_t* input = BuildDeterministicInput(input_length);
+  EXPECT_FALSE(log_stream()->Write(input, input_length));
+  std::string data(ConvertToString(test_output_stream()->all_data()));
+  EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard);
+}
+
+TEST_F(LogOutputStreamTest, FlushAbort) {
+  size_t input_length = kOutputCap + kLineBufferSize / 2;
+  const uint8_t* input = BuildDeterministicInput(input_length);
+  EXPECT_TRUE(log_stream()->Write(input, input_length));
+  EXPECT_FALSE(log_stream()->Flush());
+  std::string data(ConvertToString(test_output_stream()->all_data()));
+  EXPECT_EQ(data.substr(data.size() - strlen(kAbortGuard)), kAbortGuard);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/stream/output_stream_interface.h b/util/stream/output_stream_interface.h
new file mode 100644
index 0000000..397860e
--- /dev/null
+++ b/util/stream/output_stream_interface.h
@@ -0,0 +1,64 @@
+// Copyright 2019 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_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_
+#define CRASHPAD_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace crashpad {
+
+//! \brief The interface for an output stream pipeline.
+//!
+//! Example:
+//! <code>
+//!   class OutputStreamInterfaceImpl : public OutputStreamInterface {
+//!     ...
+//!   };
+//!
+//!   // Create a OutputStream.
+//!   OutputStreamInterfaceImpl impl(...);
+//!   // Write the data multiple times.
+//!   while (has_data) {
+//!     impl.Write(data, size);
+//!     ...
+//!   }
+//!   // Flush internal buffer to indicate all data has been written.
+//!   impl.Flush();
+//! </code>
+//!
+class OutputStreamInterface {
+ public:
+  virtual ~OutputStreamInterface() = default;
+
+  //! \brief Writes \a data to this stream. This method may be called multiple
+  //! times for streaming.
+  //!
+  //! \param[in] data The data that should be written.
+  //! \param[in] size The size of \a data.
+  //!
+  //! \return `true` on success.
+  virtual bool Write(const uint8_t* data, size_t size) = 0;
+
+  //! \brief Flush the internal buffer after all data has been written.
+  //!
+  //! Write() can't be called afterwards.
+  //! \return `true` on success.
+  virtual bool Flush() = 0;
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_OUTPUT_STREAM_INTERFACE_H_
diff --git a/util/stream/test_output_stream.cc b/util/stream/test_output_stream.cc
new file mode 100644
index 0000000..04ef700
--- /dev/null
+++ b/util/stream/test_output_stream.cc
@@ -0,0 +1,48 @@
+// Copyright 2019 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.
+
+#include "util/stream/test_output_stream.h"
+#include "base/logging.h"
+
+namespace crashpad {
+namespace test {
+
+TestOutputStream::TestOutputStream()
+    : last_written_data_(),
+      all_data_(),
+      write_count_(0),
+      flush_count_(0),
+      flush_needed_(false) {}
+
+TestOutputStream::~TestOutputStream() {
+  DCHECK(!flush_needed_);
+}
+
+bool TestOutputStream::Write(const uint8_t* data, size_t size) {
+  last_written_data_.assign(data, data + size);
+  all_data_.insert(all_data_.end(), data, data + size);
+
+  flush_needed_ = true;
+  write_count_++;
+  return true;
+}
+
+bool TestOutputStream::Flush() {
+  flush_needed_ = false;
+  flush_count_++;
+  return true;
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/stream/test_output_stream.h b/util/stream/test_output_stream.h
new file mode 100644
index 0000000..dcd0d45
--- /dev/null
+++ b/util/stream/test_output_stream.h
@@ -0,0 +1,66 @@
+// Copyright 2019 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_UTIL_STREAM_TEST_OUTPUT_STREAM_H_
+#define CRASHPAD_UTIL_STREAM_TEST_OUTPUT_STREAM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+namespace test {
+
+//! \brief The help class for \a OutputStreamInterface related tests.
+class TestOutputStream : public OutputStreamInterface {
+ public:
+  TestOutputStream();
+  ~TestOutputStream() override;
+
+  // OutputStreamInterface:
+  bool Write(const uint8_t* data, size_t size) override;
+  bool Flush() override;
+
+  //! \return the data that has been received by the last call of Write().
+  const std::vector<uint8_t>& last_written_data() const {
+    return last_written_data_;
+  }
+
+  //! \return all data that has been received.
+  const std::vector<uint8_t>& all_data() const { return all_data_; }
+
+  //! \return the number of times Write() has been called.
+  size_t write_count() const { return write_count_; }
+
+  //! \return the number of times Flush() has been called.
+  size_t flush_count() const { return flush_count_; }
+
+ private:
+  std::vector<uint8_t> last_written_data_;
+  std::vector<uint8_t> all_data_;
+  size_t write_count_;
+  size_t flush_count_;
+  bool flush_needed_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestOutputStream);
+};
+
+}  // namespace test
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_TEST_OUTPUT_STREAM_H_
diff --git a/util/stream/zlib_output_stream.cc b/util/stream/zlib_output_stream.cc
new file mode 100644
index 0000000..88861db
--- /dev/null
+++ b/util/stream/zlib_output_stream.cc
@@ -0,0 +1,139 @@
+// Copyright 2019 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.
+
+#include "util/stream/zlib_output_stream.h"
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/stl_util.h"
+#include "util/misc/zlib.h"
+
+namespace crashpad {
+
+ZlibOutputStream::ZlibOutputStream(
+    Mode mode,
+    std::unique_ptr<OutputStreamInterface> output_stream)
+    : output_stream_(std::move(output_stream)),
+      mode_(mode),
+      initialized_(),
+      flush_needed_(false) {}
+
+ZlibOutputStream::~ZlibOutputStream() {
+  if (!initialized_.is_valid())
+    return;
+  DCHECK(!flush_needed_);
+  if (mode_ == Mode::kCompress) {
+    if (deflateEnd(&zlib_stream_) != Z_OK)
+      LOG(ERROR) << "deflateEnd: " << zlib_stream_.msg;
+  } else if (mode_ == Mode::kDecompress) {
+    if (inflateEnd(&zlib_stream_) != Z_OK)
+      LOG(ERROR) << "inflateEnd: " << zlib_stream_.msg;
+  }
+}
+
+bool ZlibOutputStream::Write(const uint8_t* data, size_t size) {
+  if (initialized_.is_uninitialized()) {
+    initialized_.set_invalid();
+
+    zlib_stream_.zalloc = Z_NULL;
+    zlib_stream_.zfree = Z_NULL;
+    zlib_stream_.opaque = Z_NULL;
+
+    if (mode_ == Mode::kDecompress) {
+      int result = inflateInit(&zlib_stream_);
+      if (result != Z_OK) {
+        LOG(ERROR) << "inflateInit: " << ZlibErrorString(result);
+        return false;
+      }
+    } else if (mode_ == Mode::kCompress) {
+      int result = deflateInit(&zlib_stream_, Z_BEST_COMPRESSION);
+      if (result != Z_OK) {
+        LOG(ERROR) << "deflateInit: " << ZlibErrorString(result);
+        return false;
+      }
+    }
+    zlib_stream_.next_out = buffer_;
+    zlib_stream_.avail_out = base::saturated_cast<uInt>(base::size(buffer_));
+    initialized_.set_valid();
+  }
+
+  if (!initialized_.is_valid())
+    return false;
+
+  zlib_stream_.next_in = data;
+  zlib_stream_.avail_in = base::saturated_cast<uInt>(size);
+  flush_needed_ = false;
+  while (zlib_stream_.avail_in > 0) {
+    if (mode_ == Mode::kCompress) {
+      if (deflate(&zlib_stream_, Z_NO_FLUSH) != Z_OK) {
+        LOG(ERROR) << "deflate: " << zlib_stream_.msg;
+        return false;
+      }
+    } else if (mode_ == Mode::kDecompress) {
+      int result = inflate(&zlib_stream_, Z_NO_FLUSH);
+      if (result == Z_STREAM_END) {
+        if (zlib_stream_.avail_in > 0) {
+          LOG(ERROR) << "inflate: unconsumed input";
+          return false;
+        }
+      } else if (result != Z_OK) {
+        LOG(ERROR) << "inflate: " << zlib_stream_.msg;
+        return false;
+      }
+    }
+
+    if (!WriteOutputStream())
+      return false;
+  }
+  flush_needed_ = true;
+  return true;
+}
+
+bool ZlibOutputStream::Flush() {
+  if (initialized_.is_valid() && flush_needed_) {
+    flush_needed_ = false;
+    int result = Z_OK;
+    do {
+      if (mode_ == Mode::kCompress) {
+        result = deflate(&zlib_stream_, Z_FINISH);
+        if (result != Z_STREAM_END && result != Z_BUF_ERROR && result != Z_OK) {
+          LOG(ERROR) << "deflate: " << zlib_stream_.msg;
+          return false;
+        }
+      } else if (mode_ == Mode::kDecompress) {
+        result = inflate(&zlib_stream_, Z_FINISH);
+        if (result != Z_STREAM_END && result != Z_BUF_ERROR && result != Z_OK) {
+          LOG(ERROR) << "inflate: " << zlib_stream_.msg;
+          return false;
+        }
+      }
+      if (!WriteOutputStream())
+        return false;
+    } while (result != Z_STREAM_END);
+  }
+  return output_stream_->Flush();
+}
+
+bool ZlibOutputStream::WriteOutputStream() {
+  auto valid_size = base::size(buffer_) - zlib_stream_.avail_out;
+  if (valid_size > 0 && !output_stream_->Write(buffer_, valid_size))
+    return false;
+
+  zlib_stream_.next_out = buffer_;
+  zlib_stream_.avail_out = base::saturated_cast<uInt>(base::size(buffer_));
+
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/util/stream/zlib_output_stream.h b/util/stream/zlib_output_stream.h
new file mode 100644
index 0000000..d8e62a6
--- /dev/null
+++ b/util/stream/zlib_output_stream.h
@@ -0,0 +1,82 @@
+// Copyright 2019 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_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_
+#define CRASHPAD_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "third_party/zlib/zlib_crashpad.h"
+#include "util/misc/initialization_state.h"
+#include "util/stream/output_stream_interface.h"
+
+namespace crashpad {
+
+//! \brief The class wraps zlib into \a OutputStreamInterface.
+class ZlibOutputStream : public OutputStreamInterface {
+ public:
+  //! \brief Whether this object is configured to compress or decompress data.
+  enum class Mode : bool {
+    //! \brief Data passed through this object is compressed.
+    kCompress = false,
+    //! \brief Data passed through this object is decompressed.
+    kDecompress = true
+  };
+
+  //! \param[in] mode The work mode of this object.
+  //! \param[in] output_stream The output_stream that this object writes to.
+  //!
+  //! To construct an output pipeline, the output stream needs an output stream
+  //! to write the result to. For example, the code below constructs a
+  //! compress->base94-encoding->log output stream pipline.
+  //!
+  //! <code>
+  //!   ZlibOutputStream zlib_output_stream(
+  //!        ZlibOutputStream::Mode::kDeflate,
+  //!        std::make_unique<Base94OutputStream>(
+  //!            Base94OutputStream::Mode::kEncode,
+  //!            std::make_unique<LogOutputStream>()));
+  //! </code>
+  //!
+  //!
+  ZlibOutputStream(Mode mode,
+                   std::unique_ptr<OutputStreamInterface> output_stream);
+  ~ZlibOutputStream() override;
+
+  // OutputStreamInterface:
+  bool Write(const uint8_t* data, size_t size) override;
+  bool Flush() override;
+
+ private:
+  // Write compressed/decompressed data to |output_stream_| and empty the output
+  // buffer in |zlib_stream_|.
+  bool WriteOutputStream();
+
+  uint8_t buffer_[4096];
+  z_stream zlib_stream_;
+  std::unique_ptr<OutputStreamInterface> output_stream_;
+  Mode mode_;
+  InitializationState initialized_;  // protects zlib_stream_
+  bool flush_needed_;
+
+  DISALLOW_COPY_AND_ASSIGN(ZlibOutputStream);
+};
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_STREAM_ZLIB_OUTPUT_STREAM_H_
diff --git a/util/stream/zlib_output_stream_test.cc b/util/stream/zlib_output_stream_test.cc
new file mode 100644
index 0000000..dfa935b
--- /dev/null
+++ b/util/stream/zlib_output_stream_test.cc
@@ -0,0 +1,186 @@
+// Copyright 2019 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.
+
+#include "util/stream/zlib_output_stream.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "util/stream/test_output_stream.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr size_t kShortDataLength = 10;
+constexpr size_t kLongDataLength = 4096 * 10;
+
+class ZlibOutputStreamTest : public testing::Test {
+ public:
+  ZlibOutputStreamTest() : input_(), deterministic_input_() {
+    auto test_output_stream = std::make_unique<TestOutputStream>();
+    test_output_stream_ = test_output_stream.get();
+    zlib_output_stream_ = std::make_unique<ZlibOutputStream>(
+        ZlibOutputStream::Mode::kCompress,
+        std::make_unique<ZlibOutputStream>(ZlibOutputStream::Mode::kDecompress,
+                                           std::move(test_output_stream)));
+  }
+
+  const uint8_t* BuildDeterministicInput(size_t size) {
+    deterministic_input_ = std::make_unique<uint8_t[]>(size);
+    uint8_t* deterministic_input_base = deterministic_input_.get();
+    while (size-- > 0)
+      deterministic_input_base[size] = static_cast<uint8_t>(size);
+    return deterministic_input_base;
+  }
+
+  const uint8_t* BuildRandomInput(size_t size) {
+    input_ = std::make_unique<uint8_t[]>(size);
+    base::RandBytes(&input_[0], size);
+    return input_.get();
+  }
+
+  const TestOutputStream& test_output_stream() const {
+    return *test_output_stream_;
+  }
+
+  ZlibOutputStream* zlib_output_stream() const {
+    return zlib_output_stream_.get();
+  }
+
+ private:
+  std::unique_ptr<ZlibOutputStream> zlib_output_stream_;
+  std::unique_ptr<uint8_t[]> input_;
+  std::unique_ptr<uint8_t[]> deterministic_input_;
+  TestOutputStream* test_output_stream_;  // weak, owned by zlib_output_stream_
+
+  DISALLOW_COPY_AND_ASSIGN(ZlibOutputStreamTest);
+};
+
+TEST_F(ZlibOutputStreamTest, WriteDeterministicShortData) {
+  const uint8_t* input = BuildDeterministicInput(kShortDataLength);
+  EXPECT_TRUE(zlib_output_stream()->Write(input, kShortDataLength));
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().last_written_data().size(), kShortDataLength);
+  EXPECT_EQ(memcmp(test_output_stream().last_written_data().data(),
+                   input,
+                   kShortDataLength),
+            0);
+}
+
+TEST_F(ZlibOutputStreamTest, WriteDeterministicLongDataOneTime) {
+  const uint8_t* input = BuildDeterministicInput(kLongDataLength);
+  EXPECT_TRUE(zlib_output_stream()->Write(input, kLongDataLength));
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(
+      memcmp(test_output_stream().all_data().data(), input, kLongDataLength),
+      0);
+}
+
+TEST_F(ZlibOutputStreamTest, WriteDeterministicLongDataMultipleTimes) {
+  const uint8_t* input = BuildDeterministicInput(kLongDataLength);
+
+  static constexpr size_t kWriteLengths[] = {
+      4, 96, 40, kLongDataLength - 4 - 96 - 40};
+
+  size_t offset = 0;
+  for (size_t index = 0; index < base::size(kWriteLengths); ++index) {
+    const size_t write_length = kWriteLengths[index];
+    SCOPED_TRACE(base::StringPrintf(
+        "offset %zu, write_length %zu", offset, write_length));
+    EXPECT_TRUE(zlib_output_stream()->Write(input + offset, write_length));
+    offset += write_length;
+  }
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(
+      memcmp(test_output_stream().all_data().data(), input, kLongDataLength),
+      0);
+}
+
+TEST_F(ZlibOutputStreamTest, WriteShortData) {
+  const uint8_t* input = BuildRandomInput(kShortDataLength);
+  EXPECT_TRUE(zlib_output_stream()->Write(input, kShortDataLength));
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(memcmp(test_output_stream().last_written_data().data(),
+                   input,
+                   kShortDataLength),
+            0);
+  EXPECT_EQ(test_output_stream().last_written_data().size(), kShortDataLength);
+}
+
+TEST_F(ZlibOutputStreamTest, WriteLongDataOneTime) {
+  const uint8_t* input = BuildRandomInput(kLongDataLength);
+  EXPECT_TRUE(zlib_output_stream()->Write(input, kLongDataLength));
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(
+      memcmp(test_output_stream().all_data().data(), input, kLongDataLength),
+      0);
+}
+
+TEST_F(ZlibOutputStreamTest, WriteLongDataMultipleTimes) {
+  const uint8_t* input = BuildRandomInput(kLongDataLength);
+
+  // Call Write() a random number of times.
+  size_t index = 0;
+  while (index < kLongDataLength) {
+    size_t write_length =
+        std::min(static_cast<size_t>(base::RandInt(0, 4096 * 2)),
+                 kLongDataLength - index);
+    SCOPED_TRACE(
+        base::StringPrintf("index %zu, write_length %zu", index, write_length));
+    EXPECT_TRUE(zlib_output_stream()->Write(input + index, write_length));
+    index += write_length;
+  }
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().all_data().size(), kLongDataLength);
+  EXPECT_EQ(
+      memcmp(test_output_stream().all_data().data(), input, kLongDataLength),
+      0);
+}
+
+TEST_F(ZlibOutputStreamTest, NoWriteOrFlush) {
+  EXPECT_EQ(test_output_stream().write_count(), 0u);
+  EXPECT_EQ(test_output_stream().flush_count(), 0u);
+  EXPECT_TRUE(test_output_stream().all_data().empty());
+}
+
+TEST_F(ZlibOutputStreamTest, FlushWithoutWrite) {
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().write_count(), 0u);
+  EXPECT_EQ(test_output_stream().flush_count(), 1u);
+  EXPECT_TRUE(test_output_stream().all_data().empty());
+}
+
+TEST_F(ZlibOutputStreamTest, WriteEmptyData) {
+  std::vector<uint8_t> empty_data;
+  EXPECT_TRUE(zlib_output_stream()->Write(
+      static_cast<const uint8_t*>(empty_data.data()), empty_data.size()));
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_TRUE(zlib_output_stream()->Flush());
+  EXPECT_EQ(test_output_stream().write_count(), 0u);
+  EXPECT_EQ(test_output_stream().flush_count(), 2u);
+  EXPECT_TRUE(test_output_stream().all_data().empty());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/synchronization/semaphore_test.cc b/util/synchronization/semaphore_test.cc
index 10f7546..4f1c1cd 100644
--- a/util/synchronization/semaphore_test.cc
+++ b/util/synchronization/semaphore_test.cc
@@ -16,7 +16,7 @@
 
 #include <sys/types.h>
 
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 
 #if defined(OS_POSIX)
@@ -126,7 +126,7 @@
   Semaphore semaphore(5);
   ThreadMainInfo info[10];
   size_t iterations = 0;
-  for (size_t index = 0; index < arraysize(info); ++index) {
+  for (size_t index = 0; index < base::size(info); ++index) {
     info[index].semaphore = &semaphore;
     info[index].iterations = index;
     iterations += info[index].iterations;
@@ -138,7 +138,7 @@
     semaphore.Signal();
   }
 
-  for (size_t index = 0; index < arraysize(info); ++index) {
+  for (size_t index = 0; index < base::size(info); ++index) {
     JoinThread(&info[index]);
   }
 }
diff --git a/util/thread/thread_log_messages.cc b/util/thread/thread_log_messages.cc
index 60be08b..60c8248 100644
--- a/util/thread/thread_log_messages.cc
+++ b/util/thread/thread_log_messages.cc
@@ -46,10 +46,6 @@
 
  private:
   ThreadLogMessagesMaster() {
-    DCHECK(!tls_.initialized());
-    tls_.Initialize(nullptr);
-    DCHECK(tls_.initialized());
-
     DCHECK(!logging::GetLogMessageHandler());
     logging::SetLogMessageHandler(LogMessageHandler);
   }
@@ -62,7 +58,7 @@
                                 size_t message_start,
                                 const std::string& string) {
     std::vector<std::string>* log_messages =
-        reinterpret_cast<std::vector<std::string>*>(tls_.Get());
+        reinterpret_cast<std::vector<std::string>*>(GetInstance()->tls_.Get());
     if (log_messages) {
       log_messages->push_back(string);
     }
@@ -72,15 +68,11 @@
     return false;
   }
 
-  static base::ThreadLocalStorage::StaticSlot tls_;
+  base::ThreadLocalStorage::Slot tls_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadLogMessagesMaster);
 };
 
-// static
-base::ThreadLocalStorage::StaticSlot ThreadLogMessagesMaster::tls_
-    = TLS_INITIALIZER;
-
 }  // namespace
 
 ThreadLogMessages::ThreadLogMessages() : log_messages_() {
diff --git a/util/thread/thread_log_messages_test.cc b/util/thread/thread_log_messages_test.cc
index 00b612c..94920e7 100644
--- a/util/thread/thread_log_messages_test.cc
+++ b/util/thread/thread_log_messages_test.cc
@@ -18,6 +18,7 @@
 #include <sys/types.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "util/thread/thread.h"
@@ -93,8 +94,8 @@
     const std::vector<std::string>& log_messages =
         thread_log_messages.log_messages();
 
-    EXPECT_EQ(log_messages.size(), arraysize(kMessages));
-    for (size_t index = 0; index < arraysize(kMessages); ++index) {
+    EXPECT_EQ(log_messages.size(), base::size(kMessages));
+    for (size_t index = 0; index < base::size(kMessages); ++index) {
       EXPECT_EQ(MessageString(log_messages[index]), kMessages[index])
           << "index " << index;
     }
@@ -173,7 +174,7 @@
 
   LoggingTestThread threads[20];
   int start = 0;
-  for (size_t index = 0; index < arraysize(threads); ++index) {
+  for (size_t index = 0; index < base::size(threads); ++index) {
     threads[index].Initialize(
         index, static_cast<int>(start), static_cast<int>(index));
     start += static_cast<int>(index);
diff --git a/util/thread/thread_test.cc b/util/thread/thread_test.cc
index d92544b..47a711c 100644
--- a/util/thread/thread_test.cc
+++ b/util/thread/thread_test.cc
@@ -14,7 +14,6 @@
 
 #include "util/thread/thread.h"
 
-#include "base/macros.h"
 #include "gtest/gtest.h"
 #include "util/synchronization/semaphore.h"
 
diff --git a/util/thread/worker_thread.cc b/util/thread/worker_thread.cc
index 3cf4897..b5dcdb2 100644
--- a/util/thread/worker_thread.cc
+++ b/util/thread/worker_thread.cc
@@ -33,8 +33,9 @@
     if (initial_work_delay_ > 0)
       semaphore_.TimedWait(initial_work_delay_);
 
-    while (self_->running_) {
+    while (self_->running_ || self_->do_work_now_) {
       self_->delegate_->DoWork(self_);
+      self_->do_work_now_ = false;
       semaphore_.TimedWait(self_->work_interval_);
     }
   }
@@ -57,7 +58,8 @@
     : work_interval_(work_interval),
       delegate_(delegate),
       impl_(),
-      running_(false) {}
+      running_(false),
+      do_work_now_(false) {}
 
 WorkerThread::~WorkerThread() {
   DCHECK(!running_);
@@ -88,6 +90,7 @@
 
 void WorkerThread::DoWorkNow() {
   DCHECK(running_);
+  do_work_now_ = true;
   impl_->SignalSemaphore();
 }
 
diff --git a/util/thread/worker_thread.h b/util/thread/worker_thread.h
index 97fb6ec..0fae009 100644
--- a/util/thread/worker_thread.h
+++ b/util/thread/worker_thread.h
@@ -92,6 +92,7 @@
   Delegate* delegate_;  // weak
   std::unique_ptr<internal::WorkerThreadImpl> impl_;
   bool running_;
+  bool do_work_now_;
 
   DISALLOW_COPY_AND_ASSIGN(WorkerThread);
 };
diff --git a/util/util.gyp b/util/util.gyp
index 1ffb752..78a432a 100644
--- a/util/util.gyp
+++ b/util/util.gyp
@@ -24,7 +24,9 @@
         '../compat/compat.gyp:crashpad_compat',
         '../third_party/mini_chromium/mini_chromium.gyp:base',
         '../third_party/zlib/zlib.gyp:zlib',
+        '../third_party/lss/lss.gyp:lss',
       ],
+      'defines': [ 'ZLIB_CONST' ],
       'include_dirs': [
         '..',
         '<(INTERMEDIATE_DIR)',
@@ -48,6 +50,8 @@
         'file/filesystem_win.cc',
         'file/file_writer.cc',
         'file/file_writer.h',
+        'file/output_stream_file_writer.cc',
+        'file/output_stream_file_writer.h',
         'file/scoped_remove_file.cc',
         'file/scoped_remove_file.h',
         'file/string_file.cc',
@@ -63,10 +67,14 @@
         'linux/exception_handler_protocol.cc',
         'linux/exception_handler_protocol.h',
         'linux/exception_information.h',
+        'linux/initial_signal_dispositions.cc',
+        'linux/initial_signal_dispositions.h',
         'linux/memory_map.cc',
         'linux/memory_map.h',
         'linux/proc_stat_reader.cc',
         'linux/proc_stat_reader.h',
+        'linux/proc_task_reader.cc',
+        'linux/proc_task_reader.h',
         'linux/ptrace_broker.cc',
         'linux/ptrace_broker.h',
         'linux/ptrace_client.cc',
@@ -74,10 +82,14 @@
         'linux/ptrace_connection.h',
         'linux/ptracer.cc',
         'linux/ptracer.h',
+        'linux/scoped_pr_set_dumpable.cc',
+        'linux/scoped_pr_set_dumpable.h',
         'linux/scoped_pr_set_ptracer.cc',
         'linux/scoped_pr_set_ptracer.h',
         'linux/scoped_ptrace_attach.cc',
         'linux/scoped_ptrace_attach.h',
+        'linux/socket.cc',
+        'linux/socket.h',
         'linux/thread_info.cc',
         'linux/thread_info.h',
         'linux/traits.h',
@@ -122,11 +134,9 @@
         'mach/symbolic_constants_mach.h',
         'mach/task_for_pid.cc',
         'mach/task_for_pid.h',
-        'mach/task_memory.cc',
-        'mach/task_memory.h',
         'misc/address_sanitizer.h',
         'misc/address_types.h',
-        'misc/arraysize_unsafe.h',
+        'misc/arraysize.h',
         'misc/as_underlying_type.h',
         'misc/capture_context.h',
         'misc/capture_context_linux.S',
@@ -163,6 +173,7 @@
         'misc/symbolic_constants_common.h',
         'misc/time.cc',
         'misc/time.h',
+        'misc/time_linux.cc',
         'misc/time_win.cc',
         'misc/tri_state.h',
         'misc/uuid.cc',
@@ -179,7 +190,6 @@
         'net/http_transport.cc',
         'net/http_transport.h',
         'net/http_transport_mac.mm',
-        'net/http_transport_none.cc',
         'net/http_transport_win.cc',
         'net/url.cc',
         'net/url.h',
@@ -209,10 +219,13 @@
         'posix/signals.h',
         'posix/symbolic_constants_posix.cc',
         'posix/symbolic_constants_posix.h',
+        'process/process_id.h',
         'process/process_memory.cc',
         'process/process_memory.h',
         'process/process_memory_linux.cc',
         'process/process_memory_linux.h',
+        'process/process_memory_mac.cc',
+        'process/process_memory_mac.h',
         'process/process_memory_native.h',
         'process/process_memory_range.cc',
         'process/process_memory_range.h',
@@ -227,6 +240,17 @@
         'stdlib/strnlen.cc',
         'stdlib/strnlen.h',
         'stdlib/thread_safe_vector.h',
+        'stream/base94_output_stream.cc',
+        'stream/base94_output_stream.h',
+        'stream/file_encoder.cc',
+        'stream/file_encoder.h',
+        'stream/file_output_stream.cc',
+        'stream/file_output_stream.h',
+        'stream/log_output_stream.cc',
+        'stream/log_output_stream.h',
+        'stream/output_stream_interface.h',
+        'stream/zlib_output_stream.cc',
+        'stream/zlib_output_stream.h',
         'string/split_string.cc',
         'string/split_string.h',
         'synchronization/semaphore_mac.cc',
@@ -246,6 +270,7 @@
         'win/checked_win_address_range.h',
         'win/command_line.cc',
         'win/command_line.h',
+        'win/context_wrappers.h',
         'win/critical_section_with_debug_info.cc',
         'win/critical_section_with_debug_info.h',
         'win/exception_handler_server.cc',
@@ -384,20 +409,24 @@
             'win/safe_terminate_process.asm',
           ],
         }],
-        ['OS=="linux"', {
+        ['OS=="android"', {
+          'link_settings': {
+            'libraries': [
+              '-llog',
+            ],
+          },
+        }],
+        ['OS=="linux" or OS=="android"', {
           'sources': [
             'net/http_transport_socket.cc',
+            'process/process_memory_sanitized.cc',
+            'process/process_memory_sanitized.h',
           ],
         }, {  # else: OS!="linux"
           'sources!': [
             'misc/capture_context_linux.S',
           ],
         }],
-        ['OS!="android"', {
-          'sources!': [
-            'net/http_transport_none.cc',
-          ],
-        }],
         ['OS!="linux" and OS!="android"', {
           'sources/': [
             ['exclude', '^process/'],
@@ -410,10 +439,16 @@
             ['include', '^linux/'],
             ['include', '^misc/capture_context_linux\\.S$'],
             ['include', '^misc/paths_linux\\.cc$'],
+            ['include', '^misc/time_linux\\.cc$'],
             ['include', '^posix/process_info_linux\\.cc$'],
             ['include', '^process/process_memory_linux\\.cc$'],
             ['include', '^process/process_memory_linux\\.h$'],
           ],
+        }, { # else: OS!="android"
+          'sources!': [
+            'stream/log_output_stream.cc',
+            'stream/log_output_stream.h',
+          ]
         }],
       ],
     },
diff --git a/util/util_test.gyp b/util/util_test.gyp
index ff3559e..f65a918 100644
--- a/util/util_test.gyp
+++ b/util/util_test.gyp
@@ -28,6 +28,7 @@
         '../test/test.gyp:crashpad_test',
         '../third_party/gtest/gmock.gyp:gmock',
         '../third_party/gtest/gtest.gyp:gtest',
+        '../third_party/lss/lss.gyp:lss',
         '../third_party/mini_chromium/mini_chromium.gyp:base',
         '../third_party/zlib/zlib.gyp:zlib',
       ],
@@ -44,9 +45,11 @@
         'linux/auxiliary_vector_test.cc',
         'linux/memory_map_test.cc',
         'linux/proc_stat_reader_test.cc',
+        'linux/proc_task_reader_test.cc',
         'linux/ptrace_broker_test.cc',
         'linux/ptracer_test.cc',
         'linux/scoped_ptrace_attach_test.cc',
+        'linux/socket_test.cc',
         'mac/launchd_test.mm',
         'mac/mac_util_test.mm',
         'mac/service_management_test.mm',
@@ -65,8 +68,7 @@
         'mach/notify_server_test.cc',
         'mach/scoped_task_suspend_test.cc',
         'mach/symbolic_constants_mach_test.cc',
-        'mach/task_memory_test.cc',
-        'misc/arraysize_unsafe_test.cc',
+        'misc/arraysize_test.cc',
         'misc/capture_context_test.cc',
         'misc/capture_context_test_util.h',
         'misc/capture_context_test_util_linux.cc',
@@ -98,6 +100,7 @@
         'posix/scoped_mmap_test.cc',
         'posix/signals_test.cc',
         'posix/symbolic_constants_posix_test.cc',
+        'process/process_memory_mac_test.cc',
         'process/process_memory_range_test.cc',
         'process/process_memory_test.cc',
         'stdlib/aligned_allocator_test.cc',
@@ -151,6 +154,11 @@
             ['exclude', '^net/http_transport_test\\.cc$'],
           ]
         }],
+        ['OS=="linux" or OS=="android"', {
+          'sources': [
+            'process/process_memory_sanitized_test.cc',
+          ],
+        }],
         ['OS!="linux" and OS!="android"', {
           'sources/': [
             ['exclude', '^process/'],
@@ -193,6 +201,15 @@
           'cflags!': [
             '-Wexit-time-destructors',
           ],
+          'conditions': [
+            ['OS=="win"', {
+              'link_settings': {
+                'libraries': [
+                  '-lws2_32.lib',
+                ],
+              },
+            }],
+          ],
         },
       ],
     }],
diff --git a/util/win/command_line_test.cc b/util/win/command_line_test.cc
index 025ef8a..032d555 100644
--- a/util/win/command_line_test.cc
+++ b/util/win/command_line_test.cc
@@ -19,8 +19,8 @@
 #include <sys/types.h>
 
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/scoped_generic.h"
+#include "base/stl_util.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
 #include "util/win/scoped_local_alloc.h"
@@ -65,7 +65,7 @@
         L"argument 1",
         L"argument 2",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -77,7 +77,7 @@
         L"argument 2",
         L"\\some\\path with\\spaces",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -89,7 +89,7 @@
         L"she said, \"you had me at hello\"",
         L"\\some\\path with\\spaces",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -102,7 +102,7 @@
         L"argument3",
         L"argument4",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -113,7 +113,7 @@
         L"\\some\\directory with\\spaces\\",
         L"argument2",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -124,7 +124,7 @@
         L"",
         L"argument2",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 
   {
@@ -159,7 +159,7 @@
         L"\"\"",
         L" \t\n\v\"",
     };
-    AppendCommandLineArgumentTest(arraysize(kArguments), kArguments);
+    AppendCommandLineArgumentTest(base::size(kArguments), kArguments);
   }
 }
 
diff --git a/util/win/context_wrappers.h b/util/win/context_wrappers.h
new file mode 100644
index 0000000..5d9b183
--- /dev/null
+++ b/util/win/context_wrappers.h
@@ -0,0 +1,40 @@
+// 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_UTIL_WIN_CONTEXT_WRAPPERS_H_
+#define CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_
+
+#include <windows.h>
+
+#include "build/build_config.h"
+
+namespace crashpad {
+
+//! \brief Retrieve program counter from `CONTEXT` structure for different
+//!     architectures supported by Windows.
+inline void* ProgramCounterFromCONTEXT(const CONTEXT* context) {
+#if defined(ARCH_CPU_X86)
+  return reinterpret_cast<void*>(context->Eip);
+#elif defined(ARCH_CPU_X86_64)
+  return reinterpret_cast<void*>(context->Rip);
+#elif defined(ARCH_CPU_ARM64)
+  return reinterpret_cast<void*>(context->Pc);
+#else
+#error Unsupported Windows Arch
+#endif  // ARCH_CPU_X86
+}
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_
diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc
index 6642665..c841f7b 100644
--- a/util/win/exception_handler_server.cc
+++ b/util/win/exception_handler_server.cc
@@ -23,11 +23,9 @@
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/rand_util.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "minidump/minidump_file_writer.h"
-#include "snapshot/crashpad_info_client_options.h"
-#include "snapshot/win/process_snapshot_win.h"
 #include "util/file/file_writer.h"
 #include "util/misc/tri_state.h"
 #include "util/misc/uuid.h"
@@ -307,7 +305,7 @@
 void ExceptionHandlerServer::Run(Delegate* delegate) {
   uint64_t shutdown_token = base::RandUint64();
   ScopedKernelHANDLE thread_handles[kPipeInstances];
-  for (size_t i = 0; i < arraysize(thread_handles); ++i) {
+  for (size_t i = 0; i < base::size(thread_handles); ++i) {
     HANDLE pipe;
     if (first_pipe_instance_.is_valid()) {
       pipe = first_pipe_instance_.release();
@@ -359,7 +357,7 @@
   }
 
   // Signal to the named pipe instances that they should terminate.
-  for (size_t i = 0; i < arraysize(thread_handles); ++i) {
+  for (size_t i = 0; i < base::size(thread_handles); ++i) {
     ClientToServerMessage message;
     memset(&message, 0, sizeof(message));
     message.type = ClientToServerMessage::kShutdown;
diff --git a/util/win/get_module_information.cc b/util/win/get_module_information.cc
index 1a9fd0c..850bfe1 100644
--- a/util/win/get_module_information.cc
+++ b/util/win/get_module_information.cc
@@ -22,9 +22,13 @@
                                   HMODULE module,
                                   MODULEINFO* module_info,
                                   DWORD cb) {
+#if PSAPI_VERSION == 1
   static const auto get_module_information =
-      GET_FUNCTION_REQUIRED(L"psapi.dll", GetModuleInformation);
+    GET_FUNCTION_REQUIRED(L"psapi.dll", GetModuleInformation);
   return get_module_information(process, module, module_info, cb);
+#elif PSAPI_VERSION == 2
+  return GetModuleInformation(process, module, module_info, cb);
+#endif
 }
 
 }  // namespace crashpad
diff --git a/util/win/get_module_information.h b/util/win/get_module_information.h
index c7d1cf1..8fce035 100644
--- a/util/win/get_module_information.h
+++ b/util/win/get_module_information.h
@@ -17,7 +17,9 @@
 
 #include <windows.h>
 
-#define PSAPI_VERSION 1
+#ifndef PSAPI_VERSION
+#define PSAPI_VERSION 2
+#endif
 #include <psapi.h>
 
 namespace crashpad {
diff --git a/util/win/loader_lock.cc b/util/win/loader_lock.cc
new file mode 100644
index 0000000..187ef84
--- /dev/null
+++ b/util/win/loader_lock.cc
@@ -0,0 +1,52 @@
+// Copyright 2019 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.
+
+#include "util/win/loader_lock.h"
+
+#include <windows.h>
+
+#include "build/build_config.h"
+#include "util/win/process_structs.h"
+
+namespace crashpad {
+
+namespace {
+
+#ifdef ARCH_CPU_64_BITS
+using NativeTraits = process_types::internal::Traits64;
+#else
+using NativeTraits = process_types::internal::Traits32;
+#endif  // ARCH_CPU_64_BITS
+
+using PEB = process_types::PEB<NativeTraits>;
+using TEB = process_types::TEB<NativeTraits>;
+using RTL_CRITICAL_SECTION = process_types::RTL_CRITICAL_SECTION<NativeTraits>;
+
+TEB* GetTeb() {
+  return reinterpret_cast<TEB*>(NtCurrentTeb());
+}
+
+PEB* GetPeb() {
+  return reinterpret_cast<PEB*>(GetTeb()->ProcessEnvironmentBlock);
+}
+
+}  // namespace
+
+bool IsThreadInLoaderLock() {
+  RTL_CRITICAL_SECTION* loader_lock =
+      reinterpret_cast<RTL_CRITICAL_SECTION*>(GetPeb()->LoaderLock);
+  return loader_lock->OwningThread == GetTeb()->ClientId.UniqueThread;
+}
+
+}  // namespace crashpad
diff --git a/util/net/http_transport_none.cc b/util/win/loader_lock.h
similarity index 66%
rename from util/net/http_transport_none.cc
rename to util/win/loader_lock.h
index 1c08c1f..6c6b311 100644
--- a/util/net/http_transport_none.cc
+++ b/util/win/loader_lock.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
+// Copyright 2019 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.
@@ -12,15 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "util/net/http_transport.h"
-
-#include "base/logging.h"
+#ifndef CRASHPAD_UTIL_WIN_LOADER_LOCK_H_
+#define CRASHPAD_UTIL_WIN_LOADER_LOCK_H_
 
 namespace crashpad {
 
-std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
-  NOTREACHED();  // TODO(scottmg): https://crashpad.chromium.org/bug/196
-  return std::unique_ptr<HTTPTransport>();
-}
+//! \return `true` if the current thread holds the loader lock.
+bool IsThreadInLoaderLock();
 
 }  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_WIN_LOADER_LOCK_H_
diff --git a/util/win/loader_lock_test.cc b/util/win/loader_lock_test.cc
new file mode 100644
index 0000000..d33ea5a
--- /dev/null
+++ b/util/win/loader_lock_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 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.
+
+#include "util/win/loader_lock.h"
+
+#include "gtest/gtest.h"
+#include "util/win/get_function.h"
+
+extern "C" bool LoaderLockDetected();
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(LoaderLock, Detected) {
+  EXPECT_FALSE(IsThreadInLoaderLock());
+  auto* loader_lock_detected = GET_FUNCTION_REQUIRED(
+      L"crashpad_util_test_loader_lock_test.dll", LoaderLockDetected);
+  EXPECT_TRUE(loader_lock_detected());
+  EXPECT_FALSE(IsThreadInLoaderLock());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace crashpad
diff --git a/util/win/loader_lock_test_dll.cc b/util/win/loader_lock_test_dll.cc
new file mode 100644
index 0000000..b673ab3
--- /dev/null
+++ b/util/win/loader_lock_test_dll.cc
@@ -0,0 +1,41 @@
+// Copyright 2019 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.
+
+#include <windows.h>
+
+#include "util/win/loader_lock.h"
+
+namespace {
+
+bool g_loader_lock_detected = false;
+
+}  // namespace
+
+extern "C" {
+
+__declspec(dllexport) bool LoaderLockDetected() {
+  return g_loader_lock_detected;
+}
+
+}  // extern "C"
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) {
+  switch (reason) {
+    case DLL_PROCESS_ATTACH:
+      g_loader_lock_detected = crashpad::IsThreadInLoaderLock();
+      break;
+  }
+
+  return TRUE;
+}
diff --git a/util/win/ntstatus_logging.cc b/util/win/ntstatus_logging.cc
index 442e49f..e9a9b61 100644
--- a/util/win/ntstatus_logging.cc
+++ b/util/win/ntstatus_logging.cc
@@ -16,6 +16,7 @@
 
 #include <string>
 
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 
 namespace {
@@ -29,7 +30,7 @@
       ntstatus,
       0,
       msgbuf,
-      arraysize(msgbuf),
+      static_cast<DWORD>(base::size(msgbuf)),
       nullptr);
   if (len) {
     // Most system messages end in a period and a space. Remove the space if
diff --git a/util/win/process_info.cc b/util/win/process_info.cc
index cd6bcd3..49a6f27 100644
--- a/util/win/process_info.cc
+++ b/util/win/process_info.cc
@@ -224,7 +224,7 @@
                                                  sizeof(wow64_peb_address),
                                                  &bytes_returned);
     if (!NT_SUCCESS(status)) {
-      NTSTATUS_LOG(ERROR, status), "NtQueryInformationProcess";
+      NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess";
       return false;
     }
     if (bytes_returned != sizeof(wow64_peb_address)) {
@@ -513,8 +513,14 @@
     // distinguish between these two cases.
     SYSTEM_INFO system_info;
     GetSystemInfo(&system_info);
-    is_64_bit_ =
-        system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
+
+#if defined(ARCH_CPU_X86_FAMILY)
+    constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
+#elif defined(ARCH_CPU_ARM_FAMILY)
+    constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_ARM64;
+#endif
+
+    is_64_bit_ = system_info.wProcessorArchitecture == kNative64BitArchitecture;
   }
 
 #if defined(ARCH_CPU_32_BITS)
@@ -565,12 +571,12 @@
   return is_wow64_;
 }
 
-pid_t ProcessInfo::ProcessID() const {
+crashpad::ProcessID ProcessInfo::ProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return process_id_;
 }
 
-pid_t ProcessInfo::ParentProcessID() const {
+crashpad::ProcessID ProcessInfo::ParentProcessID() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return inherited_from_process_id_;
 }
diff --git a/util/win/process_info.h b/util/win/process_info.h
index 58921c5..afbe146 100644
--- a/util/win/process_info.h
+++ b/util/win/process_info.h
@@ -24,6 +24,7 @@
 #include "base/macros.h"
 #include "util/misc/initialization_state_dcheck.h"
 #include "util/numeric/checked_range.h"
+#include "util/process/process_id.h"
 #include "util/stdlib/aligned_allocator.h"
 #include "util/win/address_types.h"
 
@@ -105,10 +106,10 @@
   bool IsWow64() const;
 
   //! \return The target process's process ID.
-  pid_t ProcessID() const;
+  crashpad::ProcessID ProcessID() const;
 
   //! \return The target process's parent process ID.
-  pid_t ParentProcessID() const;
+  crashpad::ProcessID ParentProcessID() const;
 
   //! \return The command line from the target process's Process Environment
   //!     Block.
@@ -173,8 +174,8 @@
   // This function is best-effort under low memory conditions.
   std::vector<Handle> BuildHandleVector(HANDLE process) const;
 
-  pid_t process_id_;
-  pid_t inherited_from_process_id_;
+  crashpad::ProcessID process_id_;
+  crashpad::ProcessID inherited_from_process_id_;
   HANDLE process_;
   std::wstring command_line_;
   WinVMAddress peb_address_;
diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc
index c7abdb6..a43358d 100644
--- a/util/win/process_info_test.cc
+++ b/util/win/process_info_test.cc
@@ -26,7 +26,6 @@
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
-#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
@@ -38,6 +37,7 @@
 #include "util/win/get_function.h"
 #include "util/win/handle.h"
 #include "util/win/scoped_handle.h"
+#include "util/win/scoped_registry_key.h"
 
 namespace crashpad {
 namespace test {
@@ -202,7 +202,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ProcessInfo, OtherProcessWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    DISABLED_TEST();
+    GTEST_SKIP();
   }
 
   TestOtherProcess(TestPaths::Architecture::k32Bit);
@@ -528,18 +528,6 @@
                                  &bytes_read));
 }
 
-struct ScopedRegistryKeyCloseTraits {
-  static HKEY InvalidValue() {
-    return nullptr;
-  }
-  static void Free(HKEY key) {
-    RegCloseKey(key);
-  }
-};
-
-using ScopedRegistryKey =
-    base::ScopedGeneric<HKEY, ScopedRegistryKeyCloseTraits>;
-
 TEST(ProcessInfo, Handles) {
   ScopedTempDir temp_dir;
 
diff --git a/util/win/registration_protocol_win.cc b/util/win/registration_protocol_win.cc
index 4fb536d..64ed518 100644
--- a/util/win/registration_protocol_win.cc
+++ b/util/win/registration_protocol_win.cc
@@ -14,16 +14,63 @@
 
 #include "util/win/registration_protocol_win.h"
 
-#include <stddef.h>
 #include <windows.h>
+#include <aclapi.h>
+#include <sddl.h>
+#include <stddef.h>
 
 #include "base/logging.h"
-#include "base/macros.h"
+#include "base/stl_util.h"
 #include "util/win/exception_handler_server.h"
+#include "util/win/loader_lock.h"
 #include "util/win/scoped_handle.h"
+#include "util/win/scoped_local_alloc.h"
 
 namespace crashpad {
 
+namespace {
+
+void* GetSecurityDescriptorWithUser(const base::char16* sddl_string,
+                                    size_t* size) {
+  if (size)
+    *size = 0;
+
+  PSECURITY_DESCRIPTOR base_sec_desc;
+  if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
+          sddl_string, SDDL_REVISION_1, &base_sec_desc, nullptr)) {
+    PLOG(ERROR) << "ConvertStringSecurityDescriptorToSecurityDescriptor";
+    return nullptr;
+  }
+
+  ScopedLocalAlloc base_sec_desc_owner(base_sec_desc);
+  EXPLICIT_ACCESS access;
+  wchar_t username[] = L"CURRENT_USER";
+  BuildExplicitAccessWithName(
+      &access, username, GENERIC_ALL, GRANT_ACCESS, NO_INHERITANCE);
+
+  PSECURITY_DESCRIPTOR user_sec_desc;
+  ULONG user_sec_desc_size;
+  DWORD error = BuildSecurityDescriptor(nullptr,
+                                        nullptr,
+                                        1,
+                                        &access,
+                                        0,
+                                        nullptr,
+                                        base_sec_desc,
+                                        &user_sec_desc_size,
+                                        &user_sec_desc);
+  if (error != ERROR_SUCCESS) {
+    SetLastError(error);
+    PLOG(ERROR) << "BuildSecurityDescriptor";
+    return nullptr;
+  }
+
+  *size = user_sec_desc_size;
+  return user_sec_desc;
+}
+
+}  // namespace
+
 bool SendToCrashHandlerServer(const base::string16& pipe_name,
                               const ClientToServerMessage& message,
                               ServerToClientMessage* response) {
@@ -124,16 +171,11 @@
       security_attributes_pointer);
 }
 
-const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) {
+const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size) {
   // Mandatory Label, no ACE flags, no ObjectType, integrity level untrusted is
-  // "S:(ML;;;;;S-1-16-0)". Typically
-  // ConvertStringSecurityDescriptorToSecurityDescriptor() would be used to
-  // convert from a string representation. However, that function cannot be used
-  // because it is in advapi32.dll and CreateNamedPipeInstance() is called from
-  // within DllMain() where the loader lock is held. advapi32.dll is delay
-  // loaded in chrome_elf.dll because it must avoid loading user32.dll. If an
-  // advapi32.dll function were used, it would cause a load of the DLL, which
-  // would in turn cause deadlock.
+  // "S:(ML;;;;;S-1-16-0)". This static security descriptor is used as a
+  // fallback if GetSecurityDescriptorWithUser fails, to avoid losing crashes
+  // from non-AppContainer sandboxed applications.
 
 #pragma pack(push, 1)
   static constexpr struct SecurityDescriptorBlob {
@@ -168,7 +210,8 @@
               ACL_REVISION,  // AclRevision.
               0,  // Sbz1.
               sizeof(kSecDescBlob.sacl),  // AclSize.
-              arraysize(kSecDescBlob.sacl.ace),  // AceCount.
+              static_cast<WORD>(
+                  base::size(kSecDescBlob.sacl.ace)),  // AceCount.
               0,  // Sbz2.
           },
 
@@ -188,8 +231,9 @@
                   // sid.
                   {
                       SID_REVISION,  // Revision.
-                      // SubAuthorityCount.
-                      arraysize(kSecDescBlob.sacl.ace[0].sid.SubAuthority),
+                                     // SubAuthorityCount.
+                      static_cast<BYTE>(base::size(
+                          kSecDescBlob.sacl.ace[0].sid.SubAuthority)),
                       // IdentifierAuthority.
                       {SECURITY_MANDATORY_LABEL_AUTHORITY},
                       {SECURITY_MANDATORY_UNTRUSTED_RID},  // SubAuthority.
@@ -205,4 +249,23 @@
   return reinterpret_cast<const void*>(&kSecDescBlob);
 }
 
+const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) {
+  CHECK(!IsThreadInLoaderLock());
+
+  // Get a security descriptor which grants the current user and SYSTEM full
+  // access to the named pipe. Also grant AppContainer RW access through the ALL
+  // APPLICATION PACKAGES SID (S-1-15-2-1). Finally add an Untrusted Mandatory
+  // Label for non-AppContainer sandboxed users.
+  static size_t sd_size;
+  static void* sec_desc = GetSecurityDescriptorWithUser(
+      L"D:(A;;GA;;;SY)(A;;GWGR;;;S-1-15-2-1)S:(ML;;;;;S-1-16-0)", &sd_size);
+
+  if (!sec_desc)
+    return GetFallbackSecurityDescriptorForNamedPipeInstance(size);
+
+  if (size)
+    *size = sd_size;
+  return sec_desc;
+}
+
 }  // namespace crashpad
diff --git a/util/win/registration_protocol_win.h b/util/win/registration_protocol_win.h
index 5f04a46..ef8bebc 100644
--- a/util/win/registration_protocol_win.h
+++ b/util/win/registration_protocol_win.h
@@ -145,10 +145,10 @@
 HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name,
                                bool first_instance);
 
-//! \brief Returns the SECURITY_DESCRIPTOR blob that will be used for creating
+//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating
 //!     the connection pipe in CreateNamedPipeInstance().
 //!
-//! This function is exposed for only for testing.
+//! This function is only exposed for testing.
 //!
 //! \param[out] size The size of the returned blob. May be `nullptr` if not
 //!     required.
@@ -157,6 +157,19 @@
 //!     transferred to the caller.
 const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size);
 
+//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating
+//!     the connection pipe in CreateNamedPipeInstance() if the full descriptor
+//!     can't be created.
+//!
+//! This function is only exposed for testing.
+//!
+//! \param[out] size The size of the returned blob. May be `nullptr` if not
+//!     required.
+//!
+//! \return A pointer to a self-relative `SECURITY_DESCRIPTOR`. Ownership is not
+//!     transferred to the caller.
+const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size);
+
 }  // namespace crashpad
 
 #endif  // CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_
diff --git a/util/win/registration_protocol_win_test.cc b/util/win/registration_protocol_win_test.cc
index 6601b4e..334d7d8 100644
--- a/util/win/registration_protocol_win_test.cc
+++ b/util/win/registration_protocol_win_test.cc
@@ -14,18 +14,122 @@
 
 #include "util/win/registration_protocol_win.h"
 
-#include <windows.h>
+#include <aclapi.h>
 #include <sddl.h>
 #include <string.h>
+#include <windows.h>
 
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
+#include "util/win/scoped_handle.h"
 #include "util/win/scoped_local_alloc.h"
 
 namespace crashpad {
 namespace test {
 namespace {
 
+base::string16 GetStringFromSid(PSID sid) {
+  LPWSTR sid_str;
+  if (!ConvertSidToStringSid(sid, &sid_str)) {
+    PLOG(ERROR) << "ConvertSidToStringSid";
+    return base::string16();
+  }
+  ScopedLocalAlloc sid_str_ptr(sid_str);
+  return sid_str;
+}
+
+base::string16 GetUserSidString() {
+  HANDLE token_handle;
+  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) {
+    PLOG(ERROR) << "OpenProcessToken";
+    return base::string16();
+  }
+
+  ScopedKernelHANDLE token(token_handle);
+  DWORD user_size = 0;
+  GetTokenInformation(token.get(), TokenUser, nullptr, 0, &user_size);
+  if (user_size == 0) {
+    PLOG(ERROR) << "GetTokenInformation Size";
+    return base::string16();
+  }
+
+  std::vector<char> user(user_size);
+  if (!GetTokenInformation(
+          token.get(), TokenUser, user.data(), user_size, &user_size)) {
+    PLOG(ERROR) << "GetTokenInformation";
+    return base::string16();
+  }
+
+  TOKEN_USER* user_ptr = reinterpret_cast<TOKEN_USER*>(user.data());
+  return GetStringFromSid(user_ptr->User.Sid);
+}
+
+void CheckAce(PACL acl,
+              DWORD index,
+              BYTE check_ace_type,
+              ACCESS_MASK check_mask,
+              const base::string16& check_sid) {
+  ASSERT_FALSE(check_sid.empty());
+  void* ace_ptr;
+  ASSERT_TRUE(GetAce(acl, index, &ace_ptr));
+
+  ACE_HEADER* header = static_cast<ACE_HEADER*>(ace_ptr);
+  ASSERT_EQ(check_ace_type, header->AceType);
+  ASSERT_EQ(0, header->AceFlags);
+
+  PSID sid = nullptr;
+  ACCESS_MASK mask = 0;
+  switch (header->AceType) {
+    case ACCESS_ALLOWED_ACE_TYPE: {
+      ACCESS_ALLOWED_ACE* allowed_ace =
+          static_cast<ACCESS_ALLOWED_ACE*>(ace_ptr);
+      sid = &allowed_ace->SidStart;
+      mask = allowed_ace->Mask;
+    } break;
+    case SYSTEM_MANDATORY_LABEL_ACE_TYPE: {
+      SYSTEM_MANDATORY_LABEL_ACE* label_ace =
+          static_cast<SYSTEM_MANDATORY_LABEL_ACE*>(ace_ptr);
+      sid = &label_ace->SidStart;
+      mask = label_ace->Mask;
+    } break;
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  ASSERT_EQ(check_mask, mask);
+  ASSERT_EQ(check_sid, GetStringFromSid(sid));
+}
+
+TEST(SecurityDescriptor, NamedPipeDefault) {
+  const void* sec_desc = GetSecurityDescriptorForNamedPipeInstance(nullptr);
+
+  PACL acl;
+  BOOL acl_present;
+  BOOL acl_defaulted;
+  ASSERT_TRUE(GetSecurityDescriptorDacl(
+      const_cast<void*>(sec_desc), &acl_present, &acl, &acl_defaulted));
+  ASSERT_EQ(3, acl->AceCount);
+  CheckAce(acl, 0, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, GetUserSidString());
+  // Check SYSTEM user SID.
+  CheckAce(acl, 1, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, L"S-1-5-18");
+  // Check ALL APPLICATION PACKAGES group SID.
+  CheckAce(acl,
+           2,
+           ACCESS_ALLOWED_ACE_TYPE,
+           GENERIC_READ | GENERIC_WRITE,
+           L"S-1-15-2-1");
+
+  ASSERT_TRUE(GetSecurityDescriptorSacl(
+      const_cast<void*>(sec_desc), &acl_present, &acl, &acl_defaulted));
+  ASSERT_EQ(1, acl->AceCount);
+  CheckAce(acl, 0, SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, L"S-1-16-0");
+}
+
 TEST(SecurityDescriptor, MatchesAdvapi32) {
   // This security descriptor is built manually in the connection code to avoid
   // calling the advapi32 functions. Verify that it returns the same thing as
@@ -43,7 +147,7 @@
 
   size_t created_len;
   const void* const created =
-      GetSecurityDescriptorForNamedPipeInstance(&created_len);
+      GetFallbackSecurityDescriptorForNamedPipeInstance(&created_len);
   ASSERT_EQ(created_len, sec_desc_len);
   EXPECT_EQ(memcmp(sec_desc, created, sec_desc_len), 0);
 }
diff --git a/util/win/safe_terminate_process_test.cc b/util/win/safe_terminate_process_test.cc
index 3aba159..d2e4b6d 100644
--- a/util/win/safe_terminate_process_test.cc
+++ b/util/win/safe_terminate_process_test.cc
@@ -22,6 +22,7 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
@@ -91,6 +92,8 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedExecutablePatch);
 };
 
+// SafeTerminateProcess is calling convention specific only for x86.
+#if defined(ARCH_CPU_X86_FAMILY)
 TEST(SafeTerminateProcess, PatchBadly) {
   // This is a test of SafeTerminateProcess(), but it doesn’t actually terminate
   // anything. Instead, it works with a process handle for the current process
@@ -146,7 +149,7 @@
     };
 
     void* target = reinterpret_cast<void*>(TerminateProcess);
-    ScopedExecutablePatch executable_patch(target, patch, arraysize(patch));
+    ScopedExecutablePatch executable_patch(target, patch, base::size(patch));
 
     // Make sure that SafeTerminateProcess() can be called. Since it’s been
     // patched with a no-op stub, GetLastError() shouldn’t be modified.
@@ -161,6 +164,7 @@
   EXPECT_FALSE(SafeTerminateProcess(process, 0));
   EXPECT_EQ(GetLastError(), static_cast<DWORD>(ERROR_ACCESS_DENIED));
 }
+#endif  // ARCH_CPU_X86_FAMILY
 
 TEST(SafeTerminateProcess, TerminateChild) {
   base::FilePath child_executable =
diff --git a/util/win/scoped_registry_key.h b/util/win/scoped_registry_key.h
new file mode 100644
index 0000000..4393dc2
--- /dev/null
+++ b/util/win/scoped_registry_key.h
@@ -0,0 +1,34 @@
+// Copyright 2019 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_UTIL_WIN_SCOPED_REGISTRY_KEY_H_
+#define CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_
+
+#include <windows.h>
+
+#include "base/scoped_generic.h"
+
+namespace crashpad {
+
+struct ScopedRegistryKeyCloseTraits {
+  static HKEY InvalidValue() { return nullptr; }
+  static void Free(HKEY key) { RegCloseKey(key); }
+};
+
+using ScopedRegistryKey =
+    base::ScopedGeneric<HKEY, ScopedRegistryKeyCloseTraits>;
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_
\ No newline at end of file