diff --git a/.gitignore b/.gitignore index 43ab9c0a..982a95e7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,16 @@ .gdbinit /Makefile /out +/third_party/fuchsia/.cipd +/third_party/fuchsia/clang +/third_party/fuchsia/qemu +/third_party/fuchsia/sdk /third_party/gtest/gtest +/third_party/libfuzzer +/third_party/linux/.cipd +/third_party/linux/clang +/third_party/linux/sysroot /third_party/gyp/gyp -/third_party/llvm /third_party/mini_chromium/mini_chromium /third_party/zlib/zlib /xcodebuild diff --git a/.gn b/.gn new file mode 100644 index 00000000..d447a553 --- /dev/null +++ b/.gn @@ -0,0 +1,15 @@ +# 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. + +buildconfig = "//build/BUILDCONFIG.gn" diff --git a/AUTHORS b/AUTHORS index b1e4ddf9..8dcac323 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,4 +7,8 @@ # The email address is not required for organizations. Google Inc. +Intel Corporation Opera Software ASA +Vewd Software AS +LG Electronics, Inc. +MIPS Technologies, Inc. diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 00000000..065a5e1f --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,178 @@ +# 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. + +import("build/crashpad_buildconfig.gni") +import("build/test.gni") + +config("crashpad_config") { + include_dirs = [ "." ] +} + +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_in_fuchsia) { + import("//build/package.gni") + package("crashpad_test") { + testonly = true + deprecated_system_image = true + + deps = [ + ":crashpad_tests", + "snapshot:crashpad_snapshot_test_both_dt_hash_styles", + "snapshot:crashpad_snapshot_test_module", + "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", + ] + + tests = [ + { + name = "crashpad_tests" + }, + { + name = "crashpad_test_test_multiprocess_exec_test_child" + dest = "crashpad_test_data/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" + }, + ] + + loadable_modules = [ + { + name = "crashpad_snapshot_test_both_dt_hash_styles.so" + }, + { + name = "crashpad_snapshot_test_module.so" + }, + { + name = "crashpad_snapshot_test_module_large.so" + }, + { + name = "crashpad_snapshot_test_module_small.so" + }, + ] + + resources = [ + { + path = "util/net/testdata/ascii_http_body.txt" + dest = "crashpad_test_data/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" + }, + { + path = "test/test_paths_test_data_root.txt" + dest = "crashpad_test_data/test/test_paths_test_data_root.txt" + }, + ] + } + + package("crashpad_handler") { + deprecated_system_image = true + + deps = [ + "handler:crashpad_handler", + ] + + binaries = [ + { + name = "crashpad_handler" + }, + ] + } + + package("crashpad_database_util") { + deprecated_system_image = true + + deps = [ + "tools:crashpad_database_util", + ] + + binaries = [ + { + name = "crashpad_database_util" + }, + ] + } + } +} else if (crashpad_is_standalone) { + test("crashpad_client_test") { + deps = [ + "client:client_test", + "test:gmock_main", + ] + } + + test("crashpad_handler_test") { + deps = [ + "handler:handler_test", + "test:gtest_main", + ] + } + + test("crashpad_minidump_test") { + deps = [ + "minidump:minidump_test", + "test:gtest_main", + ] + } + + test("crashpad_snapshot_test") { + deps = [ + "snapshot:snapshot_test", + "test:gtest_main", + ] + } + + test("crashpad_test_test") { + deps = [ + "test:gmock_main", + "test:test_test", + ] + } + + test("crashpad_util_test") { + deps = [ + "test:gmock_main", + "util:util_test", + ] + } +} diff --git a/DEPS b/DEPS index 94dda8ea..dbdc3a49 100644 --- a/DEPS +++ b/DEPS @@ -14,31 +14,26 @@ vars = { 'chromium_git': 'https://chromium.googlesource.com', + 'pull_linux_clang': False, + 'pull_win_toolchain': False } deps = { 'buildtools': Var('chromium_git') + '/chromium/buildtools.git@' + - 'f6d165d9d842ddd29056c127a5f3a3c5d8e0d2e3', + '6fe4a3251488f7af86d64fc25cf442e817cf6133', 'crashpad/third_party/gtest/gtest': Var('chromium_git') + '/external/github.com/google/googletest@' + - '7b6561c56e353100aca8458d7bc49c4e0119bae8', + 'c091b0469ab4c04ee9411ef770f32360945f4c53', 'crashpad/third_party/gyp/gyp': Var('chromium_git') + '/external/gyp@' + - 'f72586209ecbf70b71ce690f2182ebe51669cbb3', - - # TODO(scottmg): Consider pinning these. For now, we don't have any particular - # reason to do so. - 'crashpad/third_party/llvm': - Var('chromium_git') + '/external/llvm.org/llvm.git@HEAD', - 'crashpad/third_party/llvm/tools/clang': - Var('chromium_git') + '/external/llvm.org/clang.git@HEAD', - 'crashpad/third_party/llvm/tools/lldb': - Var('chromium_git') + '/external/llvm.org/lldb.git@HEAD', - + '5e2b3ddde7cda5eb6bc09a5546a76b00e49d888f', 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - '7d6697ceb5cb5ca02fde3813496f48b9b1d76d0c', + '793e94e2c652831af2d25bb5288b04e59048c62d', + 'crashpad/third_party/libfuzzer/src': + Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + + 'fda403cf93ecb8792cb1d061564d89a6553ca020', 'crashpad/third_party/zlib/zlib': Var('chromium_git') + '/chromium/src/third_party/zlib@' + '13dc246a58e4b72104d35f9b1809af95221ebda7', @@ -48,9 +43,9 @@ hooks = [ { 'name': 'clang_format_mac', 'pattern': '.', + 'condition': 'host_os == "mac"', 'action': [ 'download_from_google_storage', - '--platform=^darwin$', '--no_resume', '--no_auth', '--bucket=chromium-clang-format', @@ -58,25 +53,12 @@ hooks = [ 'buildtools/mac/clang-format.sha1', ], }, - { - 'name': 'clang_format_win', - 'pattern': '.', - 'action': [ - 'download_from_google_storage', - '--platform=^win32$', - '--no_resume', - '--no_auth', - '--bucket=chromium-clang-format', - '--sha1_file', - 'buildtools/win/clang-format.exe.sha1', - ], - }, { 'name': 'clang_format_linux', 'pattern': '.', + 'condition': 'host_os == "linux"', 'action': [ 'download_from_google_storage', - '--platform=^linux2?$', '--no_resume', '--no_auth', '--bucket=chromium-clang-format', @@ -85,11 +67,24 @@ hooks = [ ], }, { - 'name': 'gn_mac', + 'name': 'clang_format_win', 'pattern': '.', + 'condition': 'host_os == "win"', + 'action': [ + 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket=chromium-clang-format', + '--sha1_file', + 'buildtools/win/clang-format.exe.sha1', + ], + }, + { + 'name': 'gn_mac', + 'pattern': '.', + 'condition': 'host_os == "mac"', 'action': [ 'download_from_google_storage', - '--platform=^darwin$', '--no_resume', '--no_auth', '--bucket=chromium-gn', @@ -98,11 +93,24 @@ hooks = [ ], }, { - 'name': 'gn_win', + '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', - '--platform=^win32$', '--no_resume', '--no_auth', '--bucket=chromium-gn', @@ -111,16 +119,134 @@ hooks = [ ], }, { - 'name': 'gn_linux', + # This uses “cipd install” so that mac-amd64 and linux-amd64 can coexist + # peacefully. “cipd ensure” would remove the macOS package when running on a + # Linux build host and vice-versa. https://crbug.com/789364. This package is + # only updated when the solution in .gclient includes an entry like: + # "custom_vars": { "pull_linux_clang": True } + # The ref used is "goma". This is like "latest", but is considered a more + # stable latest by the Fuchsia toolchain team. + 'name': 'clang_linux', 'pattern': '.', + 'condition': 'checkout_linux and pull_linux_clang', 'action': [ - 'download_from_google_storage', - '--platform=^linux2?$', - '--no_resume', - '--no_auth', - '--bucket=chromium-gn', - '--sha1_file', - 'buildtools/linux64/gn.sha1', + 'cipd', + 'install', + # sic, using Fuchsia team's generic build of clang for linux-amd64 to + # build for linux-amd64 target too. + 'fuchsia/clang/linux-amd64', + 'goma', + '-root', 'crashpad/third_party/linux/clang/linux-amd64', + '-log-level', 'info', + ], + }, + { + # If using a local clang ("pull_linux_clang" above), also pull down a + # sysroot. + 'name': 'sysroot_linux', + 'pattern': '.', + 'condition': 'checkout_linux and pull_linux_clang', + 'action': [ + 'crashpad/build/install_linux_sysroot.py', + ], + }, + { + # Same rationale for using "install" rather than "ensure" as for first clang + # package. https://crbug.com/789364. + # Same rationale for using "goma" instead of "latest" as clang_linux above. + 'name': 'fuchsia_clang_mac', + 'pattern': '.', + 'condition': 'checkout_fuchsia and host_os == "mac"', + 'action': [ + 'cipd', + 'install', + 'fuchsia/clang/mac-amd64', + 'goma', + '-root', 'crashpad/third_party/fuchsia/clang/mac-amd64', + '-log-level', 'info', + ], + }, + { + # Same rationale for using "install" rather than "ensure" as for first clang + # package. https://crbug.com/789364. + # Same rationale for using "goma" instead of "latest" as clang_linux above. + 'name': 'fuchsia_clang_linux', + 'pattern': '.', + 'condition': 'checkout_fuchsia and host_os == "linux"', + 'action': [ + 'cipd', + 'install', + 'fuchsia/clang/linux-amd64', + 'goma', + '-root', 'crashpad/third_party/fuchsia/clang/linux-amd64', + '-log-level', 'info', + ], + }, + { + # Same rationale for using "install" rather than "ensure" as for clang + # packages. https://crbug.com/789364. + 'name': 'fuchsia_qemu_mac', + 'pattern': '.', + 'condition': 'checkout_fuchsia and host_os == "mac"', + 'action': [ + 'cipd', + 'install', + 'fuchsia/qemu/mac-amd64', + 'latest', + '-root', 'crashpad/third_party/fuchsia/qemu/mac-amd64', + '-log-level', 'info', + ], + }, + { + # Same rationale for using "install" rather than "ensure" as for clang + # packages. https://crbug.com/789364. + 'name': 'fuchsia_qemu_linux', + 'pattern': '.', + 'condition': 'checkout_fuchsia and host_os == "linux"', + 'action': [ + 'cipd', + 'install', + 'fuchsia/qemu/linux-amd64', + 'latest', + '-root', 'crashpad/third_party/fuchsia/qemu/linux-amd64', + '-log-level', 'info', + ], + }, + { + # 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). + 'name': 'fuchsia_sdk', + 'pattern': '.', + 'condition': 'checkout_fuchsia', + 'action': [ + 'cipd', + 'install', + 'fuchsia/sdk/linux-amd64', + 'latest', + '-root', 'crashpad/third_party/fuchsia/sdk/linux-amd64', + '-log-level', 'info', + ], + }, + { + 'name': 'toolchain_win', + 'pattern': '.', + # This package is only updated when the solution in .gclient includes an + # entry like: + # "custom_vars": { "pull_win_toolchain": True } + # This is because the contained bits are not redistributable. + 'condition': 'checkout_win and pull_win_toolchain', + 'action': [ + 'cipd', + 'install', + 'chrome_internal/third_party/sdk/windows', + 'uploaded:2018-06-13', + '-root', 'crashpad/third_party/win/toolchain', + '-log-level', 'info', ], }, { diff --git a/build/BUILD.gn b/build/BUILD.gn new file mode 100644 index 00000000..0ef1127f --- /dev/null +++ b/build/BUILD.gn @@ -0,0 +1,51 @@ +# 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. + +# When building in Chromium, these configs is used to set #defines that indicate +# whether code is being built standalone, or in Chromium, or potentially in some +# other configutation. + +import("crashpad_buildconfig.gni") + +config("crashpad_is_in_chromium") { + if (crashpad_is_in_chromium) { + defines = [ "CRASHPAD_IS_IN_CHROMIUM" ] + } +} + +config("crashpad_is_in_fuchsia") { + if (crashpad_is_in_fuchsia) { + defines = [ "CRASHPAD_IS_IN_FUCHSIA" ] + } +} + +group("default_exe_manifest_win") { + if (crashpad_is_in_chromium) { + deps = [ + "//build/win:default_exe_manifest", + ] + } +} + +config("crashpad_fuzzer_flags") { + cflags = [ + "-fsanitize=address", + "-fsanitize-address-use-after-scope", + "-fsanitize=fuzzer", + ] + + ldflags = [ + "-fsanitize=address", + ] +} diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn new file mode 100644 index 00000000..95dc2771 --- /dev/null +++ b/build/BUILDCONFIG.gn @@ -0,0 +1,92 @@ +# 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. + +# Intentionally very minimal, so that Crashpad can build in-tree in a variety of +# other projects, unrelated to the variables that are set in those projects' +# BUILDCONFIG.gn. Do not add more variables here. Instead, make them available +# in build/crashpad_buildconfig.gni if they must be globally available. + +if (target_os == "") { + target_os = host_os +} + +if (current_os == "") { + current_os = target_os +} + +if (target_cpu == "") { + target_cpu = host_cpu +} + +if (current_cpu == "") { + current_cpu = target_cpu +} + +if (current_os == "win") { + set_default_toolchain( + "//third_party/mini_chromium/mini_chromium/build:msvc_toolchain_$current_cpu") +} else { + set_default_toolchain( + "//third_party/mini_chromium/mini_chromium/build:gcc_like_toolchain") +} + +declare_args() { + # When true, enables the debug configuration, with additional run-time checks + # and logging. When false, enables the release configuration, with additional + # optimizations. + is_debug = false + + # When true, build all code with -fsanitize=fuzzer, and enable various + # *_fuzzer targets. + crashpad_use_libfuzzer = false +} + +_default_configs = [ + "//third_party/mini_chromium/mini_chromium/build:default", + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", +] + +if (crashpad_use_libfuzzer) { + _default_configs += [ "//build:crashpad_fuzzer_flags" ] +} + +_default_executable_configs = + _default_configs + [ + "//third_party/mini_chromium/mini_chromium/build:executable", + "//third_party/mini_chromium/mini_chromium/build:win_console", + ] + +set_defaults("source_set") { + configs = _default_configs +} + +set_defaults("static_library") { + configs = _default_configs +} + +set_defaults("executable") { + configs = _default_executable_configs +} + +set_defaults("loadable_module") { + configs = _default_configs +} + +set_defaults("shared_library") { + configs = _default_configs +} + +set_defaults("test") { + configs = _default_executable_configs +} diff --git a/build/crashpad_buildconfig.gni b/build/crashpad_buildconfig.gni new file mode 100644 index 00000000..f767027f --- /dev/null +++ b/build/crashpad_buildconfig.gni @@ -0,0 +1,108 @@ +# 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" + + 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_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_mac = is_mac + crashpad_is_win = is_win + crashpad_is_linux = is_linux + crashpad_is_android = is_android + crashpad_is_fuchsia = is_fuchsia + + crashpad_is_posix = is_posix + + 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") + crashpad_is_mac = mini_chromium_is_mac + crashpad_is_win = mini_chromium_is_win + crashpad_is_linux = mini_chromium_is_linux + crashpad_is_android = mini_chromium_is_android + crashpad_is_fuchsia = mini_chromium_is_fuchsia + + crashpad_is_posix = mini_chromium_is_posix + + crashpad_is_clang = mini_chromium_is_clang +} + +template("crashpad_executable") { + executable(target_name) { + forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) + if (defined(invoker.remove_configs)) { + configs -= invoker.remove_configs + } + + if (defined(invoker.configs)) { + 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" ] + } + } +} + +template("crashpad_loadable_module") { + loadable_module(target_name) { + forward_variables_from(invoker, "*", [ "configs", "remove_configs" ]) + if (defined(invoker.remove_configs)) { + configs -= invoker.remove_configs + } + + if (defined(invoker.configs)) { + 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" ] + } + } +} diff --git a/build/crashpad_dependencies.gni b/build/crashpad_dependencies.gni new file mode 100644 index 00000000..1964facd --- /dev/null +++ b/build/crashpad_dependencies.gni @@ -0,0 +1,53 @@ +# 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 new file mode 100644 index 00000000..8e0eaa9f --- /dev/null +++ b/build/crashpad_fuzzer_test.gni @@ -0,0 +1,46 @@ +# 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("crashpad_buildconfig.gni") +import("test.gni") + +template("fuzzer_test") { + if (crashpad_is_standalone && crashpad_use_libfuzzer) { + test(target_name) { + forward_variables_from(invoker, + [ + "cflags", + "cflags_cc", + "check_includes", + "defines", + "include_dirs", + "sources", + ]) + configs += [ "..:crashpad_config" ] + if (defined(invoker.deps)) { + deps = invoker.deps + } + deps += [ "../third_party/libfuzzer" ] + + if (!defined(invoker.cflags)) { + cflags = [] + } + cflags += [ "-fsanitize=fuzzer" ] + } + } else { + not_needed(invoker, "*") + group(target_name) { + } + } +} diff --git a/build/gyp_crashpad.py b/build/gyp_crashpad.py index d3cef710..84856606 100755 --- a/build/gyp_crashpad.py +++ b/build/gyp_crashpad.py @@ -78,7 +78,7 @@ def main(args): # 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 xrange(0, len(args)): + 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 diff --git a/build/gyp_crashpad_android.py b/build/gyp_crashpad_android.py index 4461425a..f78c05b9 100755 --- a/build/gyp_crashpad_android.py +++ b/build/gyp_crashpad_android.py @@ -69,26 +69,32 @@ def main(args): os.environ['CXX_target'] = os.path.join(ndk_bin_dir, '%s-g++' % arch_triplet) - # 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 unified - # headers are not being used, the Clang wrapper won’t mention - # __ANDROID_API__, but the standalone toolchain’s will - # #define it for both Clang and GCC. - # - # 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. - 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') + # 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 + # 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') for tool in ('ar', 'nm', 'readelf'): os.environ['%s_target' % tool.upper()] = ( diff --git a/build/install_linux_sysroot.py b/build/install_linux_sysroot.py new file mode 100755 index 00000000..afa88157 --- /dev/null +++ b/build/install_linux_sysroot.py @@ -0,0 +1,74 @@ +#!/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. + +# Various code adapted from: +# https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/install-sysroot.py + +import os +import shutil +import subprocess +import sys +import urllib2 + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Sysroot revision from: +# https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/sysroots.json +SERVER = 'https://commondatastorage.googleapis.com' +PATH = 'chrome-linux-sysroot/toolchain' +REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66' +FILENAME = 'debian_stretch_amd64_sysroot.tar.xz' + +def main(): + url = '%s/%s/%s/%s' % (SERVER, PATH, REVISION, FILENAME) + + 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 + + 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 + + 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]) + + os.remove(tarball) + + with open(stamp, 'w') as s: + s.write(url) + + +if __name__ == '__main__': + main() + sys.exit(0) diff --git a/build/run_fuchsia_qemu.py b/build/run_fuchsia_qemu.py new file mode 100755 index 00000000..135b314e --- /dev/null +++ b/build/run_fuchsia_qemu.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python + +# 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. + +"""Helper script to [re]start or stop a helper Fuchsia QEMU instance to be used +for running tests without a device. +""" + +from __future__ import print_function + +import getpass +import os +import random +import signal +import subprocess +import sys +import tempfile +import time + +try: + from subprocess import DEVNULL +except ImportError: + 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) + + +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 + + +def _Start(pid_file): + 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') + + 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) + + with open(pid_file, 'wb') as f: + f.write('%d\n' % popen.pid) + + 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 + + command = args[0] + + pid_file = os.path.join(tempfile.gettempdir(), 'crashpad_fuchsia_qemu_pid') + _Stop(pid_file) + if command == 'start': + return _Start(pid_file) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/build/run_tests.py b/build/run_tests.py index 3eae77a5..df7d8af1 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -15,22 +15,436 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import print_function + +import argparse import os +import pipes +import posixpath +import re import subprocess import sys +import uuid + +CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.pardir) +IS_WINDOWS_HOST = sys.platform.startswith('win') + + +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.""" + + 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 = ... 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 + + +def _BinaryDirTargetOS(binary_dir): + """Returns the apparent target OS of binary_dir, or None if none appear to be + explicitly specified.""" + + 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) + + # 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' + + 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. + """ + + 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 + + try: + # From . 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 + + 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 + + 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) + + 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) + + 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 = 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) + + 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]) + + 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'] + + 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] + + 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 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]) + + +def _GetFuchsiaSDKRoot(): + 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 /.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]) + + +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. + + 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. + + 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 ';'. + """ + 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]) # 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): - if len(args) != 1: - print >> sys.stderr, 'usage: run_tests.py ' - return 1 - - crashpad_dir = \ - os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) - binary_dir = args[0] + 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 @@ -38,37 +452,88 @@ def main(args): # 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 binary_dir.endswith('_x64') and + if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): - binary_dir_32 = binary_dir[:-4] + 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' + tests = [ 'crashpad_client_test', + 'crashpad_handler_test', 'crashpad_minidump_test', 'crashpad_snapshot_test', 'crashpad_test_test', 'crashpad_util_test', ] - if sys.platform == 'win32': - tests.append('crashpad_handler_test') - tests = sorted(tests) + 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 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 - subprocess.check_call(os.path.join(binary_dir, test)) - - if sys.platform == 'win32': - script = 'snapshot/win/end_to_end_test.py' - print '-' * 80 - print script - print '-' * 80 - subprocess.check_call( - [sys.executable, os.path.join(crashpad_dir, script), binary_dir]) + 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) return 0 diff --git a/build/test.gni b/build/test.gni new file mode 100644 index 00000000..f46520b7 --- /dev/null +++ b/build/test.gni @@ -0,0 +1,26 @@ +# 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. + +import("crashpad_buildconfig.gni") + +if (crashpad_is_in_chromium) { + import("//testing/test.gni") +} else { + template("test") { + executable(target_name) { + testonly = true + forward_variables_from(invoker, "*") + } + } +} diff --git a/client/BUILD.gn b/client/BUILD.gn new file mode 100644 index 00000000..a074b503 --- /dev/null +++ b/client/BUILD.gn @@ -0,0 +1,141 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +static_library("client") { + sources = [ + "annotation.cc", + "annotation.h", + "annotation_list.cc", + "annotation_list.h", + "crash_report_database.cc", + "crash_report_database.h", + "crashpad_client.h", + "crashpad_info.cc", + "crashpad_info.h", + "prune_crash_reports.cc", + "prune_crash_reports.h", + "settings.cc", + "settings.h", + "simple_address_range_bag.h", + "simple_string_dictionary.h", + "simulate_crash.h", + ] + + if (crashpad_is_mac) { + sources += [ + "crash_report_database_mac.mm", + "crashpad_client_mac.cc", + "simulate_crash_mac.cc", + "simulate_crash_mac.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android) { + set_sources_assignment_filter([]) + sources += [ + "crashpad_client_linux.cc", + "simulate_crash_linux.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ + "client_argv_handling.cc", + "client_argv_handling.h", + "crashpad_info_note.S", + ] + } + + if (crashpad_is_win) { + sources += [ + "crash_report_database_win.cc", + "crashpad_client_win.cc", + "simulate_crash_win.h", + ] + } + + if (crashpad_is_fuchsia) { + sources += [ "crashpad_client_fuchsia.cc" ] + } + + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ "crash_report_database_generic.cc" ] + } + + public_configs = [ "..:crashpad_config" ] + + deps = [ + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_win) { + libs = [ "rpcrt4.lib" ] + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } + + if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) { + deps += [ + "//zircon/public/lib/fdio", + ] + } +} + +source_set("client_test") { + testonly = true + + sources = [ + "annotation_list_test.cc", + "annotation_test.cc", + "crash_report_database_test.cc", + "prune_crash_reports_test.cc", + "settings_test.cc", + "simple_address_range_bag_test.cc", + "simple_string_dictionary_test.cc", + ] + + if (crashpad_is_mac) { + sources += [ "simulate_crash_mac_test.cc" ] + } + + if (crashpad_is_win) { + sources += [ "crashpad_client_win_test.cc" ] + } + + if (crashpad_is_linux || crashpad_is_android) { + sources += [ "crashpad_client_linux_test.cc" ] + } + + deps = [ + ":client", + "../compat", + "../snapshot", + "../test", + "../third_party/gtest:gmock", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + data_deps = [ + "../handler:crashpad_handler", + ] + + if (crashpad_is_win) { + data_deps += [ "../handler:crashpad_handler_console" ] + } +} diff --git a/client/annotation.cc b/client/annotation.cc index 22c73956..258f9654 100644 --- a/client/annotation.cc +++ b/client/annotation.cc @@ -30,7 +30,9 @@ constexpr size_t Annotation::kValueMaxSize; void Annotation::SetSize(ValueSizeType size) { DCHECK_LT(size, kValueMaxSize); size_ = size; - AnnotationList::Get()->Add(this); + // Use Register() instead of Get() in case the calling module has not + // explicitly initialized the annotation list, to avoid crashing. + AnnotationList::Register()->Add(this); } void Annotation::Clear() { diff --git a/client/annotation.h b/client/annotation.h index f76ddf68..46cde62c 100644 --- a/client/annotation.h +++ b/client/annotation.h @@ -25,6 +25,7 @@ #include "base/logging.h" #include "base/macros.h" #include "base/numerics/safe_conversions.h" +#include "base/strings/string_piece.h" #include "build/build_config.h" namespace crashpad { @@ -71,7 +72,7 @@ class Annotation { static constexpr size_t kNameMaxLength = 64; //! \brief The maximum size of an annotation’s value, in bytes. - static constexpr size_t kValueMaxSize = 2048; + static constexpr size_t kValueMaxSize = 5 * 4096; //! \brief The type used for \a SetSize(). using ValueSizeType = uint32_t; @@ -195,12 +196,36 @@ class Annotation { template class StringAnnotation : public Annotation { public: + //! \brief A constructor tag that enables braced initialization in C arrays. + //! + //! \sa StringAnnotation() + enum class Tag { kArray }; + //! \brief Constructs a new StringAnnotation with the given \a name. //! //! \param[in] name The Annotation name. constexpr explicit StringAnnotation(const char name[]) : Annotation(Type::kString, name, value_), value_() {} + //! \brief Constructs a new StringAnnotation with the given \a name. + //! + //! This constructor takes the ArrayInitializerTag for use when + //! initializing a C array of annotations. The main constructor is + //! explicit and cannot be brace-initialized. As an example: + //! + //! \code + //! static crashpad::StringAnnotation<32> annotations[] = { + //! {"name-1", crashpad::StringAnnotation<32>::Tag::kArray}, + //! {"name-2", crashpad::StringAnnotation<32>::Tag::kArray}, + //! {"name-3", crashpad::StringAnnotation<32>::Tag::kArray}, + //! }; + //! \endcode + //! + //! \param[in] name The Annotation name. + //! \param[in] tag A constructor tag. + constexpr StringAnnotation(const char name[], Tag tag) + : StringAnnotation(name) {} + //! \brief Sets the Annotation's string value. //! //! \param[in] value The `NUL`-terminated C-string value. @@ -210,6 +235,22 @@ class StringAnnotation : public Annotation { std::min(MaxSize, base::saturated_cast(strlen(value)))); } + //! \brief Sets the Annotation's string value. + //! + //! \param[in] string The string value. + void Set(base::StringPiece string) { + Annotation::ValueSizeType size = + std::min(MaxSize, base::saturated_cast(string.size())); + memcpy(value_, string.data(), size); + // Check for no embedded `NUL` characters. + DCHECK(!memchr(value_, '\0', size)) << "embedded NUL"; + SetSize(size); + } + + const base::StringPiece value() const { + return base::StringPiece(value_, size()); + } + private: // This value is not `NUL`-terminated, since the size is stored by the base // annotation. diff --git a/client/annotation_test.cc b/client/annotation_test.cc index 25530715..365c0c22 100644 --- a/client/annotation_test.cc +++ b/client/annotation_test.cc @@ -14,11 +14,13 @@ #include "client/annotation.h" +#include #include #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "gtest/gtest.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { @@ -81,14 +83,13 @@ TEST_F(Annotation, Basics) { TEST_F(Annotation, StringType) { crashpad::StringAnnotation<5> annotation("name"); - const char* value_ptr = static_cast(annotation.value()); EXPECT_FALSE(annotation.is_set()); EXPECT_EQ(crashpad::Annotation::Type::kString, annotation.type()); EXPECT_EQ(0u, annotation.size()); EXPECT_EQ(std::string("name"), annotation.name()); - EXPECT_EQ(0u, strlen(value_ptr)); + EXPECT_EQ(0u, annotation.value().size()); annotation.Set("test"); @@ -96,17 +97,39 @@ TEST_F(Annotation, StringType) { EXPECT_EQ(1u, AnnotationsCount()); EXPECT_EQ(4u, annotation.size()); - EXPECT_EQ(std::string("test"), value_ptr); + EXPECT_EQ("test", annotation.value()); - annotation.Set("loooooooooooong"); + annotation.Set(std::string("loooooooooooong")); EXPECT_TRUE(annotation.is_set()); EXPECT_EQ(1u, AnnotationsCount()); EXPECT_EQ(5u, annotation.size()); - EXPECT_EQ(std::string("loooo"), std::string(value_ptr, annotation.size())); + EXPECT_EQ("loooo", annotation.value()); } +TEST(StringAnnotation, ArrayOfString) { + static crashpad::StringAnnotation<4> annotations[] = { + {"test-1", crashpad::StringAnnotation<4>::Tag::kArray}, + {"test-2", crashpad::StringAnnotation<4>::Tag::kArray}, + {"test-3", crashpad::StringAnnotation<4>::Tag::kArray}, + {"test-4", crashpad::StringAnnotation<4>::Tag::kArray}, + }; + + for (auto& annotation : annotations) { + EXPECT_FALSE(annotation.is_set()); + } +} + +#if DCHECK_IS_ON() + +TEST(AnnotationDeathTest, EmbeddedNUL) { + crashpad::StringAnnotation<5> annotation("name"); + EXPECT_DEATH_CHECK(annotation.Set(std::string("te\0st", 5)), "embedded NUL"); +} + +#endif + } // namespace } // namespace test } // namespace crashpad diff --git a/client/capture_context_mac.h b/client/capture_context_mac.h deleted file mode 100644 index 74e440ed..00000000 --- a/client/capture_context_mac.h +++ /dev/null @@ -1,48 +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_CLIENT_CAPTURE_CONTEXT_MAC_H_ -#define CRASHPAD_CLIENT_CAPTURE_CONTEXT_MAC_H_ - -#include - -#include "build/build_config.h" - -namespace crashpad { - -#if defined(ARCH_CPU_X86_FAMILY) -using NativeCPUContext = x86_thread_state; -#endif - -//! \brief Saves the CPU context. -//! -//! The CPU context will be captured as accurately and completely as possible, -//! containing an atomic snapshot at the point of this function’s return. This -//! function does not modify any registers. -//! -//! \param[out] cpu_context The structure to store the context in. -//! -//! \note On x86_64, the value for `%%rdi` will be populated with the address of -//! this function’s argument, as mandated by the ABI. If the value of -//! `%%rdi` prior to calling this function is needed, it must be obtained -//! separately prior to calling this function. For example: -//! \code -//! uint64_t rdi; -//! asm("movq %%rdi, %0" : "=m"(rdi)); -//! \endcode -void CaptureContext(NativeCPUContext* cpu_context); - -} // namespace crashpad - -#endif // CRASHPAD_CLIENT_CAPTURE_CONTEXT_MAC_H_ diff --git a/client/capture_context_mac_test.cc b/client/capture_context_mac_test.cc deleted file mode 100644 index 15640210..00000000 --- a/client/capture_context_mac_test.cc +++ /dev/null @@ -1,158 +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 "client/capture_context_mac.h" - -#include -#include - -#include - -#include "build/build_config.h" -#include "gtest/gtest.h" -#include "util/misc/address_sanitizer.h" -#include "util/misc/implicit_cast.h" - -namespace crashpad { -namespace test { -namespace { - -// If the context structure has fields that tell whether it’s valid, such as -// magic numbers or size fields, sanity-checks those fields for validity with -// fatal gtest assertions. For other fields, where it’s possible to reason about -// their validity based solely on their contents, sanity-checks via nonfatal -// gtest assertions. -void SanityCheckContext(const NativeCPUContext& context) { -#if defined(ARCH_CPU_X86) - ASSERT_EQ(implicit_cast(context.tsh.flavor), - implicit_cast(x86_THREAD_STATE32)); - ASSERT_EQ(implicit_cast(context.tsh.count), - implicit_cast(x86_THREAD_STATE32_COUNT)); -#elif defined(ARCH_CPU_X86_64) - ASSERT_EQ(implicit_cast(context.tsh.flavor), - implicit_cast(x86_THREAD_STATE64)); - ASSERT_EQ(implicit_cast(context.tsh.count), - implicit_cast(x86_THREAD_STATE64_COUNT)); -#endif - -#if defined(ARCH_CPU_X86_FAMILY) - // The segment registers are only capable of storing 16-bit quantities, but - // the context structure provides native integer-width fields for them. Ensure - // that the high bits are all clear. - // - // Many bit positions in the flags register are reserved and will always read - // a known value. Most reserved bits are always 0, but bit 1 is always 1. - // Check that the reserved bits are all set to their expected values. Note - // that the set of reserved bits may be relaxed over time with newer CPUs, and - // that this test may need to be changed to reflect these developments. The - // current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel - // Software Developer’s Manual, Volume 1: Basic Architecture (253665-051), - // 3.4.3 “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume - // 2: System Programming (24593-3.24), 3.1.6 “RFLAGS Register”. -#if defined(ARCH_CPU_X86) - EXPECT_EQ(context.uts.ts32.__cs & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__ds & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__es & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__fs & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__gs & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__ss & ~0xffff, 0u); - EXPECT_EQ(context.uts.ts32.__eflags & 0xffc0802a, 2u); -#elif defined(ARCH_CPU_X86_64) - EXPECT_EQ(context.uts.ts64.__cs & ~UINT64_C(0xffff), 0u); - EXPECT_EQ(context.uts.ts64.__fs & ~UINT64_C(0xffff), 0u); - EXPECT_EQ(context.uts.ts64.__gs & ~UINT64_C(0xffff), 0u); - EXPECT_EQ(context.uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a), 2u); -#endif -#endif -} - -// A CPU-independent function to return the program counter. -uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { -#if defined(ARCH_CPU_X86) - return context.uts.ts32.__eip; -#elif defined(ARCH_CPU_X86_64) - return context.uts.ts64.__rip; -#endif -} - -// A CPU-independent function to return the stack pointer. -uintptr_t StackPointerFromContext(const NativeCPUContext& context) { -#if defined(ARCH_CPU_X86) - return context.uts.ts32.__esp; -#elif defined(ARCH_CPU_X86_64) - return context.uts.ts64.__rsp; -#endif -} - -void TestCaptureContext() { - NativeCPUContext context_1; - CaptureContext(&context_1); - - { - SCOPED_TRACE("context_1"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1)); - } - - // The program counter reference value is this function’s address. The - // captured program counter should be slightly greater than or equal to the - // reference program counter. - uintptr_t pc = ProgramCounterFromContext(context_1); - -#if !defined(ADDRESS_SANITIZER) - // AddressSanitizer can cause enough code bloat that the “nearby” check would - // likely fail. - const uintptr_t kReferencePC = - reinterpret_cast(TestCaptureContext); - EXPECT_LT(pc - kReferencePC, 64u); -#endif // !defined(ADDRESS_SANITIZER) - - // Declare sp and context_2 here because all local variables need to be - // declared before computing the stack pointer reference value, so that the - // reference value can be the lowest value possible. - uintptr_t sp; - NativeCPUContext context_2; - - // 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. - const uintptr_t kReferenceSP = - std::min(std::min(reinterpret_cast(&context_1), - reinterpret_cast(&context_2)), - std::min(reinterpret_cast(&pc), - reinterpret_cast(&sp))); - sp = StackPointerFromContext(context_1); - EXPECT_LT(kReferenceSP - sp, 512u); - - // Capture the context again, expecting that the stack pointer stays the same - // and the program counter increases. Strictly speaking, there’s no guarantee - // that these conditions will hold, although they do for known compilers even - // under typical optimization. - CaptureContext(&context_2); - - { - SCOPED_TRACE("context_2"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2)); - } - - EXPECT_EQ(StackPointerFromContext(context_2), sp); - EXPECT_GT(ProgramCounterFromContext(context_2), pc); -} - -TEST(CaptureContextMac, CaptureContext) { - ASSERT_NO_FATAL_FAILURE(TestCaptureContext()); -} - -} // namespace -} // namespace test -} // namespace crashpad diff --git a/client/client.gyp b/client/client.gyp index 4b14c4bf..a23d0c86 100644 --- a/client/client.gyp +++ b/client/client.gyp @@ -33,13 +33,12 @@ 'annotation.h', 'annotation_list.cc', 'annotation_list.h', - 'capture_context_mac.S', - 'capture_context_mac.h', 'crash_report_database.cc', 'crash_report_database.h', 'crash_report_database_mac.mm', 'crash_report_database_win.cc', 'crashpad_client.h', + 'crashpad_client_linux.cc', 'crashpad_client_mac.cc', 'crashpad_client_win.cc', 'crashpad_info.cc', @@ -51,6 +50,7 @@ 'simple_string_dictionary.h', 'simple_address_range_bag.h', 'simulate_crash.h', + 'simulate_crash_linux.h', 'simulate_crash_mac.cc', 'simulate_crash_mac.h', 'simulate_crash_win.h', @@ -63,9 +63,20 @@ ], }, }], - ['OS!="mac"', { - 'sources!': [ - 'capture_context_mac.S', + ['OS=="linux" or OS=="android"', { + 'sources': [ + 'client_argv_handling.cc', + 'client_argv_handling.h', + 'crashpad_info_note.S', + 'crash_report_database_generic.cc', + ], + }], + ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^crashpad_client_linux\\.cc$'], + ['include', '^simulate_crash_linux\\.h$'], ], }], ], diff --git a/client/client_argv_handling.cc b/client/client_argv_handling.cc new file mode 100644 index 00000000..6933aac6 --- /dev/null +++ b/client/client_argv_handling.cc @@ -0,0 +1,74 @@ +// 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 "client/client_argv_handling.h" + +#include "base/strings/stringprintf.h" + +namespace crashpad { + +namespace { + +std::string FormatArgumentString(const std::string& name, + const std::string& value) { + return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); +} + +} // namespace + +std::vector BuildHandlerArgvStrings( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments) { + std::vector argv_strings(1, handler.value()); + + for (const auto& argument : arguments) { + argv_strings.push_back(argument); + } + + if (!database.empty()) { + argv_strings.push_back(FormatArgumentString("database", database.value())); + } + + if (!metrics_dir.empty()) { + argv_strings.push_back( + FormatArgumentString("metrics-dir", metrics_dir.value())); + } + + if (!url.empty()) { + argv_strings.push_back(FormatArgumentString("url", url)); + } + + for (const auto& kv : annotations) { + argv_strings.push_back( + FormatArgumentString("annotation", kv.first + '=' + kv.second)); + } + + return argv_strings; +} + +void ConvertArgvStrings(const std::vector& argv_strings, + std::vector* argv) { + argv->clear(); + argv->reserve(argv_strings.size() + 1); + for (const auto& arg : argv_strings) { + argv->push_back(arg.c_str()); + } + argv->push_back(nullptr); +} + +} // namespace crashpad diff --git a/client/client_argv_handling.h b/client/client_argv_handling.h new file mode 100644 index 00000000..d380b1a0 --- /dev/null +++ b/client/client_argv_handling.h @@ -0,0 +1,51 @@ +// 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_CLIENT_CLIENT_ARGV_HANDLING_H_ +#define CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_ + +#include +#include +#include + +#include "base/files/file_path.h" + +namespace crashpad { + +//! \brief Builds a vector of arguments suitable for invoking a handler process +//! based on arguments passed to StartHandler-type(). +//! +//! See StartHandlerAtCrash() for documentation on the input arguments. +//! +//! \return A vector of arguments suitable for starting the handler with. +std::vector BuildHandlerArgvStrings( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments); + +//! \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& argv_strings, + std::vector* argv); + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_CLIENT_ARGV_HANDLING_H_ diff --git a/client/client_test.gyp b/client/client_test.gyp index 4ea4ef2b..0686cc67 100644 --- a/client/client_test.gyp +++ b/client/client_test.gyp @@ -24,6 +24,7 @@ 'client.gyp:crashpad_client', '../compat/compat.gyp:crashpad_compat', '../handler/handler.gyp:crashpad_handler', + '../snapshot/snapshot.gyp:crashpad_snapshot', '../test/test.gyp:crashpad_gmock_main', '../test/test.gyp:crashpad_test', '../third_party/gtest/gmock.gyp:gmock', @@ -37,9 +38,9 @@ 'sources': [ 'annotation_test.cc', 'annotation_list_test.cc', - 'capture_context_mac_test.cc', 'crash_report_database_test.cc', 'crashpad_client_win_test.cc', + 'crashpad_client_linux_test.cc', 'prune_crash_reports_test.cc', 'settings_test.cc', 'simple_address_range_bag_test.cc', @@ -53,6 +54,13 @@ ], }], ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^crashpad_client_linux_test\\.cc$'], + ], + }], + ], }, ], } diff --git a/client/crash_report_database.cc b/client/crash_report_database.cc index 8451e469..aa365beb 100644 --- a/client/crash_report_database.cc +++ b/client/crash_report_database.cc @@ -14,6 +14,9 @@ #include "client/crash_report_database.h" +#include "base/logging.h" +#include "build/build_config.h" + namespace crashpad { CrashReportDatabase::Report::Report() @@ -26,22 +29,68 @@ CrashReportDatabase::Report::Report() upload_attempts(0), upload_explicitly_requested(false) {} -CrashReportDatabase::CallErrorWritingCrashReport::CallErrorWritingCrashReport( +CrashReportDatabase::NewReport::NewReport() + : writer_(std::make_unique()), + file_remover_(), + attachment_writers_(), + attachment_removers_(), + uuid_(), + database_() {} + +CrashReportDatabase::NewReport::~NewReport() = default; + +bool CrashReportDatabase::NewReport::Initialize( CrashReportDatabase* database, - NewReport* new_report) - : database_(database), - new_report_(new_report) { + const base::FilePath& directory, + const base::FilePath::StringType& extension) { + database_ = database; + + if (!uuid_.InitializeWithNew()) { + return false; + } + +#if defined(OS_WIN) + const std::wstring uuid_string = uuid_.ToString16(); +#else + const std::string uuid_string = uuid_.ToString(); +#endif + + const base::FilePath path = directory.Append(uuid_string + extension); + if (!writer_->Open( + path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) { + return false; + } + file_remover_.reset(path); + return true; } -CrashReportDatabase::CallErrorWritingCrashReport:: - ~CallErrorWritingCrashReport() { - if (new_report_) { - database_->ErrorWritingCrashReport(new_report_); +CrashReportDatabase::UploadReport::UploadReport() + : Report(), + reader_(std::make_unique()), + database_(nullptr), + attachment_readers_(), + attachment_map_() {} + +CrashReportDatabase::UploadReport::~UploadReport() { + if (database_) { + database_->RecordUploadAttempt(this, false, std::string()); } } -void CrashReportDatabase::CallErrorWritingCrashReport::Disarm() { - new_report_ = nullptr; +bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath path, + CrashReportDatabase* db) { + database_ = db; + InitializeAttachments(); + return reader_->Open(path); +} + +CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete( + std::unique_ptr report_in, + const std::string& id) { + UploadReport* report = const_cast(report_in.get()); + + report->database_ = nullptr; + return RecordUploadAttempt(report, true, id); } } // namespace crashpad diff --git a/client/crash_report_database.h b/client/crash_report_database.h index 62117899..9ceeddc3 100644 --- a/client/crash_report_database.h +++ b/client/crash_report_database.h @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -24,6 +25,9 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "util/file/file_io.h" +#include "util/file/file_reader.h" +#include "util/file/file_writer.h" +#include "util/file/scoped_remove_file.h" #include "util/misc/metrics.h" #include "util/misc/uuid.h" @@ -47,7 +51,7 @@ class Settings; //! processed, or it was has been brought back from 'Completed' state by //! user request. //! 3. Completed: The report has been locally processed, either by uploading -//! it to a collection server and calling RecordUploadAttempt(), or by +//! it to a collection server and calling RecordUploadComplete(), or by //! calling SkipReportUpload(). class CrashReportDatabase { public: @@ -98,44 +102,84 @@ class CrashReportDatabase { //! \brief A crash report that is in the process of being written. //! - //! An instance of this struct should be created via PrepareNewCrashReport() - //! and destroyed with FinishedWritingCrashReport(). - struct NewReport { - //! The file handle to which the report should be written. - FileHandle handle; + //! An instance of this class should be created via PrepareNewCrashReport(). + class NewReport { + public: + NewReport(); + ~NewReport(); + + //! An open FileWriter with which to write the report. + FileWriter* Writer() const { return writer_.get(); } //! A unique identifier by which this report will always be known to the //! database. - UUID uuid; + const UUID& ReportID() const { return uuid_; } - //! The path to the crash report being written. - base::FilePath path; - }; - - //! \brief A scoper to cleanly handle the interface requirement imposed by - //! PrepareNewCrashReport(). - //! - //! Calls ErrorWritingCrashReport() upon destruction unless disarmed by - //! calling Disarm(). Armed upon construction. - class CallErrorWritingCrashReport { - public: - //! \brief Arms the object to call ErrorWritingCrashReport() on \a database - //! with an argument of \a new_report on destruction. - CallErrorWritingCrashReport(CrashReportDatabase* database, - NewReport* new_report); - - //! \brief Calls ErrorWritingCrashReport() if the object is armed. - ~CallErrorWritingCrashReport(); - - //! \brief Disarms the object so that CallErrorWritingCrashReport() will not - //! be called upon destruction. - void Disarm(); + //! \brief Adds an attachment to the report. + //! + //! \note This function is not yet implemented on macOS or Windows. + //! + //! \param[in] name The key and name for the attachment, which will be + //! included in the http upload. The attachment will not appear in the + //! minidump report. \a name should only use characters from the set + //! `[a-zA-Z0-9._-]`. + //! \return A FileWriter that the caller should use to write the contents of + //! the attachment, or `nullptr` on failure with an error logged. + FileWriter* AddAttachment(const std::string& name); private: - CrashReportDatabase* database_; // weak - NewReport* new_report_; // weak + friend class CrashReportDatabaseGeneric; + friend class CrashReportDatabaseMac; + friend class CrashReportDatabaseWin; - DISALLOW_COPY_AND_ASSIGN(CallErrorWritingCrashReport); + bool Initialize(CrashReportDatabase* database, + const base::FilePath& directory, + const base::FilePath::StringType& extension); + + std::unique_ptr writer_; + ScopedRemoveFile file_remover_; + std::vector> attachment_writers_; + std::vector attachment_removers_; + UUID uuid_; + CrashReportDatabase* database_; + + DISALLOW_COPY_AND_ASSIGN(NewReport); + }; + + //! \brief A crash report that is in the process of being uploaded. + //! + //! An instance of this class should be created via GetReportForUploading(). + class UploadReport : public Report { + public: + UploadReport(); + virtual ~UploadReport(); + + //! \brief An open FileReader with which to read the report. + FileReader* Reader() const { return reader_.get(); } + + //! \brief Obtains a mapping of names to file readers for any attachments + //! for the report. + //! + //! This is not implemented on macOS or Windows. + std::map GetAttachments() const { + return attachment_map_; + }; + + private: + friend class CrashReportDatabase; + friend class CrashReportDatabaseGeneric; + friend class CrashReportDatabaseMac; + friend class CrashReportDatabaseWin; + + bool Initialize(const base::FilePath path, CrashReportDatabase* database); + void InitializeAttachments(); + + std::unique_ptr reader_; + CrashReportDatabase* database_; + std::vector> attachment_readers_; + std::map attachment_map_; + + DISALLOW_COPY_AND_ASSIGN(UploadReport); }; //! \brief The result code for operations performed on a database. @@ -217,49 +261,31 @@ class CrashReportDatabase { //! \brief Creates a record of a new crash report. //! - //! Callers can then write the crash report using the file handle provided. - //! The caller does not own the new crash report record or its file handle, - //! both of which must be explicitly disposed of by calling - //! FinishedWritingCrashReport() or ErrorWritingCrashReport(). + //! Callers should write the crash report using the FileWriter provided. + //! Callers should then call FinishedWritingCrashReport() to complete report + //! creation. If an error is encountered while writing the crash report, no + //! special action needs to be taken. If FinishedWritingCrashReport() is not + //! called, the report will be removed from the database when \a report is + //! destroyed. //! - //! To arrange to call ErrorWritingCrashReport() during any early return, use - //! CallErrorWritingCrashReport. - //! - //! \param[out] report A NewReport object containing a file handle to which - //! the crash report data should be written. Only valid if this returns - //! #kNoError. The caller must not delete the NewReport object or close - //! the file handle within. + //! \param[out] report A NewReport object containing a FileWriter with which + //! to write the report data. Only valid if this returns #kNoError. //! //! \return The operation status code. - virtual OperationStatus PrepareNewCrashReport(NewReport** report) = 0; + virtual OperationStatus PrepareNewCrashReport( + std::unique_ptr* report) = 0; - //! \brief Informs the database that a crash report has been written. - //! - //! After calling this method, the database is permitted to move and rename - //! the file at NewReport::path. + //! \brief Informs the database that a crash report has been successfully + //! written. //! //! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The - //! NewReport object and file handle within will be invalidated as part of - //! this call. + //! NewReport object will be invalidated as part of this call. //! \param[out] uuid The UUID of this crash report. //! //! \return The operation status code. - virtual OperationStatus FinishedWritingCrashReport(NewReport* report, - UUID* uuid) = 0; - - //! \brief Informs the database that an error occurred while attempting to - //! write a crash report, and that any resources associated with it should - //! be cleaned up. - //! - //! After calling this method, the database is permitted to remove the file at - //! NewReport::path. - //! - //! \param[in] report A NewReport obtained with PrepareNewCrashReport(). The - //! NewReport object and file handle within will be invalidated as part of - //! this call. - //! - //! \return The operation status code. - virtual OperationStatus ErrorWritingCrashReport(NewReport* report) = 0; + virtual OperationStatus FinishedWritingCrashReport( + std::unique_ptr report, + UUID* uuid) = 0; //! \brief Returns the crash report record for the unique identifier. //! @@ -288,42 +314,38 @@ class CrashReportDatabase { //! \return The operation status code. virtual OperationStatus GetCompletedReports(std::vector* reports) = 0; - //! \brief Obtains a report object for uploading to a collection server. + //! \brief Obtains and locks a report object for uploading to a collection + //! server. //! - //! The file at Report::file_path should be uploaded by the caller, and then - //! the returned Report object must be disposed of via a call to - //! RecordUploadAttempt(). - //! - //! A subsequent call to this method with the same \a uuid is illegal until - //! RecordUploadAttempt() has been called. + //! Callers should upload the crash report using the FileReader provided. + //! Callers should then call RecordUploadComplete() to record a successful + //! upload. If RecordUploadComplete() is not called, the upload attempt will + //! be recorded as unsuccessful and the report lock released when \a report is + //! destroyed. //! //! \param[in] uuid The unique identifier for the crash report record. //! \param[out] report A crash report record for the report to be uploaded. - //! The caller does not own this object. Only valid if this returns - //! #kNoError. + //! Only valid if this returns #kNoError. //! //! \return The operation status code. - virtual OperationStatus GetReportForUploading(const UUID& uuid, - const Report** report) = 0; + virtual OperationStatus GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) = 0; - //! \brief Adjusts a crash report record’s metadata to account for an upload - //! attempt, and updates the last upload attempt time as returned by + //! \brief Records a successful upload for a report and updates the last + //! upload attempt time as returned by //! Settings::GetLastUploadAttemptTime(). //! - //! After calling this method, the database is permitted to move and rename - //! the file at Report::file_path. - //! - //! \param[in] report The report object obtained from - //! GetReportForUploading(). This object is invalidated after this call. - //! \param[in] successful Whether the upload attempt was successful. - //! \param[in] id The identifier assigned to this crash report by the - //! collection server. Must be empty if \a successful is `false`; may be - //! empty if it is `true`. + //! \param[in] report A UploadReport object obtained from + //! GetReportForUploading(). The UploadReport object will be invalidated + //! and the report unlocked as part of this call. + //! \param[in] id The possibly empty identifier assigned to this crash report + //! by the collection server. //! //! \return The operation status code. - virtual OperationStatus RecordUploadAttempt(const Report* report, - bool successful, - const std::string& id) = 0; + OperationStatus RecordUploadComplete( + std::unique_ptr report, + const std::string& id); //! \brief Moves a report from the pending state to the completed state, but //! without the report being uploaded. @@ -355,10 +377,37 @@ class CrashReportDatabase { //! \return The operation status code. virtual OperationStatus RequestUpload(const UUID& uuid) = 0; + //! \brief Cleans the database of expired lockfiles, metadata without report + //! files, and report files without metadata. + //! + //! This method does nothing on the macOS and Windows implementations of the + //! database. + //! + //! \param[in] lockfile_ttl The number of seconds at which lockfiles or new + //! report files are considered expired. + //! \return The number of reports cleaned. + virtual int CleanDatabase(time_t lockfile_ttl) { return 0; } + protected: CrashReportDatabase() {} private: + //! \brief Adjusts a crash report record’s metadata to account for an upload + //! attempt, and updates the last upload attempt time as returned by + //! Settings::GetLastUploadAttemptTime(). + //! + //! \param[in] report The report object obtained from + //! GetReportForUploading(). + //! \param[in] successful Whether the upload attempt was successful. + //! \param[in] id The identifier assigned to this crash report by the + //! collection server. Must be empty if \a successful is `false`; may be + //! empty if it is `true`. + //! + //! \return The operation status code. + virtual OperationStatus RecordUploadAttempt(UploadReport* report, + bool successful, + const std::string& id) = 0; + DISALLOW_COPY_AND_ASSIGN(CrashReportDatabase); }; diff --git a/client/crash_report_database_generic.cc b/client/crash_report_database_generic.cc new file mode 100644 index 00000000..6dc8e9e6 --- /dev/null +++ b/client/crash_report_database_generic.cc @@ -0,0 +1,996 @@ +// 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 "client/crash_report_database.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "client/settings.h" +#include "util/file/directory_reader.h" +#include "util/file/filesystem.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +namespace { + +base::FilePath ReplaceFinalExtension( + const base::FilePath& path, + const base::FilePath::StringType extension) { + return base::FilePath(path.RemoveFinalExtension().value() + extension); +} + +UUID UUIDFromReportPath(const base::FilePath& path) { + UUID uuid; + uuid.InitializeFromString(path.RemoveFinalExtension().BaseName().value()); + return uuid; +} + +bool AttachmentNameIsOK(const std::string& name) { + for (const char c : name) { + if (c != '_' && c != '-' && c != '.' && !isalnum(c)) + return false; + } + return true; +} + +using OperationStatus = CrashReportDatabase::OperationStatus; + +constexpr base::FilePath::CharType kSettings[] = + FILE_PATH_LITERAL("settings.dat"); + +constexpr base::FilePath::CharType kCrashReportExtension[] = + FILE_PATH_LITERAL(".dmp"); +constexpr base::FilePath::CharType kMetadataExtension[] = + FILE_PATH_LITERAL(".meta"); +constexpr base::FilePath::CharType kLockExtension[] = + FILE_PATH_LITERAL(".lock"); + +constexpr base::FilePath::CharType kNewDirectory[] = FILE_PATH_LITERAL("new"); +constexpr base::FilePath::CharType kPendingDirectory[] = + FILE_PATH_LITERAL("pending"); +constexpr base::FilePath::CharType kCompletedDirectory[] = + FILE_PATH_LITERAL("completed"); +constexpr base::FilePath::CharType kAttachmentsDirectory[] = + FILE_PATH_LITERAL("attachments"); + +constexpr const base::FilePath::CharType* kReportDirectories[] = { + kNewDirectory, + kPendingDirectory, + kCompletedDirectory, +}; + +enum { + //! \brief Corresponds to uploaded bit of the report state. + kAttributeUploaded = 1 << 0, + + //! \brief Corresponds to upload_explicity_requested bit of the report state. + kAttributeUploadExplicitlyRequested = 1 << 1, +}; + +struct ReportMetadata { + static constexpr int32_t kVersion = 1; + + int32_t version = kVersion; + int32_t upload_attempts = 0; + int64_t last_upload_attempt_time = 0; + time_t creation_time = 0; + uint8_t attributes = 0; +}; + +// A lock held while using database resources. +class ScopedLockFile { + public: + ScopedLockFile() = default; + ~ScopedLockFile() = default; + + ScopedLockFile& operator=(ScopedLockFile&& other) { + lock_file_.reset(other.lock_file_.release()); + return *this; + } + + // Attempt to acquire a lock for the report at report_path. + // Return `true` on success, otherwise `false`. + bool ResetAcquire(const base::FilePath& report_path) { + lock_file_.reset(); + + base::FilePath lock_path(report_path.RemoveFinalExtension().value() + + kLockExtension); + ScopedFileHandle lock_fd(LoggingOpenFileForWrite( + lock_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); + if (!lock_fd.is_valid()) { + return false; + } + lock_file_.reset(lock_path); + + time_t timestamp = time(nullptr); + if (!LoggingWriteFile(lock_fd.get(), ×tamp, sizeof(timestamp))) { + return false; + } + + return true; + } + + // Returns `true` if the lock is held. + bool is_valid() const { return lock_file_.is_valid(); } + + // Returns `true` if the lockfile at lock_path has expired. + static bool IsExpired(const base::FilePath& lock_path, time_t lockfile_ttl) { + time_t now = time(nullptr); + + timespec filetime; + if (FileModificationTime(lock_path, &filetime) && + filetime.tv_sec > now + lockfile_ttl) { + return false; + } + + ScopedFileHandle lock_fd(LoggingOpenFileForReadAndWrite( + lock_path, FileWriteMode::kReuseOrFail, FilePermissions::kOwnerOnly)); + if (!lock_fd.is_valid()) { + return false; + } + + time_t timestamp; + if (!LoggingReadFileExactly(lock_fd.get(), ×tamp, sizeof(timestamp))) { + return false; + } + + return now >= timestamp + lockfile_ttl; + } + + private: + ScopedRemoveFile lock_file_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLockFile); +}; + +} // namespace + +class CrashReportDatabaseGeneric : public CrashReportDatabase { + public: + CrashReportDatabaseGeneric(); + ~CrashReportDatabaseGeneric() override; + + bool Initialize(const base::FilePath& path, bool may_create); + + // CrashReportDatabase: + Settings* GetSettings() override; + OperationStatus PrepareNewCrashReport( + std::unique_ptr* report) override; + OperationStatus FinishedWritingCrashReport(std::unique_ptr report, + UUID* uuid) override; + OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; + OperationStatus GetPendingReports(std::vector* reports) override; + OperationStatus GetCompletedReports(std::vector* reports) override; + OperationStatus GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) override; + OperationStatus SkipReportUpload(const UUID& uuid, + Metrics::CrashSkippedReason reason) override; + OperationStatus DeleteReport(const UUID& uuid) override; + OperationStatus RequestUpload(const UUID& uuid) override; + int CleanDatabase(time_t lockfile_ttl) override; + + // Build a filepath for the directory for the report to hold attachments. + base::FilePath AttachmentsPath(const UUID& uuid); + + private: + struct LockfileUploadReport : public UploadReport { + ScopedLockFile lock_file; + }; + + enum ReportState : int32_t { + kUninitialized = -1, + + // Being created by a caller of PrepareNewCrashReport(). + kNew, + + // Created by FinishedWritingCrashReport(), but not yet uploaded. + kPending, + + // Upload completed or skipped. + kCompleted, + + // Specifies either kPending or kCompleted. + kSearchable, + }; + + // CrashReportDatabase: + OperationStatus RecordUploadAttempt(UploadReport* report, + bool successful, + const std::string& id) override; + + // Builds a filepath for the report with the specified uuid and state. + base::FilePath ReportPath(const UUID& uuid, ReportState state); + + // Locates the report with id uuid and returns its file path in path and a + // lock for the report in lock_file. This method succeeds as long as the + // report file exists and the lock can be acquired. No validation is done on + // the existence or content of the metadata file. + OperationStatus LocateAndLockReport(const UUID& uuid, + ReportState state, + base::FilePath* path, + ScopedLockFile* lock_file); + + // Locates, locks, and reads the metadata for the report with the specified + // uuid and state. This method will fail and may remove reports if invalid + // metadata is detected. state may be kPending, kCompleted, or kSearchable. + OperationStatus CheckoutReport(const UUID& uuid, + ReportState state, + base::FilePath* path, + ScopedLockFile* lock_file, + Report* report); + + // Reads metadata for all reports in state and returns it in reports. + OperationStatus ReportsInState(ReportState state, + std::vector* reports); + + // Cleans lone metadata, reports, or expired locks in a particular state. + int CleanReportsInState(ReportState state, time_t lockfile_ttl); + + // Cleans any attachments that have no associated report in any state. + void CleanOrphanedAttachments(); + + // Attempt to remove any attachments associated with the given report UUID. + // There may not be any, so failing is not an error. + 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); + + // Wraps ReadMetadata and removes the report from the database on failure. + bool CleaningReadMetadata(const base::FilePath& path, Report* report); + + // Writes metadata for a new report to the filesystem at path. + static bool WriteNewMetadata(const base::FilePath& path); + + // Writes the metadata for report to the filesystem at path. + static bool WriteMetadata(const base::FilePath& path, const Report& report); + + base::FilePath base_dir_; + Settings settings_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseGeneric); +}; + +FileWriter* CrashReportDatabase::NewReport::AddAttachment( + const std::string& name) { + if (!AttachmentNameIsOK(name)) { + LOG(ERROR) << "invalid name for attachment " << name; + return nullptr; + } + + base::FilePath attachments_dir = + static_cast(database_)->AttachmentsPath( + uuid_); + if (!LoggingCreateDirectory( + attachments_dir, FilePermissions::kOwnerOnly, true)) { + return nullptr; + } + + base::FilePath path = attachments_dir.Append(name); + + auto writer = std::make_unique(); + if (!writer->Open( + path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) { + LOG(ERROR) << "could not open " << path.value(); + return nullptr; + } + attachment_writers_.emplace_back(std::move(writer)); + attachment_removers_.emplace_back(ScopedRemoveFile(path)); + return attachment_writers_.back().get(); +} + +void CrashReportDatabase::UploadReport::InitializeAttachments() { + base::FilePath attachments_dir = + static_cast(database_)->AttachmentsPath( + uuid); + DirectoryReader reader; + if (!reader.Open(attachments_dir)) { + return; + } + + base::FilePath filename; + DirectoryReader::Result dir_result; + while ((dir_result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath filepath(attachments_dir.Append(filename)); + std::unique_ptr reader(std::make_unique()); + if (!reader->Open(filepath)) { + LOG(ERROR) << "attachment " << filepath.value() + << " couldn't be opened, skipping"; + continue; + } + attachment_readers_.emplace_back(std::move(reader)); + attachment_map_[filename.value()] = attachment_readers_.back().get(); + } +} + +CrashReportDatabaseGeneric::CrashReportDatabaseGeneric() = default; + +CrashReportDatabaseGeneric::~CrashReportDatabaseGeneric() = default; + +bool CrashReportDatabaseGeneric::Initialize(const base::FilePath& path, + bool may_create) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + base_dir_ = path; + + if (!IsDirectory(base_dir_, true) && + !(may_create && + LoggingCreateDirectory(base_dir_, FilePermissions::kOwnerOnly, true))) { + return false; + } + + for (const base::FilePath::CharType* subdir : kReportDirectories) { + if (!LoggingCreateDirectory( + base_dir_.Append(subdir), FilePermissions::kOwnerOnly, true)) { + return false; + } + } + + if (!LoggingCreateDirectory(base_dir_.Append(kAttachmentsDirectory), + FilePermissions::kOwnerOnly, + true)) { + return false; + } + + if (!settings_.Initialize(base_dir_.Append(kSettings))) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +// static +std::unique_ptr CrashReportDatabase::Initialize( + const base::FilePath& path) { + auto database = std::make_unique(); + return database->Initialize(path, true) ? std::move(database) : nullptr; +} + +// static +std::unique_ptr +CrashReportDatabase::InitializeWithoutCreating(const base::FilePath& path) { + auto database = std::make_unique(); + return database->Initialize(path, false) ? std::move(database) : nullptr; +} + +Settings* CrashReportDatabaseGeneric::GetSettings() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &settings_; +} + +OperationStatus CrashReportDatabaseGeneric::PrepareNewCrashReport( + std::unique_ptr* report) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + auto new_report = std::make_unique(); + if (!new_report->Initialize( + this, base_dir_.Append(kNewDirectory), kCrashReportExtension)) { + return kFileSystemError; + } + + report->reset(new_report.release()); + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::FinishedWritingCrashReport( + std::unique_ptr report, + UUID* uuid) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + base::FilePath path = ReportPath(report->ReportID(), kPending); + ScopedLockFile lock_file; + if (!lock_file.ResetAcquire(path)) { + return kBusyError; + } + + if (!WriteNewMetadata(ReplaceFinalExtension(path, kMetadataExtension))) { + return kDatabaseError; + } + + FileOffset size = report->Writer()->Seek(0, SEEK_END); + + report->Writer()->Close(); + if (!MoveFileOrDirectory(report->file_remover_.get(), path)) { + return kFileSystemError; + } + // We've moved the report to pending, so it no longer needs to be removed. + ignore_result(report->file_remover_.release()); + + // Close all the attachments and disarm their removers too. + for (auto& writer : report->attachment_writers_) { + writer->Close(); + } + for (auto& remover : report->attachment_removers_) { + ignore_result(remover.release()); + } + + *uuid = report->ReportID(); + + Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); + Metrics::CrashReportSize(size); + + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::LookUpCrashReport(const UUID& uuid, + Report* report) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + ScopedLockFile lock_file; + base::FilePath path; + return CheckoutReport(uuid, kSearchable, &path, &lock_file, report); +} + +OperationStatus CrashReportDatabaseGeneric::GetPendingReports( + std::vector* reports) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return ReportsInState(kPending, reports); +} + +OperationStatus CrashReportDatabaseGeneric::GetCompletedReports( + std::vector* reports) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return ReportsInState(kCompleted, reports); +} + +OperationStatus CrashReportDatabaseGeneric::GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + auto upload_report = std::make_unique(); + + base::FilePath path; + OperationStatus os = CheckoutReport( + uuid, kPending, &path, &upload_report->lock_file, upload_report.get()); + if (os != kNoError) { + return os; + } + + if (!upload_report->Initialize(path, this)) { + return kFileSystemError; + } + + report->reset(upload_report.release()); + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::SkipReportUpload( + const UUID& uuid, + Metrics::CrashSkippedReason reason) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + Metrics::CrashUploadSkipped(reason); + + base::FilePath path; + ScopedLockFile lock_file; + Report report; + OperationStatus os = + CheckoutReport(uuid, kPending, &path, &lock_file, &report); + if (os != kNoError) { + return os; + } + + base::FilePath completed_path(ReportPath(uuid, kCompleted)); + ScopedLockFile completed_lock_file; + if (!completed_lock_file.ResetAcquire(completed_path)) { + return kBusyError; + } + + report.upload_explicitly_requested = false; + if (!WriteMetadata(completed_path, report)) { + return kDatabaseError; + } + + if (!MoveFileOrDirectory(path, completed_path)) { + return kFileSystemError; + } + + if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { + return kDatabaseError; + } + + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::DeleteReport(const UUID& uuid) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + base::FilePath path; + ScopedLockFile lock_file; + OperationStatus os = + LocateAndLockReport(uuid, kSearchable, &path, &lock_file); + if (os != kNoError) { + return os; + } + + if (!LoggingRemoveFile(path)) { + return kFileSystemError; + } + + if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { + return kDatabaseError; + } + + RemoveAttachmentsByUUID(uuid); + + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::RequestUpload(const UUID& uuid) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + base::FilePath path; + ScopedLockFile lock_file; + Report report; + OperationStatus os = + CheckoutReport(uuid, kSearchable, &path, &lock_file, &report); + if (os != kNoError) { + return os; + } + + if (report.uploaded) { + return kCannotRequestUpload; + } + + report.upload_explicitly_requested = true; + base::FilePath pending_path = ReportPath(uuid, kPending); + if (!MoveFileOrDirectory(path, pending_path)) { + return kFileSystemError; + } + + if (!WriteMetadata(pending_path, report)) { + return kDatabaseError; + } + + if (pending_path != path) { + if (!LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension))) { + return kDatabaseError; + } + } + + Metrics::CrashReportPending(Metrics::PendingReportReason::kUserInitiated); + return kNoError; +} + +int CrashReportDatabaseGeneric::CleanDatabase(time_t lockfile_ttl) { + int removed = 0; + time_t now = time(nullptr); + + DirectoryReader reader; + const base::FilePath new_dir(base_dir_.Append(kNewDirectory)); + if (reader.Open(new_dir)) { + base::FilePath filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath filepath(new_dir.Append(filename)); + timespec filetime; + if (!FileModificationTime(filepath, &filetime)) { + continue; + } + if (filetime.tv_sec <= now - lockfile_ttl) { + if (LoggingRemoveFile(filepath)) { + ++removed; + } + } + } + } + + removed += CleanReportsInState(kPending, lockfile_ttl); + removed += CleanReportsInState(kCompleted, lockfile_ttl); + CleanOrphanedAttachments(); + return removed; +} + +OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt( + UploadReport* report, + bool successful, + const std::string& id) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + Metrics::CrashUploadAttempted(successful); + time_t now = time(nullptr); + + report->id = id; + report->uploaded = successful; + report->last_upload_attempt_time = now; + ++report->upload_attempts; + + base::FilePath report_path(report->file_path); + + ScopedLockFile lock_file; + if (successful) { + report->upload_explicitly_requested = false; + + base::FilePath completed_report_path = ReportPath(report->uuid, kCompleted); + + if (!lock_file.ResetAcquire(completed_report_path)) { + return kBusyError; + } + + report->Reader()->Close(); + if (!MoveFileOrDirectory(report_path, completed_report_path)) { + return kFileSystemError; + } + + LoggingRemoveFile(ReplaceFinalExtension(report_path, kMetadataExtension)); + report_path = completed_report_path; + } + + if (!WriteMetadata(report_path, *report)) { + return kDatabaseError; + } + + if (!settings_.SetLastUploadAttemptTime(now)) { + return kDatabaseError; + } + + return kNoError; +} + +base::FilePath CrashReportDatabaseGeneric::ReportPath(const UUID& uuid, + ReportState state) { + DCHECK_NE(state, kUninitialized); + DCHECK_NE(state, kSearchable); + +#if defined(OS_WIN) + const std::wstring uuid_string = uuid.ToString16(); +#else + const std::string uuid_string = uuid.ToString(); +#endif + + return base_dir_.Append(kReportDirectories[state]) + .Append(uuid_string + kCrashReportExtension); +} + +base::FilePath CrashReportDatabaseGeneric::AttachmentsPath(const UUID& uuid) { +#if defined(OS_WIN) + const std::wstring uuid_string = uuid.ToString16(); +#else + const std::string uuid_string = uuid.ToString(); +#endif + + return base_dir_.Append(kAttachmentsDirectory).Append(uuid_string); +} + +OperationStatus CrashReportDatabaseGeneric::LocateAndLockReport( + const UUID& uuid, + ReportState desired_state, + base::FilePath* path, + ScopedLockFile* lock_file) { + std::vector searchable_states; + if (desired_state == kSearchable) { + searchable_states.push_back(kPending); + searchable_states.push_back(kCompleted); + } else { + DCHECK(desired_state == kPending || desired_state == kCompleted); + searchable_states.push_back(desired_state); + } + + for (const ReportState state : searchable_states) { + base::FilePath local_path(ReportPath(uuid, state)); + ScopedLockFile local_lock; + if (!local_lock.ResetAcquire(local_path)) { + return kBusyError; + } + + if (!IsRegularFile(local_path)) { + continue; + } + + *path = local_path; + *lock_file = std::move(local_lock); + return kNoError; + } + + return kReportNotFound; +} + +OperationStatus CrashReportDatabaseGeneric::CheckoutReport( + const UUID& uuid, + ReportState state, + base::FilePath* path, + ScopedLockFile* lock_file, + Report* report) { + ScopedLockFile local_lock; + base::FilePath local_path; + OperationStatus os = + LocateAndLockReport(uuid, state, &local_path, &local_lock); + if (os != kNoError) { + return os; + } + + if (!CleaningReadMetadata(local_path, report)) { + return kDatabaseError; + } + + *path = local_path; + *lock_file = std::move(local_lock); + return kNoError; +} + +OperationStatus CrashReportDatabaseGeneric::ReportsInState( + ReportState state, + std::vector* reports) { + DCHECK(reports->empty()); + DCHECK_NE(state, kUninitialized); + DCHECK_NE(state, kSearchable); + DCHECK_NE(state, kNew); + + const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state])); + DirectoryReader reader; + if (!reader.Open(dir_path)) { + return kDatabaseError; + } + + base::FilePath filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath::StringType extension(filename.FinalExtension()); + if (extension.compare(kCrashReportExtension) != 0) { + continue; + } + + const base::FilePath filepath(dir_path.Append(filename)); + ScopedLockFile lock_file; + if (!lock_file.ResetAcquire(filepath)) { + continue; + } + + Report report; + if (!CleaningReadMetadata(filepath, &report)) { + continue; + } + reports->push_back(report); + reports->back().file_path = filepath; + } + return kNoError; +} + +int CrashReportDatabaseGeneric::CleanReportsInState(ReportState state, + time_t lockfile_ttl) { + const base::FilePath dir_path(base_dir_.Append(kReportDirectories[state])); + DirectoryReader reader; + if (!reader.Open(dir_path)) { + return 0; + } + + int removed = 0; + base::FilePath filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath::StringType extension(filename.FinalExtension()); + const base::FilePath filepath(dir_path.Append(filename)); + + // Remove any report files without metadata. + if (extension.compare(kCrashReportExtension) == 0) { + const base::FilePath metadata_path( + ReplaceFinalExtension(filepath, kMetadataExtension)); + ScopedLockFile report_lock; + if (report_lock.ResetAcquire(filepath) && !IsRegularFile(metadata_path) && + LoggingRemoveFile(filepath)) { + ++removed; + RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); + } + continue; + } + + // Remove any metadata files without report files. + if (extension.compare(kMetadataExtension) == 0) { + const base::FilePath report_path( + ReplaceFinalExtension(filepath, kCrashReportExtension)); + ScopedLockFile report_lock; + if (report_lock.ResetAcquire(report_path) && + !IsRegularFile(report_path) && LoggingRemoveFile(filepath)) { + ++removed; + RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); + } + continue; + } + + // Remove any expired locks only if we can remove the report and metadata. + if (extension.compare(kLockExtension) == 0 && + ScopedLockFile::IsExpired(filepath, lockfile_ttl)) { + const base::FilePath no_ext(filepath.RemoveFinalExtension()); + const base::FilePath report_path(no_ext.value() + kCrashReportExtension); + const base::FilePath metadata_path(no_ext.value() + kMetadataExtension); + if ((IsRegularFile(report_path) && !LoggingRemoveFile(report_path)) || + (IsRegularFile(metadata_path) && !LoggingRemoveFile(metadata_path))) { + continue; + } + + if (LoggingRemoveFile(filepath)) { + ++removed; + RemoveAttachmentsByUUID(UUIDFromReportPath(filepath)); + } + continue; + } + } + + return removed; +} + +void CrashReportDatabaseGeneric::CleanOrphanedAttachments() { + base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory)); + DirectoryReader reader; + if (!reader.Open(root_attachments_dir)) { + LOG(ERROR) << "no attachments dir"; + return; + } + + base::FilePath filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath path(root_attachments_dir.Append(filename)); + if (IsDirectory(path, false)) { + UUID uuid; + if (!uuid.InitializeFromString(filename.value())) { + LOG(ERROR) << "unexpected attachment dir name " << filename.value(); + continue; + } + + // Check to see if the report is being created in "new". + base::FilePath new_dir_path = + base_dir_.Append(kNewDirectory) + .Append(uuid.ToString() + kCrashReportExtension); + if (IsRegularFile(new_dir_path)) { + continue; + } + + // Check to see if the report is in "pending" or "completed". + ScopedLockFile local_lock; + base::FilePath local_path; + OperationStatus os = + LocateAndLockReport(uuid, kSearchable, &local_path, &local_lock); + if (os != kReportNotFound) { + continue; + } + + // Couldn't find a report, assume these attachments are orphaned. + RemoveAttachmentsByUUID(uuid); + } + } +} + +void CrashReportDatabaseGeneric::RemoveAttachmentsByUUID(const UUID& uuid) { + base::FilePath attachments_dir = AttachmentsPath(uuid); + DirectoryReader reader; + if (!reader.Open(attachments_dir)) { + return; + } + + base::FilePath filename; + DirectoryReader::Result result; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath filepath(attachments_dir.Append(filename)); + LoggingRemoveFile(filepath); + } + + LoggingRemoveDirectory(attachments_dir); +} + +// static +bool CrashReportDatabaseGeneric::ReadMetadata(const base::FilePath& path, + Report* report) { + const base::FilePath metadata_path( + ReplaceFinalExtension(path, kMetadataExtension)); + + ScopedFileHandle handle(LoggingOpenFileForRead(metadata_path)); + if (!handle.is_valid()) { + return false; + } + + if (!report->uuid.InitializeFromString( + path.BaseName().RemoveFinalExtension().value())) { + LOG(ERROR) << "Couldn't interpret report uuid"; + return false; + } + + ReportMetadata metadata; + if (!LoggingReadFileExactly(handle.get(), &metadata, sizeof(metadata))) { + return false; + } + + if (metadata.version != ReportMetadata::kVersion) { + LOG(ERROR) << "metadata version mismatch"; + return false; + } + + if (!LoggingReadToEOF(handle.get(), &report->id)) { + return false; + } + + report->upload_attempts = metadata.upload_attempts; + report->last_upload_attempt_time = metadata.last_upload_attempt_time; + report->creation_time = metadata.creation_time; + report->uploaded = (metadata.attributes & kAttributeUploaded) != 0; + report->upload_explicitly_requested = + (metadata.attributes & kAttributeUploadExplicitlyRequested) != 0; + report->file_path = path; + return true; +} + +bool CrashReportDatabaseGeneric::CleaningReadMetadata( + const base::FilePath& path, + Report* report) { + if (ReadMetadata(path, report)) { + return true; + } + + LoggingRemoveFile(path); + LoggingRemoveFile(ReplaceFinalExtension(path, kMetadataExtension)); + RemoveAttachmentsByUUID(report->uuid); + return false; +} + +// static +bool CrashReportDatabaseGeneric::WriteNewMetadata(const base::FilePath& path) { + const base::FilePath metadata_path( + ReplaceFinalExtension(path, kMetadataExtension)); + + ScopedFileHandle handle(LoggingOpenFileForWrite(metadata_path, + FileWriteMode::kCreateOrFail, + FilePermissions::kOwnerOnly)); + if (!handle.is_valid()) { + return false; + } + + ReportMetadata metadata; + metadata.creation_time = time(nullptr); + + return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)); +} + +// static +bool CrashReportDatabaseGeneric::WriteMetadata(const base::FilePath& path, + const Report& report) { + const base::FilePath metadata_path( + ReplaceFinalExtension(path, kMetadataExtension)); + + ScopedFileHandle handle( + LoggingOpenFileForWrite(metadata_path, + FileWriteMode::kTruncateOrCreate, + FilePermissions::kOwnerOnly)); + if (!handle.is_valid()) { + return false; + } + + ReportMetadata metadata; + metadata.creation_time = report.creation_time; + metadata.last_upload_attempt_time = report.last_upload_attempt_time; + metadata.upload_attempts = report.upload_attempts; + metadata.attributes = + (report.uploaded ? kAttributeUploaded : 0) | + (report.upload_explicitly_requested ? kAttributeUploadExplicitlyRequested + : 0); + + return LoggingWriteFile(handle.get(), &metadata, sizeof(metadata)) && + LoggingWriteFile(handle.get(), report.id.c_str(), report.id.size()); +} + +} // namespace crashpad diff --git a/client/crash_report_database_mac.mm b/client/crash_report_database_mac.mm index 7a9154b8..79b876d2 100644 --- a/client/crash_report_database_mac.mm +++ b/client/crash_report_database_mac.mm @@ -107,6 +107,8 @@ std::string XattrNameInternal(const base::StringPiece& name, bool new_name) { name.data()); } +} // namespace + //! \brief A CrashReportDatabase that uses HFS+ extended attributes to store //! report metadata. //! @@ -130,24 +132,27 @@ class CrashReportDatabaseMac : public CrashReportDatabase { // CrashReportDatabase: Settings* GetSettings() override; - OperationStatus PrepareNewCrashReport(NewReport** report) override; - OperationStatus FinishedWritingCrashReport(NewReport* report, + OperationStatus PrepareNewCrashReport( + std::unique_ptr* report) override; + OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override; - OperationStatus ErrorWritingCrashReport(NewReport* report) override; OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; OperationStatus GetPendingReports(std::vector* reports) override; OperationStatus GetCompletedReports(std::vector* reports) override; - OperationStatus GetReportForUploading(const UUID& uuid, - const Report** report) override; - OperationStatus RecordUploadAttempt(const Report* report, - bool successful, - const std::string& id) override; + OperationStatus GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) override; OperationStatus SkipReportUpload(const UUID& uuid, Metrics::CrashSkippedReason reason) override; OperationStatus DeleteReport(const UUID& uuid) override; OperationStatus RequestUpload(const UUID& uuid) override; private: + // CrashReportDatabase: + OperationStatus RecordUploadAttempt(UploadReport* report, + bool successful, + const std::string& id) override; + //! \brief Report states for use with LocateCrashReport(). //! //! ReportState may be considered to be a bitfield. @@ -161,10 +166,10 @@ class CrashReportDatabaseMac : public CrashReportDatabase { //! \brief A private extension of the Report class that maintains bookkeeping //! information of the database. - struct UploadReport : public Report { + struct UploadReportMac : public UploadReport { //! \brief Stores the flock of the file for the duration of //! GetReportForUploading() and RecordUploadAttempt(). - int lock_fd; + base::ScopedFD lock_fd; }; //! \brief Locates a crash report in the database by UUID. @@ -240,10 +245,20 @@ class CrashReportDatabaseMac : public CrashReportDatabase { DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac); }; +FileWriter* CrashReportDatabase::NewReport::AddAttachment( + const std::string& name) { + // Attachments aren't implemented in the Mac database yet. + return nullptr; +} + +void CrashReportDatabase::UploadReport::InitializeAttachments() { + // Attachments aren't implemented in the Mac database yet. +} + CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path) : CrashReportDatabase(), base_dir_(path), - settings_(base_dir_.Append(kSettings)), + settings_(), xattr_new_names_(false), initialized_() { } @@ -268,7 +283,7 @@ bool CrashReportDatabaseMac::Initialize(bool may_create) { return false; } - if (!settings_.Initialize()) + if (!settings_.Initialize(base_dir_.Append(kSettings))) return false; // Do an xattr operation as the last step, to ensure the filesystem has @@ -301,103 +316,68 @@ Settings* CrashReportDatabaseMac::GetSettings() { } CrashReportDatabase::OperationStatus -CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) { +CrashReportDatabaseMac::PrepareNewCrashReport( + std::unique_ptr* out_report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr report(new NewReport()); - - uuid_t uuid_gen; - uuid_generate(uuid_gen); - report->uuid.InitializeFromBytes(uuid_gen); - - report->path = - base_dir_.Append(kWriteDirectory) - .Append(report->uuid.ToString() + "." + kCrashReportFileExtension); - - report->handle = HANDLE_EINTR( - open(report->path.value().c_str(), - O_WRONLY | O_EXLOCK | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC, - 0600)); - if (report->handle < 0) { - PLOG(ERROR) << "open " << report->path.value(); + if (!report->Initialize(this, + base_dir_.Append(kWriteDirectory), + std::string(".") + kCrashReportFileExtension)) { return kFileSystemError; } // TODO(rsesek): Potentially use an fsetxattr() here instead. - if (!WriteXattr( - report->path, XattrName(kXattrUUID), report->uuid.ToString())) { - PLOG_IF(ERROR, IGNORE_EINTR(close(report->handle)) != 0) << "close"; + if (!WriteXattr(report->file_remover_.get(), + XattrName(kXattrUUID), + report->ReportID().ToString())) { return kDatabaseError; } - *out_report = report.release(); - + out_report->reset(report.release()); return kNoError; } CrashReportDatabase::OperationStatus -CrashReportDatabaseMac::FinishedWritingCrashReport(NewReport* report, - UUID* uuid) { +CrashReportDatabaseMac::FinishedWritingCrashReport( + std::unique_ptr report, + UUID* uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - // Takes ownership of the |handle| and the O_EXLOCK. - base::ScopedFD lock(report->handle); - - // Take ownership of the report. - std::unique_ptr scoped_report(report); + const base::FilePath& path = report->file_remover_.get(); // Get the report's UUID to return. std::string uuid_string; - if (ReadXattr(report->path, XattrName(kXattrUUID), - &uuid_string) != XattrStatus::kOK || + if (ReadXattr(path, XattrName(kXattrUUID), &uuid_string) != + XattrStatus::kOK || !uuid->InitializeFromString(uuid_string)) { - LOG(ERROR) << "Failed to read UUID for crash report " - << report->path.value(); + LOG(ERROR) << "Failed to read UUID for crash report " << path.value(); return kDatabaseError; } - if (*uuid != report->uuid) { - LOG(ERROR) << "UUID mismatch for crash report " << report->path.value(); + if (*uuid != report->ReportID()) { + LOG(ERROR) << "UUID mismatch for crash report " << path.value(); return kDatabaseError; } // Record the creation time of this report. - if (!WriteXattrTimeT(report->path, XattrName(kXattrCreationTime), - time(nullptr))) { + if (!WriteXattrTimeT(path, XattrName(kXattrCreationTime), time(nullptr))) { return kDatabaseError; } + FileOffset size = report->Writer()->Seek(0, SEEK_END); + // Move the report to its new location for uploading. base::FilePath new_path = - base_dir_.Append(kUploadPendingDirectory).Append(report->path.BaseName()); - if (rename(report->path.value().c_str(), new_path.value().c_str()) != 0) { - PLOG(ERROR) << "rename " << report->path.value() << " to " - << new_path.value(); + base_dir_.Append(kUploadPendingDirectory).Append(path.BaseName()); + if (rename(path.value().c_str(), new_path.value().c_str()) != 0) { + PLOG(ERROR) << "rename " << path.value() << " to " << new_path.value(); return kFileSystemError; } + ignore_result(report->file_remover_.release()); Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); - Metrics::CrashReportSize(report->handle); - - return kNoError; -} - -CrashReportDatabase::OperationStatus -CrashReportDatabaseMac::ErrorWritingCrashReport(NewReport* report) { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - - // Takes ownership of the |handle| and the O_EXLOCK. - base::ScopedFD lock(report->handle); - - // Take ownership of the report. - std::unique_ptr scoped_report(report); - - // Remove the file that the report would have been written to had no error - // occurred. - if (unlink(report->path.value().c_str()) != 0) { - PLOG(ERROR) << "unlink " << report->path.value(); - return kFileSystemError; - } + Metrics::CrashReportSize(size); return kNoError; } @@ -440,31 +420,36 @@ CrashReportDatabaseMac::GetCompletedReports( } CrashReportDatabase::OperationStatus -CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid, - const Report** report) { +CrashReportDatabaseMac::GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - base::FilePath report_path = LocateCrashReport(uuid, kReportStatePending); - if (report_path.empty()) + auto upload_report = std::make_unique(); + + upload_report->file_path = LocateCrashReport(uuid, kReportStatePending); + if (upload_report->file_path.empty()) return kReportNotFound; - std::unique_ptr upload_report(new UploadReport()); - upload_report->file_path = report_path; - - base::ScopedFD lock(ObtainReportLock(report_path)); + base::ScopedFD lock(ObtainReportLock(upload_report->file_path)); if (!lock.is_valid()) return kBusyError; - if (!ReadReportMetadataLocked(report_path, upload_report.get())) + if (!ReadReportMetadataLocked(upload_report->file_path, upload_report.get())) return kDatabaseError; - upload_report->lock_fd = lock.release(); - *report = upload_report.release(); + if (!upload_report->reader_->Open(upload_report->file_path)) { + return kFileSystemError; + } + + upload_report->database_ = this; + upload_report->lock_fd.reset(lock.release()); + report->reset(upload_report.release()); return kNoError; } CrashReportDatabase::OperationStatus -CrashReportDatabaseMac::RecordUploadAttempt(const Report* report, +CrashReportDatabaseMac::RecordUploadAttempt(UploadReport* report, bool successful, const std::string& id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); @@ -479,13 +464,6 @@ CrashReportDatabaseMac::RecordUploadAttempt(const Report* report, if (report_path.empty()) return kReportNotFound; - std::unique_ptr upload_report( - static_cast(report)); - - base::ScopedFD lock(upload_report->lock_fd); - if (!lock.is_valid()) - return kBusyError; - if (successful) { CrashReportDatabase::OperationStatus os = MarkReportCompletedLocked(report_path, &report_path); @@ -774,8 +752,6 @@ std::unique_ptr InitializeInternal( return std::unique_ptr(database_mac.release()); } -} // namespace - // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { diff --git a/client/crash_report_database_test.cc b/client/crash_report_database_test.cc index c4266961..2dbb4fc1 100644 --- a/client/crash_report_database_test.cc +++ b/client/crash_report_database_test.cc @@ -19,8 +19,11 @@ #include "gtest/gtest.h" #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" namespace crashpad { namespace test { @@ -48,20 +51,19 @@ class CrashReportDatabaseTest : public testing::Test { } void CreateCrashReport(CrashReportDatabase::Report* report) { - CrashReportDatabase::NewReport* new_report = nullptr; + std::unique_ptr new_report; ASSERT_EQ(db_->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); static constexpr char kTest[] = "test"; - ASSERT_TRUE(LoggingWriteFile(new_report->handle, kTest, sizeof(kTest))); + ASSERT_TRUE(new_report->Writer()->Write(kTest, sizeof(kTest))); UUID uuid; - EXPECT_EQ(db_->FinishedWritingCrashReport(new_report, &uuid), + EXPECT_EQ(db_->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(db_->LookUpCrashReport(uuid, report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(*report); - ASSERT_TRUE(FileExists(report->file_path)); } void UploadReport(const UUID& uuid, bool successful, const std::string& id) { @@ -70,15 +72,19 @@ class CrashReportDatabaseTest : public testing::Test { time_t times[2]; ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[0])); - const CrashReportDatabase::Report* report = nullptr; + std::unique_ptr report; ASSERT_EQ(db_->GetReportForUploading(uuid, &report), CrashReportDatabase::kNoError); EXPECT_NE(report->uuid, UUID()); EXPECT_FALSE(report->file_path.empty()); EXPECT_TRUE(FileExists(report->file_path)) << report->file_path.value(); EXPECT_GT(report->creation_time, 0); - EXPECT_EQ(db_->RecordUploadAttempt(report, successful, id), - CrashReportDatabase::kNoError); + if (successful) { + EXPECT_EQ(db_->RecordUploadComplete(std::move(report), id), + CrashReportDatabase::kNoError); + } else { + report.reset(); + } ASSERT_TRUE(settings->GetLastUploadAttemptTime(×[1])); EXPECT_NE(times[1], 0); @@ -176,13 +182,12 @@ TEST_F(CrashReportDatabaseTest, Initialize) { } TEST_F(CrashReportDatabaseTest, NewCrashReport) { - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); - UUID expect_uuid = new_report->uuid; - EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value(); + UUID expect_uuid = new_report->ReportID(); UUID uuid; - EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid), + EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); EXPECT_EQ(uuid, expect_uuid); @@ -201,17 +206,6 @@ TEST_F(CrashReportDatabaseTest, NewCrashReport) { EXPECT_TRUE(reports.empty()); } -TEST_F(CrashReportDatabaseTest, ErrorWritingCrashReport) { - CrashReportDatabase::NewReport* new_report = nullptr; - ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), - CrashReportDatabase::kNoError); - base::FilePath new_report_path = new_report->path; - EXPECT_TRUE(FileExists(new_report_path)) << new_report_path.value(); - EXPECT_EQ(db()->ErrorWritingCrashReport(new_report), - CrashReportDatabase::kNoError); - EXPECT_FALSE(FileExists(new_report_path)) << new_report_path.value(); -} - TEST_F(CrashReportDatabaseTest, LookUpCrashReport) { UUID uuid; @@ -465,16 +459,16 @@ TEST_F(CrashReportDatabaseTest, DuelingUploads) { CrashReportDatabase::Report report; CreateCrashReport(&report); - const CrashReportDatabase::Report* upload_report; + std::unique_ptr upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); - const CrashReportDatabase::Report* upload_report_2 = nullptr; + std::unique_ptr upload_report_2; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kBusyError); EXPECT_FALSE(upload_report_2); - EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()), + EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()), CrashReportDatabase::kNoError); } @@ -482,25 +476,24 @@ TEST_F(CrashReportDatabaseTest, UploadAlreadyUploaded) { CrashReportDatabase::Report report; CreateCrashReport(&report); - const CrashReportDatabase::Report* upload_report; + std::unique_ptr upload_report; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report), CrashReportDatabase::kNoError); - EXPECT_EQ(db()->RecordUploadAttempt(upload_report, true, std::string()), + EXPECT_EQ(db()->RecordUploadComplete(std::move(upload_report), std::string()), CrashReportDatabase::kNoError); - const CrashReportDatabase::Report* upload_report_2 = nullptr; + std::unique_ptr upload_report_2; EXPECT_EQ(db()->GetReportForUploading(report.uuid, &upload_report_2), CrashReportDatabase::kReportNotFound); - EXPECT_FALSE(upload_report_2); + EXPECT_FALSE(upload_report_2.get()); } TEST_F(CrashReportDatabaseTest, MoveDatabase) { - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); - EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value(); UUID uuid; - EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid), + EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); RelocateDatabase(); @@ -509,28 +502,22 @@ TEST_F(CrashReportDatabaseTest, MoveDatabase) { EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); ExpectPreparedCrashReport(report); - EXPECT_TRUE(FileExists(report.file_path)) << report.file_path.value(); } TEST_F(CrashReportDatabaseTest, ReportRemoved) { - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), CrashReportDatabase::kNoError); - EXPECT_TRUE(FileExists(new_report->path)) << new_report->path.value(); + UUID uuid; - EXPECT_EQ(db()->FinishedWritingCrashReport(new_report, &uuid), + EXPECT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), CrashReportDatabase::kNoError); CrashReportDatabase::Report report; EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kNoError); -#if defined(OS_WIN) - EXPECT_EQ(_wunlink(report.file_path.value().c_str()), 0); -#else - EXPECT_EQ(unlink(report.file_path.value().c_str()), 0) - << ErrnoMessage("unlink"); -#endif + EXPECT_TRUE(LoggingRemoveFile(report.file_path)); EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), CrashReportDatabase::kReportNotFound); @@ -642,21 +629,21 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) { ASSERT_EQ(pending_reports.size(), 2u); // Check individual reports. - const CrashReportDatabase::Report* expicitly_requested_report; + const CrashReportDatabase::Report* explicitly_requested_report; const CrashReportDatabase::Report* pending_report; if (pending_reports[0].uuid == report_0_uuid) { pending_report = &pending_reports[0]; - expicitly_requested_report = &pending_reports[1]; + explicitly_requested_report = &pending_reports[1]; } else { pending_report = &pending_reports[1]; - expicitly_requested_report = &pending_reports[0]; + explicitly_requested_report = &pending_reports[0]; } EXPECT_EQ(pending_report->uuid, report_0_uuid); EXPECT_FALSE(pending_report->upload_explicitly_requested); - EXPECT_EQ(expicitly_requested_report->uuid, report_1_uuid); - EXPECT_TRUE(expicitly_requested_report->upload_explicitly_requested); + EXPECT_EQ(explicitly_requested_report->uuid, report_1_uuid); + EXPECT_TRUE(explicitly_requested_report->upload_explicitly_requested); // Explicitly requested reports will not have upload_explicitly_requested bit // after getting skipped. @@ -683,6 +670,171 @@ TEST_F(CrashReportDatabaseTest, RequestUpload) { CrashReportDatabase::kCannotRequestUpload); } +TEST_F(CrashReportDatabaseTest, Attachments) { +#if defined(OS_MACOSX) || defined(OS_WIN) + // Attachments aren't supported on Mac and Windows yet. + DISABLED_TEST(); +#else + std::unique_ptr new_report; + ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), + CrashReportDatabase::kNoError); + + FileWriter* attach_some_file = new_report->AddAttachment("some_file"); + ASSERT_NE(attach_some_file, nullptr); + static constexpr char test_data[] = "test data"; + attach_some_file->Write(test_data, sizeof(test_data)); + + FileWriter* failed_attach = new_report->AddAttachment("not/a valid fi!e"); + EXPECT_EQ(failed_attach, nullptr); + + UUID expect_uuid = new_report->ReportID(); + UUID uuid; + ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), + CrashReportDatabase::kNoError); + EXPECT_EQ(uuid, expect_uuid); + + CrashReportDatabase::Report report; + EXPECT_EQ(db()->LookUpCrashReport(uuid, &report), + CrashReportDatabase::kNoError); + ExpectPreparedCrashReport(report); + + std::vector reports; + EXPECT_EQ(db()->GetPendingReports(&reports), CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 1u); + EXPECT_EQ(reports[0].uuid, report.uuid); + + std::unique_ptr upload_report; + ASSERT_EQ(db()->GetReportForUploading(reports[0].uuid, &upload_report), + CrashReportDatabase::kNoError); + std::map result_attachments = + upload_report->GetAttachments(); + EXPECT_EQ(result_attachments.size(), 1u); + EXPECT_NE(result_attachments.find("some_file"), result_attachments.end()); + char result_buffer[sizeof(test_data)]; + result_attachments["some_file"]->Read(result_buffer, sizeof(result_buffer)); + EXPECT_EQ(memcmp(test_data, result_buffer, sizeof(test_data)), 0); +#endif +} + +TEST_F(CrashReportDatabaseTest, OrphanedAttachments) { +#if defined(OS_MACOSX) || defined(OS_WIN) + // Attachments aren't supported on Mac and Windows yet. + DISABLED_TEST(); +#else + // TODO: This is using paths that are specific to the generic implementation + // and will need to be generalized for Mac and Windows. + std::unique_ptr new_report; + ASSERT_EQ(db()->PrepareNewCrashReport(&new_report), + CrashReportDatabase::kNoError); + + FileWriter* file1 = new_report->AddAttachment("file1"); + ASSERT_NE(file1, nullptr); + FileWriter* file2 = new_report->AddAttachment("file2"); + ASSERT_NE(file2, nullptr); + + UUID expect_uuid = new_report->ReportID(); + UUID uuid; + ASSERT_EQ(db()->FinishedWritingCrashReport(std::move(new_report), &uuid), + CrashReportDatabase::kNoError); + EXPECT_EQ(uuid, expect_uuid); + + CrashReportDatabase::Report report; + ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), + CrashReportDatabase::kNoError); + + ASSERT_TRUE(LoggingRemoveFile(report.file_path)); + + ASSERT_TRUE(LoggingRemoveFile(base::FilePath( + report.file_path.RemoveFinalExtension().value() + ".meta"))); + + ASSERT_EQ(db()->LookUpCrashReport(uuid, &report), + CrashReportDatabase::kReportNotFound); + + base::FilePath report_attachments_dir( + path().Append("attachments").Append(uuid.ToString())); + base::FilePath file_path1(report_attachments_dir.Append("file1")); + base::FilePath file_path2(report_attachments_dir.Append("file2")); + EXPECT_TRUE(FileExists(file_path1)); + EXPECT_TRUE(FileExists(file_path1)); + + EXPECT_EQ(db()->CleanDatabase(0), 0); + + EXPECT_FALSE(FileExists(file_path1)); + EXPECT_FALSE(FileExists(file_path2)); + EXPECT_FALSE(FileExists(report_attachments_dir)); +#endif +} + +// This test uses knowledge of the database format to break it, so it only +// applies to the unfified database implementation. +#if !defined(OS_MACOSX) && !defined(OS_WIN) +TEST_F(CrashReportDatabaseTest, CleanBrokenDatabase) { + // Remove report files if metadata goes missing. + CrashReportDatabase::Report report; + ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); + + const base::FilePath metadata( + report.file_path.RemoveFinalExtension().value() + + FILE_PATH_LITERAL(".meta")); + ASSERT_TRUE(PathExists(report.file_path)); + ASSERT_TRUE(PathExists(metadata)); + + ASSERT_TRUE(LoggingRemoveFile(metadata)); + EXPECT_EQ(db()->CleanDatabase(0), 1); + + EXPECT_FALSE(PathExists(report.file_path)); + EXPECT_FALSE(PathExists(metadata)); + + // Remove metadata files if reports go missing. + ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); + const base::FilePath metadata2( + report.file_path.RemoveFinalExtension().value() + + FILE_PATH_LITERAL(".meta")); + ASSERT_TRUE(PathExists(report.file_path)); + ASSERT_TRUE(PathExists(metadata2)); + + ASSERT_TRUE(LoggingRemoveFile(report.file_path)); + EXPECT_EQ(db()->CleanDatabase(0), 1); + + EXPECT_FALSE(PathExists(report.file_path)); + EXPECT_FALSE(PathExists(metadata2)); + + // Remove stale new files. + std::unique_ptr new_report; + EXPECT_EQ(db()->PrepareNewCrashReport(&new_report), + CrashReportDatabase::kNoError); + new_report->Writer()->Close(); + EXPECT_EQ(db()->CleanDatabase(0), 1); + + // Remove stale lock files and their associated reports. + ASSERT_NO_FATAL_FAILURE(CreateCrashReport(&report)); + const base::FilePath metadata3( + report.file_path.RemoveFinalExtension().value() + + FILE_PATH_LITERAL(".meta")); + ASSERT_TRUE(PathExists(report.file_path)); + ASSERT_TRUE(PathExists(metadata3)); + + const base::FilePath lockpath( + report.file_path.RemoveFinalExtension().value() + + FILE_PATH_LITERAL(".lock")); + ScopedFileHandle handle(LoggingOpenFileForWrite( + lockpath, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); + ASSERT_TRUE(handle.is_valid()); + + time_t expired_timestamp = time(nullptr) - 60 * 60 * 24 * 3; + + ASSERT_TRUE(LoggingWriteFile( + handle.get(), &expired_timestamp, sizeof(expired_timestamp))); + ASSERT_TRUE(LoggingCloseFile(handle.get())); + ignore_result(handle.release()); + + EXPECT_EQ(db()->CleanDatabase(0), 1); + + EXPECT_FALSE(PathExists(report.file_path)); + EXPECT_FALSE(PathExists(metadata3)); +} +#endif // !OS_MACOSX && !OS_WIN + } // namespace } // namespace test } // namespace crashpad diff --git a/client/crash_report_database_win.cc b/client/crash_report_database_win.cc index 538eff5f..e19e6058 100644 --- a/client/crash_report_database_win.cc +++ b/client/crash_report_database_win.cc @@ -29,6 +29,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "client/settings.h" +#include "util/misc/implicit_cast.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/metrics.h" @@ -571,6 +572,8 @@ bool CreateDirectoryIfNecessary(const base::FilePath& path) { return EnsureDirectory(path); } +} // namespace + // CrashReportDatabaseWin ------------------------------------------------------ class CrashReportDatabaseWin : public CrashReportDatabase { @@ -582,24 +585,27 @@ class CrashReportDatabaseWin : public CrashReportDatabase { // CrashReportDatabase: Settings* GetSettings() override; - OperationStatus PrepareNewCrashReport(NewReport** report) override; - OperationStatus FinishedWritingCrashReport(NewReport* report, + OperationStatus PrepareNewCrashReport( + std::unique_ptr* report) override; + OperationStatus FinishedWritingCrashReport(std::unique_ptr report, UUID* uuid) override; - OperationStatus ErrorWritingCrashReport(NewReport* report) override; OperationStatus LookUpCrashReport(const UUID& uuid, Report* report) override; OperationStatus GetPendingReports(std::vector* reports) override; OperationStatus GetCompletedReports(std::vector* reports) override; - OperationStatus GetReportForUploading(const UUID& uuid, - const Report** report) override; - OperationStatus RecordUploadAttempt(const Report* report, - bool successful, - const std::string& id) override; + OperationStatus GetReportForUploading( + const UUID& uuid, + std::unique_ptr* report) override; OperationStatus SkipReportUpload(const UUID& uuid, Metrics::CrashSkippedReason reason) override; OperationStatus DeleteReport(const UUID& uuid) override; OperationStatus RequestUpload(const UUID& uuid) override; private: + // CrashReportDatabase: + OperationStatus RecordUploadAttempt(UploadReport* report, + bool successful, + const std::string& id) override; + std::unique_ptr AcquireMetadata(); base::FilePath base_dir_; @@ -609,13 +615,19 @@ class CrashReportDatabaseWin : public CrashReportDatabase { DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseWin); }; -CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path) - : CrashReportDatabase(), - base_dir_(path), - settings_(base_dir_.Append(kSettings)), - initialized_() { +FileWriter* CrashReportDatabase::NewReport::AddAttachment( + const std::string& name) { + // Attachments aren't implemented in the Windows database yet. + return nullptr; } +void CrashReportDatabase::UploadReport::InitializeAttachments() { + // Attachments aren't implemented in the Windows database yet. +} + +CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path) + : CrashReportDatabase(), base_dir_(path), settings_(), initialized_() {} + CrashReportDatabaseWin::~CrashReportDatabaseWin() { } @@ -634,7 +646,7 @@ bool CrashReportDatabaseWin::Initialize(bool may_create) { if (!CreateDirectoryIfNecessary(base_dir_.Append(kReportsDirectory))) return false; - if (!settings_.Initialize()) + if (!settings_.Initialize(base_dir_.Append(kSettings))) return false; INITIALIZATION_STATE_SET_VALID(initialized_); @@ -647,67 +659,39 @@ Settings* CrashReportDatabaseWin::GetSettings() { } OperationStatus CrashReportDatabaseWin::PrepareNewCrashReport( - NewReport** report) { + std::unique_ptr* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr new_report(new NewReport()); - if (!new_report->uuid.InitializeWithNew()) - return kFileSystemError; - new_report->path = base_dir_.Append(kReportsDirectory) - .Append(new_report->uuid.ToString16() + L"." + - kCrashReportFileExtension); - new_report->handle = LoggingOpenFileForWrite(new_report->path, - FileWriteMode::kCreateOrFail, - FilePermissions::kOwnerOnly); - if (new_report->handle == INVALID_HANDLE_VALUE) + if (!new_report->Initialize(this, + base_dir_.Append(kReportsDirectory), + std::wstring(L".") + kCrashReportFileExtension)) { return kFileSystemError; + } - *report = new_report.release(); + report->reset(new_report.release()); return kNoError; } OperationStatus CrashReportDatabaseWin::FinishedWritingCrashReport( - NewReport* report, + std::unique_ptr report, UUID* uuid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - // Take ownership of the report. - std::unique_ptr scoped_report(report); - // Take ownership of the file handle. - ScopedFileHandle handle(report->handle); - std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; - metadata->AddNewRecord(ReportDisk(scoped_report->uuid, - scoped_report->path, + metadata->AddNewRecord(ReportDisk(report->ReportID(), + report->file_remover_.get(), time(nullptr), ReportState::kPending)); - *uuid = scoped_report->uuid; + + ignore_result(report->file_remover_.release()); + + *uuid = report->ReportID(); Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated); - Metrics::CrashReportSize(handle.get()); - - return kNoError; -} - -OperationStatus CrashReportDatabaseWin::ErrorWritingCrashReport( - NewReport* report) { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - - // Take ownership of the report. - std::unique_ptr scoped_report(report); - - // Close the outstanding handle. - LoggingCloseFile(report->handle); - - // We failed to write, so remove the dump file. There's no entry in the - // metadata table yet. - if (!DeleteFile(scoped_report->path.value().c_str())) { - PLOG(ERROR) << "DeleteFile " - << base::UTF16ToUTF8(scoped_report->path.value()); - return kFileSystemError; - } + Metrics::CrashReportSize(report->Writer()->Seek(0, SEEK_END)); return kNoError; } @@ -747,44 +731,38 @@ OperationStatus CrashReportDatabaseWin::GetCompletedReports( OperationStatus CrashReportDatabaseWin::GetReportForUploading( const UUID& uuid, - const Report** report) { + std::unique_ptr* report) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; - // TODO(scottmg): After returning this report to the client, there is no way - // to reap this report if the uploader fails to call RecordUploadAttempt() or - // SkipReportUpload() (if it crashed or was otherwise buggy). To resolve this, - // one possibility would be to change the interface to be FileHandle based, so - // that instead of giving the file_path back to the client and changing state - // to kUploading, we return an exclusive access handle, and use that as the - // signal that the upload is pending, rather than an update to state in the - // metadata. Alternatively, there could be a "garbage collection" at startup - // where any reports that are orphaned in the kUploading state are either - // reset to kPending to retry, or discarded. + ReportDisk* report_disk; OperationStatus os = metadata->FindSingleReportAndMarkDirty( uuid, ReportState::kPending, &report_disk); if (os == kNoError) { report_disk->state = ReportState::kUploading; - // Create a copy for passing back to client. This will be freed in - // RecordUploadAttempt. - *report = new Report(*report_disk); + auto upload_report = std::make_unique(); + *implicit_cast(upload_report.get()) = *report_disk; + + if (!upload_report->Initialize(upload_report->file_path, this)) { + return kFileSystemError; + } + + report->reset(upload_report.release()); } return os; } OperationStatus CrashReportDatabaseWin::RecordUploadAttempt( - const Report* report, + UploadReport* report, bool successful, const std::string& id) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); Metrics::CrashUploadAttempted(successful); - // Take ownership, allocated in GetReportForUploading. - std::unique_ptr upload_report(report); std::unique_ptr metadata(AcquireMetadata()); if (!metadata) return kDatabaseError; @@ -903,8 +881,6 @@ OperationStatus CrashReportDatabaseWin::RequestUpload(const UUID& uuid) { return kNoError; } -} // namespace - // static std::unique_ptr CrashReportDatabase::Initialize( const base::FilePath& path) { diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 7799bd9c..ae409d43 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -24,12 +24,16 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "build/build_config.h" +#include "util/misc/capture_context.h" #if defined(OS_MACOSX) #include "base/mac/scoped_mach_port.h" #elif defined(OS_WIN) #include #include "util/win/scoped_handle.h" +#elif defined(OS_LINUX) || defined(OS_ANDROID) +#include +#include #endif namespace crashpad { @@ -71,6 +75,9 @@ class CrashpadClient { //! Crashpad. Optionally, use WaitForHandlerStart() to join with the //! background thread and retrieve the status of handler startup. //! + //! On Fuchsia, this method binds to the exception port of the current default + //! job, and starts a Crashpad handler to monitor that port. + //! //! \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. @@ -105,6 +112,105 @@ class CrashpadClient { bool restartable, bool asynchronous_start); +#if defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN + //! \brief Installs a signal handler to launch a handler process in reponse to + //! a crash. + //! + //! The handler process will create a crash dump for this process and exit. + //! + //! \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. + //! \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. + static bool StartHandlerAtCrash( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments); + + //! \brief Starts a handler process with an initial client. + //! + //! This method allows a process to launch the handler process on behalf of + //! another process. + //! + //! \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. + //! \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 StartHandlerForClient( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket); + + //! \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. + //! + //! TODO(jperaza): Floating point information in the context is zeroed out + //! until CaptureContext() supports collecting that information. + //! + //! \param[in] context A NativeCPUContext, generally captured by + //! CaptureContext() or similar. + static void DumpWithoutCrash(NativeCPUContext* context); + + //! \brief The type for custom handlers installed by clients. + using FirstChanceHandler = bool (*)(int, siginfo_t*, ucontext_t*); + + //! \brief Installs a custom crash signal handler which runs before the + //! currently installed Crashpad handler. + //! + //! Handling signals appropriately can be tricky and use of this method + //! should be avoided, if possible. + //! + //! A handler must have already been installed before calling this method. + //! + //! The custom handler runs in a signal handler context and must be safe for + //! that purpose. + //! + //! If the custom handler returns `true`, the signal is considered handled and + //! the signal handler returns. Otherwise, the currently installed Crashpad + //! signal handler is run. + //! + //! \param[in] handler The custom crash signal handler to install. + static void SetFirstChanceExceptionHandler(FirstChanceHandler handler); + +#endif // OS_LINUX || OS_ANDROID || DOXYGEN + #if defined(OS_MACOSX) || DOXYGEN //! \brief Sets the process’ crash handler to a Mach service registered with //! the bootstrap server. @@ -239,9 +345,9 @@ class CrashpadClient { //! used as the exception code in the exception record. //! //! \return `true` if the exception was triggered successfully. - bool DumpAndCrashTargetProcess(HANDLE process, - HANDLE blame_thread, - DWORD exception_code) const; + static bool DumpAndCrashTargetProcess(HANDLE process, + HANDLE blame_thread, + DWORD exception_code); enum : uint32_t { //! \brief The exception code (roughly "Client called") used when diff --git a/client/crashpad_client_fuchsia.cc b/client/crashpad_client_fuchsia.cc new file mode 100644 index 00000000..0d1b65bf --- /dev/null +++ b/client/crashpad_client_fuchsia.cc @@ -0,0 +1,113 @@ +// 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 "client/crashpad_client.h" + +#include +#include +#include + +#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 { + +CrashpadClient::CrashpadClient() {} + +CrashpadClient::~CrashpadClient() {} + +bool CrashpadClient::StartHandler( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + bool restartable, + bool asynchronous_start) { + 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 argv_strings = BuildHandlerArgvStrings( + handler, database, metrics_dir, url, annotations, arguments); + + std::vector argv; + ConvertArgvStrings(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); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_handle_duplicate"; + return false; + } + actions[1].h.handle = exception_port.release(); + + char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; + zx_handle_t child_raw; + // 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. + status = fdio_spawn_etc(ZX_HANDLE_INVALID, + FDIO_SPAWN_CLONE_ALL, + argv[0], + argv.data(), + nullptr, + kActionCount, + actions, + &child_raw, + error_message); + base::ScopedZxHandle child(child_raw); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "fdio_spawn_etc: " << error_message; + return false; + } + + return true; +} + +} // namespace crashpad diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc new file mode 100644 index 00000000..54fe268a --- /dev/null +++ b/client/crashpad_client_linux.cc @@ -0,0 +1,234 @@ +// 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 "client/crashpad_client.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "client/client_argv_handling.h" +#include "util/file/file_io.h" +#include "util/linux/exception_handler_client.h" +#include "util/linux/exception_information.h" +#include "util/linux/scoped_pr_set_ptracer.h" +#include "util/misc/from_pointer_cast.h" +#include "util/posix/double_fork_and_exec.h" +#include "util/posix/signals.h" + +namespace crashpad { + +namespace { + +std::string FormatArgumentInt(const std::string& name, int value) { + return base::StringPrintf("--%s=%d", name.c_str(), value); +} + +std::string FormatArgumentAddress(const std::string& name, void* addr) { + return base::StringPrintf("--%s=%p", name.c_str(), addr); +} + +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; + + void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) { + first_chance_handler_ = handler; + } + + protected: + SignalHandler() = default; + ~SignalHandler() = default; + + 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* 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(context))) { + return true; + } + + exception_information_.siginfo_address = + FromPointerCast( + siginfo); + exception_information_.context_address = + FromPointerCast( + context); + exception_information_.thread_id = syscall(SYS_gettid); + + ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ false); + + pid_t pid = fork(); + if (pid < 0) { + return false; + } + if (pid == 0) { + execv(argv_[0], const_cast(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: + LaunchAtCrashHandler() = default; + + ~LaunchAtCrashHandler() = delete; + + static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + auto state = Get(); + state->HandleCrashFatal(signo, siginfo, context); + } + + std::vector argv_strings_; + std::vector argv_; + ExceptionInformation exception_information_; + + 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; + +} // namespace + +CrashpadClient::CrashpadClient() {} + +CrashpadClient::~CrashpadClient() {} + +bool CrashpadClient::StartHandler( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& 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; +} + +// static +bool CrashpadClient::StartHandlerAtCrash( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments) { + std::vector argv = BuildHandlerArgvStrings( + 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; +} + +// static +bool CrashpadClient::StartHandlerForClient( + const base::FilePath& handler, + const base::FilePath& database, + const base::FilePath& metrics_dir, + const std::string& url, + const std::map& annotations, + const std::vector& arguments, + int socket) { + std::vector argv = BuildHandlerArgvStrings( + handler, database, metrics_dir, url, annotations, arguments); + + argv.push_back(FormatArgumentInt("initial-client", socket)); + + return DoubleForkAndExec(argv, socket, true, nullptr); +} + +// static +void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { + DCHECK(g_crash_handler); + +#if defined(ARCH_CPU_ARMEL) + memset(context->uc_regspace, 0, sizeof(context->uc_regspace)); +#elif defined(ARCH_CPU_ARM64) + memset(context->uc_mcontext.__reserved, + 0, + sizeof(context->uc_mcontext.__reserved)); +#endif + + siginfo_t siginfo; + siginfo.si_signo = Signals::kSimulatedSigno; + siginfo.si_errno = 0; + siginfo.si_code = 0; + g_crash_handler->HandleCrashNonFatal( + siginfo.si_signo, &siginfo, reinterpret_cast(context)); +} + +// static +void CrashpadClient::SetFirstChanceExceptionHandler( + FirstChanceHandler handler) { + DCHECK(g_crash_handler); + g_crash_handler->SetFirstChanceHandler(handler); +} + +} // namespace crashpad diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc new file mode 100644 index 00000000..4c5bf4de --- /dev/null +++ b/client/crashpad_client_linux_test.cc @@ -0,0 +1,395 @@ +// 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 "client/crashpad_client.h" + +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "client/annotation.h" +#include "client/annotation_list.h" +#include "client/crash_report_database.h" +#include "client/simulate_crash.h" +#include "gtest/gtest.h" +#include "snapshot/annotation_snapshot.h" +#include "snapshot/minidump/process_snapshot_minidump.h" +#include "snapshot/sanitized/sanitization_information.h" +#include "test/multiprocess.h" +#include "test/multiprocess_exec.h" +#include "test/scoped_temp_dir.h" +#include "test/test_paths.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/misc/address_types.h" +#include "util/misc/from_pointer_cast.h" +#include "util/posix/signals.h" + +namespace crashpad { +namespace test { +namespace { + +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::vector())); + + auto database = + CrashReportDatabase::InitializeWithoutCreating(temp_dir.path()); + ASSERT_TRUE(database); + + { + CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully); + + CRASHPAD_SIMULATE_CRASH(); + + std::vector 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 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); + } +} + +constexpr char kTestAnnotationName[] = "name_of_annotation"; +constexpr char kTestAnnotationValue[] = "value_of_annotation"; + +void ValidateDump(const CrashReportDatabase::UploadReport* report) { + ProcessSnapshotMinidump minidump_snapshot; + ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader())); + + for (const ModuleSnapshot* module : minidump_snapshot.Modules()) { + for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { + if (static_cast(annotation.type) != + Annotation::Type::kString) { + continue; + } + + if (annotation.name == kTestAnnotationName) { + std::string value( + reinterpret_cast(annotation.value.data()), + annotation.value.size()); + EXPECT_EQ(value, kTestAnnotationValue); + return; + } + } + } + ADD_FAILURE(); +} + +CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + + VMSize temp_dir_length; + CheckedReadFileExactly(in, &temp_dir_length, sizeof(temp_dir_length)); + + std::string temp_dir(temp_dir_length, '\0'); + CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); + + base::FilePath handler_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("crashpad_handler")); + + crashpad::AnnotationList::Register(); + + static StringAnnotation<32> test_annotation(kTestAnnotationName); + test_annotation.Set(kTestAnnotationValue); + + crashpad::CrashpadClient client; + if (!client.StartHandlerAtCrash(handler_path, + base::FilePath(temp_dir), + base::FilePath(), + "", + std::map(), + std::vector())) { + return EXIT_FAILURE; + } + + __builtin_trap(); + + NOTREACHED(); + return EXIT_SUCCESS; +} + +class StartHandlerAtCrashTest : public MultiprocessExec { + public: + StartHandlerAtCrashTest() : MultiprocessExec() { + SetChildTestMainFunction("StartHandlerAtCrashChild"); + SetExpectedChildTerminationBuiltinTrap(); + } + + private: + void MultiprocessParent() override { + ScopedTempDir temp_dir; + VMSize temp_dir_length = temp_dir.path().value().size(); + ASSERT_TRUE(LoggingWriteFile( + WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); + ASSERT_TRUE(LoggingWriteFile( + WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); + + // Wait for child to finish. + CheckedReadFileAtEOF(ReadPipeHandle()); + + auto database = CrashReportDatabase::Initialize(temp_dir.path()); + ASSERT_TRUE(database); + + std::vector reports; + ASSERT_EQ(database->GetCompletedReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 0u); + + reports.clear(); + ASSERT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 1u); + + std::unique_ptr report; + ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), + CrashReportDatabase::kNoError); + ValidateDump(report.get()); + } + + DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); +}; + +TEST(CrashpadClient, StartHandlerAtCrash) { + StartHandlerAtCrashTest test; + test.Run(); +} + +// Test state for starting the handler for another process. +class StartHandlerForClientTest { + public: + StartHandlerForClientTest() = default; + ~StartHandlerForClientTest() = default; + + 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; + } + + bool StartHandlerOnDemand() { + char c; + if (!LoggingReadFileExactly(server_sock_.get(), &c, sizeof(c))) { + ADD_FAILURE(); + return false; + } + + base::FilePath handler_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("crashpad_handler")); + + CrashpadClient client; + if (!client.StartHandlerForClient(handler_path, + temp_dir_.path(), + base::FilePath(), + "", + std::map(), + std::vector(), + server_sock_.get())) { + ADD_FAILURE(); + return false; + } + + return true; + } + + void ExpectReport() { + auto database = + CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path()); + ASSERT_TRUE(database); + + std::vector reports; + ASSERT_EQ(database->GetCompletedReports(&reports), + CrashReportDatabase::kNoError); + EXPECT_EQ(reports.size(), 0u); + + reports.clear(); + ASSERT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + if (sanitize_) { + EXPECT_EQ(reports.size(), 0u); + } else { + EXPECT_EQ(reports.size(), 1u); + } + } + + bool InstallHandler() { + auto signal_handler = SandboxedHandler::Get(); + return signal_handler->Initialize(client_sock_.get(), sanitize_); + } + + private: + // A signal handler that defers handler process startup to another, presumably + // more privileged, process. + class SandboxedHandler { + public: + static SandboxedHandler* Get() { + static SandboxedHandler* instance = new SandboxedHandler(); + return instance; + } + + bool Initialize(FileHandle client_sock, bool sanitize) { + client_sock_ = client_sock; + sanitize_ = sanitize; + return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); + } + + private: + SandboxedHandler() = default; + ~SandboxedHandler() = delete; + + static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + auto state = Get(); + + char c; + CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); + + ExceptionInformation exception_information; + exception_information.siginfo_address = + FromPointerCast( + siginfo); + exception_information.context_address = + FromPointerCast( + context); + exception_information.thread_id = syscall(SYS_gettid); + + ClientInformation info; + info.exception_information_address = + FromPointerCast( + &exception_information); + + SanitizationInformation sanitization_info = {}; + if (state->sanitize_) { + info.sanitization_information_address = + FromPointerCast(&sanitization_info); + // Target a non-module address to prevent a crash dump. + sanitization_info.target_module_address = + FromPointerCast(&sanitization_info); + } + + ExceptionHandlerClient handler_client(state->client_sock_); + CHECK_EQ(handler_client.RequestCrashDump(info), 0); + + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + } + + FileHandle client_sock_; + bool sanitize_; + + DISALLOW_COPY_AND_ASSIGN(SandboxedHandler); + }; + + ScopedTempDir temp_dir_; + ScopedFileHandle client_sock_; + ScopedFileHandle server_sock_; + bool sanitize_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForClientTest); +}; + +// Tests starting the handler for a child process. +class StartHandlerForChildTest : public Multiprocess { + public: + StartHandlerForChildTest() = default; + ~StartHandlerForChildTest() = default; + + bool Initialize(bool sanitize) { + SetExpectedChildTerminationBuiltinTrap(); + return test_state_.Initialize(sanitize); + } + + private: + void MultiprocessParent() { + ASSERT_TRUE(test_state_.StartHandlerOnDemand()); + + // Wait for chlid to finish. + CheckedReadFileAtEOF(ReadPipeHandle()); + + test_state_.ExpectReport(); + } + + void MultiprocessChild() { + CHECK(test_state_.InstallHandler()); + + __builtin_trap(); + + NOTREACHED(); + } + + StartHandlerForClientTest test_state_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForChildTest); +}; + +TEST(CrashpadClient, StartHandlerForChild) { + StartHandlerForChildTest test; + ASSERT_TRUE(test.Initialize(/* sanitize= */ false)); + test.Run(); +} + +TEST(CrashpadClient, SanitizedChild) { + StartHandlerForChildTest test; + ASSERT_TRUE(test.Initialize(/* sanitize= */ true)); + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index a9e4aca4..4a408206 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -18,15 +18,12 @@ #include #include #include -#include -#include #include #include #include "base/logging.h" #include "base/mac/mach_logging.h" -#include "base/posix/eintr_wrapper.h" #include "base/strings/stringprintf.h" #include "util/mac/mac_util.h" #include "util/mach/child_port_handshake.h" @@ -36,7 +33,7 @@ #include "util/mach/notify_server.h" #include "util/misc/clock.h" #include "util/misc/implicit_cast.h" -#include "util/posix/close_multiple.h" +#include "util/posix/double_fork_and_exec.h" namespace crashpad { @@ -305,9 +302,6 @@ class HandlerStarter final : public NotifyServer::DefaultInterface { handler_restarter->last_start_time_ = ClockMonotonicNanoseconds(); } - // Set up the arguments for execve() first. These aren’t needed until - // execve() is called, but it’s dangerous to do this in a child process - // after fork(). ChildPortHandshake child_port_handshake; base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD(); @@ -337,110 +331,23 @@ class HandlerStarter final : public NotifyServer::DefaultInterface { } argv.push_back(FormatArgumentInt("handshake-fd", server_write_fd.get())); - const char* handler_c = handler.value().c_str(); - - // argv_c contains const char* pointers and is terminated by nullptr. argv - // is required because the pointers in argv_c need to point somewhere, and - // they can’t point to temporaries such as those returned by - // FormatArgumentString(). - std::vector argv_c; - argv_c.reserve(argv.size() + 1); - for (const std::string& argument : argv) { - argv_c.push_back(argument.c_str()); - } - argv_c.push_back(nullptr); - - // Double-fork(). The three processes involved are parent, child, and - // grandchild. The grandchild will become the handler process. The child - // exits immediately after spawning the grandchild, so the grandchild - // becomes an orphan and its parent process ID becomes 1. This relieves the - // parent and child of the responsibility for reaping the grandchild with - // waitpid() or similar. The handler process is expected to outlive the - // parent process, so the parent shouldn’t be concerned with reaping it. - // This approach means that accidental early termination of the handler - // process will not result in a zombie process. - pid_t pid = fork(); - if (pid < 0) { - PLOG(ERROR) << "fork"; + // When restarting, reset the system default crash handler first. Otherwise, + // the crash exception port in the handler will have been inherited from + // this parent process, which was probably using the exception server now + // being restarted. The handler can’t monitor itself for its own crashes via + // this interface. + if (!DoubleForkAndExec( + argv, + server_write_fd.get(), + true, + restart ? CrashpadClient::UseSystemDefaultHandler : nullptr)) { return false; } - if (pid == 0) { - // Child process. - - if (restart) { - // When restarting, reset the system default crash handler first. - // Otherwise, the crash exception port here will have been inherited - // from the parent process, which was probably using the exception - // server now being restarted. The handler can’t monitor itself for its - // own crashes via this interface. - CrashpadClient::UseSystemDefaultHandler(); - } - - // Call setsid(), creating a new process group and a new session, both led - // by this process. The new process group has no controlling terminal. - // This disconnects it from signals generated by the parent process’ - // terminal. - // - // setsid() is done in the child instead of the grandchild so that the - // grandchild will not be a session leader. If it were a session leader, - // an accidental open() of a terminal device without O_NOCTTY would make - // that terminal the controlling terminal. - // - // It’s not desirable for the handler to have a controlling terminal. The - // handler monitors clients on its own and manages its own lifetime, - // exiting when it loses all clients and when it deems it appropraite to - // do so. It may serve clients in different process groups or sessions - // than its original client, and receiving signals intended for its - // original client’s process group could be harmful in that case. - PCHECK(setsid() != -1) << "setsid"; - - pid = fork(); - if (pid < 0) { - PLOG(FATAL) << "fork"; - } - - if (pid > 0) { - // Child process. - - // _exit() instead of exit(), because fork() was called. - _exit(EXIT_SUCCESS); - } - - // Grandchild process. - - CloseMultipleNowOrOnExec(STDERR_FILENO + 1, server_write_fd.get()); - - // &argv_c[0] is a pointer to a pointer to const char data, but because of - // how C (not C++) works, execvp() wants a pointer to a const pointer to - // char data. It modifies neither the data nor the pointers, so the - // const_cast is safe. - execvp(handler_c, const_cast(&argv_c[0])); - PLOG(FATAL) << "execvp " << handler_c; - } - - // Parent process. - // Close the write side of the pipe, so that the handler process is the only // process that can write to it. server_write_fd.reset(); - // waitpid() for the child, so that it does not become a zombie process. The - // child normally exits quickly. - int status; - pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0)); - PCHECK(wait_pid != -1) << "waitpid"; - DCHECK_EQ(wait_pid, pid); - - if (WIFSIGNALED(status)) { - LOG(WARNING) << "intermediate process: signal " << WTERMSIG(status); - } else if (!WIFEXITED(status)) { - DLOG(WARNING) << "intermediate process: unknown termination " << status; - } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { - LOG(WARNING) << "intermediate process: exit status " - << WEXITSTATUS(status); - } - // Rendezvous with the handler running in the grandchild process. if (!child_port_handshake.RunClient(receive_right.get(), MACH_MSG_TYPE_MOVE_RECEIVE)) { diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 0da0c228..8ceded33 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -30,10 +30,10 @@ #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "util/file/file_io.h" +#include "util/misc/capture_context.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/random_string.h" #include "util/win/address_types.h" -#include "util/win/capture_context.h" #include "util/win/command_line.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/get_function.h" @@ -750,9 +750,9 @@ void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { // We include a fake exception and use a code of '0x517a7ed' (something like // "simulated") so that it's relatively obvious in windbg that it's not // actually an exception. Most values in - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082.aspx have - // some of the top nibble set, so we make sure to pick a value that doesn't, - // so as to be unlikely to conflict. + // https://msdn.microsoft.com/library/aa363082.aspx have some of the top + // nibble set, so we make sure to pick a value that doesn't, so as to be + // unlikely to conflict. constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed; EXCEPTION_RECORD record = {}; record.ExceptionCode = kSimulatedExceptionCode; @@ -790,9 +790,10 @@ void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { UnhandledExceptionHandler(exception_pointers); } +// static bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process, HANDLE blame_thread, - DWORD exception_code) const { + DWORD exception_code) { // Confirm we're on Vista or later. const DWORD version = GetVersion(); const DWORD major_version = LOBYTE(LOWORD(version)); diff --git a/client/crashpad_client_win_test.cc b/client/crashpad_client_win_test.cc index fb47ad72..99778baa 100644 --- a/client/crashpad_client_win_test.cc +++ b/client/crashpad_client_win_test.cc @@ -126,7 +126,13 @@ class HandlerLaunchFailureCrash : public WinMultiprocess { } }; -TEST(CrashpadClient, HandlerLaunchFailureCrash) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/845011 +#define MAYBE_HandlerLaunchFailureCrash DISABLED_HandlerLaunchFailureCrash +#else +#define MAYBE_HandlerLaunchFailureCrash HandlerLaunchFailureCrash +#endif +TEST(CrashpadClient, MAYBE_HandlerLaunchFailureCrash) { WinMultiprocess::Run(); } @@ -150,7 +156,14 @@ class HandlerLaunchFailureDumpAndCrash : public WinMultiprocess { } }; -TEST(CrashpadClient, HandlerLaunchFailureDumpAndCrash) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/845011 +#define MAYBE_HandlerLaunchFailureDumpAndCrash \ + DISABLED_HandlerLaunchFailureDumpAndCrash +#else +#define MAYBE_HandlerLaunchFailureDumpAndCrash HandlerLaunchFailureDumpAndCrash +#endif +TEST(CrashpadClient, MAYBE_HandlerLaunchFailureDumpAndCrash) { WinMultiprocess::Run(); } diff --git a/client/crashpad_info.cc b/client/crashpad_info.cc index 9452382b..7c1316e8 100644 --- a/client/crashpad_info.cc +++ b/client/crashpad_info.cc @@ -25,6 +25,11 @@ namespace { +// Don’t change this when simply adding fields. Readers will size-check the +// structure and ignore fields they’re aware of when not present, as well as +// fields they’re not aware of. Only change this when introducing an +// incompatible layout, with the understanding that existing readers will not +// understand new versions. constexpr uint32_t kCrashpadInfoVersion = 1; } // namespace @@ -46,18 +51,15 @@ static_assert(std::is_standard_layout::value, // This may result in a static module initializer in debug-mode builds, but // because it’s POD, no code should need to run to initialize this under // release-mode optimization. + #if defined(OS_POSIX) __attribute__(( +#if defined(OS_MACOSX) // Put the structure in a well-known section name where it can be easily // found without having to consult the symbol table. -#if defined(OS_MACOSX) section(SEG_DATA ",crashpad_info"), -#elif defined(OS_LINUX) || defined(OS_ANDROID) - section("crashpad_info"), -#else // !defined(OS_MACOSX) && !defined(OS_LINUX) && !defined(OS_ANDROID) -#error Port -#endif // !defined(OS_MACOSX) && !defined(OS_LINUX) && !defined(OS_ANDROID) +#endif #if defined(ADDRESS_SANITIZER) // AddressSanitizer would add a trailing red zone of at least 32 bytes, @@ -68,12 +70,12 @@ __attribute__(( aligned(64), #endif // defined(ADDRESS_SANITIZER) - // The “used” attribute prevents the structure from being dead-stripped. - used, - - // There’s no need to expose this as a public symbol from the symbol table. + // There's no need to expose this as a public symbol from the symbol table. // All accesses from the outside can locate the well-known section name. - visibility("hidden"))) + visibility("hidden"), + + // The “used” attribute prevents the structure from being dead-stripped. + used)) #elif defined(OS_WIN) @@ -88,8 +90,19 @@ __declspec(allocate("CPADinfo")) CrashpadInfo g_crashpad_info; +extern "C" int* CRASHPAD_NOTE_REFERENCE; + // static CrashpadInfo* CrashpadInfo::GetCrashpadInfo() { +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) + // This otherwise-unused reference is used so that any module that + // references GetCrashpadInfo() will also include the note in the + // .note.crashpad.info section. That note in turn contains the address of + // g_crashpad_info. This allows the module reader to find the CrashpadInfo + // structure without requiring the use of the dynamic symbol table. + static volatile int* pointer_to_note_section = CRASHPAD_NOTE_REFERENCE; + (void)pointer_to_note_section; +#endif return &g_crashpad_info; } @@ -106,13 +119,7 @@ CrashpadInfo::CrashpadInfo() extra_memory_ranges_(nullptr), simple_annotations_(nullptr), user_data_minidump_stream_head_(nullptr), - annotations_list_(nullptr) -#if !defined(NDEBUG) && defined(OS_WIN) - , - invalid_read_detection_(0xbadc0de) -#endif -{ -} + annotations_list_(nullptr) {} void CrashpadInfo::AddUserDataMinidumpStream(uint32_t stream_type, const void* data, diff --git a/client/crashpad_info.h b/client/crashpad_info.h index fc391bf5..5db9a6cd 100644 --- a/client/crashpad_info.h +++ b/client/crashpad_info.h @@ -233,10 +233,10 @@ struct CrashpadInfo { #pragma clang diagnostic ignored "-Wunused-private-field" #endif - // Fields present in version 1: + // Fields present in version 1, subject to a check of the size_ field: uint32_t signature_; // kSignature uint32_t size_; // The size of the entire CrashpadInfo structure. - uint32_t version_; // kVersion + uint32_t version_; // kCrashpadInfoVersion uint32_t indirectly_referenced_memory_cap_; uint32_t padding_0_; TriState crashpad_handler_behavior_; @@ -248,9 +248,11 @@ struct CrashpadInfo { internal::UserDataMinidumpStreamListEntry* user_data_minidump_stream_head_; AnnotationList* annotations_list_; // weak -#if !defined(NDEBUG) && defined(OS_WIN) - uint32_t invalid_read_detection_; -#endif + // It’s generally safe to add new fields without changing + // kCrashpadInfoVersion, because readers should check size_ and ignore fields + // that aren’t present, as well as unknown fields. + // + // Adding fields? Consider snapshot/crashpad_info_size_test_module.cc too. #if defined(__clang__) #pragma clang diagnostic pop diff --git a/client/crashpad_info_note.S b/client/crashpad_info_note.S new file mode 100644 index 00000000..4c29298d --- /dev/null +++ b/client/crashpad_info_note.S @@ -0,0 +1,59 @@ +// 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 note section is used on ELF platforms to give ElfImageReader a method +// of finding the instance of CrashpadInfo g_crashpad_info without requiring +// that symbol to be in the dynamic symbol table. + +#include "util/misc/elf_note_types.h" + +// namespace crashpad { +// CrashpadInfo g_crashpad_info; +// } // namespace crashpad +#define CRASHPAD_INFO_SYMBOL _ZN8crashpad15g_crashpad_infoE + +#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 + .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: + .long name_end - name // namesz + .long desc_end - desc // descsz + .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type +name: + .asciz CRASHPAD_ELF_NOTE_NAME +name_end: + .balign NOTE_ALIGN +desc: +#if defined(__LP64__) + .quad CRASHPAD_INFO_SYMBOL +#else +#if defined(__LITTLE_ENDIAN__) + .long CRASHPAD_INFO_SYMBOL + .long 0 +#else + .long 0 + .long CRASHPAD_INFO_SYMBOL +#endif // __LITTLE_ENDIAN__ +#endif // __LP64__ +desc_end: + .size CRASHPAD_NOTE_REFERENCE, .-CRASHPAD_NOTE_REFERENCE diff --git a/client/prune_crash_reports_test.cc b/client/prune_crash_reports_test.cc index 54d6941e..2648dee2 100644 --- a/client/prune_crash_reports_test.cc +++ b/client/prune_crash_reports_test.cc @@ -36,20 +36,27 @@ class MockDatabase : public CrashReportDatabase { public: // CrashReportDatabase: MOCK_METHOD0(GetSettings, Settings*()); - MOCK_METHOD1(PrepareNewCrashReport, OperationStatus(NewReport**)); - MOCK_METHOD2(FinishedWritingCrashReport, OperationStatus(NewReport*, UUID*)); - MOCK_METHOD1(ErrorWritingCrashReport, OperationStatus(NewReport*)); + MOCK_METHOD1(PrepareNewCrashReport, + OperationStatus(std::unique_ptr*)); MOCK_METHOD2(LookUpCrashReport, OperationStatus(const UUID&, Report*)); MOCK_METHOD1(GetPendingReports, OperationStatus(std::vector*)); MOCK_METHOD1(GetCompletedReports, OperationStatus(std::vector*)); MOCK_METHOD2(GetReportForUploading, - OperationStatus(const UUID&, const Report**)); + OperationStatus(const UUID&, + std::unique_ptr*)); MOCK_METHOD3(RecordUploadAttempt, - OperationStatus(const Report*, bool, const std::string&)); + OperationStatus(UploadReport*, bool, const std::string&)); MOCK_METHOD2(SkipReportUpload, OperationStatus(const UUID&, Metrics::CrashSkippedReason)); MOCK_METHOD1(DeleteReport, OperationStatus(const UUID&)); MOCK_METHOD1(RequestUpload, OperationStatus(const UUID&)); + + // gmock doesn't support mocking methods with non-copyable types such as + // unique_ptr. + OperationStatus FinishedWritingCrashReport(std::unique_ptr report, + UUID* uuid) override { + return kNoError; + } }; time_t NDaysAgo(int num_days) { diff --git a/client/settings.cc b/client/settings.cc index 15d16f2e..20bd2581 100644 --- a/client/settings.cc +++ b/client/settings.cc @@ -20,10 +20,55 @@ #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "util/file/filesystem.h" #include "util/numeric/in_range_cast.h" namespace crashpad { +#if defined(OS_FUCHSIA) + +Settings::ScopedLockedFileHandle::ScopedLockedFileHandle() + : handle_(kInvalidFileHandle), lockfile_path_() { + } + +Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( + FileHandle handle, + const base::FilePath& lockfile_path) + : handle_(handle), lockfile_path_(lockfile_path) { +} + +Settings::ScopedLockedFileHandle::ScopedLockedFileHandle( + ScopedLockedFileHandle&& other) + : handle_(other.handle_), lockfile_path_(other.lockfile_path_) { + other.handle_ = kInvalidFileHandle; + other.lockfile_path_ = base::FilePath(); +} + +Settings::ScopedLockedFileHandle& Settings::ScopedLockedFileHandle::operator=( + ScopedLockedFileHandle&& other) { + handle_ = other.handle_; + lockfile_path_ = other.lockfile_path_; + + other.handle_ = kInvalidFileHandle; + other.lockfile_path_ = base::FilePath(); + return *this; +} + +Settings::ScopedLockedFileHandle::~ScopedLockedFileHandle() { + Destroy(); +} + +void Settings::ScopedLockedFileHandle::Destroy() { + if (handle_ != kInvalidFileHandle) { + CheckedCloseFile(handle_); + } + if (!lockfile_path_.empty()) { + DCHECK(LoggingRemoveFile(lockfile_path_)); + } +} + +#else // OS_FUCHSIA + namespace internal { // static @@ -36,6 +81,8 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) { } // namespace internal +#endif // OS_FUCHSIA + struct Settings::Data { static const uint32_t kSettingsMagic = 'CPds'; static const uint32_t kSettingsVersion = 1; @@ -59,16 +106,14 @@ struct Settings::Data { UUID client_id; }; -Settings::Settings(const base::FilePath& file_path) - : file_path_(file_path), - initialized_() { -} +Settings::Settings() = default; -Settings::~Settings() { -} +Settings::~Settings() = default; -bool Settings::Initialize() { +bool Settings::Initialize(const base::FilePath& file_path) { + DCHECK(initialized_.is_uninitialized()); initialized_.set_invalid(); + file_path_ = file_path; Data settings; if (!OpenForWritingAndReadSettings(&settings).is_valid()) @@ -144,18 +189,33 @@ bool Settings::SetLastUploadAttemptTime(time_t time) { // static Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle( FileHandle file, - FileLocking locking) { + FileLocking locking, + const base::FilePath& file_path) { ScopedFileHandle scoped(file); +#if defined(OS_FUCHSIA) + base::FilePath lockfile_path(file_path.value() + ".__lock__"); + if (scoped.is_valid()) { + ScopedFileHandle lockfile_scoped( + LoggingOpenFileForWrite(lockfile_path, + FileWriteMode::kCreateOrFail, + FilePermissions::kWorldReadable)); + // This is a lightweight attempt to try to catch racy behavior. + DCHECK(lockfile_scoped.is_valid()); + return ScopedLockedFileHandle(scoped.release(), lockfile_path); + } + return ScopedLockedFileHandle(scoped.release(), base::FilePath()); +#else if (scoped.is_valid()) { if (!LoggingLockFile(scoped.get(), locking)) scoped.reset(); } return ScopedLockedFileHandle(scoped.release()); +#endif } Settings::ScopedLockedFileHandle Settings::OpenForReading() { - return MakeScopedLockedFileHandle(LoggingOpenFileForRead(file_path()), - FileLocking::kShared); + return MakeScopedLockedFileHandle( + LoggingOpenFileForRead(file_path()), FileLocking::kShared, file_path()); } Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting( @@ -171,7 +231,8 @@ Settings::ScopedLockedFileHandle Settings::OpenForReadingAndWriting( file_path(), mode, FilePermissions::kWorldReadable); } - return MakeScopedLockedFileHandle(handle, FileLocking::kExclusive); + return MakeScopedLockedFileHandle( + handle, FileLocking::kExclusive, file_path()); } bool Settings::OpenAndReadSettings(Data* out_data) { diff --git a/client/settings.h b/client/settings.h index b64f74fb..a2b0c746 100644 --- a/client/settings.h +++ b/client/settings.h @@ -22,6 +22,7 @@ #include "base/files/file_path.h" #include "base/macros.h" #include "base/scoped_generic.h" +#include "build/build_config.h" #include "util/file/file_io.h" #include "util/misc/initialization_state.h" #include "util/misc/uuid.h" @@ -44,10 +45,18 @@ struct ScopedLockedFileHandleTraits { //! should be retrieved via CrashReportDatabase::GetSettings(). class Settings { public: - explicit Settings(const base::FilePath& file_path); + Settings(); ~Settings(); - bool Initialize(); + //! \brief Initializes the settings data store. + //! + //! This method must be called only once, and must be successfully called + //! before any other method in this class may be called. + //! + //! \param[in] path The location to store the settings data. + //! \return `true` if the data store was initialized successfully, otherwise + //! `false` with an error logged. + bool Initialize(const base::FilePath& path); //! \brief Retrieves the immutable identifier for this client, which is used //! on a server to locate all crash reports from a specific Crashpad @@ -106,11 +115,45 @@ class Settings { struct Data; // This must be constructed with MakeScopedLockedFileHandle(). It both unlocks - // and closes the file on destruction. + // and closes the file on destruction. Note that on Fuchsia, this handle DOES + // NOT offer correct operation, only an attempt to DCHECK if racy behavior is + // detected. +#if defined(OS_FUCHSIA) + struct ScopedLockedFileHandle { + public: + ScopedLockedFileHandle(); + ScopedLockedFileHandle(FileHandle handle, + const base::FilePath& lockfile_path); + ScopedLockedFileHandle(ScopedLockedFileHandle&& other); + ScopedLockedFileHandle& operator=(ScopedLockedFileHandle&& other); + ~ScopedLockedFileHandle(); + + // These mirror the non-Fuchsia ScopedLockedFileHandle via ScopedGeneric so + // that calling code can pretend this implementation is the same. + bool is_valid() const { return handle_ != kInvalidFileHandle; } + FileHandle get() { return handle_; } + void reset() { + Destroy(); + handle_ = kInvalidFileHandle; + lockfile_path_ = base::FilePath(); + } + + private: + void Destroy(); + + FileHandle handle_; + base::FilePath lockfile_path_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLockedFileHandle); + }; +#else // OS_FUCHSIA using ScopedLockedFileHandle = base::ScopedGeneric; - static ScopedLockedFileHandle MakeScopedLockedFileHandle(FileHandle file, - FileLocking locking); +#endif // OS_FUCHSIA + static ScopedLockedFileHandle MakeScopedLockedFileHandle( + FileHandle file, + FileLocking locking, + const base::FilePath& file_path); // Opens the settings file for reading. On error, logs a message and returns // the invalid handle. diff --git a/client/settings_test.cc b/client/settings_test.cc index ca961a23..3a5730bd 100644 --- a/client/settings_test.cc +++ b/client/settings_test.cc @@ -26,7 +26,7 @@ namespace { class SettingsTest : public testing::Test { public: - SettingsTest() : settings_(settings_path()) {} + SettingsTest() = default; base::FilePath settings_path() { return temp_dir_.path().Append(FILE_PATH_LITERAL("settings")); @@ -49,7 +49,7 @@ class SettingsTest : public testing::Test { protected: // testing::Test: void SetUp() override { - ASSERT_TRUE(settings()->Initialize()); + ASSERT_TRUE(settings()->Initialize(settings_path())); } private: @@ -64,8 +64,8 @@ TEST_F(SettingsTest, ClientID) { EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); - Settings local_settings(settings_path()); - EXPECT_TRUE(local_settings.Initialize()); + Settings local_settings; + EXPECT_TRUE(local_settings.Initialize(settings_path())); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); @@ -81,8 +81,8 @@ TEST_F(SettingsTest, UploadsEnabled) { EXPECT_TRUE(settings()->GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); - Settings local_settings(settings_path()); - EXPECT_TRUE(local_settings.Initialize()); + Settings local_settings; + EXPECT_TRUE(local_settings.Initialize(settings_path())); enabled = false; EXPECT_TRUE(local_settings.GetUploadsEnabled(&enabled)); EXPECT_TRUE(enabled); @@ -107,8 +107,8 @@ TEST_F(SettingsTest, LastUploadAttemptTime) { EXPECT_TRUE(settings()->GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); - Settings local_settings(settings_path()); - EXPECT_TRUE(local_settings.Initialize()); + Settings local_settings; + EXPECT_TRUE(local_settings.Initialize(settings_path())); actual = -1; EXPECT_TRUE(local_settings.GetLastUploadAttemptTime(&actual)); EXPECT_EQ(actual, expected); @@ -120,8 +120,8 @@ TEST_F(SettingsTest, LastUploadAttemptTime) { TEST_F(SettingsTest, BadFileOnInitialize) { InitializeBadFile(); - Settings settings(settings_path()); - EXPECT_TRUE(settings.Initialize()); + Settings settings; + EXPECT_TRUE(settings.Initialize(settings_path())); } TEST_F(SettingsTest, BadFileOnGet) { @@ -131,8 +131,8 @@ TEST_F(SettingsTest, BadFileOnGet) { EXPECT_TRUE(settings()->GetClientID(&client_id)); EXPECT_NE(client_id, UUID()); - Settings local_settings(settings_path()); - EXPECT_TRUE(local_settings.Initialize()); + Settings local_settings; + EXPECT_TRUE(local_settings.Initialize(settings_path())); UUID actual; EXPECT_TRUE(local_settings.GetClientID(&actual)); EXPECT_EQ(actual, client_id); @@ -161,8 +161,8 @@ TEST_F(SettingsTest, UnlinkFile) { << ErrnoMessage("unlink"); #endif - Settings local_settings(settings_path()); - EXPECT_TRUE(local_settings.Initialize()); + Settings local_settings; + EXPECT_TRUE(local_settings.Initialize(settings_path())); UUID new_client_id; EXPECT_TRUE(local_settings.GetClientID(&new_client_id)); EXPECT_NE(new_client_id, client_id); diff --git a/client/simple_address_range_bag_test.cc b/client/simple_address_range_bag_test.cc index ac81a1f4..e9a6d9f3 100644 --- a/client/simple_address_range_bag_test.cc +++ b/client/simple_address_range_bag_test.cc @@ -16,7 +16,7 @@ #include "base/logging.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { diff --git a/client/simple_string_dictionary_test.cc b/client/simple_string_dictionary_test.cc index 6944934f..5fdeb5bf 100644 --- a/client/simple_string_dictionary_test.cc +++ b/client/simple_string_dictionary_test.cc @@ -16,7 +16,7 @@ #include "base/logging.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { @@ -253,21 +253,30 @@ TEST(SimpleStringDictionary, OutOfSpace) { #if DCHECK_IS_ON() -TEST(SimpleStringDictionaryDeathTest, NullKey) { +TEST(SimpleStringDictionaryDeathTest, SetKeyValueWithNullKey) { TSimpleStringDictionary<4, 6, 6> map; ASSERT_DEATH_CHECK(map.SetKeyValue(nullptr, "hello"), "key"); +} +TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithNullKey) { + TSimpleStringDictionary<4, 6, 6> map; map.SetKeyValue("hi", "there"); ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key"); EXPECT_STREQ("there", map.GetValueForKey("hi")); - - ASSERT_DEATH_CHECK(map.GetValueForKey(nullptr), "key"); - map.RemoveKey("hi"); - EXPECT_EQ(map.GetCount(), 0u); } #endif +// The tests above, without DEATH_CHECK assertions. +TEST(SimpleStringDictionaryDeathTest, GetValueForKeyWithoutNullKey) { + TSimpleStringDictionary<4, 6, 6> map; + + map.SetKeyValue("hi", "there"); + EXPECT_STREQ("there", map.GetValueForKey("hi")); + map.RemoveKey("hi"); + EXPECT_EQ(map.GetCount(), 0u); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/client/simulate_crash.h b/client/simulate_crash.h index 299fe97c..63e09a17 100644 --- a/client/simulate_crash.h +++ b/client/simulate_crash.h @@ -21,6 +21,8 @@ #include "client/simulate_crash_mac.h" #elif defined(OS_WIN) #include "client/simulate_crash_win.h" +#elif defined(OS_LINUX) || defined(OS_ANDROID) +#include "client/simulate_crash_linux.h" #endif #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_ diff --git a/client/simulate_crash_linux.h b/client/simulate_crash_linux.h new file mode 100644 index 00000000..e6c3e487 --- /dev/null +++ b/client/simulate_crash_linux.h @@ -0,0 +1,31 @@ +// 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_CLIENT_SIMULATE_CRASH_LINUX_H_ +#define CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_ + +#include "client/crashpad_client.h" +#include "util/misc/capture_context.h" + +//! \file + +//! \brief Captures the CPU context and simulates an exception without crashing. +#define CRASHPAD_SIMULATE_CRASH() \ + do { \ + crashpad::NativeCPUContext simulate_crash_cpu_context; \ + crashpad::CaptureContext(&simulate_crash_cpu_context); \ + crashpad::CrashpadClient::DumpWithoutCrash(&simulate_crash_cpu_context); \ + } while (false) + +#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_LINUX_H_ diff --git a/client/simulate_crash_mac.h b/client/simulate_crash_mac.h index e14db3c9..dcbcaae3 100644 --- a/client/simulate_crash_mac.h +++ b/client/simulate_crash_mac.h @@ -17,7 +17,7 @@ #include -#include "client/capture_context_mac.h" +#include "util/misc/capture_context.h" //! \file diff --git a/client/simulate_crash_win.h b/client/simulate_crash_win.h index a20f3dad..140424f3 100644 --- a/client/simulate_crash_win.h +++ b/client/simulate_crash_win.h @@ -18,16 +18,17 @@ #include #include "client/crashpad_client.h" -#include "util/win/capture_context.h" +#include "util/misc/capture_context.h" //! \file //! \brief Captures the CPU context and captures a dump without an exception. -#define CRASHPAD_SIMULATE_CRASH() \ - do { \ - CONTEXT context; \ - crashpad::CaptureContext(&context); \ - crashpad::CrashpadClient::DumpWithoutCrash(context); \ +#define CRASHPAD_SIMULATE_CRASH() \ + do { \ + /* Not "context" to avoid variable shadowing warnings. */ \ + CONTEXT simulate_crash_cpu_context; \ + crashpad::CaptureContext(&simulate_crash_cpu_context); \ + crashpad::CrashpadClient::DumpWithoutCrash(simulate_crash_cpu_context); \ } while (false) #endif // CRASHPAD_CLIENT_SIMULATE_CRASH_WIN_H_ diff --git a/compat/BUILD.gn b/compat/BUILD.gn new file mode 100644 index 00000000..2bfe074f --- /dev/null +++ b/compat/BUILD.gn @@ -0,0 +1,127 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +config("compat_config") { + include_dirs = [] + + if (crashpad_is_mac) { + include_dirs += [ "mac" ] + } + + if (crashpad_is_linux || crashpad_is_android) { + include_dirs += [ "linux" ] + } + + if (crashpad_is_android) { + include_dirs += [ "android" ] + } + + if (crashpad_is_win) { + include_dirs += [ "win" ] + } else { + include_dirs += [ "non_win" ] + } +} + +template("compat_target") { + if (crashpad_is_mac) { + # There are no sources to compile, which doesn’t mix will with a + # static_library. + group(target_name) { + forward_variables_from(invoker, "*") + } + } else { + static_library(target_name) { + forward_variables_from(invoker, "*") + } + } +} + +compat_target("compat") { + sources = [] + + if (crashpad_is_mac) { + sources += [ + "mac/AvailabilityMacros.h", + "mac/kern/exc_resource.h", + "mac/mach-o/loader.h", + "mac/mach/mach.h", + "mac/sys/resource.h", + ] + } else { + sources += [ "non_mac/mach/mach.h" ] + } + + if (crashpad_is_linux || crashpad_is_android) { + sources += [ + "linux/signal.h", + "linux/sys/ptrace.h", + "linux/sys/user.h", + ] + } + + if (crashpad_is_android) { + sources += [ + "android/dlfcn_internal.cc", + "android/dlfcn_internal.h", + "android/elf.h", + "android/linux/elf.h", + "android/linux/prctl.h", + "android/linux/ptrace.h", + "android/sched.h", + "android/sys/epoll.cc", + "android/sys/epoll.h", + "android/sys/mman.cc", + "android/sys/mman.h", + "android/sys/syscall.h", + "android/sys/user.h", + ] + } + + if (crashpad_is_win) { + sources += [ + "win/getopt.h", + "win/strings.cc", + "win/strings.h", + "win/sys/types.h", + "win/time.cc", + "win/time.h", + "win/winbase.h", + "win/winnt.h", + "win/winternl.h", + ] + } else { + sources += [ + "non_win/dbghelp.h", + "non_win/minwinbase.h", + "non_win/timezoneapi.h", + "non_win/verrsrc.h", + "non_win/windows.h", + "non_win/winnt.h", + ] + } + + public_configs = [ + ":compat_config", + "..:crashpad_config", + ] + + deps = [] + + if (crashpad_is_win) { + deps += [ "../third_party/getopt" ] + } +} diff --git a/compat/android/dlfcn_internal.cc b/compat/android/dlfcn_internal.cc new file mode 100644 index 00000000..5d98fe84 --- /dev/null +++ b/compat/android/dlfcn_internal.cc @@ -0,0 +1,166 @@ +// 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 "dlfcn_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace crashpad { +namespace internal { + +// KitKat supports API levels up to 20. +#if __ANDROID_API__ < 21 + +namespace { + +class ScopedSigactionRestore { + public: + ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {} + + ~ScopedSigactionRestore() { Reset(); } + + bool Reset() { + bool result = true; + if (valid_) { + result = sigaction(signo_, &old_action_, nullptr) == 0; + if (!result) { + PrintErrmsg(errno); + } + } + valid_ = false; + signo_ = -1; + return result; + } + + bool ResetAndInstallHandler(int signo, + void (*handler)(int, siginfo_t*, void*)) { + Reset(); + + struct sigaction act; + act.sa_sigaction = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(signo, &act, &old_action_) != 0) { + PrintErrmsg(errno); + return false; + } + signo_ = signo; + valid_ = true; + return true; + } + + private: + void PrintErrmsg(int err) { + char errmsg[256]; + + if (strerror_r(err, errmsg, sizeof(errmsg)) != 0) { + snprintf(errmsg, + sizeof(errmsg), + "%s:%d: Couldn't set errmsg for %d: %d", + __FILE__, + __LINE__, + err, + errno); + return; + } + + fprintf(stderr, "%s:%d: sigaction: %s", __FILE__, __LINE__, errmsg); + } + + struct sigaction old_action_; + int signo_; + bool valid_; +}; + +bool IsKitKat() { + char prop_buf[PROP_VALUE_MAX]; + int length = __system_property_get("ro.build.version.sdk", prop_buf); + if (length <= 0) { + fprintf(stderr, "%s:%d: Couldn't get version", __FILE__, __LINE__); + // It's safer to assume this is KitKat and execute dlsym with a signal + // handler installed. + return true; + } + if (strcmp(prop_buf, "19") == 0 || strcmp(prop_buf, "20") == 0) { + return true; + } + return false; +} + +class ScopedSetTID { + public: + explicit ScopedSetTID(pid_t* tid) : tid_(tid) { *tid_ = syscall(SYS_gettid); } + + ~ScopedSetTID() { *tid_ = -1; } + + private: + pid_t* tid_; +}; + +sigjmp_buf dlsym_sigjmp_env; + +pid_t dlsym_tid = -1; + +void HandleSIGFPE(int signo, siginfo_t* siginfo, void* context) { + if (siginfo->si_code != FPE_INTDIV || syscall(SYS_gettid) != dlsym_tid) { + return; + } + siglongjmp(dlsym_sigjmp_env, 1); +} + +} // namespace + +void* Dlsym(void* handle, const char* symbol) { + if (!IsKitKat()) { + return dlsym(handle, symbol); + } + + static std::mutex* signal_handler_mutex = new std::mutex(); + std::lock_guard lock(*signal_handler_mutex); + + ScopedSetTID set_tid(&dlsym_tid); + + ScopedSigactionRestore sig_restore; + if (!sig_restore.ResetAndInstallHandler(SIGFPE, HandleSIGFPE)) { + return nullptr; + } + + if (sigsetjmp(dlsym_sigjmp_env, 1) != 0) { + return nullptr; + } + + return dlsym(handle, symbol); +} + +#else + +void* Dlsym(void* handle, const char* symbol) { + return dlsym(handle, symbol); +} + +#endif // __ANDROID_API__ < 21 + +} // namespace internal +} // namespace crashpad diff --git a/compat/android/dlfcn_internal.h b/compat/android/dlfcn_internal.h new file mode 100644 index 00000000..ed4083db --- /dev/null +++ b/compat/android/dlfcn_internal.h @@ -0,0 +1,35 @@ +// 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_COMPAT_ANDROID_DLFCN_INTERNAL_H_ +#define CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ + +namespace crashpad { +namespace internal { + +//! \brief Provide a wrapper for `dlsym`. +//! +//! dlsym on Android KitKat (4.4.*) raises SIGFPE when searching for a +//! non-existent symbol. This wrapper avoids crashing in this circumstance. +//! https://code.google.com/p/android/issues/detail?id=61799 +//! +//! The parameters and return value for this function are the same as for +//! `dlsym`, but a return value for `dlerror` may not be set in the event of an +//! error. +void* Dlsym(void* handle, const char* symbol); + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ diff --git a/compat/android/elf.h b/compat/android/elf.h index 79e44fca..9fa92387 100644 --- a/compat/android/elf.h +++ b/compat/android/elf.h @@ -17,6 +17,8 @@ #include_next +#include + #if !defined(ELF32_ST_VISIBILITY) #define ELF32_ST_VISIBILITY(other) ((other) & 0x3) #endif @@ -35,4 +37,22 @@ #define STT_TLS 6 #endif +// ELF note header types are normally provided by . While unified +// headers include in , traditional headers do not, prior +// to API 21. and can't both be included in the same +// translation unit due to collisions, so we provide these types here. +#if __ANDROID_API__ < 21 && !defined(__ANDROID_API_N__) +typedef struct { + Elf32_Word n_namesz; + Elf32_Word n_descsz; + Elf32_Word n_type; +} Elf32_Nhdr; + +typedef struct { + Elf64_Word n_namesz; + Elf64_Word n_descsz; + Elf64_Word n_type; +} Elf64_Nhdr; +#endif // __ANDROID_API__ < 21 && !defined(NT_PRSTATUS) + #endif // CRASHPAD_COMPAT_ANDROID_ELF_H_ diff --git a/compat/android/sys/epoll.cc b/compat/android/sys/epoll.cc new file mode 100644 index 00000000..7fd3bb37 --- /dev/null +++ b/compat/android/sys/epoll.cc @@ -0,0 +1,36 @@ +// 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 + +#include +#include +#include + +#include "dlfcn_internal.h" + +#if __ANDROID_API__ < 21 + +extern "C" { + +int epoll_create1(int flags) { + static const auto epoll_create1_p = reinterpret_cast( + crashpad::internal::Dlsym(RTLD_DEFAULT, "epoll_create1")); + return epoll_create1_p ? epoll_create1_p(flags) + : syscall(SYS_epoll_create1, flags); +} + +} // extern "C" + +#endif // __ANDROID_API__ < 21 diff --git a/compat/android/sys/epoll.h b/compat/android/sys/epoll.h new file mode 100644 index 00000000..387813e6 --- /dev/null +++ b/compat/android/sys/epoll.h @@ -0,0 +1,50 @@ +// 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_COMPAT_ANDROID_SYS_EPOLL_H_ +#define CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_ + +#include_next + +#include +#include + +// This is missing from traditional headers before API 21. +#if !defined(EPOLLRDHUP) +#define EPOLLRDHUP 0x00002000 +#endif + +// EPOLL_CLOEXEC is undefined in traditional headers before API 21 and removed +// from unified headers at API levels < 21 as a means to indicate that +// epoll_create1 is missing from the C library, but the raw system call should +// still be available. +#if !defined(EPOLL_CLOEXEC) +#define EPOLL_CLOEXEC O_CLOEXEC +#endif + +#if __ANDROID_API__ < 21 + +#ifdef __cplusplus +extern "C" { +#endif + +int epoll_create1(int flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __ANDROID_API__ < 21 + +#endif // CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_ diff --git a/compat/android/sys/mman.cc b/compat/android/sys/mman.cc index f4d722c9..4c29a9df 100644 --- a/compat/android/sys/mman.cc +++ b/compat/android/sys/mman.cc @@ -19,6 +19,8 @@ #include #include +#include "dlfcn_internal.h" + #if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 // Bionic has provided a wrapper for __mmap2() since the beginning of time. See @@ -87,8 +89,8 @@ void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) { // Use the system’s mmap64() wrapper if available. It will be available on // Android 5.0 (“Lollipop”) and later. using Mmap64Type = void* (*)(void*, size_t, int, int, int, off64_t); - static const Mmap64Type mmap64 = - reinterpret_cast(dlsym(RTLD_DEFAULT, "mmap64")); + static const Mmap64Type mmap64 = reinterpret_cast( + crashpad::internal::Dlsym(RTLD_DEFAULT, "mmap64")); if (mmap64) { return mmap64(addr, size, prot, flags, fd, offset); } diff --git a/compat/android/sys/syscall.h b/compat/android/sys/syscall.h index 81fce78a..facce20f 100644 --- a/compat/android/sys/syscall.h +++ b/compat/android/sys/syscall.h @@ -19,6 +19,10 @@ // Android 5.0.0 (API 21) NDK +#if !defined(SYS_epoll_create1) +#define SYS_epoll_create1 __NR_epoll_create1 +#endif + #if !defined(SYS_gettid) #define SYS_gettid __NR_gettid #endif diff --git a/compat/compat.gyp b/compat/compat.gyp index b0aec0af..1229ee0c 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -19,25 +19,27 @@ 'targets': [ { 'target_name': 'crashpad_compat', - 'type': 'static_library', 'sources': [ + 'android/dlfcn_internal.cc', + 'android/dlfcn_internal.h', 'android/elf.h', 'android/linux/elf.h', 'android/linux/prctl.h', 'android/linux/ptrace.h', 'android/sched.h', + 'android/sys/epoll.cc', + 'android/sys/epoll.h', 'android/sys/mman.cc', 'android/sys/mman.h', 'android/sys/syscall.h', 'android/sys/user.h', 'linux/signal.h', 'linux/sys/ptrace.h', + 'linux/sys/user.h', 'mac/AvailabilityMacros.h', 'mac/kern/exc_resource.h', 'mac/mach/i386/thread_state.h', 'mac/mach/mach.h', - 'mac/mach-o/getsect.cc', - 'mac/mach-o/getsect.h', 'mac/mach-o/loader.h', 'mac/sys/resource.h', 'non_mac/mach/mach.h', @@ -60,9 +62,7 @@ ], 'conditions': [ ['OS=="mac"', { - 'dependencies': [ - '../third_party/apple_cctools/apple_cctools.gyp:apple_cctools', - ], + 'type': 'none', 'include_dirs': [ 'mac', ], @@ -71,8 +71,18 @@ 'mac', ], }, + }, { + 'include_dirs': [ + 'non_mac', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'non_mac', + ], + }, }], ['OS=="win"', { + 'type': 'static_library', 'include_dirs': [ 'win', ], @@ -95,6 +105,7 @@ }, }], ['OS=="android"', { + 'type': 'static_library', 'include_dirs': [ 'android', 'linux', @@ -112,6 +123,7 @@ }, }], ['OS=="linux"', { + 'type': 'none', 'include_dirs': [ 'linux', ], diff --git a/compat/linux/signal.h b/compat/linux/signal.h index 62b9c086..4e1cdc1f 100644 --- a/compat/linux/signal.h +++ b/compat/linux/signal.h @@ -18,10 +18,43 @@ #include_next // Missing from glibc and bionic-x86_64 + #if defined(__x86_64__) || defined(__i386__) #if !defined(X86_FXSR_MAGIC) #define X86_FXSR_MAGIC 0x0000 #endif #endif // __x86_64__ || __i386__ +#if defined(__aarch64__) || defined(__arm__) + +#if !defined(FPSIMD_MAGIC) +#define FPSIMD_MAGIC 0x46508001 +#endif + +#if !defined(ESR_MAGIC) +#define ESR_MAGIC 0x45535201 +#endif + +#if !defined(EXTRA_MAGIC) +#define EXTRA_MAGIC 0x45585401 +#endif + +#if !defined(VFP_MAGIC) +#define VFP_MAGIC 0x56465001 +#endif + +#if !defined(CRUNCH_MAGIC) +#define CRUNCH_MAGIC 0x5065cf03 +#endif + +#if !defined(DUMMY_MAGIC) +#define DUMMY_MAGIC 0xb0d9ed01 +#endif + +#if !defined(IWMMXT_MAGIC) +#define IWMMXT_MAGIC 0x12ef842a +#endif + +#endif // __aarch64__ || __arm__ + #endif // CRASHPAD_COMPAT_LINUX_SIGNAL_H_ diff --git a/compat/linux/sys/ptrace.h b/compat/linux/sys/ptrace.h index 57c5c5ba..fa894381 100644 --- a/compat/linux/sys/ptrace.h +++ b/compat/linux/sys/ptrace.h @@ -19,9 +19,33 @@ #include -#if defined(__GLIBC__) && defined(__x86_64__) +// https://sourceware.org/bugzilla/show_bug.cgi?id=22433 +#if !defined(PTRACE_GET_THREAD_AREA) && !defined(PT_GET_THREAD_AREA) && \ + defined(__GLIBC__) +#if defined(__i386__) || defined(__x86_64__) static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = static_cast<__ptrace_request>(25); -#endif // __GLIBC__ && __x86_64__ +#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA +#elif defined(__arm__) || defined(__arm64__) +static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = + static_cast<__ptrace_request>(22); +#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA +#elif defined(__mips__) +static constexpr __ptrace_request PTRACE_GET_THREAD_AREA = + static_cast<__ptrace_request>(25); +#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA +static constexpr __ptrace_request PTRACE_GET_THREAD_AREA_3264 = + static_cast<__ptrace_request>(0xc4); +#define PTRACE_GET_THREAD_AREA_3264 PTRACE_GET_THREAD_AREA_3264 +#endif +#endif // !PTRACE_GET_THREAD_AREA && !PT_GET_THREAD_AREA && defined(__GLIBC__) + +// https://sourceware.org/bugzilla/show_bug.cgi?id=22433 +#if !defined(PTRACE_GETVFPREGS) && !defined(PT_GETVFPREGS) && \ + defined(__GLIBC__) && (defined(__arm__) || defined(__arm64__)) +static constexpr __ptrace_request PTRACE_GETVFPREGS = + static_cast<__ptrace_request>(27); +#define PTRACE_GETVFPREGS PTRACE_GETVFPREGS +#endif #endif // CRASHPAD_COMPAT_LINUX_SYS_PTRACE_H_ diff --git a/compat/linux/sys/user.h b/compat/linux/sys/user.h new file mode 100644 index 00000000..0ce5338b --- /dev/null +++ b/compat/linux/sys/user.h @@ -0,0 +1,30 @@ +// 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_LINUX_SYS_USER_H_ +#define CRASHPAD_COMPAT_LINUX_SYS_USER_H_ + +#include_next + +#include + +// glibc for 64-bit ARM uses different names for these structs prior to 2.20. +#if defined(__arm64__) && defined(__GLIBC__) +#if !__GLIBC_PREREQ(2, 20) +using user_regs_struct = user_pt_regs; +using user_fpsimd_struct = user_fpsimd_state; +#endif +#endif + +#endif // CRASHPAD_COMPAT_LINUX_SYS_USER_H_ diff --git a/compat/mac/mach-o/getsect.cc b/compat/mac/mach-o/getsect.cc deleted file mode 100644 index 6cda9ffe..00000000 --- a/compat/mac/mach-o/getsect.cc +++ /dev/null @@ -1,107 +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 - -// This is only necessary when building code that might run on systems earlier -// than 10.7. When building for 10.7 or later, getsectiondata() and -// getsegmentdata() are always present in libmacho and made available through -// libSystem. When building for earlier systems, custom definitions of -// these functions are needed. -// -// This file checks the deployment target instead of the SDK. The deployment -// target is correct because it identifies the earliest possible system that -// the code being compiled is expected to run on. - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 - -#include -#include - -#include "third_party/apple_cctools/cctools/include/mach-o/getsect.h" - -namespace { - -// Returns a dlopen() handle to the same library that provides the -// getsectbyname() function. getsectbyname() is always present in libmacho. -// getsectiondata() and getsegmentdata() are not always present, but when they -// are, they’re in the same library as getsectbyname(). If the library cannot -// be found or a handle to it cannot be returned, returns nullptr. -void* SystemLibMachOHandle() { - Dl_info info; - if (!dladdr(reinterpret_cast(getsectbyname), &info)) { - return nullptr; - } - return dlopen(info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); -} - -// Returns a function pointer to a function in libmacho based on a lookup of -// that function by symbol name. Returns nullptr if libmacho cannot be found or -// opened, or if the named symbol cannot be found in libmacho. -void* LookUpSystemLibMachOSymbol(const char* symbol) { - static void* dl_handle = SystemLibMachOHandle(); - if (!dl_handle) { - return nullptr; - } - return dlsym(dl_handle, symbol); -} - -#ifndef __LP64__ -using MachHeader = mach_header; -#else -using MachHeader = mach_header_64; -#endif - -using GetSectionDataType = - uint8_t*(*)(const MachHeader*, const char*, const char*, unsigned long*); -using GetSegmentDataType = - uint8_t*(*)(const MachHeader*, const char*, unsigned long*); - -} // namespace - -extern "C" { - -// These implementations look up their functions in libmacho at run time. If the -// system libmacho provides these functions as it normally does on OS X 10.7 and -// later, the system’s versions are used directly. Otherwise, the versions in -// third_party/apple_cctools are used, which are actually just copies of the -// system’s functions. - -uint8_t* getsectiondata(const MachHeader* mhp, - const char* segname, - const char* sectname, - unsigned long* size) { - static GetSectionDataType system_getsectiondata = - reinterpret_cast( - LookUpSystemLibMachOSymbol("getsectiondata")); - if (system_getsectiondata) { - return system_getsectiondata(mhp, segname, sectname, size); - } - return crashpad_getsectiondata(mhp, segname, sectname, size); -} - -uint8_t* getsegmentdata( - const MachHeader* mhp, const char* segname, unsigned long* size) { - static GetSegmentDataType system_getsegmentdata = - reinterpret_cast( - LookUpSystemLibMachOSymbol("getsegmentdata")); - if (system_getsegmentdata) { - return system_getsegmentdata(mhp, segname, size); - } - return crashpad_getsegmentdata(mhp, segname, size); -} - -} // extern "C" - -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 diff --git a/compat/mac/mach-o/getsect.h b/compat/mac/mach-o/getsect.h deleted file mode 100644 index c9169e16..00000000 --- a/compat/mac/mach-o/getsect.h +++ /dev/null @@ -1,69 +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_COMPAT_MAC_MACH_O_GETSECT_H_ -#define CRASHPAD_COMPAT_MAC_MACH_O_GETSECT_H_ - -#include_next - -#include - -// This file checks the SDK instead of the deployment target. The SDK is correct -// because this file is concerned with providing compile-time declarations, -// which are either present in a specific SDK version or not. - -#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Don’t use a type alias to account for the mach_header/mach_header_64 -// difference between the 32-bit and 64-bit versions of getsectiondata() and -// getsegmentdata(). This file should be faithfully equivalent to the native -// SDK, and adding type aliases here would pollute the namespace in a way that -// the native SDK does not. - -#if !defined(__LP64__) - -uint8_t* getsectiondata(const struct mach_header* mhp, - const char* segname, - const char* sectname, - unsigned long* size); - -uint8_t* getsegmentdata( - const struct mach_header* mhp, const char* segname, unsigned long* size); - -#else - -uint8_t* getsectiondata(const struct mach_header_64* mhp, - const char* segname, - const char* sectname, - unsigned long* size); - -uint8_t* getsegmentdata( - const struct mach_header_64* mhp, const char* segname, unsigned long* size); - -#endif - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 - -#endif // CRASHPAD_COMPAT_MAC_MACH_O_GETSECT_H_ diff --git a/compat/non_win/dbghelp.h b/compat/non_win/dbghelp.h index a8d5a0f3..5ce88b88 100644 --- a/compat/non_win/dbghelp.h +++ b/compat/non_win/dbghelp.h @@ -564,16 +564,15 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_MODULE { //! Microsoft //! Symbol and Type Information, section 7.2, “Debug Information Format” - //! for a list of debug information formats, and Undocumented - //! Windows 2000 Secrets, Windows 2000 Debugging Support/Microsoft Symbol - //! File Internals/CodeView Subsections for an in-depth description of the - //! CodeView 4.1 format. Signatures seen in the wild include “NB09” - //! (0x3930424e) for CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0. - //! This form of debugging information within the module, as opposed to a link - //! to an external `.pdb` file, is chosen by building with `/Z7` in Visual - //! Studio 6.0 (1998) and earlier. This embedded form of debugging information - //! is now considered obsolete. + //! for a list of debug information formats, and Undocumented Windows 2000 + //! Secrets, Windows 2000 Debugging Support/Microsoft Symbol File + //! Internals/CodeView Subsections for an in-depth description of the CodeView + //! 4.1 format. Signatures seen in the wild include “NB09” (0x3930424e) for + //! CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0. This form of + //! debugging information within the module, as opposed to a link to an + //! external `.pdb` file, is chosen by building with `/Z7` in Visual Studio + //! 6.0 (1998) and earlier. This embedded form of debugging information is now + //! considered obsolete. //! //! On Windows, the CodeView record is taken from a module’s //! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value diff --git a/compat/win/winnt.h b/compat/win/winnt.h index 6a5e0164..34ea80f9 100644 --- a/compat/win/winnt.h +++ b/compat/win/winnt.h @@ -18,8 +18,8 @@ // include_next #include <../um/winnt.h> -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa373184.aspx: -// "Note that this structure definition was accidentally omitted from WinNT.h." +// https://msdn.microsoft.com/library/aa373184.aspx: "Note that this structure +// definition was accidentally omitted from WinNT.h." struct PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; diff --git a/doc/developing.md b/doc/developing.md index 6b2a9e4e..84d1cf76 100644 --- a/doc/developing.md +++ b/doc/developing.md @@ -22,7 +22,7 @@ limitations under the License. ## Introduction -Crashpad is a [Chromium project](https://dev.chromium.org/Home). Most of its +Crashpad is a [Chromium project](https://www.chromium.org/Home). Most of its development practices follow Chromium’s. In order to function on its own in other projects, Crashpad uses [mini_chromium](https://chromium.googlesource.com/chromium/mini_chromium/), a @@ -43,9 +43,9 @@ the `$PATH` environment variable: C++ support and the Windows SDK. MSVS 2015 and MSVS 2017 are both supported. Some tests also require the CDB debugger, installed with [Debugging Tools for - Windows](https://msdn.microsoft.com/library/windows/hardware/ff551063.aspx). + Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). * Chromium’s - [depot_tools](https://dev.chromium.org/developers/how-tos/depottools). + [depot_tools](https://www.chromium.org/developers/how-tos/depottools). * [Git](https://git-scm.com/). This is provided by Xcode on macOS and by depot_tools on Windows. * [Python](https://www.python.org/). This is provided by the operating system @@ -57,12 +57,12 @@ The main source code repository is a Git repository hosted at https://chromium.googlesource.com/crashpad/crashpad. Although it is possible to check out this repository directly with `git clone`, Crashpad’s dependencies are managed by -[`gclient`](https://dev.chromium.org/developers/how-tos/depottools#TOC-gclient) +[`gclient`](https://www.chromium.org/developers/how-tos/depottools#TOC-gclient) instead of Git submodules, so to work on Crashpad, it is best to use `fetch` to get the source code. `fetch` and `gclient` are part of the -[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s +[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s no need to install them separately. ### Initial Checkout @@ -86,28 +86,25 @@ $ gclient sync ## Building -Crashpad uses [GYP](https://gyp.gsrc.io/) to generate -[Ninja](https://ninja-build.org/) build files. The build is described by `.gyp` -files throughout the Crashpad source code tree. The -[`build/gyp_crashpad.py`](https://chromium.googlesource.com/crashpad/crashpad/+/master/build/gyp_crashpad.py) -script runs GYP properly for Crashpad, and is also called when you run `fetch -crashpad`, `gclient sync`, or `gclient runhooks`. +### Windows, Mac, Linux, Fuchsia -The Ninja build files and build output are in the `out` directory. Both debug- -and release-mode configurations are available. The examples below show the debug -configuration. To build and test the release configuration, substitute `Release` -for `Debug`. On Windows, four configurations are available: `Debug` and -`Release` produce 32-bit x86 executables, and `Debug_x64` and `Release_x64` -produce x86_64 executables. +On Windows, Mac, Linux, and Fuchsia Crashpad uses +[GN](https://gn.googlesource.com/gn) to generate +[Ninja](https://ninja-build.org/) build files. For example, ``` $ cd ~/crashpad/crashpad -$ ninja -C out/Debug +$ gn gen out/Default +$ ninja -C out/Default ``` -Ninja is part of the -[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s -no need to install it separately. +You can then use `gn args out/Default` or edit `out/Default/args.gn` to +configure the build, for example things like `is_debug=true` or +`target_cpu="x86"`. + +GN and Ninja are part of the +[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s +no need to install them separately. ### Android @@ -119,7 +116,7 @@ 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-r15b`. +`~/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). @@ -133,8 +130,8 @@ desired. To build a standalone toolchain targeting 64-bit ARM and API level 21 ``` $ cd ~ -$ python android-ndk-r15b/build/tools/make_standalone_toolchain.py \ - --arch=arm64 --api=21 --install-dir=android-ndk-r15b_arm64_api21 +$ python android-ndk-r16/build/tools/make_standalone_toolchain.py \ + --arch=arm64 --api=21 --install-dir=android-ndk-r16_arm64_api21 ``` Note that Chrome uses Android API level 21 for 64-bit platforms and 16 for @@ -152,7 +149,7 @@ operation. ``` $ cd ~/crashpad/crashpad $ python build/gyp_crashpad_android.py \ - --ndk ~/android-ndk-r15b_arm64_api21 \ + --ndk ~/android-ndk-r16_arm64_api21 \ --generator-output out/android_arm64_api21 ``` @@ -200,7 +197,7 @@ $ python build/run_tests.py out/Debug On Windows, `end_to_end_test.py` requires the CDB debugger, installed with [Debugging Tools for -Windows](https://msdn.microsoft.com/library/windows/hardware/ff551063.aspx). +Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). This can be installed either as part of the [Windows Driver Kit](https://go.microsoft.com/fwlink/p?LinkID=239721) or the [Windows SDK](https://go.microsoft.com/fwlink/p?LinkID=271979). If the Windows SDK has @@ -210,40 +207,23 @@ Software Development Kit. ### Android -To test on Android, use [ADB (Android Debug -Bridge)](https://developer.android.com/studio/command-line/adb.html) to `adb -push` test executables and test data to a device or emulator, then use `adb -shell` to get a shell to run the test executables from. ADB is part of the -[Android SDK](https://developer.android.com/sdk/). Note that it is sufficient to -install just the command-line tools. The entire Android Studio IDE is not -necessary to obtain ADB. +To test on Android, [ADB (Android Debug +Bridge)](https://developer.android.com/studio/command-line/adb.html) from the +[Android SDK](https://developer.android.com/sdk/) must be in the `PATH`. Note +that it is sufficient to install just the command-line tools from the Android +SDK. The entire Android Studio IDE is not necessary to obtain ADB. -This example runs `crashpad_test_test` on a device. This test executable has a -run-time dependency on a second executable and a test data file, which are also -transferred to the device prior to running the test. - -``` -$ cd ~/crashpad/crashpad -$ adb push out/android_arm64_api21/out/Debug/crashpad_test_test /data/local/tmp/ -[100%] /data/local/tmp/crashpad_test_test -$ adb push \ - out/android_arm64_api21/out/Debug/crashpad_test_test_multiprocess_exec_test_child \ - /data/local/tmp/ -[100%] /data/local/tmp/crashpad_test_test_multiprocess_exec_test_child -$ adb shell mkdir -p /data/local/tmp/crashpad_test_data_root/test -$ adb push test/test_paths_test_data_root.txt \ - /data/local/tmp/crashpad_test_data_root/test/ -[100%] /data/local/tmp/crashpad_test_data_root/test/test_paths_test_data_root.txt -$ adb shell -device:/ $ cd /data/local/tmp -device:/data/local/tmp $ CRASHPAD_TEST_DATA_ROOT=crashpad_test_data_root \ - ./crashpad_test_test -``` +When asked to test an Android build directory, `run_tests.py` will detect a +single connected Android device (including an emulator). If multiple devices are +connected, one may be chosen explicitly with the `ANDROID_DEVICE` environment +variable. `run_tests.py` will upload test executables and data to a temporary +location on the detected or selected device, run them, and clean up after itself +when done. ## Contributing Crashpad’s contribution process is very similar to [Chromium’s contribution -process](https://dev.chromium.org/developers/contributing-code). +process](https://www.chromium.org/developers/contributing-code). ### Code Review @@ -256,7 +236,7 @@ must be sent to an appropriate reviewer, with a Cc sent to file specifies this environment to `git-cl`. `git-cl` is part of the -[depot_tools](https://dev.chromium.org/developers/how-tos/depottools). There’s +[depot_tools](https://www.chromium.org/developers/how-tos/depottools). There’s no need to install it separately. ``` @@ -282,7 +262,7 @@ patch set with `git cl upload` and let your reviewer know you’ve addressed the feedback. The most recently uploaded patch set on a review may be tested on a [try -server](https://dev.chromium.org/developers/testing/try-server-usage) by running +server](https://www.chromium.org/developers/testing/try-server-usage) by running `git cl try` or by clicking the “CQ Dry Run” button in Gerrit. These set the “Commit-Queue: +1” label. This does not mean that the patch will be committed, but the try server and commit queue share infrastructure and a Gerrit label. The @@ -294,7 +274,7 @@ Crashpad and Chromium committers. After code review is complete and “Code-Review: +1” has been received from all reviewers, the patch can be submitted to Crashpad’s [commit -queue](https://dev.chromium.org/developers/testing/commit-queue) by clicking the +queue](https://www.chromium.org/developers/testing/commit-queue) by clicking the “Submit to CQ” button in Gerrit. This sets the “Commit-Queue: +2” label, which tests the patch on the try server before landing it. Commit queue access is available to Crashpad and Chromium committers. diff --git a/doc/status.md b/doc/status.md index be4503a1..dcb6e62e 100644 --- a/doc/status.md +++ b/doc/status.md @@ -21,7 +21,7 @@ limitations under the License. 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://dev.chromium.org/Home) on macOS as of [March +[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). diff --git a/doc/support/generate_doxygen.py b/doc/support/generate_doxygen.py index 11dd0ad0..a93028df 100755 --- a/doc/support/generate_doxygen.py +++ b/doc/support/generate_doxygen.py @@ -37,7 +37,7 @@ def main(args): elif os.path.exists(output_dir): os.unlink(output_dir) - os.makedirs(output_dir, 0755) + os.makedirs(output_dir, 0o755) doxy_file = os.path.join('doc', 'support', 'crashpad.doxy') subprocess.check_call(['doxygen', doxy_file]) diff --git a/handler/BUILD.gn b/handler/BUILD.gn new file mode 100644 index 00000000..7928461b --- /dev/null +++ b/handler/BUILD.gn @@ -0,0 +1,334 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +static_library("handler") { + sources = [ + "crash_report_upload_thread.cc", + "crash_report_upload_thread.h", + "handler_main.cc", + "handler_main.h", + "minidump_to_upload_parameters.cc", + "minidump_to_upload_parameters.h", + "prune_crash_reports_thread.cc", + "prune_crash_reports_thread.h", + "user_stream_data_source.cc", + "user_stream_data_source.h", + ] + + if (crashpad_is_mac) { + sources += [ + "mac/crash_report_exception_handler.cc", + "mac/crash_report_exception_handler.h", + "mac/exception_handler_server.cc", + "mac/exception_handler_server.h", + "mac/file_limit_annotation.cc", + "mac/file_limit_annotation.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android) { + set_sources_assignment_filter([]) + sources += [ + "linux/crash_report_exception_handler.cc", + "linux/crash_report_exception_handler.h", + "linux/exception_handler_server.cc", + "linux/exception_handler_server.h", + ] + } + + if (crashpad_is_win) { + sources += [ + "win/crash_report_exception_handler.cc", + "win/crash_report_exception_handler.h", + ] + } + + 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 = [ + "../client", + "../compat", + "../minidump", + "../snapshot", + "../third_party/mini_chromium:base", + "../tools:tool_support", + "../util", + ] + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } +} + +source_set("handler_test") { + testonly = true + + sources = [ + "minidump_to_upload_parameters_test.cc", + ] + + if (crashpad_is_linux || crashpad_is_android) { + sources += [ "linux/exception_handler_server_test.cc" ] + } + + if (crashpad_is_win) { + sources += [ "crashpad_handler_test.cc" ] + } + + deps = [ + ":handler", + "../client", + "../compat", + "../snapshot", + "../snapshot:test_support", + "../test", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_win) { + data_deps = [ + ":crashpad_handler_test_extended_handler", + ":fake_handler_that_crashes_at_startup", + ] + } +} + +crashpad_executable("crashpad_handler") { + sources = [ + "main.cc", + ] + + deps = [ + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + ] + + 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" ] + } + } +} + +# There is not any normal way to package native executables in an Android APK. +# It is normal to package native code as a loadable module but Android's APK +# installer will ignore files not named like a shared object, so give the +# handler executable an acceptable name. +if (crashpad_is_android) { + copy("crashpad_handler_module") { + deps = [ + ":crashpad_handler", + ] + + sources = [ + "$root_out_dir/crashpad_handler", + ] + + outputs = [ + "$root_out_dir/libcrashpad_handler.so", + ] + } +} + +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", + ] + + # 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 + # directly. Instead, use crashpad_handler_console. + output_extension = "com" + + deps = [ + ":handler", + "../build:default_exe_manifest_win", + "../compat", + "../third_party/mini_chromium:base", + ] + } + + copy("crashpad_handler_console") { + 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", + ] + + deps = [ + "../client", + "../test", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + ] + } + + crashpad_executable("crashy_program") { + testonly = true + + sources = [ + "win/crashy_test_program.cc", + ] + + deps = [ + "../client", + "../third_party/mini_chromium:base", + ] + } + + crashpad_executable("crashy_signal") { + testonly = true + + sources = [ + "win/crashy_signal.cc", + ] + + cflags = [ "/wd4702" ] # Unreachable code. + + deps = [ + "../client", + "../third_party/mini_chromium:base", + ] + } + + crashpad_executable("fake_handler_that_crashes_at_startup") { + testonly = true + + sources = [ + "win/fake_handler_that_crashes_at_startup.cc", + ] + } + + crashpad_executable("hanging_program") { + testonly = true + + sources = [ + "win/hanging_program.cc", + ] + + deps = [ + "../client", + "../third_party/mini_chromium:base", + ] + } + + crashpad_loadable_module("loader_lock_dll") { + testonly = true + + sources = [ + "win/loader_lock_dll.cc", + ] + } + + crashpad_executable("self_destroying_program") { + testonly = true + + sources = [ + "win/self_destroying_test_program.cc", + ] + + deps = [ + "../client", + "../compat", + "../snapshot", + "../third_party/mini_chromium:base", + ] + } + + if (current_cpu == "x86") { + # Cannot create an x64 DLL with embedded debug info. + crashpad_executable("crashy_z7_loader") { + testonly = true + + sources = [ + "win/crashy_test_z7_loader.cc", + ] + + deps = [ + "../client", + "../test", + "../third_party/mini_chromium:base", + ] + } + } +} diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index 7038108a..4783ecb2 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -27,6 +27,7 @@ #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "client/settings.h" +#include "handler/minidump_to_upload_parameters.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "snapshot/module_snapshot.h" #include "util/file/file_reader.h" @@ -44,106 +45,6 @@ namespace crashpad { -namespace { - -void InsertOrReplaceMapEntry(std::map* map, - const std::string& key, - const std::string& value) { - std::string old_value; - if (!MapInsertOrReplace(map, key, value, &old_value)) { - LOG(WARNING) << "duplicate key " << key << ", discarding value " - << old_value; - } -} - -// Given a minidump file readable by |minidump_file_reader|, returns a map of -// key-value pairs to use as HTTP form parameters for upload to a Breakpad -// server. The map is built by combining the process simple annotations map with -// each module’s simple annotations map. In the case of duplicate keys, the map -// will retain the first value found for any key, and will log a warning about -// discarded values. Each module’s annotations vector is also examined and built -// into a single string value, with distinct elements separated by newlines, and -// stored at the key named “list_annotations”, which supersedes any other key -// found by that name. The client ID stored in the minidump is converted to -// a string and stored at the key named “guid”, which supersedes any other key -// found by that name. -// -// In the event of an error reading the minidump file, a message will be logged. -std::map BreakpadHTTPFormParametersFromMinidump( - FileReaderInterface* minidump_file_reader) { - ProcessSnapshotMinidump minidump_process_snapshot; - if (!minidump_process_snapshot.Initialize(minidump_file_reader)) { - return std::map(); - } - - std::map parameters = - minidump_process_snapshot.AnnotationsSimpleMap(); - - std::string list_annotations; - for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) { - for (const auto& kv : module->AnnotationsSimpleMap()) { - if (!parameters.insert(kv).second) { - LOG(WARNING) << "duplicate key " << kv.first << ", discarding value " - << kv.second; - } - } - - for (std::string annotation : module->AnnotationsVector()) { - list_annotations.append(annotation); - list_annotations.append("\n"); - } - } - - if (!list_annotations.empty()) { - // Remove the final newline character. - list_annotations.resize(list_annotations.size() - 1); - - InsertOrReplaceMapEntry(¶meters, "list_annotations", list_annotations); - } - - UUID client_id; - minidump_process_snapshot.ClientID(&client_id); - InsertOrReplaceMapEntry(¶meters, "guid", client_id.ToString()); - - return parameters; -} - -// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to -// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire() -// triggers an immediate call. Armed upon construction. -class CallRecordUploadAttempt { - public: - CallRecordUploadAttempt(CrashReportDatabase* database, - const CrashReportDatabase::Report* report) - : database_(database), - report_(report) { - } - - ~CallRecordUploadAttempt() { - Fire(); - } - - void Fire() { - if (report_) { - database_->RecordUploadAttempt(report_, false, std::string()); - } - - Disarm(); - } - - void Disarm() { - report_ = nullptr; - } - - private: - CrashReportDatabase* database_; // weak - const CrashReportDatabase::Report* report_; // weak - - DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt); -}; - -} // namespace - CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database, const std::string& url, const Options& options) @@ -157,11 +58,18 @@ CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database, : WorkerThread::kIndefiniteWait, this), known_pending_report_uuids_(), - database_(database) {} + database_(database) { + DCHECK(!url_.empty()); +} CrashReportUploadThread::~CrashReportUploadThread() { } +void CrashReportUploadThread::ReportPending(const UUID& report_uuid) { + known_pending_report_uuids_.PushBack(report_uuid); + thread_.DoWorkNow(); +} + void CrashReportUploadThread::Start() { thread_.Start( options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait); @@ -171,11 +79,6 @@ void CrashReportUploadThread::Stop() { thread_.Stop(); } -void CrashReportUploadThread::ReportPending(const UUID& report_uuid) { - known_pending_report_uuids_.PushBack(report_uuid); - thread_.DoWorkNow(); -} - void CrashReportUploadThread::ProcessPendingReports() { std::vector known_report_uuids = known_pending_report_uuids_.Drain(); for (const UUID& report_uuid : known_report_uuids) { @@ -239,9 +142,8 @@ void CrashReportUploadThread::ProcessPendingReport( Settings* const settings = database_->GetSettings(); bool uploads_enabled; - if (url_.empty() || - (!report.upload_explicitly_requested && - (!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled))) { + if (!report.upload_explicitly_requested && + (!settings->GetUploadsEnabled(&uploads_enabled) || !uploads_enabled)) { // Don’t attempt an upload if there’s no URL to upload to. Allow upload if // it has been explicitly requested by the user, otherwise, respect the // upload-enabled state stored in the database’s settings. @@ -290,7 +192,7 @@ void CrashReportUploadThread::ProcessPendingReport( } } - const CrashReportDatabase::Report* upload_report; + std::unique_ptr upload_report; CrashReportDatabase::OperationStatus status = database_->GetReportForUploading(report.uuid, &upload_report); switch (status) { @@ -317,18 +219,16 @@ void CrashReportUploadThread::ProcessPendingReport( return; } - CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report); - std::string response_body; - UploadResult upload_result = UploadReport(upload_report, &response_body); + UploadResult upload_result = + UploadReport(upload_report.get(), &response_body); switch (upload_result) { case UploadResult::kSuccess: - call_record_upload_attempt.Disarm(); - database_->RecordUploadAttempt(upload_report, true, response_body); + database_->RecordUploadComplete(std::move(upload_report), response_body); break; case UploadResult::kPermanentFailure: case UploadResult::kRetry: - call_record_upload_attempt.Fire(); + upload_report.reset(); // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod() // if the result was kRetry and the report hasn’t already been retried @@ -340,17 +240,18 @@ void CrashReportUploadThread::ProcessPendingReport( } CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( - const CrashReportDatabase::Report* report, + 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 parameters; - FileReader minidump_file_reader; - if (!minidump_file_reader.Open(report->file_path)) { - // If the minidump file can’t be opened, all hope is lost. - return UploadResult::kPermanentFailure; - } - - FileOffset start_offset = minidump_file_reader.SeekGet(); + FileReader* reader = report->Reader(); + FileOffset start_offset = reader->SeekGet(); if (start_offset < 0) { return UploadResult::kPermanentFailure; } @@ -359,9 +260,13 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( // minidump file. This may result in its being uploaded with few or no // parameters, but as long as there’s a dump file, the server can decide what // to do with it. - parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader); + ProcessSnapshotMinidump minidump_process_snapshot; + if (minidump_process_snapshot.Initialize(reader)) { + parameters = + BreakpadHTTPFormParametersFromMinidump(&minidump_process_snapshot); + } - if (!minidump_file_reader.SeekSet(start_offset)) { + if (!reader->SeekSet(start_offset)) { return UploadResult::kPermanentFailure; } @@ -379,15 +284,15 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( } } - http_multipart_builder.SetFileAttachment( - kMinidumpKey, -#if defined(OS_WIN) - base::UTF16ToUTF8(report->file_path.BaseName().value()), -#else - report->file_path.BaseName().value(), -#endif - &minidump_file_reader, - "application/octet-stream"); + for (const auto& it : report->GetAttachments()) { + http_multipart_builder.SetFileAttachment( + it.first, it.first, it.second, "application/octet-stream"); + } + + http_multipart_builder.SetFileAttachment(kMinidumpKey, + report->uuid.ToString() + ".dmp", + reader, + "application/octet-stream"); std::unique_ptr http_transport(HTTPTransport::Create()); HTTPHeaders content_headers; @@ -429,6 +334,7 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( } return UploadResult::kSuccess; +#endif // OS_ANDROID } void CrashReportUploadThread::DoWork(const WorkerThread* thread) { diff --git a/handler/crash_report_upload_thread.h b/handler/crash_report_upload_thread.h index cdd1502b..2ec1147d 100644 --- a/handler/crash_report_upload_thread.h +++ b/handler/crash_report_upload_thread.h @@ -22,6 +22,7 @@ #include "client/crash_report_database.h" #include "util/misc/uuid.h" #include "util/stdlib/thread_safe_vector.h" +#include "util/thread/stoppable.h" #include "util/thread/worker_thread.h" namespace crashpad { @@ -39,7 +40,8 @@ namespace crashpad { //! It also catches reports that are added without a ReportPending() signal //! being caught. This may happen if crash reports are added to the database by //! other processes. -class CrashReportUploadThread : public WorkerThread::Delegate { +class CrashReportUploadThread : public WorkerThread::Delegate, + public Stoppable { public: //! \brief Options to be passed to the CrashReportUploadThread constructor. struct Options { @@ -70,11 +72,22 @@ class CrashReportUploadThread : public WorkerThread::Delegate { const Options& options); ~CrashReportUploadThread(); + //! \brief Informs the upload thread that a new pending report has been added + //! to the database. + //! + //! \param[in] report_uuid The unique identifier of the newly added pending + //! report. + //! + //! This method may be called from any thread. + void ReportPending(const UUID& report_uuid); + + // Stoppable: + //! \brief Starts a dedicated upload thread, which executes ThreadMain(). //! //! This method may only be be called on a newly-constructed object or after //! a call to Stop(). - void Start(); + void Start() override; //! \brief Stops the upload thread. //! @@ -88,16 +101,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate { //! //! This method may be called from any thread other than the upload thread. //! It is expected to only be called from the same thread that called Start(). - void Stop(); - - //! \brief Informs the upload thread that a new pending report has been added - //! to the database. - //! - //! \param[in] report_uuid The unique identifier of the newly added pending - //! report. - //! - //! This method may be called from any thread. - void ReportPending(const UUID& report_uuid); + void Stop() override; private: //! \brief The result code from UploadReport(). @@ -148,14 +152,14 @@ class CrashReportUploadThread : public WorkerThread::Delegate { //! \param[in] report The report to upload. The caller is responsible for //! calling CrashReportDatabase::GetReportForUploading() before calling //! this method, and for calling - //! CrashReportDatabase::RecordUploadAttempt() after calling this method. + //! CrashReportDatabase::RecordUploadComplete() after calling this method. //! \param[out] response_body If the upload attempt is successful, this will //! be set to the response body sent by the server. Breakpad-type servers //! provide the crash ID assigned by the server in the response body. //! //! \return A member of UploadResult indicating the result of the upload //! attempt. - UploadResult UploadReport(const CrashReportDatabase::Report* report, + UploadResult UploadReport(const CrashReportDatabase::UploadReport* report, std::string* response_body); // WorkerThread::Delegate: diff --git a/handler/crashpad_handler.md b/handler/crashpad_handler.md index 5eff39ae..003ee2ef 100644 --- a/handler/crashpad_handler.md +++ b/handler/crashpad_handler.md @@ -73,6 +73,13 @@ when run normally from a shell using only the basename (without an explicit stdio will be hooked up as expected to the parent console so that logging output will be visible. +On Linux/Android, the handler may create a crash dump for its parent process +using **--trace-parent-with-exception**. In this mode, the handler process +creates a crash dump for its parent and exits. Alternatively, the handler may +be launched with **--initial-client-fd** which will start the server connected +to an initial client. The server will exit when all connected client sockets are +closed. + It is not normally appropriate to invoke this program directly. Usually, it will be invoked by a Crashpad client using the Crashpad client library, or started by another system service. On macOS, arbitrary programs may be run with a Crashpad @@ -238,6 +245,18 @@ establish the Crashpad client environment before running a program. parent process. This option is only valid on macOS. Use of this option is discouraged. It should not be used absent extraordinary circumstances. + * **--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. + * **--url**=_URL_ If uploads are enabled, sends crash reports to the Breakpad-type crash report diff --git a/handler/crashpad_handler_test.cc b/handler/crashpad_handler_test.cc index 65fed90f..afa12014 100644 --- a/handler/crashpad_handler_test.cc +++ b/handler/crashpad_handler_test.cc @@ -28,7 +28,7 @@ #include "test/test_paths.h" #include "test/win/win_multiprocess_with_temp_dir.h" #include "util/file/file_reader.h" -#include "util/win/capture_context.h" +#include "util/misc/capture_context.h" namespace crashpad { namespace test { @@ -93,7 +93,7 @@ void CrashWithExtendedHandler::ValidateGeneratedDump() { ASSERT_TRUE(database); std::vector reports; - ASSERT_EQ(database->GetCompletedReports(&reports), + ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); ASSERT_EQ(reports.size(), 1u); @@ -133,7 +133,13 @@ void CrashWithExtendedHandler::ValidateGeneratedDump() { EXPECT_EQ(found_extension_streams, 1u); } -TEST(CrashpadHandler, ExtensibilityCalloutsWork) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/845011 +#define MAYBE_ExtensibilityCalloutsWork DISABLED_ExtensibilityCalloutsWork +#else +#define MAYBE_ExtensibilityCalloutsWork ExtensibilityCalloutsWork +#endif +TEST(CrashpadHandler, MAYBE_ExtensibilityCalloutsWork) { WinMultiprocessWithTempDir::Run(); } diff --git a/handler/crashpad_handler_test_extended_handler.cc b/handler/crashpad_handler_test_extended_handler.cc index e1c18e83..7a8f4563 100644 --- a/handler/crashpad_handler_test_extended_handler.cc +++ b/handler/crashpad_handler_test_extended_handler.cc @@ -56,7 +56,7 @@ int ExtendedHandlerMain(int argc, char* argv[]) { } // namespace -#if defined(OS_MACOSX) +#if defined(OS_POSIX) int main(int argc, char* argv[]) { return ExtendedHandlerMain(argc, argv); @@ -68,4 +68,4 @@ int wmain(int argc, wchar_t* argv[]) { return crashpad::ToolSupport::Wmain(argc, argv, &ExtendedHandlerMain); } -#endif // OS_MACOSX +#endif // OS_POSIX diff --git a/handler/fuchsia/crash_report_exception_handler.cc b/handler/fuchsia/crash_report_exception_handler.cc new file mode 100644 index 00000000..1da09851 --- /dev/null +++ b/handler/fuchsia/crash_report_exception_handler.cc @@ -0,0 +1,182 @@ +// 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 + +#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* process_annotations, + const std::map* 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 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 new file mode 100644 index 00000000..5043d7e9 --- /dev/null +++ b/handler/fuchsia/crash_report_exception_handler.h @@ -0,0 +1,105 @@ +// 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 +#include + +#include +#include + +#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* process_annotations, + const std::map* 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* process_annotations_; // weak + const std::map* 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 new file mode 100644 index 00000000..63153d28 --- /dev/null +++ b/handler/fuchsia/exception_handler_server.cc @@ -0,0 +1,60 @@ +// 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 +#include + +#include + +#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 new file mode 100644 index 00000000..184d1489 --- /dev/null +++ b/handler/fuchsia/exception_handler_server.h @@ -0,0 +1,55 @@ +// 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 aefd4de5..60a6f251 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -39,12 +39,18 @@ 'crash_report_upload_thread.h', 'handler_main.cc', 'handler_main.h', + 'linux/crash_report_exception_handler.cc', + 'linux/crash_report_exception_handler.h', + 'linux/exception_handler_server.cc', + 'linux/exception_handler_server.h', 'mac/crash_report_exception_handler.cc', 'mac/crash_report_exception_handler.h', 'mac/exception_handler_server.cc', 'mac/exception_handler_server.h', 'mac/file_limit_annotation.cc', 'mac/file_limit_annotation.h', + 'minidump_to_upload_parameters.cc', + 'minidump_to_upload_parameters.h', 'prune_crash_reports_thread.cc', 'prune_crash_reports_thread.h', 'user_stream_data_source.cc', @@ -52,6 +58,13 @@ 'win/crash_report_exception_handler.cc', 'win/crash_report_exception_handler.h', ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^linux/'], + ], + }], + ], }, { 'target_name': 'crashpad_handler', diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 2e46f86a..412ee112 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -34,6 +34,7 @@ #include "base/logging.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/scoped_generic.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" @@ -46,6 +47,7 @@ #include "handler/prune_crash_reports_thread.h" #include "tools/tool_support.h" #include "util/file/file_io.h" +#include "util/misc/address_types.h" #include "util/misc/metrics.h" #include "util/misc/paths.h" #include "util/numeric/in_range_cast.h" @@ -54,7 +56,13 @@ #include "util/string/split_string.h" #include "util/synchronization/semaphore.h" -#if defined(OS_MACOSX) +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include + +#include "handler/linux/crash_report_exception_handler.h" +#include "handler/linux/exception_handler_server.h" +#include "util/posix/signals.h" +#elif defined(OS_MACOSX) #include #include @@ -74,6 +82,15 @@ #include "util/win/handle.h" #include "util/win/initial_client_data.h" #include "util/win/session_end_watcher.h" +#elif defined(OS_FUCHSIA) +#include +#include + +#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" #endif // OS_MACOSX namespace crashpad { @@ -123,6 +140,13 @@ void Usage(const base::FilePath& me) { " --reset-own-crash-exception-port-to-system-default\n" " reset the server's exception handler to default\n" #endif // OS_MACOSX +#if defined(OS_LINUX) || defined(OS_ANDROID) +" --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" " --help display this help and exit\n" @@ -142,6 +166,10 @@ struct Options { std::string mach_service; int handshake_fd; 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; #elif defined(OS_WIN) std::string pipe_name; InitialClientData initial_client_data; @@ -208,7 +236,9 @@ class CallMetricsRecordNormalExit { DISALLOW_COPY_AND_ASSIGN(CallMetricsRecordNormalExit); }; -#if defined(OS_MACOSX) +#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); @@ -244,7 +274,9 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { } Metrics::HandlerCrashed(metrics_code); - Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + struct sigaction* old_action = + g_old_crash_signal_handlers.ActionForSignal(sig); + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); } void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { @@ -252,6 +284,8 @@ void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { 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 @@ -290,6 +324,23 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) { 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) LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr; @@ -345,6 +396,22 @@ void InstallCrashHandler() { 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) { @@ -381,6 +448,16 @@ void MonitorSelf(const Options& options) { // 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 (!crashpad_client.StartHandlerAtCrash(executable_path, + options.database, + base::FilePath(), + options.url, + options.annotations, + extra_arguments)) { + return; + } +#else if (!crashpad_client.StartHandler(executable_path, options.database, base::FilePath(), @@ -391,12 +468,33 @@ void MonitorSelf(const Options& options) { false)) { return; } +#endif // Make sure that appropriate metrics will be recorded on crash before this // process is terminated. ReinstallCrashHandler(); } +class ScopedStoppable { + public: + ScopedStoppable() = default; + + ~ScopedStoppable() { + if (stoppable_) { + stoppable_->Stop(); + } + } + + void Reset(Stoppable* stoppable) { stoppable_.reset(stoppable); } + + Stoppable* Get() { return stoppable_.get(); } + + private: + std::unique_ptr stoppable_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStoppable); +}; + } // namespace int HandlerMain(int argc, @@ -437,6 +535,11 @@ int HandlerMain(int argc, #if defined(OS_MACOSX) kOptionResetOwnCrashExceptionPortToSystemDefault, #endif // OS_MACOSX +#if defined(OS_LINUX) || defined(OS_ANDROID) + kOptionTraceParentWithException, + kOptionInitialClientFD, + kOptionSanitizationInformation, +#endif kOptionURL, // Standard options. @@ -485,6 +588,17 @@ int HandlerMain(int argc, nullptr, 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}, +#endif // OS_LINUX || OS_ANDROID {"url", required_argument, nullptr, kOptionURL}, {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, @@ -499,6 +613,11 @@ int HandlerMain(int argc, 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; +#endif int opt; while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { @@ -588,6 +707,32 @@ int HandlerMain(int argc, break; } #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)) { + ToolSupport::UsageHint(me, + "failed to parse --sanitization-information"); + return ExitFailure(); + } + break; + } +#endif // OS_LINUX || OS_ANDROID case kOptionURL: { options.url = optarg; break; @@ -632,6 +777,20 @@ int HandlerMain(int argc, me, "--initial-client-data and --pipe-name are incompatible"); return ExitFailure(); } +#elif defined(OS_LINUX) || defined(OS_ANDROID) + if (!options.exception_information_address && + options.initial_client_fd == kInvalidFileHandle) { + ToolSupport::UsageHint( + me, "--trace-parent-with-exception or --initial_client_fd is required"); + return ExitFailure(); + } + if (options.sanitization_information_address && + !options.exception_information_address) { + ToolSupport::UsageHint( + me, + "--sanitization_information requires --trace-parent-with-exception"); + return ExitFailure(); + } #endif // OS_MACOSX if (options.database.empty()) { @@ -674,6 +833,57 @@ int HandlerMain(int argc, } } + std::unique_ptr database( + CrashReportDatabase::Initialize(options.database)); + if (!database) { + return ExitFailure(); + } + + ScopedStoppable upload_thread; + if (!options.url.empty()) { + // TODO(scottmg): options.rate_limit should be removed when we have a + // configurable database setting to control upload limiting. + // See https://crashpad.chromium.org/bug/23. + CrashReportUploadThread::Options upload_thread_options; + upload_thread_options.identify_client_via_url = + options.identify_client_via_url; + upload_thread_options.rate_limit = options.rate_limit; + upload_thread_options.upload_gzip = options.upload_gzip; + upload_thread_options.watch_pending_reports = options.periodic_tasks; + + upload_thread.Reset(new CrashReportUploadThread( + database.get(), options.url, upload_thread_options)); + upload_thread.Get()->Start(); + } + + CrashReportExceptionHandler exception_handler( + database.get(), + static_cast(upload_thread.Get()), + &options.annotations, +#if defined(OS_FUCHSIA) + // TODO(scottmg): Process level file attachments, and for all platforms. + nullptr, +#endif + user_stream_sources); + + #if defined(OS_LINUX) || defined(OS_ANDROID) + if (options.exception_information_address) { + 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(); + } +#endif // OS_LINUX || OS_ANDROID + + ScopedStoppable prune_thread; + if (options.periodic_tasks) { + prune_thread.Reset(new PruneCrashReportThread( + database.get(), PruneCondition::GetDefault())); + prune_thread.Get()->Start(); + } + #if defined(OS_MACOSX) if (options.mach_service.empty()) { // Don’t do this when being run by launchd. See launchd.plist(5). @@ -727,6 +937,29 @@ int HandlerMain(int argc, 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 base::GlobalHistogramAllocator* histogram_allocator = nullptr; @@ -742,52 +975,21 @@ int HandlerMain(int argc, Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted); - std::unique_ptr database( - CrashReportDatabase::Initialize(options.database)); - if (!database) { - return ExitFailure(); - } - - // TODO(scottmg): options.rate_limit should be removed when we have a - // configurable database setting to control upload limiting. - // See https://crashpad.chromium.org/bug/23. - CrashReportUploadThread::Options upload_thread_options; - upload_thread_options.identify_client_via_url = - options.identify_client_via_url; - upload_thread_options.rate_limit = options.rate_limit; - upload_thread_options.upload_gzip = options.upload_gzip; - upload_thread_options.watch_pending_reports = options.periodic_tasks; - CrashReportUploadThread upload_thread(database.get(), - options.url, - upload_thread_options); - upload_thread.Start(); - - std::unique_ptr prune_thread; - if (options.periodic_tasks) { - prune_thread.reset(new PruneCrashReportThread( - database.get(), PruneCondition::GetDefault())); - prune_thread->Start(); - } - - CrashReportExceptionHandler exception_handler(database.get(), - &upload_thread, - &options.annotations, - user_stream_sources); - #if defined(OS_WIN) if (options.initial_client_data.IsValid()) { exception_handler_server.InitializeWithInheritedDataForInitialClient( options.initial_client_data, &exception_handler); } +#elif defined(OS_LINUX) || defined(OS_ANDROID) + if (options.initial_client_fd == kInvalidFileHandle || + !exception_handler_server.InitializeWithClient( + ScopedFileHandle(options.initial_client_fd))) { + return ExitFailure(); + } #endif // OS_WIN exception_handler_server.Run(&exception_handler); - upload_thread.Stop(); - if (prune_thread) { - prune_thread->Stop(); - } - return EXIT_SUCCESS; } diff --git a/handler/handler_test.gyp b/handler/handler_test.gyp index 588305f8..c047e7c9 100644 --- a/handler/handler_test.gyp +++ b/handler/handler_test.gyp @@ -17,6 +17,48 @@ '../build/crashpad.gypi', ], 'targets': [ + { + 'target_name': 'crashpad_handler_test', + 'type': 'executable', + 'dependencies': [ + 'crashpad_handler_test_extended_handler', + 'handler.gyp:crashpad_handler_lib', + '../client/client.gyp:crashpad_client', + '../compat/compat.gyp:crashpad_compat', + '../snapshot/snapshot.gyp:crashpad_snapshot', + '../snapshot/snapshot_test.gyp:crashpad_snapshot_test_lib', + '../test/test.gyp:crashpad_gtest_main', + '../test/test.gyp:crashpad_test', + '../third_party/gtest/gtest.gyp:gtest', + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../util/util.gyp:crashpad_util', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'crashpad_handler_test.cc', + 'linux/exception_handler_server_test.cc', + 'minidump_to_upload_parameters_test.cc', + ], + 'conditions': [ + ['OS!="win"', { + 'dependencies!': [ + 'crashpad_handler_test_extended_handler', + ], + 'sources!': [ + 'crashpad_handler_test.cc', + ], + }], + ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^linux/'], + ], + }], + ], + }, { 'target_name': 'crashpad_handler_test_extended_handler', 'type': 'executable', @@ -52,28 +94,6 @@ 'win/crash_other_program.cc', ], }, - { - # The handler is only tested on Windows for now. - 'target_name': 'crashpad_handler_test', - 'type': 'executable', - 'dependencies': [ - 'crashpad_handler_test_extended_handler', - 'handler.gyp:crashpad_handler_lib', - '../client/client.gyp:crashpad_client', - '../compat/compat.gyp:crashpad_compat', - '../test/test.gyp:crashpad_gtest_main', - '../test/test.gyp:crashpad_test', - '../third_party/gtest/gtest.gyp:gtest', - '../third_party/mini_chromium/mini_chromium.gyp:base', - '../util/util.gyp:crashpad_util', - ], - 'include_dirs': [ - '..', - ], - 'sources': [ - 'crashpad_handler_test.cc', - ], - }, { 'target_name': 'crashy_program', 'type': 'executable', diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc new file mode 100644 index 00000000..101c49f5 --- /dev/null +++ b/handler/linux/crash_report_exception_handler.cc @@ -0,0 +1,193 @@ +// 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 "handler/linux/crash_report_exception_handler.h" + +#include + +#include "base/logging.h" +#include "client/settings.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/linux/direct_ptrace_connection.h" +#include "util/linux/ptrace_client.h" +#include "util/misc/metrics.h" +#include "util/misc/tri_state.h" +#include "util/misc/uuid.h" + +namespace crashpad { + +CrashReportExceptionHandler::CrashReportExceptionHandler( + CrashReportDatabase* database, + CrashReportUploadThread* upload_thread, + const std::map* process_annotations, + const UserStreamDataSources* user_stream_data_sources) + : database_(database), + upload_thread_(upload_thread), + process_annotations_(process_annotations), + user_stream_data_sources_(user_stream_data_sources) {} + +CrashReportExceptionHandler::~CrashReportExceptionHandler() = default; + +bool CrashReportExceptionHandler::HandleException( + pid_t client_process_id, + const ClientInformation& info) { + Metrics::ExceptionEncountered(); + + DirectPtraceConnection connection; + if (!connection.Initialize(client_process_id)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kDirectPtraceFailed); + return false; + } + + return HandleExceptionWithConnection(&connection, info); +} + +bool CrashReportExceptionHandler::HandleExceptionWithBroker( + pid_t client_process_id, + const ClientInformation& info, + int broker_sock) { + Metrics::ExceptionEncountered(); + + PtraceClient client; + if (!client.Initialize(broker_sock, client_process_id)) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kBrokeredPtraceFailed); + return false; + } + + return HandleExceptionWithConnection(&client, info); +} + +bool CrashReportExceptionHandler::HandleExceptionWithConnection( + PtraceConnection* connection, + const ClientInformation& info) { + ProcessSnapshotLinux process_snapshot; + if (!process_snapshot.Initialize(connection)) { + Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed); + return false; + } + + if (!process_snapshot.InitializeException( + info.exception_information_address)) { + 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) { + 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 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; + } + + process_snapshot.SetReportID(new_report->ReportID()); + + ProcessSnapshot* snapshot = nullptr; + ProcessSnapshotSanitized sanitized; + std::vector 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); + } + } + + Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); + return true; +} + +} // namespace crashpad diff --git a/handler/linux/crash_report_exception_handler.h b/handler/linux/crash_report_exception_handler.h new file mode 100644 index 00000000..24d5995a --- /dev/null +++ b/handler/linux/crash_report_exception_handler.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_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_ +#define CRASHPAD_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_ + +#include +#include + +#include "base/macros.h" +#include "client/crash_report_database.h" +#include "handler/crash_report_upload_thread.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" + +namespace crashpad { + +//! \brief An exception handler that writes crash reports for exceptions +//! to a CrashReportDatabase. +class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { + 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. Report upload is skipped if this + //! value is `nullptr`. + //! \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. + CrashReportExceptionHandler( + CrashReportDatabase* database, + CrashReportUploadThread* upload_thread, + const std::map* process_annotations, + const UserStreamDataSources* user_stream_data_sources); + + ~CrashReportExceptionHandler(); + + // ExceptionHandlerServer::Delegate: + + bool HandleException(pid_t client_process_id, + const ClientInformation& info) override; + + bool HandleExceptionWithBroker(pid_t client_process_id, + const ClientInformation& info, + int broker_sock) override; + + private: + bool HandleExceptionWithConnection(PtraceConnection* connection, + const ClientInformation& info); + + CrashReportDatabase* database_; // weak + CrashReportUploadThread* upload_thread_; // weak + const std::map* process_annotations_; // weak + const UserStreamDataSources* user_stream_data_sources_; // weak + + DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_LINUX_CRASH_REPORT_EXCEPTION_HANDLER_H_ diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc new file mode 100644 index 00000000..748bf6e9 --- /dev/null +++ b/handler/linux/exception_handler_server.cc @@ -0,0 +1,462 @@ +// 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/linux/exception_handler_server.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "util/file/file_io.h" +#include "util/file/filesystem.h" +#include "util/misc/as_underlying_type.h" + +namespace crashpad { + +namespace { + +// Log an error for a socket after an EPOLLERR. +void LogSocketError(int sock) { + int err; + socklen_t err_len = sizeof(err); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) != 0) { + PLOG(ERROR) << "getsockopt"; + } else { + errno = err; + PLOG(ERROR) << "EPOLLERR"; + } +} + +enum class PtraceScope { + kClassic = 0, + kRestricted, + kAdminOnly, + kNoAttach, + kUnknown +}; + +PtraceScope GetPtraceScope() { + const base::FilePath settings_file("/proc/sys/kernel/yama/ptrace_scope"); + if (!IsRegularFile(base::FilePath(settings_file))) { + return PtraceScope::kClassic; + } + + std::string contents; + if (!LoggingReadEntireFile(settings_file, &contents)) { + return PtraceScope::kUnknown; + } + + if (contents.back() != '\n') { + LOG(ERROR) << "format error"; + return PtraceScope::kUnknown; + } + contents.pop_back(); + + int ptrace_scope; + if (!base::StringToInt(contents, &ptrace_scope)) { + LOG(ERROR) << "format error"; + return PtraceScope::kUnknown; + } + + if (ptrace_scope < static_cast(PtraceScope::kClassic) || + ptrace_scope >= static_cast(PtraceScope::kUnknown)) { + LOG(ERROR) << "invalid ptrace scope"; + return PtraceScope::kUnknown; + } + + return static_cast(ptrace_scope); +} + +bool HaveCapSysPtrace() { + struct __user_cap_header_struct cap_header = {}; + struct __user_cap_data_struct cap_data = {}; + + cap_header.pid = getpid(); + + if (capget(&cap_header, &cap_data) != 0) { + PLOG(ERROR) << "capget"; + 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; +} + +bool SendMessageToClient(int client_sock, ServerToClientMessage::Type type) { + ServerToClientMessage message = {}; + message.type = type; + if (type == ServerToClientMessage::kTypeSetPtracer) { + message.pid = getpid(); + } + return LoggingWriteFile(client_sock, &message, sizeof(message)); +} + +class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { + public: + PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {} + ~PtraceStrategyDeciderImpl() = default; + + Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + switch (GetPtraceScope()) { + case PtraceScope::kClassic: + if (getuid() == client_credentials.uid) { + return Strategy::kDirectPtrace; + } + return TryForkingBroker(sock); + + case PtraceScope::kRestricted: + if (!SendMessageToClient(sock, + ServerToClientMessage::kTypeSetPtracer)) { + return Strategy::kError; + } + + Errno status; + if (!LoggingReadFileExactly(sock, &status, sizeof(status))) { + return Strategy::kError; + } + + if (status != 0) { + errno = status; + PLOG(ERROR) << "Handler Client SetPtracer"; + return TryForkingBroker(sock); + } + return Strategy::kDirectPtrace; + + case PtraceScope::kAdminOnly: + if (HaveCapSysPtrace()) { + return Strategy::kDirectPtrace; + } + FALLTHROUGH; + case PtraceScope::kNoAttach: + LOG(WARNING) << "no ptrace"; + return Strategy::kNoPtrace; + + case PtraceScope::kUnknown: + LOG(WARNING) << "Unknown ptrace scope"; + return Strategy::kError; + } + + DCHECK(false); + return Strategy::kError; + } + + private: + static Strategy TryForkingBroker(int client_sock) { + if (!SendMessageToClient(client_sock, + ServerToClientMessage::kTypeForkBroker)) { + return Strategy::kError; + } + + Errno status; + if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) { + return Strategy::kError; + } + + if (status != 0) { + errno = status; + PLOG(ERROR) << "Handler Client ForkBroker"; + return Strategy::kNoPtrace; + } + return Strategy::kUseBroker; + } +}; + +} // namespace + +struct ExceptionHandlerServer::Event { + enum class Type { kShutdown, kClientMessage } type; + + ScopedFileHandle fd; +}; + +ExceptionHandlerServer::ExceptionHandlerServer() + : clients_(), + shutdown_event_(), + strategy_decider_(new PtraceStrategyDeciderImpl()), + delegate_(nullptr), + pollfd_(), + keep_running_(true) {} + +ExceptionHandlerServer::~ExceptionHandlerServer() = default; + +void ExceptionHandlerServer::SetPtraceStrategyDecider( + std::unique_ptr decider) { + strategy_decider_ = std::move(decider); +} + +bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + pollfd_.reset(epoll_create1(EPOLL_CLOEXEC)); + if (!pollfd_.is_valid()) { + PLOG(ERROR) << "epoll_create1"; + return false; + } + + shutdown_event_ = std::make_unique(); + shutdown_event_->type = Event::Type::kShutdown; + shutdown_event_->fd.reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); + if (!shutdown_event_->fd.is_valid()) { + PLOG(ERROR) << "eventfd"; + return false; + } + + epoll_event poll_event; + poll_event.events = EPOLLIN; + poll_event.data.ptr = shutdown_event_.get(); + if (epoll_ctl(pollfd_.get(), + EPOLL_CTL_ADD, + shutdown_event_->fd.get(), + &poll_event) != 0) { + PLOG(ERROR) << "epoll_ctl"; + return false; + } + + if (!InstallClientSocket(std::move(sock))) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +void ExceptionHandlerServer::Run(Delegate* delegate) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + delegate_ = delegate; + + while (keep_running_ && clients_.size() > 0) { + epoll_event poll_event; + int res = HANDLE_EINTR(epoll_wait(pollfd_.get(), &poll_event, 1, -1)); + if (res < 0) { + PLOG(ERROR) << "epoll_wait"; + return; + } + DCHECK_EQ(res, 1); + + Event* eventp = reinterpret_cast(poll_event.data.ptr); + if (eventp->type == Event::Type::kShutdown) { + if (poll_event.events & EPOLLERR) { + LogSocketError(eventp->fd.get()); + } + keep_running_ = false; + } else { + HandleEvent(eventp, poll_event.events); + } + } +} + +void ExceptionHandlerServer::Stop() { + keep_running_ = false; + if (shutdown_event_ && shutdown_event_->fd.is_valid()) { + uint64_t value = 1; + LoggingWriteFile(shutdown_event_->fd.get(), &value, sizeof(value)); + } +} + +void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) { + DCHECK_EQ(AsUnderlyingType(event->type), + AsUnderlyingType(Event::Type::kClientMessage)); + + if (event_type & EPOLLERR) { + LogSocketError(event->fd.get()); + UninstallClientSocket(event); + return; + } + + if (event_type & EPOLLIN) { + if (!ReceiveClientMessage(event)) { + UninstallClientSocket(event); + } + return; + } + + if (event_type & EPOLLHUP || event_type & EPOLLRDHUP) { + UninstallClientSocket(event); + return; + } + + LOG(ERROR) << "Unexpected event 0x" << std::hex << event_type; + return; +} + +bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) { + int optval = 1; + socklen_t 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->type = Event::Type::kClientMessage; + event->fd.reset(socket.release()); + + Event* eventp = event.get(); + + if (!clients_.insert(std::make_pair(event->fd.get(), std::move(event))) + .second) { + LOG(ERROR) << "duplicate descriptor"; + return false; + } + + epoll_event poll_event; + poll_event.events = EPOLLIN | EPOLLRDHUP; + poll_event.data.ptr = eventp; + + if (epoll_ctl(pollfd_.get(), EPOLL_CTL_ADD, eventp->fd.get(), &poll_event) != + 0) { + PLOG(ERROR) << "epoll_ctl"; + clients_.erase(eventp->fd.get()); + return false; + } + + return true; +} + +bool ExceptionHandlerServer::UninstallClientSocket(Event* event) { + if (epoll_ctl(pollfd_.get(), EPOLL_CTL_DEL, event->fd.get(), nullptr) != 0) { + PLOG(ERROR) << "epoll_ctl"; + return false; + } + + if (clients_.erase(event->fd.get()) != 1) { + LOG(ERROR) << "event not found"; + return false; + } + + return true; +} + +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. + return false; + } + + if (msg.msg_name != nullptr || msg.msg_namelen != 0) { + LOG(ERROR) << "unexpected msg name"; + return false; + } + + 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(msg.msg_iov[0].iov_base); + + switch (client_msg->type) { + case ClientToServerMessage::kCrashDumpRequest: + return HandleCrashDumpRequest( + msg, client_msg->client_info, event->fd.get()); + } + + DCHECK(false); + LOG(ERROR) << "Unknown message type"; + return false; +} + +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; + } + + 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(CMSG_DATA(cmsg)); + pid_t client_process_id = client_credentials->pid; + + switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) { + case PtraceStrategyDecider::Strategy::kError: + return false; + + case PtraceStrategyDecider::Strategy::kNoPtrace: + return SendMessageToClient(client_sock, + ServerToClientMessage::kTypeCrashDumpFailed); + + case PtraceStrategyDecider::Strategy::kDirectPtrace: + delegate_->HandleException(client_process_id, client_info); + break; + + case PtraceStrategyDecider::Strategy::kUseBroker: + delegate_->HandleExceptionWithBroker( + client_process_id, client_info, client_sock); + break; + } + + return SendMessageToClient(client_sock, + ServerToClientMessage::kTypeCrashDumpComplete); +} + +} // namespace crashpad diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h new file mode 100644 index 00000000..5f491826 --- /dev/null +++ b/handler/linux/exception_handler_server.h @@ -0,0 +1,152 @@ +// 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_LINUX_EXCEPTION_HANDLER_SERVER_H_ +#define CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_ + +#include +#include + +#include +#include + +#include "base/macros.h" +#include "util/file/file_io.h" +#include "util/linux/exception_handler_protocol.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +//! \brief Abstract base class for deciding how the handler should `ptrace` a +//! client. +class PtraceStrategyDecider { + public: + virtual ~PtraceStrategyDecider() = default; + + //! \brief The possible return values for ChooseStrategy(). + enum class Strategy { + //! \brief An error occurred, with a message logged. + kError, + + //! \brief Ptrace cannot be used. + kNoPtrace, + + //! \brief The handler should `ptrace`-attach the client directly. + kDirectPtrace, + + //! \brief The client has `fork`ed a PtraceBroker for the handler. + kUseBroker, + }; + + //! \brief Chooses an appropriate `ptrace` strategy. + //! + //! \param[in] sock A socket conncted to a ExceptionHandlerClient. + //! \param[in] client_credentials The credentials for the connected client. + //! \return the chosen #Strategy. + virtual Strategy ChooseStrategy(int sock, + const ucred& client_credentials) = 0; + + protected: + PtraceStrategyDecider() = default; +}; + +//! \brief Runs the main exception-handling server in Crashpad’s handler +//! process. +class ExceptionHandlerServer { + public: + class Delegate { + public: + //! \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] info Information on the client. + //! \return `true` on success. `false` on failure with a message logged. + virtual bool HandleException(pid_t client_process_id, + const ClientInformation& info) = 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] info Information on the client. + //! \param[in] broker_sock A socket connected to the PtraceBroker. + //! \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; + + protected: + ~Delegate() {} + }; + + ExceptionHandlerServer(); + ~ExceptionHandlerServer(); + + //! \brief Sets the handler's PtraceStrategyDecider. + //! + //! If this method is not called, a default PtraceStrategyDecider will be + //! used. + void SetPtraceStrategyDecider(std::unique_ptr decider); + + //! \brief Initializes this object. + //! + //! This method must be successfully called before Run(). + //! + //! \param[in] sock A socket on which to receive client requests. + //! \return `true` on success. `false` on failure with a message logged. + bool InitializeWithClient(ScopedFileHandle sock); + + //! \brief Runs the exception-handling server. + //! + //! This method must only be called once on an ExceptionHandlerServer object. + //! This method returns when there are no more client connections or Stop() + //! has been called. + //! + //! \param[in] delegate An object to send exceptions to. + void Run(Delegate* delegate); + + //! \brief Stops a running exception-handling server. + //! + //! Stop() may be called at any time, and may be called from a signal handler. + //! If Stop() is called before Run() it will cause Run() to return as soon as + //! it is called. It is harmless to call Stop() after Run() has already + //! returned, or to call Stop() after it has already been called. + void Stop(); + + private: + struct Event; + + void HandleEvent(Event* event, uint32_t event_type); + bool InstallClientSocket(ScopedFileHandle socket); + bool UninstallClientSocket(Event* event); + bool ReceiveClientMessage(Event* event); + bool HandleCrashDumpRequest(const msghdr& msg, + const ClientInformation& client_info, + int client_sock); + + std::unordered_map> clients_; + std::unique_ptr shutdown_event_; + std::unique_ptr strategy_decider_; + Delegate* delegate_; + ScopedFileHandle pollfd_; + bool keep_running_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_ diff --git a/handler/linux/exception_handler_server_test.cc b/handler/linux/exception_handler_server_test.cc new file mode 100644 index 00000000..eda14d31 --- /dev/null +++ b/handler/linux/exception_handler_server_test.cc @@ -0,0 +1,326 @@ +// 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/linux/exception_handler_server.h" + +#include +#include + +#include "base/logging.h" +#include "gtest/gtest.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/synchronization/semaphore.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { +namespace { + +// Runs the ExceptionHandlerServer on a background thread. +class RunServerThread : public Thread { + public: + RunServerThread(ExceptionHandlerServer* server, + ExceptionHandlerServer::Delegate* delegate) + : server_(server), delegate_(delegate), join_sem_(0) {} + + ~RunServerThread() override {} + + bool JoinWithTimeout(double timeout) { + if (!join_sem_.TimedWait(timeout)) { + return false; + } + Join(); + return true; + } + + private: + // Thread: + void ThreadMain() override { + server_->Run(delegate_); + join_sem_.Signal(); + } + + ExceptionHandlerServer* server_; + ExceptionHandlerServer::Delegate* delegate_; + Semaphore join_sem_; + + DISALLOW_COPY_AND_ASSIGN(RunServerThread); +}; + +class ScopedStopServerAndJoinThread { + public: + ScopedStopServerAndJoinThread(ExceptionHandlerServer* server, + RunServerThread* thread) + : server_(server), thread_(thread) {} + + ~ScopedStopServerAndJoinThread() { + server_->Stop(); + EXPECT_TRUE(thread_->JoinWithTimeout(5.0)); + } + + private: + ExceptionHandlerServer* server_; + RunServerThread* thread_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread); +}; + +class TestDelegate : public ExceptionHandlerServer::Delegate { + public: + TestDelegate() + : Delegate(), last_exception_address_(0), last_client_(-1), sem_(0) {} + + ~TestDelegate() {} + + bool WaitForException(double timeout_seconds, + pid_t* last_client, + VMAddress* last_address) { + if (sem_.TimedWait(timeout_seconds)) { + *last_client = last_client_; + *last_address = last_exception_address_; + return true; + } + + return false; + } + + bool HandleException(pid_t client_process_id, + const ClientInformation& info) override { + DirectPtraceConnection connection; + bool connected = connection.Initialize(client_process_id); + EXPECT_TRUE(connected); + + last_exception_address_ = info.exception_information_address; + last_client_ = client_process_id; + sem_.Signal(); + return connected; + } + + bool HandleExceptionWithBroker(pid_t client_process_id, + const ClientInformation& info, + int broker_sock) override { + PtraceClient client; + bool connected = client.Initialize(broker_sock, client_process_id); + EXPECT_TRUE(connected); + + last_exception_address_ = info.exception_information_address, + last_client_ = client_process_id; + sem_.Signal(); + return connected; + } + + private: + VMAddress last_exception_address_; + pid_t last_client_; + Semaphore sem_; + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +class MockPtraceStrategyDecider : public PtraceStrategyDecider { + public: + MockPtraceStrategyDecider(PtraceStrategyDecider::Strategy strategy) + : PtraceStrategyDecider(), strategy_(strategy) {} + + ~MockPtraceStrategyDecider() {} + + Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + if (strategy_ == Strategy::kUseBroker) { + ServerToClientMessage message = {}; + message.type = ServerToClientMessage::kTypeForkBroker; + + Errno status; + bool result = LoggingWriteFile(sock, &message, sizeof(message)) && + LoggingReadFileExactly(sock, &status, sizeof(status)); + EXPECT_TRUE(result); + + if (!result) { + return Strategy::kError; + } + + if (status != 0) { + errno = status; + ADD_FAILURE() << ErrnoMessage("Handler Client ForkBroker"); + return Strategy::kNoPtrace; + } + } + return strategy_; + } + + private: + Strategy strategy_; + + DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider); +}; + +class ExceptionHandlerServerTest : public testing::Test { + public: + ExceptionHandlerServerTest() + : server_(), + delegate_(), + server_thread_(&server_, &delegate_), + sock_to_handler_() {} + + ~ExceptionHandlerServerTest() = default; + + int SockToHandler() { return sock_to_handler_.get(); } + + TestDelegate* Delegate() { return &delegate_; } + + void Hangup() { sock_to_handler_.reset(); } + + RunServerThread* ServerThread() { return &server_thread_; } + + ExceptionHandlerServer* Server() { return &server_; } + + class CrashDumpTest : public Multiprocess { + public: + CrashDumpTest(ExceptionHandlerServerTest* server_test, bool succeeds) + : Multiprocess(), server_test_(server_test), succeeds_(succeeds) {} + + ~CrashDumpTest() = default; + + void MultiprocessParent() override { + ClientInformation info; + ASSERT_TRUE( + LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info))); + + if (succeeds_) { + VMAddress last_address; + pid_t last_client; + ASSERT_TRUE(server_test_->Delegate()->WaitForException( + 5.0, &last_client, &last_address)); + EXPECT_EQ(last_address, info.exception_information_address); + EXPECT_EQ(last_client, ChildPID()); + } else { + CheckedReadFileAtEOF(ReadPipeHandle()); + } + } + + void MultiprocessChild() override { + ASSERT_EQ(close(server_test_->sock_to_client_), 0); + + 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 + // as the ptracer for this process. Setting this process as its own + // ptracer allows the broker to inherit this condition. + ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true); + + ExceptionHandlerClient client(server_test_->SockToHandler()); + ASSERT_EQ(client.RequestCrashDump(info), 0); + } + + private: + ExceptionHandlerServerTest* server_test_; + bool succeeds_; + + DISALLOW_COPY_AND_ASSIGN(CrashDumpTest); + }; + + void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy, + bool succeeds) { + ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); + ServerThread()->Start(); + + Server()->SetPtraceStrategyDecider( + std::make_unique(strategy)); + + CrashDumpTest test(this, succeeds); + test.Run(); + } + + protected: + void SetUp() override { + int socks[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0); + sock_to_handler_.reset(socks[0]); + sock_to_client_ = socks[1]; + + ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]))); + } + + private: + ExceptionHandlerServer server_; + TestDelegate delegate_; + RunServerThread server_thread_; + ScopedFileHandle sock_to_handler_; + int sock_to_client_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); +}; + +TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) { + ServerThread()->Start(); + Hangup(); + ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); +} + +TEST_F(ExceptionHandlerServerTest, StopWithClients) { + ServerThread()->Start(); + Server()->Stop(); + ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); +} + +TEST_F(ExceptionHandlerServerTest, StopBeforeRun) { + Server()->Stop(); + ServerThread()->Start(); + ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); +} + +TEST_F(ExceptionHandlerServerTest, MultipleStops) { + ServerThread()->Start(); + Server()->Stop(); + Server()->Stop(); + ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); +} + +TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) { + ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); + ServerThread()->Start(); + + CrashDumpTest test(this, true); + test.Run(); +} + +TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { + ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace, + false); +} + +TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { + ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker, + true); +} + +TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { + ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace, + true); +} + +TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) { + ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 6f9cdbe6..9919e955 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -14,6 +14,7 @@ #include "handler/mac/crash_report_exception_handler.h" +#include #include #include "base/logging.h" @@ -155,7 +156,7 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( process_snapshot.SetClientID(client_id); process_snapshot.SetAnnotationsSimpleMap(*process_annotations_); - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; CrashReportDatabase::OperationStatus database_status = database_->PrepareNewCrashReport(&new_report); if (database_status != CrashReportDatabase::kNoError) { @@ -164,35 +165,31 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( return KERN_FAILURE; } - process_snapshot.SetReportID(new_report->uuid); - - CrashReportDatabase::CallErrorWritingCrashReport - call_error_writing_crash_report(database_, new_report); - - WeakFileHandleFileWriter file_writer(new_report->handle); + process_snapshot.SetReportID(new_report->ReportID()); MinidumpFileWriter minidump; minidump.InitializeFromSnapshot(&process_snapshot); AddUserExtensionStreams( user_stream_data_sources_, &process_snapshot, &minidump); - if (!minidump.WriteEverything(&file_writer)) { + if (!minidump.WriteEverything(new_report->Writer())) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kMinidumpWriteFailed); return KERN_FAILURE; } - call_error_writing_crash_report.Disarm(); - UUID uuid; - database_status = database_->FinishedWritingCrashReport(new_report, &uuid); + database_status = + database_->FinishedWritingCrashReport(std::move(new_report), &uuid); if (database_status != CrashReportDatabase::kNoError) { Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kFinishedWritingCrashReportFailed); return KERN_FAILURE; } - upload_thread_->ReportPending(uuid); + if (upload_thread_) { + upload_thread_->ReportPending(uuid); + } } if (client_options.system_crash_reporter_forwarding != TriState::kDisabled && diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h index cc314f1f..0b44de67 100644 --- a/handler/mac/crash_report_exception_handler.h +++ b/handler/mac/crash_report_exception_handler.h @@ -36,7 +36,8 @@ class CrashReportExceptionHandler : public UniversalMachExcServer::Interface { //! //! \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. + //! report is written into \a database. Report upload is skipped if this + //! value is `nullptr`. //! \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 diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index 7d893002..272cf762 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -63,9 +63,7 @@ class ExceptionHandlerServer { //! \brief Stops a running exception-handling server. //! - //! The normal mode of operation is to call Stop() while Run() is running. It - //! is expected that Stop() would be called from a signal handler. - //! + //! Stop() may be called at any time, and may be called from a signal handler. //! If Stop() is called before Run() it will cause Run() to return as soon as //! it is called. It is harmless to call Stop() after Run() has already //! returned, or to call Stop() after it has already been called. diff --git a/handler/main.cc b/handler/main.cc index c0a04f40..3ae73ecd 100644 --- a/handler/main.cc +++ b/handler/main.cc @@ -21,7 +21,7 @@ #include #endif -#if defined(OS_MACOSX) +#if defined(OS_POSIX) int main(int argc, char* argv[]) { return crashpad::HandlerMain(argc, argv, nullptr); @@ -50,4 +50,4 @@ int wmain(int argc, wchar_t* argv[]) { return crashpad::ToolSupport::Wmain(argc, argv, HandlerMainAdaptor); } -#endif // OS_MACOSX +#endif // OS_POSIX diff --git a/handler/minidump_to_upload_parameters.cc b/handler/minidump_to_upload_parameters.cc new file mode 100644 index 00000000..03c6fe14 --- /dev/null +++ b/handler/minidump_to_upload_parameters.cc @@ -0,0 +1,86 @@ +// 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/minidump_to_upload_parameters.h" + +#include "base/logging.h" +#include "client/annotation.h" +#include "snapshot/module_snapshot.h" +#include "util/stdlib/map_insert.h" + +namespace crashpad { + +namespace { + +void InsertOrReplaceMapEntry(std::map* map, + const std::string& key, + const std::string& value) { + std::string old_value; + if (!MapInsertOrReplace(map, key, value, &old_value)) { + LOG(WARNING) << "duplicate key " << key << ", discarding value " + << old_value; + } +} + +} // namespace + +std::map BreakpadHTTPFormParametersFromMinidump( + const ProcessSnapshot* process_snapshot) { + std::map parameters = + process_snapshot->AnnotationsSimpleMap(); + + std::string list_annotations; + for (const ModuleSnapshot* module : process_snapshot->Modules()) { + for (const auto& kv : module->AnnotationsSimpleMap()) { + if (!parameters.insert(kv).second) { + LOG(WARNING) << "duplicate key " << kv.first << ", discarding value " + << kv.second; + } + } + + for (std::string annotation : module->AnnotationsVector()) { + list_annotations.append(annotation); + list_annotations.append("\n"); + } + + for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { + if (annotation.type != static_cast(Annotation::Type::kString)) { + continue; + } + + std::string value(reinterpret_cast(annotation.value.data()), + annotation.value.size()); + std::pair entry(annotation.name, value); + if (!parameters.insert(entry).second) { + LOG(WARNING) << "duplicate annotation name " << annotation.name + << ", discarding value " << value; + } + } + } + + if (!list_annotations.empty()) { + // Remove the final newline character. + list_annotations.resize(list_annotations.size() - 1); + + InsertOrReplaceMapEntry(¶meters, "list_annotations", list_annotations); + } + + UUID client_id; + process_snapshot->ClientID(&client_id); + InsertOrReplaceMapEntry(¶meters, "guid", client_id.ToString()); + + return parameters; +} + +} // namespace crashpad diff --git a/handler/minidump_to_upload_parameters.h b/handler/minidump_to_upload_parameters.h new file mode 100644 index 00000000..41056f70 --- /dev/null +++ b/handler/minidump_to_upload_parameters.h @@ -0,0 +1,61 @@ +// 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 HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_ +#define HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_ + +#include +#include + +#include "snapshot/process_snapshot.h" + +namespace crashpad { + +//! \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. +//! +//! The map is built by combining the process simple annotations map with +//! each module’s simple annotations map and annotation objects. +//! +//! In the case of duplicate simple map keys or annotation names, the map will +//! retain the first value found for any key, and will log a warning about +//! discarded values. The precedence rules for annotation names are: the two +//! reserved keys discussed below, process simple annotations, module simple +//! annotations, and module annotation objects. +//! +//! For annotation objects, only ones of that are Annotation::Type::kString are +//! included. +//! +//! Each module’s annotations vector is also examined and built into a single +//! string value, with distinct elements separated by newlines, and stored at +//! the key named “list_annotations”, which supersedes any other key found by +//! that name. +//! +//! The client ID stored in the minidump is converted to a string and stored at +//! the key named “guid”, which supersedes any other key found by that name. +//! +//! In the event of an error reading the minidump file, a message will be +//! logged. +//! +//! \param[in] process_snapshot The process snapshot from which annotations +//! will be extracted. +//! +//! \returns A string map of the annotations. +std::map BreakpadHTTPFormParametersFromMinidump( + const ProcessSnapshot* process_snapshot); + +} // namespace crashpad + +#endif // HANDLER_MINIDUMP_TO_UPLOAD_PARAMETERS_H_ diff --git a/handler/minidump_to_upload_parameters_test.cc b/handler/minidump_to_upload_parameters_test.cc new file mode 100644 index 00000000..0c3a0e7c --- /dev/null +++ b/handler/minidump_to_upload_parameters_test.cc @@ -0,0 +1,99 @@ +// 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/minidump_to_upload_parameters.h" + +#include "gtest/gtest.h" +#include "snapshot/test/test_module_snapshot.h" +#include "snapshot/test/test_process_snapshot.h" +#include "util/misc/uuid.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(MinidumpToUploadParameters, PrecedenceRules) { + const std::string guid = "00112233-4455-6677-8899-aabbccddeeff"; + UUID uuid; + ASSERT_TRUE(uuid.InitializeFromString(guid)); + + TestProcessSnapshot process_snapshot; + process_snapshot.SetClientID(uuid); + process_snapshot.SetAnnotationsSimpleMap({ + {"process-1", "abcdefg"}, + {"list_annotations", "BAD: process_annotations"}, + {"guid", "BAD: process_annotations"}, + {"first", "process"}, + }); + + auto module_snapshot_0 = std::make_unique(); + module_snapshot_0->SetAnnotationsVector( + {"list-module-0-1", "list-module-0-2"}); + module_snapshot_0->SetAnnotationsSimpleMap({ + {"module-0-1", "goat"}, + {"module-0-2", "doge"}, + {"list_annotations", "BAD: module 0"}, + {"guid", "BAD: module 0"}, + {"first", "BAD: module 0"}, + {"second", "module 0"}, + }); + module_snapshot_0->SetAnnotationObjects({ + {"module-0-3", 1, {'s', 't', 'a', 'r'}}, + {"module-0-4", 0xFFFA, {0x42}}, + {"guid", 1, {'B', 'A', 'D', '*', '0', '-', '0'}}, + {"list_annotations", 1, {'B', 'A', 'D', '*', '0', '-', '1'}}, + {"first", 1, {'B', 'A', 'D', '*', '0', '-', '2'}}, + }); + process_snapshot.AddModule(std::move(module_snapshot_0)); + + auto module_snapshot_1 = std::make_unique(); + module_snapshot_1->SetAnnotationsVector( + {"list-module-1-1", "list-module-1-2"}); + module_snapshot_1->SetAnnotationsSimpleMap({ + {"module-1-1", "bear"}, + {"list_annotations", "BAD: module 1"}, + {"guid", "BAD: module 1"}, + {"first", "BAD: module 1"}, + {"second", "BAD: module 1"}, + }); + module_snapshot_1->SetAnnotationObjects({ + {"module-1-3", 0xBEEF, {'a', 'b', 'c'}}, + {"module-1-4", 1, {'m', 'o', 'o', 'n'}}, + {"guid", 1, {'B', 'A', 'D', '*', '1', '-', '0'}}, + {"list_annotations", 1, {'B', 'A', 'D', '*', '1', '-', '1'}}, + {"second", 1, {'B', 'A', 'D', '*', '1', '-', '2'}}, + }); + process_snapshot.AddModule(std::move(module_snapshot_1)); + + auto upload_parameters = + BreakpadHTTPFormParametersFromMinidump(&process_snapshot); + + EXPECT_EQ(upload_parameters.size(), 10u); + EXPECT_EQ(upload_parameters["process-1"], "abcdefg"); + EXPECT_EQ(upload_parameters["first"], "process"); + EXPECT_EQ(upload_parameters["module-0-1"], "goat"); + EXPECT_EQ(upload_parameters["module-0-2"], "doge"); + EXPECT_EQ(upload_parameters["module-0-3"], "star"); + EXPECT_EQ(upload_parameters["second"], "module 0"); + EXPECT_EQ(upload_parameters["module-1-1"], "bear"); + EXPECT_EQ(upload_parameters["module-1-4"], "moon"); + EXPECT_EQ(upload_parameters["list_annotations"], + "list-module-0-1\nlist-module-0-2\n" + "list-module-1-1\nlist-module-1-2"); + EXPECT_EQ(upload_parameters["guid"], guid); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/handler/prune_crash_reports_thread.cc b/handler/prune_crash_reports_thread.cc index 722275f5..7876c2fe 100644 --- a/handler/prune_crash_reports_thread.cc +++ b/handler/prune_crash_reports_thread.cc @@ -38,6 +38,7 @@ void PruneCrashReportThread::Stop() { } void PruneCrashReportThread::DoWork(const WorkerThread* thread) { + database_->CleanDatabase(60 * 60 * 24 * 3); PruneCrashReportDatabase(database_, condition_.get()); } diff --git a/handler/prune_crash_reports_thread.h b/handler/prune_crash_reports_thread.h index 72b69fc6..0fd365b0 100644 --- a/handler/prune_crash_reports_thread.h +++ b/handler/prune_crash_reports_thread.h @@ -18,6 +18,7 @@ #include #include "base/macros.h" +#include "util/thread/stoppable.h" #include "util/thread/worker_thread.h" namespace crashpad { @@ -31,7 +32,7 @@ class PruneCondition; //! After the thread is started, the database is pruned using the condition //! every 24 hours. Upon calling Start(), the thread waits 10 minutes before //! performing the initial prune operation. -class PruneCrashReportThread : public WorkerThread::Delegate { +class PruneCrashReportThread : public WorkerThread::Delegate, public Stoppable { public: //! \brief Constructs a new object. //! @@ -42,6 +43,8 @@ class PruneCrashReportThread : public WorkerThread::Delegate { std::unique_ptr condition); ~PruneCrashReportThread(); + // Stoppable: + //! \brief Starts a dedicated pruning thread. //! //! The thread waits before running the initial prune, so as to not interfere @@ -49,7 +52,7 @@ class PruneCrashReportThread : public WorkerThread::Delegate { //! //! This method may only be be called on a newly-constructed object or after //! a call to Stop(). - void Start(); + void Start() override; //! \brief Stops the pruning thread. //! @@ -58,7 +61,7 @@ class PruneCrashReportThread : public WorkerThread::Delegate { //! //! This method may be called from any thread other than the pruning thread. //! It is expected to only be called from the same thread that called Start(). - void Stop(); + void Stop() override; private: // WorkerThread::Delegate: diff --git a/handler/win/crash_other_program.cc b/handler/win/crash_other_program.cc index ddad4c53..bbd25af5 100644 --- a/handler/win/crash_other_program.cc +++ b/handler/win/crash_other_program.cc @@ -33,7 +33,7 @@ namespace { constexpr DWORD kCrashAndDumpTargetExitCode = 0xdeadbea7; -bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) { +bool CrashAndDumpTarget(HANDLE process) { DWORD target_pid = GetProcessId(process); ScopedFileHANDLE thread_snap(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)); @@ -52,9 +52,8 @@ bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) { do { if (te32.th32OwnerProcessID == target_pid) { // We set the thread priority of "Thread1" to a non-default value before - // going to sleep. Dump and blame this thread. For an explanation of - // "9", see - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100.aspx. + // going to sleep. Dump and blame this thread. For an explanation of "9", + // see https://msdn.microsoft.com/library/ms685100.aspx. if (te32.tpBasePri == 9) { ScopedKernelHANDLE thread( OpenThread(kXPThreadAllAccess, false, te32.th32ThreadID)); @@ -62,7 +61,7 @@ bool CrashAndDumpTarget(const CrashpadClient& client, HANDLE process) { PLOG(ERROR) << "OpenThread"; return false; } - if (!client.DumpAndCrashTargetProcess( + if (!CrashpadClient::DumpAndCrashTargetProcess( process, thread.get(), kCrashAndDumpTargetExitCode)) { return false; } @@ -110,11 +109,12 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) { DWORD expect_exit_code; if (argc == 3 && wcscmp(argv[2], L"noexception") == 0) { expect_exit_code = CrashpadClient::kTriggeredExceptionCode; - if (!client.DumpAndCrashTargetProcess(child.process_handle(), 0, 0)) + if (!CrashpadClient::DumpAndCrashTargetProcess( + child.process_handle(), 0, 0)) return EXIT_FAILURE; } else { expect_exit_code = kCrashAndDumpTargetExitCode; - if (!CrashAndDumpTarget(client, child.process_handle())) { + if (!CrashAndDumpTarget(child.process_handle())) { return EXIT_FAILURE; } } @@ -122,7 +122,7 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) { DWORD exit_code = child.WaitForExit(); if (exit_code != expect_exit_code) { LOG(ERROR) << base::StringPrintf( - "incorrect exit code, expected 0x%x, observed 0x%x", + "incorrect exit code, expected 0x%lx, observed 0x%lx", expect_exit_code, exit_code); return EXIT_FAILURE; diff --git a/handler/win/crash_report_exception_handler.cc b/handler/win/crash_report_exception_handler.cc index 0ab206c1..d845c446 100644 --- a/handler/win/crash_report_exception_handler.cc +++ b/handler/win/crash_report_exception_handler.cc @@ -15,6 +15,7 @@ #include "handler/win/crash_report_exception_handler.h" #include +#include #include "client/crash_report_database.h" #include "client/settings.h" @@ -59,7 +60,6 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException( ProcessSuspensionState::kSuspended, exception_information_address, debug_critical_section_address)) { - LOG(WARNING) << "ProcessSnapshotWin::Initialize failed"; Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed); return kTerminationCodeSnapshotFailed; } @@ -90,7 +90,7 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException( process_snapshot.SetClientID(client_id); process_snapshot.SetAnnotationsSimpleMap(*process_annotations_); - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; CrashReportDatabase::OperationStatus database_status = database_->PrepareNewCrashReport(&new_report); if (database_status != CrashReportDatabase::kNoError) { @@ -100,29 +100,23 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException( return termination_code; } - process_snapshot.SetReportID(new_report->uuid); - - CrashReportDatabase::CallErrorWritingCrashReport - call_error_writing_crash_report(database_, new_report); - - WeakFileHandleFileWriter file_writer(new_report->handle); + process_snapshot.SetReportID(new_report->ReportID()); MinidumpFileWriter minidump; minidump.InitializeFromSnapshot(&process_snapshot); AddUserExtensionStreams( user_stream_data_sources_, &process_snapshot, &minidump); - if (!minidump.WriteEverything(&file_writer)) { + if (!minidump.WriteEverything(new_report->Writer())) { LOG(ERROR) << "WriteEverything failed"; Metrics::ExceptionCaptureResult( Metrics::CaptureResult::kMinidumpWriteFailed); return termination_code; } - call_error_writing_crash_report.Disarm(); - UUID uuid; - database_status = database_->FinishedWritingCrashReport(new_report, &uuid); + database_status = + database_->FinishedWritingCrashReport(std::move(new_report), &uuid); if (database_status != CrashReportDatabase::kNoError) { LOG(ERROR) << "FinishedWritingCrashReport failed"; Metrics::ExceptionCaptureResult( @@ -130,7 +124,9 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException( return termination_code; } - upload_thread_->ReportPending(uuid); + if (upload_thread_) { + upload_thread_->ReportPending(uuid); + } } Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess); diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h index 54dfa971..c2781de3 100644 --- a/handler/win/crash_report_exception_handler.h +++ b/handler/win/crash_report_exception_handler.h @@ -37,7 +37,8 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { //! //! \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. + //! report is written into \a database. Report upload is skipped if this + //! value is `nullptr`. //! \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 @@ -58,7 +59,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { const std::map* process_annotations, const UserStreamDataSources* user_stream_data_sources); - ~CrashReportExceptionHandler() override; + ~CrashReportExceptionHandler(); // ExceptionHandlerServer::Delegate: diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index a5532da8..a4f4797b 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include "base/files/file_path.h" @@ -39,8 +40,8 @@ namespace crashpad { -int* g_extra_memory_pointer; -int* g_extra_memory_not_saved; +uint32_t* g_extra_memory_pointer; +uint32_t* g_extra_memory_not_saved; namespace { @@ -142,29 +143,33 @@ void SomeCrashyFunction() { void AllocateExtraMemoryToBeSaved( crashpad::SimpleAddressRangeBag* extra_ranges) { - constexpr size_t kNumInts = 2000; - int* extra_memory = new int[kNumInts]; + constexpr size_t kNumVals = 2000; + auto extra_memory = new uint32_t[kNumVals]; g_extra_memory_pointer = extra_memory; - for (int i = 0; i < kNumInts; ++i) - extra_memory[i] = i * 13 + 2; - extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumInts); + for (size_t i = 0; i < kNumVals; ++i) + extra_memory[i] = + static_cast::type>( + i * 13 + 2); + extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumVals); extra_ranges->Insert(&g_extra_memory_pointer, sizeof(g_extra_memory_pointer)); } void AllocateExtraUnsavedMemory(crashpad::SimpleAddressRangeBag* extra_ranges) { // Allocate some extra memory, and then Insert() but also Remove() it so we // can confirm it doesn't get saved. - constexpr size_t kNumInts = 2000; - int* extra_memory = new int[kNumInts]; + constexpr size_t kNumVals = 2000; + auto extra_memory = new uint32_t[kNumVals]; g_extra_memory_not_saved = extra_memory; - for (int i = 0; i < kNumInts; ++i) - extra_memory[i] = i * 17 + 7; - extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumInts); + for (size_t i = 0; i < kNumVals; ++i) + extra_memory[i] = + static_cast::type>( + i * 17 + 7); + extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumVals); extra_ranges->Insert(&g_extra_memory_not_saved, sizeof(g_extra_memory_not_saved)); // We keep the pointer's memory, but remove the pointed-to memory. - extra_ranges->Remove(extra_memory, sizeof(extra_memory[0]) * kNumInts); + extra_ranges->Remove(extra_memory, sizeof(extra_memory[0]) * kNumVals); } int CrashyMain(int argc, wchar_t* argv[]) { diff --git a/handler/win/loader_lock_dll.cc b/handler/win/loader_lock_dll.cc index cfa098b2..63c145cf 100644 --- a/handler/win/loader_lock_dll.cc +++ b/handler/win/loader_lock_dll.cc @@ -55,7 +55,7 @@ __declspec(noreturn) void LogFatal(const char* file, va_end(va); if (get_last_error) { - fprintf(stderr, ": error %u", last_error); + fprintf(stderr, ": error %lu", last_error); } fputs("\n", stderr); diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg index 32f3854d..b6bf0434 100644 --- a/infra/config/cq.cfg +++ b/infra/config/cq.cfg @@ -35,14 +35,18 @@ verifiers { name: "master.client.crashpad" builders { name: "crashpad_try_mac_dbg" } builders { name: "crashpad_try_mac_rel" } - builders { name: "crashpad_try_win_x64_dbg" } - builders { name: "crashpad_try_win_x64_rel" } - # crbug.com/743139 - disabled until we can move these to - # swarming, at which point we can just remove them. + builders { name: "crashpad_try_win_dbg" } + builders { name: "crashpad_try_win_rel" } + 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" } - builders { name: "crashpad_try_win_x86_wow64_dbg" } - builders { name: "crashpad_try_win_x86_wow64_rel" } } } } diff --git a/minidump/BUILD.gn b/minidump/BUILD.gn new file mode 100644 index 00000000..88a10815 --- /dev/null +++ b/minidump/BUILD.gn @@ -0,0 +1,169 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +static_library("minidump") { + sources = [ + "minidump_annotation_writer.cc", + "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", + "minidump_handle_writer.h", + "minidump_memory_info_writer.cc", + "minidump_memory_info_writer.h", + "minidump_memory_writer.cc", + "minidump_memory_writer.h", + "minidump_misc_info_writer.cc", + "minidump_misc_info_writer.h", + "minidump_module_crashpad_info_writer.cc", + "minidump_module_crashpad_info_writer.h", + "minidump_module_writer.cc", + "minidump_module_writer.h", + "minidump_rva_list_writer.cc", + "minidump_rva_list_writer.h", + "minidump_simple_string_dictionary_writer.cc", + "minidump_simple_string_dictionary_writer.h", + "minidump_stream_writer.cc", + "minidump_stream_writer.h", + "minidump_string_writer.cc", + "minidump_string_writer.h", + "minidump_system_info_writer.cc", + "minidump_system_info_writer.h", + "minidump_thread_id_map.cc", + "minidump_thread_id_map.h", + "minidump_thread_writer.cc", + "minidump_thread_writer.h", + "minidump_unloaded_module_writer.cc", + "minidump_unloaded_module_writer.h", + "minidump_user_extension_stream_data_source.cc", + "minidump_user_extension_stream_data_source.h", + "minidump_user_stream_writer.cc", + "minidump_user_stream_writer.h", + "minidump_writable.cc", + "minidump_writable.h", + "minidump_writer_util.cc", + "minidump_writer_util.h", + ] + + public_configs = [ "..:crashpad_config" ] + + public_deps = [ + "../compat", + ] + + deps = [ + "../snapshot", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_win) { + cflags = [ + "/wd4201", # nonstandard extension used : nameless struct/union + "/wd4324", # 'struct' : structure was padded due to __declspec(align()) + ] + } +} + +static_library("test_support") { + testonly = true + + sources = [ + "test/minidump_byte_array_writer_test_util.cc", + "test/minidump_byte_array_writer_test_util.h", + "test/minidump_context_test_util.cc", + "test/minidump_context_test_util.h", + "test/minidump_file_writer_test_util.cc", + "test/minidump_file_writer_test_util.h", + "test/minidump_memory_writer_test_util.cc", + "test/minidump_memory_writer_test_util.h", + "test/minidump_rva_list_test_util.cc", + "test/minidump_rva_list_test_util.h", + "test/minidump_string_writer_test_util.cc", + "test/minidump_string_writer_test_util.h", + "test/minidump_user_extension_stream_util.cc", + "test/minidump_user_extension_stream_util.h", + "test/minidump_writable_test_util.cc", + "test/minidump_writable_test_util.h", + ] + + public_configs = [ "..:crashpad_config" ] + + public_deps = [ + ":minidump", + ] + + deps = [ + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + ] + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } +} + +source_set("minidump_test") { + testonly = true + + sources = [ + "minidump_annotation_writer_test.cc", + "minidump_byte_array_writer_test.cc", + "minidump_context_writer_test.cc", + "minidump_crashpad_info_writer_test.cc", + "minidump_exception_writer_test.cc", + "minidump_file_writer_test.cc", + "minidump_handle_writer_test.cc", + "minidump_memory_info_writer_test.cc", + "minidump_memory_writer_test.cc", + "minidump_misc_info_writer_test.cc", + "minidump_module_crashpad_info_writer_test.cc", + "minidump_module_writer_test.cc", + "minidump_rva_list_writer_test.cc", + "minidump_simple_string_dictionary_writer_test.cc", + "minidump_string_writer_test.cc", + "minidump_system_info_writer_test.cc", + "minidump_thread_id_map_test.cc", + "minidump_thread_writer_test.cc", + "minidump_unloaded_module_writer_test.cc", + "minidump_user_stream_writer_test.cc", + "minidump_writable_test.cc", + ] + + deps = [ + ":test_support", + "../snapshot:test_support", + "../test", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } +} diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index e36006c9..63e0ba6b 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -33,6 +33,8 @@ '..', ], 'sources': [ + 'minidump_annotation_writer.cc', + 'minidump_annotation_writer.h', 'minidump_byte_array_writer.cc', 'minidump_byte_array_writer.h', 'minidump_context.h', diff --git a/minidump/minidump_annotation_writer.cc b/minidump/minidump_annotation_writer.cc new file mode 100644 index 00000000..54a3ecba --- /dev/null +++ b/minidump/minidump_annotation_writer.cc @@ -0,0 +1,162 @@ +// 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 "minidump/minidump_annotation_writer.h" + +#include + +#include "base/logging.h" +#include "util/file/file_writer.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +MinidumpAnnotationWriter::MinidumpAnnotationWriter() = default; + +MinidumpAnnotationWriter::~MinidumpAnnotationWriter() = default; + +void MinidumpAnnotationWriter::InitializeFromSnapshot( + const AnnotationSnapshot& snapshot) { + DCHECK_EQ(state(), kStateMutable); + + name_.SetUTF8(snapshot.name); + annotation_.type = snapshot.type; + annotation_.reserved = 0; + value_.set_data(snapshot.value); +} + +void MinidumpAnnotationWriter::InitializeWithData( + const std::string& name, + uint16_t type, + const std::vector& data) { + DCHECK_EQ(state(), kStateMutable); + + name_.SetUTF8(name); + annotation_.type = type; + annotation_.reserved = 0; + value_.set_data(data); +} + +bool MinidumpAnnotationWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + name_.RegisterRVA(&annotation_.name); + value_.RegisterRVA(&annotation_.value); + + return true; +} + +size_t MinidumpAnnotationWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // This object is written by the MinidumpAnnotationListWriter, and its + // children write themselves. + return 0; +} + +std::vector MinidumpAnnotationWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + return {&name_, &value_}; +} + +bool MinidumpAnnotationWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + // This object is written by the MinidumpAnnotationListWriter, and its + // children write themselves. + return true; +} + +MinidumpAnnotationListWriter::MinidumpAnnotationListWriter() + : minidump_list_(new MinidumpAnnotationList()) {} + +MinidumpAnnotationListWriter::~MinidumpAnnotationListWriter() = default; + +void MinidumpAnnotationListWriter::InitializeFromList( + const std::vector& list) { + DCHECK_EQ(state(), kStateMutable); + for (const auto& annotation : list) { + auto writer = std::make_unique(); + writer->InitializeFromSnapshot(annotation); + AddObject(std::move(writer)); + } +} + +void MinidumpAnnotationListWriter::AddObject( + std::unique_ptr annotation_writer) { + DCHECK_EQ(state(), kStateMutable); + + objects_.push_back(std::move(annotation_writer)); +} + +bool MinidumpAnnotationListWriter::IsUseful() const { + return !objects_.empty(); +} + +bool MinidumpAnnotationListWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + if (!AssignIfInRange(&minidump_list_->count, objects_.size())) { + LOG(ERROR) << "annotation list size " << objects_.size() + << " is out of range"; + return false; + } + + return true; +} + +size_t MinidumpAnnotationListWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(*minidump_list_) + sizeof(MinidumpAnnotation) * objects_.size(); +} + +std::vector +MinidumpAnnotationListWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children(objects_.size()); + for (size_t i = 0; i < objects_.size(); ++i) { + children[i] = objects_[i].get(); + } + + return children; +} + +bool MinidumpAnnotationListWriter::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + std::vector iov(1 + objects_.size()); + iov[0].iov_base = minidump_list_.get(); + iov[0].iov_len = sizeof(*minidump_list_); + + for (const auto& object : objects_) { + iov.emplace_back(WritableIoVec{object->minidump_annotation(), + sizeof(MinidumpAnnotation)}); + } + + return file_writer->WriteIoVec(&iov); +} + +} // namespace crashpad diff --git a/minidump/minidump_annotation_writer.h b/minidump/minidump_annotation_writer.h new file mode 100644 index 00000000..fc30dbc3 --- /dev/null +++ b/minidump/minidump_annotation_writer.h @@ -0,0 +1,109 @@ +// 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_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ + +#include +#include + +#include "minidump/minidump_byte_array_writer.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_string_writer.h" +#include "minidump/minidump_writable.h" +#include "snapshot/annotation_snapshot.h" + +namespace crashpad { + +//! \brief The writer for a MinidumpAnnotation object in a minidump file. +//! +//! Because MinidumpAnnotation objects only appear as elements +//! of MinidumpAnnotationList objects, this class does not write any +//! data on its own. It makes its MinidumpAnnotation data available to its +//! MinidumpAnnotationList parent, which writes it as part of a +//! MinidumpAnnotationList. +class MinidumpAnnotationWriter final : public internal::MinidumpWritable { + public: + MinidumpAnnotationWriter(); + ~MinidumpAnnotationWriter(); + + //! \brief Initializes the annotation writer with data from an + //! AnnotationSnapshot. + void InitializeFromSnapshot(const AnnotationSnapshot& snapshot); + + //! \brief Initializes the annotation writer with data values. + void InitializeWithData(const std::string& name, + uint16_t type, + const std::vector& data); + + //! \brief Returns the MinidumpAnnotation referencing this object’s data. + const MinidumpAnnotation* minidump_annotation() const { return &annotation_; } + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + MinidumpAnnotation annotation_; + internal::MinidumpUTF8StringWriter name_; + MinidumpByteArrayWriter value_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpAnnotationWriter); +}; + +//! \brief The writer for a MinidumpAnnotationList object in a minidump file, +//! containing a list of MinidumpAnnotation objects. +class MinidumpAnnotationListWriter final : public internal::MinidumpWritable { + public: + MinidumpAnnotationListWriter(); + ~MinidumpAnnotationListWriter(); + + //! \brief Initializes the annotation list writer with a list of + //! AnnotationSnapshot objects. + void InitializeFromList(const std::vector& list); + + //! \brief Adds a single MinidumpAnnotationWriter to the list to be written. + void AddObject(std::unique_ptr annotation_writer); + + //! \brief Determines whether the object is useful. + //! + //! A useful object is one that carries data that makes a meaningful + //! contribution to a minidump file. An object carrying entries would be + //! considered useful. + //! + //! \return `true` if the object is useful, `false` otherwise. + bool IsUseful() const; + + protected: + // MinidumpWritable: + + bool Freeze() override; + size_t SizeOfObject() override; + std::vector Children() override; + bool WriteObject(FileWriterInterface* file_writer) override; + + private: + std::unique_ptr minidump_list_; + std::vector> objects_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpAnnotationListWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_ANNOTATION_WRITER_H_ diff --git a/minidump/minidump_annotation_writer_test.cc b/minidump/minidump_annotation_writer_test.cc new file mode 100644 index 00000000..1641d1d6 --- /dev/null +++ b/minidump/minidump_annotation_writer_test.cc @@ -0,0 +1,192 @@ +// 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 "minidump/minidump_annotation_writer.h" + +#include + +#include "base/macros.h" +#include "gtest/gtest.h" +#include "minidump/minidump_extensions.h" +#include "minidump/test/minidump_byte_array_writer_test_util.h" +#include "minidump/test/minidump_string_writer_test_util.h" +#include "minidump/test/minidump_writable_test_util.h" +#include "util/file/string_file.h" + +namespace crashpad { +namespace test { +namespace { + +const MinidumpAnnotationList* MinidumpAnnotationListAtStart( + const std::string& file_contents, + uint32_t count) { + MINIDUMP_LOCATION_DESCRIPTOR location_descriptor; + location_descriptor.DataSize = static_cast( + sizeof(MinidumpAnnotationList) + count * sizeof(MinidumpAnnotation)); + location_descriptor.Rva = 0; + return MinidumpWritableAtLocationDescriptor( + file_contents, location_descriptor); +} + +TEST(MinidumpAnnotationWriter, EmptyList) { + StringFile string_file; + + MinidumpAnnotationListWriter list_writer; + + EXPECT_FALSE(list_writer.IsUseful()); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), sizeof(MinidumpAnnotationList)); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 0); + ASSERT_TRUE(list); + EXPECT_EQ(list->count, 0u); +} + +TEST(MinidumpAnnotationWriter, OneItem) { + StringFile string_file; + + const char kName[] = "name"; + const uint16_t kType = 0xFFFF; + const std::vector kValue{'v', 'a', 'l', 'u', 'e', '\0'}; + + auto annotation_writer = std::make_unique(); + annotation_writer->InitializeWithData(kName, kType, kValue); + + MinidumpAnnotationListWriter list_writer; + list_writer.AddObject(std::move(annotation_writer)); + + EXPECT_TRUE(list_writer.IsUseful()); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + sizeof(MinidumpAnnotation) + + sizeof(MinidumpUTF8String) + sizeof(kName) + + sizeof(MinidumpByteArray) + kValue.size() + + 3); // 3 for padding. + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 1); + ASSERT_TRUE(list); + EXPECT_EQ(list->count, 1u); + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[0].name), + kName); + EXPECT_EQ(list->objects[0].type, kType); + EXPECT_EQ(list->objects[0].reserved, 0u); + EXPECT_EQ( + + MinidumpByteArrayAtRVA(string_file.string(), list->objects[0].value), + kValue); +} + +TEST(MinidumpAnnotationWriter, ThreeItems) { + StringFile string_file; + + const char* kNames[] = { + "~~FIRST~~", " second + ", "3", + }; + const uint16_t kTypes[] = { + 0x1, 0xABCD, 0x42, + }; + const std::vector kValues[] = { + {'\0'}, {0xB0, 0xA0, 0xD0, 0xD0, 0xD0}, {'T'}, + }; + + MinidumpAnnotationListWriter list_writer; + + for (size_t i = 0; i < arraysize(kNames); ++i) { + auto annotation = std::make_unique(); + annotation->InitializeWithData(kNames[i], kTypes[i], kValues[i]); + list_writer.AddObject(std::move(annotation)); + } + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + 3 * sizeof(MinidumpAnnotation) + + 3 * sizeof(MinidumpUTF8String) + 3 * sizeof(MinidumpByteArray) + + strlen(kNames[0]) + 1 + kValues[0].size() + 2 + + strlen(kNames[1]) + 1 + 3 + kValues[1].size() + 1 + + strlen(kNames[2]) + 1 + 3 + kValues[2].size() + 2); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 3); + ASSERT_TRUE(list); + EXPECT_EQ(list->count, 3u); + + for (size_t i = 0; i < 3; ++i) { + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[i].name), + kNames[i]); + EXPECT_EQ(list->objects[i].type, kTypes[i]); + EXPECT_EQ(list->objects[i].reserved, 0u); + EXPECT_EQ( + + MinidumpByteArrayAtRVA(string_file.string(), list->objects[i].value), + kValues[i]); + } +} + +TEST(MinidumpAnnotationWriter, DuplicateNames) { + StringFile string_file; + + const char kName[] = "@@name!"; + const uint16_t kType = 0x1; + const std::vector kValue1{'r', 'e', 'd', '\0'}; + const std::vector kValue2{'m', 'a', 'g', 'e', 'n', 't', 'a', '\0'}; + + MinidumpAnnotationListWriter list_writer; + + auto annotation = std::make_unique(); + annotation->InitializeWithData(kName, kType, kValue1); + list_writer.AddObject(std::move(annotation)); + + annotation = std::make_unique(); + annotation->InitializeWithData(kName, kType, kValue2); + list_writer.AddObject(std::move(annotation)); + + EXPECT_TRUE(list_writer.WriteEverything(&string_file)); + + ASSERT_EQ(string_file.string().size(), + sizeof(MinidumpAnnotationList) + 2 * sizeof(MinidumpAnnotation) + + 2 * sizeof(MinidumpUTF8String) + 2 * sizeof(MinidumpByteArray) + + 2 * sizeof(kName) + kValue1.size() + kValue2.size()); + + auto* list = MinidumpAnnotationListAtStart(string_file.string(), 2); + ASSERT_TRUE(list); + EXPECT_EQ(list->count, 2u); + + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[0].name), + kName); + EXPECT_EQ(list->objects[0].type, kType); + EXPECT_EQ( + + MinidumpByteArrayAtRVA(string_file.string(), list->objects[0].value), + kValue1); + + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + list->objects[1].name), + kName); + EXPECT_EQ(list->objects[1].type, kType); + EXPECT_EQ( + + MinidumpByteArrayAtRVA(string_file.string(), list->objects[1].value), + kValue2); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/minidump/minidump_context.h b/minidump/minidump_context.h index f558832f..6f51b5a4 100644 --- a/minidump/minidump_context.h +++ b/minidump/minidump_context.h @@ -26,7 +26,7 @@ namespace crashpad { //! \brief Architecture-independent flags for `context_flags` fields in Minidump //! context structures. // -// http://zachsaw.blogspot.com/2010/11/wow64-bug-getthreadcontext-may-return.html#c5639760895973344002 +// https://zachsaw.blogspot.com/2010/11/wow64-bug-getthreadcontext-may-return.html#c5639760895973344002 enum MinidumpContextFlags : uint32_t { //! \brief The thread was executing a trap handler in kernel mode //! (`CONTEXT_EXCEPTION_ACTIVE`). @@ -61,7 +61,7 @@ enum MinidumpContextFlags : uint32_t { //! //! If this bit is set, it indicates that the bits indicating how the thread //! had entered kernel mode (::kMinidumpContextExceptionActive and - //! and ::kMinidumpContextServiceActive) are valid. This bit is only used on + //! ::kMinidumpContextServiceActive) are valid. This bit is only used on //! Windows. kMinidumpContextExceptionReporting = 0x80000000, }; @@ -154,8 +154,8 @@ struct MinidumpContextX86 { uint32_t dr6; uint32_t dr7; - // CPUContextX86::Fsave has identical layout to what the x86 CONTEXT - // structure places here. + // CPUContextX86::Fsave has identical layout to what the x86 CONTEXT structure + // places here. CPUContextX86::Fsave fsave; union { uint32_t spare_0; // As in the native x86 CONTEXT structure since Windows 8 @@ -324,9 +324,8 @@ struct alignas(16) MinidumpContextAMD64 { //! //! See Intel Software Developer’s Manual, Volume 3B: System Programming, Part //! 2 (253669-051), 17.4 “Last Branch, Interrupt, and Exception Recording - //! Overview”, and AMD Architecture Programmer’s Manual, Volume 2: - //! System Programming (24593-3.24), 13.1.6 “Control-Transfer Breakpoint - //! Features”. + //! Overview”, and AMD Architecture Programmer’s Manual, Volume 2: System + //! Programming (24593-3.24), 13.1.6 “Control-Transfer Breakpoint Features”. //! //! \{ uint64_t debug_control; @@ -337,6 +336,234 @@ struct alignas(16) MinidumpContextAMD64 { //! \} }; +//! \brief 32-bit ARM-specifc flags for MinidumpContextARM::context_flags. +enum MinidumpContextARMFlags : uint32_t { + //! \brief Identifies the context structure as 32-bit ARM. + kMinidumpContextARM = 0x40000000, + + //! \brief Indicates the validity of integer regsiters. + //! + //! Regsiters `r0`-`r15` and `cpsr` are valid. + kMinidumpContextARMInteger = kMinidumpContextARM | 0x00000002, + + //! \brief Inidicates the validity of VFP regsiters. + //! + //! Registers `d0`-`d31` and `fpscr` are valid. + kMinidumpContextARMVFP = kMinidumpContextARM | 0x00000004, + + //! \brief Indicates the validity of all registers. + kMinidumpContextARMAll = kMinidumpContextARMInteger | kMinidumpContextARMVFP, +}; + +//! \brief A 32-bit ARM CPU context (register state) carried in a minidump file. +struct MinidumpContextARM { + //! \brief A bitfield composed of values of #MinidumpContextFlags and + //! #MinidumpContextARMFlags. + //! + //! This field identifies the context structure as a 32-bit ARM CPU context, + //! and indicates which other fields in the structure are valid. + uint32_t context_flags; + + //! \brief General-purpose registers `r0`-`r15`. + uint32_t regs[11]; + uint32_t fp; // r11 + uint32_t ip; // r12 + uint32_t sp; // r13 + uint32_t lr; // r14 + uint32_t pc; // r15 + + //! \brief Current program status register. + uint32_t cpsr; + + //! \brief Floating-point status and control register. + uint32_t fpscr; + + //! \brief VFP registers `d0`-`d31`. + uint64_t vfp[32]; + + //! \brief This space is unused. It is included for compatibility with + //! breakpad (which also doesn't use it). + uint32_t extra[8]; +}; + +//! \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, + + //! \brief Indicates the validty of integer registers. + //! + //! Registers `x0`-`x31`, `pc`, and `cpsr`. + kMinidumpContextARM64Integer = kMinidumpContextARM64 | 0x00000002, + + //! \brief Indicates the validity of fpsimd registers. + //! + //! Registers `v0`-`v31`, `fpsr`, and `fpcr` are valid. + kMinidumpContextARM64Fpsimd = kMinidumpContextARM64 | 0x00000004, + + //! \brief Indicates the validity of all registers. + kMinidumpContextARM64All = + kMinidumpContextARM64Integer | kMinidumpContextARM64Fpsimd, +}; + +//! \brief A 64-bit ARM CPU context (register state) carried in a minidump file. +struct MinidumpContextARM64 { + uint64_t context_flags; + + //! \brief General-purpose registers `x0`-`x30`. + uint64_t regs[31]; + + //! \brief Stack pointer or `x31`. + uint64_t sp; + + //! \brief Program counter. + uint64_t pc; + + //! \brief Current program status register. + uint32_t cpsr; + + //! \brief Floating-point status register. + uint32_t fpsr; + + //! \brief Floating-point control register. + uint32_t fpcr; + + //! \brief NEON registers `v0`-`v31`. + uint128_struct fpsimd[32]; +}; + +//! \brief 32bit MIPS-specifc flags for MinidumpContextMIPS::context_flags. +//! Based on minidump_cpu_mips.h from breakpad +enum MinidumpContextMIPSFlags : uint32_t { + //! \brief Identifies the context structure as MIPSEL. + kMinidumpContextMIPS = 0x00040000, + + //! \brief Indicates the validity of integer registers. + //! + //! Registers `0`-`31`, `mdhi`, `mdlo`, `epc`, `badvaddr`, `status` and + //! `cause` are valid. + kMinidumpContextMIPSInteger = kMinidumpContextMIPS | 0x00000002, + + //! \brief Indicates the validity of floating point registers. + //! + //! Floating point registers `0`-`31`, `fpcsr` and `fir` are valid + kMinidumpContextMIPSFloatingPoint = kMinidumpContextMIPS | 0x00000004, + + //! \brief Indicates the validity of DSP registers. + //! + //! Registers `hi0`-`hi2`, `lo0`-`lo2` and `dsp_control` are valid + kMinidumpContextMIPSDSP = kMinidumpContextMIPS | 0x00000008, + + //! \brief Indicates the validity of all registers. + kMinidumpContextMIPSAll = kMinidumpContextMIPSInteger | + kMinidumpContextMIPSFloatingPoint | + kMinidumpContextMIPSDSP, +}; + +//! \brief A 32bit MIPS CPU context (register state) carried in a minidump file. +struct MinidumpContextMIPS { + uint32_t context_flags; + + //! \brief This padding field is included for breakpad compatibility. + uint32_t _pad0; + //! \brief General purpose registers `0`-`31`. + uint64_t regs[32]; + + //! \brief Multiply/divide result. + uint64_t mdhi, mdlo; + + //! \brief DSP registers. + uint32_t hi[3]; + uint32_t lo[3]; + uint32_t dsp_control; + //! \brief This padding field is included for breakpad compatibility. + uint32_t _pad1; + + // \brief cp0 registers. + uint64_t epc; + uint64_t badvaddr; + uint32_t status; + uint32_t cause; + + //! \brief FPU registers. + union { + struct { + float _fp_fregs; + uint32_t _fp_pad; + } fregs[32]; + double dregs[32]; + } fpregs; + + //! \brief FPU status register. + uint32_t fpcsr; + //! \brief FPU implementation register. + uint32_t fir; +}; + +//! \brief 64bit MIPS-specifc flags for MinidumpContextMIPS64::context_flags. +//! Based on minidump_cpu_mips.h from breakpad +enum MinidumpContextMIPS64Flags : uint32_t { + //! \brief Identifies the context structure as MIPS64EL. + kMinidumpContextMIPS64 = 0x00080000, + + //! \brief Indicates the validity of integer registers. + //! + //! Registers `0`-`31`, `mdhi`, `mdlo`, `epc`, `badvaddr`, `status` and + //! `cause` are valid. + kMinidumpContextMIPS64Integer = kMinidumpContextMIPS64 | 0x00000002, + + //! \brief Indicates the validity of floating point registers. + //! + //! Floating point registers `0`-`31`, `fpcsr` and `fir` are valid + kMinidumpContextMIPS64FloatingPoint = kMinidumpContextMIPS64 | 0x00000004, + + //! \brief Indicates the validity of DSP registers. + //! + //! Registers `hi0`-`hi2`, `lo0`-`lo2` and `dsp_control` are valid. + kMinidumpContextMIPS64DSP = kMinidumpContextMIPS64 | 0x00000008, + + //! \brief Indicates the validity of all registers. + kMinidumpContextMIPS64All = kMinidumpContextMIPS64Integer | + kMinidumpContextMIPS64FloatingPoint | + kMinidumpContextMIPS64DSP, +}; + +//! \brief A 32bit MIPS CPU context (register state) carried in a minidump file. +struct MinidumpContextMIPS64 { + uint64_t context_flags; + + //! \brief General purpose registers. + uint64_t regs[32]; + + //! \brief Multiply/divide result. + uint64_t mdhi, mdlo; + + //! \brief DSP registers. + uint64_t hi[3]; + uint64_t lo[3]; + uint64_t dsp_control; + + //! \brief cp0 registers. + uint64_t epc; + uint64_t badvaddr; + uint64_t status; + uint64_t cause; + + //! \brief FPU registers. + union { + struct { + float _fp_fregs; + uint32_t _fp_pad; + } fregs[32]; + double dregs[32]; + } fpregs; + + //! \brief FPU status register. + uint64_t fpcsr; + //! \brief FPU implementation register. + uint64_t fir; +}; + } // namespace crashpad #endif // CRASHPAD_MINIDUMP_MINIDUMP_CONTEXT_H_ diff --git a/minidump/minidump_context_writer.cc b/minidump/minidump_context_writer.cc index 218d7775..20adbf3f 100644 --- a/minidump/minidump_context_writer.cc +++ b/minidump/minidump_context_writer.cc @@ -73,6 +73,34 @@ MinidumpContextWriter::CreateFromSnapshot(const CPUContext* context_snapshot) { break; } + case kCPUArchitectureARM: { + context = std::make_unique(); + reinterpret_cast(context.get()) + ->InitializeFromSnapshot(context_snapshot->arm); + break; + } + + case kCPUArchitectureARM64: { + context = std::make_unique(); + reinterpret_cast(context.get()) + ->InitializeFromSnapshot(context_snapshot->arm64); + break; + } + + case kCPUArchitectureMIPSEL: { + context = std::make_unique(); + reinterpret_cast(context.get()) + ->InitializeFromSnapshot(context_snapshot->mipsel); + break; + } + + case kCPUArchitectureMIPS64EL: { + context = std::make_unique(); + reinterpret_cast(context.get()) + ->InitializeFromSnapshot(context_snapshot->mips64); + break; + } + default: { LOG(ERROR) << "unknown context architecture " << context_snapshot->architecture; @@ -239,4 +267,187 @@ size_t MinidumpContextAMD64Writer::ContextSize() const { return sizeof(context_); } +MinidumpContextARMWriter::MinidumpContextARMWriter() + : MinidumpContextWriter(), context_() { + context_.context_flags = kMinidumpContextARM; +} + +MinidumpContextARMWriter::~MinidumpContextARMWriter() = default; + +void MinidumpContextARMWriter::InitializeFromSnapshot( + const CPUContextARM* context_snapshot) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(context_.context_flags, kMinidumpContextARM); + + context_.context_flags = kMinidumpContextARMAll; + + static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), + "GPRS size mismatch"); + memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); + context_.fp = context_snapshot->fp; + context_.ip = context_snapshot->ip; + context_.sp = context_snapshot->sp; + context_.lr = context_snapshot->lr; + context_.pc = context_snapshot->pc; + context_.cpsr = context_snapshot->cpsr; + + context_.fpscr = context_snapshot->vfp_regs.fpscr; + static_assert(sizeof(context_.vfp) == sizeof(context_snapshot->vfp_regs.vfp), + "VFP size mismatch"); + memcpy(context_.vfp, context_snapshot->vfp_regs.vfp, sizeof(context_.vfp)); + + memset(context_.extra, 0, sizeof(context_.extra)); +} + +bool MinidumpContextARMWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + return file_writer->Write(&context_, sizeof(context_)); +} + +size_t MinidumpContextARMWriter::ContextSize() const { + DCHECK_GE(state(), kStateFrozen); + return sizeof(context_); +} + +MinidumpContextARM64Writer::MinidumpContextARM64Writer() + : MinidumpContextWriter(), context_() { + context_.context_flags = kMinidumpContextARM64; +} + +MinidumpContextARM64Writer::~MinidumpContextARM64Writer() = default; + +void MinidumpContextARM64Writer::InitializeFromSnapshot( + const CPUContextARM64* context_snapshot) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(context_.context_flags, kMinidumpContextARM64); + + context_.context_flags = kMinidumpContextARM64All; + + static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), + "GPRs size mismatch"); + memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); + context_.sp = context_snapshot->sp; + context_.pc = context_snapshot->pc; + + if (context_snapshot->pstate > + std::numeric_limits::max()) { + LOG(WARNING) << "pstate truncation"; + } + context_.cpsr = + static_cast(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)); +} + +bool MinidumpContextARM64Writer::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + return file_writer->Write(&context_, sizeof(context_)); +} + +size_t MinidumpContextARM64Writer::ContextSize() const { + DCHECK_GE(state(), kStateFrozen); + return sizeof(context_); +} + +MinidumpContextMIPSWriter::MinidumpContextMIPSWriter() + : MinidumpContextWriter(), context_() { + context_.context_flags = kMinidumpContextMIPS; +} + +MinidumpContextMIPSWriter::~MinidumpContextMIPSWriter() = default; + +void MinidumpContextMIPSWriter::InitializeFromSnapshot( + const CPUContextMIPS* context_snapshot) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS); + + context_.context_flags = kMinidumpContextMIPSAll; + + static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), + "GPRs size mismatch"); + memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); + context_.mdhi = context_snapshot->mdhi; + context_.mdlo = context_snapshot->mdlo; + context_.epc = context_snapshot->cp0_epc; + context_.badvaddr = context_snapshot->cp0_badvaddr; + context_.status = context_snapshot->cp0_status; + context_.cause = context_snapshot->cp0_cause; + + static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs), + "FPRs size mismatch"); + memcpy(&context_.fpregs, &context_snapshot->fpregs, sizeof(context_.fpregs)); + context_.fpcsr = context_snapshot->fpcsr; + context_.fir = context_snapshot->fir; + + for (size_t index = 0; index < 3; ++index) { + context_.hi[index] = context_snapshot->hi[index]; + context_.lo[index] = context_snapshot->lo[index]; + } + context_.dsp_control = context_snapshot->dsp_control; +} + +bool MinidumpContextMIPSWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + return file_writer->Write(&context_, sizeof(context_)); +} + +size_t MinidumpContextMIPSWriter::ContextSize() const { + DCHECK_GE(state(), kStateFrozen); + return sizeof(context_); +} + +MinidumpContextMIPS64Writer::MinidumpContextMIPS64Writer() + : MinidumpContextWriter(), context_() { + context_.context_flags = kMinidumpContextMIPS64; +} + +MinidumpContextMIPS64Writer::~MinidumpContextMIPS64Writer() = default; + +void MinidumpContextMIPS64Writer::InitializeFromSnapshot( + const CPUContextMIPS64* context_snapshot) { + DCHECK_EQ(state(), kStateMutable); + DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS64); + + context_.context_flags = kMinidumpContextMIPS64All; + + static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), + "GPRs size mismatch"); + memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); + context_.mdhi = context_snapshot->mdhi; + context_.mdlo = context_snapshot->mdlo; + context_.epc = context_snapshot->cp0_epc; + context_.badvaddr = context_snapshot->cp0_badvaddr; + context_.status = context_snapshot->cp0_status; + context_.cause = context_snapshot->cp0_cause; + + static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs), + "FPRs size mismatch"); + memcpy(context_.fpregs.dregs, + context_snapshot->fpregs.dregs, + sizeof(context_.fpregs.dregs)); + context_.fpcsr = context_snapshot->fpcsr; + context_.fir = context_snapshot->fir; + + for (size_t index = 0; index < 3; ++index) { + context_.hi[index] = context_snapshot->hi[index]; + context_.lo[index] = context_snapshot->lo[index]; + } + context_.dsp_control = context_snapshot->dsp_control; +} + +bool MinidumpContextMIPS64Writer::WriteObject( + FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + return file_writer->Write(&context_, sizeof(context_)); +} + +size_t MinidumpContextMIPS64Writer::ContextSize() const { + DCHECK_GE(state(), kStateFrozen); + return sizeof(context_); +} + } // namespace crashpad diff --git a/minidump/minidump_context_writer.h b/minidump/minidump_context_writer.h index 25d717e5..d4ab936e 100644 --- a/minidump/minidump_context_writer.h +++ b/minidump/minidump_context_writer.h @@ -155,6 +155,166 @@ class MinidumpContextAMD64Writer final : public MinidumpContextWriter { DISALLOW_COPY_AND_ASSIGN(MinidumpContextAMD64Writer); }; +//! \brief The writer for a MinidumpContextARM structure in a minidump file. +class MinidumpContextARMWriter final : public MinidumpContextWriter { + public: + MinidumpContextARMWriter(); + ~MinidumpContextARMWriter() override; + + //! \brief Initializes the MinidumpContextARM based on \a context_snapshot. + //! + //! \param[in] context_snapshot The context snapshot to use as source data. + //! + //! \note Valid in #kStateMutable. No mutation of context() may be done before + //! calling this method, and it is not normally necessary to alter + //! context() after calling this method. + void InitializeFromSnapshot(const CPUContextARM* context_snapshot); + + //! \brief Returns a pointer to the context structure that this object will + //! write. + //! + //! \attention This returns a non-`const` pointer to this object’s private + //! data so that a caller can populate the context structure directly. + //! This is done because providing setter interfaces to each field in the + //! context structure would be unwieldy and cumbersome. Care must be taken + //! to populate the context structure correctly. The context structure + //! must only be modified while this object is in the #kStateMutable + //! state. + MinidumpContextARM* context() { return &context_; } + + protected: + // MinidumpWritable: + bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpContextWriter: + size_t ContextSize() const override; + + private: + MinidumpContextARM context_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpContextARMWriter); +}; + +//! \brief The writer for a MinidumpContextARM64 structure in a minidump file. +class MinidumpContextARM64Writer final : public MinidumpContextWriter { + public: + MinidumpContextARM64Writer(); + ~MinidumpContextARM64Writer() override; + + //! \brief Initializes the MinidumpContextARM64 based on \a context_snapshot. + //! + //! \param[in] context_snapshot The context snapshot to use as source data. + //! + //! \note Valid in #kStateMutable. No mutation of context() may be done before + //! calling this method, and it is not normally necessary to alter + //! context() after calling this method. + void InitializeFromSnapshot(const CPUContextARM64* context_snapshot); + + //! \brief Returns a pointer to the context structure that this object will + //! write. + //! + //! \attention This returns a non-`const` pointer to this object’s private + //! data so that a caller can populate the context structure directly. + //! This is done because providing setter interfaces to each field in the + //! context structure would be unwieldy and cumbersome. Care must be taken + //! to populate the context structure correctly. The context structure + //! must only be modified while this object is in the #kStateMutable + //! state. + MinidumpContextARM64* context() { return &context_; } + + protected: + // MinidumpWritable: + bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpContextWriter: + size_t ContextSize() const override; + + private: + MinidumpContextARM64 context_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpContextARM64Writer); +}; + +//! \brief The writer for a MinidumpContextMIPS structure in a minidump file. +class MinidumpContextMIPSWriter final : public MinidumpContextWriter { + public: + MinidumpContextMIPSWriter(); + ~MinidumpContextMIPSWriter() override; + + //! \brief Initializes the MinidumpContextMIPS based on \a context_snapshot. + //! + //! \param[in] context_snapshot The context snapshot to use as source data. + //! + //! \note Valid in #kStateMutable. No mutation of context() may be done before + //! calling this method, and it is not normally necessary to alter + //! context() after calling this method. + void InitializeFromSnapshot(const CPUContextMIPS* context_snapshot); + + //! \brief Returns a pointer to the context structure that this object will + //! write. + //! + //! \attention This returns a non-`const` pointer to this object’s private + //! data so that a caller can populate the context structure directly. + //! This is done because providing setter interfaces to each field in the + //! context structure would be unwieldy and cumbersome. Care must be taken + //! to populate the context structure correctly. The context structure + //! must only be modified while this object is in the #kStateMutable + //! state. + MinidumpContextMIPS* context() { return &context_; } + + protected: + // MinidumpWritable: + bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpContextWriter: + size_t ContextSize() const override; + + private: + MinidumpContextMIPS context_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpContextMIPSWriter); +}; + +//! \brief The writer for a MinidumpContextMIPS64 structure in a minidump file. +class MinidumpContextMIPS64Writer final : public MinidumpContextWriter { + public: + MinidumpContextMIPS64Writer(); + ~MinidumpContextMIPS64Writer() override; + + //! \brief Initializes the MinidumpContextMIPS based on \a context_snapshot. + //! + //! \param[in] context_snapshot The context snapshot to use as source data. + //! + //! \note Valid in #kStateMutable. No mutation of context() may be done before + //! calling this method, and it is not normally necessary to alter + //! context() after calling this method. + void InitializeFromSnapshot(const CPUContextMIPS64* context_snapshot); + + //! \brief Returns a pointer to the context structure that this object will + //! write. + //! + //! \attention This returns a non-`const` pointer to this object’s private + //! data so that a caller can populate the context structure directly. + //! This is done because providing setter interfaces to each field in the + //! context structure would be unwieldy and cumbersome. Care must be taken + //! to populate the context structure correctly. The context structure + //! must only be modified while this object is in the #kStateMutable + //! state. + MinidumpContextMIPS64* context() { return &context_; } + + protected: + // MinidumpWritable: + bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpContextWriter: + size_t ContextSize() const override; + + private: + MinidumpContextMIPS64 context_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpContextMIPS64Writer); +}; + } // namespace crashpad #endif // CRASHPAD_MINIDUMP_MINIDUMP_CONTEXT_WRITER_H_ diff --git a/minidump/minidump_context_writer_test.cc b/minidump/minidump_context_writer_test.cc index 82b4db75..3216a906 100644 --- a/minidump/minidump_context_writer_test.cc +++ b/minidump/minidump_context_writer_test.cc @@ -28,6 +28,20 @@ namespace crashpad { namespace test { namespace { +template +void EmptyContextTest(void (*expect_context)(uint32_t, const Context*, bool)) { + Writer context_writer; + StringFile string_file; + EXPECT_TRUE(context_writer.WriteEverything(&string_file)); + ASSERT_EQ(string_file.string().size(), sizeof(Context)); + + const Context* observed = + MinidumpWritableAtRVA(string_file.string(), 0); + ASSERT_TRUE(observed); + + expect_context(0, observed, false); +} + TEST(MinidumpContextWriter, MinidumpContextX86Writer) { StringFile string_file; @@ -36,16 +50,8 @@ TEST(MinidumpContextWriter, MinidumpContextX86Writer) { // context. SCOPED_TRACE("zero"); - MinidumpContextX86Writer context_writer; - - EXPECT_TRUE(context_writer.WriteEverything(&string_file)); - ASSERT_EQ(string_file.string().size(), sizeof(MinidumpContextX86)); - - const MinidumpContextX86* observed = - MinidumpWritableAtRVA(string_file.string(), 0); - ASSERT_TRUE(observed); - - ExpectMinidumpContextX86(0, observed, false); + EmptyContextTest( + ExpectMinidumpContextX86); } { @@ -85,16 +91,8 @@ TEST(MinidumpContextWriter, MinidumpContextAMD64Writer) { // context. SCOPED_TRACE("zero"); - MinidumpContextAMD64Writer context_writer; - - EXPECT_TRUE(context_writer.WriteEverything(&string_file)); - ASSERT_EQ(string_file.string().size(), sizeof(MinidumpContextAMD64)); - - const MinidumpContextAMD64* observed = - MinidumpWritableAtRVA(string_file.string(), 0); - ASSERT_TRUE(observed); - - ExpectMinidumpContextAMD64(0, observed, false); + EmptyContextTest( + ExpectMinidumpContextAMD64); } { @@ -117,48 +115,102 @@ TEST(MinidumpContextWriter, MinidumpContextAMD64Writer) { } } -TEST(MinidumpContextWriter, CreateFromSnapshot_X86) { - constexpr uint32_t kSeed = 32; - - CPUContextX86 context_snapshot_x86; - CPUContext context_snapshot; - context_snapshot.x86 = &context_snapshot_x86; - InitializeCPUContextX86(&context_snapshot, kSeed); - +template +void FromSnapshotTest(const CPUContext& snapshot_context, + void (*expect_context)(uint32_t, const Context*, bool), + uint32_t seed) { std::unique_ptr context_writer = - MinidumpContextWriter::CreateFromSnapshot(&context_snapshot); + MinidumpContextWriter::CreateFromSnapshot(&snapshot_context); ASSERT_TRUE(context_writer); StringFile string_file; ASSERT_TRUE(context_writer->WriteEverything(&string_file)); - const MinidumpContextX86* observed = - MinidumpWritableAtRVA(string_file.string(), 0); + const Context* observed = + MinidumpWritableAtRVA(string_file.string(), 0); ASSERT_TRUE(observed); - ExpectMinidumpContextX86(kSeed, observed, true); + expect_context(seed, observed, true); } -TEST(MinidumpContextWriter, CreateFromSnapshot_AMD64) { +TEST(MinidumpContextWriter, X86_FromSnapshot) { + constexpr uint32_t kSeed = 32; + CPUContextX86 context_x86; + CPUContext context; + context.x86 = &context_x86; + InitializeCPUContextX86(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextX86, kSeed); +} + +TEST(MinidumpContextWriter, AMD64_FromSnapshot) { constexpr uint32_t kSeed = 64; + CPUContextX86_64 context_x86_64; + CPUContext context; + context.x86_64 = &context_x86_64; + InitializeCPUContextX86_64(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextAMD64, kSeed); +} - CPUContextX86_64 context_snapshot_x86_64; - CPUContext context_snapshot; - context_snapshot.x86_64 = &context_snapshot_x86_64; - InitializeCPUContextX86_64(&context_snapshot, kSeed); +TEST(MinidumpContextWriter, ARM_Zeros) { + EmptyContextTest( + ExpectMinidumpContextARM); +} - std::unique_ptr context_writer = - MinidumpContextWriter::CreateFromSnapshot(&context_snapshot); - ASSERT_TRUE(context_writer); +TEST(MinidumpContextWRiter, ARM64_Zeros) { + EmptyContextTest( + ExpectMinidumpContextARM64); +} - StringFile string_file; - ASSERT_TRUE(context_writer->WriteEverything(&string_file)); +TEST(MinidumpContextWriter, ARM_FromSnapshot) { + constexpr uint32_t kSeed = 32; + CPUContextARM context_arm; + CPUContext context; + context.arm = &context_arm; + InitializeCPUContextARM(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextARM, kSeed); +} - const MinidumpContextAMD64* observed = - MinidumpWritableAtRVA(string_file.string(), 0); - ASSERT_TRUE(observed); +TEST(MinidumpContextWriter, ARM64_FromSnapshot) { + constexpr uint32_t kSeed = 64; + CPUContextARM64 context_arm64; + CPUContext context; + context.arm64 = &context_arm64; + InitializeCPUContextARM64(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextARM64, kSeed); +} - ExpectMinidumpContextAMD64(kSeed, observed, true); +TEST(MinidumpContextWriter, MIPS_Zeros) { + EmptyContextTest( + ExpectMinidumpContextMIPS); +} + +TEST(MinidumpContextWriter, MIPS64_Zeros) { + EmptyContextTest( + ExpectMinidumpContextMIPS64); +} + +TEST(MinidumpContextWriter, MIPS_FromSnapshot) { + constexpr uint32_t kSeed = 32; + CPUContextMIPS context_mips; + CPUContext context; + context.mipsel = &context_mips; + InitializeCPUContextMIPS(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextMIPS, kSeed); +} + +TEST(MinidumpContextWriter, MIPS64_FromSnapshot) { + constexpr uint32_t kSeed = 64; + CPUContextMIPS64 context_mips; + CPUContext context; + context.mips64 = &context_mips; + InitializeCPUContextMIPS64(&context, kSeed); + FromSnapshotTest( + context, ExpectMinidumpContextMIPS64, kSeed); } } // namespace diff --git a/minidump/minidump_exception_writer_test.cc b/minidump/minidump_exception_writer_test.cc index a748641b..e4dc5faa 100644 --- a/minidump/minidump_exception_writer_test.cc +++ b/minidump/minidump_exception_writer_test.cc @@ -27,7 +27,7 @@ #include "minidump/test/minidump_writable_test_util.h" #include "snapshot/test/test_cpu_context.h" #include "snapshot/test/test_exception_snapshot.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/string_file.h" namespace crashpad { diff --git a/minidump/minidump_extensions.h b/minidump/minidump_extensions.h index ad69aecb..f3701dc2 100644 --- a/minidump/minidump_extensions.h +++ b/minidump/minidump_extensions.h @@ -248,6 +248,9 @@ enum MinidumpOS : uint32_t { //! \brief Native Client (NaCl). kMinidumpOSNaCl = 0x8205, + //! \brief Fuchsia. + kMinidumpOSFuchsia = 0x8206, + //! \brief Unknown operating system. kMinidumpOSUnknown = 0xffffffff, }; @@ -282,6 +285,32 @@ struct ALIGNAS(4) PACKED MinidumpSimpleStringDictionary { MinidumpSimpleStringDictionaryEntry entries[0]; }; +//! \brief A typed annotation object. +struct ALIGNAS(4) PACKED MinidumpAnnotation { + //! \brief ::RVA of a MinidumpUTF8String containing the name of the + //! annotation. + RVA name; + + //! \brief The type of data stored in the \a value of the annotation. This + //! may correspond to an \a Annotation::Type or it may be user-defined. + uint16_t type; + + //! \brief This field is always `0`. + uint16_t reserved; + + //! \brief ::RVA of a MinidumpByteArray to the data for the annotation. + RVA value; +}; + +//! \brief A list of annotation objects. +struct ALIGNAS(4) PACKED MinidumpAnnotationList { + //! \brief The number of annotation objects present. + uint32_t count; + + //! \brief A list of MinidumpAnnotation objects. + MinidumpAnnotation objects[0]; +}; + //! \brief Additional Crashpad-specific information about a module carried //! within a minidump file. //! @@ -318,7 +347,7 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfo { //! module controls the data that appears here. //! //! These strings correspond to ModuleSnapshot::AnnotationsVector() and do not - //! duplicate anything in #simple_annotations. + //! duplicate anything in #simple_annotations or #annotation_objects. //! //! This field is present when #version is at least `1`. MINIDUMP_LOCATION_DESCRIPTOR list_annotations; @@ -328,10 +357,20 @@ struct ALIGNAS(4) PACKED MinidumpModuleCrashpadInfo { //! //! These key-value pairs correspond to //! ModuleSnapshot::AnnotationsSimpleMap() and do not duplicate anything in - //! #list_annotations. + //! #list_annotations or #annotation_objects. //! //! This field is present when #version is at least `1`. MINIDUMP_LOCATION_DESCRIPTOR simple_annotations; + + //! \brief A MinidumpAnnotationList object containing the annotation objects + //! stored within the module. The module controls the data that appears + //! here. + //! + //! These key-value pairs correspond to ModuleSnapshot::AnnotationObjects() + //! and do not duplicate anything in #list_annotations or #simple_annotations. + //! + //! This field may be present when #version is at least `1`. + MINIDUMP_LOCATION_DESCRIPTOR annotation_objects; }; //! \brief A link between a MINIDUMP_MODULE structure and additional diff --git a/minidump/minidump_file_writer.cc b/minidump/minidump_file_writer.cc index b4f0a624..72265453 100644 --- a/minidump/minidump_file_writer.cc +++ b/minidump/minidump_file_writer.cc @@ -59,7 +59,7 @@ void MinidumpFileWriter::InitializeFromSnapshot( DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(header_.Signature, 0u); DCHECK_EQ(header_.TimeDateStamp, 0u); - DCHECK_EQ(header_.Flags, MiniDumpNormal); + DCHECK_EQ(static_cast(header_.Flags), MiniDumpNormal); DCHECK(streams_.empty()); // This time is truncated to an integer number of seconds, not rounded, for diff --git a/minidump/minidump_file_writer_test.cc b/minidump/minidump_file_writer_test.cc index e41ed75f..730da26b 100644 --- a/minidump/minidump_file_writer_test.cc +++ b/minidump/minidump_file_writer_test.cc @@ -21,7 +21,6 @@ #include #include "base/compiler_specific.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 +34,7 @@ #include "snapshot/test/test_process_snapshot.h" #include "snapshot/test/test_system_snapshot.h" #include "snapshot/test/test_thread_snapshot.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/string_file.h" namespace crashpad { @@ -396,16 +395,16 @@ TEST(MinidumpFileWriter, InitializeFromSnapshot_Exception) { // 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(COMPILER_GCC) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconstant-conversion" +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconstant-conversion" #define DISABLED_WCONSTANT_CONVERSION -#endif // COMPILER_GCC || __clang__ +#endif // __clang__ MSVC_SUPPRESS_WARNING(4309); // Truncation of constant value. MSVC_SUPPRESS_WARNING(4838); // Narrowing conversion. constexpr timeval kSnapshotTimeval = {static_cast(kSnapshotTime), 0}; #if defined(DISABLED_WCONSTANT_CONVERSION) -#pragma GCC diagnostic pop +#pragma clang diagnostic pop #undef DISABLED_WCONSTANT_CONVERSION #endif // DISABLED_WCONSTANT_CONVERSION diff --git a/minidump/minidump_memory_info_writer_test.cc b/minidump/minidump_memory_info_writer_test.cc index 73710392..634d3f1a 100644 --- a/minidump/minidump_memory_info_writer_test.cc +++ b/minidump/minidump_memory_info_writer_test.cc @@ -84,14 +84,16 @@ TEST(MinidumpMemoryInfoWriter, OneRegion) { auto memory_map_region = std::make_unique(); - MINIDUMP_MEMORY_INFO mmi = {0}; + MINIDUMP_MEMORY_INFO mmi; mmi.BaseAddress = 0x12340000; mmi.AllocationBase = 0x12000000; mmi.AllocationProtect = PAGE_READWRITE; + mmi.__alignment1 = 0; mmi.RegionSize = 0x6000; mmi.State = MEM_COMMIT; mmi.Protect = PAGE_NOACCESS; mmi.Type = MEM_PRIVATE; + mmi.__alignment2 = 0; memory_map_region->SetMindumpMemoryInfo(mmi); std::vector memory_map; diff --git a/minidump/minidump_memory_writer.cc b/minidump/minidump_memory_writer.cc index f6aca081..868c356e 100644 --- a/minidump/minidump_memory_writer.cc +++ b/minidump/minidump_memory_writer.cc @@ -14,6 +14,8 @@ #include "minidump/minidump_memory_writer.h" +#include +#include #include #include "base/auto_reset.h" @@ -37,7 +39,7 @@ SnapshotMinidumpMemoryWriter::~SnapshotMinidumpMemoryWriter() {} bool SnapshotMinidumpMemoryWriter::MemorySnapshotDelegateRead(void* data, size_t size) { DCHECK_EQ(state(), kStateWritable); - DCHECK_EQ(size, UnderlyingSnapshot().Size()); + DCHECK_EQ(size, UnderlyingSnapshot()->Size()); return file_writer_->Write(data, size); } @@ -50,7 +52,17 @@ bool SnapshotMinidumpMemoryWriter::WriteObject( file_writer); // This will result in MemorySnapshotDelegateRead() being called. - return memory_snapshot_->Read(this); + if (!memory_snapshot_->Read(this)) { + // If the Read() fails (perhaps because the process' memory map has changed + // since it the range was captured), write an empty block of memory. It + // would be nice to instead not include this memory, but at this point in + // the writing process, it would be difficult to amend the minidump's + // structure. See https://crashpad.chromium.org/234 for background. + std::vector empty(memory_snapshot_->Size(), 0xfe); + MemorySnapshotDelegateRead(empty.data(), empty.size()); + } + + return true; } const MINIDUMP_MEMORY_DESCRIPTOR* @@ -89,7 +101,7 @@ size_t SnapshotMinidumpMemoryWriter::Alignment() { size_t SnapshotMinidumpMemoryWriter::SizeOfObject() { DCHECK_GE(state(), kStateFrozen); - return UnderlyingSnapshot().Size(); + return UnderlyingSnapshot()->Size(); } bool SnapshotMinidumpMemoryWriter::WillWriteAtOffsetImpl(FileOffset offset) { @@ -99,7 +111,7 @@ bool SnapshotMinidumpMemoryWriter::WillWriteAtOffsetImpl(FileOffset offset) { // object’s own memory_descriptor_ field. DCHECK_GE(registered_memory_descriptors_.size(), 1u); - uint64_t base_address = UnderlyingSnapshot().Address(); + uint64_t base_address = UnderlyingSnapshot()->Address(); decltype(registered_memory_descriptors_[0]->StartOfMemoryRange) local_address; if (!AssignIfInRange(&local_address, base_address)) { LOG(ERROR) << "base_address " << base_address << " out of range"; @@ -125,10 +137,11 @@ internal::MinidumpWritable::Phase SnapshotMinidumpMemoryWriter::WritePhase() { MinidumpMemoryListWriter::MinidumpMemoryListWriter() : MinidumpStreamWriter(), - memory_writers_(), + non_owned_memory_writers_(), children_(), - memory_list_base_() { -} + snapshots_created_during_merge_(), + all_memory_writers_(), + memory_list_base_() {} MinidumpMemoryListWriter::~MinidumpMemoryListWriter() { } @@ -148,25 +161,80 @@ void MinidumpMemoryListWriter::AddMemory( std::unique_ptr memory_writer) { DCHECK_EQ(state(), kStateMutable); - AddExtraMemory(memory_writer.get()); children_.push_back(std::move(memory_writer)); } -void MinidumpMemoryListWriter::AddExtraMemory( +void MinidumpMemoryListWriter::AddNonOwnedMemory( SnapshotMinidumpMemoryWriter* memory_writer) { DCHECK_EQ(state(), kStateMutable); - memory_writers_.push_back(memory_writer); + non_owned_memory_writers_.push_back(memory_writer); +} + +void MinidumpMemoryListWriter::CoalesceOwnedMemory() { + if (children_.empty()) + return; + + DropRangesThatOverlapNonOwned(); + + std::sort(children_.begin(), + children_.end(), + [](const std::unique_ptr& a_ptr, + const std::unique_ptr& b_ptr) { + const MemorySnapshot* a = a_ptr->UnderlyingSnapshot(); + const MemorySnapshot* b = b_ptr->UnderlyingSnapshot(); + if (a->Address() == b->Address()) { + return a->Size() < b->Size(); + } + return a->Address() < b->Address(); + }); + + // Remove any empty ranges. + children_.erase( + std::remove_if(children_.begin(), + children_.end(), + [](const auto& snapshot) { + return snapshot->UnderlyingSnapshot()->Size() == 0; + }), + children_.end()); + + std::vector> all_merged; + all_merged.push_back(std::move(children_.front())); + for (size_t i = 1; i < children_.size(); ++i) { + SnapshotMinidumpMemoryWriter* top = all_merged.back().get(); + auto& child = children_[i]; + if (!DetermineMergedRange( + child->UnderlyingSnapshot(), top->UnderlyingSnapshot(), nullptr)) { + // If it doesn't overlap with the current range, push it. + all_merged.push_back(std::move(child)); + } else { + // Otherwise, merge and update the current element. + std::unique_ptr merged( + top->UnderlyingSnapshot()->MergeWithOtherSnapshot( + child->UnderlyingSnapshot())); + top->SetSnapshot(merged.get()); + snapshots_created_during_merge_.push_back(std::move(merged)); + } + } + std::swap(children_, all_merged); } bool MinidumpMemoryListWriter::Freeze() { DCHECK_EQ(state(), kStateMutable); + CoalesceOwnedMemory(); + + std::copy(non_owned_memory_writers_.begin(), + non_owned_memory_writers_.end(), + std::back_inserter(all_memory_writers_)); + for (const auto& ptr : children_) + all_memory_writers_.push_back(ptr.get()); + if (!MinidumpStreamWriter::Freeze()) { return false; } - size_t memory_region_count = memory_writers_.size(); + size_t memory_region_count = all_memory_writers_.size(); CHECK_LE(children_.size(), memory_region_count); if (!AssignIfInRange(&memory_list_base_.NumberOfMemoryRanges, @@ -181,15 +249,15 @@ bool MinidumpMemoryListWriter::Freeze() { size_t MinidumpMemoryListWriter::SizeOfObject() { DCHECK_GE(state(), kStateFrozen); - DCHECK_LE(children_.size(), memory_writers_.size()); + DCHECK_LE(children_.size(), all_memory_writers_.size()); return sizeof(memory_list_base_) + - memory_writers_.size() * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); + all_memory_writers_.size() * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); } std::vector MinidumpMemoryListWriter::Children() { DCHECK_GE(state(), kStateFrozen); - DCHECK_LE(children_.size(), memory_writers_.size()); + DCHECK_LE(children_.size(), all_memory_writers_.size()); std::vector children; for (const auto& child : children_) { @@ -207,7 +275,8 @@ bool MinidumpMemoryListWriter::WriteObject(FileWriterInterface* file_writer) { iov.iov_len = sizeof(memory_list_base_); std::vector iovecs(1, iov); - for (const SnapshotMinidumpMemoryWriter* memory_writer : memory_writers_) { + for (const SnapshotMinidumpMemoryWriter* memory_writer : + all_memory_writers_) { iov.iov_base = memory_writer->MinidumpMemoryDescriptor(); iov.iov_len = sizeof(MINIDUMP_MEMORY_DESCRIPTOR); iovecs.push_back(iov); @@ -220,4 +289,23 @@ MinidumpStreamType MinidumpMemoryListWriter::StreamType() const { return kMinidumpStreamTypeMemoryList; } +void MinidumpMemoryListWriter::DropRangesThatOverlapNonOwned() { + std::vector> non_overlapping; + non_overlapping.reserve(children_.size()); + for (auto& child_ptr : children_) { + bool overlaps = false; + for (const auto* non_owned : non_owned_memory_writers_) { + if (DetermineMergedRange(child_ptr->UnderlyingSnapshot(), + non_owned->UnderlyingSnapshot(), + nullptr)) { + overlaps = true; + break; + } + } + if (!overlaps) + non_overlapping.push_back(std::move(child_ptr)); + } + std::swap(children_, non_overlapping); +} + } // namespace crashpad diff --git a/minidump/minidump_memory_writer.h b/minidump/minidump_memory_writer.h index 95520907..4f4f27f6 100644 --- a/minidump/minidump_memory_writer.h +++ b/minidump/minidump_memory_writer.h @@ -60,7 +60,15 @@ class SnapshotMinidumpMemoryWriter : public internal::MinidumpWritable, //! \note Valid in #kStateFrozen or any preceding state. void RegisterMemoryDescriptor(MINIDUMP_MEMORY_DESCRIPTOR* memory_descriptor); + //! \brief Sets the underlying memory snapshot. Does not take ownership of \a + //! memory_snapshot. + void SetSnapshot(const MemorySnapshot* memory_snapshot) { + memory_snapshot_ = memory_snapshot; + } + private: + friend class MinidumpMemoryListWriter; + // MemorySnapshot::Delegate: bool MemorySnapshotDelegateRead(void* data, size_t size) override; @@ -95,7 +103,7 @@ class SnapshotMinidumpMemoryWriter : public internal::MinidumpWritable, //! \brief Gets the underlying memory snapshot that the memory writer will //! write to the minidump. - const MemorySnapshot& UnderlyingSnapshot() const { return *memory_snapshot_; } + const MemorySnapshot* UnderlyingSnapshot() const { return memory_snapshot_; } MINIDUMP_MEMORY_DESCRIPTOR memory_descriptor_; @@ -147,7 +155,7 @@ class MinidumpMemoryListWriter final : public internal::MinidumpStreamWriter { //! a SnapshotMinidumpMemoryWriter for thread stack memory, is an example. //! //! \note Valid in #kStateMutable. - void AddExtraMemory(SnapshotMinidumpMemoryWriter* memory_writer); + void AddNonOwnedMemory(SnapshotMinidumpMemoryWriter* memory_writer); protected: // MinidumpWritable: @@ -159,9 +167,35 @@ class MinidumpMemoryListWriter final : public internal::MinidumpStreamWriter { // MinidumpStreamWriter: MinidumpStreamType StreamType() const override; + //! \brief Merges any overlapping and abutting memory ranges that were added + //! via AddFromSnapshot() and AddMemory() into single entries. + //! + //! This is expected to be called once just before writing, generally from + //! Freeze(). + //! + //! This function has the side-effect of merging owned ranges, dropping any + //! owned ranges that overlap with non-owned ranges, removing empty ranges, + //! and sorting all ranges by address. + //! + //! Per its name, this coalesces owned memory, however, this is not a complete + //! solution for ensuring that no overlapping memory ranges are emitted in the + //! minidump. In particular, if AddNonOwnedMemory() is used to add multiple + //! overlapping ranges, then overlapping ranges will still be emitted to the + //! minidump. Currently, AddNonOwnedMemory() is used only for adding thread + //! stacks, so overlapping shouldn't be a problem in practice. For more + //! details see https://crashpad.chromium.org/bug/61 and + //! https://crrev.com/c/374539. + void CoalesceOwnedMemory(); + private: - std::vector memory_writers_; // weak + //! \brief Drops children_ ranges that overlap non_owned_memory_writers_. + void DropRangesThatOverlapNonOwned(); + + std::vector non_owned_memory_writers_; // weak std::vector> children_; + std::vector> + snapshots_created_during_merge_; + std::vector all_memory_writers_; // weak MINIDUMP_MEMORY_LIST memory_list_base_; DISALLOW_COPY_AND_ASSIGN(MinidumpMemoryListWriter); diff --git a/minidump/minidump_memory_writer_test.cc b/minidump/minidump_memory_writer_test.cc index 0290b20d..60b7fa8a 100644 --- a/minidump/minidump_memory_writer_test.cc +++ b/minidump/minidump_memory_writer_test.cc @@ -188,6 +188,47 @@ TEST(MinidumpMemoryWriter, TwoMemoryRegions) { } } +TEST(MinidumpMemoryWriter, RegionReadFails) { + MinidumpFileWriter minidump_file_writer; + auto memory_list_writer = std::make_unique(); + + constexpr uint64_t kBaseAddress = 0xfedcba9876543210; + constexpr size_t kSize = 0x1000; + constexpr uint8_t kValue = 'm'; + + auto memory_writer = + std::make_unique(kBaseAddress, kSize, kValue); + + // Make the read of that memory fail. + memory_writer->SetShouldFailRead(true); + + memory_list_writer->AddMemory(std::move(memory_writer)); + + ASSERT_TRUE(minidump_file_writer.AddStream(std::move(memory_list_writer))); + + StringFile string_file; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + const MINIDUMP_MEMORY_LIST* memory_list = nullptr; + ASSERT_NO_FATAL_FAILURE( + GetMemoryListStream(string_file.string(), &memory_list, 1)); + + MINIDUMP_MEMORY_DESCRIPTOR expected; + expected.StartOfMemoryRange = kBaseAddress; + expected.Memory.DataSize = kSize; + expected.Memory.Rva = + sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_MEMORY_LIST) + + memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); + ExpectMinidumpMemoryDescriptorAndContents( + &expected, + &memory_list->MemoryRanges[0], + string_file.string(), + 0xfe, // Not kValue ('m'), but the value that the implementation inserts + // if memory is unreadable. + true); +} + class TestMemoryStream final : public internal::MinidumpStreamWriter { public: TestMemoryStream(uint64_t base_address, size_t size, uint8_t value) @@ -241,7 +282,7 @@ TEST(MinidumpMemoryWriter, ExtraMemory) { std::make_unique(kBaseAddress0, kSize0, kValue0); auto memory_list_writer = std::make_unique(); - memory_list_writer->AddExtraMemory(test_memory_stream->memory()); + memory_list_writer->AddNonOwnedMemory(test_memory_stream->memory()); ASSERT_TRUE(minidump_file_writer.AddStream(std::move(test_memory_stream))); @@ -305,7 +346,7 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) { expect_memory_descriptors[0].Memory.DataSize = 0x1000; values[0] = 0x01; - expect_memory_descriptors[1].StartOfMemoryRange = 0x1000; + expect_memory_descriptors[1].StartOfMemoryRange = 0x2000; expect_memory_descriptors[1].Memory.DataSize = 0x2000; values[1] = 0xf4; @@ -353,6 +394,298 @@ TEST(MinidumpMemoryWriter, AddFromSnapshot) { } } +TEST(MinidumpMemoryWriter, CoalesceExplicitMultiple) { + MINIDUMP_MEMORY_DESCRIPTOR expect_memory_descriptors[4] = {}; + uint8_t values[arraysize(expect_memory_descriptors)] = {}; + + expect_memory_descriptors[0].StartOfMemoryRange = 0; + expect_memory_descriptors[0].Memory.DataSize = 1000; + values[0] = 0x01; + + expect_memory_descriptors[1].StartOfMemoryRange = 10000; + expect_memory_descriptors[1].Memory.DataSize = 2000; + values[1] = 0xf4; + + expect_memory_descriptors[2].StartOfMemoryRange = 0x1111111111111111; + expect_memory_descriptors[2].Memory.DataSize = 1024; + values[2] = 0x99; + + expect_memory_descriptors[3].StartOfMemoryRange = 0xfedcba9876543210; + expect_memory_descriptors[3].Memory.DataSize = 1024; + values[3] = 0x88; + + struct { + uint64_t base; + size_t size; + uint8_t value; + } snapshots_to_add[] = { + // Various overlapping. + {0, 500, 0x01}, + {0, 500, 0x01}, + {250, 500, 0x01}, + {600, 400, 0x01}, + + // Empty removed. + {0, 0, 0xbb}, + {300, 0, 0xcc}, + {1000, 0, 0xdd}, + {12000, 0, 0xee}, + + // Abutting. + {10000, 500, 0xf4}, + {10500, 500, 0xf4}, + {11000, 1000, 0xf4}, + + // Large base addresses. + { 0xfedcba9876543210, 1024, 0x88 }, + { 0x1111111111111111, 1024, 0x99 }, + }; + + std::vector> memory_snapshots_owner; + std::vector memory_snapshots; + for (const auto& to_add : snapshots_to_add) { + memory_snapshots_owner.push_back(std::make_unique()); + TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get(); + memory_snapshot->SetAddress(to_add.base); + memory_snapshot->SetSize(to_add.size); + memory_snapshot->SetValue(to_add.value); + memory_snapshots.push_back(memory_snapshot); + } + + auto memory_list_writer = std::make_unique(); + memory_list_writer->AddFromSnapshot(memory_snapshots); + + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.AddStream(std::move(memory_list_writer)); + + StringFile string_file; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + const MINIDUMP_MEMORY_LIST* memory_list = nullptr; + ASSERT_NO_FATAL_FAILURE( + GetMemoryListStream(string_file.string(), &memory_list, 1)); + + ASSERT_EQ(4u, memory_list->NumberOfMemoryRanges); + + for (size_t index = 0; index < memory_list->NumberOfMemoryRanges; ++index) { + SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index)); + ExpectMinidumpMemoryDescriptorAndContents( + &expect_memory_descriptors[index], + &memory_list->MemoryRanges[index], + string_file.string(), + values[index], + index == memory_list->NumberOfMemoryRanges - 1); + } +} + +struct TestRange { + TestRange(uint64_t base, size_t size) : base(base), size(size) {} + + uint64_t base; + size_t size; +}; + +// Parses a string spec to build a list of ranges suitable for CoalesceTest(). +std::vector ParseCoalesceSpec(const char* spec) { + std::vector result; + enum { kNone, kSpace, kDot } state = kNone; + const char* range_started_at = nullptr; + for (const char* p = spec;; ++p) { + EXPECT_TRUE(*p == ' ' || *p == '.' || *p == 0); + if (*p == ' ' || *p == 0) { + if (state == kDot) { + result.push_back( + TestRange(range_started_at - spec, p - range_started_at)); + } + state = kSpace; + range_started_at = nullptr; + } else if (*p == '.') { + if (state != kDot) { + range_started_at = p; + state = kDot; + } + } + + if (*p == 0) + break; + } + + return result; +} + +TEST(MinidumpMemoryWriter, CoalesceSpecHelperParse) { + const auto empty = ParseCoalesceSpec(""); + ASSERT_EQ(empty.size(), 0u); + + const auto a = ParseCoalesceSpec("..."); + ASSERT_EQ(a.size(), 1u); + EXPECT_EQ(a[0].base, 0u); + EXPECT_EQ(a[0].size, 3u); + + const auto b = ParseCoalesceSpec(" ..."); + ASSERT_EQ(b.size(), 1u); + EXPECT_EQ(b[0].base, 2u); + EXPECT_EQ(b[0].size, 3u); + + const auto c = ParseCoalesceSpec(" ... "); + ASSERT_EQ(c.size(), 1u); + EXPECT_EQ(c[0].base, 2u); + EXPECT_EQ(c[0].size, 3u); + + const auto d = ParseCoalesceSpec(" ... ...."); + ASSERT_EQ(d.size(), 2u); + EXPECT_EQ(d[0].base, 2u); + EXPECT_EQ(d[0].size, 3u); + EXPECT_EQ(d[1].base, 7u); + EXPECT_EQ(d[1].size, 4u); + + const auto e = ParseCoalesceSpec(" ... ...... ... "); + ASSERT_EQ(e.size(), 3u); + EXPECT_EQ(e[0].base, 2u); + EXPECT_EQ(e[0].size, 3u); + EXPECT_EQ(e[1].base, 7u); + EXPECT_EQ(e[1].size, 6u); + EXPECT_EQ(e[2].base, 14u); + EXPECT_EQ(e[2].size, 3u); +} + +constexpr uint8_t kMemoryValue = 0xcd; + +// Builds a coalesce test out of specs of ' ' and '.'. Tests that when the two +// ranges are added and coalesced, the result is equal to expected. +void CoalesceTest(const char* r1_spec, + const char* r2_spec, + const char* expected_spec) { + auto r1 = ParseCoalesceSpec(r1_spec); + auto r2 = ParseCoalesceSpec(r2_spec); + auto expected = ParseCoalesceSpec(expected_spec); + + std::vector expect_memory_descriptors; + for (const auto& range : expected) { + MINIDUMP_MEMORY_DESCRIPTOR mmd = {}; + mmd.StartOfMemoryRange = range.base; + mmd.Memory.DataSize = static_cast(range.size); + expect_memory_descriptors.push_back(mmd); + } + + std::vector> memory_snapshots_owner; + std::vector memory_snapshots; + + const auto add_test_memory_snapshots = [&memory_snapshots_owner, + &memory_snapshots]( + std::vector ranges) { + for (const auto& r : ranges) { + memory_snapshots_owner.push_back(std::make_unique()); + TestMemorySnapshot* memory_snapshot = memory_snapshots_owner.back().get(); + memory_snapshot->SetAddress(r.base); + memory_snapshot->SetSize(r.size); + memory_snapshot->SetValue(kMemoryValue); + memory_snapshots.push_back(memory_snapshot); + } + }; + add_test_memory_snapshots(r1); + add_test_memory_snapshots(r2); + + auto memory_list_writer = std::make_unique(); + memory_list_writer->AddFromSnapshot(memory_snapshots); + + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.AddStream(std::move(memory_list_writer)); + + StringFile string_file; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file)); + + const MINIDUMP_MEMORY_LIST* memory_list = nullptr; + ASSERT_NO_FATAL_FAILURE( + GetMemoryListStream(string_file.string(), &memory_list, 1)); + + ASSERT_EQ(expected.size(), memory_list->NumberOfMemoryRanges); + + for (size_t index = 0; index < memory_list->NumberOfMemoryRanges; ++index) { + SCOPED_TRACE(base::StringPrintf("index %" PRIuS, index)); + ExpectMinidumpMemoryDescriptorAndContents( + &expect_memory_descriptors[index], + &memory_list->MemoryRanges[index], + string_file.string(), + kMemoryValue, + index == memory_list->NumberOfMemoryRanges - 1); + } +} + +TEST(MinidumpMemoryWriter, CoalescePairsVariousCases) { + // clang-format off + + CoalesceTest(" .........", + " .......", + /* result */ " .............."); + + CoalesceTest(" .......", + " .........", + /* result */ " .............."); + + CoalesceTest(" ...", + " .........", + /* result */ " ........."); + + CoalesceTest(" .........", + " ......", + /* result */ " ........."); + + CoalesceTest(" ...", + " ........", + /* result */ " ........"); + + CoalesceTest(" ........", + " ...", + /* result */ " ........"); + + CoalesceTest(" ...", + " ........", + /* result */ " ........"); + + CoalesceTest(" ........", + " ...", + /* result */ " ........"); + + CoalesceTest(" ... ", + " ...", + /* result */ " ... ..."); + + CoalesceTest(" ...", + " ... ", + /* result */ " ... ..."); + + CoalesceTest("...", + ".....", + /* result */ "....."); + + CoalesceTest("...", + " ..", + /* result */ "....."); + + CoalesceTest(" .....", + " ..", + /* result */ " ......."); + + CoalesceTest(" ......... ......", + " .......", + /* result */ " .................."); + + CoalesceTest(" .......", + " ......... ......", + /* result */ " .................."); + + CoalesceTest(" .....", + " ......... ......", + /* result */ " ......... ......"); + + CoalesceTest(" ......... ....... .... .", + " ......... ...... ....", + /* result */ " .......................... ......."); + + // clang-format on +} + } // namespace } // namespace test } // namespace crashpad diff --git a/minidump/minidump_misc_info_writer.cc b/minidump/minidump_misc_info_writer.cc index 3e0dbbc9..d83ed237 100644 --- a/minidump/minidump_misc_info_writer.cc +++ b/minidump/minidump_misc_info_writer.cc @@ -108,6 +108,8 @@ std::string MinidumpMiscInfoDebugBuildString() { static constexpr char kOS[] = "linux"; #elif defined(OS_WIN) static constexpr char kOS[] = "win"; +#elif defined(OS_FUCHSIA) + static constexpr char kOS[] = "fuchsia"; #else #error define kOS for this operating system #endif @@ -120,6 +122,10 @@ std::string MinidumpMiscInfoDebugBuildString() { static constexpr char kCPU[] = "arm"; #elif defined(ARCH_CPU_ARM64) static constexpr char kCPU[] = "arm64"; +#elif defined(ARCH_CPU_MIPSEL) + static constexpr char kCPU[] = "mips"; +#elif defined(ARCH_CPU_MIPS64EL) + static constexpr char kCPU[] = "mips64"; #else #error define kCPU for this CPU #endif diff --git a/minidump/minidump_module_crashpad_info_writer.cc b/minidump/minidump_module_crashpad_info_writer.cc index a9c5e5d6..a31feaea 100644 --- a/minidump/minidump_module_crashpad_info_writer.cc +++ b/minidump/minidump_module_crashpad_info_writer.cc @@ -17,6 +17,7 @@ #include #include "base/logging.h" +#include "minidump/minidump_annotation_writer.h" #include "minidump/minidump_simple_string_dictionary_writer.h" #include "snapshot/module_snapshot.h" #include "util/file/file_writer.h" @@ -28,7 +29,8 @@ MinidumpModuleCrashpadInfoWriter::MinidumpModuleCrashpadInfoWriter() : MinidumpWritable(), module_(), list_annotations_(), - simple_annotations_() { + simple_annotations_(), + annotation_objects_() { module_.version = MinidumpModuleCrashpadInfo::kVersion; } @@ -54,6 +56,12 @@ void MinidumpModuleCrashpadInfoWriter::InitializeFromSnapshot( if (simple_annotations->IsUseful()) { SetSimpleAnnotations(std::move(simple_annotations)); } + + auto annotation_objects = std::make_unique(); + annotation_objects->InitializeFromList(module_snapshot->AnnotationObjects()); + if (annotation_objects->IsUseful()) { + SetAnnotationObjects(std::move(annotation_objects)); + } } void MinidumpModuleCrashpadInfoWriter::SetListAnnotations( @@ -70,8 +78,15 @@ void MinidumpModuleCrashpadInfoWriter::SetSimpleAnnotations( simple_annotations_ = std::move(simple_annotations); } +void MinidumpModuleCrashpadInfoWriter::SetAnnotationObjects( + std::unique_ptr annotation_objects) { + DCHECK_EQ(state(), kStateMutable); + + annotation_objects_ = std::move(annotation_objects); +} + bool MinidumpModuleCrashpadInfoWriter::IsUseful() const { - return list_annotations_ || simple_annotations_; + return list_annotations_ || simple_annotations_ || annotation_objects_; } bool MinidumpModuleCrashpadInfoWriter::Freeze() { @@ -90,6 +105,11 @@ bool MinidumpModuleCrashpadInfoWriter::Freeze() { &module_.simple_annotations); } + if (annotation_objects_) { + annotation_objects_->RegisterLocationDescriptor( + &module_.annotation_objects); + } + return true; } @@ -110,6 +130,9 @@ MinidumpModuleCrashpadInfoWriter::Children() { if (simple_annotations_) { children.push_back(simple_annotations_.get()); } + if (annotation_objects_) { + children.push_back(annotation_objects_.get()); + } return children; } diff --git a/minidump/minidump_module_crashpad_info_writer.h b/minidump/minidump_module_crashpad_info_writer.h index f6a2f320..850db040 100644 --- a/minidump/minidump_module_crashpad_info_writer.h +++ b/minidump/minidump_module_crashpad_info_writer.h @@ -28,6 +28,7 @@ namespace crashpad { +class MinidumpAnnotationListWriter; class MinidumpSimpleStringDictionaryWriter; class ModuleSnapshot; @@ -74,6 +75,17 @@ class MinidumpModuleCrashpadInfoWriter final void SetSimpleAnnotations( std::unique_ptr simple_annotations); + //! \brief Arranges for MinidumpModuleCrashpadInfo::annotation_objects to + //! point to the MinidumpAnnotationListWriter object to be written by + //! \a annotation_objects. + //! + //! This object takes ownership of \a annotation_objects and becomes its + //! parent in the overall tree of internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void SetAnnotationObjects( + std::unique_ptr annotation_objects); + //! \brief Determines whether the object is useful. //! //! A useful object is one that carries data that makes a meaningful @@ -94,6 +106,7 @@ class MinidumpModuleCrashpadInfoWriter final MinidumpModuleCrashpadInfo module_; std::unique_ptr list_annotations_; std::unique_ptr simple_annotations_; + std::unique_ptr annotation_objects_; DISALLOW_COPY_AND_ASSIGN(MinidumpModuleCrashpadInfoWriter); }; diff --git a/minidump/minidump_module_crashpad_info_writer_test.cc b/minidump/minidump_module_crashpad_info_writer_test.cc index d6e8efc8..63a9338c 100644 --- a/minidump/minidump_module_crashpad_info_writer_test.cc +++ b/minidump/minidump_module_crashpad_info_writer_test.cc @@ -20,7 +20,9 @@ #include #include "gtest/gtest.h" +#include "minidump/minidump_annotation_writer.h" #include "minidump/minidump_simple_string_dictionary_writer.h" +#include "minidump/test/minidump_byte_array_writer_test_util.h" #include "minidump/test/minidump_file_writer_test_util.h" #include "minidump/test/minidump_string_writer_test_util.h" #include "minidump/test/minidump_writable_test_util.h" @@ -103,6 +105,8 @@ TEST(MinidumpModuleCrashpadInfoWriter, EmptyModule) { EXPECT_EQ(module->list_annotations.Rva, 0u); EXPECT_EQ(module->simple_annotations.DataSize, 0u); EXPECT_EQ(module->simple_annotations.Rva, 0u); + EXPECT_EQ(module->annotation_objects.DataSize, 0u); + EXPECT_EQ(module->annotation_objects.Rva, 0u); } TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { @@ -111,6 +115,7 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { static constexpr char kValue[] = "value"; static constexpr char kEntry[] = "entry"; std::vector vector(1, std::string(kEntry)); + const AnnotationSnapshot annotation("one", 42, {'t', 'e', 's', 't'}); StringFile string_file; @@ -130,6 +135,10 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { std::move(simple_string_dictionary_entry_writer)); module_writer->SetSimpleAnnotations( std::move(simple_string_dictionary_writer)); + auto annotation_list_writer = + std::make_unique(); + annotation_list_writer->InitializeFromList({annotation}); + module_writer->SetAnnotationObjects(std::move(annotation_list_writer)); EXPECT_TRUE(module_writer->IsUseful()); module_list_writer->AddModule(std::move(module_writer), kMinidumpModuleListIndex); @@ -140,14 +149,16 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { ASSERT_EQ(string_file.string().size(), sizeof(MinidumpModuleCrashpadInfoList) + sizeof(MinidumpModuleCrashpadInfoLink) + - sizeof(MinidumpModuleCrashpadInfo) + - sizeof(MinidumpRVAList) + - sizeof(RVA) + - sizeof(MinidumpSimpleStringDictionary) + + sizeof(MinidumpModuleCrashpadInfo) + sizeof(MinidumpRVAList) + + sizeof(RVA) + sizeof(MinidumpSimpleStringDictionary) + sizeof(MinidumpSimpleStringDictionaryEntry) + - sizeof(MinidumpUTF8String) + arraysize(kEntry) + 2 + // padding + sizeof(MinidumpAnnotationList) + 2 + // padding + sizeof(MinidumpAnnotation) + sizeof(MinidumpUTF8String) + + arraysize(kEntry) + 2 + // padding sizeof(MinidumpUTF8String) + arraysize(kKey) + - sizeof(MinidumpUTF8String) + arraysize(kValue)); + sizeof(MinidumpUTF8String) + arraysize(kValue) + + sizeof(MinidumpUTF8String) + annotation.name.size() + 1 + + sizeof(MinidumpByteArray) + annotation.value.size()); const MinidumpModuleCrashpadInfoList* module_list = MinidumpModuleCrashpadInfoListAtStart(string_file.string(), 1); @@ -165,6 +176,8 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { EXPECT_NE(module->list_annotations.Rva, 0u); EXPECT_NE(module->simple_annotations.DataSize, 0u); EXPECT_NE(module->simple_annotations.Rva, 0u); + EXPECT_NE(module->annotation_objects.DataSize, 0u); + EXPECT_NE(module->annotation_objects.Rva, 0u); const MinidumpRVAList* list_annotations = MinidumpWritableAtLocationDescriptor( @@ -188,18 +201,36 @@ TEST(MinidumpModuleCrashpadInfoWriter, FullModule) { EXPECT_EQ(MinidumpUTF8StringAtRVAAsString( string_file.string(), simple_annotations->entries[0].value), kValue); + + const MinidumpAnnotationList* annotation_objects = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module->annotation_objects); + ASSERT_TRUE(annotation_objects); + + ASSERT_EQ(annotation_objects->count, 1u); + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString( + string_file.string(), annotation_objects->objects[0].name), + annotation.name); + EXPECT_EQ(annotation_objects->objects[0].type, 42u); + EXPECT_EQ(annotation_objects->objects[0].reserved, 0u); + EXPECT_EQ(MinidumpByteArrayAtRVA(string_file.string(), + annotation_objects->objects[0].value), + annotation.value); } TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { constexpr uint32_t kMinidumpModuleListIndex0 = 0; static constexpr char kKey0[] = "key"; static constexpr char kValue0[] = "value"; + const AnnotationSnapshot annotation0("name", 0x8FFF, {'t', '\0', 't'}); constexpr uint32_t kMinidumpModuleListIndex1 = 2; constexpr uint32_t kMinidumpModuleListIndex2 = 5; static constexpr char kKey2A[] = "K"; static constexpr char kValue2A[] = "VVV"; static constexpr char kKey2B[] = "river"; static constexpr char kValue2B[] = "hudson"; + const AnnotationSnapshot annotation2a("A2", 0xDDDD, {2, 4, 6, 8}); + const AnnotationSnapshot annotation2b("A3", 0xDDDF, {'m', 'o', 'o'}); StringFile string_file; @@ -216,6 +247,12 @@ TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { std::move(simple_string_dictionary_entry_writer_0)); module_writer_0->SetSimpleAnnotations( std::move(simple_string_dictionary_writer_0)); + auto annotation_list_writer_0 = + std::make_unique(); + auto annotation_writer_0 = std::make_unique(); + annotation_writer_0->InitializeFromSnapshot(annotation0); + annotation_list_writer_0->AddObject(std::move(annotation_writer_0)); + module_writer_0->SetAnnotationObjects(std::move(annotation_list_writer_0)); EXPECT_TRUE(module_writer_0->IsUseful()); module_list_writer->AddModule(std::move(module_writer_0), kMinidumpModuleListIndex0); @@ -240,6 +277,15 @@ TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { std::move(simple_string_dictionary_entry_writer_2b)); module_writer_2->SetSimpleAnnotations( std::move(simple_string_dictionary_writer_2)); + auto annotation_list_writer_2 = + std::make_unique(); + auto annotation_writer_2a = std::make_unique(); + annotation_writer_2a->InitializeFromSnapshot(annotation2a); + auto annotation_writer_2b = std::make_unique(); + annotation_writer_2b->InitializeFromSnapshot(annotation2b); + annotation_list_writer_2->AddObject(std::move(annotation_writer_2a)); + annotation_list_writer_2->AddObject(std::move(annotation_writer_2b)); + module_writer_2->SetAnnotationObjects(std::move(annotation_list_writer_2)); EXPECT_TRUE(module_writer_2->IsUseful()); module_list_writer->AddModule(std::move(module_writer_2), kMinidumpModuleListIndex2); @@ -279,6 +325,21 @@ TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { string_file.string(), simple_annotations_0->entries[0].value), kValue0); + const MinidumpAnnotationList* annotation_list_0 = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_0->annotation_objects); + ASSERT_TRUE(annotation_list_0); + + ASSERT_EQ(annotation_list_0->count, 1u); + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + annotation_list_0->objects[0].name), + annotation0.name); + EXPECT_EQ(annotation_list_0->objects[0].type, annotation0.type); + EXPECT_EQ(annotation_list_0->objects[0].reserved, 0u); + EXPECT_EQ(MinidumpByteArrayAtRVA(string_file.string(), + annotation_list_0->objects[0].value), + annotation0.value); + EXPECT_EQ(module_list->modules[1].minidump_module_list_index, kMinidumpModuleListIndex1); const MinidumpModuleCrashpadInfo* module_1 = @@ -298,6 +359,11 @@ TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { string_file.string(), module_1->simple_annotations); EXPECT_FALSE(simple_annotations_1); + const MinidumpAnnotationList* annotation_list_1 = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_1->annotation_objects); + EXPECT_FALSE(annotation_list_1); + EXPECT_EQ(module_list->modules[2].minidump_module_list_index, kMinidumpModuleListIndex2); const MinidumpModuleCrashpadInfo* module_2 = @@ -330,6 +396,30 @@ TEST(MinidumpModuleCrashpadInfoWriter, ThreeModules) { EXPECT_EQ(MinidumpUTF8StringAtRVAAsString( string_file.string(), simple_annotations_2->entries[1].value), kValue2B); + + const MinidumpAnnotationList* annotation_list_2 = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_2->annotation_objects); + ASSERT_TRUE(annotation_list_2); + + ASSERT_EQ(annotation_list_2->count, 2u); + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + annotation_list_2->objects[0].name), + annotation2a.name); + EXPECT_EQ(annotation_list_2->objects[0].type, annotation2a.type); + EXPECT_EQ(annotation_list_2->objects[0].reserved, 0u); + EXPECT_EQ(MinidumpByteArrayAtRVA(string_file.string(), + annotation_list_2->objects[0].value), + annotation2a.value); + + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + annotation_list_2->objects[1].name), + annotation2b.name); + EXPECT_EQ(annotation_list_2->objects[1].type, annotation2b.type); + EXPECT_EQ(annotation_list_2->objects[1].reserved, 0u); + EXPECT_EQ(MinidumpByteArrayAtRVA(string_file.string(), + annotation_list_2->objects[1].value), + annotation2b.value); } TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { @@ -341,6 +431,8 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { static constexpr char kValue2[] = "different_value"; static constexpr char kEntry3A[] = "list"; static constexpr char kEntry3B[] = "erine"; + const AnnotationSnapshot annotation( + "market", 1, {'2', '3', 'r', 'd', ' ', 'S', 't', '.'}); std::vector module_snapshots; @@ -369,6 +461,10 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { module_snapshot_3.SetAnnotationsVector(annotations_vector_3); module_snapshots.push_back(&module_snapshot_3); + TestModuleSnapshot module_snapshot_4; + module_snapshot_4.SetAnnotationObjects({annotation}); + module_snapshots.push_back(&module_snapshot_4); + auto module_list_writer = std::make_unique(); module_list_writer->InitializeFromSnapshot(module_snapshots); @@ -378,7 +474,7 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { ASSERT_TRUE(module_list_writer->WriteEverything(&string_file)); const MinidumpModuleCrashpadInfoList* module_list = - MinidumpModuleCrashpadInfoListAtStart(string_file.string(), 3); + MinidumpModuleCrashpadInfoListAtStart(string_file.string(), 4); ASSERT_TRUE(module_list); EXPECT_EQ(module_list->modules[0].minidump_module_list_index, 0u); @@ -413,6 +509,9 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { string_file.string(), simple_annotations_0->entries[1].value), kValue0A); + EXPECT_FALSE(MinidumpWritableAtLocationDescriptor( + string_file.string(), module_0->annotation_objects)); + EXPECT_EQ(module_list->modules[1].minidump_module_list_index, 2u); const MinidumpModuleCrashpadInfo* module_2 = MinidumpWritableAtLocationDescriptor( @@ -439,6 +538,9 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { string_file.string(), simple_annotations_2->entries[0].value), kValue2); + EXPECT_FALSE(MinidumpWritableAtLocationDescriptor( + string_file.string(), module_2->annotation_objects)); + EXPECT_EQ(module_list->modules[2].minidump_module_list_index, 3u); const MinidumpModuleCrashpadInfo* module_3 = MinidumpWritableAtLocationDescriptor( @@ -464,6 +566,39 @@ TEST(MinidumpModuleCrashpadInfoWriter, InitializeFromSnapshot) { MinidumpWritableAtLocationDescriptor( string_file.string(), module_3->simple_annotations); EXPECT_FALSE(simple_annotations_3); + + EXPECT_FALSE(MinidumpWritableAtLocationDescriptor( + string_file.string(), module_3->annotation_objects)); + + EXPECT_EQ(module_list->modules[3].minidump_module_list_index, 4u); + const MinidumpModuleCrashpadInfo* module_4 = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_list->modules[3].location); + ASSERT_TRUE(module_4); + + EXPECT_EQ(module_4->version, MinidumpModuleCrashpadInfo::kVersion); + + EXPECT_FALSE(MinidumpWritableAtLocationDescriptor( + string_file.string(), module_4->list_annotations)); + EXPECT_FALSE( + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_4->simple_annotations)); + + auto* annotation_list_4 = + MinidumpWritableAtLocationDescriptor( + string_file.string(), module_4->annotation_objects); + ASSERT_TRUE(annotation_list_4); + + ASSERT_EQ(annotation_list_4->count, 1u); + + EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), + annotation_list_4->objects[0].name), + annotation.name); + EXPECT_EQ(annotation_list_4->objects[0].type, annotation.type); + EXPECT_EQ(annotation_list_4->objects[0].reserved, 0u); + EXPECT_EQ(MinidumpByteArrayAtRVA(string_file.string(), + annotation_list_4->objects[0].value), + annotation.value); } } // namespace diff --git a/minidump/minidump_module_writer.h b/minidump/minidump_module_writer.h index 775a8523..555c4115 100644 --- a/minidump/minidump_module_writer.h +++ b/minidump/minidump_module_writer.h @@ -317,8 +317,8 @@ class MinidumpModuleListWriter final : public internal::MinidumpStreamWriter { //! \param[in] module_snapshots The module snapshots to use as source data. //! //! \note Valid in #kStateMutable. AddModule() may not be called before this - //! this method, and it is not normally necessary to call AddModule() - //! after this method. + //! method, and it is not normally necessary to call AddModule() after + //! this method. void InitializeFromSnapshot( const std::vector& module_snapshots); diff --git a/minidump/minidump_module_writer_test.cc b/minidump/minidump_module_writer_test.cc index f6a278e9..0fddfe88 100644 --- a/minidump/minidump_module_writer_test.cc +++ b/minidump/minidump_module_writer_test.cc @@ -28,7 +28,7 @@ #include "minidump/test/minidump_string_writer_test_util.h" #include "minidump/test/minidump_writable_test_util.h" #include "snapshot/test/test_module_snapshot.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/string_file.h" #include "util/misc/implicit_cast.h" #include "util/misc/uuid.h" @@ -83,7 +83,7 @@ TEST(MinidumpModuleWriter, EmptyModuleList) { // If |expected_pdb_name| is not nullptr, |codeview_record| is used to locate a // CodeView record in |file_contents|, and its fields are compared against the -// the |expected_pdb_*| values. If |expected_pdb_uuid| is supplied, the CodeView +// |expected_pdb_*| values. If |expected_pdb_uuid| is supplied, the CodeView // record must be a PDB 7.0 link, otherwise, it must be a PDB 2.0 link. If // |expected_pdb_name| is nullptr, |codeview_record| must not point to anything. void ExpectCodeViewRecord(const MINIDUMP_LOCATION_DESCRIPTOR* codeview_record, diff --git a/minidump/minidump_string_writer_test.cc b/minidump/minidump_string_writer_test.cc index b8eb7030..382baaf7 100644 --- a/minidump/minidump_string_writer_test.cc +++ b/minidump/minidump_string_writer_test.cc @@ -84,12 +84,11 @@ TEST(MinidumpStringWriter, MinidumpUTF16StringWriter) { const size_t expected_utf16_units_with_nul = kTestData[index].output_length + 1; - MINIDUMP_STRING tmp = {0}; + MINIDUMP_STRING* tmp; ALLOW_UNUSED_LOCAL(tmp); const size_t expected_utf16_bytes = - expected_utf16_units_with_nul * sizeof(tmp.Buffer[0]); - ASSERT_EQ(string_file.string().size(), - sizeof(MINIDUMP_STRING) + expected_utf16_bytes); + expected_utf16_units_with_nul * sizeof(tmp->Buffer[0]); + ASSERT_EQ(string_file.string().size(), sizeof(*tmp) + expected_utf16_bytes); const MINIDUMP_STRING* minidump_string = MinidumpStringAtRVA(string_file.string(), 0); @@ -129,11 +128,11 @@ TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) { const MINIDUMP_STRING* minidump_string = MinidumpStringAtRVA(string_file.string(), 0); EXPECT_TRUE(minidump_string); - MINIDUMP_STRING tmp = {0}; + MINIDUMP_STRING* tmp; ALLOW_UNUSED_LOCAL(tmp); - EXPECT_EQ(minidump_string->Length, - string_file.string().size() - sizeof(MINIDUMP_STRING) - - sizeof(tmp.Buffer[0])); + EXPECT_EQ( + minidump_string->Length, + string_file.string().size() - sizeof(*tmp) - sizeof(tmp->Buffer[0])); base::string16 output_string = MinidumpStringAtRVAAsString(string_file.string(), 0); EXPECT_FALSE(output_string.empty()); diff --git a/minidump/minidump_system_info_writer.cc b/minidump/minidump_system_info_writer.cc index 9c665f4f..cc87d242 100644 --- a/minidump/minidump_system_info_writer.cc +++ b/minidump/minidump_system_info_writer.cc @@ -123,6 +123,12 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( case kCPUArchitectureX86_64: cpu_architecture = kMinidumpCPUArchitectureAMD64; break; + case kCPUArchitectureARM: + cpu_architecture = kMinidumpCPUArchitectureARM; + break; + case kCPUArchitectureARM64: + cpu_architecture = kMinidumpCPUArchitectureARM64; + break; default: NOTREACHED(); cpu_architecture = kMinidumpCPUArchitectureUnknown; @@ -160,6 +166,15 @@ void MinidumpSystemInfoWriter::InitializeFromSnapshot( case SystemSnapshot::kOperatingSystemWindows: operating_system = kMinidumpOSWin32NT; break; + case SystemSnapshot::kOperatingSystemLinux: + operating_system = kMinidumpOSLinux; + break; + case SystemSnapshot::kOperatingSystemAndroid: + operating_system = kMinidumpOSAndroid; + break; + case SystemSnapshot::kOperatingSystemFuchsia: + operating_system = kMinidumpOSFuchsia; + break; default: NOTREACHED(); operating_system = kMinidumpOSUnknown; diff --git a/minidump/minidump_system_info_writer_test.cc b/minidump/minidump_system_info_writer_test.cc index 645f39ed..99c599c1 100644 --- a/minidump/minidump_system_info_writer_test.cc +++ b/minidump/minidump_system_info_writer_test.cc @@ -27,7 +27,7 @@ #include "minidump/test/minidump_string_writer_test_util.h" #include "minidump/test/minidump_writable_test_util.h" #include "snapshot/test/test_system_snapshot.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/string_file.h" namespace crashpad { @@ -39,11 +39,11 @@ void GetSystemInfoStream(const std::string& file_contents, const MINIDUMP_SYSTEM_INFO** system_info, const MINIDUMP_STRING** csd_version) { // The expected number of bytes for the CSD version’s MINIDUMP_STRING::Buffer. - MINIDUMP_STRING tmp = {0}; + MINIDUMP_STRING* tmp; ALLOW_UNUSED_LOCAL(tmp); - const size_t kCSDVersionBytes = csd_version_length * sizeof(tmp.Buffer[0]); + const size_t kCSDVersionBytes = csd_version_length * sizeof(tmp->Buffer[0]); const size_t kCSDVersionBytesWithNUL = - kCSDVersionBytes + sizeof(tmp.Buffer[0]); + kCSDVersionBytes + sizeof(tmp->Buffer[0]); constexpr size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); constexpr size_t kSystemInfoStreamOffset = diff --git a/minidump/minidump_test.gyp b/minidump/minidump_test.gyp index beb41519..cfc0606d 100644 --- a/minidump/minidump_test.gyp +++ b/minidump/minidump_test.gyp @@ -64,6 +64,7 @@ '..', ], 'sources': [ + 'minidump_annotation_writer_test.cc', 'minidump_byte_array_writer_test.cc', 'minidump_context_writer_test.cc', 'minidump_crashpad_info_writer_test.cc', diff --git a/minidump/minidump_thread_writer.cc b/minidump/minidump_thread_writer.cc index 4f390ddb..67658e18 100644 --- a/minidump/minidump_thread_writer.cc +++ b/minidump/minidump_thread_writer.cc @@ -173,7 +173,7 @@ void MinidumpThreadListWriter::AddThread( if (memory_list_writer_) { SnapshotMinidumpMemoryWriter* stack = thread->Stack(); if (stack) { - memory_list_writer_->AddExtraMemory(stack); + memory_list_writer_->AddNonOwnedMemory(stack); } } diff --git a/minidump/minidump_thread_writer_test.cc b/minidump/minidump_thread_writer_test.cc index 60173da2..e95c9044 100644 --- a/minidump/minidump_thread_writer_test.cc +++ b/minidump/minidump_thread_writer_test.cc @@ -31,7 +31,7 @@ #include "snapshot/test/test_cpu_context.h" #include "snapshot/test/test_memory_snapshot.h" #include "snapshot/test/test_thread_snapshot.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/string_file.h" namespace crashpad { @@ -545,7 +545,7 @@ void RunInitializeFromSnapshotTest(bool thread_id_collision) { expect_threads[1].ThreadId = 11; expect_threads[1].SuspendCount = 12; expect_threads[1].Priority = 13; - expect_threads[1].Teb = 0xfedcba9876543210; + expect_threads[1].Teb = 0x1111111111111111; expect_threads[1].ThreadContext.DataSize = sizeof(MinidumpContextType); context_seeds[1] = 0x40000001; tebs[1].StartOfMemoryRange = expect_threads[1].Teb; @@ -554,7 +554,7 @@ void RunInitializeFromSnapshotTest(bool thread_id_collision) { expect_threads[2].ThreadId = 21; expect_threads[2].SuspendCount = 22; expect_threads[2].Priority = 23; - expect_threads[2].Teb = 0x1111111111111111; + expect_threads[2].Teb = 0xfedcba9876543210; expect_threads[2].Stack.StartOfMemoryRange = 0x3000; expect_threads[2].Stack.Memory.DataSize = 0x300; expect_threads[2].ThreadContext.DataSize = sizeof(MinidumpContextType); diff --git a/minidump/minidump_writable.h b/minidump/minidump_writable.h index 3518e6a9..b2ebf04e 100644 --- a/minidump/minidump_writable.h +++ b/minidump/minidump_writable.h @@ -127,9 +127,9 @@ class MinidumpWritable { //! Some objects, such as those capturing memory region snapshots, are //! written to minidump files after all other objects. This “late” phase //! identifies such objects. This is useful to improve spatial locality in - //! in minidump files in accordance with expected access patterns: unlike - //! most other data, memory snapshots are large and the entire snapshots do - //! not need to be consulted in order to process a minidump file. + //! minidump files in accordance with expected access patterns: unlike most + //! other data, memory snapshots are large and do not usually need to be + //! consulted in their entirety in order to process a minidump file. kPhaseLate, }; diff --git a/minidump/test/minidump_context_test_util.cc b/minidump/test/minidump_context_test_util.cc index cd8e5227..28f94106 100644 --- a/minidump/test/minidump_context_test_util.cc +++ b/minidump/test/minidump_context_test_util.cc @@ -140,6 +140,135 @@ void InitializeMinidumpContextAMD64(MinidumpContextAMD64* context, context->last_exception_from_rip = value++; } +void InitializeMinidumpContextARM(MinidumpContextARM* context, uint32_t seed) { + if (seed == 0) { + memset(context, 0, sizeof(*context)); + context->context_flags = kMinidumpContextARM; + return; + } + + context->context_flags = kMinidumpContextARMAll; + + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(context->regs); ++index) { + context->regs[index] = value++; + } + context->fp = value++; + context->ip = value++; + context->ip = value++; + context->sp = value++; + context->lr = value++; + context->pc = value++; + context->cpsr = value++; + + for (size_t index = 0; index < arraysize(context->vfp); ++index) { + context->vfp[index] = value++; + } + context->fpscr = value++; +} + +void InitializeMinidumpContextARM64(MinidumpContextARM64* context, + uint32_t seed) { + if (seed == 0) { + memset(context, 0, sizeof(*context)); + context->context_flags = kMinidumpContextARM64; + return; + } + + context->context_flags = kMinidumpContextARM64All; + + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(context->regs); ++index) { + context->regs[index] = value++; + } + context->sp = value++; + context->pc = value++; + context->cpsr = value++; + + for (size_t index = 0; index < arraysize(context->fpsimd); ++index) { + context->fpsimd[index].lo = value++; + context->fpsimd[index].hi = value++; + } + context->fpsr = value++; + context->fpcr = value++; +} + +void InitializeMinidumpContextMIPS(MinidumpContextMIPS* context, + uint32_t seed) { + if (seed == 0) { + memset(context, 0, sizeof(*context)); + context->context_flags = kMinidumpContextMIPS; + return; + } + + context->context_flags = kMinidumpContextMIPSAll; + + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(context->regs); ++index) { + context->regs[index] = value++; + } + + context->mdlo = value++; + context->mdhi = value++; + context->epc = value++; + context->badvaddr = value++; + context->status = value++; + context->cause = value++; + + for (size_t index = 0; index < arraysize(context->fpregs.fregs); ++index) { + context->fpregs.fregs[index]._fp_fregs = static_cast(value++); + } + + context->fpcsr = value++; + context->fir = value++; + + for (size_t index = 0; index < 3; ++index) { + context->hi[index] = value++; + context->lo[index] = value++; + } + + context->dsp_control = value++; +} + +void InitializeMinidumpContextMIPS64(MinidumpContextMIPS64* context, + uint32_t seed) { + if (seed == 0) { + memset(context, 0, sizeof(*context)); + context->context_flags = kMinidumpContextMIPS64; + return; + } + + context->context_flags = kMinidumpContextMIPS64All; + + uint64_t value = seed; + + for (size_t index = 0; index < arraysize(context->regs); ++index) { + context->regs[index] = value++; + } + + context->mdlo = value++; + context->mdhi = value++; + context->epc = value++; + context->badvaddr = value++; + context->status = value++; + context->cause = value++; + + for (size_t index = 0; index < arraysize(context->fpregs.dregs); ++index) { + context->fpregs.dregs[index] = static_cast(value++); + } + context->fpcsr = value++; + context->fir = value++; + + for (size_t index = 0; index < 3; ++index) { + context->hi[index] = value++; + context->lo[index] = value++; + } + context->dsp_control = value++; +} + namespace { // Using gtest assertions, compares |expected| to |observed|. This is @@ -350,5 +479,118 @@ void ExpectMinidumpContextAMD64( } } +void ExpectMinidumpContextARM(uint32_t expect_seed, + const MinidumpContextARM* observed, + bool snapshot) { + MinidumpContextARM expected; + InitializeMinidumpContextARM(&expected, expect_seed); + + EXPECT_EQ(observed->context_flags, expected.context_flags); + + for (size_t index = 0; index < arraysize(expected.regs); ++index) { + EXPECT_EQ(observed->regs[index], expected.regs[index]); + } + EXPECT_EQ(observed->fp, expected.fp); + EXPECT_EQ(observed->ip, expected.ip); + EXPECT_EQ(observed->sp, expected.sp); + EXPECT_EQ(observed->lr, expected.lr); + EXPECT_EQ(observed->pc, expected.pc); + EXPECT_EQ(observed->cpsr, expected.cpsr); + + EXPECT_EQ(observed->fpscr, expected.fpscr); + for (size_t index = 0; index < arraysize(expected.vfp); ++index) { + EXPECT_EQ(observed->vfp[index], expected.vfp[index]); + } + for (size_t index = 0; index < arraysize(expected.extra); ++index) { + EXPECT_EQ(observed->extra[index], snapshot ? 0 : expected.extra[index]); + } +} + +void ExpectMinidumpContextARM64(uint32_t expect_seed, + const MinidumpContextARM64* observed, + bool snapshot) { + MinidumpContextARM64 expected; + InitializeMinidumpContextARM64(&expected, expect_seed); + + EXPECT_EQ(observed->context_flags, expected.context_flags); + + for (size_t index = 0; index < arraysize(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) { + EXPECT_EQ(observed->fpsimd[index].lo, expected.fpsimd[index].lo); + EXPECT_EQ(observed->fpsimd[index].hi, expected.fpsimd[index].hi); + } +} + +void ExpectMinidumpContextMIPS(uint32_t expect_seed, + const MinidumpContextMIPS* observed, + bool snapshot) { + MinidumpContextMIPS expected; + InitializeMinidumpContextMIPS(&expected, expect_seed); + + EXPECT_EQ(observed->context_flags, expected.context_flags); + + for (size_t index = 0; index < arraysize(expected.regs); ++index) { + EXPECT_EQ(observed->regs[index], expected.regs[index]); + } + + EXPECT_EQ(observed->mdlo, expected.mdlo); + EXPECT_EQ(observed->mdhi, expected.mdhi); + EXPECT_EQ(observed->epc, expected.epc); + EXPECT_EQ(observed->badvaddr, expected.badvaddr); + EXPECT_EQ(observed->status, expected.status); + EXPECT_EQ(observed->cause, expected.cause); + + for (size_t index = 0; index < arraysize(expected.fpregs.fregs); ++index) { + EXPECT_EQ(observed->fpregs.fregs[index]._fp_fregs, + expected.fpregs.fregs[index]._fp_fregs); + } + EXPECT_EQ(observed->fpcsr, expected.fpcsr); + EXPECT_EQ(observed->fir, expected.fir); + + for (size_t index = 0; index < 3; ++index) { + EXPECT_EQ(observed->hi[index], expected.hi[index]); + EXPECT_EQ(observed->lo[index], expected.lo[index]); + } + EXPECT_EQ(observed->dsp_control, expected.dsp_control); +} + +void ExpectMinidumpContextMIPS64(uint32_t expect_seed, + const MinidumpContextMIPS64* observed, + bool snapshot) { + MinidumpContextMIPS64 expected; + InitializeMinidumpContextMIPS64(&expected, expect_seed); + + EXPECT_EQ(observed->context_flags, expected.context_flags); + + for (size_t index = 0; index < arraysize(expected.regs); ++index) { + EXPECT_EQ(observed->regs[index], expected.regs[index]); + } + + EXPECT_EQ(observed->mdlo, expected.mdlo); + EXPECT_EQ(observed->mdhi, expected.mdhi); + EXPECT_EQ(observed->epc, expected.epc); + EXPECT_EQ(observed->badvaddr, expected.badvaddr); + EXPECT_EQ(observed->status, expected.status); + EXPECT_EQ(observed->cause, expected.cause); + + for (size_t index = 0; index < arraysize(expected.fpregs.dregs); ++index) { + EXPECT_EQ(observed->fpregs.dregs[index], expected.fpregs.dregs[index]); + } + EXPECT_EQ(observed->fpcsr, expected.fpcsr); + EXPECT_EQ(observed->fir, expected.fir); + + for (size_t index = 0; index < 3; ++index) { + EXPECT_EQ(observed->hi[index], expected.hi[index]); + EXPECT_EQ(observed->lo[index], expected.lo[index]); + } + EXPECT_EQ(observed->dsp_control, expected.dsp_control); +} + } // namespace test } // namespace crashpad diff --git a/minidump/test/minidump_context_test_util.h b/minidump/test/minidump_context_test_util.h index 14f57430..080e04a0 100644 --- a/minidump/test/minidump_context_test_util.h +++ b/minidump/test/minidump_context_test_util.h @@ -41,6 +41,12 @@ namespace test { void InitializeMinidumpContextX86(MinidumpContextX86* context, uint32_t seed); void InitializeMinidumpContextAMD64(MinidumpContextAMD64* context, uint32_t seed); +void InitializeMinidumpContextARM(MinidumpContextARM* context, uint32_t seed); +void InitializeMinidumpContextARM64(MinidumpContextARM64* context, + uint32_t seed); +void InitializeMinidumpContextMIPS(MinidumpContextMIPS* context, uint32_t seed); +void InitializeMinidumpContextMIPS64(MinidumpContextMIPS* context, + uint32_t seed); //! \} //! \brief Verifies, via gtest assertions, that a context structure contains @@ -67,6 +73,18 @@ void ExpectMinidumpContextX86( uint32_t expect_seed, const MinidumpContextX86* observed, bool snapshot); void ExpectMinidumpContextAMD64( uint32_t expect_seed, const MinidumpContextAMD64* observed, bool snapshot); +void ExpectMinidumpContextARM(uint32_t expect_seed, + const MinidumpContextARM* observed, + bool snapshot); +void ExpectMinidumpContextARM64(uint32_t expect_seed, + const MinidumpContextARM64* observed, + bool snapshot); +void ExpectMinidumpContextMIPS(uint32_t expect_seed, + const MinidumpContextMIPS* observed, + bool snapshot); +void ExpectMinidumpContextMIPS64(uint32_t expect_seed, + const MinidumpContextMIPS64* observed, + bool snapshot); //! \} } // namespace test diff --git a/minidump/test/minidump_file_writer_test_util.cc b/minidump/test/minidump_file_writer_test_util.cc index f372212b..aae357dc 100644 --- a/minidump/test/minidump_file_writer_test_util.cc +++ b/minidump/test/minidump_file_writer_test_util.cc @@ -52,7 +52,7 @@ void VerifyMinidumpHeader(const MINIDUMP_HEADER* header, ASSERT_EQ(header->StreamDirectoryRva, streams ? sizeof(MINIDUMP_HEADER) : 0u); EXPECT_EQ(header->CheckSum, 0u); EXPECT_EQ(header->TimeDateStamp, timestamp); - EXPECT_EQ(header->Flags, MiniDumpNormal); + EXPECT_EQ(static_cast(header->Flags), MiniDumpNormal); } } // namespace test diff --git a/minidump/test/minidump_memory_writer_test_util.cc b/minidump/test/minidump_memory_writer_test_util.cc index 03a0e18a..f198abeb 100644 --- a/minidump/test/minidump_memory_writer_test_util.cc +++ b/minidump/test/minidump_memory_writer_test_util.cc @@ -31,6 +31,10 @@ TestMinidumpMemoryWriter::TestMinidumpMemoryWriter(uint64_t base_address, TestMinidumpMemoryWriter::~TestMinidumpMemoryWriter() { } +void TestMinidumpMemoryWriter::SetShouldFailRead(bool should_fail) { + test_snapshot_.SetShouldFailRead(should_fail); +} + void ExpectMinidumpMemoryDescriptor( const MINIDUMP_MEMORY_DESCRIPTOR* expected, const MINIDUMP_MEMORY_DESCRIPTOR* observed) { diff --git a/minidump/test/minidump_memory_writer_test_util.h b/minidump/test/minidump_memory_writer_test_util.h index db198909..0890cd0e 100644 --- a/minidump/test/minidump_memory_writer_test_util.h +++ b/minidump/test/minidump_memory_writer_test_util.h @@ -40,6 +40,8 @@ class TestMinidumpMemoryWriter final : public SnapshotMinidumpMemoryWriter { TestMinidumpMemoryWriter(uint64_t base_address, size_t size, uint8_t value); ~TestMinidumpMemoryWriter(); + void SetShouldFailRead(bool should_fail); + private: TestMemorySnapshot test_snapshot_; diff --git a/minidump/test/minidump_writable_test_util.cc b/minidump/test/minidump_writable_test_util.cc index 1841243a..1a832bf3 100644 --- a/minidump/test/minidump_writable_test_util.cc +++ b/minidump/test/minidump_writable_test_util.cc @@ -223,6 +223,12 @@ struct MinidumpSimpleStringDictionaryListTraits { } }; +struct MinidumpAnnotationListObjectsTraits { + using ListType = MinidumpAnnotationList; + enum : size_t { kElementSize = sizeof(MinidumpAnnotation) }; + static size_t ElementCount(const ListType* list) { return list->count; } +}; + template const typename T::ListType* MinidumpListAtLocationDescriptor( const std::string& file_contents, @@ -313,6 +319,15 @@ MinidumpWritableAtLocationDescriptor( MinidumpSimpleStringDictionaryListTraits>(file_contents, location); } +template <> +const MinidumpAnnotationList* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location) { + return MinidumpListAtLocationDescriptor( + file_contents, location); +} + namespace { template diff --git a/minidump/test/minidump_writable_test_util.h b/minidump/test/minidump_writable_test_util.h index dd8f5531..5b176d2b 100644 --- a/minidump/test/minidump_writable_test_util.h +++ b/minidump/test/minidump_writable_test_util.h @@ -96,6 +96,7 @@ MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_MEMORY_INFO_LIST); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpModuleCrashpadInfoList); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpRVAList); MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpSimpleStringDictionary); +MINIDUMP_ALLOW_OVERSIZED_DATA(MinidumpAnnotationList); // These types have final fields carrying variable-sized data (typically string // data). @@ -141,10 +142,10 @@ const T* TMinidumpWritableAtLocationDescriptor( //! - With a MINIDUMP_HEADER template parameter, a template specialization //! ensures that the structure’s magic number and version fields are correct. //! - With a MINIDUMP_MEMORY_LIST, MINIDUMP_THREAD_LIST, MINIDUMP_MODULE_LIST, -//! MINIDUMP_MEMORY_INFO_LIST, or MinidumpSimpleStringDictionary template -//! parameter, template specializations ensure that the size given by \a -//! location matches the size expected of a stream containing the number of -//! elements it claims to have. +//! MINIDUMP_MEMORY_INFO_LIST, MinidumpSimpleStringDictionary, or +//! MinidumpAnnotationList template parameter, template specializations +//! ensure that the size given by \a location matches the size expected of a +//! stream containing the number of elements it claims to have. //! - With an IMAGE_DEBUG_MISC, CodeViewRecordPDB20, or CodeViewRecordPDB70 //! template parameter, template specializations ensure that the structure //! has the expected format including any magic number and the `NUL`- @@ -230,6 +231,12 @@ MinidumpWritableAtLocationDescriptor( const std::string& file_contents, const MINIDUMP_LOCATION_DESCRIPTOR& location); +template <> +const MinidumpAnnotationList* +MinidumpWritableAtLocationDescriptor( + const std::string& file_contents, + const MINIDUMP_LOCATION_DESCRIPTOR& location); + //! \brief Returns a typed minidump object located within a minidump file’s //! contents, where the offset of the object is known. //! diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn new file mode 100644 index 00000000..134df8b1 --- /dev/null +++ b/snapshot/BUILD.gn @@ -0,0 +1,599 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") +import("../build/crashpad_fuzzer_test.gni") + +if (crashpad_is_in_chromium) { + import("//build/config/compiler/compiler.gni") +} + +static_library("snapshot") { + sources = [ + "annotation_snapshot.cc", + "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", + "handle_snapshot.cc", + "handle_snapshot.h", + "memory_snapshot.cc", + "memory_snapshot.h", + "memory_snapshot_generic.h", + "minidump/minidump_annotation_reader.cc", + "minidump/minidump_annotation_reader.h", + "minidump/minidump_simple_string_dictionary_reader.cc", + "minidump/minidump_simple_string_dictionary_reader.h", + "minidump/minidump_string_list_reader.cc", + "minidump/minidump_string_list_reader.h", + "minidump/minidump_string_reader.cc", + "minidump/minidump_string_reader.h", + "minidump/module_snapshot_minidump.cc", + "minidump/module_snapshot_minidump.h", + "minidump/process_snapshot_minidump.cc", + "minidump/process_snapshot_minidump.h", + "module_snapshot.h", + "process_snapshot.h", + "snapshot_constants.h", + "system_snapshot.h", + "thread_snapshot.h", + "unloaded_module_snapshot.cc", + "unloaded_module_snapshot.h", + ] + + if (crashpad_is_posix || crashpad_is_fuchsia) { + sources += [ + "posix/timezone.cc", + "posix/timezone.h", + ] + } + + if (crashpad_is_mac) { + sources += [ + "mac/cpu_context_mac.cc", + "mac/cpu_context_mac.h", + "mac/exception_snapshot_mac.cc", + "mac/exception_snapshot_mac.h", + "mac/mach_o_image_annotations_reader.cc", + "mac/mach_o_image_annotations_reader.h", + "mac/mach_o_image_reader.cc", + "mac/mach_o_image_reader.h", + "mac/mach_o_image_segment_reader.cc", + "mac/mach_o_image_segment_reader.h", + "mac/mach_o_image_symbol_table_reader.cc", + "mac/mach_o_image_symbol_table_reader.h", + "mac/module_snapshot_mac.cc", + "mac/module_snapshot_mac.h", + "mac/process_reader_mac.cc", + "mac/process_reader_mac.h", + "mac/process_snapshot_mac.cc", + "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", + "mac/thread_snapshot_mac.cc", + "mac/thread_snapshot_mac.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android) { + set_sources_assignment_filter([]) + sources += [ + "linux/cpu_context_linux.cc", + "linux/cpu_context_linux.h", + "linux/debug_rendezvous.cc", + "linux/debug_rendezvous.h", + "linux/exception_snapshot_linux.cc", + "linux/exception_snapshot_linux.h", + "linux/process_reader_linux.cc", + "linux/process_reader_linux.h", + "linux/process_snapshot_linux.cc", + "linux/process_snapshot_linux.h", + "linux/signal_context.h", + "linux/system_snapshot_linux.cc", + "linux/system_snapshot_linux.h", + "linux/thread_snapshot_linux.cc", + "linux/thread_snapshot_linux.h", + "sanitized/memory_snapshot_sanitized.cc", + "sanitized/memory_snapshot_sanitized.h", + "sanitized/module_snapshot_sanitized.cc", + "sanitized/module_snapshot_sanitized.h", + "sanitized/process_snapshot_sanitized.cc", + "sanitized/process_snapshot_sanitized.h", + "sanitized/sanitization_information.cc", + "sanitized/sanitization_information.h", + "sanitized/thread_snapshot_sanitized.cc", + "sanitized/thread_snapshot_sanitized.h", + ] + } + + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ + "crashpad_types/crashpad_info_reader.cc", + "crashpad_types/crashpad_info_reader.h", + "crashpad_types/image_annotation_reader.cc", + "crashpad_types/image_annotation_reader.h", + "elf/elf_dynamic_array_reader.cc", + "elf/elf_dynamic_array_reader.h", + "elf/elf_image_reader.cc", + "elf/elf_image_reader.h", + "elf/elf_symbol_table_reader.cc", + "elf/elf_symbol_table_reader.h", + "elf/module_snapshot_elf.cc", + "elf/module_snapshot_elf.h", + ] + } + + if (crashpad_is_win) { + sources += [ + "win/capture_memory_delegate_win.cc", + "win/capture_memory_delegate_win.h", + "win/cpu_context_win.cc", + "win/cpu_context_win.h", + "win/exception_snapshot_win.cc", + "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", + "win/pe_image_annotations_reader.h", + "win/pe_image_reader.cc", + "win/pe_image_reader.h", + "win/pe_image_resource_reader.cc", + "win/pe_image_resource_reader.h", + "win/process_reader_win.cc", + "win/process_reader_win.h", + "win/process_snapshot_win.cc", + "win/process_snapshot_win.h", + "win/process_subrange_reader.cc", + "win/process_subrange_reader.h", + "win/system_snapshot_win.cc", + "win/system_snapshot_win.h", + "win/thread_snapshot_win.cc", + "win/thread_snapshot_win.h", + ] + } + + if (crashpad_is_fuchsia) { + sources += [ + "fuchsia/cpu_context_fuchsia.cc", + "fuchsia/cpu_context_fuchsia.h", + "fuchsia/exception_snapshot_fuchsia.cc", + "fuchsia/exception_snapshot_fuchsia.h", + "fuchsia/memory_map_fuchsia.cc", + "fuchsia/memory_map_fuchsia.h", + "fuchsia/process_reader_fuchsia.cc", + "fuchsia/process_reader_fuchsia.h", + "fuchsia/process_snapshot_fuchsia.cc", + "fuchsia/process_snapshot_fuchsia.h", + "fuchsia/system_snapshot_fuchsia.cc", + "fuchsia/system_snapshot_fuchsia.h", + "fuchsia/thread_snapshot_fuchsia.cc", + "fuchsia/thread_snapshot_fuchsia.h", + ] + } + + if (target_cpu == "x86" || target_cpu == "x64") { + sources += [ + "x86/cpuid_reader.cc", + "x86/cpuid_reader.h", + ] + } + + public_configs = [ "..:crashpad_config" ] + + deps = [ + "../client", + "../compat", + "../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" ] + + deps = [ + ":snapshot", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } +} 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") { + testonly = true + + sources = [ + "test/test_cpu_context.cc", + "test/test_cpu_context.h", + "test/test_exception_snapshot.cc", + "test/test_exception_snapshot.h", + "test/test_memory_map_region_snapshot.cc", + "test/test_memory_map_region_snapshot.h", + "test/test_memory_snapshot.cc", + "test/test_memory_snapshot.h", + "test/test_module_snapshot.cc", + "test/test_module_snapshot.h", + "test/test_process_snapshot.cc", + "test/test_process_snapshot.h", + "test/test_system_snapshot.cc", + "test/test_system_snapshot.h", + "test/test_thread_snapshot.cc", + "test/test_thread_snapshot.h", + ] + + public_configs = [ "..:crashpad_config" ] + + public_deps = [ + ":snapshot", + ] + + deps = [ + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + } +} + +config("snapshot_test_link") { + visibility = [ ":*" ] + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + # 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", + ] + ldflags = [ "-Wl,--dynamic-list," + rebase_path(inputs[0], root_build_dir) ] + } +} + +source_set("snapshot_test") { + testonly = true + + sources = [ + "cpu_context_test.cc", + "memory_snapshot_test.cc", + "minidump/process_snapshot_minidump_test.cc", + ] + + if (crashpad_is_mac) { + sources += [ + "mac/cpu_context_mac_test.cc", + "mac/mach_o_image_annotations_reader_test.cc", + "mac/mach_o_image_reader_test.cc", + "mac/mach_o_image_segment_reader_test.cc", + "mac/process_reader_mac_test.cc", + "mac/process_types_test.cc", + "mac/system_snapshot_mac_test.cc", + ] + } + + if (crashpad_is_linux || crashpad_is_android) { + sources += [ + "linux/debug_rendezvous_test.cc", + "linux/exception_snapshot_linux_test.cc", + "linux/process_reader_linux_test.cc", + "linux/system_snapshot_linux_test.cc", + "sanitized/process_snapshot_sanitized_test.cc", + "sanitized/sanitization_information_test.cc", + ] + } else { + sources += [ "crashpad_info_client_options_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/pe_image_reader_test.cc", + "win/process_reader_win_test.cc", + "win/process_snapshot_win_test.cc", + "win/system_snapshot_win_test.cc", + ] + } else if (!crashpad_is_fuchsia) { + # Timezones are currently non-functional on Fuchsia: + # https://fuchsia.googlesource.com/zircon/+/master/third_party/ulib/musl/src/time/__tz.c#9 + # https://crashpad.chromium.org/bug/196. Relevant upstream bugs are ZX-337 + # and ZX-1731. + sources += [ "posix/timezone_test.cc" ] + } + + if (crashpad_is_fuchsia) { + sources += [ "fuchsia/process_reader_fuchsia_test.cc" ] + } + + # public_configs isn’t quite right. snapshot_test_link sets ldflags, and + # what’s really needed is a way to push ldflags to dependent targets that + # produce linker output. Luckily in this case, all dependents do produce + # linker output. https://crbug.com/796183. + public_configs = [ ":snapshot_test_link" ] + + deps = [ + ":snapshot_api", + ":test_support", + "../client", + "../compat", + "../test", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + data_deps = [ + ":crashpad_snapshot_test_module", + ":crashpad_snapshot_test_module_large", + ":crashpad_snapshot_test_module_small", + ] + + if (crashpad_is_mac) { + libs = [ "OpenCL.framework" ] + + data_deps += [ + ":crashpad_snapshot_test_module_crashy_initializer", + ":crashpad_snapshot_test_no_op", + ] + } + + if (crashpad_is_linux || crashpad_is_android) { + libs = [ "dl" ] + } + + if ((crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) && + target_cpu != "mipsel" && target_cpu != "mips64el") { + data_deps += [ ":crashpad_snapshot_test_both_dt_hash_styles" ] + } + + if (crashpad_is_win) { + cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union + + data_deps += [ + ":crashpad_snapshot_test_annotations", + ":crashpad_snapshot_test_crashing_child", + ":crashpad_snapshot_test_dump_without_crashing", + ":crashpad_snapshot_test_extra_memory_ranges", + ":crashpad_snapshot_test_image_reader", + ":crashpad_snapshot_test_image_reader_module", + ] + } +} + +crashpad_loadable_module("crashpad_snapshot_test_module") { + testonly = true + sources = [ + "crashpad_info_client_options_test_module.cc", + ] + deps = [ + "../client", + "../third_party/mini_chromium:base", + ] +} + +crashpad_loadable_module("crashpad_snapshot_test_module_large") { + testonly = true + sources = [ + "crashpad_info_size_test_module.cc", + ] + + deps = [] + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ "crashpad_info_size_test_note.S" ] + deps += [ "../util" ] + } + + defines = [ "CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE" ] + deps += [ "../third_party/mini_chromium:base" ] +} + +crashpad_loadable_module("crashpad_snapshot_test_module_small") { + testonly = true + sources = [ + "crashpad_info_size_test_module.cc", + ] + + deps = [] + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { + sources += [ "crashpad_info_size_test_note.S" ] + deps += [ "../util" ] + } + + defines = [ "CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL" ] + deps += [ "../third_party/mini_chromium:base" ] +} + +if ((crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) && + target_cpu != "mipsel" && target_cpu != "mips64el") { + crashpad_loadable_module("crashpad_snapshot_test_both_dt_hash_styles") { + testonly = true + sources = [ + "hash_types_test.cc", + ] + + # This makes `ld` emit both .hash and .gnu.hash sections. + ldflags = [ "-Wl,--hash-style=both" ] + } +} + +if (crashpad_is_mac) { + crashpad_loadable_module("crashpad_snapshot_test_module_crashy_initializer") { + testonly = true + sources = [ + "mac/mach_o_image_annotations_reader_test_module_crashy_initializer.cc", + ] + } + + crashpad_executable("crashpad_snapshot_test_no_op") { + testonly = true + 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", + ] + deps = [ + "../client", + "../compat", + "../third_party/mini_chromium:base", + ] + } + + crashpad_executable("crashpad_snapshot_test_crashing_child") { + testonly = true + sources = [ + "win/crashpad_snapshot_test_crashing_child.cc", + ] + deps = [ + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } + + crashpad_executable("crashpad_snapshot_test_dump_without_crashing") { + testonly = true + sources = [ + "win/crashpad_snapshot_test_dump_without_crashing.cc", + ] + deps = [ + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } + + crashpad_executable("crashpad_snapshot_test_extra_memory_ranges") { + testonly = true + sources = [ + "win/crashpad_snapshot_test_extra_memory_ranges.cc", + ] + deps = [ + "../client", + "../compat", + "../third_party/mini_chromium:base", + ] + } + + crashpad_executable("crashpad_snapshot_test_image_reader") { + testonly = true + sources = [ + "win/crashpad_snapshot_test_image_reader.cc", + ] + deps = [ + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + if (crashpad_is_in_chromium) { + if (symbol_level == 0) { + # The tests that use this executable rely on at least minimal debug + # info. + remove_configs = [ "//build/config/compiler:default_symbols" ] + configs = [ "//build/config/compiler:minimal_symbols" ] + } + } + } + + crashpad_loadable_module("crashpad_snapshot_test_image_reader_module") { + testonly = true + sources = [ + "win/crashpad_snapshot_test_image_reader_module.cc", + ] + deps = [ + "../client", + "../third_party/mini_chromium:base", + ] + if (crashpad_is_in_chromium) { + if (symbol_level == 0) { + # The tests that use this module rely on at least minimal debug info. + remove_configs = [ "//build/config/compiler:default_symbols" ] + configs = [ "//build/config/compiler:minimal_symbols" ] + } + } + } +} diff --git a/snapshot/capture_memory.cc b/snapshot/capture_memory.cc index c860285a..98f400e8 100644 --- a/snapshot/capture_memory.cc +++ b/snapshot/capture_memory.cc @@ -94,8 +94,24 @@ void CaptureMemory::PointedToByContext(const CPUContext& context, MaybeCaptureMemoryAround(delegate, context.x86->ebp); MaybeCaptureMemoryAround(delegate, context.x86->eip); } +#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) { + MaybeCaptureMemoryAround(delegate, context.arm64->regs[i]); + } + } else { + MaybeCaptureMemoryAround(delegate, context.arm->pc); + for (size_t i = 0; i < arraysize(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) { + MaybeCaptureMemoryAround(delegate, context.mipsel->regs[i]); + } #else -#error non-x86 +#error Port. #endif } diff --git a/snapshot/cpu_architecture.h b/snapshot/cpu_architecture.h index 208e98f2..811a7209 100644 --- a/snapshot/cpu_architecture.h +++ b/snapshot/cpu_architecture.h @@ -32,6 +32,18 @@ enum CPUArchitecture { //! \brief x86_64. kCPUArchitectureX86_64, + + //! \brief 32-bit ARM. + kCPUArchitectureARM, + + //! \brief 64-bit ARM. + kCPUArchitectureARM64, + + //! \brief 32-bit MIPSEL. + kCPUArchitectureMIPSEL, + + //! \brief 64-bit MIPSEL. + kCPUArchitectureMIPS64EL }; } // namespace crashpad diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc index 5c964480..4d7c1e5a 100644 --- a/snapshot/cpu_context.cc +++ b/snapshot/cpu_context.cc @@ -164,10 +164,46 @@ uint64_t CPUContext::InstructionPointer() const { return x86->eip; case kCPUArchitectureX86_64: return x86_64->rip; + case kCPUArchitectureARM: + return arm->pc; + case kCPUArchitectureARM64: + return arm64->pc; default: NOTREACHED(); return ~0ull; } } +uint64_t CPUContext::StackPointer() const { + switch (architecture) { + case kCPUArchitectureX86: + return x86->esp; + case kCPUArchitectureX86_64: + return x86_64->rsp; + case kCPUArchitectureARM: + return arm->sp; + case kCPUArchitectureARM64: + return arm64->sp; + default: + NOTREACHED(); + return ~0ull; + } +} + +bool CPUContext::Is64Bit() const { + switch (architecture) { + case kCPUArchitectureX86_64: + case kCPUArchitectureARM64: + case kCPUArchitectureMIPS64EL: + return true; + case kCPUArchitectureX86: + case kCPUArchitectureARM: + case kCPUArchitectureMIPSEL: + return false; + default: + NOTREACHED(); + return false; + } +} + } // namespace crashpad diff --git a/snapshot/cpu_context.h b/snapshot/cpu_context.h index ba3ac181..4dde9436 100644 --- a/snapshot/cpu_context.h +++ b/snapshot/cpu_context.h @@ -18,6 +18,7 @@ #include #include "snapshot/cpu_architecture.h" +#include "util/numeric/int128.h" namespace crashpad { @@ -258,6 +259,99 @@ struct CPUContextX86_64 { uint64_t dr7; }; +//! \brief A context structure carrying ARM CPU state. +struct CPUContextARM { + uint32_t regs[11]; + uint32_t fp; // r11 + uint32_t ip; // r12 + uint32_t sp; // r13 + uint32_t lr; // r14 + uint32_t pc; // r15 + uint32_t cpsr; + + struct { + struct fp_reg { + uint32_t sign1 : 1; + uint32_t unused : 15; + uint32_t sign2 : 1; + uint32_t exponent : 14; + uint32_t j : 1; + uint32_t mantissa1 : 31; + uint32_t mantisss0 : 32; + } fpregs[8]; + uint32_t fpsr : 32; + uint32_t fpcr : 32; + uint8_t type[8]; + uint32_t init_flag; + } fpa_regs; + + struct { + uint64_t vfp[32]; + uint32_t fpscr; + } vfp_regs; + + bool have_fpa_regs; + bool have_vfp_regs; +}; + +//! \brief A context structure carrying ARM64 CPU state. +struct CPUContextARM64 { + uint64_t regs[31]; + uint64_t sp; + uint64_t pc; + uint64_t pstate; + + uint128_struct fpsimd[32]; + uint32_t fpsr; + uint32_t fpcr; +}; + +//! \brief A context structure carrying MIPS CPU state. +struct CPUContextMIPS { + uint64_t regs[32]; + uint32_t mdlo; + uint32_t mdhi; + uint32_t cp0_epc; + uint32_t cp0_badvaddr; + uint32_t cp0_status; + uint32_t cp0_cause; + uint32_t hi[3]; + uint32_t lo[3]; + uint32_t dsp_control; + union { + double dregs[32]; + struct { + float _fp_fregs; + uint32_t _fp_pad; + } fregs[32]; + } fpregs; + uint32_t fpcsr; + uint32_t fir; +}; + +//! \brief A context structure carrying MIPS64 CPU state. +struct CPUContextMIPS64 { + uint64_t regs[32]; + uint64_t mdlo; + uint64_t mdhi; + uint64_t cp0_epc; + uint64_t cp0_badvaddr; + uint64_t cp0_status; + uint64_t cp0_cause; + uint64_t hi[3]; + uint64_t lo[3]; + uint64_t dsp_control; + union { + double dregs[32]; + struct { + float _fp_fregs; + uint32_t _fp_pad; + } fregs[32]; + } fpregs; + uint64_t fpcsr; + uint64_t fir; +}; + //! \brief A context structure capable of carrying the context of any supported //! CPU architecture. struct CPUContext { @@ -268,12 +362,26 @@ struct CPUContext { //! context structure. uint64_t InstructionPointer() const; + //! \brief Returns the stack pointer value from the context structure. + //! + //! This is a CPU architecture-independent method that is capable of + //! recovering the stack pointer from any supported CPU architecture’s + //! context structure. + uint64_t StackPointer() const; + + //! \brief Returns `true` if this context is for a 64-bit architecture. + bool Is64Bit() const; + //! \brief The CPU architecture of a context structure. This field controls //! the expression of the union. CPUArchitecture architecture; union { CPUContextX86* x86; CPUContextX86_64* x86_64; + CPUContextARM* arm; + CPUContextARM64* arm64; + CPUContextMIPS* mipsel; + CPUContextMIPS64* mips64; }; }; diff --git a/snapshot/crashpad_info_client_options.h b/snapshot/crashpad_info_client_options.h index 17469b77..89c947a6 100644 --- a/snapshot/crashpad_info_client_options.h +++ b/snapshot/crashpad_info_client_options.h @@ -23,14 +23,14 @@ namespace crashpad { //! \brief Options represented in a client’s CrashpadInfo structure. //! -//! The CrashpadInfo structure is not suitable to expose client options -//! in a generic way at the snapshot level. This structure duplicates -//! option-related fields from the client structure for general use within the -//! snapshot layer and by users of this layer. +//! The CrashpadInfo structure is not suitable to expose client options in a +//! generic way at the snapshot level. This structure duplicates option-related +//! fields from the client structure for general use within the snapshot layer +//! and by users of this layer. //! //! For objects of this type corresponding to a module, option values are taken //! from the module’s CrashpadInfo structure directly. If the module has no such -//! such structure, option values appear unset. +//! structure, option values appear unset. //! //! For objects of this type corresponding to an entire process, option values //! are taken from the CrashpadInfo structures of modules within the process. diff --git a/snapshot/crashpad_info_client_options_test.cc b/snapshot/crashpad_info_client_options_test.cc index 45f3f17b..1fe38acf 100644 --- a/snapshot/crashpad_info_client_options_test.cc +++ b/snapshot/crashpad_info_client_options_test.cc @@ -14,6 +14,7 @@ #include "snapshot/crashpad_info_client_options.h" +#include "base/auto_reset.h" #include "base/files/file_path.h" #include "base/macros.h" #include "base/strings/utf_string_conversions.h" @@ -30,6 +31,9 @@ #elif defined(OS_WIN) #include #include "snapshot/win/process_snapshot_win.h" +#elif defined(OS_FUCHSIA) +#include +#include "snapshot/fuchsia/process_snapshot_fuchsia.h" #endif namespace crashpad { @@ -80,6 +84,9 @@ CrashpadInfoClientOptions SelfProcessSnapshotAndGetCrashpadOptions() { ProcessSnapshotWin process_snapshot; EXPECT_TRUE(process_snapshot.Initialize( GetCurrentProcess(), ProcessSuspensionState::kRunning, 0, 0)); +#elif defined(OS_FUCHSIA) + ProcessSnapshotFuchsia process_snapshot; + EXPECT_TRUE(process_snapshot.Initialize(zx_process_self())); #else #error Port. #endif // OS_MACOSX @@ -145,7 +152,7 @@ TEST(CrashpadInfoClientOptions, TwoModules) { TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), FILE_PATH_LITERAL("module"), TestPaths::FileType::kLoadableModule); -#if defined(OS_MACOSX) +#if defined(OS_POSIX) ScopedModuleHandle module( dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": " @@ -229,6 +236,101 @@ TEST(CrashpadInfoClientOptions, TwoModules) { } } +class CrashpadInfoSizes_ClientOptions + : public testing::TestWithParam {}; + +TEST_P(CrashpadInfoSizes_ClientOptions, DifferentlySizedStruct) { + base::FilePath::StringType artifact(FILE_PATH_LITERAL("module_")); + artifact += GetParam(); + + // Open the module, which has a CrashpadInfo-like structure that’s smaller or + // larger than the current version’s CrashpadInfo structure defined in the + // client library. + base::FilePath module_path = + TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), + artifact, + TestPaths::FileType::kLoadableModule); +#if defined(OS_POSIX) + ScopedModuleHandle module( + dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); + ASSERT_TRUE(module.valid()) + << "dlopen " << module_path.value() << ": " << dlerror(); +#elif defined(OS_WIN) + ScopedModuleHandle module(LoadLibrary(module_path.value().c_str())); + ASSERT_TRUE(module.valid()) + << "LoadLibrary " << base::UTF16ToUTF8(module_path.value()) << ": " + << ErrorMessage(); +#else +#error Port. +#endif // OS_MACOSX + + // Get the function pointer from the module. + CrashpadInfo* (*TestModule_GetCrashpadInfo)() = + module.LookUpSymbol("TestModule_GetCrashpadInfo"); + ASSERT_TRUE(TestModule_GetCrashpadInfo); + + auto options = SelfProcessSnapshotAndGetCrashpadOptions(); + + // Make sure that the initial state has all values unset. + EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); + EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); + EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); + + // Get the remote CrashpadInfo structure. + CrashpadInfo* remote_crashpad_info = TestModule_GetCrashpadInfo(); + ASSERT_TRUE(remote_crashpad_info); + + { + ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); + + // Make sure that a change in the remote structure can be read back out, + // even though it’s a different size. + remote_crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); + remote_crashpad_info->set_system_crash_reporter_forwarding( + TriState::kDisabled); + + options = SelfProcessSnapshotAndGetCrashpadOptions(); + EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); + EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kDisabled); + EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); + } + + { + ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); + + // Make sure that the portion of the remote structure lying beyond its + // declared size reads as zero. + + // 4 = offsetof(CrashpadInfo, size_), but it’s private. + uint32_t* size = reinterpret_cast( + reinterpret_cast(remote_crashpad_info) + 4); + + // 21 = offsetof(CrashpadInfo, system_crash_reporter_forwarding_, but it’s + // private. + base::AutoReset reset_size(size, 21); + + // system_crash_reporter_forwarding_ is now beyond the struct’s declared + // size. Storage has actually been allocated for it, so it’s safe to set + // here. + remote_crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); + remote_crashpad_info->set_system_crash_reporter_forwarding( + TriState::kDisabled); + + // Since system_crash_reporter_forwarding_ is beyond the struct’s declared + // size, it should read as 0 (TriState::kUnset), even though it was set to + // a different value above. + options = SelfProcessSnapshotAndGetCrashpadOptions(); + EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); + EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); + EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); + } +} + +INSTANTIATE_TEST_CASE_P(CrashpadInfoSizes_ClientOptions, + CrashpadInfoSizes_ClientOptions, + testing::Values(FILE_PATH_LITERAL("small"), + FILE_PATH_LITERAL("large"))); + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/crashpad_info_size_test_module.cc b/snapshot/crashpad_info_size_test_module.cc new file mode 100644 index 00000000..53f4a3ff --- /dev/null +++ b/snapshot/crashpad_info_size_test_module.cc @@ -0,0 +1,128 @@ +// 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 + +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#include +#elif defined(OS_WIN) +#include +#endif // OS_MACOSX + +namespace crashpad { + +#if defined(CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL) == \ + defined(CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE) +#error Define exactly one of these macros +#endif + +// This module contains a CrashpadInfo structure that’s either smaller or larger +// than the one defined in the client library, depending on which macro is +// defined when it’s compiled. This tests the snapshot layer’s ability to read +// smaller structures (as might be found in modules built with older versions of +// the client library than a handler’s snapshot library) and larger ones (the +// “vice-versa” situation). This needs to be done without taking a dependency on +// the client library, which would bring with it a correct copy of the +// CrashpadInfo structure. As a result, all types have been simplified to +// fixed-size integers and void* pointers. +struct TestCrashpadInfo { + uint32_t signature_; + uint32_t size_; + uint32_t version_; + uint32_t indirectly_referenced_memory_cap_; + uint32_t padding_0_; + uint8_t crashpad_handler_behavior_; + uint8_t system_crash_reporter_forwarding_; + uint8_t gather_indirectly_referenced_memory_; + uint8_t padding_1_; + void* extra_memory_ranges_; + void* simple_annotations_; +#if !defined(CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL) + void* user_data_minidump_stream_head_; + void* annotations_list_; +#endif // CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL +#if defined(CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE) + uint8_t trailer_[64 * 1024]; +#endif // CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE +}; + +// Put it in the correct section. +// +// The initializer also duplicates constants from the client library, sufficient +// to get this test version to be interpreted as a genuine CrashpadInfo +// structure. The size is set to the actual size of this structure (that’s kind +// of the point of this test). +#if defined(OS_POSIX) +__attribute__(( +#if defined(OS_MACOSX) + section(SEG_DATA ",crashpad_info"), +#endif +#if defined(ADDRESS_SANITIZER) + aligned(64), +#endif // defined(ADDRESS_SANITIZER) + visibility("hidden"), + used)) +#elif defined(OS_WIN) +#pragma section("CPADinfo", read, write) +__declspec(allocate("CPADinfo")) +#else // !defined(OS_POSIX) && !defined(OS_WIN) +#error Port +#endif // !defined(OS_POSIX) && !defined(OS_WIN) +TestCrashpadInfo g_test_crashpad_info = {'CPad', + sizeof(TestCrashpadInfo), + 1, + 0, + 0, + 0, + 0, + 0, + 0, + nullptr, + nullptr, +#if !defined(CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL) + nullptr, + nullptr, +#endif // CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL +#if defined(CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE) + {} +#endif // CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE +}; + +} // namespace crashpad + +extern "C" { + +#if defined(OS_POSIX) +__attribute__((visibility("default"))) +#elif defined(OS_WIN) +__declspec(dllexport) +#else +#error Port +#endif // OS_POSIX +crashpad::TestCrashpadInfo* TestModule_GetCrashpadInfo() { + // Note that there's no need to do the back-reference here to the note on + // POSIX like CrashpadInfo::GetCrashpadInfo() because the note .S file is + // directly included into this test binary. + return &crashpad::g_test_crashpad_info; +} + +} // extern "C" + +#if defined(OS_WIN) +BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD reason, LPVOID reserved) { + return TRUE; +} +#endif // OS_WIN diff --git a/snapshot/crashpad_info_size_test_note.S b/snapshot/crashpad_info_size_test_note.S new file mode 100644 index 00000000..96b996db --- /dev/null +++ b/snapshot/crashpad_info_size_test_note.S @@ -0,0 +1,55 @@ +// 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 note section is used on ELF platforms to give ElfImageReader a method +// of finding the instance of CrashpadInfo g_crashpad_info without requiring +// that symbol to be in the dynamic symbol table. + +#include "util/misc/elf_note_types.h" + +// namespace crashpad { +// CrashpadInfo g_test_crashpad_info; +// } // namespace crashpad +#define TEST_CRASHPAD_INFO_SYMBOL _ZN8crashpad20g_test_crashpad_infoE + +#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 + .balign NOTE_ALIGN + .type info_size_test_note, %object +info_size_test_note: + .long name_end - name // namesz + .long desc_end - desc // descsz + .long CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO // type +name: + .asciz CRASHPAD_ELF_NOTE_NAME +name_end: + .balign NOTE_ALIGN +desc: +#if defined(__LP64__) + .quad TEST_CRASHPAD_INFO_SYMBOL +#else +#if defined(__LITTLE_ENDIAN__) + .long TEST_CRASHPAD_INFO_SYMBOL + .long 0 +#else + .long 0 + .long TEST_CRASHPAD_INFO_SYMBOL +#endif // __LITTLE_ENDIAN__ +#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 new file mode 100644 index 00000000..dfc438fc --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader.cc @@ -0,0 +1,191 @@ +// 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 "snapshot/crashpad_types/crashpad_info_reader.h" + +#include + +#include "build/build_config.h" +#include "client/crashpad_info.h" +#include "util/linux/traits.h" +#include "util/misc/as_underlying_type.h" + +namespace crashpad { + +namespace { + +void UnsetIfNotValidTriState(TriState* value) { + switch (AsUnderlyingType(*value)) { + case AsUnderlyingType(TriState::kUnset): + case AsUnderlyingType(TriState::kEnabled): + case AsUnderlyingType(TriState::kDisabled): + return; + } + LOG(WARNING) << "Unsetting invalid TriState " << AsUnderlyingType(*value); + *value = TriState::kUnset; +} + +} // namespace + +class CrashpadInfoReader::InfoContainer { + public: + virtual ~InfoContainer() = default; + + virtual bool Read(const ProcessMemoryRange* memory, VMAddress address) = 0; + + protected: + InfoContainer() = default; +}; + +template +class CrashpadInfoReader::InfoContainerSpecific : public InfoContainer { + public: + InfoContainerSpecific() : InfoContainer() {} + ~InfoContainerSpecific() override = default; + + bool Read(const ProcessMemoryRange* memory, VMAddress address) override { + if (!memory->Read(address, + offsetof(decltype(info), size) + sizeof(info.size), + &info)) { + return false; + } + + if (info.signature != CrashpadInfo::kSignature) { + LOG(ERROR) << "invalid signature 0x" << std::hex << info.signature; + return false; + } + + if (!memory->Read(address, + std::min(VMSize{info.size}, VMSize{sizeof(info)}), + &info)) { + return false; + } + + if (info.size > sizeof(info)) { + LOG(INFO) << "large crashpad info size " << info.size; + } + + if (info.version != 1) { + LOG(ERROR) << "unexpected version " << info.version; + return false; + } + + if (sizeof(info) > info.size) { + memset(reinterpret_cast(&info) + info.size, + 0, + sizeof(info) - info.size); + } + + UnsetIfNotValidTriState(&info.crashpad_handler_behavior); + UnsetIfNotValidTriState(&info.system_crash_reporter_forwarding); + UnsetIfNotValidTriState(&info.gather_indirectly_referenced_memory); + + return true; + } + + struct { + uint32_t signature; + uint32_t size; + uint32_t version; + uint32_t indirectly_referenced_memory_cap; + uint32_t padding_0; + TriState crashpad_handler_behavior; + TriState system_crash_reporter_forwarding; + TriState gather_indirectly_referenced_memory; + uint8_t padding_1; + typename Traits::Address extra_memory_ranges; + typename Traits::Address simple_annotations; + typename Traits::Address user_data_minidump_stream_head; + typename Traits::Address annotations_list; + } info; + +#if defined(ARCH_CPU_64_BITS) +#define NATIVE_TRAITS Traits64 +#else +#define NATIVE_TRAITS Traits32 +#endif + static_assert(!std::is_same::value || + sizeof(info) == sizeof(CrashpadInfo), + "CrashpadInfo size mismtach"); +#undef NATIVE_TRAITS +}; + +CrashpadInfoReader::CrashpadInfoReader() + : container_(), is_64_bit_(false), initialized_() {} + +CrashpadInfoReader::~CrashpadInfoReader() = default; + +bool CrashpadInfoReader::Initialize(const ProcessMemoryRange* memory, + VMAddress address) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + is_64_bit_ = memory->Is64Bit(); + + std::unique_ptr new_container; + if (is_64_bit_) { + new_container = std::make_unique>(); + } else { + new_container = std::make_unique>(); + } + + if (!new_container->Read(memory, address)) { + return false; + } + container_ = std::move(new_container); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +#define GET_MEMBER(name) \ + (is_64_bit_ \ + ? reinterpret_cast*>(container_.get()) \ + ->info.name \ + : reinterpret_cast*>(container_.get()) \ + ->info.name) + +#define DEFINE_GETTER(type, method, member) \ + type CrashpadInfoReader::method() { \ + INITIALIZATION_STATE_DCHECK_VALID(initialized_); \ + return GET_MEMBER(member); \ + } + +DEFINE_GETTER(TriState, CrashpadHandlerBehavior, crashpad_handler_behavior); + +DEFINE_GETTER(TriState, + SystemCrashReporterForwarding, + system_crash_reporter_forwarding); + +DEFINE_GETTER(TriState, + GatherIndirectlyReferencedMemory, + gather_indirectly_referenced_memory); + +DEFINE_GETTER(uint32_t, + IndirectlyReferencedMemoryCap, + indirectly_referenced_memory_cap); + +DEFINE_GETTER(VMAddress, ExtraMemoryRanges, extra_memory_ranges); + +DEFINE_GETTER(VMAddress, SimpleAnnotations, simple_annotations); + +DEFINE_GETTER(VMAddress, AnnotationsList, annotations_list); + +DEFINE_GETTER(VMAddress, + UserDataMinidumpStreamHead, + user_data_minidump_stream_head); + +#undef DEFINE_GETTER +#undef GET_MEMBER + +} // namespace crashpad diff --git a/snapshot/crashpad_types/crashpad_info_reader.h b/snapshot/crashpad_types/crashpad_info_reader.h new file mode 100644 index 00000000..5f2352ef --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader.h @@ -0,0 +1,75 @@ +// 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_SNAPSHOT_CRASHPAD_TYPES_CRASHPAD_INFO_READER_H_ +#define CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_CRASHPAD_INFO_READER_H_ + +#include + +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/misc/tri_state.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief Reads CrashpadInfo structs from another process via a +//! ProcessMemoryRange. +class CrashpadInfoReader { + public: + CrashpadInfoReader(); + ~CrashpadInfoReader(); + + //! \brief Initializes this object. + //! + //! This method must be successfully called bfore any other method in this + //! class. + //! + //! \param[in] memory The reader for the remote process. + //! \param[in] address The address in the remote process' address space of a + //! CrashpadInfo struct. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(const ProcessMemoryRange* memory, VMAddress address); + + //! \{ + //! \see CrashpadInfo + TriState CrashpadHandlerBehavior(); + TriState SystemCrashReporterForwarding(); + TriState GatherIndirectlyReferencedMemory(); + uint32_t IndirectlyReferencedMemoryCap(); + VMAddress ExtraMemoryRanges(); + VMAddress SimpleAnnotations(); + VMAddress AnnotationsList(); + VMAddress UserDataMinidumpStreamHead(); + //! \} + + private: + class InfoContainer; + + template + class InfoContainerSpecific; + + std::unique_ptr container_; + bool is_64_bit_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(CrashpadInfoReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_CRASHPAD_INFO_READER_H_ diff --git a/snapshot/crashpad_types/crashpad_info_reader_test.cc b/snapshot/crashpad_types/crashpad_info_reader_test.cc new file mode 100644 index 00000000..87bafc68 --- /dev/null +++ b/snapshot/crashpad_types/crashpad_info_reader_test.cc @@ -0,0 +1,224 @@ +// 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 "snapshot/crashpad_types/crashpad_info_reader.h" + +#include +#include + +#include + +#include "build/build_config.h" +#include "client/annotation_list.h" +#include "client/crashpad_info.h" +#include "client/simple_address_range_bag.h" +#include "client/simple_string_dictionary.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "test/process_type.h" +#include "util/file/file_io.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_native.h" + +#if defined(OS_FUCHSIA) +#include +#endif + +namespace crashpad { +namespace test { +namespace { + +constexpr TriState kCrashpadHandlerBehavior = TriState::kEnabled; +constexpr TriState kSystemCrashReporterForwarding = TriState::kDisabled; +constexpr TriState kGatherIndirectlyReferencedMemory = TriState::kUnset; + +constexpr uint32_t kIndirectlyReferencedMemoryCap = 42; + +class ScopedUnsetCrashpadInfo { + public: + explicit ScopedUnsetCrashpadInfo(CrashpadInfo* crashpad_info) + : crashpad_info_(crashpad_info) {} + + ~ScopedUnsetCrashpadInfo() { + crashpad_info_->set_crashpad_handler_behavior(TriState::kUnset); + crashpad_info_->set_system_crash_reporter_forwarding(TriState::kUnset); + crashpad_info_->set_gather_indirectly_referenced_memory(TriState::kUnset, + 0); + crashpad_info_->set_extra_memory_ranges(nullptr); + crashpad_info_->set_simple_annotations(nullptr); + crashpad_info_->set_annotations_list(nullptr); + } + + private: + CrashpadInfo* crashpad_info_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUnsetCrashpadInfo); +}; + +class CrashpadInfoTestDataSetup { + public: + CrashpadInfoTestDataSetup() { + CrashpadInfo* info = CrashpadInfo::GetCrashpadInfo(); + unset_.reset(new ScopedUnsetCrashpadInfo(info)); + + info->set_extra_memory_ranges(&extra_memory_); + info->set_simple_annotations(&simple_annotations_); + info->set_annotations_list(&annotation_list_); + info->set_crashpad_handler_behavior(kCrashpadHandlerBehavior); + info->set_system_crash_reporter_forwarding(kSystemCrashReporterForwarding); + info->set_gather_indirectly_referenced_memory( + kGatherIndirectlyReferencedMemory, kIndirectlyReferencedMemoryCap); + } + + void GetAddresses(VMAddress* info_address, + VMAddress* extra_memory_address, + VMAddress* simple_annotations_address, + VMAddress* annotations_list_address) { + *info_address = FromPointerCast(CrashpadInfo::GetCrashpadInfo()); + *extra_memory_address = FromPointerCast(&extra_memory_); + *simple_annotations_address = + FromPointerCast(&simple_annotations_); + *annotations_list_address = FromPointerCast(&annotation_list_); + } + + private: + std::unique_ptr unset_; + SimpleAddressRangeBag extra_memory_; + SimpleStringDictionary simple_annotations_; + AnnotationList annotation_list_; + + DISALLOW_COPY_AND_ASSIGN(CrashpadInfoTestDataSetup); +}; + +void ExpectCrashpadInfo(ProcessType process, + bool is_64_bit, + VMAddress info_address, + VMAddress extra_memory_address, + VMAddress simple_annotations_address, + VMAddress annotations_list_address) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + + CrashpadInfoReader reader; + ASSERT_TRUE(reader.Initialize(&range, info_address)); + EXPECT_EQ(reader.CrashpadHandlerBehavior(), kCrashpadHandlerBehavior); + EXPECT_EQ(reader.SystemCrashReporterForwarding(), + kSystemCrashReporterForwarding); + EXPECT_EQ(reader.GatherIndirectlyReferencedMemory(), + kGatherIndirectlyReferencedMemory); + EXPECT_EQ(reader.IndirectlyReferencedMemoryCap(), + kIndirectlyReferencedMemoryCap); + EXPECT_EQ(reader.ExtraMemoryRanges(), extra_memory_address); + EXPECT_EQ(reader.SimpleAnnotations(), simple_annotations_address); + EXPECT_EQ(reader.AnnotationsList(), annotations_list_address); +} + +TEST(CrashpadInfoReader, ReadFromSelf) { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + CrashpadInfoTestDataSetup test_data_setup; + VMAddress info_address; + VMAddress extra_memory_address; + VMAddress simple_annotations_address; + VMAddress annotations_list_address; + test_data_setup.GetAddresses(&info_address, + &extra_memory_address, + &simple_annotations_address, + &annotations_list_address); + ExpectCrashpadInfo(GetSelfProcess(), + am_64_bit, + info_address, + extra_memory_address, + simple_annotations_address, + annotations_list_address); +} + +CRASHPAD_CHILD_TEST_MAIN(ReadFromChildTestMain) { + CrashpadInfoTestDataSetup test_data_setup; + VMAddress info_address; + VMAddress extra_memory_address; + VMAddress simple_annotations_address; + VMAddress annotations_list_address; + test_data_setup.GetAddresses(&info_address, + &extra_memory_address, + &simple_annotations_address, + &annotations_list_address); + + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + CheckedWriteFile(out, &info_address, sizeof(info_address)); + CheckedWriteFile(out, &extra_memory_address, sizeof(extra_memory_address)); + CheckedWriteFile( + out, &simple_annotations_address, sizeof(simple_annotations_address)); + CheckedWriteFile( + out, &annotations_list_address, sizeof(annotations_list_address)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadFromChildTest : public MultiprocessExec { + public: + ReadFromChildTest() : MultiprocessExec() { + SetChildTestMainFunction("ReadFromChildTestMain"); + } + + ~ReadFromChildTest() = default; + + private: + void MultiprocessParent() { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + VMAddress info_address; + VMAddress extra_memory_address; + VMAddress simple_annotations_address; + VMAddress annotations_list_address; + ASSERT_TRUE( + ReadFileExactly(ReadPipeHandle(), &info_address, sizeof(info_address))); + ASSERT_TRUE(ReadFileExactly( + ReadPipeHandle(), &extra_memory_address, sizeof(extra_memory_address))); + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), + &simple_annotations_address, + sizeof(simple_annotations_address))); + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), + &annotations_list_address, + sizeof(annotations_list_address))); + ExpectCrashpadInfo(ChildProcess(), + am_64_bit, + info_address, + extra_memory_address, + simple_annotations_address, + annotations_list_address); + } + + DISALLOW_COPY_AND_ASSIGN(ReadFromChildTest); +}; + +TEST(CrashpadInfoReader, ReadFromChild) { + ReadFromChildTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/crashpad_types/image_annotation_reader.cc b/snapshot/crashpad_types/image_annotation_reader.cc new file mode 100644 index 00000000..bd904979 --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader.cc @@ -0,0 +1,153 @@ +// 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 "snapshot/crashpad_types/image_annotation_reader.h" + +#include +#include + +#include +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "client/annotation.h" +#include "client/annotation_list.h" +#include "client/simple_string_dictionary.h" +#include "snapshot/snapshot_constants.h" +#include "util/linux/traits.h" + +namespace crashpad { + +namespace process_types { + +template +struct Annotation { + typename Traits::Address link_node; + typename Traits::Address name; + typename Traits::Address value; + uint32_t size; + uint16_t type; +}; + +template +struct AnnotationList { + typename Traits::Address tail_pointer; + Annotation head; + Annotation tail; +}; + +} // namespace process_types + +#if defined(ARCH_CPU_64_BITS) +#define NATIVE_TRAITS Traits64 +#else +#define NATIVE_TRAITS Traits32 +#endif // ARCH_CPU_64_BITS + +static_assert(sizeof(process_types::Annotation) == + sizeof(Annotation), + "Annotation size mismatch"); + +static_assert(sizeof(process_types::AnnotationList) == + sizeof(AnnotationList), + "AnnotationList size mismatch"); + +#undef NATIVE_TRAITS + +ImageAnnotationReader::ImageAnnotationReader(const ProcessMemoryRange* memory) + : memory_(memory) {} + +ImageAnnotationReader::~ImageAnnotationReader() = default; + +bool ImageAnnotationReader::SimpleMap( + VMAddress address, + std::map* annotations) const { + std::vector simple_annotations( + SimpleStringDictionary::num_entries); + + if (!memory_->Read(address, + simple_annotations.size() * sizeof(simple_annotations[0]), + &simple_annotations[0])) { + return false; + } + + for (const auto& entry : simple_annotations) { + size_t key_length = strnlen(entry.key, sizeof(entry.key)); + if (key_length) { + std::string key(entry.key, key_length); + std::string value(entry.value, strnlen(entry.value, sizeof(entry.value))); + if (!annotations->insert(std::make_pair(key, value)).second) { + LOG(WARNING) << "duplicate simple annotation " << key << " " << value; + } + } + } + return true; +} + +bool ImageAnnotationReader::AnnotationsList( + VMAddress address, + std::vector* annotations) const { + return memory_->Is64Bit() + ? ReadAnnotationList(address, annotations) + : ReadAnnotationList(address, annotations); +} + +template +bool ImageAnnotationReader::ReadAnnotationList( + VMAddress address, + std::vector* annotations) const { + process_types::AnnotationList annotation_list; + if (!memory_->Read(address, sizeof(annotation_list), &annotation_list)) { + LOG(ERROR) << "could not read annotation list"; + return false; + } + + process_types::Annotation current = annotation_list.head; + for (size_t index = 0; current.link_node != annotation_list.tail_pointer && + index < kMaxNumberOfAnnotations; + ++index) { + if (!memory_->Read(current.link_node, sizeof(current), ¤t)) { + LOG(ERROR) << "could not read annotation at index " << index; + return false; + } + + if (current.size == 0) { + continue; + } + + AnnotationSnapshot snapshot; + snapshot.type = current.type; + + if (!memory_->ReadCStringSizeLimited( + current.name, Annotation::kNameMaxLength, &snapshot.name)) { + LOG(WARNING) << "could not read annotation name at index " << index; + continue; + } + + size_t value_length = + std::min(static_cast(current.size), Annotation::kValueMaxSize); + snapshot.value.resize(value_length); + if (!memory_->Read(current.value, value_length, snapshot.value.data())) { + LOG(WARNING) << "could not read annotation value at index " << index; + continue; + } + + annotations->push_back(std::move(snapshot)); + } + + return true; +} + +} // namespace crashpad diff --git a/snapshot/crashpad_types/image_annotation_reader.h b/snapshot/crashpad_types/image_annotation_reader.h new file mode 100644 index 00000000..e425bef6 --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader.h @@ -0,0 +1,76 @@ +// 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_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ +#define CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/annotation_snapshot.h" +#include "util/misc/address_types.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief Reads Annotations from another process via a ProcessMemoryRange. +//! +//! These annotations are stored for the benefit of crash reporters, and provide +//! information thought to be potentially useful for crash analysis. +class ImageAnnotationReader { + public: + //! \brief Constructs the object. + //! + //! \param[in] memory A memory reader for the remote process. + explicit ImageAnnotationReader(const ProcessMemoryRange* memory); + + ~ImageAnnotationReader(); + + //! \brief Reads annotations that are organized as key-value pairs, where all + //! keys and values are strings. + //! + //! \param[in] address The address in the target process' address space of a + //! SimpleStringDictionary containing the annotations to read. + //! \param[out] annotations The annotations read, valid if this method + //! returns `true`. + //! \return `true` on success. `false` on failure with a message logged. + bool SimpleMap(VMAddress address, + std::map* annotations) const; + + //! \brief Reads the module's annotations that are organized as a list of + //! typed annotation objects. + //! + //! \param[in] address The address in the target process' address space of an + //! AnnotationList. + //! \param[out] annotations The annotations read, valid if this method returns + //! `true`. + //! \return `true` on success. `false` on failure with a message logged. + bool AnnotationsList(VMAddress, + std::vector* annotations) const; + + private: + template + bool ReadAnnotationList(VMAddress address, + std::vector* annotations) const; + + const ProcessMemoryRange* memory_; // weak + + DISALLOW_COPY_AND_ASSIGN(ImageAnnotationReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_CRASHPAD_TYPES_IMAGE_ANNOTATION_READER_H_ diff --git a/snapshot/crashpad_types/image_annotation_reader_test.cc b/snapshot/crashpad_types/image_annotation_reader_test.cc new file mode 100644 index 00000000..63cfcfcc --- /dev/null +++ b/snapshot/crashpad_types/image_annotation_reader_test.cc @@ -0,0 +1,187 @@ +// 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 "snapshot/crashpad_types/image_annotation_reader.h" + +#include +#include +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "client/annotation.h" +#include "client/annotation_list.h" +#include "client/simple_string_dictionary.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "test/process_type.h" +#include "util/file/file_io.h" +#include "util/misc/as_underlying_type.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_native.h" + +namespace crashpad { +namespace test { +namespace { + +void ExpectSimpleMap(const std::map& map, + const SimpleStringDictionary& expected_map) { + EXPECT_EQ(map.size(), expected_map.GetCount()); + for (const auto& pair : map) { + EXPECT_EQ(pair.second, expected_map.GetValueForKey(pair.first)); + } +} + +void ExpectAnnotationList(const std::vector& list, + AnnotationList& expected_list) { + size_t index = 0; + for (const Annotation* expected_annotation : expected_list) { + const AnnotationSnapshot& annotation = list[index++]; + EXPECT_EQ(annotation.name, expected_annotation->name()); + EXPECT_EQ(annotation.type, AsUnderlyingType(expected_annotation->type())); + EXPECT_EQ(annotation.value.size(), expected_annotation->size()); + EXPECT_EQ(memcmp(annotation.value.data(), + expected_annotation->value(), + std::min(VMSize{annotation.value.size()}, + VMSize{expected_annotation->size()})), + 0); + } +} + +void BuildTestStructures( + std::vector>* annotations_storage, + SimpleStringDictionary* into_map, + AnnotationList* into_annotation_list) { + into_map->SetKeyValue("key", "value"); + into_map->SetKeyValue("key2", "value2"); + + static constexpr char kAnnotationName[] = "test annotation"; + static constexpr char kAnnotationValue[] = "test annotation value"; + annotations_storage->push_back(std::make_unique( + Annotation::Type::kString, + kAnnotationName, + reinterpret_cast(const_cast(kAnnotationValue)))); + annotations_storage->back()->SetSize(sizeof(kAnnotationValue)); + into_annotation_list->Add(annotations_storage->back().get()); + + static constexpr char kAnnotationName2[] = "test annotation2"; + static constexpr char kAnnotationValue2[] = "test annotation value2"; + annotations_storage->push_back(std::make_unique( + Annotation::Type::kString, + kAnnotationName2, + reinterpret_cast(const_cast(kAnnotationValue2)))); + annotations_storage->back()->SetSize(sizeof(kAnnotationValue2)); + into_annotation_list->Add(annotations_storage->back().get()); +} + +void ExpectAnnotations(ProcessType process, + bool is_64_bit, + VMAddress simple_map_address, + VMAddress annotation_list_address) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + + SimpleStringDictionary expected_simple_map; + std::vector> storage; + AnnotationList expected_annotations; + BuildTestStructures(&storage, &expected_simple_map, &expected_annotations); + + ImageAnnotationReader reader(&range); + + std::map simple_map; + ASSERT_TRUE(reader.SimpleMap(simple_map_address, &simple_map)); + ExpectSimpleMap(simple_map, expected_simple_map); + + std::vector annotation_list; + ASSERT_TRUE( + reader.AnnotationsList(annotation_list_address, &annotation_list)); + ExpectAnnotationList(annotation_list, expected_annotations); +} + +TEST(ImageAnnotationReader, ReadFromSelf) { + SimpleStringDictionary map; + std::vector> storage; + AnnotationList annotations; + BuildTestStructures(&storage, &map, &annotations); + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + ExpectAnnotations(GetSelfProcess(), + am_64_bit, + FromPointerCast(&map), + FromPointerCast(&annotations)); +} + +CRASHPAD_CHILD_TEST_MAIN(ReadAnnotationsFromChildTestMain) { + SimpleStringDictionary map; + std::vector> storage; + AnnotationList annotations; + BuildTestStructures(&storage, &map, &annotations); + + VMAddress simple_map_address = FromPointerCast(&map); + VMAddress annotations_address = FromPointerCast(&annotations); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + CheckedWriteFile(out, &simple_map_address, sizeof(simple_map_address)); + CheckedWriteFile(out, &annotations_address, sizeof(annotations_address)); + + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadFromChildTest : public MultiprocessExec { + public: + ReadFromChildTest() : MultiprocessExec() { + SetChildTestMainFunction("ReadAnnotationsFromChildTestMain"); + } + + ~ReadFromChildTest() = default; + + private: + void MultiprocessParent() { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif + + VMAddress simple_map_address; + VMAddress annotations_address; + ASSERT_TRUE(ReadFileExactly( + ReadPipeHandle(), &simple_map_address, sizeof(simple_map_address))); + ASSERT_TRUE(ReadFileExactly( + ReadPipeHandle(), &annotations_address, sizeof(annotations_address))); + ExpectAnnotations( + ChildProcess(), am_64_bit, simple_map_address, annotations_address); + } + + DISALLOW_COPY_AND_ASSIGN(ReadFromChildTest); +}; + +TEST(ImageAnnotationReader, ReadFromChild) { + ReadFromChildTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/elf/elf_dynamic_array_reader.cc b/snapshot/elf/elf_dynamic_array_reader.cc index 681b1b57..9a44e12e 100644 --- a/snapshot/elf/elf_dynamic_array_reader.cc +++ b/snapshot/elf/elf_dynamic_array_reader.cc @@ -16,6 +16,8 @@ #include +#include + #include "util/stdlib/map_insert.h" namespace crashpad { @@ -48,8 +50,14 @@ bool Read(const ProcessMemoryRange& memory, // Skip these entries for now. break; default: + static_assert(std::is_unsigned::value, + "type must be unsigned"); + static_assert(static_cast(&entry.d_un.d_ptr) == + static_cast(&entry.d_un.d_val) && + sizeof(entry.d_un.d_ptr) == sizeof(entry.d_un.d_val), + "d_ptr and d_val must be aliases"); if (!MapInsertOrReplace( - &local_values, entry.d_tag, entry.d_un.d_val, nullptr)) { + &local_values, entry.d_tag, entry.d_un.d_ptr, nullptr)) { LOG(ERROR) << "duplicate dynamic array entry"; return false; } diff --git a/snapshot/elf/elf_dynamic_array_reader.h b/snapshot/elf/elf_dynamic_array_reader.h index 36628a0b..e80a8cbc 100644 --- a/snapshot/elf/elf_dynamic_array_reader.h +++ b/snapshot/elf/elf_dynamic_array_reader.h @@ -50,13 +50,15 @@ class ElfDynamicArrayReader { //! //! \param[in] tag Specifies which value should be retrieved. The possible //! values for this parameter are the `DT_*` values from ``. + //! \param[in] log Specifies whether an error should be logged if \a tag is + //! not found. //! \param[out] value The value, casted to an appropriate type, if found. //! \return `true` if the value is found. template - bool GetValue(uint64_t tag, V* value) { + bool GetValue(uint64_t tag, bool log, V* value) { auto iter = values_.find(tag); if (iter == values_.end()) { - LOG(ERROR) << "tag not found"; + LOG_IF(ERROR, log) << "tag not found"; return false; } return ReinterpretBytes(iter->second, value); diff --git a/snapshot/elf/elf_image_reader.cc b/snapshot/elf/elf_image_reader.cc index ccf33532..c6816605 100644 --- a/snapshot/elf/elf_image_reader.cc +++ b/snapshot/elf/elf_image_reader.cc @@ -16,7 +16,9 @@ #include +#include #include +#include #include #include "base/logging.h" @@ -36,6 +38,13 @@ class ElfImageReader::ProgramHeaderTable { virtual bool GetPreferredLoadedMemoryRange(VMAddress* address, VMSize* size) const = 0; + // Locate the next PT_NOTE segment starting at segment index start_index. If a + // PT_NOTE segment is found, start_index is set to the next index after the + // found segment. + virtual bool GetNoteSegment(size_t* start_index, + VMAddress* address, + VMSize* size) const = 0; + protected: ProgramHeaderTable() {} }; @@ -150,6 +159,21 @@ class ElfImageReader::ProgramHeaderTableSpecific return false; } + bool GetNoteSegment(size_t* start_index, + VMAddress* address, + VMSize* size) const override { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + for (size_t index = *start_index; index < table_.size(); ++index) { + if (table_[index].p_type == PT_NOTE && table_[index].p_vaddr != 0) { + *start_index = index + 1; + *address = table_[index].p_vaddr; + *size = table_[index].p_memsz; + return true; + } + } + return false; + } + private: std::vector table_; InitializationStateDcheck initialized_; @@ -157,6 +181,150 @@ class ElfImageReader::ProgramHeaderTableSpecific DISALLOW_COPY_AND_ASSIGN(ProgramHeaderTableSpecific); }; +ElfImageReader::NoteReader::~NoteReader() = default; + +ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::NextNote( + std::string* name, + NoteType* type, + std::string* desc) { + if (!is_valid_) { + LOG(ERROR) << "invalid note reader"; + return Result::kError; + } + + Result result = Result::kError; + do { + while (current_address_ == segment_end_address_) { + VMSize segment_size; + if (!phdr_table_->GetNoteSegment( + &phdr_index_, ¤t_address_, &segment_size)) { + return Result::kNoMoreNotes; + } + current_address_ += elf_reader_->GetLoadBias(); + segment_end_address_ = current_address_ + segment_size; + segment_range_ = std::make_unique(); + if (!segment_range_->Initialize(*range_) || + !segment_range_->RestrictRange(current_address_, segment_size)) { + return Result::kError; + } + } + + retry_ = false; + result = range_->Is64Bit() ? ReadNote(name, type, desc) + : ReadNote(name, type, desc); + } while (retry_); + + if (result == Result::kSuccess) { + return Result::kSuccess; + } + is_valid_ = false; + return Result::kError; +} + +ElfImageReader::NoteReader::NoteReader(const ElfImageReader* elf_reader, + const ProcessMemoryRange* range, + const ProgramHeaderTable* phdr_table, + ssize_t max_note_size, + const std::string& name_filter, + NoteType type_filter, + bool use_filter) + : current_address_(0), + segment_end_address_(0), + elf_reader_(elf_reader), + range_(range), + phdr_table_(phdr_table), + segment_range_(), + phdr_index_(0), + max_note_size_(max_note_size), + name_filter_(name_filter), + type_filter_(type_filter), + use_filter_(use_filter), + is_valid_(true), + retry_(false) {} + +template +ElfImageReader::NoteReader::Result ElfImageReader::NoteReader::ReadNote( + std::string* name, + NoteType* type, + std::string* desc) { + static_assert(sizeof(*type) >= sizeof(NhdrType::n_namesz), + "Note field size mismatch"); + DCHECK_LT(current_address_, segment_end_address_); + + NhdrType note_info; + if (!segment_range_->Read(current_address_, sizeof(note_info), ¬e_info)) { + return Result::kError; + } + 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; + + // Notes typically have 4-byte alignment. However, .note.android.ident may + // inadvertently use 2-byte alignment. + // https://android-review.googlesource.com/c/platform/bionic/+/554986/ + // We can still find .note.android.ident if it appears first in a note segment + // 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(max_note_size_)) { + current_address_ = end_of_note; + retry_ = true; + return Result::kError; + } + + if (use_filter_ && note_info.n_type != type_filter_) { + current_address_ = end_of_note; + retry_ = true; + return Result::kError; + } + + std::string local_name(note_info.n_namesz, '\0'); + if (!segment_range_->Read( + current_address_, note_info.n_namesz, &local_name[0])) { + return Result::kError; + } + if (!local_name.empty()) { + if (local_name.back() != '\0') { + LOG(ERROR) << "unterminated note name"; + return Result::kError; + } + local_name.pop_back(); + } + + if (use_filter_ && local_name != name_filter_) { + current_address_ = end_of_note; + retry_ = true; + return Result::kError; + } + + current_address_ += padded_namesz; + + std::string local_desc(note_info.n_descsz, '\0'); + if (!segment_range_->Read( + current_address_, note_info.n_descsz, &local_desc[0])) { + return Result::kError; + } + + current_address_ = end_of_note; + + if (name) { + name->swap(local_name); + } + if (type) { + *type = note_info.n_type; + } + desc->swap(local_desc); + return Result::kSuccess; +} + ElfImageReader::ElfImageReader() : header_64_(), ehdr_address_(0), @@ -351,8 +519,8 @@ bool ElfImageReader::ReadDynamicStringTableAtOffset(VMSize offset, VMAddress string_table_address; VMSize string_table_size; - if (!GetAddressFromDynamicArray(DT_STRTAB, &string_table_address) || - !dynamic_array_->GetValue(DT_STRSZ, &string_table_size)) { + if (!GetAddressFromDynamicArray(DT_STRTAB, true, &string_table_address) || + !dynamic_array_->GetValue(DT_STRSZ, true, &string_table_size)) { LOG(ERROR) << "missing string table info"; return false; } @@ -374,7 +542,7 @@ bool ElfImageReader::GetDebugAddress(VMAddress* debug) { if (!InitializeDynamicArray()) { return false; } - return GetAddressFromDynamicArray(DT_DEBUG, debug); + return GetAddressFromDynamicArray(DT_DEBUG, true, debug); } bool ElfImageReader::InitializeProgramHeaders() { @@ -441,25 +609,40 @@ bool ElfImageReader::InitializeDynamicSymbolTable() { } VMAddress symbol_table_address; - if (!GetAddressFromDynamicArray(DT_SYMTAB, &symbol_table_address)) { + if (!GetAddressFromDynamicArray(DT_SYMTAB, true, &symbol_table_address)) { LOG(ERROR) << "no symbol table"; return false; } - symbol_table_.reset( - new ElfSymbolTableReader(&memory_, this, symbol_table_address)); + // Try both DT_HASH and DT_GNU_HASH. They're completely different, but both + // circuitously offer a way to find the number of entries in the symbol table. + // DT_HASH is specifically checked first, because depending on the linker, the + // count maybe be incorrect for zero-export cases. In practice, it is believed + // that the zero-export case is probably not particularly useful, so this + // incorrect count will only occur in constructed test cases (see + // ElfImageReader.DtHashAndDtGnuHashMatch). + VMSize number_of_symbol_table_entries; + if (!GetNumberOfSymbolEntriesFromDtHash(&number_of_symbol_table_entries) && + !GetNumberOfSymbolEntriesFromDtGnuHash(&number_of_symbol_table_entries)) { + LOG(ERROR) << "could not retrieve number of symbol table entries"; + return false; + } + + symbol_table_.reset(new ElfSymbolTableReader( + &memory_, this, symbol_table_address, number_of_symbol_table_entries)); symbol_table_initialized_.set_valid(); return true; } bool ElfImageReader::GetAddressFromDynamicArray(uint64_t tag, + bool log, VMAddress* address) { - if (!dynamic_array_->GetValue(tag, address)) { + if (!dynamic_array_->GetValue(tag, log, address)) { return false; } -#if defined(OS_ANDROID) - // The GNU loader updates the dynamic array according to the load bias while - // the Android loader only updates the debug address. +#if defined(OS_ANDROID) || defined(OS_FUCHSIA) + // The GNU loader updates the dynamic array according to the load bias. + // The Android and Fuchsia loaders only update the debug address. if (tag != DT_DEBUG) { *address += GetLoadBias(); } @@ -467,4 +650,116 @@ bool ElfImageReader::GetAddressFromDynamicArray(uint64_t tag, return true; } +bool ElfImageReader::GetNumberOfSymbolEntriesFromDtHash( + VMSize* number_of_symbol_table_entries) { + if (!InitializeDynamicArray()) { + return false; + } + + VMAddress dt_hash_address; + if (!GetAddressFromDynamicArray(DT_HASH, false, &dt_hash_address)) { + return false; + } + + struct { + uint32_t nbucket; + uint32_t nchain; + } header; + + if (!memory_.Read(dt_hash_address, sizeof(header), &header)) { + LOG(ERROR) << "failed to read DT_HASH header"; + return false; + } + + *number_of_symbol_table_entries = header.nchain; + return true; +} + +bool ElfImageReader::GetNumberOfSymbolEntriesFromDtGnuHash( + VMSize* number_of_symbol_table_entries) { + if (!InitializeDynamicArray()) { + return false; + } + + VMAddress dt_gnu_hash_address; + if (!GetAddressFromDynamicArray(DT_GNU_HASH, false, &dt_gnu_hash_address)) { + return false; + } + + // See https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ and + // https://sourceware.org/ml/binutils/2006-10/msg00377.html. + struct { + uint32_t nbuckets; + uint32_t symoffset; + uint32_t bloom_size; + uint32_t bloom_shift; + } header; + if (!memory_.Read(dt_gnu_hash_address, sizeof(header), &header)) { + LOG(ERROR) << "failed to read DT_GNU_HASH header"; + return false; + } + + std::vector buckets(header.nbuckets); + const size_t kNumBytesForBuckets = sizeof(buckets[0]) * buckets.size(); + const size_t kWordSize = + memory_.Is64Bit() ? sizeof(uint64_t) : sizeof(uint32_t); + const VMAddress buckets_address = + dt_gnu_hash_address + sizeof(header) + (kWordSize * header.bloom_size); + if (!memory_.Read(buckets_address, kNumBytesForBuckets, buckets.data())) { + LOG(ERROR) << "read buckets"; + return false; + } + + // Locate the chain that handles the largest index bucket. + uint32_t last_symbol = 0; + for (uint32_t i = 0; i < header.nbuckets; ++i) { + last_symbol = std::max(buckets[i], last_symbol); + } + + if (last_symbol < header.symoffset) { + *number_of_symbol_table_entries = header.symoffset; + return true; + } + + // Walk the bucket's chain to add the chain length to the total. + const VMAddress chains_base_address = buckets_address + kNumBytesForBuckets; + for (;;) { + uint32_t chain_entry; + if (!memory_.Read(chains_base_address + (last_symbol - header.symoffset) * + sizeof(chain_entry), + sizeof(chain_entry), + &chain_entry)) { + LOG(ERROR) << "read chain entry"; + return false; + } + + ++last_symbol; + + // If the low bit is set, this entry is the end of the chain. + if (chain_entry & 1) + break; + } + + *number_of_symbol_table_entries = last_symbol; + return true; +} + +std::unique_ptr ElfImageReader::Notes( + ssize_t max_note_size) { + return std::make_unique( + this, &memory_, program_headers_.get(), max_note_size); +} + +std::unique_ptr +ElfImageReader::NotesWithNameAndType(const std::string& name, + NoteReader::NoteType type, + ssize_t max_note_size) { + return std::make_unique( + this, &memory_, program_headers_.get(), max_note_size, name, type, true); +} + +const ProcessMemoryRange* ElfImageReader::Memory() const { + return &memory_; +} + } // namespace crashpad diff --git a/snapshot/elf/elf_image_reader.h b/snapshot/elf/elf_image_reader.h index 7f05be56..7549f346 100644 --- a/snapshot/elf/elf_image_reader.h +++ b/snapshot/elf/elf_image_reader.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -35,7 +36,77 @@ namespace crashpad { //! //! This class is capable of reading both 32-bit and 64-bit images. class ElfImageReader { + private: + class ProgramHeaderTable; + public: + //! \brief This class enables reading note segments from an ELF image. + //! + //! Objects of this class should be created by calling + //! ElfImageReader::Notes() or ElfImageReader::NotesWithNameAndType(). + class NoteReader { + public: + ~NoteReader(); + + //! \brief The return value for NextNote(). + enum class Result { + //! \brief An error occurred. The NoteReader is invalidated and message is + //! logged. + kError, + + //! \brief A note was found. + kSuccess, + + //! \brief No more notes were found. + kNoMoreNotes, + }; + + //! \brief A type large enough to hold a note type, potentially across + //! bitness. + using NoteType = decltype(Elf64_Nhdr::n_type); + + //! \brief Searches for the next note in the image. + //! + //! \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); + + // private + NoteReader(const ElfImageReader* elf_reader_, + const ProcessMemoryRange* range, + const ProgramHeaderTable* phdr_table, + ssize_t max_note_size, + const std::string& name_filter = std::string(), + NoteType type_filter = 0, + bool use_filter = false); + + private: + // Reads the next note at the current segment address. Sets retry_ to true + // and returns kError if use_filter_ is true and the note's name and type do + // not match name_filter_ and type_filter_. + template + Result ReadNote(std::string* name, NoteType* type, std::string* desc); + + VMAddress current_address_; + VMAddress segment_end_address_; + const ElfImageReader* elf_reader_; // weak + const ProcessMemoryRange* range_; // weak + const ProgramHeaderTable* phdr_table_; // weak + std::unique_ptr segment_range_; + size_t phdr_index_; + ssize_t max_note_size_; + std::string name_filter_; + NoteType type_filter_; + bool use_filter_; + bool is_valid_; + bool retry_; + + DISALLOW_COPY_AND_ASSIGN(NoteReader); + }; + ElfImageReader(); ~ElfImageReader(); @@ -101,15 +172,76 @@ class ElfImageReader { //! \return `true` if the debug address was found. bool GetDebugAddress(VMAddress* debug); + //! \brief Return a NoteReader for this image, which scans all PT_NOTE + //! segments in the image. + //! + //! The returned NoteReader is only valid for the lifetime of the + //! ElfImageReader that created it. + //! + //! \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. + //! \return A NoteReader object capable of reading notes in this image. + std::unique_ptr Notes(ssize_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. + //! + //! The returned NoteReader is only valid for the lifetime of the + //! ElfImageReader that created it. + //! + //! \param[in] name The note name to match. + //! \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. + //! \return A NoteReader object capable of reading notes in this image. + std::unique_ptr NotesWithNameAndType(const std::string& name, + NoteReader::NoteType type, + ssize_t max_note_size); + + //! \brief Return a ProcessMemoryRange restricted to the range of this image. + //! + //! The caller does not take ownership of the returned object. + const ProcessMemoryRange* Memory() const; + + //! \brief Retrieves the number of symbol table entries in `DT_SYMTAB` + //! according to the data in the `DT_HASH` section. + //! + //! \note Exposed for testing, not normally otherwise useful. + //! + //! \param[out] number_of_symbol_table_entries The number of entries expected + //! in `DT_SYMTAB`. + //! \return `true` if a `DT_HASH` section was found, and was read + //! successfully, otherwise `false` with an error logged. + bool GetNumberOfSymbolEntriesFromDtHash( + VMSize* number_of_symbol_table_entries); + + //! \brief Retrieves the number of symbol table entries in `DT_SYMTAB` + //! according to the data in the `DT_GNU_HASH` section. + //! + //! \note Exposed for testing, not normally otherwise useful. + //! + //! \note Depending on the linker that generated the `DT_GNU_HASH` section, + //! this value may not be as expected if there are zero exported symbols. + //! + //! \param[out] number_of_symbol_table_entries The number of entries expected + //! in `DT_SYMTAB`. + //! \return `true` if a `DT_GNU_HASH` section was found, and was read + //! successfully, otherwise `false` with an error logged. + bool GetNumberOfSymbolEntriesFromDtGnuHash( + VMSize* number_of_symbol_table_entries); + private: - class ProgramHeaderTable; template class ProgramHeaderTableSpecific; bool InitializeProgramHeaders(); bool InitializeDynamicArray(); bool InitializeDynamicSymbolTable(); - bool GetAddressFromDynamicArray(uint64_t tag, VMAddress* address); + bool GetAddressFromDynamicArray(uint64_t tag, bool log, VMAddress* address); union { Elf32_Ehdr header_32_; diff --git a/snapshot/elf/elf_image_reader_fuzzer.cc b/snapshot/elf/elf_image_reader_fuzzer.cc new file mode 100644 index 00000000..1686650b --- /dev/null +++ b/snapshot/elf/elf_image_reader_fuzzer.cc @@ -0,0 +1,75 @@ +// 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 + +#include "base/logging.h" +#include "snapshot/elf/elf_image_reader.h" +#include "util/process/process_memory.h" + +using namespace crashpad; + +class FakeProcessMemory : public ProcessMemory { + public: + FakeProcessMemory(const uint8_t* data, size_t size, VMAddress fake_base) + : data_(data), size_(size), fake_base_(fake_base) {} + + ssize_t ReadUpTo(VMAddress address, + size_t size, + void* buffer) const override { + 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); + memcpy(buffer, &data_[offset_in_data], read_size); + return read_size; + } + + private: + const uint8_t* data_; + size_t size_; + VMAddress fake_base_; +}; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + // Swallow all logs to avoid spam. + logging::SetLogMessageHandler( + [](logging::LogSeverity, const char*, int, size_t, const std::string&) { + return true; + }); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + constexpr size_t kBase = 0x10000; + FakeProcessMemory process_memory(data, size, kBase); + ProcessMemoryRange process_memory_range; + process_memory_range.Initialize(&process_memory, true, kBase, size); + + ElfImageReader reader; + if (!reader.Initialize(process_memory_range, kBase)) + return 0; + + ElfImageReader::NoteReader::Result result; + std::string note_name; + std::string note_desc; + ElfImageReader::NoteReader::NoteType note_type; + auto notes = reader.Notes(-1); + while ((result = notes->NextNote(¬e_name, ¬e_type, ¬e_desc)) == + ElfImageReader::NoteReader::Result::kSuccess) { + LOG(ERROR) << note_name << note_type << note_desc; + } + + return 0; +} diff --git a/snapshot/elf/elf_image_reader_fuzzer_corpus/crashpad_snapshot_test_both_dt_hash_styles.so b/snapshot/elf/elf_image_reader_fuzzer_corpus/crashpad_snapshot_test_both_dt_hash_styles.so new file mode 100755 index 00000000..4dc7cdbe Binary files /dev/null and b/snapshot/elf/elf_image_reader_fuzzer_corpus/crashpad_snapshot_test_both_dt_hash_styles.so differ diff --git a/snapshot/elf/elf_image_reader_fuzzer_corpus/ret42 b/snapshot/elf/elf_image_reader_fuzzer_corpus/ret42 new file mode 100755 index 00000000..59eb8f2b Binary files /dev/null and b/snapshot/elf/elf_image_reader_fuzzer_corpus/ret42 differ diff --git a/snapshot/elf/elf_image_reader_test.cc b/snapshot/elf/elf_image_reader_test.cc index 6eb5d410..d64fd5c4 100644 --- a/snapshot/elf/elf_image_reader_test.cc +++ b/snapshot/elf/elf_image_reader_test.cc @@ -15,18 +15,39 @@ #include "snapshot/elf/elf_image_reader.h" #include +#include #include #include "base/logging.h" #include "build/build_config.h" #include "gtest/gtest.h" -#include "test/multiprocess.h" +#include "test/multiprocess_exec.h" +#include "test/process_type.h" +#include "test/scoped_module_handle.h" +#include "test/test_paths.h" #include "util/file/file_io.h" +#include "util/misc/address_types.h" +#include "util/misc/elf_note_types.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_native.h" + +#if defined(OS_FUCHSIA) + +#include + +#include "base/fuchsia/fuchsia_logging.h" + +#elif defined(OS_LINUX) || defined(OS_ANDROID) + +#include "test/linux/fake_ptrace_connection.h" #include "util/linux/auxiliary_vector.h" #include "util/linux/memory_map.h" -#include "util/misc/address_types.h" -#include "util/misc/from_pointer_cast.h" -#include "util/process/process_memory_linux.h" + +#else + +#error Port. + +#endif // OS_FUCHSIA extern "C" { __attribute__((visibility("default"))) void @@ -37,15 +58,49 @@ namespace crashpad { namespace test { namespace { -void LocateExecutable(pid_t pid, bool is_64_bit, VMAddress* elf_address) { + +#if defined(OS_FUCHSIA) + +void LocateExecutable(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)); + 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. + EXPECT_NE(debug_address, 0u); + + constexpr auto k_r_debug_map_offset = offsetof(r_debug, r_map); + uintptr_t map; + ASSERT_TRUE( + memory->Read(debug_address + k_r_debug_map_offset, sizeof(map), &map)) + << "read link_map"; + + constexpr auto k_link_map_addr_offset = offsetof(link_map, l_addr); + uintptr_t base; + ASSERT_TRUE(memory->Read(map + k_link_map_addr_offset, sizeof(base), &base)) + << "read base"; + + *elf_address = base; +} + +#elif defined(OS_LINUX) || defined(OS_ANDROID) + +void LocateExecutable(PtraceConnection* connection, + ProcessMemory* memory, + VMAddress* elf_address) { AuxiliaryVector aux; - ASSERT_TRUE(aux.Initialize(pid, is_64_bit)); + ASSERT_TRUE(aux.Initialize(connection)); VMAddress phdrs; ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs)); MemoryMap memory_map; - ASSERT_TRUE(memory_map.Initialize(pid)); + ASSERT_TRUE(memory_map.Initialize(connection)); const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); const MemoryMap::Mapping* exe_mapping = @@ -54,106 +109,250 @@ void LocateExecutable(pid_t pid, bool is_64_bit, VMAddress* elf_address) { *elf_address = exe_mapping->range.Base(); } -void ExpectElfImageWithSymbol(pid_t pid, - VMAddress address, - bool is_64_bit, - std::string symbol_name, - VMAddress expected_symbol_address) { - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); - ProcessMemoryRange range; - ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); - - ElfImageReader reader; - ASSERT_TRUE(reader.Initialize(range, address)); +#endif // OS_FUCHSIA +void ExpectSymbol(ElfImageReader* reader, + const std::string& symbol_name, + VMAddress expected_symbol_address) { VMAddress symbol_address; VMSize symbol_size; ASSERT_TRUE( - reader.GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size)); + reader->GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size)); EXPECT_EQ(symbol_address, expected_symbol_address); EXPECT_FALSE( - reader.GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size)); + reader->GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size)); } -void ReadThisExecutableInTarget(pid_t pid) { +void ReadThisExecutableInTarget(ProcessType process, + VMAddress exported_symbol_address) { #if defined(ARCH_CPU_64_BITS) constexpr bool am_64_bit = true; #else constexpr bool am_64_bit = false; #endif // ARCH_CPU_64_BITS - VMAddress elf_address; - LocateExecutable(pid, am_64_bit, &elf_address); + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); - ExpectElfImageWithSymbol( - pid, - elf_address, - am_64_bit, - "ElfImageReaderTestExportedSymbol", + VMAddress elf_address; +#if defined(OS_LINUX) || defined(OS_ANDROID) + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(process)); + LocateExecutable(&connection, &memory, &elf_address); +#elif defined(OS_FUCHSIA) + LocateExecutable(process, &memory, &elf_address); +#endif + ASSERT_NO_FATAL_FAILURE(); + + ElfImageReader reader; + ASSERT_TRUE(reader.Initialize(range, elf_address)); + + ExpectSymbol( + &reader, "ElfImageReaderTestExportedSymbol", exported_symbol_address); + + ElfImageReader::NoteReader::Result result; + std::string note_name; + std::string note_desc; + ElfImageReader::NoteReader::NoteType note_type; + + std::unique_ptr notes = reader.Notes(-1); + while ((result = notes->NextNote(¬e_name, ¬e_type, ¬e_desc)) == + ElfImageReader::NoteReader::Result::kSuccess) { + } + EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes); + + notes = reader.Notes(0); + EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc), + 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(¬e_name, ¬e_type, ¬e_desc), + ElfImageReader::NoteReader::Result::kSuccess); + EXPECT_EQ(note_name, CRASHPAD_ELF_NOTE_NAME); + EXPECT_EQ(note_type, + implicit_cast(CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST)); + EXPECT_EQ(note_desc.size(), sizeof(kCrashpadNoteDesc)); + EXPECT_EQ(*reinterpret_cast(¬e_desc[0]), + kCrashpadNoteDesc); + + EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc), + ElfImageReader::NoteReader::Result::kNoMoreNotes); +} + +void ReadLibcInTarget(ProcessType process, + VMAddress elf_address, + VMAddress getpid_address) { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif // ARCH_CPU_64_BITS + + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); + + ElfImageReader reader; + ASSERT_TRUE(reader.Initialize(range, elf_address)); + + ExpectSymbol(&reader, "getpid", getpid_address); +} + +TEST(ElfImageReader, MainExecutableSelf) { + ReadThisExecutableInTarget( + GetSelfProcess(), FromPointerCast(ElfImageReaderTestExportedSymbol)); } -// Assumes that libc is loaded at the same address in this process as in the -// target, which it is for the fork test below. -void ReadLibcInTarget(pid_t pid) { -#if defined(ARCH_CPU_64_BITS) - constexpr bool am_64_bit = true; -#else - constexpr bool am_64_bit = false; -#endif // ARCH_CPU_64_BITS - - Dl_info info; - ASSERT_TRUE(dladdr(reinterpret_cast(getpid), &info)) << "dladdr:" - << dlerror(); - VMAddress elf_address = FromPointerCast(info.dli_fbase); - - ExpectElfImageWithSymbol(pid, - elf_address, - am_64_bit, - "getpid", - FromPointerCast(getpid)); +CRASHPAD_CHILD_TEST_MAIN(ReadExecutableChild) { + VMAddress exported_symbol_address = + FromPointerCast(ElfImageReaderTestExportedSymbol); + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &exported_symbol_address, + sizeof(exported_symbol_address)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; } -class ReadExecutableChildTest : public Multiprocess { +class ReadExecutableChildTest : public MultiprocessExec { public: - ReadExecutableChildTest() : Multiprocess() {} - ~ReadExecutableChildTest() {} + ReadExecutableChildTest() : MultiprocessExec() {} private: - void MultiprocessParent() { ReadThisExecutableInTarget(ChildPID()); } - void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); } + void MultiprocessParent() { + // This read serves two purposes -- on Fuchsia, the loader may have not + // filled in debug address as soon as the child process handle is valid, so + // this causes a wait at least until the main() of the child, at which point + // it will always be valid. Secondarily, the address of the symbol to be + // looked up needs to be communicated. + VMAddress exported_symbol_address; + CheckedReadFileExactly(ReadPipeHandle(), + &exported_symbol_address, + sizeof(exported_symbol_address)); + ReadThisExecutableInTarget(ChildProcess(), exported_symbol_address); + } }; -TEST(ElfImageReader, MainExecutableSelf) { - ReadThisExecutableInTarget(getpid()); -} - TEST(ElfImageReader, MainExecutableChild) { ReadExecutableChildTest test; + test.SetChildTestMainFunction("ReadExecutableChild"); test.Run(); } TEST(ElfImageReader, OneModuleSelf) { - ReadLibcInTarget(getpid()); + Dl_info info; + ASSERT_TRUE(dladdr(reinterpret_cast(getpid), &info)) << "dladdr:" + << dlerror(); + VMAddress elf_address = FromPointerCast(info.dli_fbase); + ReadLibcInTarget( + GetSelfProcess(), elf_address, FromPointerCast(getpid)); } -class ReadLibcChildTest : public Multiprocess { +CRASHPAD_CHILD_TEST_MAIN(ReadLibcChild) { + // Get the address of libc (by using getpid() as a representative member), + // and also the address of getpid() itself, and write them to the parent, so + // it can validate reading this information back out. + Dl_info info; + EXPECT_TRUE(dladdr(reinterpret_cast(getpid), &info)) + << "dladdr:" << dlerror(); + VMAddress elf_address = FromPointerCast(info.dli_fbase); + VMAddress getpid_address = FromPointerCast(getpid); + + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &elf_address, + sizeof(elf_address)); + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &getpid_address, + sizeof(getpid_address)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadLibcChildTest : public MultiprocessExec { public: - ReadLibcChildTest() : Multiprocess() {} + ReadLibcChildTest() : MultiprocessExec() {} ~ReadLibcChildTest() {} private: - void MultiprocessParent() { ReadLibcInTarget(ChildPID()); } - void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); } + void MultiprocessParent() { + VMAddress elf_address, getpid_address; + CheckedReadFileExactly(ReadPipeHandle(), &elf_address, sizeof(elf_address)); + CheckedReadFileExactly( + ReadPipeHandle(), &getpid_address, sizeof(getpid_address)); + ReadLibcInTarget(ChildProcess(), elf_address, getpid_address); + } }; TEST(ElfImageReader, OneModuleChild) { ReadLibcChildTest test; + test.SetChildTestMainFunction("ReadLibcChild"); test.Run(); } +#if defined(OS_FUCHSIA) + +// crashpad_snapshot_test_both_dt_hash_styles is specially built and forced to +// include both .hash and .gnu.hash sections. Linux, Android, and Fuchsia have +// different defaults for which of these sections should be included; this test +// confirms that we get the same count from both sections. +// +// TODO(scottmg): Investigation in https://crrev.com/c/876879 resulted in +// realizing that ld.bfd does not emit a .gnu.hash that is very useful for this +// purpose when there's 0 exported entries in the module. This is not likely to +// be too important, as there's little need to look up non-exported symbols. +// However, it makes this test not work on Linux, where the default build uses +// ld.bfd. On Fuchsia, the only linker in use is lld, and it generates the +// expected .gnu.hash. So, for now, this test is only run on Fuchsia, not Linux. +// +// TODO(scottmg): Separately, the location of the ELF on Android needs some +// work, and then the test could also be enabled there. +TEST(ElfImageReader, DtHashAndDtGnuHashMatch) { + base::FilePath module_path = + TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), + FILE_PATH_LITERAL("both_dt_hash_styles"), + TestPaths::FileType::kLoadableModule); + // TODO(scottmg): Remove this when upstream Fuchsia bug ZX-1619 is resolved. + // See also explanation in build/run_tests.py for Fuchsia .so files. + module_path = module_path.BaseName(); + ScopedModuleHandle module( + dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); + ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": " + << dlerror(); + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif // ARCH_CPU_64_BITS + + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); + + struct link_map* lm = reinterpret_cast(module.get()); + + ElfImageReader reader; + ASSERT_TRUE(reader.Initialize(range, lm->l_addr)); + + VMSize from_dt_hash; + ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtHash(&from_dt_hash)); + + VMSize from_dt_gnu_hash; + ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtGnuHash(&from_dt_gnu_hash)); + + EXPECT_EQ(from_dt_hash, from_dt_gnu_hash); +} + +#endif // OS_FUCHSIA + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/elf/elf_image_reader_test_note.S b/snapshot/elf/elf_image_reader_test_note.S new file mode 100644 index 00000000..9ab03389 --- /dev/null +++ b/snapshot/elf/elf_image_reader_test_note.S @@ -0,0 +1,32 @@ +// 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 "util/misc/elf_note_types.h" + +#define NOTE_ALIGN 4 + .section .note.crashpad.test,"a",%note + .balign NOTE_ALIGN + .type testnote, %object +testnote: + .long name_end - name // namesz + .long desc_end - desc // descsz + .long CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST // type +name: + .asciz CRASHPAD_ELF_NOTE_NAME +name_end: + .balign NOTE_ALIGN +desc: + .long 42 +desc_end: + .size testnote, .-testnote diff --git a/snapshot/elf/elf_symbol_table_reader.cc b/snapshot/elf/elf_symbol_table_reader.cc index c6395db7..c2c6abf0 100644 --- a/snapshot/elf/elf_symbol_table_reader.cc +++ b/snapshot/elf/elf_symbol_table_reader.cc @@ -51,8 +51,12 @@ uint8_t GetVisibility(const Elf64_Sym& sym) { ElfSymbolTableReader::ElfSymbolTableReader(const ProcessMemoryRange* memory, ElfImageReader* elf_reader, - VMAddress address) - : memory_(memory), elf_reader_(elf_reader), base_address_(address) {} + VMAddress address, + VMSize num_entries) + : memory_(memory), + elf_reader_(elf_reader), + base_address_(address), + num_entries_(num_entries) {} ElfSymbolTableReader::~ElfSymbolTableReader() {} @@ -68,9 +72,10 @@ bool ElfSymbolTableReader::ScanSymbolTable(const std::string& name, VMAddress address = base_address_; SymEnt entry; std::string string; - while (memory_->Read(address, sizeof(entry), &entry) && - elf_reader_->ReadDynamicStringTableAtOffset(entry.st_name, &string)) { - if (string == name) { + size_t i = 0; + while (i < num_entries_ && memory_->Read(address, sizeof(entry), &entry)) { + if (elf_reader_->ReadDynamicStringTableAtOffset(entry.st_name, &string) && + string == name) { info_out->address = entry.st_value; info_out->size = entry.st_size; info_out->shndx = entry.st_shndx; @@ -79,7 +84,9 @@ bool ElfSymbolTableReader::ScanSymbolTable(const std::string& name, info_out->visibility = GetVisibility(entry); return true; } + // TODO(scottmg): This should respect DT_SYMENT if present. address += sizeof(entry); + ++i; } return false; } diff --git a/snapshot/elf/elf_symbol_table_reader.h b/snapshot/elf/elf_symbol_table_reader.h index 880915a2..15930372 100644 --- a/snapshot/elf/elf_symbol_table_reader.h +++ b/snapshot/elf/elf_symbol_table_reader.h @@ -61,7 +61,8 @@ class ElfSymbolTableReader { // lookup. ElfSymbolTableReader(const ProcessMemoryRange* memory, ElfImageReader* elf_reader, - VMAddress address); + VMAddress address, + VMSize num_entries); ~ElfSymbolTableReader(); //! \brief Lookup information about a symbol. @@ -78,6 +79,7 @@ class ElfSymbolTableReader { const ProcessMemoryRange* const memory_; // weak ElfImageReader* const elf_reader_; // weak const VMAddress base_address_; + const VMSize num_entries_; DISALLOW_COPY_AND_ASSIGN(ElfSymbolTableReader); }; diff --git a/snapshot/elf/module_snapshot_elf.cc b/snapshot/elf/module_snapshot_elf.cc new file mode 100644 index 00000000..62a961d3 --- /dev/null +++ b/snapshot/elf/module_snapshot_elf.cc @@ -0,0 +1,202 @@ +// 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/elf/module_snapshot_elf.h" + +#include + +#include + +#include "base/files/file_path.h" +#include "snapshot/crashpad_types/image_annotation_reader.h" +#include "util/misc/elf_note_types.h" + +namespace crashpad { +namespace internal { + +ModuleSnapshotElf::ModuleSnapshotElf(const std::string& name, + ElfImageReader* elf_reader, + ModuleSnapshot::ModuleType type, + ProcessMemoryRange* process_memory_range) + : ModuleSnapshot(), + name_(name), + elf_reader_(elf_reader), + process_memory_range_(process_memory_range), + crashpad_info_(), + type_(type), + initialized_() {} + +ModuleSnapshotElf::~ModuleSnapshotElf() = default; + +bool ModuleSnapshotElf::Initialize() { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + if (!elf_reader_) { + LOG(ERROR) << "no elf reader"; + return false; + } + + // The data payload is only sizeof(VMAddress) in the note, but add a bit to + // account for the name, header, and padding. + constexpr ssize_t kMaxNoteSize = 256; + std::unique_ptr notes = + elf_reader_->NotesWithNameAndType(CRASHPAD_ELF_NOTE_NAME, + CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO, + kMaxNoteSize); + std::string desc; + VMAddress info_address; + if (notes->NextNote(nullptr, nullptr, &desc) == + ElfImageReader::NoteReader::Result::kSuccess) { + info_address = *reinterpret_cast(&desc[0]); + + ProcessMemoryRange range; + if (range.Initialize(*elf_reader_->Memory())) { + auto info = std::make_unique(); + if (info->Initialize(&range, info_address)) { + crashpad_info_ = std::move(info); + } + } + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ModuleSnapshotElf::GetCrashpadOptions(CrashpadInfoClientOptions* options) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (!crashpad_info_) { + return false; + } + + options->crashpad_handler_behavior = + crashpad_info_->CrashpadHandlerBehavior(); + options->system_crash_reporter_forwarding = + crashpad_info_->SystemCrashReporterForwarding(); + options->gather_indirectly_referenced_memory = + crashpad_info_->GatherIndirectlyReferencedMemory(); + options->indirectly_referenced_memory_cap = + crashpad_info_->IndirectlyReferencedMemoryCap(); + return true; +} + +std::string ModuleSnapshotElf::Name() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return name_; +} + +uint64_t ModuleSnapshotElf::Address() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return elf_reader_->Address(); +} + +uint64_t ModuleSnapshotElf::Size() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return elf_reader_->Size(); +} + +time_t ModuleSnapshotElf::Timestamp() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return 0; +} + +void ModuleSnapshotElf::FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *version_0 = 0; + *version_1 = 0; + *version_2 = 0; + *version_3 = 0; +} + +void ModuleSnapshotElf::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 = 0; + *version_1 = 0; + *version_2 = 0; + *version_3 = 0; +} + +ModuleSnapshot::ModuleType ModuleSnapshotElf::GetModuleType() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return type_; +} + +void ModuleSnapshotElf::UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *age = 0; + + std::unique_ptr 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(&desc[0])); + + // TODO(scottmg): https://crashpad.chromium.org/bug/229. These are + // endian-swapped to match FileID::ConvertIdentifierToUUIDString() in + // Breakpad. This is necessary as this identifier is used for symbol lookup. + uuid->data_1 = htobe32(uuid->data_1); + uuid->data_2 = htobe16(uuid->data_2); + uuid->data_3 = htobe16(uuid->data_3); +} + +std::string ModuleSnapshotElf::DebugFileName() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return base::FilePath(Name()).BaseName().value(); +} + +std::vector ModuleSnapshotElf::AnnotationsVector() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::map ModuleSnapshotElf::AnnotationsSimpleMap() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::map annotations; + if (crashpad_info_ && crashpad_info_->SimpleAnnotations()) { + ImageAnnotationReader reader(process_memory_range_); + reader.SimpleMap(crashpad_info_->SimpleAnnotations(), &annotations); + } + return annotations; +} + +std::vector ModuleSnapshotElf::AnnotationObjects() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector annotations; + if (crashpad_info_ && crashpad_info_->AnnotationsList()) { + ImageAnnotationReader reader(process_memory_range_); + reader.AnnotationsList(crashpad_info_->AnnotationsList(), &annotations); + } + return annotations; +} + +std::set> ModuleSnapshotElf::ExtraMemoryRanges() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::set>(); +} + +std::vector +ModuleSnapshotElf::CustomMinidumpStreams() const { + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/elf/module_snapshot_elf.h b/snapshot/elf/module_snapshot_elf.h new file mode 100644 index 00000000..15c3f3c2 --- /dev/null +++ b/snapshot/elf/module_snapshot_elf.h @@ -0,0 +1,100 @@ +// 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_ELF_MODULE_SNAPSHOT_ELF_H_ +#define CRASHPAD_SNAPSHOT_ELF_MODULE_SNAPSHOT_ELF_H_ + +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "client/crashpad_info.h" +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/crashpad_types/crashpad_info_reader.h" +#include "snapshot/elf/elf_image_reader.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 a system that uses ELF modules. +class ModuleSnapshotElf final : public ModuleSnapshot { + public: + //! \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. + ModuleSnapshotElf(const std::string& name, + ElfImageReader* elf_reader, + ModuleSnapshot::ModuleType type, + ProcessMemoryRange* process_memory_range); + ~ModuleSnapshotElf() override; + + //! \brief Initializes the object. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(); + + //! \brief Returns options from the module’s CrashpadInfo structure. + //! + //! \param[out] options Options set in the module’s CrashpadInfo structure. + //! \return `true` if there were options returned. Otherwise `false`. + bool GetCrashpadOptions(CrashpadInfoClientOptions* options); + + // 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(crashpad::UUID* uuid, uint32_t* age) const override; + std::string DebugFileName() const override; + std::vector AnnotationsVector() const override; + std::map AnnotationsSimpleMap() const override; + std::vector AnnotationObjects() const override; + std::set> ExtraMemoryRanges() const override; + std::vector CustomMinidumpStreams() const override; + + private: + std::string name_; + ElfImageReader* elf_reader_; + ProcessMemoryRange* process_memory_range_; + std::unique_ptr crashpad_info_; + ModuleType type_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotElf); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_ELF_MODULE_SNAPSHOT_ELF_H_ diff --git a/snapshot/linux/test_exported_symbols.sym b/snapshot/elf/test_exported_symbols.sym similarity index 100% rename from snapshot/linux/test_exported_symbols.sym rename to snapshot/elf/test_exported_symbols.sym diff --git a/snapshot/fuchsia/cpu_context_fuchsia.cc b/snapshot/fuchsia/cpu_context_fuchsia.cc new file mode 100644 index 00000000..5a02f62f --- /dev/null +++ b/snapshot/fuchsia/cpu_context_fuchsia.cc @@ -0,0 +1,51 @@ +// 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/cpu_context_fuchsia.h" + +#include + +namespace crashpad { +namespace internal { + +#if defined(ARCH_CPU_X86_64) + +void InitializeCPUContextX86_64( + const zx_thread_state_general_regs_t& thread_context, + CPUContextX86_64* context) { + memset(context, 0, sizeof(*context)); + context->rax = thread_context.rax; + context->rbx = thread_context.rbx; + context->rcx = thread_context.rcx; + context->rdx = thread_context.rdx; + context->rdi = thread_context.rdi; + context->rsi = thread_context.rsi; + context->rbp = thread_context.rbp; + context->rsp = thread_context.rsp; + context->r8 = thread_context.r8; + context->r9 = thread_context.r9; + context->r10 = thread_context.r10; + context->r11 = thread_context.r11; + context->r12 = thread_context.r12; + context->r13 = thread_context.r13; + context->r14 = thread_context.r14; + context->r15 = thread_context.r15; + context->rip = thread_context.rip; + context->rflags = thread_context.rflags; +} + +#endif // ARCH_CPU_X86_64 + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/fuchsia/cpu_context_fuchsia.h b/snapshot/fuchsia/cpu_context_fuchsia.h new file mode 100644 index 00000000..f5336fdd --- /dev/null +++ b/snapshot/fuchsia/cpu_context_fuchsia.h @@ -0,0 +1,46 @@ +// 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_CPU_CONTEXT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_CPU_CONTEXT_FUCHSIA_H_ + +#include + +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/fuchsia/process_reader_fuchsia.h" + +namespace crashpad { +namespace internal { + +#if defined(ARCH_CPU_X86_64) || DOXYGEN + +//! \brief Initializes a CPUContextX86_64 structure from native context +//! structures on Fuchsia. +//! +//! Floating point registers are currently initialized to zero. +//! Segment registers are currently initialized to zero. +//! +//! \param[in] thread_context The native thread context. +//! \param[out] context The CPUContextX86_64 structure to initialize. +void InitializeCPUContextX86_64( + const zx_thread_state_general_regs_t& thread_context, + CPUContextX86_64* context); + +#endif // ARCH_CPU_X86_64 || DOXYGEN + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_CPU_CONTEXT_FUCHSIA_H_ diff --git a/snapshot/fuchsia/exception_snapshot_fuchsia.cc b/snapshot/fuchsia/exception_snapshot_fuchsia.cc new file mode 100644 index 00000000..44b4e5cd --- /dev/null +++ b/snapshot/fuchsia/exception_snapshot_fuchsia.cc @@ -0,0 +1,129 @@ +// 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/exception_snapshot_fuchsia.h" + +#include "base/numerics/safe_conversions.h" +#include "snapshot/fuchsia/cpu_context_fuchsia.h" +#include "snapshot/fuchsia/process_reader_fuchsia.h" + +namespace crashpad { +namespace internal { + +ExceptionSnapshotFuchsia::ExceptionSnapshotFuchsia() = default; +ExceptionSnapshotFuchsia::~ExceptionSnapshotFuchsia() = default; + +void ExceptionSnapshotFuchsia::Initialize( + ProcessReaderFuchsia* process_reader, + zx_koid_t thread_id, + const zx_exception_report_t& exception_report) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + exception_ = exception_report.header.type; + thread_id_ = thread_id; + + // TODO(scottmg): Not sure whether these values for exception_info_ are + // helpful or correct. Other values in the structures are stored below into + // Codes() in case they are useful. +#if defined(ARCH_CPU_X86_64) + DCHECK(base::IsValueInRangeForNumericType( + exception_report.context.arch.u.x86_64.err_code)); + exception_info_ = exception_report.context.arch.u.x86_64.err_code; +#elif defined(ARCH_CPU_ARM64) + exception_info_ = exception_report.context.arch.u.arm_64.esr; +#endif + + codes_.push_back(exception_); + codes_.push_back(exception_info_); + +#if defined(ARCH_CPU_X86_64) + codes_.push_back(exception_report.context.arch.u.x86_64.vector); + codes_.push_back(exception_report.context.arch.u.x86_64.cr2); +#elif defined(ARCH_CPU_ARM64) + codes_.push_back(exception_report.context.arch.u.arm_64.far); +#endif + + for (const auto& t : process_reader->Threads()) { + if (t.id == thread_id) { +#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); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arch_; + // TODO(scottmg): Implement context capture for arm64. +#else +#error Port. +#endif + } + } + + if (context_.InstructionPointer() != 0 && + (exception_ == ZX_EXCP_UNDEFINED_INSTRUCTION || + exception_ == ZX_EXCP_SW_BREAKPOINT || + exception_ == ZX_EXCP_HW_BREAKPOINT)) { + exception_address_ = context_.InstructionPointer(); + } else { +#if defined(ARCH_CPU_X86_64) + exception_address_ = exception_report.context.arch.u.x86_64.cr2; +#elif defined(ARCH_CPU_ARM64) + exception_address_ = exception_report.context.arch.u.arm_64.far; +#endif + } + + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +const CPUContext* ExceptionSnapshotFuchsia::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &context_; +} + +uint64_t ExceptionSnapshotFuchsia::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_id_; +} + +uint32_t ExceptionSnapshotFuchsia::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_; +} + +uint32_t ExceptionSnapshotFuchsia::ExceptionInfo() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_info_; +} + +uint64_t ExceptionSnapshotFuchsia::ExceptionAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_address_; +} + +const std::vector& ExceptionSnapshotFuchsia::Codes() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return codes_; +} + +std::vector ExceptionSnapshotFuchsia::ExtraMemory() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/fuchsia/exception_snapshot_fuchsia.h b/snapshot/fuchsia/exception_snapshot_fuchsia.h new file mode 100644 index 00000000..eda1c66d --- /dev/null +++ b/snapshot/fuchsia/exception_snapshot_fuchsia.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_FUCHSIA_EXCEPTION_SNAPSHOT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_EXCEPTION_SNAPSHOT_FUCHSIA_H_ + +#include +#include +#include + +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/exception_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +class ProcessReaderFuchsia; + +namespace internal { + +//! \brief An ExceptionSnapshot of an exception sustained by a process on a +//! Fuchsia system. +class ExceptionSnapshotFuchsia final : public ExceptionSnapshot { + public: + ExceptionSnapshotFuchsia(); + ~ExceptionSnapshotFuchsia() override; + + //! \brief Initializes the object. + //! + //! \param[in] process_reader A ProcessReaderFuchsia for the process that + //! sustained the exception. + //! \param[in] thread_id The koid of the thread that sustained the exception. + //! \param[in] exception_report The `zx_exception_report_t` retrieved from the + //! thread in the exception state, corresponding to \a thread_id. + void Initialize(ProcessReaderFuchsia* process_reader, + zx_koid_t thread_id, + const zx_exception_report_t& exception_report); + + // 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& Codes() const override; + std::vector ExtraMemory() const override; + + private: +#if defined(ARCH_CPU_X86_64) + CPUContextX86_64 context_arch_; +#elif defined(ARCH_CPU_ARM64) + CPUContextARM64 context_arch_; +#endif + CPUContext context_; + std::vector codes_; + zx_koid_t thread_id_; + zx_vaddr_t exception_address_; + uint32_t exception_; + uint32_t exception_info_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotFuchsia); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_EXCEPTION_SNAPSHOT_FUCHSIA_H_ diff --git a/snapshot/fuchsia/memory_map_fuchsia.cc b/snapshot/fuchsia/memory_map_fuchsia.cc new file mode 100644 index 00000000..b60531d0 --- /dev/null +++ b/snapshot/fuchsia/memory_map_fuchsia.cc @@ -0,0 +1,88 @@ +// 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_fuchsia.h" + +#include + +#include "base/fuchsia/fuchsia_logging.h" +#include "util/numeric/checked_range.h" + +namespace crashpad { + +MemoryMapFuchsia::MemoryMapFuchsia() = default; + +MemoryMapFuchsia::~MemoryMapFuchsia() = default; + +bool MemoryMapFuchsia::Initialize(zx_handle_t process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + // There's no way to know what an appropriate buffer size is before starting. + // Start at a size that should be more than enough for any reasonable process. + map_entries_.resize(4096); + + // Retrieving the maps is racy with new mappings being created, so retry this + // loop up to |tries| times until the number of actual mappings retrieved + // matches those available. + int tries = 5; + for (;;) { + 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); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_MAPS"; + map_entries_.clear(); + return false; + } + if (actual < available && tries-- > 0) { + // Make the buffer slightly larger than |available| to attempt to account + // for the race between here and the next retrieval. + map_entries_.resize(available + 20); + continue; + } + + map_entries_.resize(actual); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; + } +} + +bool MemoryMapFuchsia::FindMappingForAddress(zx_vaddr_t address, + zx_info_maps_t* map) const { + bool found = false; + zx_info_maps_t result = {}; + for (const auto& m : map_entries_) { + CheckedRange range(m.base, m.size); + if (range.ContainsValue(address)) { + if (!found || m.depth > result.depth) { + result = m; + found = true; + } + } + } + + if (found) { + *map = result; + } + return found; +} + +} // namespace crashpad diff --git a/snapshot/fuchsia/memory_map_fuchsia.h b/snapshot/fuchsia/memory_map_fuchsia.h new file mode 100644 index 00000000..88df5695 --- /dev/null +++ b/snapshot/fuchsia/memory_map_fuchsia.h @@ -0,0 +1,57 @@ +// 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_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_ + +#include + +#include + +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +//! \brief A list of mappings in the address space of a Fuchsia process. +class MemoryMapFuchsia { + public: + MemoryMapFuchsia(); + ~MemoryMapFuchsia(); + + //! \brief Initializes this object with information about the mapped memory + //! regions in the given process. + //! + //! \return `true` on success, or `false`, with an error logged. + bool Initialize(zx_handle_t process); + + //! \brief Searches through the previously retrieved memory map for the given + //! address. If found, returns the deepest `zx_info_maps_t` mapping that + //! contains \a address. + //! + //! \param[in] address The address to locate. + //! \param[out] map The `zx_info_maps_t` data corresponding to the address. + //! \return `true` if a mapping for \a address was found, in which case \a map + //! will be filled out, otherwise `false` and \a map will be unchanged. + bool FindMappingForAddress(zx_vaddr_t address, zx_info_maps_t* map) const; + + private: + std::vector map_entries_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(MemoryMapFuchsia); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_MEMORY_MAP_FUCHSIA_H_ diff --git a/snapshot/fuchsia/process_reader_fuchsia.cc b/snapshot/fuchsia/process_reader_fuchsia.cc new file mode 100644 index 00000000..0a6a84bb --- /dev/null +++ b/snapshot/fuchsia/process_reader_fuchsia.cc @@ -0,0 +1,303 @@ +// 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/process_reader_fuchsia.h" + +#include +#include + +#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 { + +// Based on the thread's SP and the process's memory map, attempts to figure out +// the stack regions for the thread. Fuchsia's C ABI specifies +// https://fuchsia.googlesource.com/zircon/+/master/docs/safestack.md so the +// callstack and locals-that-have-their-address-taken are in two different +// stacks. +void GetStackRegions( + const zx_thread_state_general_regs_t& regs, + const MemoryMapFuchsia& memory_map, + std::vector>* stack_regions) { + stack_regions->clear(); + + uint64_t sp; +#if defined(ARCH_CPU_X86_64) + sp = regs.rsp; +#elif defined(ARCH_CPU_ARM64) + sp = regs.sp; +#else +#error Port +#endif + + zx_info_maps_t range_with_sp; + if (!memory_map.FindMappingForAddress(sp, &range_with_sp)) { + LOG(ERROR) << "stack pointer not found in mapping"; + return; + } + + if (range_with_sp.type != ZX_INFO_MAPS_TYPE_MAPPING) { + LOG(ERROR) << "stack range has unexpected type, continuing anyway"; + } + + if (range_with_sp.u.mapping.mmu_flags & ZX_VM_FLAG_PERM_EXECUTE) { + LOG(ERROR) + << "stack range is unexpectedly marked executable, continuing anyway"; + } + + stack_regions->push_back( + CheckedRange(range_with_sp.base, range_with_sp.size)); + + // TODO(scottmg): https://crashpad.chromium.org/bug/196, once the retrievable + // registers include FS and similar for ARM, retrieve the region for the + // unsafe part of the stack too. +} + +} // namespace + +ProcessReaderFuchsia::Module::Module() = default; + +ProcessReaderFuchsia::Module::~Module() = default; + +ProcessReaderFuchsia::Thread::Thread() = default; + +ProcessReaderFuchsia::Thread::~Thread() = default; + +ProcessReaderFuchsia::ProcessReaderFuchsia() = default; + +ProcessReaderFuchsia::~ProcessReaderFuchsia() = default; + +bool ProcessReaderFuchsia::Initialize(zx_handle_t process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + process_ = process; + + process_memory_.reset(new ProcessMemoryFuchsia()); + process_memory_->Initialize(process_); + + memory_map_.Initialize(process_); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +const std::vector& +ProcessReaderFuchsia::Modules() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (!initialized_modules_) { + InitializeModules(); + } + + return modules_; +} + +const std::vector& +ProcessReaderFuchsia::Threads() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (!initialized_threads_) { + InitializeThreads(); + } + + return threads_; +} + +void ProcessReaderFuchsia::InitializeModules() { + DCHECK(!initialized_modules_); + DCHECK(modules_.empty()); + + initialized_modules_ = true; + + // TODO(scottmg): does some of this, but doesn't + // expose any of the data that's necessary to fill out a Module after it + // 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)); + if (status != ZX_OK || debug_address == 0) { + LOG(ERROR) << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR"; + return; + } + + constexpr auto k_r_debug_map_offset = offsetof(r_debug, r_map); + uintptr_t map; + if (!process_memory_->Read( + debug_address + k_r_debug_map_offset, sizeof(map), &map)) { + LOG(ERROR) << "read link_map"; + return; + } + + int i = 0; + constexpr int kMaxDso = 1000; // Stop after an unreasonably large number. + while (map != 0) { + if (++i >= kMaxDso) { + LOG(ERROR) << "possibly circular dso list, terminating"; + return; + } + + constexpr auto k_link_map_addr_offset = offsetof(link_map, l_addr); + zx_vaddr_t base; + if (!process_memory_->Read( + map + k_link_map_addr_offset, sizeof(base), &base)) { + LOG(ERROR) << "Read base"; + // Could theoretically continue here, but realistically if any part of + // link_map fails to read, things are looking bad, so just abort. + break; + } + + constexpr auto k_link_map_next_offset = offsetof(link_map, l_next); + zx_vaddr_t next; + if (!process_memory_->Read( + map + k_link_map_next_offset, sizeof(next), &next)) { + LOG(ERROR) << "Read next"; + break; + } + + constexpr auto k_link_map_name_offset = offsetof(link_map, l_name); + zx_vaddr_t name_address; + if (!process_memory_->Read(map + k_link_map_name_offset, + sizeof(name_address), + &name_address)) { + LOG(ERROR) << "Read name address"; + break; + } + + std::string dsoname; + if (!process_memory_->ReadCString(name_address, &dsoname)) { + // In this case, it could be reasonable to continue on to the next module + // as this data isn't strictly in the link_map. + 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 "" to "libzircon.so" so that symbol + // resolution works correctly. + if (dsoname == "") { + dsoname = "libzircon.so"; + } + + Module module; + if (dsoname.empty()) { + module.name = app_name; + module.type = ModuleSnapshot::kModuleTypeExecutable; + } else { + module.name = dsoname; + // TODO(scottmg): Handle kModuleTypeDynamicLoader. + module.type = ModuleSnapshot::kModuleTypeSharedLibrary; + } + + std::unique_ptr reader(new ElfImageReader()); + + std::unique_ptr 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)); + + reader->Initialize(*process_memory_ranges_.back(), base); + module.reader = reader.get(); + module_readers_.push_back(std::move(reader)); + modules_.push_back(module); + + map = next; + } +} + +void ProcessReaderFuchsia::InitializeThreads() { + DCHECK(!initialized_threads_); + DCHECK(threads_.empty()); + + initialized_threads_ = true; + + std::vector thread_koids = + GetChildKoids(process_, ZX_INFO_PROCESS_THREADS); + std::vector thread_handles = + GetHandlesForChildKoids(process_, thread_koids); + DCHECK_EQ(thread_koids.size(), thread_handles.size()); + + for (size_t i = 0; i < thread_handles.size(); ++i) { + Thread thread; + thread.id = thread_koids[i]; + + 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)); + if (status != ZX_OK) { + ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME"; + } else { + thread.name.assign(name); + } + + 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); + 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, + ®s, + sizeof(regs)); + if (status != ZX_OK) { + ZX_LOG(WARNING, status) << "zx_thread_read_state"; + } else { + thread.general_registers = regs; + + GetStackRegions(regs, memory_map_, &thread.stack_regions); + } + } + + threads_.push_back(thread); + } +} + +} // namespace crashpad diff --git a/snapshot/fuchsia/process_reader_fuchsia.h b/snapshot/fuchsia/process_reader_fuchsia.h new file mode 100644 index 00000000..2d0878c3 --- /dev/null +++ b/snapshot/fuchsia/process_reader_fuchsia.h @@ -0,0 +1,134 @@ +// 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_PROCESS_READER_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "build/build_config.h" +#include "snapshot/elf/elf_image_reader.h" +#include "snapshot/fuchsia/memory_map_fuchsia.h" +#include "snapshot/module_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/numeric/checked_range.h" +#include "util/process/process_memory_fuchsia.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief Accesses information about another process, identified by a Fuchsia +//! process. +class ProcessReaderFuchsia { + public: + //! \brief Contains information about a module loaded into a process. + struct Module { + Module(); + ~Module(); + + //! \brief The `ZX_PROP_NAME` of the module. + std::string name; + + //! \brief An image reader for the module. + //! + //! The lifetime of this ElfImageReader is scoped to the lifetime of the + //! ProcessReaderFuchsia that created it. + //! + //! This field may be `nullptr` if a reader could not be created for the + //! module. + ElfImageReader* reader; + + //! \brief The module's type. + ModuleSnapshot::ModuleType type = ModuleSnapshot::kModuleTypeUnknown; + }; + + //! \brief Contains information about a thread that belongs to a process. + struct Thread { + Thread(); + ~Thread(); + + //! \brief The kernel identifier for the thread. + zx_koid_t id = ZX_KOID_INVALID; + + //! \brief The state of the thread, the `ZX_THREAD_STATE_*` value or `-1` if + //! the value could not be retrieved. + uint32_t state = -1; + + //! \brief The `ZX_PROP_NAME` property of the thread. This may be empty. + std::string name; + + //! \brief The raw architecture-specific `zx_thread_state_general_regs_t` as + //! returned by `zx_thread_read_state()`. + zx_thread_state_general_regs_t general_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. + std::vector> stack_regions; + }; + + ProcessReaderFuchsia(); + ~ProcessReaderFuchsia(); + + //! \brief Initializes this object. This method must be called before any + //! other. + //! + //! \param[in] process A process handle with permissions to read properties + //! and memory from the target process. + //! + //! \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); + + //! \return The modules loaded in the process. The first element (at index + //! `0`) corresponds to the main executable. + const std::vector& Modules(); + + //! \return The threads that are in the process. + const std::vector& Threads(); + + //! \brief Return a memory reader for the target process. + ProcessMemory* Memory() { return process_memory_.get(); } + + private: + //! Performs lazy initialization of the \a modules_ vector on behalf of + //! Modules(). + void InitializeModules(); + + //! Performs lazy initialization of the \a threads_ vector on behalf of + //! Threads(). + void InitializeThreads(); + + std::vector modules_; + std::vector threads_; + std::vector> module_readers_; + std::vector> process_memory_ranges_; + std::unique_ptr process_memory_; + MemoryMapFuchsia memory_map_; + zx_handle_t process_; + bool initialized_modules_ = false; + bool initialized_threads_ = false; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessReaderFuchsia); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_READER_H_ diff --git a/snapshot/fuchsia/process_reader_fuchsia_test.cc b/snapshot/fuchsia/process_reader_fuchsia_test.cc new file mode 100644 index 00000000..546d8290 --- /dev/null +++ b/snapshot/fuchsia/process_reader_fuchsia_test.cc @@ -0,0 +1,176 @@ +// 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/process_reader_fuchsia.h" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "test/test_paths.h" +#include "util/fuchsia/scoped_task_suspend.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(ProcessReaderFuchsia, SelfBasic) { + ProcessReaderFuchsia process_reader; + ASSERT_TRUE(process_reader.Initialize(zx_process_self())); + + static constexpr char kTestMemory[] = "Some test memory"; + char buffer[arraysize(kTestMemory)]; + ASSERT_TRUE(process_reader.Memory()->Read( + reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); + EXPECT_STREQ(kTestMemory, buffer); + + const auto& modules = process_reader.Modules(); + EXPECT_GT(modules.size(), 0u); + for (const auto& module : modules) { + EXPECT_FALSE(module.name.empty()); + EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); + } + + const auto& threads = process_reader.Threads(); + EXPECT_GT(threads.size(), 0u); + + zx_info_handle_basic_t info; + ASSERT_EQ(zx_object_get_info(zx_thread_self(), + ZX_INFO_HANDLE_BASIC, + &info, + sizeof(info), + nullptr, + nullptr), + ZX_OK); + EXPECT_EQ(threads[0].id, info.koid); + EXPECT_EQ(threads[0].state, ZX_THREAD_STATE_RUNNING); + EXPECT_EQ(threads[0].name, "initial-thread"); +} + +constexpr char kTestMemory[] = "Read me from another process"; + +CRASHPAD_CHILD_TEST_MAIN(ProcessReaderBasicChildTestMain) { + zx_vaddr_t addr = reinterpret_cast(kTestMemory); + CheckedWriteFile( + StdioFileHandle(StdioStream::kStandardOutput), &addr, sizeof(addr)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class BasicChildTest : public MultiprocessExec { + public: + BasicChildTest() : MultiprocessExec() { + SetChildTestMainFunction("ProcessReaderBasicChildTestMain"); + } + ~BasicChildTest() {} + + private: + void MultiprocessParent() override { + ProcessReaderFuchsia process_reader; + ASSERT_TRUE(process_reader.Initialize(ChildProcess())); + + zx_vaddr_t addr; + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &addr, sizeof(addr))); + + std::string read_string; + ASSERT_TRUE(process_reader.Memory()->ReadCString(addr, &read_string)); + EXPECT_EQ(read_string, kTestMemory); + } + + DISALLOW_COPY_AND_ASSIGN(BasicChildTest); +}; + +TEST(ProcessReaderFuchsia, ChildBasic) { + BasicChildTest test; + test.Run(); +} + +void* SignalAndSleep(void* arg) { + zx_port_packet_t packet = {}; + packet.type = ZX_PKT_TYPE_USER; + zx_port_queue(*reinterpret_cast(arg), &packet); + zx_nanosleep(ZX_TIME_INFINITE); + return nullptr; +} + +CRASHPAD_CHILD_TEST_MAIN(ProcessReaderChildThreadsTestMain) { + // Create 5 threads with stack sizes of 4096, 8192, ... + zx_handle_t port; + zx_status_t status = zx_port_create(0, &port); + EXPECT_EQ(status, ZX_OK); + + constexpr size_t kNumThreads = 5; + for (size_t i = 0; i < kNumThreads; ++i) { + pthread_attr_t attr; + EXPECT_EQ(pthread_attr_init(&attr), 0); + EXPECT_EQ(pthread_attr_setstacksize(&attr, (i + 1) * 4096), 0); + pthread_t thread; + EXPECT_EQ(pthread_create(&thread, &attr, &SignalAndSleep, &port), 0); + } + + // Wait until all threads are ready. + for (size_t i = 0; i < kNumThreads; ++i) { + zx_port_packet_t packet; + zx_port_wait(port, ZX_TIME_INFINITE, &packet); + } + + char c = ' '; + CheckedWriteFile( + StdioFileHandle(StdioStream::kStandardOutput), &c, sizeof(c)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ThreadsChildTest : public MultiprocessExec { + public: + ThreadsChildTest() : MultiprocessExec() { + SetChildTestMainFunction("ProcessReaderChildThreadsTestMain"); + } + ~ThreadsChildTest() {} + + private: + void MultiprocessParent() override { + char c; + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &c, 1)); + ASSERT_EQ(c, ' '); + + ScopedTaskSuspend suspend(ChildProcess()); + + ProcessReaderFuchsia process_reader; + 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); + } + } + + DISALLOW_COPY_AND_ASSIGN(ThreadsChildTest); +}; + +TEST(ProcessReaderFuchsia, ChildThreads) { + ThreadsChildTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.cc b/snapshot/fuchsia/process_snapshot_fuchsia.cc new file mode 100644 index 00000000..cc66db25 --- /dev/null +++ b/snapshot/fuchsia/process_snapshot_fuchsia.cc @@ -0,0 +1,219 @@ +// 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 "snapshot/fuchsia/process_snapshot_fuchsia.h" + +#include + +#include "base/logging.h" +#include "util/fuchsia/koid_utilities.h" + +namespace crashpad { + +ProcessSnapshotFuchsia::ProcessSnapshotFuchsia() = default; + +ProcessSnapshotFuchsia::~ProcessSnapshotFuchsia() = default; + +bool ProcessSnapshotFuchsia::Initialize(zx_handle_t process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + if (gettimeofday(&snapshot_time_, nullptr) != 0) { + PLOG(ERROR) << "gettimeofday"; + return false; + } + + if (!process_reader_.Initialize(process) || + !memory_range_.Initialize(process_reader_.Memory(), true)) { + return false; + } + + system_.Initialize(&snapshot_time_); + + InitializeThreads(); + InitializeModules(); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessSnapshotFuchsia::InitializeException( + zx_koid_t thread_id, + const zx_exception_report_t& report) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + exception_.reset(new internal::ExceptionSnapshotFuchsia()); + exception_->Initialize(&process_reader_, thread_id, report); + return true; +} + +void ProcessSnapshotFuchsia::GetCrashpadOptions( + CrashpadInfoClientOptions* options) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + CrashpadInfoClientOptions local_options; + + for (const auto& module : modules_) { + CrashpadInfoClientOptions module_options; + module->GetCrashpadOptions(&module_options); + + if (local_options.crashpad_handler_behavior == TriState::kUnset) { + local_options.crashpad_handler_behavior = + module_options.crashpad_handler_behavior; + } + if (local_options.system_crash_reporter_forwarding == TriState::kUnset) { + local_options.system_crash_reporter_forwarding = + module_options.system_crash_reporter_forwarding; + } + if (local_options.gather_indirectly_referenced_memory == TriState::kUnset) { + local_options.gather_indirectly_referenced_memory = + module_options.gather_indirectly_referenced_memory; + local_options.indirectly_referenced_memory_cap = + module_options.indirectly_referenced_memory_cap; + } + + // If non-default values have been found for all options, the loop can end + // early. + if (local_options.crashpad_handler_behavior != TriState::kUnset && + local_options.system_crash_reporter_forwarding != TriState::kUnset && + local_options.gather_indirectly_referenced_memory != TriState::kUnset) { + break; + } + } + + *options = local_options; +} + +pid_t ProcessSnapshotFuchsia::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return GetKoidForHandle(zx_process_self()); +} + +pid_t ProcessSnapshotFuchsia::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 + return 0; +} + +void ProcessSnapshotFuchsia::SnapshotTime(timeval* snapshot_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *snapshot_time = snapshot_time_; +} + +void ProcessSnapshotFuchsia::ProcessStartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): https://crashpad.chromium.org/bug/196. Nothing available. + *start_time = timeval{}; +} + +void ProcessSnapshotFuchsia::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): https://crashpad.chromium.org/bug/196. Nothing available. + *user_time = timeval{}; + *system_time = timeval{}; +} + +void ProcessSnapshotFuchsia::ReportID(UUID* report_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *report_id = report_id_; +} + +void ProcessSnapshotFuchsia::ClientID(UUID* client_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *client_id = client_id_; +} + +const std::map& +ProcessSnapshotFuchsia::AnnotationsSimpleMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return annotations_simple_map_; +} + +const SystemSnapshot* ProcessSnapshotFuchsia::System() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &system_; +} + +std::vector ProcessSnapshotFuchsia::Threads() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; +} + +std::vector ProcessSnapshotFuchsia::Modules() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector modules; + for (const auto& module : modules_) { + modules.push_back(module.get()); + } + return modules; +} + +std::vector ProcessSnapshotFuchsia::UnloadedModules() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // dlclose() never unloads on Fuchsia. ZX-1728 upstream. + return std::vector(); +} + +const ExceptionSnapshot* ProcessSnapshotFuchsia::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_.get(); +} + +std::vector ProcessSnapshotFuchsia::MemoryMap() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ProcessSnapshotFuchsia::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ProcessSnapshotFuchsia::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +void ProcessSnapshotFuchsia::InitializeThreads() { + const std::vector& process_reader_threads = + process_reader_.Threads(); + for (const ProcessReaderFuchsia::Thread& process_reader_thread : + process_reader_threads) { + auto thread = std::make_unique(); + if (thread->Initialize(&process_reader_, process_reader_thread)) { + threads_.push_back(std::move(thread)); + } + } +} + +void ProcessSnapshotFuchsia::InitializeModules() { + for (const ProcessReaderFuchsia::Module& reader_module : + process_reader_.Modules()) { + auto module = + std::make_unique(reader_module.name, + reader_module.reader, + reader_module.type, + &memory_range_); + if (module->Initialize()) { + modules_.push_back(std::move(module)); + } + } +} + +} // namespace crashpad diff --git a/snapshot/fuchsia/process_snapshot_fuchsia.h b/snapshot/fuchsia/process_snapshot_fuchsia.h new file mode 100644 index 00000000..2590ed58 --- /dev/null +++ b/snapshot/fuchsia/process_snapshot_fuchsia.h @@ -0,0 +1,148 @@ +// 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_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_ + +#include +#include +#include + +#include +#include + +#include "base/macros.h" +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/elf/elf_image_reader.h" +#include "snapshot/elf/module_snapshot_elf.h" +#include "snapshot/fuchsia/exception_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_memory_range.h" + +namespace crashpad { + +//! \brief A ProcessSnapshot of a running (or crashed) process running on a +//! Fuchsia system. This class is not yet implemented. +class ProcessSnapshotFuchsia : public ProcessSnapshot { + public: + ProcessSnapshotFuchsia(); + ~ProcessSnapshotFuchsia() override; + + //! \brief Initializes the object. + //! + //! \param[in] process The process handle to create a snapshot from. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(zx_handle_t process); + + //! \brief Initializes the object's exception. + //! + //! This populates the data to be returned by Exception(). The thread + //! identified by \a thread_id must be in an exception. + //! + //! This method must not be called until after a successful call to + //! Initialize(). + //! + //! \param[in] thread_id Koid of the thread which sustained the exception. + //! \param[in] report The `zx_exception_report_t` for the thread which + //! sustained the exception. + //! \return `true` if the exception information could be initialized, `false` + //! otherwise with an appropriate message logged. When this method returns + //! `false`, the ProcessSnapshotFuchsia object’s validity remains + //! unchanged. + bool InitializeException(zx_koid_t thread_id, + const zx_exception_report_t& report); + + //! \brief Returns options from CrashpadInfo structures found in modules in + //! the process. + //! + //! \param[out] options Options set in CrashpadInfo structures in modules in + //! the process. + void GetCrashpadOptions(CrashpadInfoClientOptions* options); + + //! \brief Sets the value to be returned by ReportID(). + //! + //! On Fuchsia, 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; } + + //! \brief Sets the value to be returned by ClientID(). + //! + //! On Fuchsia, 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 AnnotationsSimpleMap(). + //! + //! On Fuchsia, all 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& annotations_simple_map) { + annotations_simple_map_ = annotations_simple_map; + } + + // 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& AnnotationsSimpleMap() + const override; + const SystemSnapshot* System() const override; + std::vector Threads() const override; + std::vector Modules() const override; + std::vector UnloadedModules() const override; + const ExceptionSnapshot* Exception() const override; + std::vector MemoryMap() const override; + std::vector Handles() const override; + std::vector ExtraMemory() const override; + + private: + // Initializes threads_ on behalf of Initialize(). + void InitializeThreads(); + + // Initializes modules_ on behalf of Initialize(). + void InitializeModules(); + + internal::SystemSnapshotFuchsia system_; + std::vector> threads_; + std::vector> modules_; + std::unique_ptr exception_; + ProcessReaderFuchsia process_reader_; + ProcessMemoryRange memory_range_; + std::map annotations_simple_map_; + UUID report_id_; + UUID client_id_; + timeval snapshot_time_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotFuchsia); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_PROCESS_SNAPSHOT_FUCHSIA_H_ diff --git a/snapshot/fuchsia/system_snapshot_fuchsia.cc b/snapshot/fuchsia/system_snapshot_fuchsia.cc new file mode 100644 index 00000000..889817ad --- /dev/null +++ b/snapshot/fuchsia/system_snapshot_fuchsia.cc @@ -0,0 +1,215 @@ +// 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/system_snapshot_fuchsia.h" + +#include + +#include "base/fuchsia/fuchsia_logging.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" +#include "snapshot/posix/timezone.h" + +namespace crashpad { +namespace internal { + +SystemSnapshotFuchsia::SystemSnapshotFuchsia() = default; + +SystemSnapshotFuchsia::~SystemSnapshotFuchsia() = default; + +void SystemSnapshotFuchsia::Initialize(const timeval* snapshot_time) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + snapshot_time_ = snapshot_time; + + // This version string mirrors `uname -a` as written by + // 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"; + +#if defined(ARCH_CPU_X86_64) + static constexpr const char kArch[] = "x86_64"; +#elif defined(ARCH_CPU_ARM64) + static constexpr const char kArch[] = "aarch64"; +#else + static constexpr const char kArch[] = "unknown"; +#endif + os_version_full_ = + base::StringPrintf("Zircon prerelease %s %s", kernel_version, kArch); + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +CPUArchitecture SystemSnapshotFuchsia::GetCPUArchitecture() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + +#if defined(ARCH_CPU_X86_64) + return kCPUArchitectureX86_64; +#elif defined(ARCH_CPU_ARM64) + return kCPUArchitectureARM64; +#else +#error Port +#endif +} + +uint32_t SystemSnapshotFuchsia::CPURevision() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.Revision(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return 0; +#endif +} + +uint8_t SystemSnapshotFuchsia::CPUCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return base::saturated_cast(zx_system_get_num_cpus()); +} + +std::string SystemSnapshotFuchsia::CPUVendor() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.Vendor(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return std::string(); +#endif +} + +void SystemSnapshotFuchsia::CPUFrequency(uint64_t* current_hz, + uint64_t* max_hz) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): https://crashpad.chromium.org/bug/196. + *current_hz = 0; + *max_hz = 0; +} + +uint32_t SystemSnapshotFuchsia::CPUX86Signature() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.Signature(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return 0; +#endif +} + +uint64_t SystemSnapshotFuchsia::CPUX86Features() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.Features(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return 0; +#endif +} + +uint64_t SystemSnapshotFuchsia::CPUX86ExtendedFeatures() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.ExtendedFeatures(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return 0; +#endif +} + +uint32_t SystemSnapshotFuchsia::CPUX86Leaf7Features() const { +#if defined(ARCH_CPU_X86_64) + return cpuid_.Leaf7Features(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return 0; +#endif +} + +bool SystemSnapshotFuchsia::CPUX86SupportsDAZ() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.SupportsDAZ(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return false; +#endif +} + +SystemSnapshot::OperatingSystem SystemSnapshotFuchsia::GetOperatingSystem() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return kOperatingSystemFuchsia; +} + +bool SystemSnapshotFuchsia::OSServer() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return false; +} + +void SystemSnapshotFuchsia::OSVersion(int* major, + int* minor, + int* bugfix, + std::string* build) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): https://crashpad.chromium.org/bug/196. There's no version + // available to be reported yet. + *major = 0; + *minor = 0; + *bugfix = 0; + *build = std::string(); +} + +std::string SystemSnapshotFuchsia::OSVersionFull() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return os_version_full_; +} + +std::string SystemSnapshotFuchsia::MachineDescription() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(scottmg): https://crashpad.chromium.org/bug/196. Not yet available, + // upstream ZX-1775. + return std::string(); +} + +bool SystemSnapshotFuchsia::NXEnabled() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_64) + return cpuid_.NXEnabled(); +#else + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196. + return false; +#endif +} + +void SystemSnapshotFuchsia::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_); + + internal::TimeZone(*snapshot_time_, + dst_status, + standard_offset_seconds, + daylight_offset_seconds, + standard_name, + daylight_name); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/fuchsia/system_snapshot_fuchsia.h b/snapshot/fuchsia/system_snapshot_fuchsia.h new file mode 100644 index 00000000..c7cd35dc --- /dev/null +++ b/snapshot/fuchsia/system_snapshot_fuchsia.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_FUCHSIA_SYSTEM_SNAPSHOT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_SYSTEM_SNAPSHOT_FUCHSIA_H_ + +#include + +#include "base/macros.h" +#include "build/build_config.h" +#include "snapshot/system_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" + +#if defined(ARCH_CPU_X86_FAMILY) +#include "snapshot/x86/cpuid_reader.h" +#endif + +namespace crashpad { +namespace internal { + +//! \brief A SystemSnapshot of the running system, when the system runs Fuchsia. +class SystemSnapshotFuchsia final : public SystemSnapshot { + public: + SystemSnapshotFuchsia(); + ~SystemSnapshotFuchsia() override; + + //! \brief Initializes the object. + //! + //! \param[in] snapshot_time The time of the snapshot being taken. + //! + //! This parameter is necessary for TimeZone() to determine whether daylight + //! saving time was in effect at the time the snapshot was taken. Otherwise, + //! it would need to base its determination on the current time, which may be + //! different than the snapshot time for snapshots generated around the + //! daylight saving transition time. + void Initialize(const timeval* snapshot_time); + + // 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_full_; + const timeval* snapshot_time_; // weak +#if defined(ARCH_CPU_X86_FAMILY) + CpuidReader cpuid_; +#endif // ARCH_CPU_X86_FAMILY + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(SystemSnapshotFuchsia); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_SYSTEM_SNAPSHOT_FUCHSIA_H_ diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.cc b/snapshot/fuchsia/thread_snapshot_fuchsia.cc new file mode 100644 index 00000000..03055d8c --- /dev/null +++ b/snapshot/fuchsia/thread_snapshot_fuchsia.cc @@ -0,0 +1,105 @@ +// 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/thread_snapshot_fuchsia.h" + +#include "base/logging.h" +#include "snapshot/fuchsia/cpu_context_fuchsia.h" + +namespace crashpad { +namespace internal { + +ThreadSnapshotFuchsia::ThreadSnapshotFuchsia() + : ThreadSnapshot(), + context_arch_(), + context_(), + stack_(), + thread_id_(ZX_KOID_INVALID), + thread_specific_data_address_(0), + initialized_() {} + +ThreadSnapshotFuchsia::~ThreadSnapshotFuchsia() {} + +bool ThreadSnapshotFuchsia::Initialize( + ProcessReaderFuchsia* process_reader, + const ProcessReaderFuchsia::Thread& thread) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + +#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); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arch_; + // TODO(scottmg): Implement context capture for arm64. +#else +#error Port. +#endif + + if (thread.stack_regions.empty()) { + stack_.Initialize(process_reader, 0, 0); + } else { + stack_.Initialize(process_reader, + thread.stack_regions[0].base(), + thread.stack_regions[0].size()); + // TODO(scottmg): Handle split stack by adding other parts to ExtraMemory(). + } + + thread_id_ = thread.id; + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +const CPUContext* ThreadSnapshotFuchsia::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &context_; +} + +const MemorySnapshot* ThreadSnapshotFuchsia::Stack() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &stack_; +} + +uint64_t ThreadSnapshotFuchsia::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_id_; +} + +int ThreadSnapshotFuchsia::SuspendCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // There is not (currently) a suspend count for threads on Fuchsia. + return 0; +} + +int ThreadSnapshotFuchsia::Priority() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // There is not (currently) thread priorities on Fuchsia. + return 0; +} + +uint64_t ThreadSnapshotFuchsia::ThreadSpecificDataAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_specific_data_address_; +} + +std::vector ThreadSnapshotFuchsia::ExtraMemory() const { + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/fuchsia/thread_snapshot_fuchsia.h b/snapshot/fuchsia/thread_snapshot_fuchsia.h new file mode 100644 index 00000000..db975976 --- /dev/null +++ b/snapshot/fuchsia/thread_snapshot_fuchsia.h @@ -0,0 +1,81 @@ +// 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_THREAD_SNAPSHOT_FUCHSIA_H_ +#define CRASHPAD_SNAPSHOT_FUCHSIA_THREAD_SNAPSHOT_FUCHSIA_H_ + +#include +#include + +#include "base/macros.h" +#include "build/build_config.h" +#include "snapshot/cpu_context.h" +#include "snapshot/fuchsia/process_reader_fuchsia.h" +#include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" +#include "snapshot/thread_snapshot.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A ThreadSnapshot of a thread on a Fuchsia system. +class ThreadSnapshotFuchsia final : public ThreadSnapshot { + public: + ThreadSnapshotFuchsia(); + ~ThreadSnapshotFuchsia() override; + + //! \brief Initializes the object. + //! + //! \param[in] process_reader A ProcessReaderFuchsia for the process + //! containing the thread. + //! \param[in] thread The thread within the ProcessReaderFuchsia for + //! which the snapshot should be created. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! a message logged. + bool Initialize(ProcessReaderFuchsia* process_reader, + const ProcessReaderFuchsia::Thread& thread); + + // 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 ExtraMemory() const override; + + private: +#if defined(ARCH_CPU_X86_64) + CPUContextX86_64 context_arch_; +#elif defined(ARCH_CPU_ARM64) + CPUContextARM64 context_arch_; +#else +#error Port. +#endif + CPUContext context_; + MemorySnapshotGeneric stack_; + zx_koid_t thread_id_; + zx_vaddr_t thread_specific_data_address_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotFuchsia); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_FUCHSIA_THREAD_SNAPSHOT_FUCHSIA_H_ diff --git a/snapshot/handle_snapshot.h b/snapshot/handle_snapshot.h index 1346b41d..eee7e7e4 100644 --- a/snapshot/handle_snapshot.h +++ b/snapshot/handle_snapshot.h @@ -38,7 +38,7 @@ struct HandleSnapshot { //! \brief The ACCESS_MASK for the handle in this process. //! //! See - //! http://blogs.msdn.com/b/openspecification/archive/2010/04/01/about-the-access-mask-structure.aspx + //! https://blogs.msdn.microsoft.com/openspecification/2010/04/01/about-the-access_mask-structure/ //! for more information. uint32_t granted_access; diff --git a/snapshot/hash_types_test.cc b/snapshot/hash_types_test.cc new file mode 100644 index 00000000..436323b2 --- /dev/null +++ b/snapshot/hash_types_test.cc @@ -0,0 +1,17 @@ +// 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. + +int main() { + return 0; +} diff --git a/snapshot/linux/cpu_context_linux.cc b/snapshot/linux/cpu_context_linux.cc index 26c65be1..ebf9d7e9 100644 --- a/snapshot/linux/cpu_context_linux.cc +++ b/snapshot/linux/cpu_context_linux.cc @@ -77,6 +77,8 @@ void InitializeCPUContextX86_NoFloatingPoint( CPUContextX86* context) { SET_GPRS32(); + memset(&context->fxsave, 0, sizeof(context->fxsave)); + context->dr0 = 0; context->dr1 = 0; context->dr2 = 0; @@ -151,9 +153,112 @@ void InitializeCPUContextX86_64(const SignalThreadContext64& thread_context, context->dr6 = 0; context->dr7 = 0; } -#else -#error Port. -#endif // ARCH_CPU_X86_FAMILY || DOXYGEN + +void InitializeCPUContextX86_64_NoFloatingPoint( + const SignalThreadContext64& thread_context, + CPUContextX86_64* context) { + SET_GPRS64(); + + memset(&context->fxsave, 0, sizeof(context->fxsave)); + + context->dr0 = 0; + context->dr1 = 0; + context->dr2 = 0; + context->dr3 = 0; + context->dr4 = 0; + context->dr5 = 0; + context->dr6 = 0; + context->dr7 = 0; +} + +#elif defined(ARCH_CPU_ARM_FAMILY) + +void InitializeCPUContextARM(const ThreadContext::t32_t& thread_context, + const FloatContext::f32_t& float_context, + CPUContextARM* context) { + static_assert(sizeof(context->regs) == sizeof(thread_context.regs), + "registers size mismatch"); + memcpy(&context->regs, &thread_context.regs, sizeof(context->regs)); + context->fp = thread_context.fp; + context->ip = thread_context.ip; + context->sp = thread_context.sp; + context->lr = thread_context.lr; + context->pc = thread_context.pc; + context->cpsr = thread_context.cpsr; + + static_assert(sizeof(context->vfp_regs) == sizeof(float_context.vfp), + "vfp size mismatch"); + context->have_vfp_regs = float_context.have_vfp; + if (float_context.have_vfp) { + memcpy(&context->vfp_regs, &float_context.vfp, sizeof(context->vfp_regs)); + } + + static_assert(sizeof(context->fpa_regs) == sizeof(float_context.fpregs), + "fpregs size mismatch"); + context->have_fpa_regs = float_context.have_fpregs; + if (float_context.have_fpregs) { + memcpy( + &context->fpa_regs, &float_context.fpregs, sizeof(context->fpa_regs)); + } +} + +void InitializeCPUContextARM_NoFloatingPoint( + const SignalThreadContext32& thread_context, + CPUContextARM* context) { + static_assert(sizeof(context->regs) == sizeof(thread_context.regs), + "registers size mismatch"); + memcpy(&context->regs, &thread_context.regs, sizeof(context->regs)); + context->fp = thread_context.fp; + context->ip = thread_context.ip; + context->sp = thread_context.sp; + context->lr = thread_context.lr; + context->pc = thread_context.pc; + context->cpsr = thread_context.cpsr; + + memset(&context->fpa_regs, 0, sizeof(context->fpa_regs)); + memset(&context->vfp_regs, 0, sizeof(context->vfp_regs)); + context->have_fpa_regs = false; + context->have_vfp_regs = false; +} + +void InitializeCPUContextARM64(const ThreadContext::t64_t& thread_context, + const FloatContext::f64_t& float_context, + CPUContextARM64* context) { + InitializeCPUContextARM64_NoFloatingPoint(thread_context, context); + + static_assert(sizeof(context->fpsimd) == sizeof(float_context.vregs), + "fpsimd context size mismatch"); + memcpy(context->fpsimd, float_context.vregs, sizeof(context->fpsimd)); + context->fpsr = float_context.fpsr; + context->fpcr = float_context.fpcr; +} + +void InitializeCPUContextARM64_NoFloatingPoint( + const ThreadContext::t64_t& thread_context, + CPUContextARM64* context) { + static_assert(sizeof(context->regs) == sizeof(thread_context.regs), + "gpr context size mismtach"); + memcpy(context->regs, thread_context.regs, sizeof(context->regs)); + context->sp = thread_context.sp; + context->pc = thread_context.pc; + context->pstate = thread_context.pstate; + + memset(&context->fpsimd, 0, sizeof(context->fpsimd)); + context->fpsr = 0; + context->fpcr = 0; +} + +void InitializeCPUContextARM64_OnlyFPSIMD( + const SignalFPSIMDContext& float_context, + CPUContextARM64* context) { + static_assert(sizeof(context->fpsimd) == sizeof(float_context.vregs), + "fpsimd context size mismatch"); + memcpy(context->fpsimd, float_context.vregs, sizeof(context->fpsimd)); + context->fpsr = float_context.fpsr; + context->fpcr = float_context.fpcr; +} + +#endif // ARCH_CPU_X86_FAMILY } // namespace internal } // namespace crashpad diff --git a/snapshot/linux/cpu_context_linux.h b/snapshot/linux/cpu_context_linux.h index 8a2e812c..37fbc432 100644 --- a/snapshot/linux/cpu_context_linux.h +++ b/snapshot/linux/cpu_context_linux.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_CPU_CONTEXT_LINUX_H_ -#define CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_CPU_CONTEXT_LINUX_H_ +#ifndef CRASHPAD_SNAPSHOT_LINUX_CPU_CONTEXT_LINUX_H_ +#define CRASHPAD_SNAPSHOT_LINUX_CPU_CONTEXT_LINUX_H_ #include "build/build_config.h" #include "snapshot/cpu_context.h" @@ -44,8 +44,7 @@ void InitializeCPUContextX86(const SignalThreadContext32& thread_context, //! \brief Initializes GPR and debug state in a CPUContextX86 from a native //! signal context structure on Linux. //! -//! Floating point state is not initialized. Debug registers are initialized to -//! zero. +//! Floating point state and debug registers are initialized to zero. //! //! \param[in] thread_context The native thread context. //! \param[out] context The CPUContextX86 structure to initialize. @@ -53,7 +52,7 @@ void InitializeCPUContextX86_NoFloatingPoint( const SignalThreadContext32& thread_context, CPUContextX86* context); -// \{ +//! \{ //! \brief Initializes a CPUContextX86_64 structure from native context //! structures on Linux. //! @@ -68,11 +67,114 @@ void InitializeCPUContextX86_64(const SignalThreadContext64& thread_context, const SignalFloatContext64& float_context, CPUContextX86_64* context); //! \} -#else -#error Port. // TODO(jperaza): ARM + +//! \brief Initializes GPR and debug state in a CPUContextX86_64 from a native +//! signal context structure on Linux. +//! +//! Floating point state and debug registers are initialized to zero. +//! +//! \param[in] thread_context The native thread context. +//! \param[out] context The CPUContextX86_64 structure to initialize. +void InitializeCPUContextX86_64_NoFloatingPoint( + const SignalThreadContext64& thread_context, + CPUContextX86_64* context); + #endif // ARCH_CPU_X86_FAMILY || DOXYGEN +#if defined(ARCH_CPU_ARM_FAMILY) || DOXYGEN + +//! \brief Initializes a CPUContextARM structure from native context structures +//! on Linux. +//! +//! \param[in] thread_context The native thread context. +//! \param[in] float_context The native float context. +//! \param[out] context The CPUContextARM structure to initialize. +void InitializeCPUContextARM(const ThreadContext::t32_t& thread_context, + const FloatContext::f32_t& float_context, + CPUContextARM* context); + +//! \brief Initializes GPR state in a CPUContextARM from a native signal context +//! structure on Linux. +//! +//! Floating point state is initialized to zero. +//! +//! \param[in] thread_context The native thread context. +//! \param[out] context The CPUContextARM structure to initialize. +void InitializeCPUContextARM_NoFloatingPoint( + const SignalThreadContext32& thread_context, + CPUContextARM* context); + +//! \brief Initializes a CPUContextARM64 structure from native context +//! structures on Linux. +//! +//! \param[in] thread_context The native thread context. +//! \param[in] float_context The native float context. +//! \param[out] context The CPUContextARM64 structure to initialize. +void InitializeCPUContextARM64(const ThreadContext::t64_t& thread_context, + const FloatContext::f64_t& float_context, + CPUContextARM64* context); + +//! \brief Initializes GPR state in a CPUContextARM64 from a native context +//! structure on Linux. +//! +//! Floating point state is initialized to zero. +//! +//! \param[in] thread_context The native thread context. +//! \param[out] context The CPUContextARM64 structure to initialize. +void InitializeCPUContextARM64_NoFloatingPoint( + const ThreadContext::t64_t& thread_context, + CPUContextARM64* context); + +//! \brief Initializes FPSIMD state in a CPUContextARM64 from a native fpsimd +//! signal context structure on Linux. +//! +//! General purpose registers are not initialized. +//! +//! \param[in] float_context The native fpsimd context. +//! \param[out] context The CPUContextARM64 structure to initialize. +void InitializeCPUContextARM64_OnlyFPSIMD( + const SignalFPSIMDContext& float_context, + CPUContextARM64* context); + +#endif // ARCH_CPU_ARM_FAMILY || DOXYGEN + +#if defined(ARCH_CPU_MIPS_FAMILY) || DOXYGEN + +//! \brief Initializes a CPUContextMIPS structure from native context +//! structures on Linux. +//! +//! This function has template specializations for MIPSEL and MIPS64EL +//! architecture contexts, using ContextTraits32 or ContextTraits64 as template +//! parameter, respectively. +//! +//! \param[in] thread_context The native thread context. +//! \param[in] float_context The native float context. +//! \param[out] context The CPUContextMIPS structure to initialize. +template +void InitializeCPUContextMIPS( + const typename Traits::SignalThreadContext& thread_context, + const typename Traits::SignalFloatContext& float_context, + typename Traits::CPUContext* context) { + static_assert(sizeof(context->regs) == sizeof(thread_context.regs), + "registers size mismatch"); + static_assert(sizeof(context->fpregs) == sizeof(float_context.fpregs), + "fp registers size mismatch"); + memcpy(&context->regs, &thread_context.regs, sizeof(context->regs)); + context->mdlo = thread_context.lo; + context->mdhi = thread_context.hi; + context->cp0_epc = thread_context.cp0_epc; + context->cp0_badvaddr = thread_context.cp0_badvaddr; + context->cp0_status = thread_context.cp0_status; + context->cp0_cause = thread_context.cp0_cause; + + 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 + } // namespace internal } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_CPU_CONTEXT_LINUX_H_ +#endif // CRASHPAD_SNAPSHOT_LINUX_CPU_CONTEXT_LINUX_H_ diff --git a/snapshot/linux/debug_rendezvous.cc b/snapshot/linux/debug_rendezvous.cc index e47fc0c6..e27768dd 100644 --- a/snapshot/linux/debug_rendezvous.cc +++ b/snapshot/linux/debug_rendezvous.cc @@ -63,7 +63,7 @@ bool ReadLinkEntry(const ProcessMemoryRange& memory, std::string name; if (!memory.ReadCStringSizeLimited(entry.l_name, 4096, &name)) { - return false; + name.clear(); } entry_out->load_bias = entry.l_addr; diff --git a/snapshot/linux/debug_rendezvous_test.cc b/snapshot/linux/debug_rendezvous_test.cc index 91f2cede..f9920ab4 100644 --- a/snapshot/linux/debug_rendezvous_test.cc +++ b/snapshot/linux/debug_rendezvous_test.cc @@ -27,9 +27,11 @@ #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/elf/elf_image_reader.h" +#include "test/linux/fake_ptrace_connection.h" #include "test/multiprocess.h" #include "util/linux/address_types.h" #include "util/linux/auxiliary_vector.h" +#include "util/linux/direct_ptrace_connection.h" #include "util/linux/memory_map.h" #include "util/process/process_memory_linux.h" #include "util/process/process_memory_range.h" @@ -57,18 +59,18 @@ int AndroidRuntimeAPI() { } #endif // OS_ANDROID -void TestAgainstTarget(pid_t pid, bool is_64_bit) { +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 // get the address, but Android does not. AuxiliaryVector aux; - ASSERT_TRUE(aux.Initialize(pid, is_64_bit)); + ASSERT_TRUE(aux.Initialize(connection)); LinuxVMAddress phdrs; ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs)); MemoryMap mappings; - ASSERT_TRUE(mappings.Initialize(pid)); + ASSERT_TRUE(mappings.Initialize(connection)); const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); @@ -77,9 +79,9 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) { LinuxVMAddress elf_address = exe_mapping->range.Base(); ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); + ASSERT_TRUE(memory.Initialize(connection->GetProcessID())); ProcessMemoryRange range; - ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + ASSERT_TRUE(range.Initialize(&memory, connection->Is64Bit())); ElfImageReader exe_reader; ASSERT_TRUE(exe_reader.Initialize(range, elf_address)); @@ -107,7 +109,7 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) { // glibc's loader does not set the name for the executable. EXPECT_TRUE(debug.Executable()->name.empty()); CheckedLinuxAddressRange exe_range( - is_64_bit, exe_reader.Address(), exe_reader.Size()); + connection->Is64Bit(), exe_reader.Address(), exe_reader.Size()); EXPECT_TRUE(exe_range.ContainsValue(debug.Executable()->dynamic_array)); #endif // OS_ANDROID @@ -179,19 +181,16 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) { } CheckedLinuxAddressRange module_range( - is_64_bit, module_reader.Address(), module_reader.Size()); + connection->Is64Bit(), module_reader.Address(), module_reader.Size()); EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array)); } } TEST(DebugRendezvous, Self) { -#if defined(ARCH_CPU_64_BITS) - constexpr bool is_64_bit = true; -#else - constexpr bool is_64_bit = false; -#endif + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); - TestAgainstTarget(getpid(), is_64_bit); + TestAgainstTarget(&connection); } class ChildTest : public Multiprocess { @@ -201,13 +200,10 @@ class ChildTest : public Multiprocess { private: void MultiprocessParent() { -#if defined(ARCH_CPU_64_BITS) - constexpr bool is_64_bit = true; -#else - constexpr bool is_64_bit = false; -#endif + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildPID())); - TestAgainstTarget(ChildPID(), is_64_bit); + TestAgainstTarget(&connection); } void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); } diff --git a/snapshot/linux/exception_snapshot_linux.cc b/snapshot/linux/exception_snapshot_linux.cc index 1fcbced4..4256f942 100644 --- a/snapshot/linux/exception_snapshot_linux.cc +++ b/snapshot/linux/exception_snapshot_linux.cc @@ -18,7 +18,7 @@ #include "base/logging.h" #include "snapshot/linux/cpu_context_linux.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include "snapshot/linux/signal_context.h" #include "util/linux/traits.h" #include "util/misc/reinterpret_bytes.h" @@ -41,9 +41,10 @@ ExceptionSnapshotLinux::ExceptionSnapshotLinux() ExceptionSnapshotLinux::~ExceptionSnapshotLinux() {} #if defined(ARCH_CPU_X86_FAMILY) + template <> bool ExceptionSnapshotLinux::ReadContext( - ProcessReader* reader, + ProcessReaderLinux* reader, LinuxVMAddress context_address) { UContext ucontext; if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) { @@ -54,29 +55,41 @@ bool ExceptionSnapshotLinux::ReadContext( context_.architecture = kCPUArchitectureX86; context_.x86 = &context_union_.x86; - if (ucontext.fprs.magic == X86_FXSR_MAGIC) { - if (!reader->Memory()->Read(context_address + - offsetof(UContext, fprs) + - offsetof(SignalFloatContext32, fxsave), - sizeof(CPUContextX86::Fxsave), - &context_.x86->fxsave)) { + if (!ucontext.mcontext.fpptr) { + InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs, + context_.x86); + return true; + } + + SignalFloatContext32 fprs; + if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) { + LOG(ERROR) << "Couldn't read float context"; + return false; + } + + if (fprs.magic == X86_FXSR_MAGIC) { + InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs, + context_.x86); + if (!reader->Memory()->Read( + ucontext.mcontext.fpptr + offsetof(SignalFloatContext32, fxsave), + sizeof(CPUContextX86::Fxsave), + &context_.x86->fxsave)) { LOG(ERROR) << "Couldn't read fxsave"; return false; } - InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs, - context_.x86); - + } else if (fprs.magic == 0xffff) { + InitializeCPUContextX86(ucontext.mcontext.gprs, fprs, context_.x86); } else { - DCHECK_EQ(ucontext.fprs.magic, 0xffff); - InitializeCPUContextX86( - ucontext.mcontext.gprs, ucontext.fprs, context_.x86); + LOG(ERROR) << "unexpected magic 0x" << std::hex << fprs.magic; + return false; } + return true; } template <> bool ExceptionSnapshotLinux::ReadContext( - ProcessReader* reader, + ProcessReaderLinux* reader, LinuxVMAddress context_address) { UContext ucontext; if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) { @@ -87,13 +100,232 @@ bool ExceptionSnapshotLinux::ReadContext( context_.architecture = kCPUArchitectureX86_64; context_.x86_64 = &context_union_.x86_64; - InitializeCPUContextX86_64( - ucontext.mcontext.gprs, ucontext.fprs, context_.x86_64); + if (!ucontext.mcontext.fpptr) { + InitializeCPUContextX86_64_NoFloatingPoint(ucontext.mcontext.gprs, + context_.x86_64); + return true; + } + + SignalFloatContext64 fprs; + if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) { + LOG(ERROR) << "Couldn't read float context"; + return false; + } + + InitializeCPUContextX86_64(ucontext.mcontext.gprs, fprs, context_.x86_64); return true; } + +#elif defined(ARCH_CPU_ARM_FAMILY) + +template <> +bool ExceptionSnapshotLinux::ReadContext( + ProcessReaderLinux* reader, + LinuxVMAddress context_address) { + context_.architecture = kCPUArchitectureARM; + context_.arm = &context_union_.arm; + + CPUContextARM* dest_context = context_.arm; + ProcessMemory* memory = reader->Memory(); + + LinuxVMAddress gprs_address = + context_address + offsetof(UContext, mcontext32) + + offsetof(ContextTraits32::MContext32, gprs); + + SignalThreadContext32 thread_context; + if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) { + LOG(ERROR) << "Couldn't read gprs"; + return false; + } + InitializeCPUContextARM_NoFloatingPoint(thread_context, dest_context); + + LinuxVMAddress reserved_address = + context_address + offsetof(UContext, reserved); + if ((reserved_address & 7) != 0) { + LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address; + return false; + } + + constexpr VMSize kMaxContextSpace = 1024; + + ProcessMemoryRange range; + if (!range.Initialize(memory, false, reserved_address, kMaxContextSpace)) { + return false; + } + + do { + CoprocessorContextHead head; + if (!range.Read(reserved_address, sizeof(head), &head)) { + LOG(ERROR) << "missing context terminator"; + return false; + } + reserved_address += sizeof(head); + + switch (head.magic) { + case VFP_MAGIC: + if (head.size != sizeof(SignalVFPContext) + sizeof(head)) { + LOG(ERROR) << "unexpected vfp context size " << head.size; + return false; + } + static_assert( + sizeof(SignalVFPContext::vfp) == sizeof(dest_context->vfp_regs), + "vfp context size mismatch"); + if (!range.Read(reserved_address + offsetof(SignalVFPContext, vfp), + sizeof(dest_context->vfp_regs), + &dest_context->vfp_regs)) { + LOG(ERROR) << "Couldn't read vfp"; + return false; + } + dest_context->have_vfp_regs = true; + return true; + + case CRUNCH_MAGIC: + case IWMMXT_MAGIC: + case DUMMY_MAGIC: + reserved_address += head.size - sizeof(head); + continue; + + case 0: + return true; + + default: + LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic; + return false; + } + } while (true); +} + +template <> +bool ExceptionSnapshotLinux::ReadContext( + ProcessReaderLinux* reader, + LinuxVMAddress context_address) { + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_union_.arm64; + + CPUContextARM64* dest_context = context_.arm64; + ProcessMemory* memory = reader->Memory(); + + LinuxVMAddress gprs_address = + context_address + offsetof(UContext, mcontext64) + + offsetof(ContextTraits64::MContext64, gprs); + + ThreadContext::t64_t thread_context; + if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) { + LOG(ERROR) << "Couldn't read gprs"; + return false; + } + InitializeCPUContextARM64_NoFloatingPoint(thread_context, dest_context); + + LinuxVMAddress reserved_address = + context_address + offsetof(UContext, reserved); + if ((reserved_address & 15) != 0) { + LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address; + return false; + } + + constexpr VMSize kMaxContextSpace = 4096; + + ProcessMemoryRange range; + if (!range.Initialize(memory, true, reserved_address, kMaxContextSpace)) { + return false; + } + + do { + CoprocessorContextHead head; + if (!range.Read(reserved_address, sizeof(head), &head)) { + LOG(ERROR) << "missing context terminator"; + return false; + } + reserved_address += sizeof(head); + + switch (head.magic) { + case FPSIMD_MAGIC: + if (head.size != sizeof(SignalFPSIMDContext) + sizeof(head)) { + LOG(ERROR) << "unexpected fpsimd context size " << head.size; + return false; + } + SignalFPSIMDContext fpsimd; + if (!range.Read(reserved_address, sizeof(fpsimd), &fpsimd)) { + LOG(ERROR) << "Couldn't read fpsimd " << head.size; + return false; + } + InitializeCPUContextARM64_OnlyFPSIMD(fpsimd, dest_context); + return true; + + case ESR_MAGIC: + case EXTRA_MAGIC: + reserved_address += head.size - sizeof(head); + continue; + + case 0: + LOG(WARNING) << "fpsimd not found"; + return true; + + default: + LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic; + return false; + } + } while (true); +} + +#elif defined(ARCH_CPU_MIPS_FAMILY) + +template +static bool ReadContext(ProcessReaderLinux* reader, + LinuxVMAddress context_address, + typename Traits::CPUContext* dest_context) { + ProcessMemory* memory = reader->Memory(); + + LinuxVMAddress gregs_address = context_address + + offsetof(UContext, mcontext) + + offsetof(typename Traits::MContext, gregs); + + typename Traits::SignalThreadContext thread_context; + if (!memory->Read(gregs_address, sizeof(thread_context), &thread_context)) { + LOG(ERROR) << "Couldn't read gregs"; + return false; + } + + LinuxVMAddress fpregs_address = context_address + + offsetof(UContext, mcontext) + + offsetof(typename Traits::MContext, fpregs); + + typename Traits::SignalFloatContext fp_context; + if (!memory->Read(fpregs_address, sizeof(fp_context), &fp_context)) { + LOG(ERROR) << "Couldn't read fpregs"; + return false; + } + + InitializeCPUContextMIPS(thread_context, fp_context, dest_context); + + return true; +} + +template <> +bool ExceptionSnapshotLinux::ReadContext( + ProcessReaderLinux* reader, + LinuxVMAddress context_address) { + context_.architecture = kCPUArchitectureMIPSEL; + context_.mipsel = &context_union_.mipsel; + + return internal::ReadContext( + reader, context_address, context_.mipsel); +} + +template <> +bool ExceptionSnapshotLinux::ReadContext( + ProcessReaderLinux* reader, + LinuxVMAddress context_address) { + context_.architecture = kCPUArchitectureMIPS64EL; + context_.mips64 = &context_union_.mips64; + + return internal::ReadContext( + reader, context_address, context_.mips64); +} + #endif // ARCH_CPU_X86_FAMILY -bool ExceptionSnapshotLinux::Initialize(ProcessReader* process_reader, +bool ExceptionSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, LinuxVMAddress siginfo_address, LinuxVMAddress context_address, pid_t thread_id) { @@ -118,7 +350,7 @@ bool ExceptionSnapshotLinux::Initialize(ProcessReader* process_reader, } template -bool ExceptionSnapshotLinux::ReadSiginfo(ProcessReader* reader, +bool ExceptionSnapshotLinux::ReadSiginfo(ProcessReaderLinux* reader, LinuxVMAddress siginfo_address) { Siginfo siginfo; if (!reader->Memory()->Read(siginfo_address, sizeof(siginfo), &siginfo)) { diff --git a/snapshot/linux/exception_snapshot_linux.h b/snapshot/linux/exception_snapshot_linux.h index a744356d..ea0cd210 100644 --- a/snapshot/linux/exception_snapshot_linux.h +++ b/snapshot/linux/exception_snapshot_linux.h @@ -24,7 +24,7 @@ #include "build/build_config.h" #include "snapshot/cpu_context.h" #include "snapshot/exception_snapshot.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include "snapshot/memory_snapshot.h" #include "util/linux/address_types.h" #include "util/misc/initialization_state_dcheck.h" @@ -41,7 +41,8 @@ class ExceptionSnapshotLinux final : public ExceptionSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the process that received + //! \param[in] process_reader A ProcessReaderLinux for the process that + //! received //! the signal. //! \param[in] siginfo_address The address in the target process' address //! space of the siginfo_t passed to the signal handler. @@ -51,7 +52,7 @@ class ExceptionSnapshotLinux final : public ExceptionSnapshot { //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(ProcessReader* process_reader, + bool Initialize(ProcessReaderLinux* process_reader, LinuxVMAddress siginfo_address, LinuxVMAddress context_address, pid_t thread_id); @@ -68,17 +69,23 @@ class ExceptionSnapshotLinux final : public ExceptionSnapshot { private: template - bool ReadSiginfo(ProcessReader* reader, LinuxVMAddress siginfo_address); + bool ReadSiginfo(ProcessReaderLinux* reader, LinuxVMAddress siginfo_address); template - bool ReadContext(ProcessReader* reader, LinuxVMAddress context_address); + bool ReadContext(ProcessReaderLinux* reader, LinuxVMAddress context_address); -#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_ARM_FAMILY) + CPUContextARM arm; + CPUContextARM64 arm64; +#elif defined(ARCH_CPU_MIPS_FAMILY) + CPUContextMIPS mipsel; + CPUContextMIPS64 mips64; #endif + } context_union_; CPUContext context_; std::vector codes_; uint64_t thread_id_; diff --git a/snapshot/linux/exception_snapshot_linux_test.cc b/snapshot/linux/exception_snapshot_linux_test.cc index bbccf7d8..df9ad9e9 100644 --- a/snapshot/linux/exception_snapshot_linux_test.cc +++ b/snapshot/linux/exception_snapshot_linux_test.cc @@ -21,11 +21,13 @@ #include #include +#include "base/bit_cast.h" #include "base/macros.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "snapshot/cpu_architecture.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" +#include "snapshot/linux/signal_context.h" #include "sys/syscall.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" @@ -52,6 +54,7 @@ using NativeCPUContext = FxsaveUContext; void InitializeContext(NativeCPUContext* context) { context->ucontext.uc_mcontext.gregs[REG_EAX] = 0xabcd1234; + context->ucontext.uc_mcontext.fpregs = &context->ucontext.__fpregs_mem; // glibc and bionic use an unsigned long for status, but the kernel treats // status as two uint16_t, with the upper 16 bits called "magic" which, if set // to X86_FXSR_MAGIC, indicate that an fxsave follows. @@ -76,6 +79,7 @@ using NativeCPUContext = ucontext_t; void InitializeContext(NativeCPUContext* context) { context->uc_mcontext.gregs[REG_RAX] = 0xabcd1234abcd1234; + context->uc_mcontext.fpregs = &context->__fpregs_mem; memset(&context->__fpregs_mem, 44, sizeof(context->__fpregs_mem)); } @@ -92,6 +96,206 @@ void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { reinterpret_cast(&expected.__fpregs_mem)[byte_offset]); } } +#elif defined(ARCH_CPU_ARMEL) +// A native ucontext_t on ARM doesn't have enough regspace (yet) to hold all of +// the different possible coprocessor contexts at once. However, the ABI allows +// it and the native regspace may be expanded in the future. Append some extra +// space so this is testable now. +struct NativeCPUContext { + ucontext_t ucontext; + char extra[1024]; +}; + +struct CrunchContext { + uint32_t mvdx[16][2]; + uint32_t mvax[4][3]; + uint32_t dspsc[2]; +}; + +struct IWMMXTContext { + uint32_t save[38]; +}; + +struct TestCoprocessorContext { + struct { + internal::CoprocessorContextHead head; + CrunchContext context; + } crunch; + struct { + internal::CoprocessorContextHead head; + IWMMXTContext context; + } iwmmxt; + struct { + internal::CoprocessorContextHead head; + IWMMXTContext context; + } dummy; + struct { + internal::CoprocessorContextHead head; + internal::SignalVFPContext context; + } vfp; + internal::CoprocessorContextHead terminator; +}; + +void InitializeContext(NativeCPUContext* context) { + memset(context, 'x', sizeof(*context)); + + for (int index = 0; index < (&context->ucontext.uc_mcontext.fault_address - + &context->ucontext.uc_mcontext.arm_r0); + ++index) { + (&context->ucontext.uc_mcontext.arm_r0)[index] = index; + } + + static_assert( + sizeof(TestCoprocessorContext) <= + sizeof(context->ucontext.uc_regspace) + sizeof(context->extra), + "Insufficient context space"); + auto test_context = + reinterpret_cast(context->ucontext.uc_regspace); + + test_context->crunch.head.magic = CRUNCH_MAGIC; + test_context->crunch.head.size = sizeof(test_context->crunch); + memset( + &test_context->crunch.context, 'c', sizeof(test_context->crunch.context)); + + test_context->iwmmxt.head.magic = IWMMXT_MAGIC; + test_context->iwmmxt.head.size = sizeof(test_context->iwmmxt); + memset( + &test_context->iwmmxt.context, 'i', sizeof(test_context->iwmmxt.context)); + + test_context->dummy.head.magic = DUMMY_MAGIC; + test_context->dummy.head.size = sizeof(test_context->dummy); + memset( + &test_context->dummy.context, 'd', sizeof(test_context->dummy.context)); + + 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); + ++reg) { + test_context->vfp.context.vfp.fpregs[reg] = reg; + } + test_context->vfp.context.vfp.fpscr = 42; + + test_context->terminator.magic = 0; + test_context->terminator.size = 0; +} + +void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { + EXPECT_EQ(actual.architecture, kCPUArchitectureARM); + + EXPECT_EQ(memcmp(actual.arm->regs, + &expected.ucontext.uc_mcontext.arm_r0, + sizeof(actual.arm->regs)), + 0); + EXPECT_EQ(actual.arm->fp, expected.ucontext.uc_mcontext.arm_fp); + EXPECT_EQ(actual.arm->ip, expected.ucontext.uc_mcontext.arm_ip); + EXPECT_EQ(actual.arm->sp, expected.ucontext.uc_mcontext.arm_sp); + EXPECT_EQ(actual.arm->lr, expected.ucontext.uc_mcontext.arm_lr); + EXPECT_EQ(actual.arm->pc, expected.ucontext.uc_mcontext.arm_pc); + EXPECT_EQ(actual.arm->cpsr, expected.ucontext.uc_mcontext.arm_cpsr); + + EXPECT_FALSE(actual.arm->have_fpa_regs); + + EXPECT_TRUE(actual.arm->have_vfp_regs); + + auto test_context = reinterpret_cast( + expected.ucontext.uc_regspace); + + EXPECT_EQ(memcmp(actual.arm->vfp_regs.vfp, + &test_context->vfp.context.vfp, + sizeof(actual.arm->vfp_regs.vfp)), + 0); +} +#elif defined(ARCH_CPU_ARM64) +using NativeCPUContext = ucontext_t; + +struct TestCoprocessorContext { + esr_context esr; + fpsimd_context fpsimd; + _aarch64_ctx terminator; +}; + +void InitializeContext(NativeCPUContext* context) { + memset(context, 'x', sizeof(*context)); + + for (size_t index = 0; index < arraysize(context->uc_mcontext.regs); + ++index) { + context->uc_mcontext.regs[index] = index; + } + context->uc_mcontext.sp = 1; + context->uc_mcontext.pc = 2; + context->uc_mcontext.pstate = 3; + + auto test_context = reinterpret_cast( + context->uc_mcontext.__reserved); + + test_context->esr.head.magic = ESR_MAGIC; + test_context->esr.head.size = sizeof(test_context->esr); + memset(&test_context->esr.esr, 'e', sizeof(test_context->esr.esr)); + + test_context->fpsimd.head.magic = FPSIMD_MAGIC; + 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) { + test_context->fpsimd.vregs[reg] = reg; + } + + test_context->terminator.magic = 0; + test_context->terminator.size = 0; +} + +void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { + EXPECT_EQ(actual.architecture, kCPUArchitectureARM64); + + EXPECT_EQ(memcmp(actual.arm64->regs, + expected.uc_mcontext.regs, + sizeof(actual.arm64->regs)), + 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); + + auto test_context = reinterpret_cast( + expected.uc_mcontext.__reserved); + + EXPECT_EQ(actual.arm64->fpsr, test_context->fpsimd.fpsr); + EXPECT_EQ(actual.arm64->fpcr, test_context->fpsimd.fpcr); + EXPECT_EQ(memcmp(actual.arm64->fpsimd, + &test_context->fpsimd.vregs, + sizeof(actual.arm64->fpsimd)), + 0); +} +#elif defined(ARCH_CPU_MIPS_FAMILY) +using NativeCPUContext = ucontext_t; + +void InitializeContext(NativeCPUContext* context) { + for (size_t reg = 0; reg < arraysize(context->uc_mcontext.gregs); ++reg) { + context->uc_mcontext.gregs[reg] = reg; + } + memset(&context->uc_mcontext.fpregs, 44, sizeof(context->uc_mcontext.fpregs)); +} + +void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { +#if defined(ARCH_CPU_MIPSEL) + EXPECT_EQ(actual.architecture, kCPUArchitectureMIPSEL); +#define CPU_ARCH_NAME mipsel +#elif defined(ARCH_CPU_MIPS64EL) + EXPECT_EQ(actual.architecture, kCPUArchitectureMIPS64EL); +#define CPU_ARCH_NAME mips64 +#endif + + for (size_t reg = 0; reg < arraysize(expected.uc_mcontext.gregs); ++reg) { + EXPECT_EQ(actual.CPU_ARCH_NAME->regs[reg], expected.uc_mcontext.gregs[reg]); + } + + EXPECT_EQ(memcmp(&actual.CPU_ARCH_NAME->fpregs, + &expected.uc_mcontext.fpregs, + sizeof(actual.CPU_ARCH_NAME->fpregs)), + 0); +#undef CPU_ARCH_NAME +} + #else #error Port. #endif @@ -100,7 +304,7 @@ TEST(ExceptionSnapshotLinux, SelfBasic) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); siginfo_t siginfo; @@ -177,7 +381,7 @@ class RaiseTest { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); internal::ExceptionSnapshotLinux exception; @@ -240,7 +444,7 @@ class TimerTest { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); internal::ExceptionSnapshotLinux exception; diff --git a/snapshot/linux/memory_snapshot_linux.cc b/snapshot/linux/memory_snapshot_linux.cc deleted file mode 100644 index f328fefc..00000000 --- a/snapshot/linux/memory_snapshot_linux.cc +++ /dev/null @@ -1,68 +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 "snapshot/linux/memory_snapshot_linux.h" - -#include - -namespace crashpad { -namespace internal { - -MemorySnapshotLinux::MemorySnapshotLinux() - : MemorySnapshot(), - process_reader_(nullptr), - address_(0), - size_(0), - initialized_() { -} - -MemorySnapshotLinux::~MemorySnapshotLinux() { -} - -void MemorySnapshotLinux::Initialize(ProcessReader* process_reader, - LinuxVMAddress address, - size_t size) { - INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_reader_ = process_reader; - address_ = address; - size_ = size; - INITIALIZATION_STATE_SET_VALID(initialized_); -} - -uint64_t MemorySnapshotLinux::Address() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return address_; -} - -size_t MemorySnapshotLinux::Size() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return size_; -} - -bool MemorySnapshotLinux::Read(Delegate* delegate) const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - - if (size_ == 0) { - return delegate->MemorySnapshotDelegateRead(nullptr, size_); - } - - std::unique_ptr buffer(new uint8_t[size_]); - if (!process_reader_->Memory()->Read(address_, size_, buffer.get())) { - return false; - } - return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); -} - -} // namespace internal -} // namespace crashpad diff --git a/snapshot/linux/memory_snapshot_linux.h b/snapshot/linux/memory_snapshot_linux.h deleted file mode 100644 index 3c7c8391..00000000 --- a/snapshot/linux/memory_snapshot_linux.h +++ /dev/null @@ -1,69 +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_SNAPSHOT_LINUX_MEMORY_SNAPSHOT_LINUX_H_ -#define CRASHPAD_SNAPSHOT_LINUX_MEMORY_SNAPSHOT_LINUX_H_ - -#include -#include - -#include "base/macros.h" -#include "snapshot/linux/process_reader.h" -#include "snapshot/memory_snapshot.h" -#include "util/linux/address_types.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 Linux. -class MemorySnapshotLinux final : public MemorySnapshot { - public: - MemorySnapshotLinux(); - ~MemorySnapshotLinux() 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(ProcessReader* process_reader, - LinuxVMAddress address, - size_t size); - - // MemorySnapshot: - - uint64_t Address() const override; - size_t Size() const override; - bool Read(Delegate* delegate) const override; - - private: - ProcessReader* process_reader_; // weak - uint64_t address_; - size_t size_; - InitializationStateDcheck initialized_; - - DISALLOW_COPY_AND_ASSIGN(MemorySnapshotLinux); -}; - -} // namespace internal -} // namespace crashpad - -#endif // CRASHPAD_SNAPSHOT_LINUX_MEMORY_SNAPSHOT_LINUX_H_ diff --git a/snapshot/linux/process_reader.cc b/snapshot/linux/process_reader_linux.cc similarity index 67% rename from snapshot/linux/process_reader.cc rename to snapshot/linux/process_reader_linux.cc index c9a5a382..038d5cef 100644 --- a/snapshot/linux/process_reader.cc +++ b/snapshot/linux/process_reader_linux.cc @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" +#include #include #include #include @@ -26,7 +27,9 @@ #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" @@ -38,12 +41,14 @@ bool ShouldMergeStackMappings(const MemoryMap::Mapping& stack_mapping, const MemoryMap::Mapping& adj_mapping) { DCHECK(stack_mapping.readable); return adj_mapping.readable && stack_mapping.device == adj_mapping.device && - stack_mapping.inode == adj_mapping.inode; + stack_mapping.inode == adj_mapping.inode && + (stack_mapping.name == adj_mapping.name || + stack_mapping.name.empty() || adj_mapping.name.empty()); } } // namespace -ProcessReader::Thread::Thread() +ProcessReaderLinux::Thread::Thread() : thread_info(), stack_region_address(0), stack_region_size(0), @@ -51,9 +56,10 @@ ProcessReader::Thread::Thread() static_priority(-1), nice_value(-1) {} -ProcessReader::Thread::~Thread() {} +ProcessReaderLinux::Thread::~Thread() {} -bool ProcessReader::Thread::InitializePtrace(PtraceConnection* connection) { +bool ProcessReaderLinux::Thread::InitializePtrace( + PtraceConnection* connection) { if (!connection->GetThreadInfo(tid, &thread_info)) { return false; } @@ -86,7 +92,7 @@ bool ProcessReader::Thread::InitializePtrace(PtraceConnection* connection) { return true; } -void ProcessReader::Thread::InitializeStack(ProcessReader* reader) { +void ProcessReaderLinux::Thread::InitializeStack(ProcessReaderLinux* reader) { LinuxVMAddress stack_pointer; #if defined(ARCH_CPU_X86_FAMILY) stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.rsp @@ -94,10 +100,18 @@ void ProcessReader::Thread::InitializeStack(ProcessReader* reader) { #elif defined(ARCH_CPU_ARM_FAMILY) stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.sp : thread_info.thread_context.t32.sp; +#elif defined(ARCH_CPU_MIPS_FAMILY) + stack_pointer = reader->Is64Bit() ? thread_info.thread_context.t64.regs[29] + : thread_info.thread_context.t32.regs[29]; #else #error Port. #endif + InitializeStackFromSP(reader, stack_pointer); +} +void ProcessReaderLinux::Thread::InitializeStackFromSP( + ProcessReaderLinux* reader, + LinuxVMAddress stack_pointer) { const MemoryMap* memory_map = reader->GetMemoryMap(); // If we can't find the mapping, it's probably a bad stack pointer @@ -166,19 +180,26 @@ void ProcessReader::Thread::InitializeStack(ProcessReader* reader) { } } -ProcessReader::ProcessReader() +ProcessReaderLinux::Module::Module() + : name(), elf_reader(nullptr), type(ModuleSnapshot::kModuleTypeUnknown) {} + +ProcessReaderLinux::Module::~Module() = default; + +ProcessReaderLinux::ProcessReaderLinux() : connection_(), process_info_(), memory_map_(), threads_(), - process_memory_(), + modules_(), + elf_readers_(), is_64_bit_(false), initialized_threads_(false), + initialized_modules_(false), initialized_() {} -ProcessReader::~ProcessReader() {} +ProcessReaderLinux::~ProcessReaderLinux() {} -bool ProcessReader::Initialize(PtraceConnection* connection) { +bool ProcessReaderLinux::Initialize(PtraceConnection* connection) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); DCHECK(connection); connection_ = connection; @@ -187,12 +208,7 @@ bool ProcessReader::Initialize(PtraceConnection* connection) { return false; } - pid_t pid = connection->GetProcessID(); - if (!memory_map_.Initialize(pid)) { - return false; - } - - if (!process_memory_.Initialize(pid)) { + if (!memory_map_.Initialize(connection_)) { return false; } @@ -202,12 +218,13 @@ bool ProcessReader::Initialize(PtraceConnection* connection) { return true; } -bool ProcessReader::StartTime(timeval* start_time) const { +bool ProcessReaderLinux::StartTime(timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_info_.StartTime(start_time); } -bool ProcessReader::CPUTimes(timeval* user_time, timeval* system_time) const { +bool ProcessReaderLinux::CPUTimes(timeval* user_time, + timeval* system_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); timerclear(user_time); timerclear(system_time); @@ -242,7 +259,7 @@ bool ProcessReader::CPUTimes(timeval* user_time, timeval* system_time) const { return true; } -const std::vector& ProcessReader::Threads() { +const std::vector& ProcessReaderLinux::Threads() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!initialized_threads_) { InitializeThreads(); @@ -250,8 +267,17 @@ const std::vector& ProcessReader::Threads() { return threads_; } -void ProcessReader::InitializeThreads() { +const std::vector& ProcessReaderLinux::Modules() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!initialized_modules_) { + InitializeModules(); + } + return modules_; +} + +void ProcessReaderLinux::InitializeThreads() { DCHECK(threads_.empty()); + initialized_threads_ = true; pid_t pid = ProcessID(); if (pid == getpid()) { @@ -307,4 +333,79 @@ void ProcessReader::InitializeThreads() { DCHECK(main_thread_found); } +void ProcessReaderLinux::InitializeModules() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + initialized_modules_ = true; + + AuxiliaryVector aux; + if (!aux.Initialize(connection_)) { + return; + } + + LinuxVMAddress phdrs; + if (!aux.GetValue(AT_PHDR, &phdrs)) { + return; + } + + const MemoryMap::Mapping* exe_mapping; + if (!(exe_mapping = GetMemoryMap()->FindMapping(phdrs)) || + !(exe_mapping = GetMemoryMap()->FindFileMmapStart(*exe_mapping))) { + return; + } + + ProcessMemoryRange range; + if (!range.Initialize(Memory(), is_64_bit_)) { + return; + } + + auto exe_reader = std::make_unique(); + if (!exe_reader->Initialize(range, exe_mapping->range.Base())) { + return; + } + + LinuxVMAddress debug_address; + if (!exe_reader->GetDebugAddress(&debug_address)) { + return; + } + + DebugRendezvous debug; + if (!debug.Initialize(range, debug_address)) { + return; + } + + Module exe = {}; + exe.name = !debug.Executable()->name.empty() ? debug.Executable()->name + : exe_mapping->name; + exe.elf_reader = exe_reader.get(); + exe.type = ModuleSnapshot::ModuleType::kModuleTypeExecutable; + + modules_.push_back(exe); + elf_readers_.push_back(std::move(exe_reader)); + + LinuxVMAddress loader_base = 0; + aux.GetValue(AT_BASE, &loader_base); + + for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) { + const MemoryMap::Mapping* mapping; + if (!(mapping = memory_map_.FindMapping(entry.dynamic_array)) || + !(mapping = memory_map_.FindFileMmapStart(*mapping))) { + continue; + } + + auto elf_reader = std::make_unique(); + if (!elf_reader->Initialize(range, mapping->range.Base())) { + continue; + } + + Module module = {}; + module.name = !entry.name.empty() ? entry.name : mapping->name; + module.elf_reader = elf_reader.get(); + module.type = loader_base && elf_reader->Address() == loader_base + ? ModuleSnapshot::kModuleTypeDynamicLoader + : ModuleSnapshot::kModuleTypeSharedLibrary; + modules_.push_back(module); + elf_readers_.push_back(std::move(elf_reader)); + } +} + } // namespace crashpad diff --git a/snapshot/linux/process_reader.h b/snapshot/linux/process_reader_linux.h similarity index 62% rename from snapshot/linux/process_reader.h rename to snapshot/linux/process_reader_linux.h index 26d777e4..7c17d9d1 100644 --- a/snapshot/linux/process_reader.h +++ b/snapshot/linux/process_reader_linux.h @@ -12,15 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_H_ -#define CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_H_ +#ifndef CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_LINUX_H_ +#define CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_LINUX_H_ #include #include +#include +#include #include #include "base/macros.h" +#include "snapshot/elf/elf_image_reader.h" +#include "snapshot/module_snapshot.h" #include "util/linux/address_types.h" #include "util/linux/memory_map.h" #include "util/linux/ptrace_connection.h" @@ -28,19 +32,32 @@ #include "util/misc/initialization_state_dcheck.h" #include "util/posix/process_info.h" #include "util/process/process_memory.h" -#include "util/process/process_memory_linux.h" namespace crashpad { //! \brief Accesses information about another process, identified by a process //! ID. -class ProcessReader { +class ProcessReaderLinux { public: //! \brief Contains information about a thread that belongs to a process. struct Thread { Thread(); ~Thread(); + //! \brief Initializes the thread's stack using \a stack_pointer instead of + //! the stack pointer in \a thread_info. + //! + //! This method initializes \a stack_region_address and \a stack_region_size + //! overwriting any values they previously contained. This is useful, for + //! example, if the thread is currently in a signal handler context, which + //! may execute on a different stack than was used before the signal was + //! received. + //! + //! \param[in] reader A process reader for the target process. + //! \param[in] stack_pointer The stack pointer for the stack to initialize. + void InitializeStackFromSP(ProcessReaderLinux* reader, + LinuxVMAddress stack_pointer); + ThreadInfo thread_info; LinuxVMAddress stack_region_address; LinuxVMSize stack_region_size; @@ -50,14 +67,35 @@ class ProcessReader { int nice_value; private: - friend class ProcessReader; + friend class ProcessReaderLinux; bool InitializePtrace(PtraceConnection* connection); - void InitializeStack(ProcessReader* reader); + void InitializeStack(ProcessReaderLinux* reader); }; - ProcessReader(); - ~ProcessReader(); + //! \brief Contains information about a module loaded into a process. + struct Module { + Module(); + ~Module(); + + //! \brief The pathname used to load the module from disk. + std::string name; + + //! \brief An image reader for the module. + //! + //! The lifetime of this ElfImageReader is scoped to the lifetime of the + //! ProcessReaderLinux that created it. + //! + //! This field may be `nullptr` if a reader could not be created for the + //! module. + ElfImageReader* elf_reader; + + //! \brief The module's type. + ModuleSnapshot::ModuleType type; + }; + + ProcessReaderLinux(); + ~ProcessReaderLinux(); //! \brief Initializes this object. //! @@ -78,7 +116,7 @@ class ProcessReader { pid_t ParentProcessID() const { return process_info_.ParentProcessID(); } //! \brief Return a memory reader for the target process. - ProcessMemory* Memory() { return &process_memory_; } + ProcessMemory* Memory() { return connection_->Memory(); } //! \brief Return a memory map of the target process. MemoryMap* GetMemoryMap() { return &memory_map_; } @@ -107,21 +145,28 @@ class ProcessReader { //! index `0`. const std::vector& Threads(); + //! \return The modules loaded in the process. The first element (at index + //! `0`) corresponds to the main executable. + const std::vector& Modules(); + private: void InitializeThreads(); + void InitializeModules(); PtraceConnection* connection_; // weak ProcessInfo process_info_; MemoryMap memory_map_; std::vector threads_; - ProcessMemoryLinux process_memory_; + std::vector modules_; + std::vector> elf_readers_; bool is_64_bit_; bool initialized_threads_; + bool initialized_modules_; InitializationStateDcheck initialized_; - DISALLOW_COPY_AND_ASSIGN(ProcessReader); + DISALLOW_COPY_AND_ASSIGN(ProcessReaderLinux); }; } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_H_ +#endif // CRASHPAD_SNAPSHOT_LINUX_PROCESS_READER_LINUX_H_ diff --git a/snapshot/linux/process_reader_test.cc b/snapshot/linux/process_reader_linux_test.cc similarity index 77% rename from snapshot/linux/process_reader_test.cc rename to snapshot/linux/process_reader_linux_test.cc index 08edf02a..6c0b98af 100644 --- a/snapshot/linux/process_reader_test.cc +++ b/snapshot/linux/process_reader_linux_test.cc @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include +#include #include #include #include @@ -35,12 +36,17 @@ #include "gtest/gtest.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" +#include "test/linux/get_tls.h" #include "test/multiprocess.h" #include "util/file/file_io.h" #include "util/linux/direct_ptrace_connection.h" #include "util/misc/from_pointer_cast.h" #include "util/synchronization/semaphore.h" +#if defined(OS_ANDROID) +#include +#endif + namespace crashpad { namespace test { namespace { @@ -49,33 +55,11 @@ pid_t gettid() { return syscall(SYS_gettid); } -LinuxVMAddress GetTLS() { - LinuxVMAddress tls; -#if defined(ARCH_CPU_ARMEL) - // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). - auto kuser_get_tls = reinterpret_cast(0xffff0fe0); - tls = FromPointerCast(kuser_get_tls()); -#elif defined(ARCH_CPU_ARM64) - // Linux/aarch64 places the tls address in system register tpidr_el0. - asm("mrs %0, tpidr_el0" : "=r"(tls)); -#elif defined(ARCH_CPU_X86) - uint32_t tls_32; - asm("movl %%gs:0x0, %0" : "=r"(tls_32)); - tls = tls_32; -#elif defined(ARCH_CPU_X86_64) - asm("movq %%fs:0x0, %0" : "=r"(tls)); -#else -#error Port. -#endif // ARCH_CPU_ARMEL - - return tls; -} - -TEST(ProcessReader, SelfBasic) { +TEST(ProcessReaderLinux, SelfBasic) { FakePtraceConnection connection; connection.Initialize(getpid()); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); #if defined(ARCH_CPU_64_BITS) @@ -108,7 +92,7 @@ class BasicChildTest : public Multiprocess { DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); #if !defined(ARCH_CPU_64_BITS) @@ -131,7 +115,7 @@ class BasicChildTest : public Multiprocess { DISALLOW_COPY_AND_ASSIGN(BasicChildTest); }; -TEST(ProcessReader, ChildBasic) { +TEST(ProcessReaderLinux, ChildBasic) { BasicChildTest test; test.Run(); } @@ -257,11 +241,12 @@ class TestThreadPool { using ThreadMap = std::map; void ExpectThreads(const ThreadMap& thread_map, - const std::vector& threads, - const pid_t pid) { + const std::vector& threads, + PtraceConnection* connection) { ASSERT_EQ(threads.size(), thread_map.size()); + MemoryMap memory_map; - ASSERT_TRUE(memory_map.Initialize(pid)); + ASSERT_TRUE(memory_map.Initialize(connection)); for (const auto& thread : threads) { SCOPED_TRACE( @@ -318,11 +303,11 @@ class ChildThreadTest : public Multiprocess { DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); - const std::vector& threads = + const std::vector& threads = process_reader.Threads(); - ExpectThreads(thread_map, threads, ChildPID()); + ExpectThreads(thread_map, threads, &connection); } void MultiprocessChild() override { @@ -366,12 +351,12 @@ class ChildThreadTest : public Multiprocess { DISALLOW_COPY_AND_ASSIGN(ChildThreadTest); }; -TEST(ProcessReader, ChildWithThreads) { +TEST(ProcessReaderLinux, ChildWithThreads) { ChildThreadTest test; test.Run(); } -TEST(ProcessReader, ChildThreadsWithSmallUserStacks) { +TEST(ProcessReaderLinux, ChildThreadsWithSmallUserStacks) { ChildThreadTest test(PTHREAD_STACK_MIN); test.Run(); } @@ -395,10 +380,10 @@ class ChildWithSplitStackTest : public Multiprocess { DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); - const std::vector& threads = + const std::vector& threads = process_reader.Threads(); ASSERT_EQ(threads.size(), 1u); @@ -456,11 +441,102 @@ class ChildWithSplitStackTest : public Multiprocess { DISALLOW_COPY_AND_ASSIGN(ChildWithSplitStackTest); }; -TEST(ProcessReader, ChildWithSplitStack) { +TEST(ProcessReaderLinux, ChildWithSplitStack) { ChildWithSplitStackTest test; test.Run(); } +// Android doesn't provide dl_iterate_phdr on ARM until API 21. +#if !defined(OS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 +int ExpectFindModule(dl_phdr_info* info, size_t size, void* data) { + SCOPED_TRACE( + base::StringPrintf("module %s at 0x%" PRIx64 " phdrs 0x%" PRIx64, + info->dlpi_name, + LinuxVMAddress{info->dlpi_addr}, + FromPointerCast(info->dlpi_phdr))); + auto modules = + reinterpret_cast*>(data); + + auto phdr_addr = FromPointerCast(info->dlpi_phdr); + +#if defined(OS_ANDROID) + // Bionic includes a null entry. + if (!phdr_addr) { + EXPECT_EQ(info->dlpi_name, nullptr); + EXPECT_EQ(info->dlpi_addr, 0u); + EXPECT_EQ(info->dlpi_phnum, 0u); + return 0; + } +#endif + + // TODO(jperaza): This can use a range map when one is available. + bool found = false; + for (const auto& module : *modules) { + if (module.elf_reader && phdr_addr >= module.elf_reader->Address() && + phdr_addr < module.elf_reader->Address() + module.elf_reader->Size()) { + found = true; + break; + } + } + EXPECT_TRUE(found); + return 0; +} +#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 + +void ExpectModulesFromSelf( + const std::vector& modules) { + for (const auto& module : modules) { + EXPECT_FALSE(module.name.empty()); + EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); + } + +// Android doesn't provide dl_iterate_phdr on ARM until API 21. +#if !defined(OS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 + EXPECT_EQ( + dl_iterate_phdr( + ExpectFindModule, + reinterpret_cast( + const_cast*>(&modules))), + 0); +#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 +} + +TEST(ProcessReaderLinux, SelfModules) { + FakePtraceConnection connection; + connection.Initialize(getpid()); + + ProcessReaderLinux process_reader; + ASSERT_TRUE(process_reader.Initialize(&connection)); + + ExpectModulesFromSelf(process_reader.Modules()); +} + +class ChildModuleTest : public Multiprocess { + public: + ChildModuleTest() : Multiprocess() {} + ~ChildModuleTest() = default; + + private: + void MultiprocessParent() override { + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildPID())); + + ProcessReaderLinux process_reader; + ASSERT_TRUE(process_reader.Initialize(&connection)); + + ExpectModulesFromSelf(process_reader.Modules()); + } + + void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } + + DISALLOW_COPY_AND_ASSIGN(ChildModuleTest); +}; + +TEST(ProcessReaderLinux, ChildModules) { + ChildModuleTest test; + test.Run(); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/linux/process_snapshot_linux.cc b/snapshot/linux/process_snapshot_linux.cc new file mode 100644 index 00000000..190e8691 --- /dev/null +++ b/snapshot/linux/process_snapshot_linux.cc @@ -0,0 +1,263 @@ +// 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 "snapshot/linux/process_snapshot_linux.h" + +#include + +#include "base/logging.h" +#include "util/linux/exception_information.h" + +namespace crashpad { + +ProcessSnapshotLinux::ProcessSnapshotLinux() = default; + +ProcessSnapshotLinux::~ProcessSnapshotLinux() = default; + +bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + if (gettimeofday(&snapshot_time_, nullptr) != 0) { + PLOG(ERROR) << "gettimeofday"; + return false; + } + + if (!process_reader_.Initialize(connection) || + !memory_range_.Initialize(process_reader_.Memory(), + process_reader_.Is64Bit())) { + return false; + } + + system_.Initialize(&process_reader_, &snapshot_time_); + + InitializeThreads(); + InitializeModules(); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessSnapshotLinux::InitializeException( + LinuxVMAddress exception_info_address) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(!exception_); + + ExceptionInformation info; + if (!process_reader_.Memory()->Read( + exception_info_address, sizeof(info), &info)) { + LOG(ERROR) << "Couldn't read exception info"; + return false; + } + + exception_.reset(new internal::ExceptionSnapshotLinux()); + if (!exception_->Initialize(&process_reader_, + info.siginfo_address, + info.context_address, + info.thread_id)) { + exception_.reset(); + return false; + } + + // The thread's existing snapshot will have captured the stack for the signal + // handler. Replace it with a thread snapshot which captures the stack for the + // exception context. + for (const auto& reader_thread : process_reader_.Threads()) { + if (reader_thread.tid == info.thread_id) { + ProcessReaderLinux::Thread thread = reader_thread; + thread.InitializeStackFromSP(&process_reader_, + exception_->Context()->StackPointer()); + + auto exc_thread_snapshot = + std::make_unique(); + if (!exc_thread_snapshot->Initialize(&process_reader_, thread)) { + return false; + } + + for (auto& thread_snapshot : threads_) { + if (thread_snapshot->ThreadID() == + static_cast(info.thread_id)) { + thread_snapshot.reset(exc_thread_snapshot.release()); + return true; + } + } + + LOG(ERROR) << "thread not found " << info.thread_id; + return false; + } + } + + LOG(ERROR) << "thread not found " << info.thread_id; + return false; +} + +void ProcessSnapshotLinux::GetCrashpadOptions( + CrashpadInfoClientOptions* options) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + CrashpadInfoClientOptions local_options; + + for (const auto& module : modules_) { + CrashpadInfoClientOptions module_options; + if (!module->GetCrashpadOptions(&module_options)) { + continue; + } + + if (local_options.crashpad_handler_behavior == TriState::kUnset) { + local_options.crashpad_handler_behavior = + module_options.crashpad_handler_behavior; + } + if (local_options.system_crash_reporter_forwarding == TriState::kUnset) { + local_options.system_crash_reporter_forwarding = + module_options.system_crash_reporter_forwarding; + } + if (local_options.gather_indirectly_referenced_memory == TriState::kUnset) { + local_options.gather_indirectly_referenced_memory = + module_options.gather_indirectly_referenced_memory; + local_options.indirectly_referenced_memory_cap = + module_options.indirectly_referenced_memory_cap; + } + + // If non-default values have been found for all options, the loop can end + // early. + if (local_options.crashpad_handler_behavior != TriState::kUnset && + local_options.system_crash_reporter_forwarding != TriState::kUnset && + local_options.gather_indirectly_referenced_memory != TriState::kUnset) { + break; + } + } + + *options = local_options; +} + +pid_t ProcessSnapshotLinux::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.ProcessID(); +} + +pid_t ProcessSnapshotLinux::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_reader_.ParentProcessID(); +} + +void ProcessSnapshotLinux::SnapshotTime(timeval* snapshot_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *snapshot_time = snapshot_time_; +} + +void ProcessSnapshotLinux::ProcessStartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + process_reader_.StartTime(start_time); +} + +void ProcessSnapshotLinux::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + process_reader_.CPUTimes(user_time, system_time); +} + +void ProcessSnapshotLinux::ReportID(UUID* report_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *report_id = report_id_; +} + +void ProcessSnapshotLinux::ClientID(UUID* client_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *client_id = client_id_; +} + +const std::map& +ProcessSnapshotLinux::AnnotationsSimpleMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return annotations_simple_map_; +} + +const SystemSnapshot* ProcessSnapshotLinux::System() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &system_; +} + +std::vector ProcessSnapshotLinux::Threads() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; +} + +std::vector ProcessSnapshotLinux::Modules() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector modules; + for (const auto& module : modules_) { + modules.push_back(module.get()); + } + return modules; +} + +std::vector ProcessSnapshotLinux::UnloadedModules() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(jperaza): Can this be implemented on Linux? + return std::vector(); +} + +const ExceptionSnapshot* ProcessSnapshotLinux::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return exception_.get(); +} + +std::vector ProcessSnapshotLinux::MemoryMap() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + // TODO(jperaza): do this. + return std::vector(); +} + +std::vector ProcessSnapshotLinux::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +std::vector ProcessSnapshotLinux::ExtraMemory() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return std::vector(); +} + +void ProcessSnapshotLinux::InitializeThreads() { + const std::vector& process_reader_threads = + process_reader_.Threads(); + for (const ProcessReaderLinux::Thread& process_reader_thread : + process_reader_threads) { + auto thread = std::make_unique(); + if (thread->Initialize(&process_reader_, process_reader_thread)) { + threads_.push_back(std::move(thread)); + } + } +} + +void ProcessSnapshotLinux::InitializeModules() { + for (const ProcessReaderLinux::Module& reader_module : + process_reader_.Modules()) { + auto module = + std::make_unique(reader_module.name, + reader_module.elf_reader, + reader_module.type, + &memory_range_); + if (module->Initialize()) { + modules_.push_back(std::move(module)); + } + } +} + +} // namespace crashpad diff --git a/snapshot/linux/process_snapshot_linux.h b/snapshot/linux/process_snapshot_linux.h new file mode 100644 index 00000000..49d648f9 --- /dev/null +++ b/snapshot/linux/process_snapshot_linux.h @@ -0,0 +1,140 @@ +// 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_SNAPSHOT_LINUX_PROCESS_SNAPSHOT_LINUX_H_ +#define CRASHPAD_SNAPSHOT_LINUX_PROCESS_SNAPSHOT_LINUX_H_ + +#include +#include + +#include +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/elf/module_snapshot_elf.h" +#include "snapshot/linux/exception_snapshot_linux.h" +#include "snapshot/linux/process_reader_linux.h" +#include "snapshot/linux/system_snapshot_linux.h" +#include "snapshot/linux/thread_snapshot_linux.h" +#include "snapshot/memory_map_region_snapshot.h" +#include "snapshot/module_snapshot.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/system_snapshot.h" +#include "snapshot/thread_snapshot.h" +#include "snapshot/unloaded_module_snapshot.h" +#include "util/linux/ptrace_connection.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/misc/uuid.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +//! \brief A ProcessSnapshot of a running (or crashed) process running on a +//! Linux system. +class ProcessSnapshotLinux final : public ProcessSnapshot { + public: + ProcessSnapshotLinux(); + ~ProcessSnapshotLinux() override; + + //! \brief Initializes the object. + //! + //! \param[in] connection A connection to the process to snapshot. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(PtraceConnection* connection); + + //! \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); + + //! \brief Sets the value to be returned by ReportID(). + //! + //! 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; } + + //! \brief Sets the value to be returned by ClientID(). + //! + //! 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 AnnotationsSimpleMap(). + //! + //! All 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& annotations_simple_map) { + annotations_simple_map_ = annotations_simple_map; + } + + //! \brief Returns options from CrashpadInfo structures found in modules in + //! the process. + //! + //! \param[out] options Options set in CrashpadInfo structures in modules in + //! the process. + void GetCrashpadOptions(CrashpadInfoClientOptions* options); + + // 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& AnnotationsSimpleMap() + const override; + const SystemSnapshot* System() const override; + std::vector Threads() const override; + std::vector Modules() const override; + std::vector UnloadedModules() const override; + const ExceptionSnapshot* Exception() const override; + std::vector MemoryMap() const override; + std::vector Handles() const override; + std::vector ExtraMemory() const override; + + private: + void InitializeThreads(); + void InitializeModules(); + + std::map annotations_simple_map_; + timeval snapshot_time_; + UUID report_id_; + UUID client_id_; + std::vector> threads_; + std::vector> modules_; + std::unique_ptr exception_; + internal::SystemSnapshotLinux system_; + ProcessReaderLinux process_reader_; + ProcessMemoryRange memory_range_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotLinux); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_LINUX_PROCESS_SNAPSHOT_LINUX_H_ diff --git a/snapshot/linux/signal_context.h b/snapshot/linux/signal_context.h index d8d5f69d..11002468 100644 --- a/snapshot/linux/signal_context.h +++ b/snapshot/linux/signal_context.h @@ -12,13 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_LINUX_H_ -#define CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_LINUX_H_ +#ifndef CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_H_ +#define CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_H_ +#include #include #include +#include + +#include +#include #include "build/build_config.h" +#include "util/linux/thread_info.h" #include "util/linux/traits.h" namespace crashpad { @@ -35,8 +41,14 @@ union Sigval { template struct Siginfo { int32_t signo; +#ifdef ARCH_CPU_MIPS_FAMILY + // Attribute order for signo_t defined in kernel is different for MIPS. + int32_t code; + int32_t err; +#else int32_t err; int32_t code; +#endif typename Traits::UInteger32_64Only padding; union { @@ -85,6 +97,35 @@ struct Siginfo { }; }; +template +struct SignalStack { + typename Traits::Address stack_pointer; + uint32_t flags; + typename Traits::UInteger32_64Only padding; + typename Traits::Size size; +}; + +template +struct Sigset {}; + +template +struct Sigset< + Traits, + typename std::enable_if::value>::type> { + uint64_t val; +}; + +template +struct Sigset< + Traits, + typename std::enable_if::value>::type> { +#if defined(OS_ANDROID) + uint64_t val; +#else + typename Traits::ULong val[16]; +#endif // OS_ANDROID +}; + #if defined(ARCH_CPU_X86_FAMILY) struct SignalThreadContext32 { @@ -166,34 +207,6 @@ struct MContext { typename Traits::ULong_64Only reserved[8]; }; -template -struct SignalStack { - typename Traits::Address stack_pointer; - uint32_t flags; - typename Traits::UInteger32_64Only padding; - typename Traits::Size size; -}; - -template -struct Sigset {}; - -template <> -struct Sigset { - uint64_t val; -}; - -#if defined(OS_ANDROID) -template <> -struct Sigset { - uint64_t val; -}; -#else -template <> -struct Sigset { - ContextTraits64::ULong val[16]; -}; -#endif // OS_ANDROID - template struct UContext { typename Traits::ULong flags; @@ -201,9 +214,214 @@ struct UContext { SignalStack stack; MContext mcontext; Sigset sigmask; - typename Traits::FloatContext fprs; + char fpregs_mem[0]; }; +#elif defined(ARCH_CPU_ARM_FAMILY) + +struct CoprocessorContextHead { + uint32_t magic; + uint32_t size; +}; + +struct SignalFPSIMDContext { + uint32_t fpsr; + uint32_t fpcr; + uint128_struct vregs[32]; +}; + +struct SignalVFPContext { + FloatContext::f32_t::vfp_t vfp; + struct vfp_exc { + uint32_t fpexc; + uint32_t fpinst; + uint32_t fpinst2; + } vfp_exc; + uint32_t padding; +}; + +struct SignalThreadContext32 { + uint32_t regs[11]; + uint32_t fp; + uint32_t ip; + uint32_t sp; + uint32_t lr; + uint32_t pc; + uint32_t cpsr; +}; + +using SignalThreadContext64 = ThreadContext::t64_t; + +struct MContext32Data { + uint32_t trap_no; + uint32_t error_code; + uint32_t oldmask; + SignalThreadContext32 gprs; + uint32_t fault_address; +}; + +struct MContext64Data { + uint64_t fault_address; + SignalThreadContext64 gprs; +}; + +struct ContextTraits32 : public Traits32 { + using MContext32 = MContext32Data; + using MContext64 = Nothing; +}; + +struct ContextTraits64 : public Traits64 { + using MContext32 = Nothing; + using MContext64 = MContext64Data; +}; + +template +struct UContext { + typename Traits::ULong flags; + typename Traits::Address link; + SignalStack stack; + typename Traits::MContext32 mcontext32; + Sigset sigmask; + char padding[128 - sizeof(sigmask)]; + typename Traits::Char_64Only padding2[8]; + typename Traits::MContext64 mcontext64; + typename Traits::Char_64Only padding3[8]; + char reserved[0]; +}; + +#if defined(ARCH_CPU_ARMEL) +static_assert(offsetof(UContext, mcontext32) == + offsetof(ucontext_t, uc_mcontext), + "context offset mismatch"); +static_assert(offsetof(UContext, reserved) == + offsetof(ucontext_t, uc_regspace), + "regspace offset mismatch"); + +#elif defined(ARCH_CPU_ARM64) +static_assert(offsetof(UContext, mcontext64) == + offsetof(ucontext_t, uc_mcontext), + "context offset mismtach"); +static_assert(offsetof(UContext, reserved) == + offsetof(ucontext_t, uc_mcontext) + + offsetof(mcontext_t, __reserved), + "reserved space offset mismtach"); +#endif + +#elif defined(ARCH_CPU_MIPS_FAMILY) + +struct MContext32 { + uint32_t regmask; + uint32_t status; + uint64_t pc; + uint64_t gregs[32]; + struct { + float _fp_fregs; + unsigned int _fp_pad; + } fpregs[32]; + uint32_t fp_owned; + uint32_t fpc_csr; + uint32_t fpc_eir; + uint32_t used_math; + uint32_t dsp; + uint64_t mdhi; + uint64_t mdlo; + uint32_t hi1; + uint32_t lo1; + uint32_t hi2; + uint32_t lo2; + uint32_t hi3; + uint32_t lo3; +}; + +struct MContext64 { + uint64_t gregs[32]; + double fpregs[32]; + uint64_t mdhi; + uint64_t hi1; + uint64_t hi2; + uint64_t hi3; + uint64_t mdlo; + uint64_t lo1; + uint64_t lo2; + uint64_t lo3; + uint64_t pc; + uint32_t fpc_csr; + uint32_t used_math; + uint32_t dsp; + uint32_t __glibc_reserved1; +}; + +struct SignalThreadContext32 { + uint64_t regs[32]; + uint32_t lo; + uint32_t hi; + uint32_t cp0_epc; + uint32_t cp0_badvaddr; + uint32_t cp0_status; + uint32_t cp0_cause; + + SignalThreadContext32() {} + explicit SignalThreadContext32( + const struct ThreadContext::t32_t& thread_context) { + for (size_t reg = 0; reg < 32; ++reg) { + regs[reg] = thread_context.regs[reg]; + } + lo = thread_context.lo; + hi = thread_context.hi; + cp0_epc = thread_context.cp0_epc; + cp0_badvaddr = thread_context.cp0_badvaddr; + cp0_status = thread_context.cp0_status; + cp0_cause = thread_context.cp0_cause; + } +}; + +struct ContextTraits32 : public Traits32 { + using MContext = MContext32; + using SignalThreadContext = SignalThreadContext32; + using SignalFloatContext = FloatContext::f32_t; + using CPUContext = CPUContextMIPS; +}; + +struct ContextTraits64 : public Traits64 { + using MContext = MContext64; + using SignalThreadContext = ThreadContext::t64_t; + using SignalFloatContext = FloatContext::f64_t; + using CPUContext = CPUContextMIPS64; +}; + +template +struct UContext { + typename Traits::ULong flags; + typename Traits::Address link; + SignalStack stack; + typename Traits::ULong_32Only alignment_padding_; + typename Traits::MContext mcontext; + Sigset sigmask; +}; + +#if defined(ARCH_CPU_MIPSEL) +static_assert(offsetof(UContext, mcontext) == + offsetof(ucontext_t, uc_mcontext), + "context offset mismatch"); +static_assert(offsetof(UContext, mcontext.gregs) == + offsetof(ucontext_t, uc_mcontext.gregs), + "context offset mismatch"); +static_assert(offsetof(UContext, mcontext.fpregs) == + offsetof(ucontext_t, uc_mcontext.fpregs), + "context offset mismatch"); + +#elif defined(ARCH_CPU_MIPS64EL) +static_assert(offsetof(UContext, mcontext) == + offsetof(ucontext_t, uc_mcontext), + "context offset mismtach"); +static_assert(offsetof(UContext, mcontext.gregs) == + offsetof(ucontext_t, uc_mcontext.gregs), + "context offset mismatch"); +static_assert(offsetof(UContext, mcontext.fpregs) == + offsetof(ucontext_t, uc_mcontext.fpregs), + "context offset mismatch"); +#endif + #else #error Port. #endif // ARCH_CPU_X86_FAMILY @@ -213,4 +431,4 @@ struct UContext { } // namespace internal } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_LINUX_H_ +#endif // CRASHPAD_SNAPSHOT_LINUX_SNAPSHOT_SIGNAL_CONTEXT_H_ diff --git a/snapshot/linux/system_snapshot_linux.cc b/snapshot/linux/system_snapshot_linux.cc index f3ed99be..8564d3d4 100644 --- a/snapshot/linux/system_snapshot_linux.cc +++ b/snapshot/linux/system_snapshot_linux.cc @@ -151,7 +151,7 @@ SystemSnapshotLinux::SystemSnapshotLinux() SystemSnapshotLinux::~SystemSnapshotLinux() {} -void SystemSnapshotLinux::Initialize(ProcessReader* process_reader, +void SystemSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, const timeval* snapshot_time) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); process_reader_ = process_reader; @@ -197,6 +197,12 @@ CPUArchitecture SystemSnapshotLinux::GetCPUArchitecture() const { #if defined(ARCH_CPU_X86_FAMILY) return process_reader_->Is64Bit() ? kCPUArchitectureX86_64 : kCPUArchitectureX86; +#elif defined(ARCH_CPU_ARM_FAMILY) + return process_reader_->Is64Bit() ? kCPUArchitectureARM64 + : kCPUArchitectureARM; +#elif defined(ARCH_CPU_MIPS_FAMILY) + return process_reader_->Is64Bit() ? kCPUArchitectureMIPS64EL + : kCPUArchitectureMIPSEL; #else #error port to your architecture #endif @@ -206,6 +212,12 @@ uint32_t SystemSnapshotLinux::CPURevision() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.Revision(); +#elif defined(ARCH_CPU_ARM_FAMILY) + // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 + return 0; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Not implementable on MIPS + return 0; #else #error port to your architecture #endif @@ -220,6 +232,12 @@ std::string SystemSnapshotLinux::CPUVendor() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); #if defined(ARCH_CPU_X86_FAMILY) return cpuid_.Vendor(); +#elif defined(ARCH_CPU_ARM_FAMILY) + // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 + return std::string(); +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Not implementable on MIPS + return std::string(); #else #error port to your architecture #endif @@ -264,7 +282,12 @@ uint64_t SystemSnapshotLinux::CPUX86Features() const { uint64_t SystemSnapshotLinux::CPUX86ExtendedFeatures() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) return cpuid_.ExtendedFeatures(); +#else + NOTREACHED(); + return 0; +#endif } uint32_t SystemSnapshotLinux::CPUX86Leaf7Features() const { @@ -340,7 +363,17 @@ std::string SystemSnapshotLinux::MachineDescription() const { bool SystemSnapshotLinux::NXEnabled() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); +#if defined(ARCH_CPU_X86_FAMILY) return cpuid_.NXEnabled(); +#elif defined(ARCH_CPU_ARM_FAMILY) + // TODO(jperaza): do this. https://crashpad.chromium.org/bug/30 + return false; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Not implementable on MIPS + return false; +#else +#error Port. +#endif // ARCH_CPU_X86_FAMILY } void SystemSnapshotLinux::TimeZone(DaylightSavingTimeStatus* dst_status, diff --git a/snapshot/linux/system_snapshot_linux.h b/snapshot/linux/system_snapshot_linux.h index a9914500..d22c49a6 100644 --- a/snapshot/linux/system_snapshot_linux.h +++ b/snapshot/linux/system_snapshot_linux.h @@ -22,7 +22,7 @@ #include "base/macros.h" #include "build/build_config.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include "snapshot/system_snapshot.h" #include "util/misc/initialization_state_dcheck.h" @@ -44,9 +44,9 @@ class SystemSnapshotLinux final : public SystemSnapshot { //! \param[in] process_reader A reader for the process being snapshotted. //! \n\n //! It seems odd that a system snapshot implementation would need a - //! ProcessReader, but some of the information reported about the system - //! depends on the process it’s being reported for. For example, the - //! architecture returned by GetCPUArchitecture() should be the + //! ProcessReaderLinux, but some of the information reported about the + //! system depends on the process it’s being reported for. For example, + //! the architecture returned by GetCPUArchitecture() should be the //! architecture of the process, which may be different than the native //! architecture of the system: an x86_64 system can run both x86_64 and //! 32-bit x86 processes. @@ -57,7 +57,8 @@ class SystemSnapshotLinux final : public SystemSnapshot { //! Otherwise, it would need to base its determination on the current //! time, which may be different than the snapshot time for snapshots //! generated around the daylight saving transition time. - void Initialize(ProcessReader* process_reader, const timeval* snapshot_time); + void Initialize(ProcessReaderLinux* process_reader, + const timeval* snapshot_time); // SystemSnapshot: @@ -91,7 +92,7 @@ class SystemSnapshotLinux final : public SystemSnapshot { std::string os_version_full_; std::string os_version_build_; - ProcessReader* process_reader_; // weak + ProcessReaderLinux* process_reader_; // weak const timeval* snapshot_time_; // weak #if defined(ARCH_CPU_X86_FAMILY) CpuidReader cpuid_; diff --git a/snapshot/linux/system_snapshot_linux_test.cc b/snapshot/linux/system_snapshot_linux_test.cc index a91dfa41..46d3845f 100644 --- a/snapshot/linux/system_snapshot_linux_test.cc +++ b/snapshot/linux/system_snapshot_linux_test.cc @@ -21,7 +21,7 @@ #include "build/build_config.h" #include "gtest/gtest.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" @@ -33,7 +33,7 @@ TEST(SystemSnapshotLinux, Basic) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); - ProcessReader process_reader; + ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); timeval snapshot_time; diff --git a/snapshot/linux/thread_snapshot_linux.cc b/snapshot/linux/thread_snapshot_linux.cc index dcfe5c40..8ffbfe8b 100644 --- a/snapshot/linux/thread_snapshot_linux.cc +++ b/snapshot/linux/thread_snapshot_linux.cc @@ -37,9 +37,8 @@ ThreadSnapshotLinux::ThreadSnapshotLinux() ThreadSnapshotLinux::~ThreadSnapshotLinux() { } -bool ThreadSnapshotLinux::Initialize( - ProcessReader* process_reader, - const ProcessReader::Thread& thread) { +bool ThreadSnapshotLinux::Initialize(ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread& thread) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); #if defined(ARCH_CPU_X86_FAMILY) @@ -56,6 +55,36 @@ bool ThreadSnapshotLinux::Initialize( thread.thread_info.float_context.f32, context_.x86); } +#elif defined(ARCH_CPU_ARM_FAMILY) + if (process_reader->Is64Bit()) { + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_union_.arm64; + InitializeCPUContextARM64(thread.thread_info.thread_context.t64, + thread.thread_info.float_context.f64, + context_.arm64); + } else { + context_.architecture = kCPUArchitectureARM; + context_.arm = &context_union_.arm; + InitializeCPUContextARM(thread.thread_info.thread_context.t32, + thread.thread_info.float_context.f32, + context_.arm); + } +#elif defined(ARCH_CPU_MIPS_FAMILY) + if (process_reader->Is64Bit()) { + context_.architecture = kCPUArchitectureMIPS64EL; + context_.mips64 = &context_union_.mips64; + InitializeCPUContextMIPS( + thread.thread_info.thread_context.t64, + thread.thread_info.float_context.f64, + context_.mips64); + } else { + context_.architecture = kCPUArchitectureMIPSEL; + context_.mipsel = &context_union_.mipsel; + InitializeCPUContextMIPS( + SignalThreadContext32(thread.thread_info.thread_context.t32), + thread.thread_info.float_context.f32, + context_.mipsel); + } #else #error Port. #endif diff --git a/snapshot/linux/thread_snapshot_linux.h b/snapshot/linux/thread_snapshot_linux.h index e0ce7adb..17e471f3 100644 --- a/snapshot/linux/thread_snapshot_linux.h +++ b/snapshot/linux/thread_snapshot_linux.h @@ -20,9 +20,9 @@ #include "base/macros.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" -#include "snapshot/linux/memory_snapshot_linux.h" -#include "snapshot/linux/process_reader.h" +#include "snapshot/linux/process_reader_linux.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/thread_snapshot.h" #include "util/misc/initialization_state_dcheck.h" @@ -37,15 +37,15 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the process containing the - //! thread. - //! \param[in] thread The thread within the ProcessReader for + //! \param[in] process_reader A ProcessReaderLinux for the process containing + //! the thread. + //! \param[in] thread The thread within the ProcessReaderLinux for //! which the snapshot should be created. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! a message logged. - bool Initialize(ProcessReader* process_reader, - const ProcessReader::Thread& thread); + bool Initialize(ProcessReaderLinux* process_reader, + const ProcessReaderLinux::Thread& thread); // ThreadSnapshot: @@ -58,16 +58,22 @@ class ThreadSnapshotLinux final : public ThreadSnapshot { std::vector 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_ARM_FAMILY) + CPUContextARM arm; + CPUContextARM64 arm64; +#elif defined(ARCH_CPU_MIPS_FAMILY) + CPUContextMIPS mipsel; + CPUContextMIPS64 mips64; #else #error Port. #endif // ARCH_CPU_X86_FAMILY + } context_union_; CPUContext context_; - MemorySnapshotLinux stack_; + MemorySnapshotGeneric stack_; LinuxVMAddress thread_specific_data_address_; pid_t thread_id_; int priority_; diff --git a/snapshot/mac/cpu_context_mac.h b/snapshot/mac/cpu_context_mac.h index c96ad3c4..30281c16 100644 --- a/snapshot/mac/cpu_context_mac.h +++ b/snapshot/mac/cpu_context_mac.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_MAC_SNAPSHOT_CPU_CONTEXT_MAC_H_ -#define CRASHPAD_SNAPSHOT_MAC_SNAPSHOT_CPU_CONTEXT_MAC_H_ +#ifndef CRASHPAD_SNAPSHOT_MAC_CPU_CONTEXT_MAC_H_ +#define CRASHPAD_SNAPSHOT_MAC_CPU_CONTEXT_MAC_H_ #include @@ -113,4 +113,4 @@ void InitializeCPUContextX86_64(CPUContextX86_64* context, } // namespace internal } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_MAC_SNAPSHOT_CPU_CONTEXT_MAC_H_ +#endif // CRASHPAD_SNAPSHOT_MAC_CPU_CONTEXT_MAC_H_ diff --git a/snapshot/mac/exception_snapshot_mac.cc b/snapshot/mac/exception_snapshot_mac.cc index 92c8450b..50d1a121 100644 --- a/snapshot/mac/exception_snapshot_mac.cc +++ b/snapshot/mac/exception_snapshot_mac.cc @@ -17,7 +17,7 @@ #include "base/logging.h" #include "base/strings/stringprintf.h" #include "snapshot/mac/cpu_context_mac.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "util/mach/exception_behaviors.h" #include "util/mach/exception_types.h" #include "util/mach/symbolic_constants_mach.h" @@ -41,7 +41,7 @@ ExceptionSnapshotMac::ExceptionSnapshotMac() ExceptionSnapshotMac::~ExceptionSnapshotMac() { } -bool ExceptionSnapshotMac::Initialize(ProcessReader* process_reader, +bool ExceptionSnapshotMac::Initialize(ProcessReaderMac* process_reader, exception_behavior_t behavior, thread_t exception_thread, exception_type_t exception, @@ -126,8 +126,9 @@ bool ExceptionSnapshotMac::Initialize(ProcessReader* process_reader, exception_code_0_ = unsigned_exception_code_0; } - const ProcessReader::Thread* thread = nullptr; - for (const ProcessReader::Thread& loop_thread : process_reader->Threads()) { + const ProcessReaderMac::Thread* thread = nullptr; + for (const ProcessReaderMac::Thread& loop_thread : + process_reader->Threads()) { if (exception_thread == loop_thread.port) { thread = &loop_thread; break; diff --git a/snapshot/mac/exception_snapshot_mac.h b/snapshot/mac/exception_snapshot_mac.h index 9a6ddcaa..52ef519b 100644 --- a/snapshot/mac/exception_snapshot_mac.h +++ b/snapshot/mac/exception_snapshot_mac.h @@ -29,7 +29,7 @@ namespace crashpad { -class ProcessReader; +class ProcessReaderMac; namespace internal { @@ -45,8 +45,8 @@ class ExceptionSnapshotMac final : public ExceptionSnapshot { //! Other than \a process_reader, the parameters may be passed directly //! through from a Mach exception handler. //! - //! \param[in] process_reader A ProcessReader for the task that sustained the - //! exception. + //! \param[in] process_reader A ProcessReaderMac for the task that sustained + //! the exception. //! \param[in] behavior //! \param[in] exception_thread //! \param[in] exception @@ -58,7 +58,7 @@ class ExceptionSnapshotMac final : public ExceptionSnapshot { //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(ProcessReader* process_reader, + bool Initialize(ProcessReaderMac* process_reader, exception_behavior_t behavior, thread_t exception_thread, exception_type_t exception, diff --git a/snapshot/mac/mach_o_image_annotations_reader.cc b/snapshot/mac/mach_o_image_annotations_reader.cc index 1a74d812..b02acaea 100644 --- a/snapshot/mac/mach_o_image_annotations_reader.cc +++ b/snapshot/mac/mach_o_image_annotations_reader.cc @@ -24,7 +24,7 @@ #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "snapshot/mac/mach_o_image_reader.h" -#include "snapshot/mac/process_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" @@ -32,13 +32,12 @@ namespace crashpad { MachOImageAnnotationsReader::MachOImageAnnotationsReader( - ProcessReader* process_reader, + ProcessReaderMac* process_reader, const MachOImageReader* image_reader, const std::string& name) : name_(name), process_reader_(process_reader), - image_reader_(image_reader) { -} + image_reader_(image_reader) {} std::vector MachOImageAnnotationsReader::Vector() const { std::vector vector_annotations; @@ -153,11 +152,8 @@ void MachOImageAnnotationsReader::ReadDyldErrorStringAnnotation( void MachOImageAnnotationsReader::ReadCrashpadSimpleAnnotations( std::map* simple_map_annotations) const { process_types::CrashpadInfo crashpad_info; - if (!image_reader_->GetCrashpadInfo(&crashpad_info)) { - return; - } - - if (!crashpad_info.simple_annotations) { + if (!image_reader_->GetCrashpadInfo(&crashpad_info) || + !crashpad_info.simple_annotations) { return; } @@ -189,11 +185,8 @@ void MachOImageAnnotationsReader::ReadCrashpadSimpleAnnotations( void MachOImageAnnotationsReader::ReadCrashpadAnnotationsList( std::vector* annotations) const { process_types::CrashpadInfo crashpad_info; - if (!image_reader_->GetCrashpadInfo(&crashpad_info)) { - return; - } - - if (!crashpad_info.annotations_list) { + if (!image_reader_->GetCrashpadInfo(&crashpad_info) || + !crashpad_info.annotations_list) { return; } diff --git a/snapshot/mac/mach_o_image_annotations_reader.h b/snapshot/mac/mach_o_image_annotations_reader.h index 06d2bea9..a56b073b 100644 --- a/snapshot/mac/mach_o_image_annotations_reader.h +++ b/snapshot/mac/mach_o_image_annotations_reader.h @@ -26,7 +26,7 @@ namespace crashpad { class MachOImageReader; -class ProcessReader; +class ProcessReaderMac; //! \brief A reader for annotations stored in a Mach-O image mapped into another //! process. @@ -54,7 +54,7 @@ class MachOImageAnnotationsReader { //! contained within the remote process. //! \param[in] name The module’s name, a string to be used in logged messages. //! This string is for diagnostic purposes only, and may be empty. - MachOImageAnnotationsReader(ProcessReader* process_reader, + MachOImageAnnotationsReader(ProcessReaderMac* process_reader, const MachOImageReader* image_reader, const std::string& name); @@ -91,7 +91,7 @@ class MachOImageAnnotationsReader { std::vector* vector_annotations) const; std::string name_; - ProcessReader* process_reader_; // weak + ProcessReaderMac* process_reader_; // weak const MachOImageReader* image_reader_; // weak DISALLOW_COPY_AND_ASSIGN(MachOImageAnnotationsReader); diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc index 3e7864b0..69302503 100644 --- a/snapshot/mac/mach_o_image_annotations_reader_test.cc +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc @@ -33,7 +33,7 @@ #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "test/errors.h" #include "test/mac/mach_errors.h" #include "test/mac/mach_multiprocess.h" @@ -101,6 +101,32 @@ class TestMachOImageAnnotationsReader final : MachMultiprocess(), UniversalMachExcServer::Interface(), test_type_(test_type) { + switch (test_type_) { + case kDontCrash: + // SetExpectedChildTermination(kTerminationNormal, EXIT_SUCCESS) is the + // default. + break; + + case kCrashAbort: + SetExpectedChildTermination(kTerminationSignal, SIGABRT); + break; + + case kCrashModuleInitialization: + // This crash is triggered by __builtin_trap(), which shows up as + // SIGILL. + SetExpectedChildTermination(kTerminationSignal, SIGILL); + break; + + case kCrashDyld: + // Prior to 10.12, dyld fatal errors result in the execution of an + // int3 instruction on x86 and a trap instruction on ARM, both of + // which raise SIGTRAP. 10.9.5 dyld-239.4/src/dyldStartup.s + // _dyld_fatal_error. This changed in 10.12 to use + // abort_with_payload(), which appears as SIGABRT to a waiting parent. + SetExpectedChildTermination( + kTerminationSignal, MacOSXMinorVersion() < 12 ? SIGTRAP : SIGABRT); + break; + } } ~TestMachOImageAnnotationsReader() {} @@ -135,15 +161,15 @@ class TestMachOImageAnnotationsReader final EXPECT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "pid_for_task"); EXPECT_EQ(task_pid, ChildPID()); - ProcessReader process_reader; + ProcessReaderMac process_reader; bool rv = process_reader.Initialize(task); if (!rv) { ADD_FAILURE(); } else { - const std::vector& modules = + const std::vector& modules = process_reader.Modules(); std::vector all_annotations_vector; - for (const ProcessReader::Module& module : modules) { + for (const ProcessReaderMac::Module& module : modules) { if (module.reader) { MachOImageAnnotationsReader module_annotations_reader( &process_reader, module.reader, module.name); @@ -245,7 +271,7 @@ class TestMachOImageAnnotationsReader final // MachMultiprocess: void MachMultiprocessParent() override { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); // Wait for the child process to indicate that it’s done setting up its @@ -255,11 +281,11 @@ class TestMachOImageAnnotationsReader final // Verify the “simple map” and object-based annotations set via the // CrashpadInfo interface. - const std::vector& modules = + const std::vector& modules = process_reader.Modules(); std::map all_annotations_simple_map; std::vector all_annotations; - for (const ProcessReader::Module& module : modules) { + for (const ProcessReaderMac::Module& module : modules) { MachOImageAnnotationsReader module_annotations_reader( &process_reader, module.reader, module.name); std::map module_annotations_simple_map = @@ -322,33 +348,6 @@ class TestMachOImageAnnotationsReader final kMachMessageTimeoutWaitIndefinitely); EXPECT_EQ(mr, MACH_MSG_SUCCESS) << MachErrorMessage(mr, "MachMessageServer::Run"); - - switch (test_type_) { - case kCrashAbort: - SetExpectedChildTermination(kTerminationSignal, SIGABRT); - break; - - case kCrashModuleInitialization: - // This crash is triggered by __builtin_trap(), which shows up as - // SIGILL. - SetExpectedChildTermination(kTerminationSignal, SIGILL); - break; - - case kCrashDyld: - // Prior to 10.12, dyld fatal errors result in the execution of an - // int3 instruction on x86 and a trap instruction on ARM, both of - // which raise SIGTRAP. 10.9.5 dyld-239.4/src/dyldStartup.s - // _dyld_fatal_error. This changed in 10.12 to use - // abort_with_payload(), which appears as SIGABRT to a waiting parent. - SetExpectedChildTermination( - kTerminationSignal, - MacOSXMinorVersion() < 12 ? SIGTRAP : SIGABRT); - break; - - default: - FAIL(); - break; - } } } diff --git a/snapshot/mac/mach_o_image_reader.cc b/snapshot/mac/mach_o_image_reader.cc index cd8f125e..6baee770 100644 --- a/snapshot/mac/mach_o_image_reader.cc +++ b/snapshot/mac/mach_o_image_reader.cc @@ -26,7 +26,7 @@ #include "client/crashpad_info.h" #include "snapshot/mac/mach_o_image_segment_reader.h" #include "snapshot/mac/mach_o_image_symbol_table_reader.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "util/mac/checked_mach_address_range.h" #include "util/misc/implicit_cast.h" @@ -62,7 +62,7 @@ MachOImageReader::MachOImageReader() MachOImageReader::~MachOImageReader() { } -bool MachOImageReader::Initialize(ProcessReader* process_reader, +bool MachOImageReader::Initialize(ProcessReaderMac* process_reader, mach_vm_address_t address, const std::string& name) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); @@ -481,24 +481,43 @@ bool MachOImageReader::GetCrashpadInfo( } if (crashpad_info_section->size < - crashpad_info->ExpectedSize(process_reader_)) { + crashpad_info->MinimumSize(process_reader_)) { LOG(WARNING) << "small crashpad info section size " << crashpad_info_section->size << module_info_; return false; } + // This Read() will zero out anything beyond the structure’s declared size. if (!crashpad_info->Read(process_reader_, crashpad_info_address)) { LOG(WARNING) << "could not read crashpad info" << module_info_; return false; } if (crashpad_info->signature != CrashpadInfo::kSignature || - crashpad_info->size != crashpad_info_section->size || - crashpad_info->version < 1) { - LOG(WARNING) << "unexpected crashpad info data" << module_info_; + crashpad_info->version != 1) { + LOG(WARNING) << base::StringPrintf( + "unexpected crashpad info signature 0x%x, version %u%s", + crashpad_info->signature, + crashpad_info->version, + module_info_.c_str()); return false; } + // Don’t require strict equality, to leave wiggle room for sloppy linkers. + if (crashpad_info->size > crashpad_info_section->size) { + LOG(WARNING) << "crashpad info struct size " << crashpad_info->size + << " large for section size " << crashpad_info_section->size + << module_info_; + return false; + } + + if (crashpad_info->size > crashpad_info->ExpectedSize(process_reader_)) { + // This isn’t strictly a problem, because unknown fields will simply be + // ignored, but it may be of diagnostic interest. + LOG(INFO) << "large crashpad info size " << crashpad_info->size + << module_info_; + } + return true; } diff --git a/snapshot/mac/mach_o_image_reader.h b/snapshot/mac/mach_o_image_reader.h index c16cce99..aded956c 100644 --- a/snapshot/mac/mach_o_image_reader.h +++ b/snapshot/mac/mach_o_image_reader.h @@ -33,7 +33,7 @@ namespace crashpad { class MachOImageSegmentReader; class MachOImageSymbolTableReader; -class ProcessReader; +class ProcessReaderMac; //! \brief A reader for Mach-O images mapped into another process. //! @@ -64,7 +64,7 @@ class MachOImageReader { //! //! \return `true` if the image was read successfully, including all load //! commands. `false` otherwise, with an appropriate message logged. - bool Initialize(ProcessReader* process_reader, + bool Initialize(ProcessReaderMac* process_reader, mach_vm_address_t address, const std::string& name); @@ -337,7 +337,7 @@ class MachOImageReader { mutable std::unique_ptr symbol_table_; std::unique_ptr id_dylib_command_; - ProcessReader* process_reader_; // weak + ProcessReaderMac* process_reader_; // weak uint32_t file_type_; InitializationStateDcheck initialized_; diff --git a/snapshot/mac/mach_o_image_reader_test.cc b/snapshot/mac/mach_o_image_reader_test.cc index d6b801f8..625f8a70 100644 --- a/snapshot/mac/mach_o_image_reader_test.cc +++ b/snapshot/mac/mach_o_image_reader_test.cc @@ -29,7 +29,7 @@ #include "client/crashpad_info.h" #include "gtest/gtest.h" #include "snapshot/mac/mach_o_image_segment_reader.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/mac/process_types.h" #include "test/mac/dyld.h" #include "util/misc/from_pointer_cast.h" @@ -496,7 +496,7 @@ void ExpectSymbolTable(const MachHeader* expect_image, } TEST(MachOImageReader, Self_MainExecutable) { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); const MachHeader* mh_execute_header = @@ -531,7 +531,7 @@ TEST(MachOImageReader, Self_MainExecutable) { } TEST(MachOImageReader, Self_DyldImages) { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); uint32_t count = _dyld_image_count(); diff --git a/snapshot/mac/mach_o_image_segment_reader.cc b/snapshot/mac/mach_o_image_segment_reader.cc index 06e1daf4..478b37d7 100644 --- a/snapshot/mac/mach_o_image_segment_reader.cc +++ b/snapshot/mac/mach_o_image_segment_reader.cc @@ -15,12 +15,13 @@ #include "snapshot/mac/mach_o_image_segment_reader.h" #include +#include #include #include "base/logging.h" #include "base/strings/stringprintf.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "util/mac/checked_mach_address_range.h" #include "util/mac/mac_util.h" #include "util/stdlib/strnlen.h" @@ -35,6 +36,37 @@ std::string SizeLimitedCString(const char* c_string, size_t max_length) { } // namespace +bool IsMalformedCLKernelsModule(uint32_t mach_o_file_type, + const std::string& module_name, + bool* has_timestamp) { + if (mach_o_file_type != MH_BUNDLE) { + return false; + } + + if (module_name == "cl_kernels") { + if (MacOSXMinorVersion() >= 10) { + if (has_timestamp) { + *has_timestamp = false; + } + return true; + } + return false; + } + + static const char kCvmsObjectPathPrefix[] = + "/private/var/db/CVMS/cvmsCodeSignObj"; + if (module_name.compare( + 0, strlen(kCvmsObjectPathPrefix), kCvmsObjectPathPrefix) == 0 && + MacOSXMinorVersion() >= 14) { + if (has_timestamp) { + *has_timestamp = true; + } + return true; + } + + return false; +} + MachOImageSegmentReader::MachOImageSegmentReader() : segment_command_(), sections_(), @@ -47,7 +79,7 @@ MachOImageSegmentReader::MachOImageSegmentReader() MachOImageSegmentReader::~MachOImageSegmentReader() { } -bool MachOImageSegmentReader::Initialize(ProcessReader* process_reader, +bool MachOImageSegmentReader::Initialize(ProcessReaderMac* process_reader, mach_vm_address_t load_command_address, const std::string& load_command_info, const std::string& module_name, @@ -121,21 +153,13 @@ bool MachOImageSegmentReader::Initialize(ProcessReader* process_reader, load_command_info.c_str()); // cl_kernels modules (for OpenCL) aren’t ld output, and they’re formatted - // incorrectly on OS X 10.10 and later. They have a single __TEXT segment, - // but one of the sections within it claims to belong to the __LD segment. - // This mismatch shouldn’t happen. This errant section also has the - // S_ATTR_DEBUG flag set, which shouldn’t happen unless all of the other - // sections in the segment also have this bit set (they don’t). These odd - // sections are reminiscent of unwind information stored in MH_OBJECT - // images, although cl_kernels images claim to be MH_BUNDLE. Because at - // least one cl_kernels module will commonly be found in a process, and - // sometimes more will be, tolerate this quirk. + // incorrectly on OS X 10.10 and later. Because at least one cl_kernels + // module will commonly be found in a process, and sometimes more will be, + // tolerate this quirk. // // https://openradar.appspot.com/20239912 if (section_segment_name != segment_name && - !(file_type == MH_BUNDLE && - module_name == "cl_kernels" && - MacOSXMinorVersion() >= 10 && + !(IsMalformedCLKernelsModule(file_type, module_name, nullptr) && segment_name == SEG_TEXT && section_segment_name == "__LD" && section_name == "__compact_unwind" && diff --git a/snapshot/mac/mach_o_image_segment_reader.h b/snapshot/mac/mach_o_image_segment_reader.h index 20f891da..1e72785d 100644 --- a/snapshot/mac/mach_o_image_segment_reader.h +++ b/snapshot/mac/mach_o_image_segment_reader.h @@ -29,6 +29,41 @@ namespace crashpad { +//! \brief Determines whether a module appears to be a malformed OpenCL +//! `cl_kernels` module based on its name and Mach-O file type. +//! +//! `cl_kernels` modules require special handling because they’re malformed on +//! OS X 10.10 and later. A `cl_kernels` module always has Mach-O type +//! `MH_BUNDLE` and is named `"cl_kernels"` until macOS 10.14, and +//! `"/private/var/db/CVMS/cvmsCodeSignObj"` plus 16 random characters on macOS +//! 10.14. +//! +//! Malformed `cl_kernels` modules have a single `__TEXT` segment, but one of +//! the sections within it claims to belong to the `__LD` segment. This mismatch +//! shouldn’t happen. This errant section also has the `S_ATTR_DEBUG` flag set, +//! which shouldn’t happen unless all of the other sections in the segment also +//! have this bit set (they don’t). These odd sections are reminiscent of unwind +//! information stored in `MH_OBJECT` images, although `cl_kernels` images claim +//! to be `MH_BUNDLE`. +//! +//! This function is exposed for testing purposes only. +//! +//! \param[in] mach_o_file_type The Mach-O type of the module being examined. +//! \param[in] module_name The pathname that `dyld` reported having loaded the +//! module from. +//! \param[out] has_timestamp Optional, may be `nullptr`. If provided, and the +//! module is a maformed `cl_kernels` module, this will be set to `true` if +//! the module was loaded from the filesystem (as is the case when loaded +//! from the CVMS directory) and is expected to have a timestamp, and +//! `false` otherwise. Note that even when loaded from the filesystem, these +//! modules are unlinked from the filesystem after loading. +//! +//! \return `true` if the module appears to be a malformed `cl_kernels` module +//! based on the provided information, `false` otherwise. +bool IsMalformedCLKernelsModule(uint32_t mach_o_file_type, + const std::string& module_name, + bool* has_timestamp); + //! \brief A reader for `LC_SEGMENT` or `LC_SEGMENT_64` load commands in Mach-O //! images mapped into another process. //! @@ -62,7 +97,7 @@ class MachOImageSegmentReader { //! //! \return `true` if the load command was read successfully. `false` //! otherwise, with an appropriate message logged. - bool Initialize(ProcessReader* process_reader, + bool Initialize(ProcessReaderMac* process_reader, mach_vm_address_t load_command_address, const std::string& load_command_info, const std::string& module_name, diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.cc b/snapshot/mac/mach_o_image_symbol_table_reader.cc index c5eb1969..361253ce 100644 --- a/snapshot/mac/mach_o_image_symbol_table_reader.cc +++ b/snapshot/mac/mach_o_image_symbol_table_reader.cc @@ -39,7 +39,7 @@ namespace internal { class MachOImageSymbolTableReaderInitializer { public: MachOImageSymbolTableReaderInitializer( - ProcessReader* process_reader, + ProcessReaderMac* process_reader, const MachOImageSegmentReader* linkedit_segment, const std::string& module_info) : module_info_(module_info), @@ -243,7 +243,7 @@ class MachOImageSymbolTableReaderInitializer { std::string module_info_; CheckedMachAddressRange linkedit_range_; - ProcessReader* process_reader_; // weak + ProcessReaderMac* process_reader_; // weak const MachOImageSegmentReader* linkedit_segment_; // weak DISALLOW_COPY_AND_ASSIGN(MachOImageSymbolTableReaderInitializer); @@ -259,7 +259,7 @@ MachOImageSymbolTableReader::~MachOImageSymbolTableReader() { } bool MachOImageSymbolTableReader::Initialize( - ProcessReader* process_reader, + ProcessReaderMac* process_reader, const process_types::symtab_command* symtab_command, const process_types::dysymtab_command* dysymtab_command, const MachOImageSegmentReader* linkedit_segment, diff --git a/snapshot/mac/mach_o_image_symbol_table_reader.h b/snapshot/mac/mach_o_image_symbol_table_reader.h index a097854b..841b479a 100644 --- a/snapshot/mac/mach_o_image_symbol_table_reader.h +++ b/snapshot/mac/mach_o_image_symbol_table_reader.h @@ -23,7 +23,7 @@ #include "base/macros.h" #include "snapshot/mac/mach_o_image_segment_reader.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/mac/process_types.h" #include "util/misc/initialization_state_dcheck.h" @@ -92,7 +92,7 @@ class MachOImageSymbolTableReader { //! //! \return `true` if the symbol table was read successfully. `false` //! otherwise, with an appropriate message logged. - bool Initialize(ProcessReader* process_reader, + bool Initialize(ProcessReaderMac* process_reader, const process_types::symtab_command* symtab_command, const process_types::dysymtab_command* dysymtab_command, const MachOImageSegmentReader* linkedit_segment, diff --git a/snapshot/mac/memory_snapshot_mac.cc b/snapshot/mac/memory_snapshot_mac.cc deleted file mode 100644 index adc77520..00000000 --- a/snapshot/mac/memory_snapshot_mac.cc +++ /dev/null @@ -1,70 +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 "snapshot/mac/memory_snapshot_mac.h" - -#include - -#include "util/mach/task_memory.h" - -namespace crashpad { -namespace internal { - -MemorySnapshotMac::MemorySnapshotMac() - : MemorySnapshot(), - process_reader_(nullptr), - address_(0), - size_(0), - initialized_() { -} - -MemorySnapshotMac::~MemorySnapshotMac() { -} - -void MemorySnapshotMac::Initialize(ProcessReader* process_reader, - uint64_t address, - uint64_t size) { - INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_reader_ = process_reader; - address_ = address; - size_ = size; - INITIALIZATION_STATE_SET_VALID(initialized_); -} - -uint64_t MemorySnapshotMac::Address() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return address_; -} - -size_t MemorySnapshotMac::Size() const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return size_; -} - -bool MemorySnapshotMac::Read(Delegate* delegate) const { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - - if (size_ == 0) { - return delegate->MemorySnapshotDelegateRead(nullptr, size_); - } - - std::unique_ptr buffer(new uint8_t[size_]); - if (!process_reader_->Memory()->Read(address_, size_, buffer.get())) { - return false; - } - return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); -} - -} // namespace internal -} // namespace crashpad diff --git a/snapshot/mac/memory_snapshot_mac.h b/snapshot/mac/memory_snapshot_mac.h deleted file mode 100644 index bbc39de3..00000000 --- a/snapshot/mac/memory_snapshot_mac.h +++ /dev/null @@ -1,68 +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_SNAPSHOT_MAC_MEMORY_SNAPSHOT_MAC_H_ -#define CRASHPAD_SNAPSHOT_MAC_MEMORY_SNAPSHOT_MAC_H_ - -#include -#include - -#include "base/macros.h" -#include "snapshot/mac/process_reader.h" -#include "snapshot/memory_snapshot.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 macOS. -class MemorySnapshotMac final : public MemorySnapshot { - public: - MemorySnapshotMac(); - ~MemorySnapshotMac() 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(ProcessReader* 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; - - private: - ProcessReader* process_reader_; // weak - uint64_t address_; - uint64_t size_; - InitializationStateDcheck initialized_; - - DISALLOW_COPY_AND_ASSIGN(MemorySnapshotMac); -}; - -} // namespace internal -} // namespace crashpad - -#endif // CRASHPAD_SNAPSHOT_MAC_MEMORY_SNAPSHOT_MAC_H_ diff --git a/snapshot/mac/module_snapshot_mac.cc b/snapshot/mac/module_snapshot_mac.cc index 2d750370..41608978 100644 --- a/snapshot/mac/module_snapshot_mac.cc +++ b/snapshot/mac/module_snapshot_mac.cc @@ -41,8 +41,8 @@ ModuleSnapshotMac::~ModuleSnapshotMac() { } bool ModuleSnapshotMac::Initialize( - ProcessReader* process_reader, - const ProcessReader::Module& process_reader_module) { + ProcessReaderMac* process_reader, + const ProcessReaderMac::Module& process_reader_module) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); process_reader_ = process_reader; @@ -187,8 +187,9 @@ std::map ModuleSnapshotMac::AnnotationsSimpleMap() std::vector ModuleSnapshotMac::AnnotationObjects() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); - return {}; + MachOImageAnnotationsReader annotations_reader( + process_reader_, mach_o_image_reader_, name_); + return annotations_reader.AnnotationsList(); } std::set> ModuleSnapshotMac::ExtraMemoryRanges() const { diff --git a/snapshot/mac/module_snapshot_mac.h b/snapshot/mac/module_snapshot_mac.h index 44c07910..fe2d40a1 100644 --- a/snapshot/mac/module_snapshot_mac.h +++ b/snapshot/mac/module_snapshot_mac.h @@ -25,7 +25,7 @@ #include "base/macros.h" #include "client/crashpad_info.h" #include "snapshot/crashpad_info_client_options.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/module_snapshot.h" #include "util/misc/initialization_state_dcheck.h" @@ -45,15 +45,15 @@ class ModuleSnapshotMac final : public ModuleSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the task containing the + //! \param[in] process_reader A ProcessReaderMac for the task containing the //! module. - //! \param[in] process_reader_module The module within the ProcessReader for - //! which the snapshot should be created. + //! \param[in] process_reader_module The module within the ProcessReaderMac + //! for which the snapshot should be created. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(ProcessReader* process_reader, - const ProcessReader::Module& process_reader_module); + bool Initialize(ProcessReaderMac* process_reader, + const ProcessReaderMac::Module& process_reader_module); //! \brief Returns options from the module’s CrashpadInfo structure. //! @@ -87,7 +87,7 @@ class ModuleSnapshotMac final : public ModuleSnapshot { std::string name_; time_t timestamp_; const MachOImageReader* mach_o_image_reader_; // weak - ProcessReader* process_reader_; // weak + ProcessReaderMac* process_reader_; // weak InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotMac); diff --git a/snapshot/mac/process_reader.cc b/snapshot/mac/process_reader_mac.cc similarity index 93% rename from snapshot/mac/process_reader.cc rename to snapshot/mac/process_reader_mac.cc index d38ab144..e142fd2e 100644 --- a/snapshot/mac/process_reader.cc +++ b/snapshot/mac/process_reader_mac.cc @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include -#include #include +#include #include #include @@ -71,7 +71,7 @@ kern_return_t MachVMRegionRecurseDeepest(task_t task, namespace crashpad { -ProcessReader::Thread::Thread() +ProcessReaderMac::Thread::Thread() : thread_context(), float_context(), debug_context(), @@ -81,16 +81,13 @@ ProcessReader::Thread::Thread() thread_specific_data_address(0), port(THREAD_NULL), suspend_count(0), - priority(0) { -} + priority(0) {} -ProcessReader::Module::Module() : name(), reader(nullptr), timestamp(0) { -} +ProcessReaderMac::Module::Module() : name(), reader(nullptr), timestamp(0) {} -ProcessReader::Module::~Module() { -} +ProcessReaderMac::Module::~Module() {} -ProcessReader::ProcessReader() +ProcessReaderMac::ProcessReaderMac() : process_info_(), threads_(), modules_(), @@ -100,17 +97,16 @@ ProcessReader::ProcessReader() initialized_(), is_64_bit_(false), initialized_threads_(false), - initialized_modules_(false) { -} + initialized_modules_(false) {} -ProcessReader::~ProcessReader() { +ProcessReaderMac::~ProcessReaderMac() { for (const Thread& thread : threads_) { kern_return_t kr = mach_port_deallocate(mach_task_self(), thread.port); MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_port_deallocate"; } } -bool ProcessReader::Initialize(task_t task) { +bool ProcessReaderMac::Initialize(task_t task) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (!process_info_.InitializeWithTask(task)) { @@ -126,12 +122,13 @@ bool ProcessReader::Initialize(task_t task) { return true; } -void ProcessReader::StartTime(timeval* start_time) const { +void ProcessReaderMac::StartTime(timeval* start_time) const { bool rv = process_info_.StartTime(start_time); DCHECK(rv); } -bool ProcessReader::CPUTimes(timeval* user_time, timeval* system_time) const { +bool ProcessReaderMac::CPUTimes(timeval* user_time, + timeval* system_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // Calculate user and system time the same way the kernel does for @@ -177,7 +174,7 @@ bool ProcessReader::CPUTimes(timeval* user_time, timeval* system_time) const { return true; } -const std::vector& ProcessReader::Threads() { +const std::vector& ProcessReaderMac::Threads() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!initialized_threads_) { @@ -187,7 +184,7 @@ const std::vector& ProcessReader::Threads() { return threads_; } -const std::vector& ProcessReader::Modules() { +const std::vector& ProcessReaderMac::Modules() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!initialized_modules_) { @@ -197,7 +194,7 @@ const std::vector& ProcessReader::Modules() { return modules_; } -mach_vm_address_t ProcessReader::DyldAllImageInfo( +mach_vm_address_t ProcessReaderMac::DyldAllImageInfo( mach_vm_size_t* all_image_info_size) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); @@ -210,9 +207,9 @@ mach_vm_address_t ProcessReader::DyldAllImageInfo( return 0; } - // TODO(mark): Deal with statically linked executables which don’t use dyld. - // This may look for the module that matches the executable path in the same - // data set that vmmap uses. +// TODO(mark): Deal with statically linked executables which don’t use dyld. +// This may look for the module that matches the executable path in the same +// data set that vmmap uses. #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 // The task_dyld_info_data_t struct grew in 10.7, adding the format field. @@ -237,7 +234,7 @@ mach_vm_address_t ProcessReader::DyldAllImageInfo( return dyld_info.all_image_info_addr; } -void ProcessReader::InitializeThreads() { +void ProcessReaderMac::InitializeThreads() { DCHECK(!initialized_threads_); DCHECK(threads_.empty()); @@ -378,7 +375,7 @@ void ProcessReader::InitializeThreads() { threads_need_owners.Disarm(); } -void ProcessReader::InitializeModules() { +void ProcessReaderMac::InitializeModules() { DCHECK(!initialized_modules_); DCHECK(modules_.empty()); @@ -465,8 +462,8 @@ void ProcessReader::InitializeModules() { image_info.imageLoadAddress == all_image_infos.dyldImageLoadAddress) { found_dyld = true; LOG(WARNING) << base::StringPrintf( - "found dylinker (%s) in dyld_all_image_infos::infoArray", - module.name.c_str()); + "found dylinker (%s) in dyld_all_image_infos::infoArray", + module.name.c_str()); LOG_IF(WARNING, file_type != MH_DYLINKER) << base::StringPrintf("dylinker (%s) has unexpected Mach-O type %d", @@ -563,7 +560,7 @@ void ProcessReader::InitializeModules() { } } -mach_vm_address_t ProcessReader::CalculateStackRegion( +mach_vm_address_t ProcessReaderMac::CalculateStackRegion( mach_vm_address_t stack_pointer, mach_vm_size_t* stack_region_size) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); @@ -675,15 +672,15 @@ mach_vm_address_t ProcessReader::CalculateStackRegion( return region_base; } -void ProcessReader::LocateRedZone(mach_vm_address_t* const start_address, - mach_vm_address_t* const region_base, - mach_vm_address_t* const region_size, - const unsigned int user_tag) { +void ProcessReaderMac::LocateRedZone(mach_vm_address_t* const start_address, + mach_vm_address_t* const region_base, + mach_vm_address_t* const region_size, + const unsigned int user_tag) { #if defined(ARCH_CPU_X86_FAMILY) if (Is64Bit()) { - // x86_64 has a red zone. See AMD64 ABI 0.99.6, - // http://www.x86-64.org/documentation/abi.pdf, section 3.2.2, “The Stack - // Frame”. + // 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”. constexpr mach_vm_size_t kRedZoneSize = 128; mach_vm_address_t red_zone_base = *start_address >= kRedZoneSize ? *start_address - kRedZoneSize : 0; diff --git a/snapshot/mac/process_reader.h b/snapshot/mac/process_reader_mac.h similarity index 96% rename from snapshot/mac/process_reader.h rename to snapshot/mac/process_reader_mac.h index ecca2a5a..91836dbb 100644 --- a/snapshot/mac/process_reader.h +++ b/snapshot/mac/process_reader_mac.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_H_ -#define CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_H_ +#ifndef CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_ +#define CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_ #include #include @@ -37,7 +37,7 @@ class MachOImageReader; //! \brief Accesses information about another process, identified by a Mach //! task. -class ProcessReader { +class ProcessReaderMac { public: //! \brief Contains information about a thread that belongs to a task //! (process). @@ -83,7 +83,7 @@ class ProcessReader { //! \brief An image reader for the module. //! //! The lifetime of this MachOImageReader is scoped to the lifetime of the - //! ProcessReader that created it. + //! ProcessReaderMac that created it. //! //! This field may be `nullptr` if a reader could not be created for the //! module. @@ -97,8 +97,8 @@ class ProcessReader { time_t timestamp; }; - ProcessReader(); - ~ProcessReader(); + ProcessReaderMac(); + ~ProcessReaderMac(); //! \brief Initializes this object. This method must be called before any //! other. @@ -244,9 +244,9 @@ class ProcessReader { bool initialized_threads_; bool initialized_modules_; - DISALLOW_COPY_AND_ASSIGN(ProcessReader); + DISALLOW_COPY_AND_ASSIGN(ProcessReaderMac); }; } // namespace crashpad -#endif // CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_H_ +#endif // CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_ diff --git a/snapshot/mac/process_reader_test.cc b/snapshot/mac/process_reader_mac_test.cc similarity index 86% rename from snapshot/mac/process_reader_test.cc rename to snapshot/mac/process_reader_mac_test.cc index c9f39e71..59884a6d 100644 --- a/snapshot/mac/process_reader_test.cc +++ b/snapshot/mac/process_reader_mac_test.cc @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include +#include #include #include #include -#include #include #include @@ -32,6 +32,7 @@ #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/mac/mach_o_image_reader.h" +#include "snapshot/mac/mach_o_image_segment_reader.h" #include "test/errors.h" #include "test/mac/dyld.h" #include "test/mac/mach_errors.h" @@ -48,8 +49,8 @@ namespace { constexpr char kDyldPath[] = "/usr/lib/dyld"; -TEST(ProcessReader, SelfBasic) { - ProcessReader process_reader; +TEST(ProcessReaderMac, SelfBasic) { + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); #if !defined(ARCH_CPU_64_BITS) @@ -80,7 +81,7 @@ class ProcessReaderChild final : public MachMultiprocess { private: void MachMultiprocessParent() override { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); #if !defined(ARCH_CPU_64_BITS) @@ -116,7 +117,7 @@ class ProcessReaderChild final : public MachMultiprocess { DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild); }; -TEST(ProcessReader, ChildBasic) { +TEST(ProcessReaderMac, ChildBasic) { ProcessReaderChild process_reader_child; process_reader_child.Run(); } @@ -131,11 +132,12 @@ uint64_t PthreadToThreadID(pthread_t pthread) { return thread_id; } -TEST(ProcessReader, SelfOneThread) { - ProcessReader process_reader; +TEST(ProcessReaderMac, SelfOneThread) { + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); - const std::vector& threads = process_reader.Threads(); + const std::vector& threads = + process_reader.Threads(); // If other tests ran in this process previously, threads may have been // created and may still be running. This check must look for at least one @@ -157,8 +159,7 @@ class TestThreadPool { int suspend_count; }; - TestThreadPool() : thread_infos_() { - } + TestThreadPool() : thread_infos_() {} // Resumes suspended threads, signals each thread’s exit semaphore asking it // to exit, and joins each thread, blocking until they have all exited. @@ -192,10 +193,8 @@ class TestThreadPool { thread_infos_.push_back(std::make_unique()); ThreadInfo* thread_info = thread_infos_.back().get(); - int rv = pthread_create(&thread_info->pthread, - nullptr, - ThreadMain, - thread_info); + int rv = pthread_create( + &thread_info->pthread, nullptr, ThreadMain, thread_info); ASSERT_EQ(rv, 0); } @@ -210,8 +209,7 @@ class TestThreadPool { ++thread_index) { thread_t thread_port = pthread_mach_thread_np(thread_infos_[thread_index]->pthread); - for (size_t suspend_count = 0; - suspend_count < thread_index; + for (size_t suspend_count = 0; suspend_count < thread_index; ++suspend_count) { kern_return_t kr = thread_suspend(thread_port); EXPECT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "thread_suspend"); @@ -222,8 +220,7 @@ class TestThreadPool { } } - uint64_t GetThreadInfo(size_t thread_index, - ThreadExpectation* expectation) { + uint64_t GetThreadInfo(size_t thread_index, ThreadExpectation* expectation) { CHECK_LT(thread_index, thread_infos_.size()); const auto& thread_info = thread_infos_[thread_index]; @@ -240,8 +237,7 @@ class TestThreadPool { stack_address(0), ready_semaphore(0), exit_semaphore(0), - suspend_count(0) { - } + suspend_count(0) {} ~ThreadInfo() {} @@ -294,14 +290,14 @@ class TestThreadPool { using ThreadMap = std::map; -// Verifies that all of the threads in |threads|, obtained from ProcessReader, -// agree with the expectation in |thread_map|. If |tolerate_extra_threads| is -// true, |threads| is allowed to contain threads that are not listed in -// |thread_map|. This is useful when testing situations where code outside of -// the test’s control (such as system libraries) may start threads, or may have -// started threads prior to a test’s execution. +// Verifies that all of the threads in |threads|, obtained from +// ProcessReaderMac, agree with the expectation in |thread_map|. If +// |tolerate_extra_threads| is true, |threads| is allowed to contain threads +// that are not listed in |thread_map|. This is useful when testing situations +// where code outside of the test’s control (such as system libraries) may start +// threads, or may have started threads prior to a test’s execution. void ExpectSeveralThreads(ThreadMap* thread_map, - const std::vector& threads, + const std::vector& threads, const bool tolerate_extra_threads) { if (tolerate_extra_threads) { ASSERT_GE(threads.size(), thread_map->size()); @@ -310,7 +306,7 @@ void ExpectSeveralThreads(ThreadMap* thread_map, } for (size_t thread_index = 0; thread_index < threads.size(); ++thread_index) { - const ProcessReader::Thread& thread = threads[thread_index]; + const ProcessReaderMac::Thread& thread = threads[thread_index]; mach_vm_address_t thread_stack_region_end = thread.stack_region_address + thread.stack_region_size; @@ -336,26 +332,26 @@ void ExpectSeveralThreads(ThreadMap* thread_map, // with any other thread’s. Each thread should have a unique value for its // ID and port, and each should have its own stack that doesn’t touch any // other thread’s stack. - for (size_t other_thread_index = 0; - other_thread_index < threads.size(); + for (size_t other_thread_index = 0; other_thread_index < threads.size(); ++other_thread_index) { if (other_thread_index == thread_index) { continue; } - const ProcessReader::Thread& other_thread = threads[other_thread_index]; + const ProcessReaderMac::Thread& other_thread = + threads[other_thread_index]; EXPECT_NE(other_thread.id, thread.id); EXPECT_NE(other_thread.port, thread.port); mach_vm_address_t other_thread_stack_region_end = other_thread.stack_region_address + other_thread.stack_region_size; - EXPECT_FALSE( - thread.stack_region_address >= other_thread.stack_region_address && - thread.stack_region_address < other_thread_stack_region_end); - EXPECT_FALSE( - thread_stack_region_end > other_thread.stack_region_address && - thread_stack_region_end <= other_thread_stack_region_end); + EXPECT_FALSE(thread.stack_region_address >= + other_thread.stack_region_address && + thread.stack_region_address < other_thread_stack_region_end); + EXPECT_FALSE(thread_stack_region_end > + other_thread.stack_region_address && + thread_stack_region_end <= other_thread_stack_region_end); } } @@ -363,12 +359,12 @@ void ExpectSeveralThreads(ThreadMap* thread_map, EXPECT_TRUE(thread_map->empty()); } -TEST(ProcessReader, SelfSeveralThreads) { - // Set up the ProcessReader here, before any other threads are running. This - // tests that the threads it returns are lazily initialized as a snapshot of - // the threads at the time of the first call to Threads(), and not at the +TEST(ProcessReaderMac, SelfSeveralThreads) { + // Set up the ProcessReaderMac here, before any other threads are running. + // This tests that the threads it returns are lazily initialized as a snapshot + // of the threads at the time of the first call to Threads(), and not at the // time the ProcessReader was created or initialized. - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); TestThreadPool thread_pool; @@ -392,7 +388,8 @@ TEST(ProcessReader, SelfSeveralThreads) { thread_map[thread_id] = expectation; } - const std::vector& threads = process_reader.Threads(); + const std::vector& threads = + process_reader.Threads(); // Other tests that have run previously may have resulted in the creation of // threads that still exist, so pass true for |tolerate_extra_threads|. @@ -403,7 +400,7 @@ TEST(ProcessReader, SelfSeveralThreads) { // shows up once. thread_t thread_self = MachThreadSelf(); bool found_thread_self = false; - for (const ProcessReader::Thread& thread : threads) { + for (const ProcessReaderMac::Thread& thread : threads) { if (thread.port == thread_self) { EXPECT_FALSE(found_thread_self); found_thread_self = true; @@ -416,15 +413,13 @@ TEST(ProcessReader, SelfSeveralThreads) { class ProcessReaderThreadedChild final : public MachMultiprocess { public: explicit ProcessReaderThreadedChild(size_t thread_count) - : MachMultiprocess(), - thread_count_(thread_count) { - } + : MachMultiprocess(), thread_count_(thread_count) {} ~ProcessReaderThreadedChild() {} private: void MachMultiprocessParent() override { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); FileHandle read_handle = ReadPipeHandle(); @@ -433,8 +428,7 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { // addresses that should lie somewhere within each thread’s stack as values. // These IDs and addresses all come from the child process via the pipe. ThreadMap thread_map; - for (size_t thread_index = 0; - thread_index < thread_count_ + 1; + for (size_t thread_index = 0; thread_index < thread_count_ + 1; ++thread_index) { uint64_t thread_id; CheckedReadFileExactly(read_handle, &thread_id, sizeof(thread_id)); @@ -453,7 +447,8 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { thread_map[thread_id] = expectation; } - const std::vector& threads = process_reader.Threads(); + const std::vector& threads = + process_reader.Threads(); // The child shouldn’t have any threads other than its main thread and the // ones it created in its pool, so pass false for |tolerate_extra_threads|. @@ -484,8 +479,7 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { sizeof(expectation.suspend_count)); // Write an entry for everything in the thread pool. - for (size_t thread_index = 0; - thread_index < thread_count_; + for (size_t thread_index = 0; thread_index < thread_count_; ++thread_index) { uint64_t thread_id = thread_pool.GetThreadInfo(thread_index, &expectation); @@ -509,14 +503,14 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { DISALLOW_COPY_AND_ASSIGN(ProcessReaderThreadedChild); }; -TEST(ProcessReader, ChildOneThread) { +TEST(ProcessReaderMac, ChildOneThread) { // The main thread plus zero child threads equals one thread. constexpr size_t kChildThreads = 0; ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); process_reader_threaded_child.Run(); } -TEST(ProcessReader, ChildSeveralThreads) { +TEST(ProcessReaderMac, ChildSeveralThreads) { constexpr size_t kChildThreads = 64; ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); process_reader_threaded_child.Run(); @@ -537,10 +531,7 @@ TEST(ProcessReader, ChildSeveralThreads) { class ScopedOpenCLNoOpKernel { public: ScopedOpenCLNoOpKernel() - : context_(nullptr), - program_(nullptr), - kernel_(nullptr) { - } + : context_(nullptr), program_(nullptr), kernel_(nullptr) {} ~ScopedOpenCLNoOpKernel() { if (kernel_) { @@ -566,10 +557,10 @@ class ScopedOpenCLNoOpKernel { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 && \ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 - // cl_device_id is really available in OpenCL.framework back to 10.5, but in - // the 10.10 SDK and later, OpenCL.framework includes , - // which has its own cl_device_id that was introduced in 10.10. That - // triggers erroneous availability warnings. +// cl_device_id is really available in OpenCL.framework back to 10.5, but in +// the 10.10 SDK and later, OpenCL.framework includes , +// which has its own cl_device_id that was introduced in 10.10. That +// triggers erroneous availability warnings. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" #define DISABLED_WUNGUARDED_AVAILABILITY @@ -642,15 +633,16 @@ bool ExpectCLKernels() { #endif } -TEST(ProcessReader, SelfModules) { +TEST(ProcessReaderMac, SelfModules) { ScopedOpenCLNoOpKernel ensure_cl_kernels; ASSERT_NO_FATAL_FAILURE(ensure_cl_kernels.SetUp()); - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); uint32_t dyld_image_count = _dyld_image_count(); - const std::vector& modules = process_reader.Modules(); + const std::vector& modules = + process_reader.Modules(); // There needs to be at least an entry for the main executable, for a dylib, // and for dyld. @@ -672,14 +664,20 @@ TEST(ProcessReader, SelfModules) { modules[index].reader->Address(), FromPointerCast(_dyld_get_image_header(index))); + bool expect_timestamp; if (index == 0) { // dyld didn’t load the main executable, so it couldn’t record its // timestamp, and it is reported as 0. EXPECT_EQ(modules[index].timestamp, 0); - } else if (modules[index].reader->FileType() == MH_BUNDLE && - modules[index].name == "cl_kernels") { - // cl_kernels doesn’t exist as a file. - EXPECT_EQ(modules[index].timestamp, 0); + } else if (IsMalformedCLKernelsModule(modules[index].reader->FileType(), + modules[index].name, + &expect_timestamp)) { + // cl_kernels doesn’t exist as a file, but may still have a timestamp. + if (!expect_timestamp) { + EXPECT_EQ(modules[index].timestamp, 0); + } else { + EXPECT_NE(modules[index].timestamp, 0); + } found_cl_kernels = true; } else { // Hope that the module didn’t change on disk. @@ -718,10 +716,10 @@ class ProcessReaderModulesChild final : public MachMultiprocess { private: void MachMultiprocessParent() override { - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); - const std::vector& modules = + const std::vector& modules = process_reader.Modules(); // There needs to be at least an entry for the main executable, for a dylib, @@ -756,14 +754,20 @@ class ProcessReaderModulesChild final : public MachMultiprocess { ASSERT_TRUE(modules[index].reader); EXPECT_EQ(modules[index].reader->Address(), expect_address); + bool expect_timestamp; if (index == 0 || index == modules.size() - 1) { // dyld didn’t load the main executable or itself, so it couldn’t record // these timestamps, and they are reported as 0. EXPECT_EQ(modules[index].timestamp, 0); - } else if (modules[index].reader->FileType() == MH_BUNDLE && - modules[index].name == "cl_kernels") { - // cl_kernels doesn’t exist as a file. - EXPECT_EQ(modules[index].timestamp, 0); + } else if (IsMalformedCLKernelsModule(modules[index].reader->FileType(), + modules[index].name, + &expect_timestamp)) { + // cl_kernels doesn’t exist as a file, but may still have a timestamp. + if (!expect_timestamp) { + EXPECT_EQ(modules[index].timestamp, 0); + } else { + EXPECT_NE(modules[index].timestamp, 0); + } found_cl_kernels = true; } else { // Hope that the module didn’t change on disk. @@ -829,7 +833,7 @@ class ProcessReaderModulesChild final : public MachMultiprocess { DISALLOW_COPY_AND_ASSIGN(ProcessReaderModulesChild); }; -TEST(ProcessReader, ChildModules) { +TEST(ProcessReaderMac, ChildModules) { ScopedOpenCLNoOpKernel ensure_cl_kernels; ASSERT_NO_FATAL_FAILURE(ensure_cl_kernels.SetUp()); diff --git a/snapshot/mac/process_snapshot_mac.cc b/snapshot/mac/process_snapshot_mac.cc index eaaf3dca..cf5233a9 100644 --- a/snapshot/mac/process_snapshot_mac.cc +++ b/snapshot/mac/process_snapshot_mac.cc @@ -218,9 +218,9 @@ std::vector ProcessSnapshotMac::ExtraMemory() const { } void ProcessSnapshotMac::InitializeThreads() { - const std::vector& process_reader_threads = + const std::vector& process_reader_threads = process_reader_.Threads(); - for (const ProcessReader::Thread& process_reader_thread : + for (const ProcessReaderMac::Thread& process_reader_thread : process_reader_threads) { auto thread = std::make_unique(); if (thread->Initialize(&process_reader_, process_reader_thread)) { @@ -230,9 +230,9 @@ void ProcessSnapshotMac::InitializeThreads() { } void ProcessSnapshotMac::InitializeModules() { - const std::vector& process_reader_modules = + const std::vector& process_reader_modules = process_reader_.Modules(); - for (const ProcessReader::Module& process_reader_module : + for (const ProcessReaderMac::Module& process_reader_module : process_reader_modules) { auto module = std::make_unique(); if (module->Initialize(&process_reader_, process_reader_module)) { diff --git a/snapshot/mac/process_snapshot_mac.h b/snapshot/mac/process_snapshot_mac.h index e7195d94..06bac749 100644 --- a/snapshot/mac/process_snapshot_mac.h +++ b/snapshot/mac/process_snapshot_mac.h @@ -30,7 +30,7 @@ #include "snapshot/exception_snapshot.h" #include "snapshot/mac/exception_snapshot_mac.h" #include "snapshot/mac/module_snapshot_mac.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/mac/system_snapshot_mac.h" #include "snapshot/mac/thread_snapshot_mac.h" #include "snapshot/memory_map_region_snapshot.h" @@ -143,7 +143,7 @@ class ProcessSnapshotMac final : public ProcessSnapshot { std::vector> threads_; std::vector> modules_; std::unique_ptr exception_; - ProcessReader process_reader_; + ProcessReaderMac process_reader_; UUID report_id_; UUID client_id_; std::map annotations_simple_map_; diff --git a/snapshot/mac/process_types.cc b/snapshot/mac/process_types.cc index 90638fbb..65c39ea5 100644 --- a/snapshot/mac/process_types.cc +++ b/snapshot/mac/process_types.cc @@ -14,6 +14,7 @@ #include "snapshot/mac/process_types.h" +#include #include #include @@ -93,7 +94,7 @@ inline void Assign(UInt64Array4* destination, namespace process_types { \ \ /* static */ \ - size_t struct_name::ExpectedSize(ProcessReader* process_reader) { \ + size_t struct_name::ExpectedSize(ProcessReaderMac* process_reader) { \ if (!process_reader->Is64Bit()) { \ return internal::struct_name::Size(); \ } else { \ @@ -102,7 +103,7 @@ inline void Assign(UInt64Array4* destination, } \ \ /* static */ \ - bool struct_name::ReadInto(ProcessReader* process_reader, \ + bool struct_name::ReadInto(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ struct_name* generic) { \ if (!process_reader->Is64Bit()) { \ @@ -116,7 +117,7 @@ inline void Assign(UInt64Array4* destination, \ /* static */ \ template \ - bool struct_name::ReadIntoInternal(ProcessReader* process_reader, \ + bool struct_name::ReadIntoInternal(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ struct_name* generic) { \ T specific; \ @@ -140,6 +141,8 @@ inline void Assign(UInt64Array4* destination, #define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) + #define PROCESS_TYPE_STRUCT_END(struct_name) \ } \ } /* namespace internal */ \ @@ -151,6 +154,7 @@ inline void Assign(UInt64Array4* destination, #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_IMPLEMENT @@ -162,27 +166,29 @@ inline void Assign(UInt64Array4* destination, // implementations in snapshot/mac/process_types/custom.cc. #define PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO 1 -#define PROCESS_TYPE_STRUCT_BEGIN(struct_name) \ - namespace crashpad { \ - namespace process_types { \ - namespace internal { \ - \ - /* static */ \ - template \ - bool struct_name::ReadInto(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - struct_name* specific) { \ - return process_reader->Memory()->Read( \ - address, sizeof(*specific), specific); \ - } \ - } /* namespace internal */ \ - } /* namespace process_types */ \ - } /* namespace crashpad */ +#define PROCESS_TYPE_STRUCT_BEGIN(struct_name) \ + namespace crashpad { \ + namespace process_types { \ + namespace internal { \ + \ + /* static */ \ + template \ + bool struct_name::ReadInto(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + struct_name* specific) { \ + return process_reader->Memory()->Read( \ + address, sizeof(*specific), specific); \ + } \ + } /* namespace internal */ \ + } /* namespace process_types */ \ + } /* namespace crashpad */ #define PROCESS_TYPE_STRUCT_MEMBER(member_type, member_name, ...) #define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) + #define PROCESS_TYPE_STRUCT_END(struct_name) #include "snapshot/mac/process_types/all.proctype" @@ -190,6 +196,7 @@ inline void Assign(UInt64Array4* destination, #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO @@ -207,7 +214,7 @@ inline void Assign(UInt64Array4* destination, \ /* static */ \ template \ - bool struct_name::ReadArrayInto(ProcessReader* process_reader, \ + bool struct_name::ReadArrayInto(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ size_t count, \ struct_name* specific) { \ @@ -218,7 +225,7 @@ inline void Assign(UInt64Array4* destination, } /* namespace internal */ \ \ /* static */ \ - bool struct_name::ReadArrayInto(ProcessReader* process_reader, \ + bool struct_name::ReadArrayInto(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ size_t count, \ struct_name* generic) { \ @@ -234,7 +241,7 @@ inline void Assign(UInt64Array4* destination, \ /* static */ \ template \ - bool struct_name::ReadArrayIntoInternal(ProcessReader* process_reader, \ + bool struct_name::ReadArrayIntoInternal(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ size_t count, \ struct_name* generic) { \ @@ -254,6 +261,8 @@ inline void Assign(UInt64Array4* destination, #define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) + #define PROCESS_TYPE_STRUCT_END(struct_name) #include "snapshot/mac/process_types/all.proctype" @@ -261,6 +270,7 @@ inline void Assign(UInt64Array4* destination, #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_IMPLEMENT_ARRAY @@ -283,7 +293,7 @@ inline void Assign(UInt64Array4* destination, \ /* static */ \ size_t struct_name::ExpectedSizeForVersion( \ - ProcessReader* process_reader, \ + ProcessReaderMac* process_reader, \ decltype(struct_name::version_field) version) { \ if (!process_reader->Is64Bit()) { \ return internal::struct_name< \ @@ -294,8 +304,10 @@ inline void Assign(UInt64Array4* destination, } \ } \ \ - } /* namespace process_types */ \ - } /* namespace crashpad */ + } /* namespace process_types */ \ + } /* namespace crashpad */ + +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) #define PROCESS_TYPE_STRUCT_END(struct_name) @@ -304,5 +316,62 @@ inline void Assign(UInt64Array4* destination, #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_IMPLEMENT_VERSIONED + +// Implement the generic crashpad::process_types::struct_name MinimumSize() and +// its templatized equivalent. The generic version delegates to the templatized +// one, which returns the minimum size of a sized structure. This can be used to +// ensure that enough of a sized structure is available to interpret its size +// field. This is only implemented for structures that use +// PROCESS_TYPE_STRUCT_SIZED(). +#define PROCESS_TYPE_STRUCT_IMPLEMENT_SIZED 1 + +#define PROCESS_TYPE_STRUCT_BEGIN(struct_name) + +#define PROCESS_TYPE_STRUCT_MEMBER(member_type, member_name, ...) + +#define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) + +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) \ + namespace crashpad { \ + namespace process_types { \ + \ + namespace internal { \ + \ + /* static */ \ + template \ + size_t struct_name::MinimumSize() { \ + return offsetof(struct_name, size_field) + \ + sizeof(struct_name::size_field); \ + } \ + \ + /* Explicit instantiations of the above. */ \ + template size_t struct_name::MinimumSize(); \ + template size_t struct_name::MinimumSize(); \ + \ + } /* namespace internal */ \ + \ + /* static */ \ + size_t struct_name::MinimumSize(ProcessReaderMac* process_reader) { \ + if (!process_reader->Is64Bit()) { \ + return internal::struct_name::MinimumSize(); \ + } else { \ + return internal::struct_name::MinimumSize(); \ + } \ + } \ + \ + } /* namespace process_types */ \ + } /* namespace crashpad */ + +#define PROCESS_TYPE_STRUCT_END(struct_name) + +#include "snapshot/mac/process_types/all.proctype" + +#undef PROCESS_TYPE_STRUCT_BEGIN +#undef PROCESS_TYPE_STRUCT_MEMBER +#undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED +#undef PROCESS_TYPE_STRUCT_END +#undef PROCESS_TYPE_STRUCT_IMPLEMENT_SIZED diff --git a/snapshot/mac/process_types.h b/snapshot/mac/process_types.h index eb397b9b..6a5f9c1c 100644 --- a/snapshot/mac/process_types.h +++ b/snapshot/mac/process_types.h @@ -21,7 +21,7 @@ #include #include -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" namespace crashpad { namespace process_types { @@ -80,66 +80,77 @@ DECLARE_PROCESS_TYPE_TRAITS_CLASS(Generic, 64) \ /* Initializes an object with data read from |process_reader| at \ * |address|, properly genericized. */ \ - bool Read(ProcessReader* process_reader, mach_vm_address_t address) { \ + bool Read(ProcessReaderMac* process_reader, mach_vm_address_t address) { \ return ReadInto(process_reader, address, this); \ } \ \ /* Reads |count| objects from |process_reader| beginning at |address|, and \ * genericizes the objects. The caller must provide storage for |count| \ * objects in |generic|. */ \ - static bool ReadArrayInto(ProcessReader* process_reader, \ + static bool ReadArrayInto(ProcessReaderMac* process_reader, \ mach_vm_address_t address, \ size_t count, \ struct_name* generic); \ \ /* Returns the size of the object that was read. This is the size of the \ * storage in the process that the data is read from, and is not the same \ - * as the size of the generic struct. */ \ + * as the size of the generic struct. For versioned and sized structures, \ + * the size of the full structure is returned. */ \ size_t Size() const { return size_; } \ \ /* Similar to Size(), but computes the expected size of a structure based \ * on the process’ bitness. This can be used prior to reading any data \ - * from a process. */ \ - static size_t ExpectedSize(ProcessReader* process_reader); + * from a process. For versioned and sized structures, \ + * ExpectedSizeForVersion() and MinimumSize() may also be useful. */ \ + static size_t ExpectedSize(ProcessReaderMac* process_reader); #define PROCESS_TYPE_STRUCT_MEMBER(member_type, member_name, ...) \ member_type member_name __VA_ARGS__; -#define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) \ - /* Similar to ExpectedSize(), but computes the expected size of a \ - * structure based on the process’ bitness and a custom value, such as a \ - * structure version number. This can be used prior to reading any data \ - * from a process. */ \ - static size_t ExpectedSizeForVersion( \ - ProcessReader* process_reader, \ - decltype(struct_name::version_field) version); +#define PROCESS_TYPE_STRUCT_VERSIONED(struct_name, version_field) \ + /* Similar to ExpectedSize(), but computes the expected size of a \ + * structure based on the process’ bitness and a custom value, such as a \ + * structure version number. This can be used prior to reading any data \ + * from a process. */ \ + static size_t ExpectedSizeForVersion( \ + ProcessReaderMac* process_reader, \ + decltype(struct_name::version_field) version); -#define PROCESS_TYPE_STRUCT_END(struct_name) \ - private: \ - /* The static form of Read(). Populates the struct at |generic|. */ \ - static bool ReadInto(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - struct_name* generic); \ - \ - template \ - static bool ReadIntoInternal(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - struct_name* generic); \ - template \ - static bool ReadArrayIntoInternal(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - size_t count, \ - struct_name* generic); \ - size_t size_; \ - }; \ - } /* namespace process_types */ \ - } /* namespace crashpad */ +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) \ + /* Similar to ExpectedSize(), but computes the minimum size of a \ + * structure based on the process’ bitness, typically including enough of \ + * a structure to contain its size field. This can be used prior to \ + * reading any data from a process. */ \ + static size_t MinimumSize(ProcessReaderMac* process_reader); + +#define PROCESS_TYPE_STRUCT_END(struct_name) \ + private: \ + /* The static form of Read(). Populates the struct at |generic|. */ \ + static bool ReadInto(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + struct_name* generic); \ + \ + template \ + static bool ReadIntoInternal(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + struct_name* generic); \ + template \ + static bool ReadArrayIntoInternal(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + size_t count, \ + struct_name* generic); \ + size_t size_; \ + } \ + ; \ + } /* namespace process_types */ \ + } /* namespace crashpad */ #include "snapshot/mac/process_types/all.proctype" #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_DECLARE @@ -153,37 +164,37 @@ DECLARE_PROCESS_TYPE_TRAITS_CLASS(Generic, 64) // remote process into the generic form. #define PROCESS_TYPE_STRUCT_DECLARE_INTERNAL 1 -#define PROCESS_TYPE_STRUCT_BEGIN(struct_name) \ - namespace crashpad { \ - namespace process_types { \ - namespace internal { \ - template \ - struct struct_name { \ - public: \ - using Long = typename Traits::Long; \ - using ULong = typename Traits::ULong; \ - using Pointer = typename Traits::Pointer; \ - using IntPtr = typename Traits::IntPtr; \ - using UIntPtr = typename Traits::UIntPtr; \ - using Reserved32_32Only = typename Traits::Reserved32_32Only; \ - using Reserved32_64Only = typename Traits::Reserved32_64Only; \ - using Reserved64_64Only = typename Traits::Reserved64_64Only; \ - using Nothing = typename Traits::Nothing; \ - \ - /* Read(), ReadArrayInto(), and Size() are as in the generic user-visible \ - * struct above. */ \ - bool Read(ProcessReader* process_reader, mach_vm_address_t address) { \ - return ReadInto(process_reader, address, this); \ - } \ - static bool ReadArrayInto(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - size_t count, \ - struct_name* specific); \ - static size_t Size() { return sizeof(struct_name); } \ - \ - /* Translates a struct from the representation used in the remote process \ - * into the generic form. */ \ - void GenericizeInto(process_types::struct_name* generic, \ +#define PROCESS_TYPE_STRUCT_BEGIN(struct_name) \ + namespace crashpad { \ + namespace process_types { \ + namespace internal { \ + template \ + struct struct_name { \ + public: \ + using Long = typename Traits::Long; \ + using ULong = typename Traits::ULong; \ + using Pointer = typename Traits::Pointer; \ + using IntPtr = typename Traits::IntPtr; \ + using UIntPtr = typename Traits::UIntPtr; \ + using Reserved32_32Only = typename Traits::Reserved32_32Only; \ + using Reserved32_64Only = typename Traits::Reserved32_64Only; \ + using Reserved64_64Only = typename Traits::Reserved64_64Only; \ + using Nothing = typename Traits::Nothing; \ + \ + /* Read(), ReadArrayInto(), and Size() are as in the generic user-visible \ + * struct above. */ \ + bool Read(ProcessReaderMac* process_reader, mach_vm_address_t address) { \ + return ReadInto(process_reader, address, this); \ + } \ + static bool ReadArrayInto(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + size_t count, \ + struct_name* specific); \ + static size_t Size() { return sizeof(struct_name); } \ + \ + /* Translates a struct from the representation used in the remote process \ + * into the generic form. */ \ + void GenericizeInto(process_types::struct_name* generic, \ size_t* specific_size); #define PROCESS_TYPE_STRUCT_MEMBER(member_type, member_name, ...) \ @@ -195,22 +206,28 @@ DECLARE_PROCESS_TYPE_TRAITS_CLASS(Generic, 64) static size_t ExpectedSizeForVersion( \ decltype(struct_name::version_field) version); -#define PROCESS_TYPE_STRUCT_END(struct_name) \ - private: \ - /* ReadInto() is as in the generic user-visible struct above. */ \ - static bool ReadInto(ProcessReader* process_reader, \ - mach_vm_address_t address, \ - struct_name* specific); \ - }; \ - } /* namespace internal */ \ - } /* namespace process_types */ \ - } /* namespace crashpad */ +#define PROCESS_TYPE_STRUCT_SIZED(struct_name, size_field) \ + /* MinimumSize() is as in the generic user-visible struct above. */ \ + static size_t MinimumSize(); + +#define PROCESS_TYPE_STRUCT_END(struct_name) \ + private: \ + /* ReadInto() is as in the generic user-visible struct above. */ \ + static bool ReadInto(ProcessReaderMac* process_reader, \ + mach_vm_address_t address, \ + struct_name* specific); \ + } \ + ; \ + } /* namespace internal */ \ + } /* namespace process_types */ \ + } /* namespace crashpad */ #include "snapshot/mac/process_types/all.proctype" #undef PROCESS_TYPE_STRUCT_BEGIN #undef PROCESS_TYPE_STRUCT_MEMBER #undef PROCESS_TYPE_STRUCT_VERSIONED +#undef PROCESS_TYPE_STRUCT_SIZED #undef PROCESS_TYPE_STRUCT_END #undef PROCESS_TYPE_STRUCT_DECLARE_INTERNAL diff --git a/snapshot/mac/process_types/crashpad_info.proctype b/snapshot/mac/process_types/crashpad_info.proctype index 41af5fc9..7c077ca7 100644 --- a/snapshot/mac/process_types/crashpad_info.proctype +++ b/snapshot/mac/process_types/crashpad_info.proctype @@ -23,10 +23,25 @@ // Client Mach-O images will contain a __DATA,crashpad_info section formatted // according to this structure. +// +// CrashpadInfo is variable-length. Its length dictated by its |size| field +// which is always present. A custom implementation of the flavored +// ReadSpecificInto function that understands how to use this field is provided +// in snapshot/mac/process_types/custom.cc. No implementation of ReadArrayInto +// is provided because CrashpadInfo structs are singletons in a module and are +// never present in arrays, so the functionality is unnecessary. + +#if !defined(PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO) && \ + !defined(PROCESS_TYPE_STRUCT_IMPLEMENT_ARRAY) + PROCESS_TYPE_STRUCT_BEGIN(CrashpadInfo) PROCESS_TYPE_STRUCT_MEMBER(uint32_t, signature) + PROCESS_TYPE_STRUCT_MEMBER(uint32_t, size) + PROCESS_TYPE_STRUCT_SIZED(CrashpadInfo, size) + PROCESS_TYPE_STRUCT_MEMBER(uint32_t, version) + PROCESS_TYPE_STRUCT_MEMBER(uint32_t, indirectly_referenced_memory_cap) PROCESS_TYPE_STRUCT_MEMBER(uint32_t, padding_0) PROCESS_TYPE_STRUCT_MEMBER(uint8_t, crashpad_handler_behavior) // TriState @@ -51,3 +66,6 @@ PROCESS_TYPE_STRUCT_BEGIN(CrashpadInfo) // AnnotationList* PROCESS_TYPE_STRUCT_MEMBER(Pointer, annotations_list) PROCESS_TYPE_STRUCT_END(CrashpadInfo) + +#endif // ! PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO && + // ! PROCESS_TYPE_STRUCT_IMPLEMENT_ARRAY diff --git a/snapshot/mac/process_types/crashreporterclient.proctype b/snapshot/mac/process_types/crashreporterclient.proctype index 409782c9..398bd639 100644 --- a/snapshot/mac/process_types/crashreporterclient.proctype +++ b/snapshot/mac/process_types/crashreporterclient.proctype @@ -13,7 +13,7 @@ // limitations under the License. // The name of this file was chosen based on -// http://llvm.org/svn/llvm-project/llvm/trunk/lib/Support/PrettyStackTrace.cpp. +// https://llvm.org/svn/llvm-project/llvm/trunk/lib/Support/PrettyStackTrace.cpp. // The name of the structure it describes was chosen based on that file as well // as 10.9.2 cups-372.2/cups/backend/usb-darwin.c. That file also provided the // names and types of the fields in the structure. @@ -33,8 +33,8 @@ // flavored ReadSpecificInto function that understands how to map this field to // the structure’s actual size is provided in // snapshot/mac/process_types/custom.cc. No implementation of ReadArrayInto is -// provided because dyld_all_image_infos structs are singletons in a process and -// are never present in arrays, so the functionality is unnecessary. +// provided because crashreporter_annotations_t structs are singletons in a +// module and are never present in arrays, so the functionality is unnecessary. #if !defined(PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO) && \ !defined(PROCESS_TYPE_STRUCT_IMPLEMENT_ARRAY) diff --git a/snapshot/mac/process_types/custom.cc b/snapshot/mac/process_types/custom.cc index 25ecf3da..7c7b172a 100644 --- a/snapshot/mac/process_types/custom.cc +++ b/snapshot/mac/process_types/custom.cc @@ -18,9 +18,12 @@ #include #include +#include #include #include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "base/strings/stringprintf.h" #include "snapshot/mac/process_types/internal.h" #include "util/mach/task_memory.h" @@ -33,32 +36,88 @@ namespace internal { namespace { template -bool ReadIntoVersioned(ProcessReader* process_reader, - mach_vm_address_t address, - T* specific) { - TaskMemory* task_memory = process_reader->Memory(); - if (!task_memory->Read( - address, sizeof(specific->version), &specific->version)) { - return false; - } - - mach_vm_size_t size = T::ExpectedSizeForVersion(specific->version); +bool ReadIntoAndZero(TaskMemory* task_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)) { return false; } // Zero out the rest of the structure in case anything accesses fields without - // checking the version. - size_t remaining = sizeof(*specific) - size; + // checking the version or size. + const size_t remaining = sizeof(*specific) - size; if (remaining > 0) { - char* start = reinterpret_cast(specific) + size; + char* const start = reinterpret_cast(specific) + size; memset(start, 0, remaining); } return true; } +template +bool FieldAddressIfInRange(mach_vm_address_t address, + size_t offset, + mach_vm_address_t* field_address) { + base::CheckedNumeric checked_field_address(address); + checked_field_address += offset; + typename T::Pointer local_field_address; + if (!checked_field_address.AssignIfValid(&local_field_address)) { + LOG(ERROR) << base::StringPrintf( + "address 0x%llx + offset 0x%zx out of range", address, offset); + return false; + } + + *field_address = local_field_address; + return true; +} + +template +bool ReadIntoVersioned(ProcessReaderMac* process_reader, + mach_vm_address_t address, + T* specific) { + mach_vm_address_t field_address; + if (!FieldAddressIfInRange( + address, offsetof(T, version), &field_address)) { + return false; + } + + TaskMemory* task_memory = process_reader->Memory(); + decltype(specific->version) version; + if (!task_memory->Read(field_address, sizeof(version), &version)) { + return false; + } + + const size_t size = T::ExpectedSizeForVersion(version); + return ReadIntoAndZero(task_memory, address, size, specific); +} + +template +bool ReadIntoSized(ProcessReaderMac* process_reader, + mach_vm_address_t address, + T* specific) { + mach_vm_address_t field_address; + if (!FieldAddressIfInRange(address, offsetof(T, size), &field_address)) { + return false; + } + + TaskMemory* task_memory = process_reader->Memory(); + decltype(specific->size) size; + if (!task_memory->Read(address + offsetof(T, size), sizeof(size), &size)) { + return false; + } + + if (size < T::MinimumSize()) { + LOG(ERROR) << "small size " << size; + return false; + } + + size = std::min(static_cast(size), sizeof(*specific)); + return ReadIntoAndZero(task_memory, address, size, specific); +} + } // namespace // static @@ -97,7 +156,7 @@ size_t dyld_all_image_infos::ExpectedSizeForVersion( // static template bool dyld_all_image_infos::ReadInto( - ProcessReader* process_reader, + ProcessReaderMac* process_reader, mach_vm_address_t address, dyld_all_image_infos* specific) { return ReadIntoVersioned(process_reader, address, specific); @@ -119,28 +178,38 @@ size_t crashreporter_annotations_t::ExpectedSizeForVersion( // static template bool crashreporter_annotations_t::ReadInto( - ProcessReader* process_reader, + ProcessReaderMac* process_reader, mach_vm_address_t address, crashreporter_annotations_t* specific) { return ReadIntoVersioned(process_reader, address, specific); } +// static +template +bool CrashpadInfo::ReadInto(ProcessReaderMac* process_reader, + mach_vm_address_t address, + CrashpadInfo* specific) { + return ReadIntoSized(process_reader, address, specific); +} + // Explicit template instantiation of the above. -#define PROCESS_TYPE_FLAVOR_TRAITS(lp_bits) \ - template size_t \ - dyld_all_image_infos::ExpectedSizeForVersion( \ - decltype(dyld_all_image_infos::version)); \ - template bool dyld_all_image_infos::ReadInto( \ - ProcessReader*, \ - mach_vm_address_t, \ - dyld_all_image_infos*); \ - template size_t \ - crashreporter_annotations_t::ExpectedSizeForVersion( \ - decltype(crashreporter_annotations_t::version)); \ - template bool crashreporter_annotations_t::ReadInto( \ - ProcessReader*, \ - mach_vm_address_t, \ - crashreporter_annotations_t*); +#define PROCESS_TYPE_FLAVOR_TRAITS(lp_bits) \ + template size_t \ + dyld_all_image_infos::ExpectedSizeForVersion( \ + decltype(dyld_all_image_infos::version)); \ + template bool dyld_all_image_infos::ReadInto( \ + ProcessReaderMac*, \ + mach_vm_address_t, \ + dyld_all_image_infos*); \ + template size_t \ + crashreporter_annotations_t::ExpectedSizeForVersion( \ + decltype(crashreporter_annotations_t::version)); \ + template bool crashreporter_annotations_t::ReadInto( \ + ProcessReaderMac*, \ + mach_vm_address_t, \ + crashreporter_annotations_t*); \ + template bool CrashpadInfo::ReadInto( \ + ProcessReaderMac*, mach_vm_address_t, CrashpadInfo*); #include "snapshot/mac/process_types/flavors.h" diff --git a/snapshot/mac/process_types_test.cc b/snapshot/mac/process_types_test.cc index 8ab15c8d..f116c4dd 100644 --- a/snapshot/mac/process_types_test.cc +++ b/snapshot/mac/process_types_test.cc @@ -101,7 +101,7 @@ TEST(ProcessTypes, DyldImagesSelf) { } #endif - ProcessReader process_reader; + ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 diff --git a/snapshot/mac/system_snapshot_mac.cc b/snapshot/mac/system_snapshot_mac.cc index 140e7f49..21f254ee 100644 --- a/snapshot/mac/system_snapshot_mac.cc +++ b/snapshot/mac/system_snapshot_mac.cc @@ -25,7 +25,7 @@ #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/posix/timezone.h" #include "util/mac/mac_util.h" #include "util/numeric/in_range_cast.h" @@ -104,7 +104,7 @@ SystemSnapshotMac::SystemSnapshotMac() SystemSnapshotMac::~SystemSnapshotMac() { } -void SystemSnapshotMac::Initialize(ProcessReader* process_reader, +void SystemSnapshotMac::Initialize(ProcessReaderMac* process_reader, const timeval* snapshot_time) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); diff --git a/snapshot/mac/system_snapshot_mac.h b/snapshot/mac/system_snapshot_mac.h index 2ac2ef90..62b2ae61 100644 --- a/snapshot/mac/system_snapshot_mac.h +++ b/snapshot/mac/system_snapshot_mac.h @@ -25,7 +25,7 @@ namespace crashpad { -class ProcessReader; +class ProcessReaderMac; namespace internal { @@ -40,9 +40,9 @@ class SystemSnapshotMac final : public SystemSnapshot { //! \param[in] process_reader A reader for the process being snapshotted. //! \n\n //! It seems odd that a system snapshot implementation would need a - //! ProcessReader, but some of the information reported about the system - //! depends on the process it’s being reported for. For example, the - //! architecture returned by GetCPUArchitecture() should be the + //! ProcessReaderMac, but some of the information reported about the + //! system depends on the process it’s being reported for. For example, + //! the architecture returned by GetCPUArchitecture() should be the //! architecture of the process, which may be different than the native //! architecture of the system: an x86_64 system can run both x86_64 and //! 32-bit x86 processes. @@ -53,7 +53,8 @@ class SystemSnapshotMac final : public SystemSnapshot { //! Otherwise, it would need to base its determination on the current //! time, which may be different than the snapshot time for snapshots //! generated around the daylight saving transition time. - void Initialize(ProcessReader* process_reader, const timeval* snapshot_time); + void Initialize(ProcessReaderMac* process_reader, + const timeval* snapshot_time); // SystemSnapshot: @@ -83,7 +84,7 @@ class SystemSnapshotMac final : public SystemSnapshot { private: std::string os_version_full_; std::string os_version_build_; - ProcessReader* process_reader_; // weak + ProcessReaderMac* process_reader_; // weak const timeval* snapshot_time_; // weak int os_version_major_; int os_version_minor_; diff --git a/snapshot/mac/system_snapshot_mac_test.cc b/snapshot/mac/system_snapshot_mac_test.cc index 646021b3..69048eb0 100644 --- a/snapshot/mac/system_snapshot_mac_test.cc +++ b/snapshot/mac/system_snapshot_mac_test.cc @@ -20,7 +20,7 @@ #include "build/build_config.h" #include "gtest/gtest.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "test/errors.h" #include "util/mac/mac_util.h" @@ -30,7 +30,7 @@ namespace { // SystemSnapshotMac objects would be cumbersome to construct in each test that // requires one, because of the repetitive and mechanical work necessary to set -// up a ProcessReader and timeval, along with the checks to verify that these +// up a ProcessReaderMac and timeval, along with the checks to verify that these // operations succeed. This test fixture class handles the initialization work // so that individual tests don’t have to. class SystemSnapshotMacTest : public testing::Test { @@ -55,7 +55,7 @@ class SystemSnapshotMacTest : public testing::Test { } private: - ProcessReader process_reader_; + ProcessReaderMac process_reader_; timeval snapshot_time_; internal::SystemSnapshotMac system_snapshot_; diff --git a/snapshot/mac/thread_snapshot_mac.cc b/snapshot/mac/thread_snapshot_mac.cc index b042f758..f45fc418 100644 --- a/snapshot/mac/thread_snapshot_mac.cc +++ b/snapshot/mac/thread_snapshot_mac.cc @@ -16,7 +16,7 @@ #include "base/logging.h" #include "snapshot/mac/cpu_context_mac.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" namespace crashpad { namespace internal { @@ -38,8 +38,8 @@ ThreadSnapshotMac::~ThreadSnapshotMac() { } bool ThreadSnapshotMac::Initialize( - ProcessReader* process_reader, - const ProcessReader::Thread& process_reader_thread) { + ProcessReaderMac* process_reader, + const ProcessReaderMac::Thread& process_reader_thread) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); thread_ = process_reader_thread.port; diff --git a/snapshot/mac/thread_snapshot_mac.h b/snapshot/mac/thread_snapshot_mac.h index 28334434..8f5d722b 100644 --- a/snapshot/mac/thread_snapshot_mac.h +++ b/snapshot/mac/thread_snapshot_mac.h @@ -21,14 +21,15 @@ #include "base/macros.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" -#include "snapshot/mac/memory_snapshot_mac.h" +#include "snapshot/mac/process_reader_mac.h" #include "snapshot/memory_snapshot.h" +#include "snapshot/memory_snapshot_generic.h" #include "snapshot/thread_snapshot.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { -class ProcessReader; +class ProcessReaderMac; namespace internal { @@ -41,15 +42,15 @@ class ThreadSnapshotMac final : public ThreadSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the task containing the + //! \param[in] process_reader A ProcessReaderMac for the task containing the //! thread. - //! \param[in] process_reader_thread The thread within the ProcessReader for - //! which the snapshot should be created. + //! \param[in] process_reader_thread The thread within the ProcessReaderMac + //! for which the snapshot should be created. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(ProcessReader* process_reader, - const ProcessReader::Thread& process_reader_thread); + bool Initialize(ProcessReaderMac* process_reader, + const ProcessReaderMac::Thread& process_reader_thread); // ThreadSnapshot: @@ -69,7 +70,7 @@ class ThreadSnapshotMac final : public ThreadSnapshot { } context_union_; #endif CPUContext context_; - MemorySnapshotMac stack_; + MemorySnapshotGeneric stack_; uint64_t thread_id_; uint64_t thread_specific_data_address_; thread_t thread_; diff --git a/snapshot/memory_snapshot.cc b/snapshot/memory_snapshot.cc new file mode 100644 index 00000000..a4c8b038 --- /dev/null +++ b/snapshot/memory_snapshot.cc @@ -0,0 +1,95 @@ +// 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/memory_snapshot.h" + +#include + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "util/numeric/checked_range.h" + +namespace crashpad { +namespace { + +bool DetermineMergedRangeImpl(bool log, + const MemorySnapshot* a, + const MemorySnapshot* b, + CheckedRange* merged) { + if (a->Size() == 0) { + LOG_IF(ERROR, log) << base::StringPrintf( + "invalid empty range at 0x%" PRIx64, a->Address()); + return false; + } + + if (b->Size() == 0) { + LOG_IF(ERROR, log) << base::StringPrintf( + "invalid empty range at 0x%" PRIx64, b->Address()); + return false; + } + + CheckedRange range_a(a->Address(), a->Size()); + if (!range_a.IsValid()) { + LOG_IF(ERROR, log) << base::StringPrintf("invalid range at 0x%" PRIx64 + ", size %" PRIuS, + range_a.base(), + range_a.size()); + return false; + } + + CheckedRange range_b(b->Address(), b->Size()); + if (!range_b.IsValid()) { + LOG_IF(ERROR, log) << base::StringPrintf("invalid range at 0x%" PRIx64 + ", size %" PRIuS, + range_b.base(), + range_b.size()); + return false; + } + + if (!range_a.OverlapsRange(range_b) && range_a.end() != range_b.base() && + range_b.end() != range_a.base()) { + LOG_IF(ERROR, log) << base::StringPrintf( + "ranges not overlapping or abutting: (0x%" PRIx64 ", size %" PRIuS + ") and (0x%" PRIx64 ", size %" PRIuS ")", + range_a.base(), + range_a.size(), + range_b.base(), + range_b.size()); + return false; + } + + if (merged) { + uint64_t base = std::min(range_a.base(), range_b.base()); + uint64_t end = std::max(range_a.end(), range_b.end()); + size_t size = static_cast(end - base); + merged->SetRange(base, size); + } + return true; +} + +} // namespace + +bool LoggingDetermineMergedRange(const MemorySnapshot* a, + const MemorySnapshot* b, + CheckedRange* merged) { + return DetermineMergedRangeImpl(true, a, b, merged); +} + +bool DetermineMergedRange(const MemorySnapshot* a, + const MemorySnapshot* b, + CheckedRange* merged) { + return DetermineMergedRangeImpl(false, a, b, merged); +} + +} // namespace crashpad diff --git a/snapshot/memory_snapshot.h b/snapshot/memory_snapshot.h index 47f8443e..9e274a0e 100644 --- a/snapshot/memory_snapshot.h +++ b/snapshot/memory_snapshot.h @@ -18,6 +18,10 @@ #include #include +#include + +#include "util/numeric/checked_range.h" + namespace crashpad { //! \brief An abstract interface to a snapshot representing a region of memory @@ -70,8 +74,68 @@ class MemorySnapshot { //! Delegate::MemorySnapshotDelegateRead(), which should be `true` on //! success and `false` on failure. virtual bool Read(Delegate* delegate) const = 0; + + //! \brief Creates a new MemorySnapshot based on merging this one with \a + //! other. + //! + //! The ranges described by the two snapshots must either overlap or abut, and + //! must be of the same concrete type. + //! + //! \return A newly allocated MemorySnapshot representing the merged range, or + //! `nullptr` with an error logged. + virtual const MemorySnapshot* MergeWithOtherSnapshot( + const MemorySnapshot* other) const = 0; }; +//! \brief Given two memory snapshots, checks if they're overlapping or +//! abutting, and if so, returns the result of merging the two ranges. +//! +//! This function is useful to implement +//! MemorySnapshot::MergeWithOtherSnapshot(). +//! +//! \param[in] a The first range. Must have Size() > 0. +//! \param[in] b The second range. Must have Size() > 0. +//! \param[out] merged The resulting merged range. May be `nullptr` if only a +//! characterization of the ranges is desired. +//! +//! \return `true` if the input ranges overlap or abut, with \a merged filled +//! out, otherwise, `false` with an error logged if \a log is `true`. +bool LoggingDetermineMergedRange(const MemorySnapshot* a, + const MemorySnapshot* b, + CheckedRange* merged); + +//! \brief The same as LoggingDetermineMergedRange but with no errors logged. +//! +//! \sa LoggingDetermineMergedRange +bool DetermineMergedRange(const MemorySnapshot* a, + const MemorySnapshot* b, + CheckedRange* merged); + +namespace internal { + +//! \brief A standard implementation of MemorySnapshot::MergeWithOtherSnapshot() +//! for concrete MemorySnapshot implementations that use a +//! `process_reader_`. +template +const MemorySnapshot* MergeWithOtherSnapshotImpl(const T* self, + const MemorySnapshot* other) { + const T* other_as_memory_snapshot_concrete = + reinterpret_cast(other); + if (self->process_reader_ != + other_as_memory_snapshot_concrete->process_reader_) { + LOG(ERROR) << "different process_reader_ for snapshots"; + return nullptr; + } + CheckedRange merged(0, 0); + if (!LoggingDetermineMergedRange(self, other, &merged)) + return nullptr; + + std::unique_ptr 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 new file mode 100644 index 00000000..402e913e --- /dev/null +++ b/snapshot/memory_snapshot_generic.h @@ -0,0 +1,107 @@ +// 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_SNAPSHOT_MEMORY_SNAPSHOT_GENERIC_H_ +#define CRASHPAD_SNAPSHOT_MEMORY_SNAPSHOT_GENERIC_H_ + +#include +#include + +#include "base/macros.h" +#include "snapshot/memory_snapshot.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory.h" + +namespace crashpad { +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 MemorySnapshotGeneric final : public MemorySnapshot { + public: + MemorySnapshotGeneric() = default; + ~MemorySnapshotGeneric() = default; + + //! \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(ProcessReaderType* process_reader, + VMAddress address, + VMSize size) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + process_reader_ = process_reader; + address_ = address; + size_ = size; + INITIALIZATION_STATE_SET_VALID(initialized_); + } + + // MemorySnapshot: + + uint64_t Address() const override { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; + } + + size_t Size() const override { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return size_; + } + + bool Read(Delegate* delegate) const override { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (size_ == 0) { + return delegate->MemorySnapshotDelegateRead(nullptr, size_); + } + + std::unique_ptr buffer(new uint8_t[size_]); + if (!process_reader_->Memory()->Read(address_, size_, buffer.get())) { + return false; + } + return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); + } + + const MemorySnapshot* MergeWithOtherSnapshot( + const MemorySnapshot* other) const override { + return MergeWithOtherSnapshotImpl(this, other); + } + + private: + template + friend const MemorySnapshot* MergeWithOtherSnapshotImpl( + const T* self, + const MemorySnapshot* other); + + ProcessReaderType* process_reader_; // weak + uint64_t address_; + uint64_t size_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotGeneric); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_GENERIC_MEMORY_SNAPSHOT_GENERIC_H_ diff --git a/snapshot/memory_snapshot_test.cc b/snapshot/memory_snapshot_test.cc new file mode 100644 index 00000000..91e847fb --- /dev/null +++ b/snapshot/memory_snapshot_test.cc @@ -0,0 +1,152 @@ +// 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/memory_snapshot.h" + +#include "base/macros.h" +#include "gtest/gtest.h" +#include "snapshot/test/test_memory_snapshot.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(DetermineMergedRange, NonOverlapping) { + TestMemorySnapshot a; + TestMemorySnapshot b; + a.SetAddress(0); + a.SetSize(100); + b.SetAddress(200); + b.SetSize(50); + CheckedRange range(0, 0); + EXPECT_FALSE(DetermineMergedRange(&a, &b, &range)); + EXPECT_FALSE(DetermineMergedRange(&b, &a, &range)); + + a.SetSize(199); + EXPECT_FALSE(DetermineMergedRange(&a, &b, &range)); +} + +TEST(DetermineMergedRange, Empty) { + TestMemorySnapshot a; + TestMemorySnapshot b; + a.SetAddress(100); + a.SetSize(0); + b.SetAddress(200); + b.SetSize(20); + + CheckedRange range(0, 0); + // Empty are invalid. + EXPECT_FALSE(DetermineMergedRange(&a, &b, &range)); + EXPECT_FALSE(DetermineMergedRange(&b, &a, &range)); + EXPECT_FALSE(DetermineMergedRange(&a, &a, &range)); +} + +TEST(DetermineMergedRange, Abutting) { + TestMemorySnapshot a; + TestMemorySnapshot b; + + a.SetAddress(0); + a.SetSize(10); + b.SetAddress(10); + b.SetSize(20); + + CheckedRange range(0, 0); + EXPECT_TRUE(DetermineMergedRange(&a, &b, &range)); + EXPECT_EQ(0u, range.base()); + EXPECT_EQ(30u, range.size()); + + EXPECT_TRUE(DetermineMergedRange(&b, &a, &range)); + EXPECT_EQ(0u, range.base()); + EXPECT_EQ(30u, range.size()); +} + +TEST(DetermineMergedRange, TypicalOverlapping) { + TestMemorySnapshot a; + TestMemorySnapshot b; + + a.SetAddress(10); + a.SetSize(100); + b.SetAddress(50); + b.SetSize(100); + + CheckedRange range(0, 0); + EXPECT_TRUE(DetermineMergedRange(&a, &b, &range)); + EXPECT_EQ(10u, range.base()); + EXPECT_EQ(140u, range.size()); + + EXPECT_TRUE(DetermineMergedRange(&b, &a, &range)); + EXPECT_EQ(10u, range.base()); + EXPECT_EQ(140u, range.size()); +} + +TEST(DetermineMergedRange, OneFullyInsideAnother) { + TestMemorySnapshot a; + TestMemorySnapshot b; + + a.SetAddress(20); + a.SetSize(100); + b.SetAddress(5); + b.SetSize(200); + + CheckedRange range(0, 0); + EXPECT_TRUE(DetermineMergedRange(&a, &b, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(200u, range.size()); + + EXPECT_TRUE(DetermineMergedRange(&b, &a, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(200u, range.size()); +} + +TEST(DetermineMergedRange, SameStart) { + TestMemorySnapshot a; + TestMemorySnapshot b; + + a.SetAddress(5); + a.SetSize(100); + b.SetAddress(5); + b.SetSize(50); + + CheckedRange range(0, 0); + EXPECT_TRUE(DetermineMergedRange(&a, &b, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(100u, range.size()); + + EXPECT_TRUE(DetermineMergedRange(&b, &a, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(100u, range.size()); +} + +TEST(DetermineMergedRange, SameEnd) { + TestMemorySnapshot a; + TestMemorySnapshot b; + + a.SetAddress(5); + a.SetSize(100); + b.SetAddress(70); + b.SetSize(35); + + CheckedRange range(0, 0); + EXPECT_TRUE(DetermineMergedRange(&a, &b, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(100u, range.size()); + + EXPECT_TRUE(DetermineMergedRange(&b, &a, &range)); + EXPECT_EQ(5u, range.base()); + EXPECT_EQ(100u, range.size()); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/minidump/minidump_annotation_reader.cc b/snapshot/minidump/minidump_annotation_reader.cc new file mode 100644 index 00000000..b1e4f985 --- /dev/null +++ b/snapshot/minidump/minidump_annotation_reader.cc @@ -0,0 +1,119 @@ +// 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 "snapshot/minidump/minidump_annotation_reader.h" + +#include + +#include "base/logging.h" +#include "minidump/minidump_extensions.h" +#include "snapshot/minidump/minidump_string_reader.h" + +namespace crashpad { +namespace internal { + +namespace { + +bool ReadMinidumpByteArray(FileReaderInterface* file_reader, + RVA rva, + std::vector* data) { + if (rva == 0) { + data->clear(); + return true; + } + + if (!file_reader->SeekSet(rva)) { + return false; + } + + uint32_t length; + if (!file_reader->ReadExactly(&length, sizeof(length))) { + return false; + } + + std::vector local_data(length); + if (!file_reader->ReadExactly(local_data.data(), length)) { + return false; + } + + data->swap(local_data); + return true; +} + +} // namespace + +bool ReadMinidumpAnnotationList(FileReaderInterface* file_reader, + const MINIDUMP_LOCATION_DESCRIPTOR& location, + std::vector* list) { + if (location.Rva == 0) { + list->clear(); + return true; + } + + if (location.DataSize < sizeof(MinidumpAnnotationList)) { + LOG(ERROR) << "annotation list size mismatch"; + return false; + } + + if (!file_reader->SeekSet(location.Rva)) { + return false; + } + + uint32_t count; + if (!file_reader->ReadExactly(&count, sizeof(count))) { + return false; + } + + if (location.DataSize != + sizeof(MinidumpAnnotationList) + count * sizeof(MinidumpAnnotation)) { + LOG(ERROR) << "annotation object size mismatch"; + return false; + } + + std::vector minidump_annotations(count); + if (!file_reader->ReadExactly(minidump_annotations.data(), + count * sizeof(MinidumpAnnotation))) { + return false; + } + + std::vector annotations; + annotations.reserve(count); + + for (size_t i = 0; i < count; ++i) { + const MinidumpAnnotation* minidump_annotation = &minidump_annotations[i]; + + AnnotationSnapshot annotation; + // The client-exposed size of this field is 16-bit, but the minidump field + // is 32-bit for padding. Take just the lower part. + annotation.type = static_cast(minidump_annotation->type); + + if (!ReadMinidumpUTF8String( + file_reader, minidump_annotation->name, &annotation.name)) { + return false; + } + + if (!ReadMinidumpByteArray( + file_reader, minidump_annotation->value, &annotation.value)) { + return false; + } + + annotations.push_back(std::move(annotation)); + } + + list->swap(annotations); + return true; +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/minidump/minidump_annotation_reader.h b/snapshot/minidump/minidump_annotation_reader.h new file mode 100644 index 00000000..a5bafb5b --- /dev/null +++ b/snapshot/minidump/minidump_annotation_reader.h @@ -0,0 +1,41 @@ +// 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 SNAPSHOT_MINIDUMP_MINIDUMP_ANNOTATION_READER_H_ +#define SNAPSHOT_MINIDUMP_MINIDUMP_ANNOTATION_READER_H_ + +#include +#include + +#include + +#include "snapshot/annotation_snapshot.h" +#include "util/file/file_reader.h" + +namespace crashpad { +namespace internal { + +//! \brief Reads a MinidumpAnnotationList from a minidump file at \a location +//! in \a file_reader, and returns it in \a list. +//! +//! \return `true` on success, with \a list set by replacing its contents. +//! `false` on failure, with a message logged. +bool ReadMinidumpAnnotationList(FileReaderInterface* file_reader, + const MINIDUMP_LOCATION_DESCRIPTOR& location, + std::vector* list); + +} // namespace internal +} // namespace crashpad + +#endif // SNAPSHOT_MINIDUMP_MINIDUMP_ANNOTATION_READER_H_ diff --git a/snapshot/minidump/module_snapshot_minidump.cc b/snapshot/minidump/module_snapshot_minidump.cc index 195fc892..06cd1bb6 100644 --- a/snapshot/minidump/module_snapshot_minidump.cc +++ b/snapshot/minidump/module_snapshot_minidump.cc @@ -15,6 +15,7 @@ #include "snapshot/minidump/module_snapshot_minidump.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" @@ -138,8 +139,7 @@ ModuleSnapshotMinidump::AnnotationsSimpleMap() const { std::vector ModuleSnapshotMinidump::AnnotationObjects() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); - return {}; + return annotation_objects_; } std::set> ModuleSnapshotMinidump::ExtraMemoryRanges() @@ -193,10 +193,21 @@ bool ModuleSnapshotMinidump::InitializeModuleCrashpadInfo( return false; } - return ReadMinidumpSimpleStringDictionary( - file_reader, - minidump_module_crashpad_info.simple_annotations, - &annotations_simple_map_); + if (!ReadMinidumpSimpleStringDictionary( + file_reader, + minidump_module_crashpad_info.simple_annotations, + &annotations_simple_map_)) { + return false; + } + + if (!ReadMinidumpAnnotationList( + file_reader, + minidump_module_crashpad_info.annotation_objects, + &annotation_objects_)) { + return false; + } + + return true; } } // namespace internal diff --git a/snapshot/minidump/module_snapshot_minidump.h b/snapshot/minidump/module_snapshot_minidump.h index ad65dbc9..e9dfa778 100644 --- a/snapshot/minidump/module_snapshot_minidump.h +++ b/snapshot/minidump/module_snapshot_minidump.h @@ -25,6 +25,7 @@ #include #include "base/macros.h" +#include "snapshot/annotation_snapshot.h" #include "snapshot/module_snapshot.h" #include "util/file/file_reader.h" #include "util/misc/initialization_state_dcheck.h" @@ -90,6 +91,7 @@ class ModuleSnapshotMinidump final : public ModuleSnapshot { MINIDUMP_MODULE minidump_module_; std::vector annotations_vector_; std::map annotations_simple_map_; + std::vector annotation_objects_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotMinidump); diff --git a/snapshot/minidump/process_snapshot_minidump_test.cc b/snapshot/minidump/process_snapshot_minidump_test.cc index cb42c396..82e14a7a 100644 --- a/snapshot/minidump/process_snapshot_minidump_test.cc +++ b/snapshot/minidump/process_snapshot_minidump_test.cc @@ -21,6 +21,7 @@ #include #include "gtest/gtest.h" +#include "snapshot/minidump/minidump_annotation_reader.h" #include "snapshot/module_snapshot.h" #include "util/file/string_file.h" @@ -66,7 +67,7 @@ TEST(ProcessSnapshotMinidump, Empty) { } // Writes |string| to |writer| as a MinidumpUTF8String, and returns the file -// offst of the beginning of the string. +// offset of the beginning of the string. RVA WriteString(FileWriterInterface* writer, const std::string& string) { RVA rva = static_cast(writer->SeekGet()); @@ -131,6 +132,48 @@ void WriteMinidumpStringList(MINIDUMP_LOCATION_DESCRIPTOR* location, rvas.size() * sizeof(RVA)); } +// Writes |data| to |writer| as a MinidumpByteArray, and returns the file offset +// from the beginning of the string. +RVA WriteByteArray(FileWriterInterface* writer, + const std::vector data) { + auto rva = static_cast(writer->SeekGet()); + + auto length = static_cast(data.size()); + EXPECT_TRUE(writer->Write(&length, sizeof(length))); + EXPECT_TRUE(writer->Write(data.data(), length)); + + return rva; +} + +// Writes |annotations| to |writer| as a MinidumpAnnotationList, and populates +// |location| with a location descriptor identifying what was written. +void WriteMinidumpAnnotationList( + MINIDUMP_LOCATION_DESCRIPTOR* location, + FileWriterInterface* writer, + const std::vector& annotations) { + std::vector minidump_annotations; + for (const auto& it : annotations) { + MinidumpAnnotation annotation; + annotation.name = WriteString(writer, it.name); + annotation.type = it.type; + annotation.reserved = 0; + annotation.value = WriteByteArray(writer, it.value); + minidump_annotations.push_back(annotation); + } + + location->Rva = static_cast(writer->SeekGet()); + + auto count = static_cast(minidump_annotations.size()); + EXPECT_TRUE(writer->Write(&count, sizeof(count))); + + for (const auto& it : minidump_annotations) { + EXPECT_TRUE(writer->Write(&it, sizeof(MinidumpAnnotation))); + } + + location->DataSize = + sizeof(MinidumpAnnotationList) + count * sizeof(MinidumpAnnotation); +} + TEST(ProcessSnapshotMinidump, ClientID) { StringFile string_file; @@ -215,6 +258,28 @@ TEST(ProcessSnapshotMinidump, AnnotationsSimpleMap) { EXPECT_EQ(annotations_simple_map, dictionary); } +TEST(ProcessSnapshotMinidump, AnnotationObjects) { + StringFile string_file; + + MINIDUMP_HEADER header{}; + EXPECT_TRUE(string_file.Write(&header, sizeof(header))); + + std::vector annotations; + annotations.emplace_back( + AnnotationSnapshot("name 1", 0xBBBB, {'t', 'e', '\0', 's', 't', '\0'})); + annotations.emplace_back( + AnnotationSnapshot("name 2", 0xABBA, {0xF0, 0x9F, 0x92, 0x83})); + + MINIDUMP_LOCATION_DESCRIPTOR location; + WriteMinidumpAnnotationList(&location, &string_file, annotations); + + std::vector read_annotations; + EXPECT_TRUE(internal::ReadMinidumpAnnotationList( + &string_file, location, &read_annotations)); + + EXPECT_EQ(read_annotations, annotations); +} + TEST(ProcessSnapshotMinidump, Modules) { StringFile string_file; @@ -222,7 +287,7 @@ TEST(ProcessSnapshotMinidump, Modules) { EXPECT_TRUE(string_file.Write(&header, sizeof(header))); MINIDUMP_MODULE minidump_module = {}; - uint32_t minidump_module_count = 3; + uint32_t minidump_module_count = 4; MINIDUMP_DIRECTORY minidump_module_list_directory = {}; minidump_module_list_directory.StreamType = kMinidumpStreamTypeModuleList; @@ -273,10 +338,26 @@ TEST(ProcessSnapshotMinidump, Modules) { crashpad_module_2_link.location.Rva = static_cast(string_file.SeekGet()); EXPECT_TRUE(string_file.Write(&crashpad_module_2, sizeof(crashpad_module_2))); + MinidumpModuleCrashpadInfo crashpad_module_4 = {}; + crashpad_module_4.version = MinidumpModuleCrashpadInfo::kVersion; + std::vector annotations_4{ + {"first one", 0xBADE, {'a', 'b', 'c'}}, + {"2", 0xEDD1, {0x11, 0x22, 0x33}}, + {"threeeeee", 0xDADA, {'f'}}, + }; + WriteMinidumpAnnotationList( + &crashpad_module_4.annotation_objects, &string_file, annotations_4); + + MinidumpModuleCrashpadInfoLink crashpad_module_4_link = {}; + crashpad_module_4_link.minidump_module_list_index = 3; + crashpad_module_4_link.location.DataSize = sizeof(crashpad_module_4); + crashpad_module_4_link.location.Rva = static_cast(string_file.SeekGet()); + EXPECT_TRUE(string_file.Write(&crashpad_module_4, sizeof(crashpad_module_4))); + MinidumpCrashpadInfo crashpad_info = {}; crashpad_info.version = MinidumpCrashpadInfo::kVersion; - uint32_t crashpad_module_count = 2; + uint32_t crashpad_module_count = 3; crashpad_info.module_list.DataSize = sizeof(MinidumpModuleCrashpadInfoList) + @@ -289,6 +370,8 @@ TEST(ProcessSnapshotMinidump, Modules) { sizeof(crashpad_module_0_link))); EXPECT_TRUE(string_file.Write(&crashpad_module_2_link, sizeof(crashpad_module_2_link))); + EXPECT_TRUE(string_file.Write(&crashpad_module_4_link, + sizeof(crashpad_module_4_link))); MINIDUMP_DIRECTORY crashpad_info_directory = {}; crashpad_info_directory.StreamType = kMinidumpStreamTypeCrashpadInfo; @@ -332,6 +415,9 @@ TEST(ProcessSnapshotMinidump, Modules) { annotations_vector = modules[2]->AnnotationsVector(); EXPECT_EQ(annotations_vector, list_annotations_2); + + auto annotation_objects = modules[3]->AnnotationObjects(); + EXPECT_EQ(annotation_objects, annotations_4); } } // namespace diff --git a/snapshot/posix/timezone_test.cc b/snapshot/posix/timezone_test.cc index 01bdff50..814506fa 100644 --- a/snapshot/posix/timezone_test.cc +++ b/snapshot/posix/timezone_test.cc @@ -15,7 +15,6 @@ #include "snapshot/posix/timezone.h" #include -#include #include #include @@ -88,7 +87,7 @@ TEST(TimeZone, Basic) { // In contemporary usage, most time zones have an integer hour offset from // UTC, although several are at a half-hour offset, and two are at 15-minute // offsets. Throughout history, other variations existed. See - // http://www.timeanddate.com/time/time-zones-interesting.html. + // https://www.timeanddate.com/time/time-zones-interesting.html. EXPECT_EQ(standard_offset_seconds % (15 * 60), 0) << "standard_offset_seconds " << standard_offset_seconds; @@ -100,9 +99,9 @@ TEST(TimeZone, Basic) { << "daylight_offset_seconds " << daylight_offset_seconds; // In contemporary usage, dst_delta_seconds will almost always be one hour, - // except for Lord Howe Island, Australia, which uses a 30-minute - // delta. Throughout history, other variations existed. See - // http://www.timeanddate.com/time/dst/#brief. + // except for Lord Howe Island, Australia, which uses a 30-minute delta. + // Throughout history, other variations existed. See + // https://www.timeanddate.com/time/dst/. int dst_delta_seconds = daylight_offset_seconds - standard_offset_seconds; if (dst_delta_seconds != 60 * 60 && dst_delta_seconds != 30 * 60) { FAIL() << "dst_delta_seconds " << dst_delta_seconds; diff --git a/snapshot/sanitized/memory_snapshot_sanitized.cc b/snapshot/sanitized/memory_snapshot_sanitized.cc new file mode 100644 index 00000000..e71629df --- /dev/null +++ b/snapshot/sanitized/memory_snapshot_sanitized.cc @@ -0,0 +1,107 @@ +// 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/sanitized/memory_snapshot_sanitized.h" + +#include + +namespace crashpad { +namespace internal { + +namespace { + +class MemorySanitizer : public MemorySnapshot::Delegate { + public: + MemorySanitizer(MemorySnapshot::Delegate* delegate, + RangeSet* ranges, + VMAddress address, + bool is_64_bit) + : delegate_(delegate), + ranges_(ranges), + address_(address), + is_64_bit_(is_64_bit) {} + + ~MemorySanitizer() = default; + + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + if (is_64_bit_) { + Sanitize(data, size); + } else { + Sanitize(data, size); + } + return delegate_->MemorySnapshotDelegateRead(data, size); + } + + private: + template + void Sanitize(void* data, size_t size) { + const Pointer defaced = + static_cast(MemorySnapshotSanitized::kDefaced); + + // Sanitize up to a word-aligned address. + const size_t aligned_offset = + ((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_; + memcpy(data, &defaced, aligned_offset); + + // Sanitize words that aren't small and don't look like pointers. + size_t word_count = (size - aligned_offset) / sizeof(Pointer); + auto words = + reinterpret_cast(static_cast(data) + aligned_offset); + for (size_t index = 0; index < word_count; ++index) { + if (words[index] > MemorySnapshotSanitized::kSmallWordMax && + !ranges_->Contains(words[index])) { + words[index] = defaced; + } + } + + // Sanitize trailing bytes beyond the word-sized items. + const size_t sanitized_bytes = + aligned_offset + word_count * sizeof(Pointer); + memcpy(static_cast(data) + sanitized_bytes, + &defaced, + size - sanitized_bytes); + } + + MemorySnapshot::Delegate* delegate_; + RangeSet* ranges_; + VMAddress address_; + bool is_64_bit_; + + DISALLOW_COPY_AND_ASSIGN(MemorySanitizer); +}; + +} // namespace + +MemorySnapshotSanitized::MemorySnapshotSanitized(const MemorySnapshot* snapshot, + RangeSet* ranges, + bool is_64_bit) + : snapshot_(snapshot), ranges_(ranges), is_64_bit_(is_64_bit) {} + +MemorySnapshotSanitized::~MemorySnapshotSanitized() = default; + +uint64_t MemorySnapshotSanitized::Address() const { + return snapshot_->Address(); +} + +size_t MemorySnapshotSanitized::Size() const { + return snapshot_->Size(); +} + +bool MemorySnapshotSanitized::Read(Delegate* delegate) const { + MemorySanitizer sanitizer(delegate, ranges_, Address(), is_64_bit_); + return snapshot_->Read(&sanitizer); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/memory_snapshot_sanitized.h b/snapshot/sanitized/memory_snapshot_sanitized.h new file mode 100644 index 00000000..41e324f5 --- /dev/null +++ b/snapshot/sanitized/memory_snapshot_sanitized.h @@ -0,0 +1,74 @@ +// 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_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ + +#include + +#include "snapshot/memory_snapshot.h" +#include "util/misc/range_set.h" + +namespace crashpad { +namespace internal { + +//! \brief A MemorySnapshot which wraps and filters sensitive information from +//! another MemorySnapshot. +//! +//! This class redacts all data from the wrapped MemorySnapshot unless: +//! 1. The data is pointer aligned and points into a whitelisted address range. +//! 2. The data is pointer aligned and is a small integer. +class MemorySnapshotSanitized final : public MemorySnapshot { + public: + //! \brief Redacted data is replaced with this value, casted to the + //! appropriate size for a pointer in the target process. + static constexpr uint64_t kDefaced = 0x0defaced0defaced; + + //! \brief Pointer-aligned data smaller than this value is not redacted. + static constexpr uint64_t kSmallWordMax = 4096; + + //! \brief Constructs this object. + //! + //! \param[in] snapshot The MemorySnapshot to sanitize. + //! \param[in] ranges A set of whitelisted address ranges. + //! \param[in] is_64_bit `true` if this memory is for a 64-bit process. + MemorySnapshotSanitized(const MemorySnapshot* snapshot, + RangeSet* ranges, + bool is_64_bit); + + ~MemorySnapshotSanitized() override; + + // 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 { + return nullptr; + } + + private: + const MemorySnapshot* snapshot_; + RangeSet* ranges_; + bool is_64_bit_; + + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/module_snapshot_sanitized.cc b/snapshot/sanitized/module_snapshot_sanitized.cc new file mode 100644 index 00000000..94cb3d00 --- /dev/null +++ b/snapshot/sanitized/module_snapshot_sanitized.cc @@ -0,0 +1,132 @@ +// 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/sanitized/module_snapshot_sanitized.h" + +namespace crashpad { +namespace internal { + +namespace { + +bool KeyIsInWhitelist(const std::string& name, + const std::vector& whitelist) { + for (const auto& key : whitelist) { + if (name == key) { + return true; + } + } + return false; +} + +} // namespace + +ModuleSnapshotSanitized::ModuleSnapshotSanitized( + const ModuleSnapshot* snapshot, + const std::vector* annotations_whitelist) + : snapshot_(snapshot), annotations_whitelist_(annotations_whitelist) {} + +ModuleSnapshotSanitized::~ModuleSnapshotSanitized() = default; + +std::string ModuleSnapshotSanitized::Name() const { + return snapshot_->Name(); +} + +uint64_t ModuleSnapshotSanitized::Address() const { + return snapshot_->Address(); +} + +uint64_t ModuleSnapshotSanitized::Size() const { + return snapshot_->Size(); +} + +time_t ModuleSnapshotSanitized::Timestamp() const { + return snapshot_->Timestamp(); +} + +void ModuleSnapshotSanitized::FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + snapshot_->FileVersion(version_0, version_1, version_2, version_3); +} + +void ModuleSnapshotSanitized::SourceVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + snapshot_->SourceVersion(version_0, version_1, version_2, version_3); +} + +ModuleSnapshot::ModuleType ModuleSnapshotSanitized::GetModuleType() const { + return snapshot_->GetModuleType(); +} + +void ModuleSnapshotSanitized::UUIDAndAge(crashpad::UUID* uuid, + uint32_t* age) const { + snapshot_->UUIDAndAge(uuid, age); +} + +std::string ModuleSnapshotSanitized::DebugFileName() const { + return snapshot_->DebugFileName(); +} + +std::vector ModuleSnapshotSanitized::AnnotationsVector() const { + // TODO(jperaza): If/when AnnotationsVector() begins to be used, determine + // whether and how the content should be sanitized. + DCHECK(snapshot_->AnnotationsVector().empty()); + return std::vector(); +} + +std::map +ModuleSnapshotSanitized::AnnotationsSimpleMap() const { + std::map annotations = + snapshot_->AnnotationsSimpleMap(); + if (annotations_whitelist_) { + for (auto kv = annotations.begin(); kv != annotations.end(); ++kv) { + if (!KeyIsInWhitelist(kv->first, *annotations_whitelist_)) { + annotations.erase(kv); + } + } + } + return annotations; +} + +std::vector ModuleSnapshotSanitized::AnnotationObjects() + const { + std::vector annotations = snapshot_->AnnotationObjects(); + if (annotations_whitelist_) { + std::vector whitelisted; + for (const auto& anno : annotations) { + if (KeyIsInWhitelist(anno.name, *annotations_whitelist_)) { + whitelisted.push_back(anno); + } + } + annotations.swap(whitelisted); + } + return annotations; +} + +std::set> ModuleSnapshotSanitized::ExtraMemoryRanges() + const { + DCHECK(snapshot_->ExtraMemoryRanges().empty()); + return std::set>(); +} + +std::vector +ModuleSnapshotSanitized::CustomMinidumpStreams() const { + return snapshot_->CustomMinidumpStreams(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/module_snapshot_sanitized.h b/snapshot/sanitized/module_snapshot_sanitized.h new file mode 100644 index 00000000..4f375ce0 --- /dev/null +++ b/snapshot/sanitized/module_snapshot_sanitized.h @@ -0,0 +1,75 @@ +// 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_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ + +#include +#include + +#include "base/macros.h" +#include "snapshot/module_snapshot.h" + +namespace crashpad { +namespace internal { + +//! \brief A ModuleSnapshot which wraps and filters sensitive information from +//! another ModuleSnapshot. +class ModuleSnapshotSanitized final : public ModuleSnapshot { + public: + //! \brief Constructs this object. + //! + //! \param[in] snapshot The ModuleSnapshot to sanitize. + //! \param[in] annotations_whitelist A list of annotation names to allow to be + //! returned by AnnotationsSimpleMap() or AnnotationObjects(). If + //! `nullptr`, all annotations will be returned. + ModuleSnapshotSanitized( + const ModuleSnapshot* snapshot, + const std::vector* annotations_whitelist); + ~ModuleSnapshotSanitized() override; + + // 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(crashpad::UUID* uuid, uint32_t* age) const override; + std::string DebugFileName() const override; + std::vector AnnotationsVector() const override; + std::map AnnotationsSimpleMap() const override; + std::vector AnnotationObjects() const override; + std::set> ExtraMemoryRanges() const override; + std::vector CustomMinidumpStreams() const override; + + private: + const ModuleSnapshot* snapshot_; + const std::vector* annotations_whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/process_snapshot_sanitized.cc b/snapshot/sanitized/process_snapshot_sanitized.cc new file mode 100644 index 00000000..76caeb5a --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized.cc @@ -0,0 +1,265 @@ +// 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/sanitized/process_snapshot_sanitized.h" + +#include + +#include "snapshot/cpu_context.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +namespace { + +class StackReferencesAddressRange : public MemorySnapshot::Delegate { + public: + // Returns true if stack contains a pointer aligned word in the range [low, + // high). The search starts at the first pointer aligned address greater than + // stack_pointer. + bool CheckStack(const MemorySnapshot* stack, + VMAddress stack_pointer, + VMAddress low, + VMAddress high, + bool is_64_bit) { + stack_ = stack; + stack_pointer_ = stack_pointer; + low_ = low; + high_ = high; + is_64_bit_ = is_64_bit; + return stack_->Read(this); + } + + // MemorySnapshot::Delegate + bool MemorySnapshotDelegateRead(void* data, size_t size) { + return is_64_bit_ ? ScanStackForPointers(data, size) + : ScanStackForPointers(data, size); + } + + private: + template + bool ScanStackForPointers(void* data, size_t size) { + size_t sp_offset; + if (!AssignIfInRange(&sp_offset, stack_pointer_ - stack_->Address())) { + return false; + } + const size_t aligned_sp_offset = + (sp_offset + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1); + + auto words = reinterpret_cast(static_cast(data) + + aligned_sp_offset); + size_t word_count = (size - aligned_sp_offset) / sizeof(Pointer); + for (size_t index = 0; index < word_count; ++index) { + if (words[index] >= low_ && words[index] < high_) { + return true; + } + } + + return false; + } + + VMAddress stack_pointer_; + VMAddress low_; + VMAddress high_; + const MemorySnapshot* stack_; + bool is_64_bit_; +}; + +} // namespace + +ProcessSnapshotSanitized::ProcessSnapshotSanitized() = default; + +ProcessSnapshotSanitized::~ProcessSnapshotSanitized() = default; + +bool ProcessSnapshotSanitized::Initialize( + const ProcessSnapshot* snapshot, + const std::vector* annotations_whitelist, + VMAddress target_module_address, + bool sanitize_stacks) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + snapshot_ = snapshot; + annotations_whitelist_ = annotations_whitelist; + sanitize_stacks_ = sanitize_stacks; + + if (target_module_address) { + const ExceptionSnapshot* exception = snapshot_->Exception(); + if (!exception) { + return false; + } + + const ThreadSnapshot* exc_thread = nullptr; + for (const auto thread : snapshot_->Threads()) { + if (thread->ThreadID() == exception->ThreadID()) { + exc_thread = thread; + break; + } + } + if (!exc_thread) { + return false; + } + + const ModuleSnapshot* target_module = nullptr; + for (const auto module : snapshot_->Modules()) { + if (target_module_address >= module->Address() && + target_module_address < module->Address() + module->Size()) { + target_module = module; + break; + } + } + if (!target_module) { + return false; + } + + // Only allow the snapshot if the program counter or some address on the + // stack points into the target module. + VMAddress pc = exception->Context()->InstructionPointer(); + VMAddress module_address_low = target_module->Address(); + VMAddress module_address_high = module_address_low + target_module->Size(); + if ((pc < module_address_low || pc >= module_address_high) && + !StackReferencesAddressRange().CheckStack( + exc_thread->Stack(), + exception->Context()->StackPointer(), + module_address_low, + module_address_high, + exception->Context()->Is64Bit())) { + return false; + } + } + + if (annotations_whitelist_) { + for (const auto module : snapshot_->Modules()) { + modules_.emplace_back(std::make_unique( + module, annotations_whitelist_)); + } + } + + if (sanitize_stacks_) { + for (const auto module : snapshot_->Modules()) { + address_ranges_.Insert(module->Address(), module->Size()); + } + + for (const auto thread : snapshot_->Threads()) { + address_ranges_.Insert(thread->Stack()->Address(), + thread->Stack()->Size()); + threads_.emplace_back(std::make_unique( + thread, &address_ranges_)); + } + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +pid_t ProcessSnapshotSanitized::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ProcessID(); +} + +pid_t ProcessSnapshotSanitized::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ParentProcessID(); +} + +void ProcessSnapshotSanitized::SnapshotTime(timeval* snapshot_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->SnapshotTime(snapshot_time); +} + +void ProcessSnapshotSanitized::ProcessStartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ProcessStartTime(start_time); +} + +void ProcessSnapshotSanitized::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ProcessCPUTimes(user_time, system_time); +} + +void ProcessSnapshotSanitized::ReportID(UUID* report_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ReportID(report_id); +} + +void ProcessSnapshotSanitized::ClientID(UUID* client_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ClientID(client_id); +} + +const std::map& +ProcessSnapshotSanitized::AnnotationsSimpleMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->AnnotationsSimpleMap(); +} + +const SystemSnapshot* ProcessSnapshotSanitized::System() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->System(); +} + +std::vector ProcessSnapshotSanitized::Threads() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!sanitize_stacks_) { + return snapshot_->Threads(); + } + + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; +} + +std::vector ProcessSnapshotSanitized::Modules() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!annotations_whitelist_) { + return snapshot_->Modules(); + } + + std::vector modules; + for (const auto& module : modules_) { + modules.push_back(module.get()); + } + return modules; +} + +std::vector ProcessSnapshotSanitized::UnloadedModules() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->UnloadedModules(); +} + +const ExceptionSnapshot* ProcessSnapshotSanitized::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->Exception(); +} + +std::vector +ProcessSnapshotSanitized::MemoryMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->MemoryMap(); +} + +std::vector ProcessSnapshotSanitized::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->Handles(); +} + +std::vector ProcessSnapshotSanitized::ExtraMemory() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ExtraMemory(); +} + +} // namespace crashpad diff --git a/snapshot/sanitized/process_snapshot_sanitized.h b/snapshot/sanitized/process_snapshot_sanitized.h new file mode 100644 index 00000000..f5cf5fa6 --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized.h @@ -0,0 +1,105 @@ +// 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_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/exception_snapshot.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/sanitized/module_snapshot_sanitized.h" +#include "snapshot/sanitized/thread_snapshot_sanitized.h" +#include "snapshot/thread_snapshot.h" +#include "snapshot/unloaded_module_snapshot.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/misc/range_set.h" + +namespace crashpad { + +//! \brief A ProcessSnapshot which wraps and filters sensitive information from +//! another ProcessSnapshot. +class ProcessSnapshotSanitized final : public ProcessSnapshot { + public: + ProcessSnapshotSanitized(); + ~ProcessSnapshotSanitized() override; + + //! \brief Initializes this object. + //! + //! This method must be successfully called before calling any other method on + //! this object. + //! + //! \param[in] snapshot The ProcessSnapshot to sanitize. + //! \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] 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 + //! this module's address range, this method will return `false`. If this + //! value is 0, this method will not check the context or stack for + //! references to any particular module. + //! \param[in] sanitize_stacks If `true`, the MemorySnapshots for each + //! thread's stack will be filtered using an + //! 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* annotations_whitelist, + VMAddress target_module_address, + bool sanitize_stacks); + + // 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& AnnotationsSimpleMap() + const override; + const SystemSnapshot* System() const override; + std::vector Threads() const override; + std::vector Modules() const override; + std::vector UnloadedModules() const override; + const ExceptionSnapshot* Exception() const override; + std::vector MemoryMap() const override; + std::vector Handles() const override; + std::vector ExtraMemory() const override; + + private: + // Only used when annotations_whitelist_ != nullptr. + std::vector> modules_; + + // Only used when sanitize_stacks_ == true. + std::vector> threads_; + + RangeSet address_ranges_; + const ProcessSnapshot* snapshot_; + const std::vector* annotations_whitelist_; + bool sanitize_stacks_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotSanitized); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/process_snapshot_sanitized_test.cc b/snapshot/sanitized/process_snapshot_sanitized_test.cc new file mode 100644 index 00000000..485ef87f --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized_test.cc @@ -0,0 +1,275 @@ +// 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/sanitized/process_snapshot_sanitized.h" + +#include "base/macros.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "util/file/file_io.h" +#include "util/numeric/safe_assignment.h" + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include + +#include "snapshot/linux/process_snapshot_linux.h" +#include "util/linux/direct_ptrace_connection.h" +#include "util/linux/exception_information.h" +#include "util/posix/signals.h" +#endif + +namespace crashpad { +namespace test { +namespace { + +class ExceptionGenerator { + public: + static ExceptionGenerator* Get() { + static ExceptionGenerator* instance = new ExceptionGenerator(); + return instance; + } + + bool Initialize(FileHandle in, FileHandle out) { + in_ = in; + out_ = out; + return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); + } + + private: + ExceptionGenerator() = default; + ~ExceptionGenerator() = delete; + + static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + auto state = Get(); + + ExceptionInformation info = {}; + info.siginfo_address = FromPointerCast(siginfo); + info.context_address = FromPointerCast(context); + info.thread_id = syscall(SYS_gettid); + + auto info_addr = FromPointerCast(&info); + ASSERT_TRUE(LoggingWriteFile(state->out_, &info_addr, sizeof(info_addr))); + + CheckedReadFileAtEOF(state->in_); + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + } + + FileHandle in_; + FileHandle out_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionGenerator); +}; + +constexpr char kWhitelistedAnnotationName[] = "name_of_whitelisted_anno"; +constexpr char kWhitelistedAnnotationValue[] = "some_value"; +constexpr char kNonWhitelistedAnnotationName[] = "non_whitelisted_anno"; +constexpr char kNonWhitelistedAnnotationValue[] = "private_annotation"; +constexpr char kSensitiveStackData[] = "sensitive_stack_data"; + +struct ChildTestAddresses { + VMAddress string_address; + VMAddress module_address; + VMAddress non_module_address; + VMAddress code_pointer_address; + VMAddress code_pointer_value; +}; + +void ChildTestFunction() { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + + static StringAnnotation<32> whitelisted_annotation( + kWhitelistedAnnotationName); + whitelisted_annotation.Set(kWhitelistedAnnotationValue); + + static StringAnnotation<32> non_whitelisted_annotation( + kNonWhitelistedAnnotationName); + non_whitelisted_annotation.Set(kNonWhitelistedAnnotationValue); + + char string_data[strlen(kSensitiveStackData) + 1]; + strcpy(string_data, kSensitiveStackData); + + void (*code_pointer)(void) = ChildTestFunction; + + ChildTestAddresses addrs = {}; + addrs.string_address = FromPointerCast(string_data); + addrs.module_address = FromPointerCast(ChildTestFunction); + addrs.non_module_address = FromPointerCast(&addrs); + addrs.code_pointer_address = FromPointerCast(&code_pointer); + addrs.code_pointer_value = FromPointerCast(code_pointer); + ASSERT_TRUE(LoggingWriteFile(out, &addrs, sizeof(addrs))); + + auto gen = ExceptionGenerator::Get(); + ASSERT_TRUE(gen->Initialize(in, out)); + + __builtin_trap(); +} + +CRASHPAD_CHILD_TEST_MAIN(ChildToBeSanitized) { + ChildTestFunction(); + NOTREACHED(); + return EXIT_SUCCESS; +} + +void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { + bool found_whitelisted = false; + bool found_non_whitelisted = false; + for (auto module : snapshot->Modules()) { + for (const auto& anno : module->AnnotationObjects()) { + if (anno.name == kWhitelistedAnnotationName) { + found_whitelisted = true; + } else if (anno.name == kNonWhitelistedAnnotationName) { + found_non_whitelisted = true; + } + } + } + + EXPECT_TRUE(found_whitelisted); + if (sanitized) { + EXPECT_FALSE(found_non_whitelisted); + } else { + EXPECT_TRUE(found_non_whitelisted); + } +} + +class StackSanitizationChecker : public MemorySnapshot::Delegate { + public: + StackSanitizationChecker() = default; + ~StackSanitizationChecker() = default; + + void CheckStack(const MemorySnapshot* stack, + const ChildTestAddresses& addrs, + bool is_64_bit, + bool sanitized) { + stack_ = stack; + addrs_ = addrs; + is_64_bit_ = is_64_bit; + sanitized_ = sanitized; + EXPECT_TRUE(stack_->Read(this)); + } + + // MemorySnapshot::Delegate + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + size_t pointer_offset; + if (!AssignIfInRange(&pointer_offset, + addrs_.code_pointer_address - stack_->Address())) { + ADD_FAILURE(); + return false; + } + + const auto data_c = static_cast(data); + VMAddress pointer_value; + if (is_64_bit_) { + pointer_value = *reinterpret_cast(data_c + pointer_offset); + } else { + pointer_value = *reinterpret_cast(data_c + pointer_offset); + } + EXPECT_EQ(pointer_value, addrs_.code_pointer_value); + + size_t string_offset; + if (!AssignIfInRange(&string_offset, + addrs_.string_address - stack_->Address())) { + ADD_FAILURE(); + return false; + } + + auto string = data_c + string_offset; + if (sanitized_) { + EXPECT_STRNE(string, kSensitiveStackData); + } else { + EXPECT_STREQ(string, kSensitiveStackData); + } + return true; + } + + private: + const MemorySnapshot* stack_; + ChildTestAddresses addrs_; + bool is_64_bit_; + bool sanitized_; +}; + +void ExpectStackData(ProcessSnapshot* snapshot, + const ChildTestAddresses& addrs, + bool sanitized) { + const ThreadSnapshot* crasher = nullptr; + for (const auto thread : snapshot->Threads()) { + if (thread->ThreadID() == snapshot->Exception()->ThreadID()) { + crasher = thread; + break; + } + } + ASSERT_TRUE(crasher); + + const MemorySnapshot* stack = crasher->Stack(); + StackSanitizationChecker().CheckStack( + stack, addrs, crasher->Context()->Is64Bit(), sanitized); +} + +class SanitizeTest : public MultiprocessExec { + public: + SanitizeTest() : MultiprocessExec() { + SetChildTestMainFunction("ChildToBeSanitized"); + SetExpectedChildTerminationBuiltinTrap(); + } + + ~SanitizeTest() = default; + + private: + void MultiprocessParent() { + ChildTestAddresses addrs = {}; + ASSERT_TRUE( + LoggingReadFileExactly(ReadPipeHandle(), &addrs, sizeof(addrs))); + + VMAddress exception_info_addr; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &exception_info_addr, sizeof(exception_info_addr))); + + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildProcess())); + + ProcessSnapshotLinux snapshot; + ASSERT_TRUE(snapshot.Initialize(&connection)); + ASSERT_TRUE(snapshot.InitializeException(exception_info_addr)); + + ExpectAnnotations(&snapshot, /* sanitized= */ false); + ExpectStackData(&snapshot, addrs, /* sanitized= */ false); + + std::vector whitelist; + whitelist.push_back(kWhitelistedAnnotationName); + + ProcessSnapshotSanitized sanitized; + ASSERT_TRUE(sanitized.Initialize( + &snapshot, &whitelist, addrs.module_address, true)); + + ExpectAnnotations(&sanitized, /* sanitized= */ true); + ExpectStackData(&sanitized, addrs, /* sanitized= */ true); + + ProcessSnapshotSanitized screened_snapshot; + EXPECT_FALSE(screened_snapshot.Initialize( + &snapshot, nullptr, addrs.non_module_address, false)); + } + + DISALLOW_COPY_AND_ASSIGN(SanitizeTest); +}; + +TEST(ProcessSnapshotSanitized, Sanitize) { + SanitizeTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/sanitized/sanitization_information.cc b/snapshot/sanitized/sanitization_information.cc new file mode 100644 index 00000000..f7be9b08 --- /dev/null +++ b/snapshot/sanitized/sanitization_information.cc @@ -0,0 +1,61 @@ +// 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/sanitized/sanitization_information.h" + +#include "client/annotation.h" + +namespace crashpad { + +namespace { + +template +bool ReadWhitelist(const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector* whitelist) { + if (!whitelist_address) { + return true; + } + + std::vector local_whitelist; + Pointer name_address; + while (memory.Read(whitelist_address, sizeof(name_address), &name_address)) { + if (!name_address) { + whitelist->swap(local_whitelist); + return true; + } + + std::string name; + if (!memory.ReadCStringSizeLimited( + name_address, Annotation::kNameMaxLength, &name)) { + return false; + } + local_whitelist.push_back(name); + whitelist_address += sizeof(Pointer); + } + + return false; +} + +} // namespace + +bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector* whitelist) { + return memory.Is64Bit() + ? ReadWhitelist(memory, whitelist_address, whitelist) + : ReadWhitelist(memory, whitelist_address, whitelist); +} + +} // namespace crashpad diff --git a/snapshot/sanitized/sanitization_information.h b/snapshot/sanitized/sanitization_information.h new file mode 100644 index 00000000..6a6d9089 --- /dev/null +++ b/snapshot/sanitized/sanitization_information.h @@ -0,0 +1,68 @@ +// 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_SANITIZED_SANITIZATION_INFORMATION_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_SANITIZATION_INFORMATION_H_ + +#include + +#include +#include + +#include "util/misc/address_types.h" +#include "util/process/process_memory_range.h" + +namespace crashpad { + +#pragma pack(push, 1) + +//! \brief Struture containing information about how snapshots should be +//! sanitized. +//! +//! \see ProcessSnapshotSanitized +struct SanitizationInformation { + //! \brief The address in the client process' address space of a nullptr + //! terminated array of NUL-terminated strings. The string values are the + //! names of whitelisted annotations. This value is 0 if there is no + //! whitelist and all annotations are allowed. + VMAddress annotations_whitelist_address; + + //! \brief An address in the client process' address space within a module to + //! target. When a target module is used, crash dumps are discarded unless + //! the crashing thread's program counter or pointer-aligned values on the + //! crashing thread's stack point into the target module. This value is 0 + //! if there is no target module. + VMAddress target_module_address; + + //! \brief Non-zero if stacks should be sanitized for possible PII. + uint8_t sanitize_stacks; +}; + +#pragma pack(pop) + +//! \brief Reads an annotations 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 The whitelist read, valid only if this function +//! returns `true`. +//! \return `true` on success, `false` on failure with a message logged. +bool ReadAnnotationsWhitelist(const ProcessMemoryRange& memory, + VMAddress whitelist_address, + std::vector* 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 new file mode 100644 index 00000000..a7bf8265 --- /dev/null +++ b/snapshot/sanitized/sanitization_information_test.cc @@ -0,0 +1,70 @@ +// 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/sanitized/sanitization_information.h" + +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_linux.h" + +namespace crashpad { +namespace test { +namespace { + +class WhitelistTest : public testing::Test { + public: + void SetUp() override { + ASSERT_TRUE(memory_.Initialize(getpid())); +#if defined(ARCH_CPU_64_BITS) + ASSERT_TRUE(range_.Initialize(&memory_, true)); +#else + ASSERT_TRUE(range_.Initialize(&memory_, false)); +#endif + } + + protected: + bool ReadWhitelist(const char* const* address) { + return ReadAnnotationsWhitelist( + range_, FromPointerCast(address), &whitelist_); + } + + ProcessMemoryLinux memory_; + ProcessMemoryRange range_; + std::vector whitelist_; +}; + +const char* const kEmptyWhitelist[] = {nullptr}; + +TEST_F(WhitelistTest, EmptyWhitelist) { + ASSERT_TRUE(ReadWhitelist(kEmptyWhitelist)); + EXPECT_EQ(whitelist_, std::vector()); +} + +const char* const kNonEmptyWhitelist[] = {"string1", + "another_string", + "", + nullptr}; + +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) { + EXPECT_EQ(whitelist_[index], kNonEmptyWhitelist[index]); + } +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/sanitized/thread_snapshot_sanitized.cc b/snapshot/sanitized/thread_snapshot_sanitized.cc new file mode 100644 index 00000000..186776ea --- /dev/null +++ b/snapshot/sanitized/thread_snapshot_sanitized.cc @@ -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. + +#include "snapshot/sanitized/thread_snapshot_sanitized.h" + +#include "snapshot/cpu_context.h" + +namespace crashpad { +namespace internal { + +ThreadSnapshotSanitized::ThreadSnapshotSanitized(const ThreadSnapshot* snapshot, + RangeSet* ranges) + : ThreadSnapshot(), + snapshot_(snapshot), + stack_(snapshot_->Stack(), ranges, snapshot_->Context()->Is64Bit()) {} + +ThreadSnapshotSanitized::~ThreadSnapshotSanitized() = default; + +const CPUContext* ThreadSnapshotSanitized::Context() const { + return snapshot_->Context(); +} + +const MemorySnapshot* ThreadSnapshotSanitized::Stack() const { + return &stack_; +} + +uint64_t ThreadSnapshotSanitized::ThreadID() const { + return snapshot_->ThreadID(); +} + +int ThreadSnapshotSanitized::SuspendCount() const { + return snapshot_->SuspendCount(); +} + +int ThreadSnapshotSanitized::Priority() const { + return snapshot_->Priority(); +} + +uint64_t ThreadSnapshotSanitized::ThreadSpecificDataAddress() const { + return snapshot_->ThreadSpecificDataAddress(); +} + +std::vector ThreadSnapshotSanitized::ExtraMemory() + const { + // TODO(jperaza): If/when ExtraMemory() is used, decide whether and how it + // should be sanitized. + DCHECK(snapshot_->ExtraMemory().empty()); + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/thread_snapshot_sanitized.h b/snapshot/sanitized/thread_snapshot_sanitized.h new file mode 100644 index 00000000..081627a6 --- /dev/null +++ b/snapshot/sanitized/thread_snapshot_sanitized.h @@ -0,0 +1,59 @@ +// 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_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ + +#include "snapshot/thread_snapshot.h" + +#include "snapshot/sanitized/memory_snapshot_sanitized.h" +#include "util/misc/range_set.h" + +namespace crashpad { +namespace internal { + +//! \brief A ThreadSnapshot which wraps and filters sensitive information from +//! another ThreadSnapshot. +class ThreadSnapshotSanitized final : public ThreadSnapshot { + public: + //! \brief Constructs this object. + //! + //! \param[in] snapshot The ThreadSnapshot to sanitize. + //! \param[in] ranges A set of address ranges with which to sanitize this + //! thread's stacks. \see internal::MemorySnapshotSanitized. + ThreadSnapshotSanitized(const ThreadSnapshot* snapshot, RangeSet* ranges); + + ~ThreadSnapshotSanitized() override; + + // 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 ExtraMemory() const override; + + private: + const ThreadSnapshot* snapshot_; + MemorySnapshotSanitized stack_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index 73a5da04..a78d21f9 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -39,12 +39,18 @@ 'cpu_context.h', 'crashpad_info_client_options.cc', 'crashpad_info_client_options.h', + 'crashpad_types/crashpad_info_reader.cc', + 'crashpad_types/crashpad_info_reader.h', + 'crashpad_types/image_annotation_reader.cc', + 'crashpad_types/image_annotation_reader.h', 'elf/elf_dynamic_array_reader.cc', 'elf/elf_dynamic_array_reader.h', 'elf/elf_image_reader.cc', 'elf/elf_image_reader.h', 'elf/elf_symbol_table_reader.cc', 'elf/elf_symbol_table_reader.h', + 'elf/module_snapshot_elf.cc', + 'elf/module_snapshot_elf.h', 'exception_snapshot.h', 'handle_snapshot.cc', 'handle_snapshot.h', @@ -54,10 +60,10 @@ 'linux/debug_rendezvous.h', 'linux/exception_snapshot_linux.cc', 'linux/exception_snapshot_linux.h', - 'linux/memory_snapshot_linux.cc', - 'linux/memory_snapshot_linux.h', - 'linux/process_reader.cc', - 'linux/process_reader.h', + 'linux/process_reader_linux.cc', + 'linux/process_reader_linux.h', + 'linux/process_snapshot_linux.cc', + 'linux/process_snapshot_linux.h', 'linux/signal_context.h', 'linux/system_snapshot_linux.cc', 'linux/system_snapshot_linux.h', @@ -75,12 +81,10 @@ 'mac/mach_o_image_segment_reader.h', 'mac/mach_o_image_symbol_table_reader.cc', 'mac/mach_o_image_symbol_table_reader.h', - 'mac/memory_snapshot_mac.cc', - 'mac/memory_snapshot_mac.h', 'mac/module_snapshot_mac.cc', 'mac/module_snapshot_mac.h', - 'mac/process_reader.cc', - 'mac/process_reader.h', + 'mac/process_reader_mac.cc', + 'mac/process_reader_mac.h', 'mac/process_snapshot_mac.cc', 'mac/process_snapshot_mac.h', 'mac/process_types.cc', @@ -100,7 +104,11 @@ 'mac/system_snapshot_mac.h', 'mac/thread_snapshot_mac.cc', 'mac/thread_snapshot_mac.h', + 'memory_snapshot.cc', 'memory_snapshot.h', + 'memory_snapshot_generic.h', + 'minidump/minidump_annotation_reader.cc', + 'minidump/minidump_annotation_reader.h', 'minidump/minidump_simple_string_dictionary_reader.cc', 'minidump/minidump_simple_string_dictionary_reader.h', 'minidump/minidump_string_list_reader.cc', @@ -115,6 +123,16 @@ 'posix/timezone.cc', 'posix/timezone.h', 'process_snapshot.h', + 'sanitized/memory_snapshot_sanitized.cc', + 'sanitized/memory_snapshot_sanitized.h', + 'sanitized/module_snapshot_sanitized.cc', + 'sanitized/module_snapshot_sanitized.h', + 'sanitized/process_snapshot_sanitized.cc', + 'sanitized/process_snapshot_sanitized.h', + 'sanitized/sanitization_information.cc', + 'sanitized/sanitization_information.h', + 'sanitized/thread_snapshot_sanitized.cc', + 'sanitized/thread_snapshot_sanitized.h', 'snapshot_constants.h', 'system_snapshot.h', 'thread_snapshot.h', @@ -167,6 +185,8 @@ }, { # else: OS!="linux" and OS!="android" 'sources/': [ ['exclude', '^elf/'], + ['exclude', '^crashpad_types/'], + ['exclude', '^sanitized/'], ], }], ['target_arch!="ia32" and target_arch!="x64"', { diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index d720a10e..b795d92f 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -52,7 +52,10 @@ 'target_name': 'crashpad_snapshot_test', 'type': 'executable', 'dependencies': [ + 'crashpad_snapshot_test_lib', 'crashpad_snapshot_test_module', + 'crashpad_snapshot_test_module_large', + 'crashpad_snapshot_test_module_small', 'snapshot.gyp:crashpad_snapshot', 'snapshot.gyp:crashpad_snapshot_api', '../client/client.gyp:crashpad_client', @@ -69,21 +72,27 @@ 'sources': [ 'api/module_annotations_win_test.cc', 'cpu_context_test.cc', + 'memory_snapshot_test.cc', 'crashpad_info_client_options_test.cc', + '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', 'linux/debug_rendezvous_test.cc', 'linux/exception_snapshot_linux_test.cc', - 'linux/process_reader_test.cc', + 'linux/process_reader_linux_test.cc', 'linux/system_snapshot_linux_test.cc', 'mac/cpu_context_mac_test.cc', 'mac/mach_o_image_annotations_reader_test.cc', 'mac/mach_o_image_reader_test.cc', 'mac/mach_o_image_segment_reader_test.cc', - 'mac/process_reader_test.cc', + 'mac/process_reader_mac_test.cc', 'mac/process_types_test.cc', 'mac/system_snapshot_mac_test.cc', 'minidump/process_snapshot_minidump_test.cc', 'posix/timezone_test.cc', + 'sanitized/process_snapshot_sanitized_test.cc', + 'sanitized/sanitization_information_test.cc', 'win/cpu_context_win_test.cc', 'win/exception_snapshot_win_test.cc', 'win/extra_memory_ranges_test.cc', @@ -94,6 +103,10 @@ 'win/system_snapshot_win_test.cc', ], 'conditions': [ + # .gnu.hash is incompatible with the MIPS ABI + ['target_arch!="mips"', { + 'dependencies': ['crashpad_snapshot_test_both_dt_hash_styles'] + }], ['OS=="mac"', { 'dependencies': [ 'crashpad_snapshot_test_module_crashy_initializer', @@ -122,7 +135,7 @@ 'copies': [{ 'destination': '<(PRODUCT_DIR)', 'files': [ - 'linux/test_exported_symbols.sym', + 'elf/test_exported_symbols.sym', ], }], 'ldflags': [ @@ -136,6 +149,8 @@ }, { # else: OS!="linux" and OS!="android" 'sources/': [ ['exclude', '^elf/'], + ['exclude', '^crashpad_types/'], + ['exclude', '^sanitized/'], ], }], ], @@ -161,6 +176,74 @@ 'crashpad_info_client_options_test_module.cc', ], }, + { + 'target_name': 'crashpad_snapshot_test_module_large', + 'type': 'loadable_module', + 'dependencies': [ + '../third_party/mini_chromium/mini_chromium.gyp:base', + ], + 'defines': [ + 'CRASHPAD_INFO_SIZE_TEST_MODULE_LARGE=1', + ], + 'sources': [ + 'crashpad_info_size_test_module.cc', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['OS=="linux" or OS=="android"', { + 'sources': [ + 'crashpad_info_size_test_note.S', + ], + 'dependencies': [ + '../util/util.gyp:crashpad_util', + ], + }], + ], + }, + { + 'target_name': 'crashpad_snapshot_test_module_small', + 'type': 'loadable_module', + 'dependencies': [ + '../third_party/mini_chromium/mini_chromium.gyp:base', + ], + 'defines': [ + 'CRASHPAD_INFO_SIZE_TEST_MODULE_SMALL=1', + ], + 'sources': [ + 'crashpad_info_size_test_module.cc', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['OS=="linux" or OS=="android"', { + 'sources': [ + 'crashpad_info_size_test_note.S', + ], + 'dependencies': [ + '../util/util.gyp:crashpad_util', + ], + }], + ], + }, + { + 'target_name': 'crashpad_snapshot_test_both_dt_hash_styles', + 'type': 'executable', + 'conditions': [ + # .gnu.hash is incompatible with the MIPS ABI + ['target_arch!="mips"', { + 'sources': [ + 'hash_types_test.cc', + ], + 'ldflags': [ + # This makes `ld` emit both .hash and .gnu.hash sections. + '-Wl,--hash-style=both', + ]}, + ] + ], + }, ], 'conditions': [ ['OS=="mac"', { diff --git a/snapshot/system_snapshot.h b/snapshot/system_snapshot.h index 6da3d376..a363c0c8 100644 --- a/snapshot/system_snapshot.h +++ b/snapshot/system_snapshot.h @@ -47,6 +47,9 @@ class SystemSnapshot { //! \brief Android. kOperatingSystemAndroid, + + //! \brief Fuchsia. + kOperatingSystemFuchsia, }; //! \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 a7506b87..1e785af8 100644 --- a/snapshot/test/test_cpu_context.cc +++ b/snapshot/test/test_cpu_context.cc @@ -22,6 +22,68 @@ namespace crashpad { namespace test { +namespace { + +// This is templatized because the CPUContextX86::Fxsave and +// CPUContextX86_64::Fxsave are nearly identical but have different sizes for +// the members |xmm|, |reserved_4|, and |available|. +template +void InitializeCPUContextFxsave(FxsaveType* fxsave, uint32_t* seed) { + uint32_t value = *seed; + + fxsave->fcw = static_cast(value++); + fxsave->fsw = static_cast(value++); + fxsave->ftw = static_cast(value++); + fxsave->reserved_1 = static_cast(value++); + fxsave->fop = static_cast(value++); + fxsave->fpu_ip = value++; + fxsave->fpu_cs = static_cast(value++); + fxsave->reserved_2 = static_cast(value++); + fxsave->fpu_dp = value++; + fxsave->fpu_ds = static_cast(value++); + fxsave->reserved_3 = static_cast(value++); + fxsave->mxcsr = value++; + fxsave->mxcsr_mask = value++; + for (size_t st_mm_index = 0; st_mm_index < arraysize(fxsave->st_mm); + ++st_mm_index) { + for (size_t byte = 0; byte < arraysize(fxsave->st_mm[st_mm_index].st); + ++byte) { + fxsave->st_mm[st_mm_index].st[byte] = static_cast(value++); + } + for (size_t byte = 0; + byte < arraysize(fxsave->st_mm[st_mm_index].st_reserved); + ++byte) { + fxsave->st_mm[st_mm_index].st_reserved[byte] = + static_cast(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) { + fxsave->xmm[xmm_index][byte] = static_cast(value++); + } + } + for (size_t byte = 0; byte < arraysize(fxsave->reserved_4); ++byte) { + fxsave->reserved_4[byte] = static_cast(value++); + } + for (size_t byte = 0; byte < arraysize(fxsave->available); ++byte) { + fxsave->available[byte] = static_cast(value++); + } + + *seed = value; +} + +} // namespace + +void InitializeCPUContextX86Fxsave(CPUContextX86::Fxsave* fxsave, + uint32_t* seed) { + return InitializeCPUContextFxsave(fxsave, seed); +} + +void InitializeCPUContextX86_64Fxsave(CPUContextX86_64::Fxsave* fxsave, + uint32_t* seed) { + return InitializeCPUContextFxsave(fxsave, seed); +} + void InitializeCPUContextX86(CPUContext* context, uint32_t seed) { context->architecture = kCPUArchitectureX86; @@ -101,68 +163,133 @@ void InitializeCPUContextX86_64(CPUContext* context, uint32_t seed) { context->x86_64->dr7 = value++; } -namespace { +void InitializeCPUContextARM(CPUContext* context, uint32_t seed) { + context->architecture = kCPUArchitectureARM; + CPUContextARM* arm = context->arm; -// This is templatized because the CPUContextX86::Fxsave and -// CPUContextX86_64::Fxsave are nearly identical but have different sizes for -// the members |xmm|, |reserved_4|, and |available|. -template -void InitializeCPUContextFxsave(FxsaveType* fxsave, uint32_t* seed) { - uint32_t value = *seed; - - fxsave->fcw = static_cast(value++); - fxsave->fsw = static_cast(value++); - fxsave->ftw = static_cast(value++); - fxsave->reserved_1 = static_cast(value++); - fxsave->fop = static_cast(value++); - fxsave->fpu_ip = value++; - fxsave->fpu_cs = static_cast(value++); - fxsave->reserved_2 = static_cast(value++); - fxsave->fpu_dp = value++; - fxsave->fpu_ds = static_cast(value++); - fxsave->reserved_3 = static_cast(value++); - fxsave->mxcsr = value++; - fxsave->mxcsr_mask = value++; - for (size_t st_mm_index = 0; - st_mm_index < arraysize(fxsave->st_mm); - ++st_mm_index) { - for (size_t byte = 0; - byte < arraysize(fxsave->st_mm[st_mm_index].st); - ++byte) { - fxsave->st_mm[st_mm_index].st[byte] = static_cast(value++); - } - for (size_t byte = 0; - byte < arraysize(fxsave->st_mm[st_mm_index].st_reserved); - ++byte) { - fxsave->st_mm[st_mm_index].st_reserved[byte] = - static_cast(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) { - fxsave->xmm[xmm_index][byte] = static_cast(value++); - } - } - for (size_t byte = 0; byte < arraysize(fxsave->reserved_4); ++byte) { - fxsave->reserved_4[byte] = static_cast(value++); - } - for (size_t byte = 0; byte < arraysize(fxsave->available); ++byte) { - fxsave->available[byte] = static_cast(value++); + if (seed == 0) { + memset(arm, 0, sizeof(*arm)); + return; } - *seed = value; + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(arm->regs); ++index) { + arm->regs[index] = value++; + } + arm->fp = value++; + arm->ip = value++; + arm->ip = value++; + arm->sp = value++; + arm->lr = value++; + arm->pc = value++; + arm->cpsr = value++; + + for (size_t index = 0; index < arraysize(arm->vfp_regs.vfp); ++index) { + arm->vfp_regs.vfp[index] = value++; + } + arm->vfp_regs.fpscr = value++; + + arm->have_fpa_regs = false; + arm->have_vfp_regs = true; } -} // namespace +void InitializeCPUContextARM64(CPUContext* context, uint32_t seed) { + context->architecture = kCPUArchitectureARM64; + CPUContextARM64* arm64 = context->arm64; -void InitializeCPUContextX86Fxsave(CPUContextX86::Fxsave* fxsave, - uint32_t* seed) { - return InitializeCPUContextFxsave(fxsave, seed); + if (seed == 0) { + memset(arm64, 0, sizeof(*arm64)); + return; + } + + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(arm64->regs); ++index) { + arm64->regs[index] = value++; + } + arm64->sp = value++; + arm64->pc = value++; + arm64->pstate = value++; + + for (size_t index = 0; index < arraysize(arm64->fpsimd); ++index) { + arm64->fpsimd[index].lo = value++; + arm64->fpsimd[index].hi = value++; + } + arm64->fpsr = value++; + arm64->fpcr = value++; } -void InitializeCPUContextX86_64Fxsave(CPUContextX86_64::Fxsave* fxsave, - uint32_t* seed) { - return InitializeCPUContextFxsave(fxsave, seed); +void InitializeCPUContextMIPS(CPUContext* context, uint32_t seed) { + context->architecture = kCPUArchitectureMIPSEL; + CPUContextMIPS* mipsel = context->mipsel; + + if (seed == 0) { + memset(mipsel, 0, sizeof(*mipsel)); + return; + } + + uint32_t value = seed; + + for (size_t index = 0; index < arraysize(mipsel->regs); ++index) { + mipsel->regs[index] = value++; + } + + mipsel->mdlo = value++; + mipsel->mdhi = value++; + mipsel->cp0_epc = value++; + mipsel->cp0_badvaddr = value++; + mipsel->cp0_status = value++; + mipsel->cp0_cause = value++; + + for (size_t index = 0; index < arraysize(mipsel->fpregs.fregs); ++index) { + mipsel->fpregs.fregs[index]._fp_fregs = static_cast(value++); + } + + mipsel->fpcsr = value++; + mipsel->fir = value++; + + for (size_t index = 0; index < 3; ++index) { + mipsel->hi[index] = value++; + mipsel->lo[index] = value++; + } + mipsel->dsp_control = value++; +} + +void InitializeCPUContextMIPS64(CPUContext* context, uint32_t seed) { + context->architecture = kCPUArchitectureMIPS64EL; + CPUContextMIPS64* mips64 = context->mips64; + + if (seed == 0) { + memset(mips64, 0, sizeof(*mips64)); + return; + } + + uint64_t value = seed; + + for (size_t index = 0; index < arraysize(mips64->regs); ++index) { + mips64->regs[index] = value++; + } + + mips64->mdlo = value++; + mips64->mdhi = value++; + mips64->cp0_epc = value++; + mips64->cp0_badvaddr = value++; + mips64->cp0_status = value++; + mips64->cp0_cause = value++; + + for (size_t index = 0; index < arraysize(mips64->fpregs.dregs); ++index) { + mips64->fpregs.dregs[index] = static_cast(value++); + } + + mips64->fpcsr = value++; + mips64->fir = value++; + + for (size_t index = 0; index < 3; ++index) { + mips64->hi[index] = value++; + mips64->lo[index] = value++; + } + mips64->dsp_control = value++; } } // namespace test diff --git a/snapshot/test/test_cpu_context.h b/snapshot/test/test_cpu_context.h index 0de2497a..ca1b6b7b 100644 --- a/snapshot/test/test_cpu_context.h +++ b/snapshot/test/test_cpu_context.h @@ -22,6 +22,25 @@ namespace crashpad { namespace test { +//! \brief Initializes an `fxsave` context substructure for testing. +//! +//! \param[out] fxsave The structure to initialize. +//! \param[in,out] seed The seed value. Initializing two `fxsave` structures of +//! the same type with identical seed values should produce identical +//! structures. Initialization with a different seed value should produce +//! a different `fxsave` structure. If \a seed is `0`, \a fxsave is zeroed +//! out entirely. If \a seed is nonzero, \a fxsave will be populated +//! entirely with nonzero values. \a seed will be updated by this function +//! to allow the caller to perform subsequent initialization of the context +//! structure containing \a fxsave. +//! +//! \{ +void InitializeCPUContextX86Fxsave(CPUContextX86::Fxsave* fxsave, + uint32_t* seed); +void InitializeCPUContextX86_64Fxsave(CPUContextX86_64::Fxsave* fxsave, + uint32_t* seed); +//! \} + //! \brief Initializes a context structure for testing. //! //! Initialization is compatible with the initialization used by minidump @@ -40,25 +59,10 @@ namespace test { //! \{ void InitializeCPUContextX86(CPUContext* context, uint32_t seed); void InitializeCPUContextX86_64(CPUContext* context, uint32_t seed); -//! \} - -//! \brief Initializes an `fxsave` context substructure for testing. -//! -//! \param[out] fxsave The structure to initialize. -//! \param[in,out] seed The seed value. Initializing two `fxsave` structures of -//! the same type with identical seed values should produce identical -//! structures. Initialization with a different seed value should produce -//! a different `fxsave` structure. If \a seed is `0`, \a fxsave is zeroed -//! out entirely. If \a seed is nonzero, \a fxsave will be populated -//! entirely with nonzero values. \a seed will be updated by this function -//! to allow the caller to perform subsequent initialization of the context -//! structure containing \a fxsave. -//! -//! \{ -void InitializeCPUContextX86Fxsave( - CPUContextX86::Fxsave* fxsave, uint32_t* seed); -void InitializeCPUContextX86_64Fxsave( - CPUContextX86_64::Fxsave* fxsave, uint32_t* seed); +void InitializeCPUContextARM(CPUContext* context, uint32_t seed); +void InitializeCPUContextARM64(CPUContext* context, uint32_t seed); +void InitializeCPUContextMIPS(CPUContext* context, uint32_t seed); +void InitializeCPUContextMIPS64(CPUContext* context, uint32_t seed); //! \} } // namespace test diff --git a/snapshot/test/test_memory_snapshot.cc b/snapshot/test/test_memory_snapshot.cc index 5201be06..95aba406 100644 --- a/snapshot/test/test_memory_snapshot.cc +++ b/snapshot/test/test_memory_snapshot.cc @@ -14,13 +14,14 @@ #include "snapshot/test/test_memory_snapshot.h" +#include #include namespace crashpad { namespace test { TestMemorySnapshot::TestMemorySnapshot() - : address_(0), size_(0), value_('\0') { + : address_(0), size_(0), value_('\0'), should_fail_(false) { } TestMemorySnapshot::~TestMemorySnapshot() { @@ -35,6 +36,10 @@ size_t TestMemorySnapshot::Size() const { } bool TestMemorySnapshot::Read(Delegate* delegate) const { + if (should_fail_) { + return false; + } + if (size_ == 0) { return delegate->MemorySnapshotDelegateRead(nullptr, size_); } @@ -43,5 +48,18 @@ bool TestMemorySnapshot::Read(Delegate* delegate) const { return delegate->MemorySnapshotDelegateRead(&buffer[0], size_); } +const MemorySnapshot* TestMemorySnapshot::MergeWithOtherSnapshot( + const MemorySnapshot* other) const { + CheckedRange merged(0, 0); + if (!DetermineMergedRange(this, other, &merged)) + return nullptr; + + std::unique_ptr result(new TestMemorySnapshot()); + result->SetAddress(merged.base()); + result->SetSize(merged.size()); + result->SetValue(value_); + return result.release(); +} + } // namespace test } // namespace crashpad diff --git a/snapshot/test/test_memory_snapshot.h b/snapshot/test/test_memory_snapshot.h index 80b45c66..011e6c6f 100644 --- a/snapshot/test/test_memory_snapshot.h +++ b/snapshot/test/test_memory_snapshot.h @@ -40,16 +40,21 @@ class TestMemorySnapshot final : public MemorySnapshot { //! called. This value will be repeated Size() times. void SetValue(char value) { value_ = value; } + void SetShouldFailRead(bool should_fail) { should_fail_ = true; } + // 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: uint64_t address_; size_t size_; char value_; + bool should_fail_; DISALLOW_COPY_AND_ASSIGN(TestMemorySnapshot); }; diff --git a/snapshot/win/capture_memory_delegate_win.h b/snapshot/win/capture_memory_delegate_win.h index d5615aee..175b4c95 100644 --- a/snapshot/win/capture_memory_delegate_win.h +++ b/snapshot/win/capture_memory_delegate_win.h @@ -54,7 +54,8 @@ class CaptureMemoryDelegateWin : public CaptureMemory::Delegate { bool ReadMemory(uint64_t at, uint64_t num_bytes, void* into) const override; std::vector> GetReadableRanges( const CheckedRange& range) const override; - void AddNewMemorySnapshot(const CheckedRange& range); + void AddNewMemorySnapshot( + const CheckedRange& range) override; private: CheckedRange stack_; diff --git a/snapshot/win/crashpad_snapshot_test_crashing_child.cc b/snapshot/win/crashpad_snapshot_test_crashing_child.cc index 146c66a1..759cc138 100644 --- a/snapshot/win/crashpad_snapshot_test_crashing_child.cc +++ b/snapshot/win/crashpad_snapshot_test_crashing_child.cc @@ -15,21 +15,12 @@ #include #include -#include "base/files/file_path.h" #include "base/logging.h" +#include "build/build_config.h" #include "client/crashpad_client.h" -#include "util/file/file_io.h" -#include "util/misc/from_pointer_cast.h" +#include "util/misc/capture_context.h" #include "util/win/address_types.h" -namespace { - -__declspec(noinline) crashpad::WinVMAddress CurrentAddress() { - return crashpad::FromPointerCast(_ReturnAddress()); -} - -} // namespace - int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); @@ -38,8 +29,25 @@ int wmain(int argc, wchar_t* argv[]) { HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle"; - crashpad::WinVMAddress break_address = CurrentAddress(); - crashpad::CheckedWriteFile(out, &break_address, sizeof(break_address)); + + 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 + + // This does not used CheckedWriteFile() because at high optimization + // settings, a lot of logging code can be inlined, causing there to be a large + // number of instructions between where the IP is captured and the actual + // __debugbreak(). Instead call Windows' WriteFile() to minimize the amount of + // code here. Because the next line is going to crash in any case, there's + // minimal difference in behavior aside from an indication of what broke when + // the other end experiences a ReadFile() error. + DWORD bytes_written; + WriteFile( + out, &break_address, sizeof(break_address), &bytes_written, nullptr); __debugbreak(); diff --git a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc index f55f503b..e2c524ae 100644 --- a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc +++ b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc @@ -18,18 +18,9 @@ #include "base/logging.h" #include "client/crashpad_client.h" #include "client/simulate_crash.h" -#include "util/file/file_io.h" -#include "util/misc/from_pointer_cast.h" +#include "util/misc/capture_context.h" #include "util/win/address_types.h" -namespace { - -__declspec(noinline) crashpad::WinVMAddress CurrentAddress() { - return crashpad::FromPointerCast(_ReturnAddress()); -} - -} // namespace - int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); @@ -38,8 +29,25 @@ int wmain(int argc, wchar_t* argv[]) { HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); PCHECK(out != INVALID_HANDLE_VALUE) << "GetStdHandle"; - crashpad::WinVMAddress current_address = CurrentAddress(); - crashpad::CheckedWriteFile(out, ¤t_address, sizeof(current_address)); + + 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 + + // This does not used CheckedWriteFile() because at high optimization + // settings, a lot of logging code can be inlined, causing there to be a large + // number of instructions between where the IP is captured and the actual + // __debugbreak(). Instead call Windows' WriteFile() to minimize the amount of + // code here. Because the next line is going to crash in any case, there's + // minimal difference in behavior aside from an indication of what broke when + // the other end experiences a ReadFile() error. + DWORD bytes_written; + WriteFile( + out, &break_address, sizeof(break_address), &bytes_written, nullptr); CRASHPAD_SIMULATE_CRASH(); diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index c640a548..fd602fbc 100755 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import print_function + import os import platform import pywintypes @@ -99,7 +101,7 @@ def NamedPipeExistsAndReady(pipe_name): """ try: win32pipe.WaitNamedPipe(pipe_name, win32pipe.NMPWAIT_WAIT_FOREVER) - except pywintypes.error, e: + except pywintypes.error as e: if e[0] == winerror.ERROR_FILE_NOT_FOUND: return False raise @@ -135,7 +137,7 @@ def GetDumpFromProgram( printed = False while not NamedPipeExistsAndReady(pipe_name): if not printed: - print 'Waiting for crashpad_handler to be ready...' + print('Waiting for crashpad_handler to be ready...') printed = True time.sleep(0.001) @@ -145,7 +147,7 @@ def GetDumpFromProgram( os.path.join(out_dir, 'crashpad_handler.com'), test_database] + list(args)) - print 'Running %s' % os.path.basename(command[0]) + 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) @@ -153,7 +155,7 @@ def GetDumpFromProgram( out = subprocess.check_output([ os.path.join(out_dir, 'crashpad_database_util.exe'), '--database=' + test_database, - '--show-completed-reports', + '--show-pending-reports', '--show-all-report-info', ]) for line in out.splitlines(): @@ -219,16 +221,16 @@ class CdbRun(object): if match_obj: # Matched. Consume up to end of match. self.out = self.out[match_obj.end(0):] - print 'ok - %s' % message + print('ok - %s' % message) sys.stdout.flush() else: - print >>sys.stderr, '-' * 80 - print >>sys.stderr, 'FAILED - %s' % message - print >>sys.stderr, '-' * 80 - print >>sys.stderr, 'did not match:\n %s' % pattern - print >>sys.stderr, '-' * 80 - print >>sys.stderr, 'remaining output was:\n %s' % self.out - print >>sys.stderr, '-' * 80 + 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 @@ -430,12 +432,12 @@ def RunTests(cdb_path, def main(args): try: if len(args) != 1: - print >>sys.stderr, 'must supply binary dir' + print('must supply binary dir', file=sys.stderr) return 1 cdb_path = GetCdbPath() if not cdb_path: - print >>sys.stderr, 'could not find cdb' + print('could not find cdb', file=sys.stderr) return 1 # Make sure we can download Windows symbols. diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h index 0d216683..f6e29d9f 100644 --- a/snapshot/win/exception_snapshot_win.h +++ b/snapshot/win/exception_snapshot_win.h @@ -52,8 +52,8 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the process that sustained - //! the exception. + //! \param[in] process_reader A ProcessReaderWin for the process that + //! sustained the exception. //! \param[in] thread_id The thread ID in which the exception occurred. //! \param[in] exception_pointers The address of an `EXCEPTION_POINTERS` //! record in the target process, passed through from the exception diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc index 843ad263..537b4ca8 100644 --- a/snapshot/win/exception_snapshot_win_test.cc +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -79,7 +79,7 @@ class CrashingDelegate : public ExceptionHandlerServer::Delegate { : server_ready_(server_ready), completed_test_event_(completed_test_event), break_near_(0) {} - ~CrashingDelegate() override {} + ~CrashingDelegate() {} void set_break_near(WinVMAddress break_near) { break_near_ = break_near; } @@ -103,7 +103,7 @@ class CrashingDelegate : public ExceptionHandlerServer::Delegate { // 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(). - constexpr uint64_t kAllowedOffset = 64; + constexpr uint64_t kAllowedOffset = 100; EXPECT_GT(snapshot.Exception()->ExceptionAddress(), break_near_); EXPECT_LT(snapshot.Exception()->ExceptionAddress(), break_near_ + kAllowedOffset); @@ -163,7 +163,13 @@ void TestCrashingChild(TestPaths::Architecture architecture) { EXPECT_EQ(child.WaitForExit(), EXCEPTION_BREAKPOINT); } -TEST(ExceptionSnapshotWinTest, ChildCrash) { +#if defined(ADDRESS_SANITIZER) +// https://crbug.com/845011 +#define MAYBE_ChildCrash DISABLED_ChildCrash +#else +#define MAYBE_ChildCrash ChildCrash +#endif +TEST(ExceptionSnapshotWinTest, MAYBE_ChildCrash) { TestCrashingChild(TestPaths::Architecture::kDefault); } @@ -183,7 +189,7 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate { : server_ready_(server_ready), completed_test_event_(completed_test_event), dump_near_(0) {} - ~SimulateDelegate() override {} + ~SimulateDelegate() {} void set_dump_near(WinVMAddress dump_near) { dump_near_ = dump_near; } @@ -204,7 +210,13 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate { // Verify the dump was captured at the expected location with some slop // space. - constexpr uint64_t kAllowedOffset = 64; +#if defined(ADDRESS_SANITIZER) + // ASan instrumentation inserts more instructions between the expected + // location and what's reported. https://crbug.com/845011. + constexpr uint64_t kAllowedOffset = 500; +#else + constexpr uint64_t kAllowedOffset = 100; +#endif EXPECT_GT(snapshot.Exception()->Context()->InstructionPointer(), dump_near_); EXPECT_LT(snapshot.Exception()->Context()->InstructionPointer(), diff --git a/snapshot/win/memory_snapshot_win.cc b/snapshot/win/memory_snapshot_win.cc index 666cb936..17e8ac11 100644 --- a/snapshot/win/memory_snapshot_win.cc +++ b/snapshot/win/memory_snapshot_win.cc @@ -16,7 +16,6 @@ #include "snapshot/win/memory_snapshot_win.h" - namespace crashpad { namespace internal { @@ -67,5 +66,10 @@ bool MemorySnapshotWin::Read(Delegate* delegate) const { 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 index 04d11842..ebc878b8 100644 --- a/snapshot/win/memory_snapshot_win.h +++ b/snapshot/win/memory_snapshot_win.h @@ -52,8 +52,15 @@ class MemorySnapshotWin final : public 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 + friend const MemorySnapshot* MergeWithOtherSnapshotImpl( + const T* self, + const MemorySnapshot* other); + ProcessReaderWin* process_reader_; // weak uint64_t address_; size_t size_; diff --git a/snapshot/win/module_snapshot_win.cc b/snapshot/win/module_snapshot_win.cc index b130eccb..880e9da0 100644 --- a/snapshot/win/module_snapshot_win.cc +++ b/snapshot/win/module_snapshot_win.cc @@ -70,9 +70,8 @@ bool ModuleSnapshotWin::Initialize( // If we fully supported all old debugging formats, we would want to extract // and emit a different type of CodeView record here (as old Microsoft tools // would do). As we don't expect to ever encounter a module that wouldn't be - // be using .PDB that we actually have symbols for, we simply set a - // plausible name here, but this will never correspond to symbols that we - // have. + // using .PDB that we actually have symbols for, we simply set a plausible + // name here, but this will never correspond to symbols that we have. pdb_name_ = base::UTF16ToUTF8(name_); } @@ -177,8 +176,8 @@ std::string ModuleSnapshotWin::DebugFileName() const { std::vector ModuleSnapshotWin::AnnotationsVector() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // These correspond to system-logged things on Mac. We don't currently track - // any of these on Windows, but could in the future. - // See https://crashpad.chromium.org/bug/38. + // any of these on Windows, but could in the future. See + // https://crashpad.chromium.org/bug/38. return std::vector(); } @@ -192,8 +191,9 @@ std::map ModuleSnapshotWin::AnnotationsSimpleMap() std::vector ModuleSnapshotWin::AnnotationObjects() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - NOTREACHED(); - return {}; + PEImageAnnotationsReader annotations_reader( + process_reader_, pe_image_reader_.get(), name_); + return annotations_reader.AnnotationsList(); } std::set> ModuleSnapshotWin::ExtraMemoryRanges() const { @@ -271,11 +271,10 @@ template void ModuleSnapshotWin::GetCrashpadExtraMemoryRanges( std::set>* ranges) const { process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) - return; - - if (!crashpad_info.extra_address_ranges) + if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info) || + !crashpad_info.extra_address_ranges) { return; + } std::vector simple_ranges( SimpleAddressRangeBag::num_entries); diff --git a/snapshot/win/module_snapshot_win.h b/snapshot/win/module_snapshot_win.h index 2a7083d1..693588db 100644 --- a/snapshot/win/module_snapshot_win.h +++ b/snapshot/win/module_snapshot_win.h @@ -47,10 +47,10 @@ class ModuleSnapshotWin final : public ModuleSnapshot { //! \brief Initializes the object. //! - //! \param[in] process_reader A ProcessReader for the task containing the - //! module. - //! \param[in] process_reader_module The module within the ProcessReader for - //! which the snapshot should be created. + //! \param[in] process_reader A ProcessReaderWin for the process containing + //! the module. + //! \param[in] process_reader_module The module within the ProcessReaderWin + //! for which the snapshot should be created. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. @@ -118,7 +118,7 @@ class ModuleSnapshotWin final : public ModuleSnapshot { InitializationStateDcheck initialized_; // VSFixedFileInfo() is logically const, but updates these members on the - // the call. See https://crashpad.chromium.org/bug/9. + // call. See https://crashpad.chromium.org/bug/9. mutable VS_FIXEDFILEINFO vs_fixed_file_info_; mutable InitializationState initialized_vs_fixed_file_info_; diff --git a/snapshot/win/pe_image_annotations_reader.cc b/snapshot/win/pe_image_annotations_reader.cc index 4ef8a1ea..67961457 100644 --- a/snapshot/win/pe_image_annotations_reader.cc +++ b/snapshot/win/pe_image_annotations_reader.cc @@ -85,11 +85,10 @@ template void PEImageAnnotationsReader::ReadCrashpadSimpleAnnotations( std::map* simple_map_annotations) const { process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) - return; - - if (!crashpad_info.simple_annotations) + if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info) || + !crashpad_info.simple_annotations) { return; + } std::vector simple_annotations(SimpleStringDictionary::num_entries); @@ -122,11 +121,8 @@ template void PEImageAnnotationsReader::ReadCrashpadAnnotationsList( std::vector* vector_annotations) const { process_types::CrashpadInfo crashpad_info; - if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info)) { - return; - } - - if (!crashpad_info.annotations_list) { + if (!pe_image_reader_->GetCrashpadInfo(&crashpad_info) || + !crashpad_info.annotations_list) { return; } diff --git a/snapshot/win/pe_image_annotations_reader_test.cc b/snapshot/win/pe_image_annotations_reader_test.cc index f576394c..e1026008 100644 --- a/snapshot/win/pe_image_annotations_reader_test.cc +++ b/snapshot/win/pe_image_annotations_reader_test.cc @@ -102,7 +102,7 @@ void TestAnnotationsOnCrash(TestType type, EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], ""); // Verify the typed annotation objects. - EXPECT_EQ(all_annotation_objects.size(), 3); + EXPECT_EQ(all_annotation_objects.size(), 3u); bool saw_same_name_3 = false, saw_same_name_4 = false; for (const auto& annotation : all_annotation_objects) { EXPECT_EQ(annotation.type, diff --git a/snapshot/win/pe_image_reader.cc b/snapshot/win/pe_image_reader.cc index 824f525b..e705bb51 100644 --- a/snapshot/win/pe_image_reader.cc +++ b/snapshot/win/pe_image_reader.cc @@ -17,9 +17,11 @@ #include #include +#include #include #include "base/logging.h" +#include "base/strings/stringprintf.h" #include "client/crashpad_info.h" #include "snapshot/win/pe_image_resource_reader.h" #include "util/misc/from_pointer_cast.h" @@ -80,39 +82,58 @@ bool PEImageReader::GetCrashpadInfo( return false; } - if (section.Misc.VirtualSize < sizeof(process_types::CrashpadInfo)) { + if (section.Misc.VirtualSize < + offsetof(process_types::CrashpadInfo, size) + + sizeof(crashpad_info->size)) { LOG(WARNING) << "small crashpad info section size " << section.Misc.VirtualSize << ", " << module_subrange_reader_.name(); return false; } - ProcessSubrangeReader crashpad_info_subrange_reader; const WinVMAddress crashpad_info_address = Address() + section.VirtualAddress; - if (!crashpad_info_subrange_reader.InitializeSubrange( - module_subrange_reader_, - crashpad_info_address, - section.Misc.VirtualSize, - "crashpad_info")) { - return false; - } - - if (!crashpad_info_subrange_reader.ReadMemory( - crashpad_info_address, - sizeof(process_types::CrashpadInfo), - crashpad_info)) { + const WinVMSize crashpad_info_size = + std::min(static_cast(sizeof(*crashpad_info)), + static_cast(section.Misc.VirtualSize)); + if (!module_subrange_reader_.ReadMemory( + crashpad_info_address, crashpad_info_size, crashpad_info)) { LOG(WARNING) << "could not read crashpad info from " << module_subrange_reader_.name(); return false; } + if (crashpad_info->size < sizeof(*crashpad_info)) { + // Zero out anything beyond the structure’s declared size. + memset(reinterpret_cast(crashpad_info) + crashpad_info->size, + 0, + sizeof(*crashpad_info) - crashpad_info->size); + } + if (crashpad_info->signature != CrashpadInfo::kSignature || - crashpad_info->version < 1) { - LOG(WARNING) << "unexpected crashpad info data in " - << module_subrange_reader_.name(); + crashpad_info->version != 1) { + LOG(WARNING) << base::StringPrintf( + "unexpected crashpad info signature 0x%x, version %u in %s", + crashpad_info->signature, + crashpad_info->version, + module_subrange_reader_.name().c_str()); return false; } + // Don’t require strict equality, to leave wiggle room for sloppy linkers. + if (crashpad_info->size > section.Misc.VirtualSize) { + LOG(WARNING) << "crashpad info struct size " << crashpad_info->size + << " large for section size " << section.Misc.VirtualSize + << " in " << module_subrange_reader_.name(); + return false; + } + + if (crashpad_info->size > sizeof(*crashpad_info)) { + // This isn’t strictly a problem, because unknown fields will simply be + // ignored, but it may be of diagnostic interest. + LOG(INFO) << "large crashpad info size " << crashpad_info->size << ", " + << module_subrange_reader_.name(); + } + return true; } @@ -214,7 +235,7 @@ bool PEImageReader::VSFixedFileInfo( } // This structure is not declared anywhere in the SDK, but is documented at - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001.aspx. + // https://msdn.microsoft.com/library/ms647001.aspx. struct VS_VERSIONINFO { WORD wLength; WORD wValueLength; diff --git a/snapshot/win/pe_image_reader_test.cc b/snapshot/win/pe_image_reader_test.cc index 3192a446..ac456e2d 100644 --- a/snapshot/win/pe_image_reader_test.cc +++ b/snapshot/win/pe_image_reader_test.cc @@ -18,46 +18,57 @@ #include #include "base/files/file_path.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" #include "snapshot/win/process_reader_win.h" #include "test/errors.h" +#include "test/scoped_module_handle.h" #include "test/test_paths.h" #include "util/misc/from_pointer_cast.h" #include "util/win/get_module_information.h" #include "util/win/module_version.h" #include "util/win/process_info.h" -extern "C" IMAGE_DOS_HEADER __ImageBase; - namespace crashpad { namespace test { namespace { TEST(PEImageReader, DebugDirectory) { + base::FilePath module_path = + TestPaths::BuildArtifact(L"snapshot", + L"image_reader_module", + TestPaths::FileType::kLoadableModule); + ScopedModuleHandle module_handle(LoadLibrary(module_path.value().c_str())); + ASSERT_TRUE(module_handle.valid()) << ErrorMessage("LoadLibrary"); + PEImageReader pe_image_reader; ProcessReaderWin process_reader; ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(), ProcessSuspensionState::kRunning)); - HMODULE self = reinterpret_cast(&__ImageBase); + MODULEINFO module_info; - ASSERT_TRUE(CrashpadGetModuleInformation( - GetCurrentProcess(), self, &module_info, sizeof(module_info))) + ASSERT_TRUE(CrashpadGetModuleInformation(GetCurrentProcess(), + module_handle.get(), + &module_info, + sizeof(module_info))) << ErrorMessage("GetModuleInformation"); - EXPECT_EQ(module_info.lpBaseOfDll, self); - ASSERT_TRUE(pe_image_reader.Initialize(&process_reader, - FromPointerCast(self), - module_info.SizeOfImage, - "self")); + EXPECT_EQ(module_info.lpBaseOfDll, module_handle.get()); + + base::FilePath module_basename = module_path.BaseName(); + ASSERT_TRUE(pe_image_reader.Initialize( + &process_reader, + FromPointerCast(module_handle.get()), + module_info.SizeOfImage, + base::UTF16ToUTF8(module_basename.value()))); + UUID uuid; DWORD age; std::string pdbname; - EXPECT_TRUE(pe_image_reader.DebugDirectoryInformation(&uuid, &age, &pdbname)); - std::string self_name = base::UTF16ToUTF8( - TestPaths::ExpectedExecutableBasename(L"crashpad_snapshot_test") - .RemoveFinalExtension() - .value()); - EXPECT_NE(pdbname.find(self_name), std::string::npos); + ASSERT_TRUE(pe_image_reader.DebugDirectoryInformation(&uuid, &age, &pdbname)); + std::string module_name = + base::UTF16ToUTF8(module_basename.RemoveFinalExtension().value()); + EXPECT_NE(pdbname.find(module_name), std::string::npos); const std::string suffix(".pdb"); EXPECT_EQ( pdbname.compare(pdbname.size() - suffix.size(), suffix.size(), suffix), @@ -86,8 +97,17 @@ void TestVSFixedFileInfo(ProcessReaderWin* process_reader, EXPECT_EQ(observed.dwFileType, static_cast(VFT_DLL)); } else { EXPECT_NE(observed.dwFileOS & VOS_NT_WINDOWS32, 0u); + + // VFT_DRV/VFT2_DRV_NETWORK is for nsi.dll, “network service interface.” + // It’s not normally loaded, but has been observed to be loaded in some + // cases. EXPECT_TRUE(observed.dwFileType == VFT_APP || - observed.dwFileType == VFT_DLL); + observed.dwFileType == VFT_DLL || + (observed.dwFileType == VFT_DRV && + observed.dwFileSubtype == VFT2_DRV_NETWORK)) + << base::StringPrintf("type 0x%lx, subtype 0x%lx", + observed.dwFileType, + observed.dwFileSubtype); } } diff --git a/snapshot/win/pe_image_resource_reader.cc b/snapshot/win/pe_image_resource_reader.cc index a010276a..f3739452 100644 --- a/snapshot/win/pe_image_resource_reader.cc +++ b/snapshot/win/pe_image_resource_reader.cc @@ -165,7 +165,7 @@ uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByLanguage( return 0; } - // https://msdn.microsoft.com/en-us/library/cc194810.aspx + // https://msdn.microsoft.com/library/cc194810.aspx // // TODO(mark): It seems like FindResourceEx() might do something more complex. // It would be best to mimic its behavior. diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index f8a2f928..2fa02584 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -21,12 +21,12 @@ #include "base/numerics/safe_conversions.h" #include "base/strings/stringprintf.h" -#include "util/win/capture_context.h" +#include "util/misc/capture_context.h" +#include "util/misc/time.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_structs.h" #include "util/win/scoped_handle.h" -#include "util/win/time.h" namespace crashpad { @@ -374,9 +374,9 @@ void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) { // TODO(scottmg): I believe we could reverse engineer the PriorityClass from // the Priority, BasePriority, and - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 . - // MinidumpThreadWriter doesn't handle it yet in any case, so investigate - // both of those at the same time if it's useful. + // https://msdn.microsoft.com/library/ms685100.aspx. MinidumpThreadWriter + // doesn't handle it yet in any case, so investigate both of those at the + // same time if it's useful. thread.priority_class = NORMAL_PRIORITY_CLASS; thread.priority = thread_info.Priority; @@ -403,7 +403,7 @@ void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) { WinVMAddress limit = 0; // If we're reading a WOW64 process, then the TIB we just retrieved is the // x64 one. The first word of the x64 TIB points at the x86 TIB. See - // https://msdn.microsoft.com/en-us/library/dn424783.aspx + // https://msdn.microsoft.com/library/dn424783.aspx. if (is_64_reading_32) { process_types::NT_TIB tib32; thread.teb_address = tib.Wow64Teb; diff --git a/snapshot/win/process_snapshot_win.cc b/snapshot/win/process_snapshot_win.cc index 532527b4..289f50c0 100644 --- a/snapshot/win/process_snapshot_win.cc +++ b/snapshot/win/process_snapshot_win.cc @@ -24,9 +24,9 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "util/misc/from_pointer_cast.h" +#include "util/misc/time.h" #include "util/win/nt_internals.h" #include "util/win/registration_protocol_win.h" -#include "util/win/time.h" namespace crashpad { @@ -265,12 +265,11 @@ void ProcessSnapshotWin::InitializeModules() { } void ProcessSnapshotWin::InitializeUnloadedModules() { - // As documented by https://msdn.microsoft.com/en-us/library/cc678403.aspx - // we can retrieve the location for our unload events, and use that address in - // the target process. Unfortunately, this of course only works for - // 64-reading-64 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. + // As documented by https://msdn.microsoft.com/library/cc678403.aspx, we can + // retrieve the location for our unload events, and use that address in the + // target process. Unfortunately, this of course only works for 64-reading-64 + // 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 (!process_reader_.Is64Bit()) { @@ -516,9 +515,9 @@ void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( WinVMSize ProcessSnapshotWin::DetermineSizeOfEnvironmentBlock( WinVMAddress start_of_environment_block) { - // http://blogs.msdn.com/b/oldnewthing/archive/2010/02/03/9957320.aspx On - // newer OSs there's no stated limit, but in practice grabbing 32k characters - // should be more than enough. + // https://blogs.msdn.microsoft.com/oldnewthing/20100203-00/?p=15083: On newer + // OSs there's no stated limit, but in practice grabbing 32k characters should + // be more than enough. std::wstring env_block; env_block.resize(32768); WinVMSize bytes_read = process_reader_.ReadAvailableMemory( diff --git a/snapshot/win/process_snapshot_win_test.cc b/snapshot/win/process_snapshot_win_test.cc index b2448fcf..8a3e6a49 100644 --- a/snapshot/win/process_snapshot_win_test.cc +++ b/snapshot/win/process_snapshot_win_test.cc @@ -26,6 +26,7 @@ #include "util/file/file_io.h" #include "util/win/scoped_handle.h" #include "util/win/scoped_process_suspend.h" +#include "util/win/scoped_set_event.h" namespace crashpad { namespace test { @@ -46,6 +47,8 @@ void TestImageReaderChild(const TestPaths::Architecture architecture) { ChildLauncher child(child_test_executable, done_uuid.ToString16()); ASSERT_NO_FATAL_FAILURE(child.Start()); + ScopedSetEvent set_done(done.get()); + char c; ASSERT_TRUE( LoggingReadFileExactly(child.stdout_read_handle(), &c, sizeof(c))); @@ -105,7 +108,7 @@ void TestImageReaderChild(const TestPaths::Architecture architecture) { } // Tell the child it can terminate. - EXPECT_TRUE(SetEvent(done.get())) << ErrorMessage("SetEvent"); + EXPECT_TRUE(set_done.Set()); EXPECT_EQ(child.WaitForExit(), 0u); } diff --git a/snapshot/win/system_snapshot_win_test.cc b/snapshot/win/system_snapshot_win_test.cc index 5f469a3f..c87a6a6a 100644 --- a/snapshot/win/system_snapshot_win_test.cc +++ b/snapshot/win/system_snapshot_win_test.cc @@ -130,26 +130,28 @@ TEST_F(SystemSnapshotWinTest, TimeZone) { // In contemporary usage, most time zones have an integer hour offset from // UTC, although several are at a half-hour offset, and two are at 15-minute // offsets. Throughout history, other variations existed. See - // http://www.timeanddate.com/time/time-zones-interesting.html. + // https://www.timeanddate.com/time/time-zones-interesting.html. EXPECT_EQ(standard_offset_seconds % (15 * 60), 0) << "standard_offset_seconds " << standard_offset_seconds; - if (dst_status == SystemSnapshot::kDoesNotObserveDaylightSavingTime) { - EXPECT_EQ(daylight_offset_seconds, standard_offset_seconds); - EXPECT_EQ(daylight_name, standard_name); - } else { - EXPECT_EQ(daylight_offset_seconds % (15 * 60), 0) - << "daylight_offset_seconds " << daylight_offset_seconds; + // dst_status of kDoesNotObserveDaylightSavingTime can mean only that the + // adjustment is not automatic, as opposed to daylight/standard differences + // not existing at all. So it cannot be asserted that the two offsets are the + // same in that case. - // In contemporary usage, dst_delta_seconds will almost always be one hour, - // except for Lord Howe Island, Australia, which uses a 30-minute - // delta. Throughout history, other variations existed. See - // http://www.timeanddate.com/time/dst/#brief. - int dst_delta_seconds = daylight_offset_seconds - standard_offset_seconds; - if (dst_delta_seconds != 60 * 60 && dst_delta_seconds != 30 * 60) { - FAIL() << "dst_delta_seconds " << dst_delta_seconds; - } + EXPECT_EQ(daylight_offset_seconds % (15 * 60), 0) + << "daylight_offset_seconds " << daylight_offset_seconds; + // In contemporary usage, dst_delta_seconds will almost always be one hour, + // except for Lord Howe Island, Australia, which uses a 30-minute delta. + // Throughout history, other variations existed. See + // https://www.timeanddate.com/time/dst/. + int dst_delta_seconds = daylight_offset_seconds - standard_offset_seconds; + if (dst_delta_seconds != 60 * 60 && dst_delta_seconds != 30 * 60) { + FAIL() << "dst_delta_seconds " << dst_delta_seconds; + } + + if (dst_status != SystemSnapshot::kDoesNotObserveDaylightSavingTime) { EXPECT_NE(standard_name, daylight_name); } } diff --git a/snapshot/x86/cpuid_reader.h b/snapshot/x86/cpuid_reader.h index 0fd02cfd..b6782afb 100644 --- a/snapshot/x86/cpuid_reader.h +++ b/snapshot/x86/cpuid_reader.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef CRASHPAD_SNAPSHOT_X86_CPUID_READER_H_ +#define CRASHPAD_SNAPSHOT_X86_CPUID_READER_H_ + #include #include @@ -61,3 +64,5 @@ class CpuidReader { } // namespace internal } // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_X86_CPUID_READER_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn new file mode 100644 index 00000000..d1e6297e --- /dev/null +++ b/test/BUILD.gn @@ -0,0 +1,218 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +static_library("test") { + testonly = true + + sources = [ + "errors.cc", + "errors.h", + "file.cc", + "file.h", + "filesystem.cc", + "filesystem.h", + "gtest_death.h", + "gtest_disabled.cc", + "gtest_disabled.h", + "hex_string.cc", + "hex_string.h", + "main_arguments.cc", + "main_arguments.h", + "multiprocess.h", + "multiprocess_exec.cc", + "multiprocess_exec.h", + "process_type.cc", + "process_type.h", + "scoped_module_handle.cc", + "scoped_module_handle.h", + "scoped_temp_dir.cc", + "scoped_temp_dir.h", + "test_paths.cc", + "test_paths.h", + ] + + if (crashpad_is_posix || crashpad_is_fuchsia) { + sources += [ "scoped_temp_dir_posix.cc" ] + + if (!crashpad_is_fuchsia) { + sources += [ + "multiprocess_exec_posix.cc", + "multiprocess_posix.cc", + ] + } + } + + 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_linux || crashpad_is_android) { + set_sources_assignment_filter([]) + sources += [ + "linux/fake_ptrace_connection.cc", + "linux/fake_ptrace_connection.h", + "linux/get_tls.cc", + "linux/get_tls.h", + ] + } + + if (crashpad_is_win) { + sources += [ + "multiprocess_exec_win.cc", + "scoped_temp_dir_win.cc", + "win/child_launcher.cc", + "win/child_launcher.h", + "win/win_child_process.cc", + "win/win_child_process.h", + "win/win_multiprocess.cc", + "win/win_multiprocess.h", + "win/win_multiprocess_with_temp_dir.cc", + "win/win_multiprocess_with_temp_dir.h", + ] + } + + if (crashpad_is_fuchsia) { + sources += [ "multiprocess_exec_fuchsia.cc" ] + } + + public_configs = [ "..:crashpad_config" ] + + configs += [ + "../build:crashpad_is_in_chromium", + "../build:crashpad_is_in_fuchsia" + ] + + data = [ + "test_paths_test_data_root.txt", + ] + + deps = [ + "../compat", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + if (crashpad_is_mac) { + libs = [ "bsm" ] + deps += [ + "../handler", + "../snapshot", + ] + } + + if (crashpad_is_win) { + libs = [ "shell32.lib" ] + } + + if (crashpad_is_fuchsia && crashpad_is_in_fuchsia) { + deps += [ + "//zircon/public/lib/fdio", + ] + } +} + +source_set("test_test") { + testonly = true + + sources = [ + "hex_string_test.cc", + "main_arguments_test.cc", + "multiprocess_exec_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) { + sources += [ "multiprocess_posix_test.cc" ] + } + + if (crashpad_is_mac) { + sources += [ "mac/mach_multiprocess_test.cc" ] + } + + if (crashpad_is_win) { + sources += [ + "win/win_child_process_test.cc", + "win/win_multiprocess_test.cc", + ] + } + + deps = [ + ":test", + "../compat", + "../third_party/gtest:gmock", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../util", + ] + + data_deps = [ + ":crashpad_test_test_multiprocess_exec_test_child", + ] +} + +crashpad_executable("crashpad_test_test_multiprocess_exec_test_child") { + sources = [ + "multiprocess_exec_test_child.cc", + ] + + deps = [ + "../third_party/mini_chromium:base", + ] +} + +static_library("gmock_main") { + testonly = true + sources = [ + "gtest_main.cc", + ] + configs += [ "../build:crashpad_is_in_chromium" ] + defines = [ "CRASHPAD_TEST_LAUNCHER_GMOCK" ] + deps = [ + ":test", + "../third_party/gtest:gmock", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../third_party/mini_chromium:base_test_support", + ] +} + +static_library("gtest_main") { + testonly = true + sources = [ + "gtest_main.cc", + ] + configs += [ "../build:crashpad_is_in_chromium" ] + defines = [ "CRASHPAD_TEST_LAUNCHER_GTEST" ] + deps = [ + ":test", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../third_party/mini_chromium:base_test_support", + ] +} diff --git a/test/errors.h b/test/errors.h index c4f1a532..cf84ad64 100644 --- a/test/errors.h +++ b/test/errors.h @@ -35,7 +35,7 @@ namespace test { //! \brief Formats an error message using an `errno` value. //! //! The returned string will combine the \a base string, if supplied, with a -//! a textual and numeric description of the error. +//! textual and numeric description of the error. //! //! The message is formatted using `strerror()`. \a err may be `0` or outside of //! the range of known error codes, and the message returned will contain the @@ -53,7 +53,7 @@ std::string ErrnoMessage(int err, const std::string& base = std::string()); //! \brief Formats an error message using `errno`. //! //! The returned string will combine the \a base string, if supplied, with a -//! a textual and numeric description of the error. +//! textual and numeric description of the error. //! //! The message is formatted using `strerror()`. `errno` may be `0` or outside //! of the range of known error codes, and the message returned will contain the @@ -71,8 +71,8 @@ std::string ErrnoMessage(const std::string& base = std::string()); //! \brief Formats an error message using `GetLastError()`. //! //! The returned string will combine the \a base string, if supplied, with a -//! a textual and numeric description of the error. The format is the same as -//! the `PLOG()` formatting in base. +//! textual and numeric description of the error. The format is the same as the +//! `PLOG()` formatting in base. std::string ErrorMessage(const std::string& base = std::string()); #endif diff --git a/test/filesystem.cc b/test/filesystem.cc index a9a73f6a..c1a912d3 100644 --- a/test/filesystem.cc +++ b/test/filesystem.cc @@ -15,7 +15,9 @@ #include "test/filesystem.h" #include +#include #include +#include #include #include "base/logging.h" @@ -25,6 +27,7 @@ #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" +#include "util/misc/time.h" #if defined(OS_POSIX) #include @@ -116,6 +119,63 @@ bool PathExists(const base::FilePath& path) { #endif } +bool SetFileModificationTime(const base::FilePath& path, + const timespec& mtime) { +#if defined(OS_MACOSX) + // utimensat() isn't available on macOS until 10.13, so lutimes() is used + // instead. + struct stat st; + if (lstat(path.value().c_str(), &st) != 0) { + PLOG(ERROR) << "lstat " << path.value(); + return false; + } + timeval times[2]; + EXPECT_TRUE(TimespecToTimeval(st.st_atimespec, ×[0])); + EXPECT_TRUE(TimespecToTimeval(mtime, ×[1])); + if (lutimes(path.value().c_str(), times) != 0) { + PLOG(ERROR) << "lutimes " << path.value(); + return false; + } + return true; +#elif defined(OS_POSIX) + timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1] = mtime; + if (utimensat(AT_FDCWD, path.value().c_str(), times, AT_SYMLINK_NOFOLLOW) != + 0) { + PLOG(ERROR) << "utimensat " << path.value(); + return false; + } + return true; +#elif defined(OS_WIN) + DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT; + if (IsDirectory(path, true)) { + // required for directory handles + flags |= FILE_FLAG_BACKUP_SEMANTICS; + } + + ScopedFileHandle handle(::CreateFile(path.value().c_str(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + flags, + nullptr)); + if (!handle.is_valid()) { + PLOG(ERROR) << "CreateFile " << base::UTF16ToUTF8(path.value()); + return false; + } + + FILETIME filetime = TimespecToFiletimeEpoch(mtime); + if (!SetFileTime(handle.get(), nullptr, nullptr, &filetime)) { + PLOG(ERROR) << "SetFileTime " << base::UTF16ToUTF8(path.value()); + return false; + } + return true; +#endif // OS_MACOSX +} + #if !defined(OS_FUCHSIA) bool CanCreateSymbolicLinks() { diff --git a/test/filesystem.h b/test/filesystem.h index 7e658456..516335bb 100644 --- a/test/filesystem.h +++ b/test/filesystem.h @@ -17,15 +17,26 @@ #include "base/files/file_path.h" +#include + #include "build/build_config.h" namespace crashpad { namespace test { -bool CreateFile(const base::FilePath& file); +//! \brief Creates an empty file at path \a filepath. +bool CreateFile(const base::FilePath& filepath); +//! \brief Returns `true` if a filesystem node exists at path \a path. bool PathExists(const base::FilePath& path); +//! \brief Sets the modification time for a file, directory, or symbolic link. +//! +//! \param[in] path The path to the file to set the modification time for. +//! \param[in] mtime The new modification time for the file. +//! \return `true` on success. Otherwise `false` with a message logged. +bool SetFileModificationTime(const base::FilePath& path, const timespec& mtime); + #if !defined(OS_FUCHSIA) || DOXYGEN // There are no symbolic links on Fuchsia. Don’t bother declaring or defining // symbolic link-related functions at all, because it’s an error to even pretend @@ -48,6 +59,11 @@ bool PathExists(const base::FilePath& path); //! in Windows 10! bool CanCreateSymbolicLinks(); +//! \brief Creates a new symbolic link. +//! +//! \param[in] target_path The target for the link. +//! \param[in] symlink_path The name for the new link. +//! \return `true` on success. Otherwise `false` with a message logged. bool CreateSymbolicLink(const base::FilePath& target_path, const base::FilePath& symlink_path); diff --git a/test/gtest_death.h b/test/gtest_death.h new file mode 100644 index 00000000..69f6361e --- /dev/null +++ b/test/gtest_death.h @@ -0,0 +1,133 @@ +// 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_TEST_GTEST_DEATH_H_ +#define CRASHPAD_TEST_GTEST_DEATH_H_ + +#include "base/logging.h" +#include "build/build_config.h" +#include "gtest/gtest.h" + +#if defined(OS_MACOSX) +#include "test/mac/exception_swallower.h" +#endif + +//! \file + +#if defined(OS_MACOSX) || DOXYGEN + +//! \brief Wraps the gtest `ASSERT_DEATH_IF_SUPPORTED()` macro to make +//! assertions about death caused by crashes. +//! +//! On macOS, this macro prevents the system’s crash reporter from handling +//! crashes that occur in \a statement. Crashes are normally visible to the +//! system’s crash reporter, but it is undesirable for intentional +//! ASSERT_DEATH_CRASH() crashes to be handled by any crash reporter. +//! +//! `ASSERT_DEATH_IF_SUPPORTED()` is used instead of `ASSERT_DEATH()` to +//! support platforms where death tests are not implemented by gtest (e.g. +//! Fuchsia). On platforms where death tests are not implemented, a warning +//! will be logged and the remainder of the test body skipped. +//! +//! \sa ASSERT_DEATH_CHECK() +//! \sa EXPECT_DEATH_CRASH() +#define ASSERT_DEATH_CRASH(statement, regex) \ + do { \ + crashpad::test::ExceptionSwallower exception_swallower; \ + ASSERT_DEATH_IF_SUPPORTED( \ + crashpad::test::ExceptionSwallower::SwallowExceptions(); \ + { statement; }, regex); \ + } while (false) + +//! \brief Wraps the gtest `EXPECT_DEATH_IF_SUPPORTED()` macro to make +//! assertions about death caused by crashes. +//! +//! On macOS, this macro prevents the system’s crash reporter from handling +//! crashes that occur in \a statement. Crashes are normally visible to the +//! system’s crash reporter, but it is undesirable for intentional +//! EXPECT_DEATH_CRASH() crashes to be handled by any crash reporter. +//! +//! `EXPECT_DEATH_IF_SUPPORTED()` is used instead of `EXPECT_DEATH()` to +//! support platforms where death tests are not implemented by gtest (e.g. +//! Fuchsia). On platforms where death tests are not implemented, a warning +//! will be logged and the remainder of the test body skipped. +//! +//! \sa EXPECT_DEATH_CHECK() +//! \sa ASSERT_DEATH_CRASH() +#define EXPECT_DEATH_CRASH(statement, regex) \ + do { \ + crashpad::test::ExceptionSwallower exception_swallower; \ + EXPECT_DEATH(crashpad::test::ExceptionSwallower::SwallowExceptions(); \ + { statement; }, \ + regex); \ + } while (false) + +#else // OS_MACOSX + +#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 + +#if !(!defined(MINI_CHROMIUM_BASE_LOGGING_H_) && \ + defined(OFFICIAL_BUILD) && \ + defined(NDEBUG)) || \ + DOXYGEN + +//! \brief Wraps the ASSERT_DEATH_CRASH() macro to make assertions about death +//! caused by `CHECK()` failures. +//! +//! In an in-Chromium build in the official configuration, `CHECK()` does not +//! print its condition or streamed messages. In that case, this macro uses an +//! empty \a regex pattern when calling ASSERT_DEATH_CRASH() to avoid looking +//! for any particular output on the standard error stream. In other build +//! configurations, the \a regex pattern is left intact. +//! +//! On macOS, `CHECK()` failures normally show up as crashes to the system’s +//! crash reporter, but it is undesirable for intentional ASSERT_DEATH_CHECK() +//! crashes to be handled by any crash reporter, so this is implemented in +//! terms of ASSERT_DEATH_CRASH() instead of `ASSERT_DEATH()`. +//! +//! \sa EXPECT_DEATH_CHECK() +#define ASSERT_DEATH_CHECK(statement, regex) \ + ASSERT_DEATH_CRASH(statement, regex) + +//! \brief Wraps the EXPECT_DEATH_CRASH() macro to make assertions about death +//! caused by `CHECK()` failures. +//! +//! In an in-Chromium build in the official configuration, `CHECK()` does not +//! print its condition or streamed messages. In that case, this macro uses an +//! empty \a regex pattern when calling EXPECT_DEATH_CRASH() to avoid looking +//! for any particular output on the standard error stream. In other build +//! configurations, the \a regex pattern is left intact. +//! +//! On macOS, `CHECK()` failures normally show up as crashes to the system’s +//! crash reporter, but it is undesirable for intentional EXPECT_DEATH_CHECK() +//! crashes to be handled by any crash reporter, so this is implemented in +//! terms of EXPECT_DEATH_CRASH() instead of `EXPECT_DEATH()`. +//! +//! \sa ASSERT_DEATH_CHECK() +#define EXPECT_DEATH_CHECK(statement, regex) \ + EXPECT_DEATH_CRASH(statement, regex) + +#else // !(!MINI_CHROMIUM_BASE_LOGGING_H_ && OFFICIAL_BUILD && NDEBUG) + +#define ASSERT_DEATH_CHECK(statement, regex) ASSERT_DEATH_CRASH(statement, "") +#define EXPECT_DEATH_CHECK(statement, regex) EXPECT_DEATH_CRASH(statement, "") + +#endif // !(!MINI_CHROMIUM_BASE_LOGGING_H_ && OFFICIAL_BUILD && NDEBUG) + +#endif // CRASHPAD_TEST_GTEST_DEATH_H_ diff --git a/test/gtest_death_check.h b/test/gtest_death_check.h deleted file mode 100644 index 8af50aac..00000000 --- a/test/gtest_death_check.h +++ /dev/null @@ -1,55 +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_TEST_GTEST_DEATH_CHECK_H_ -#define CRASHPAD_TEST_GTEST_DEATH_CHECK_H_ - -#include "base/logging.h" -#include "gtest/gtest.h" - -//! \file - -#if !(!defined(MINI_CHROMIUM_BASE_LOGGING_H_) && \ - defined(OFFICIAL_BUILD) && \ - defined(NDEBUG)) || \ - DOXYGEN - -//! \brief Wraps the gtest `ASSERT_DEATH()` macro to make assertions about death -//! caused by `CHECK()` failures. -//! -//! In an in-Chromium build in the official configuration in Release mode, -//! `CHECK()` does not print its condition or streamed messages. In that case, -//! this macro uses an empty \a regex pattern when calling `ASSERT_DEATH()` to -//! avoid looking for any particular output on the standard error stream. In -//! other build configurations, the \a regex pattern is left intact. -#define ASSERT_DEATH_CHECK(statement, regex) ASSERT_DEATH(statement, regex) - -//! \brief Wraps the gtest `EXPECT_DEATH()` macro to make assertions about death -//! caused by `CHECK()` failures. -//! -//! In an in-Chromium build in the official configuration in Release mode, -//! `CHECK()` does not print its condition or streamed messages. In that case, -//! this macro uses an empty \a regex pattern when calling `EXPECT_DEATH()` to -//! avoid looking for any particular output on the standard error stream. In -//! other build configurations, the \a regex pattern is left intact. -#define EXPECT_DEATH_CHECK(statement, regex) EXPECT_DEATH(statement, regex) - -#else - -#define ASSERT_DEATH_CHECK(statement, regex) ASSERT_DEATH(statement, "") -#define EXPECT_DEATH_CHECK(statement, regex) EXPECT_DEATH(statement, "") - -#endif - -#endif // CRASHPAD_TEST_GTEST_DEATH_CHECK_H_ diff --git a/test/gtest_main.cc b/test/gtest_main.cc index d8c2a904..ebdbeb9d 100644 --- a/test/gtest_main.cc +++ b/test/gtest_main.cc @@ -16,6 +16,7 @@ #include "gtest/gtest.h" #include "test/gtest_disabled.h" #include "test/main_arguments.h" +#include "test/multiprocess_exec.h" #if defined(CRASHPAD_TEST_LAUNCHER_GMOCK) #include "gmock/gmock.h" @@ -25,18 +26,41 @@ #include "test/win/win_child_process.h" #endif // OS_WIN -#if defined(CRASHPAD_IN_CHROMIUM) +#if defined(CRASHPAD_IS_IN_CHROMIUM) #include "base/bind.h" #include "base/test/launcher/unit_test_launcher.h" #include "base/test/test_suite.h" -#endif // CRASHPAD_IN_CHROMIUM +#endif // CRASHPAD_IS_IN_CHROMIUM + +namespace { + +bool GetChildTestFunctionName(std::string* child_func_name) { + constexpr size_t arg_length = + sizeof(crashpad::test::internal::kChildTestFunction) - 1; + for (const auto& it : crashpad::test::GetMainArguments()) { + if (it.compare( + 0, arg_length, crashpad::test::internal::kChildTestFunction) == 0) { + *child_func_name = it.substr(arg_length); + return true; + } + } + return false; +} + +} // namespace int main(int argc, char* argv[]) { crashpad::test::InitializeMainArguments(argc, argv); testing::AddGlobalTestEnvironment( crashpad::test::DisabledTestGtestEnvironment::Get()); -#if defined(CRASHPAD_IN_CHROMIUM) + std::string child_func_name; + if (GetChildTestFunctionName(&child_func_name)) { + return crashpad::test::internal::CheckedInvokeMultiprocessChild( + child_func_name); + } + +#if defined(CRASHPAD_IS_IN_CHROMIUM) #if defined(OS_WIN) // Chromium’s test launcher interferes with WinMultiprocess-based tests. Allow @@ -58,7 +82,7 @@ int main(int argc, char* argv[]) { base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); } -#endif // CRASHPAD_IN_CHROMIUM +#endif // CRASHPAD_IS_IN_CHROMIUM #if defined(CRASHPAD_TEST_LAUNCHER_GMOCK) testing::InitGoogleMock(&argc, argv); diff --git a/test/linux/fake_ptrace_connection.cc b/test/linux/fake_ptrace_connection.cc index 26d03711..022b1327 100644 --- a/test/linux/fake_ptrace_connection.cc +++ b/test/linux/fake_ptrace_connection.cc @@ -14,8 +14,11 @@ #include "test/linux/fake_ptrace_connection.h" +#include + #include "build/build_config.h" #include "gtest/gtest.h" +#include "util/file/file_io.h" namespace crashpad { namespace test { @@ -70,5 +73,24 @@ bool FakePtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) { return attached; } +bool FakePtraceConnection::ReadFileContents(const base::FilePath& path, + std::string* contents) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return LoggingReadEntireFile(path, contents); +} + +ProcessMemory* FakePtraceConnection::Memory() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!memory_) { + auto mem = std::make_unique(); + if (mem->Initialize(pid_)) { + memory_ = std::move(mem); + } else { + ADD_FAILURE(); + } + } + return memory_.get(); +} + } // namespace test } // namespace crashpad diff --git a/test/linux/fake_ptrace_connection.h b/test/linux/fake_ptrace_connection.h index e24bfa58..6597c473 100644 --- a/test/linux/fake_ptrace_connection.h +++ b/test/linux/fake_ptrace_connection.h @@ -17,11 +17,13 @@ #include +#include #include #include "base/macros.h" #include "util/linux/ptrace_connection.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory_linux.h" namespace crashpad { namespace test { @@ -53,8 +55,16 @@ class FakePtraceConnection : public PtraceConnection { //! \brief Does not modify \a info. bool GetThreadInfo(pid_t tid, ThreadInfo* info) override; + bool ReadFileContents(const base::FilePath& path, + std::string* contents) override; + + //! \brief Attempts to create a ProcessMemory when called, calling + //! ADD_FAILURE() and returning `nullptr` on failure. + ProcessMemory* Memory() override; + private: std::set attachments_; + std::unique_ptr memory_; pid_t pid_; bool is_64_bit_; InitializationStateDcheck initialized_; diff --git a/test/linux/get_tls.cc b/test/linux/get_tls.cc new file mode 100644 index 00000000..452724de --- /dev/null +++ b/test/linux/get_tls.cc @@ -0,0 +1,59 @@ +// 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/linux/get_tls.h" + +#include "build/build_config.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { + +LinuxVMAddress GetTLS() { + LinuxVMAddress tls; +#if defined(ARCH_CPU_ARMEL) + // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). + auto kuser_get_tls = reinterpret_cast(0xffff0fe0); + tls = FromPointerCast(kuser_get_tls()); +#elif defined(ARCH_CPU_ARM64) + // Linux/aarch64 places the tls address in system register tpidr_el0. + asm("mrs %0, tpidr_el0" : "=r"(tls)); +#elif defined(ARCH_CPU_X86) + uint32_t tls_32; + asm("movl %%gs:0x0, %0" : "=r"(tls_32)); + tls = tls_32; +#elif defined(ARCH_CPU_X86_64) + asm("movq %%fs:0x0, %0" : "=r"(tls)); +#elif defined(ARCH_CPU_MIPSEL) + uint32_t tls_32; + asm("rdhwr $3,$29\n\t" + "move %0,$3\n\t" + : "=r"(tls_32) + : + : "$3"); + tls = tls_32; +#elif defined(ARCH_CPU_MIPS64EL) + asm("rdhwr $3,$29\n\t" + "move %0,$3\n\t" + : "=r"(tls) + : + : "$3"); +#else +#error Port. +#endif // ARCH_CPU_ARMEL + return tls; +} + +} // namespace test +} // namespace crashpad diff --git a/test/linux/get_tls.h b/test/linux/get_tls.h new file mode 100644 index 00000000..5d59144e --- /dev/null +++ b/test/linux/get_tls.h @@ -0,0 +1,29 @@ +// 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_LINUX_GET_TLS_H_ +#define CRASHPAD_TEST_LINUX_GET_TLS_H_ + +#include "util/linux/address_types.h" + +namespace crashpad { +namespace test { + +//! \brief Return the thread-local storage address for the current thread. +LinuxVMAddress GetTLS(); + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_LINUX_GET_TLS_H_ diff --git a/test/mac/dyld.cc b/test/mac/dyld.cc index 6fa2176a..fb2156ed 100644 --- a/test/mac/dyld.cc +++ b/test/mac/dyld.cc @@ -21,7 +21,7 @@ #include #include "base/logging.h" -#include "snapshot/mac/process_reader.h" +#include "snapshot/mac/process_reader_mac.h" #include "test/scoped_module_handle.h" #include "util/numeric/safe_assignment.h" @@ -74,7 +74,7 @@ const dyld_all_image_infos* DyldGetAllImageInfos() { #endif // On 10.13 and later, do it the hard way. - ProcessReader process_reader; + ProcessReaderMac process_reader; if (!process_reader.Initialize(mach_task_self())) { return nullptr; } diff --git a/test/mac/exception_swallower.cc b/test/mac/exception_swallower.cc new file mode 100644 index 00000000..18c5cf71 --- /dev/null +++ b/test/mac/exception_swallower.cc @@ -0,0 +1,193 @@ +// 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/mac/exception_swallower.h" + +#include +#include +#include + +#include + +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/strings/stringprintf.h" +#include "handler/mac/exception_handler_server.h" +#include "util/mach/exc_server_variants.h" +#include "util/mach/exception_ports.h" +#include "util/mach/mach_extensions.h" +#include "util/misc/random_string.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { + +namespace { + +constexpr char kServiceEnvironmentVariable[] = + "CRASHPAD_EXCEPTION_SWALLOWER_SERVICE"; + +ExceptionSwallower* g_exception_swallower; + +// Like getenv(), but fails a CHECK() if the underlying function fails. It’s not +// considered a failure for |name| to be unset in the environment. In that case, +// nullptr is returned. +const char* CheckedGetenv(const char* name) { + errno = 0; + const char* value; + PCHECK((value = getenv(name)) || errno == 0) << "getenv"; + return value; +} + +} // namespace + +class ExceptionSwallower::ExceptionSwallowerThread + : public Thread, + public UniversalMachExcServer::Interface { + public: + explicit ExceptionSwallowerThread( + base::mac::ScopedMachReceiveRight receive_right) + : Thread(), + UniversalMachExcServer::Interface(), + exception_handler_server_(std::move(receive_right), true), + pid_(getpid()) { + Start(); + } + + ~ExceptionSwallowerThread() override {} + + void Stop() { exception_handler_server_.Stop(); } + + // Returns the process ID that the thread is running in. This is used to + // detect misuses that place the exception swallower server thread and code + // that wants its exceptions swallowed in the same process. + pid_t ProcessID() const { return pid_; } + + private: + // Thread: + + void ThreadMain() override { exception_handler_server_.Run(this); } + + // 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; + + // Swallow. + + ExcServerCopyState( + behavior, old_state, old_state_count, new_state, new_state_count); + return ExcServerSuccessfulReturnValue(exception, behavior, false); + } + + ExceptionHandlerServer exception_handler_server_; + pid_t pid_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSwallowerThread); +}; + +ExceptionSwallower::ExceptionSwallower() : exception_swallower_thread_() { + CHECK(!g_exception_swallower); + g_exception_swallower = this; + + if (CheckedGetenv(kServiceEnvironmentVariable)) { + // The environment variable is already set, so just proceed with the + // existing service. This normally happens when the gtest “threadsafe” death + // test style is chosen, because the test child process will re-execute code + // already run in the test parent process. See + // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles. + return; + } + + std::string service_name = + base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s", + getpid(), + RandomString().c_str()); + base::mac::ScopedMachReceiveRight receive_right( + BootstrapCheckIn(service_name)); + CHECK(receive_right.is_valid()); + + exception_swallower_thread_.reset( + new ExceptionSwallowerThread(std::move(receive_right))); + + PCHECK(setenv(kServiceEnvironmentVariable, service_name.c_str(), 1) == 0) + << "setenv"; +} + +ExceptionSwallower::~ExceptionSwallower() { + PCHECK(unsetenv(kServiceEnvironmentVariable) == 0) << "unsetenv"; + + exception_swallower_thread_->Stop(); + exception_swallower_thread_->Join(); + + CHECK_EQ(g_exception_swallower, this); + g_exception_swallower = nullptr; +} + +// static +void ExceptionSwallower::SwallowExceptions() { + // The exception swallower thread can’t be in this process, because the + // EXC_CRASH or EXC_CORPSE_NOTIFY exceptions that it needs to swallow will be + // delivered after a crash has occurred and none of its threads will be + // scheduled to run. + CHECK(!g_exception_swallower || + !g_exception_swallower->exception_swallower_thread_ || + g_exception_swallower->exception_swallower_thread_->ProcessID() != + getpid()); + + const char* service_name = CheckedGetenv(kServiceEnvironmentVariable); + CHECK(service_name); + + base::mac::ScopedMachSendRight exception_swallower_port( + BootstrapLookUp(service_name)); + CHECK(exception_swallower_port.is_valid()); + + ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, + TASK_NULL); + + // The mask is similar to the one used by CrashpadClient::UseHandler(), but + // EXC_CORPSE_NOTIFY is added. This is done for the benefit of tests that + // crash intentionally with their own custom exception port set for EXC_CRASH. + // In that case, depending on the actions taken by the EXC_CRASH handler, the + // exception may be transformed by the kernel into an EXC_CORPSE_NOTIFY, which + // would be sent to an EXC_CORPSE_NOTIFY handler, normally the system’s crash + // reporter at the task or host level. See 10.13.0 + // xnu-4570.1.46/bsd/kern/kern_exit.c proc_prepareexit(). Swallowing + // EXC_CORPSE_NOTIFY at the task level prevents such exceptions from reaching + // the system’s crash reporter. + CHECK(task_exception_ports.SetExceptionPort( + (EXC_MASK_CRASH | + EXC_MASK_RESOURCE | + EXC_MASK_GUARD | + EXC_MASK_CORPSE_NOTIFY) & ExcMaskValid(), + exception_swallower_port.get(), + EXCEPTION_DEFAULT, + THREAD_STATE_NONE)); +} + +} // namespace test +} // namespace crashpad diff --git a/test/mac/exception_swallower.h b/test/mac/exception_swallower.h new file mode 100644 index 00000000..7c3a4421 --- /dev/null +++ b/test/mac/exception_swallower.h @@ -0,0 +1,86 @@ +// 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_MAC_EXCEPTION_SWALLOWER_H_ +#define CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ + +#include + +#include "base/macros.h" + +namespace crashpad { +namespace test { + +//! \brief Swallows `EXC_CRASH` and `EXC_CORPSE_NOTIFY` exceptions in test child +//! processes. +//! +//! This class is intended to be used by test code that crashes intentionally. +//! +//! On macOS, the system’s crash reporter normally saves crash reports for all +//! crashes in test code, by virtue of being set as the `EXC_CRASH` or +//! `EXC_CORPSE_NOTIFY` handler. This litters the user’s +//! `~/Library/Logs/DiagnosticReports` directory and can be time-consuming. +//! Reports generated for code that crashes intentionally have no value, and +//! many Crashpad tests do crash intentionally. +//! +//! Instantiate an ExceptionSwallower object in a parent test process (a process +//! where `TEST()`, `TEST_F()`, and `TEST_P()` execute) to create an exception +//! swallower server running on a dedicated thread. A service mapping for this +//! server will be published with the bootstrap server and made available in the +//! `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` environment variable. In a child +//! process, call SwallowExceptions() to look up this service and set it as the +//! `EXC_CRASH` and `EXC_CORPSE_NOTIFY` handler. When these exceptions are +//! raised in the child process, they’ll be handled by the exception swallower +//! server, which performs no action but reports that exceptions were +//! successfully handled so that the system’s crash reporter, ReportCrash, will +//! not be invoked. +//! +//! At most one ExceptionSwallower may be instantiated in a process at a time. +//! If `CRASHPAD_EXCEPTION_SWALLOWER_SERVICE` is already set, ExceptionSwallower +//! leaves it in place and takes no additional action. +//! +//! Crashpad’s ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(), +//! and EXPECT_DEATH_CHECK() macros make use of this class on macOS, as does the +//! Multiprocess test interface. +class ExceptionSwallower { + public: + ExceptionSwallower(); + ~ExceptionSwallower(); + + //! \brief In a test child process, arranges to swallow `EXC_CRASH` and + //! `EXC_CORPSE_NOTIFY` exceptions. + //! + //! This must be called in a test child process. It must not be called from a + //! parent test process directly. Parent test processes are those that execute + //! `TEST()`, `TEST_F()`, and `TEST_P()`. Test child processes execute + //! ASSERT_DEATH_CRASH(), EXPECT_DEATH_CRASH(), ASSERT_DEATH_CHECK(), + //! EXPECT_DEATH_CHECK(), and Multiprocess::RunChild(). + //! + //! It is an error to call this in a test child process without having first + //! instantiated an ExceptionSwallower object in a parent test project. It is + //! also an error to call this in a parent test process. + static void SwallowExceptions(); + + private: + class ExceptionSwallowerThread; + + std::unique_ptr exception_swallower_thread_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionSwallower); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_MAC_EXCEPTION_SWALLOWER_H_ diff --git a/test/mac/mach_errors.h b/test/mac/mach_errors.h index 0376bd1e..9e43ef22 100644 --- a/test/mac/mach_errors.h +++ b/test/mac/mach_errors.h @@ -34,7 +34,7 @@ namespace test { //! \brief Formats a Mach error message. //! //! The returned string will combine the \a base string, if supplied, with a -//! a textual and numeric description of the error. +//! textual and numeric description of the error. //! //! \param[in] mach_err The Mach error code, which may be a `kern_return_t` or //! related type. @@ -50,7 +50,7 @@ std::string MachErrorMessage(mach_error_t mach_err, //! \brief Formats a bootstrap error message. //! //! The returned string will combine the \a base string, if supplied, with a -//! a textual and numeric description of the error. +//! 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. diff --git a/test/multiprocess.h b/test/multiprocess.h index a7ef6898..d0275020 100644 --- a/test/multiprocess.h +++ b/test/multiprocess.h @@ -36,8 +36,8 @@ struct MultiprocessInfo; //! Subclasses are expected to implement the parent and child by overriding the //! appropriate methods. //! -//! On Windows, this class is only an internal implementation detail of -//! MultiprocessExec and all tests must use that class. +//! On Windows and Fuchsia, this class is only an internal implementation +//! detail of MultiprocessExec and all tests must use that class. class Multiprocess { public: //! \brief The termination type for a child process. @@ -49,11 +49,13 @@ class Multiprocess { //! that call `exit()` or `_exit()`. kTerminationNormal = false, +#if !defined(OS_FUCHSIA) // There are no signals on Fuchsia. //! \brief The child terminated by signal. //! //! Signal termination happens as a result of a crash, a call to `abort()`, //! assertion failure (including gtest assertions), etc. kTerminationSignal, +#endif // !defined(OS_FUCHSIA) }; Multiprocess(); @@ -78,14 +80,25 @@ class Multiprocess { //! TerminationReason::kTerminationNormal, and the default expected //! termination code is `EXIT_SUCCESS` (`0`). //! + //! This method does not need to be called if the default termination + //! expectation is appropriate, but if this method is called, it must be + //! called before Run(). + //! //! \param[in] reason Whether to expect the child to terminate normally or //! as a result of a signal. //! \param[in] code If \a reason is TerminationReason::kTerminationNormal, //! this is the expected exit status of the child. If \a reason is //! TerminationReason::kTerminationSignal, this is the signal that is - //! expected to kill the child. + //! 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); +#if !defined(OS_WIN) + //! \brief Sets termination reason and code appropriately for a child that + //! terminates via `__builtin_trap()`. + void SetExpectedChildTerminationBuiltinTrap(); +#endif // !OS_WIN + protected: ~Multiprocess(); @@ -106,14 +119,18 @@ class Multiprocess { //! //! Subclass implementations may signal failure by raising their own fatal //! gtest assertions. - virtual void PreFork(); + virtual void PreFork() +#if defined(OS_WIN) || defined(OS_FUCHSIA) + = 0 +#endif // OS_WIN || OS_FUCHSIA + ; -#if !defined(OS_WIN) +#if !defined(OS_WIN) && !defined(OS_FUCHSIA) //! \brief Returns the child process’ process ID. //! //! This method may only be called by the parent process. pid_t ChildPID() const; -#endif // !OS_WIN +#endif // !OS_WIN && !OS_FUCHSIA //! \brief Returns the read pipe’s file handle. //! diff --git a/test/multiprocess_exec.cc b/test/multiprocess_exec.cc new file mode 100644 index 00000000..fb332417 --- /dev/null +++ b/test/multiprocess_exec.cc @@ -0,0 +1,74 @@ +// 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/multiprocess_exec.h" + +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "test/main_arguments.h" +#include "util/stdlib/map_insert.h" +#include "test/test_paths.h" + +namespace crashpad { +namespace test { + +namespace internal { + +namespace { + +std::map* GetMultiprocessFunctionMap() { + static auto* map = new std::map(); + return map; +} + +} // namespace + +AppendMultiprocessTest::AppendMultiprocessTest(const std::string& test_name, + int (*main_function_pointer)()) { + CHECK(MapInsertOrReplace( + GetMultiprocessFunctionMap(), test_name, main_function_pointer, nullptr)) + << test_name << " already registered"; +} + +int CheckedInvokeMultiprocessChild(const std::string& test_name) { + const auto* functions = internal::GetMultiprocessFunctionMap(); + auto it = functions->find(test_name); + CHECK(it != functions->end()) + << "child main " << test_name << " not registered"; + return (*it->second)(); +} + +} // namespace internal + +void MultiprocessExec::SetChildTestMainFunction( + const std::string& function_name) { + std::vector rest(GetMainArguments().begin() + 1, + GetMainArguments().end()); + rest.push_back(internal::kChildTestFunction + function_name); + +#if defined(OS_WIN) + // Instead of using argv[0] on Windows, use the actual binary name. This is + // necessary because if originally the test isn't run with ".exe" on the + // command line, then argv[0] also won't include ".exe". This argument is used + // as the lpApplicationName argument to CreateProcess(), and so will fail. + SetChildCommand(TestPaths::Executable(), &rest); +#else + SetChildCommand(base::FilePath(GetMainArguments()[0]), &rest); +#endif +} + +} // namespace test +} // namespace crashpad diff --git a/test/multiprocess_exec.h b/test/multiprocess_exec.h index 258a3f8e..84335b28 100644 --- a/test/multiprocess_exec.h +++ b/test/multiprocess_exec.h @@ -22,10 +22,59 @@ #include "base/macros.h" #include "build/build_config.h" #include "test/multiprocess.h" +#include "test/process_type.h" + +//! \file namespace crashpad { namespace test { +namespace internal { + +//! \brief Command line argument used to indicate that a child test function +//! should be run. +constexpr char kChildTestFunction[] = "--child-test-function="; + + +//! \brief Helper class used by CRASHPAD_CHILD_TEST_MAIN() to insert a child +//! function into the global mapping. +class AppendMultiprocessTest { + public: + AppendMultiprocessTest(const std::string& test_name, + int (*main_function_pointer)()); +}; + +//! \brief Used to run a child test function by name, registered by +//! CRASHPAD_CHILD_TEST_MAIN(). +//! +//! \return The exit code of the child process after running the function named +//! by \a test_name. Aborts with a CHECK() if \a test_name wasn't +//! registered. +int CheckedInvokeMultiprocessChild(const std::string& test_name); + +} // namespace internal + +//! \brief Registers a function that can be invoked as a child process by +//! MultiprocessExec. +//! +//! Used as: +//! +//! \code +//! CRASHPAD_CHILD_TEST_MAIN(MyChildTestBody) { +//! ... child body ... +//! } +//! \endcode +//! +//! In the main (parent) test body, this function can be run in a child process +//! via MultiprocessExec::SetChildTestMainFunction(). +#define CRASHPAD_CHILD_TEST_MAIN(test_main) \ + int test_main(); \ + namespace { \ + ::crashpad::test::internal::AppendMultiprocessTest \ + AddMultiprocessTest##_##test_main(#test_main, (test_main)); \ + } /* namespace */ \ + int test_main() + //! \brief Manages an `exec()`-based multiprocess test. //! //! These tests are based on `fork()` and `exec()`. The parent process is able @@ -44,14 +93,38 @@ class MultiprocessExec : public Multiprocess { //! //! This method must be called before the test can be Run(). //! + //! This method is useful when a custom executable is required for the child + //! binary, however, SetChildTestMainFunction() should generally be preferred. + //! //! \param[in] command The executable’s pathname. //! \param[in] arguments The command-line arguments to pass to the child //! process in its `argv[]` vector. This vector must begin at `argv[1]`, //! as \a command is implicitly used as `argv[0]`. This argument may be //! `nullptr` if no command-line arguments are to be passed. + //! + //! \sa SetChildTestMainFunction void SetChildCommand(const base::FilePath& command, const std::vector* arguments); + //! \brief Calls SetChildCommand() to run a child test main function + //! registered with CRASHPAD_CHILD_TEST_MAIN(). + //! + //! This uses the same launch mechanism as SetChildCommand(), but coordinates + //! with test/gtest_main.cc to allow for simple registration of a child + //! processes' entry point via the helper macro, rather than needing to + //! create a separate build target. + //! + //! \param[in] function_name The name of the function as passed to + //! CRASHPAD_CHILD_TEST_MAIN(). + void SetChildTestMainFunction(const std::string& function_name); + + //! \brief Returns a ProcessType representing the child process. + //! + //! This method is only valid during the body of MultiprocessParent(). + //! + //! \return A platform-specific type representing the child process. + ProcessType ChildProcess(); + protected: ~MultiprocessExec(); diff --git a/test/multiprocess_exec_fuchsia.cc b/test/multiprocess_exec_fuchsia.cc new file mode 100644 index 00000000..95436b47 --- /dev/null +++ b/test/multiprocess_exec_fuchsia.cc @@ -0,0 +1,210 @@ +// 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/multiprocess_exec.h" + +#include +#include +#include +#include +#include + +#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 { +namespace test { + +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"; + action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; + action->h.id = PA_HND(PA_HND_TYPE(id), target_fd); + action->h.handle = handle; + *fd_out = status; +} + +} // namespace + +namespace internal { + +struct MultiprocessInfo { + MultiprocessInfo() {} + base::ScopedFD stdin_write; + base::ScopedFD stdout_read; + base::ScopedZxHandle child; +}; + +} // namespace internal + +Multiprocess::Multiprocess() + : info_(nullptr), code_(EXIT_SUCCESS), reason_(kTerminationNormal) {} + +void Multiprocess::Run() { + // Set up and spawn the child process. + ASSERT_NO_FATAL_FAILURE(PreFork()); + RunChild(); + + // And then run the parent actions in this process. + RunParent(); + + // 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), + 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); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info"; + ADD_FAILURE() << "Unable to get exit code of child"; + } else { + if (code_ != proc_info.return_code) { + ADD_FAILURE() << "Child exited with code " << proc_info.return_code + << ", expected exit with code " << code_; + } + } +} + +void Multiprocess::SetExpectedChildTermination(TerminationReason reason, + int code) { + EXPECT_EQ(info_, nullptr) + << "SetExpectedChildTermination() must be called before Run()"; + reason_ = reason; + code_ = code; +} + +void Multiprocess::SetExpectedChildTerminationBuiltinTrap() { + SetExpectedChildTermination(kTerminationNormal, -1); +} + +Multiprocess::~Multiprocess() { + delete info_; +} + +FileHandle Multiprocess::ReadPipeHandle() const { + return info_->stdout_read.get(); +} + +FileHandle Multiprocess::WritePipeHandle() const { + return info_->stdin_write.get(); +} + +void Multiprocess::CloseReadPipe() { + info_->stdout_read.reset(); +} + +void Multiprocess::CloseWritePipe() { + info_->stdin_write.reset(); +} + +void Multiprocess::RunParent() { + MultiprocessParent(); + + info_->stdout_read.reset(); + info_->stdin_write.reset(); +} + +void Multiprocess::RunChild() { + MultiprocessChild(); +} + +MultiprocessExec::MultiprocessExec() + : Multiprocess(), command_(), arguments_(), argv_() {} + +void MultiprocessExec::SetChildCommand( + const base::FilePath& command, + const std::vector* arguments) { + command_ = command; + if (arguments) { + arguments_ = *arguments; + } else { + arguments_.clear(); + } +} + +MultiprocessExec::~MultiprocessExec() {} + +void MultiprocessExec::PreFork() { + ASSERT_FALSE(command_.empty()); + + ASSERT_TRUE(argv_.empty()); + + argv_.push_back(command_.value().c_str()); + for (const std::string& argument : arguments_) { + argv_.push_back(argument.c_str()); + } + argv_.push_back(nullptr); + + ASSERT_EQ(info(), nullptr); + set_info(new internal::MultiprocessInfo()); +} + +void MultiprocessExec::MultiprocessChild() { + constexpr size_t kActionCount = 3; + fdio_spawn_action_t actions[kActionCount]; + + int stdin_parent_side = -1; + AddPipe(&actions[0], STDIN_FILENO, &stdin_parent_side); + info()->stdin_write.reset(stdin_parent_side); + + int stdout_parent_side = -1; + AddPipe(&actions[1], STDOUT_FILENO, &stdout_parent_side); + info()->stdout_read.reset(stdout_parent_side); + + actions[2].action = FDIO_SPAWN_ACTION_CLONE_FD; + actions[2].fd.local_fd = STDERR_FILENO; + actions[2].fd.target_fd = STDERR_FILENO; + + // Pass the filesystem namespace, parent environment, and default job to the + // child, but don't include any other file handles, preferring to set them + // up explicitly below. + 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_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, + flags, + command_.value().c_str(), + argv_.data(), + nullptr, + kActionCount, + actions, + &child, + error_message); + ZX_CHECK(status == ZX_OK, status) << "fdio_spawn_etc: " << error_message; + info()->child.reset(child); +} + +ProcessType MultiprocessExec::ChildProcess() { + return info()->child.get(); +} + +} // namespace test +} // namespace crashpad diff --git a/test/multiprocess_exec_posix.cc b/test/multiprocess_exec_posix.cc index 528e8c77..c91b7f73 100644 --- a/test/multiprocess_exec_posix.cc +++ b/test/multiprocess_exec_posix.cc @@ -148,5 +148,9 @@ void MultiprocessExec::MultiprocessChild() { FAIL() << ErrnoMessage("execv") << ": " << argv_[0]; } +ProcessType MultiprocessExec::ChildProcess() { + return ChildPID(); +} + } // namespace test } // namespace crashpad diff --git a/test/multiprocess_exec_test.cc b/test/multiprocess_exec_test.cc index b8a5bc19..99a1b01f 100644 --- a/test/multiprocess_exec_test.cc +++ b/test/multiprocess_exec_test.cc @@ -14,6 +14,7 @@ #include "test/multiprocess_exec.h" +#include "base/logging.h" #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" @@ -56,6 +57,76 @@ TEST(MultiprocessExec, MultiprocessExec) { multiprocess_exec.Run(); } + +CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocess) { + char c; + CheckedReadFileExactly(StdioFileHandle(StdioStream::kStandardInput), &c, 1); + LOG_IF(FATAL, c != 'z'); + + c = 'Z'; + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), &c, 1); + return 0; +} + +TEST(MultiprocessExec, MultiprocessExecSimpleChild) { + TestMultiprocessExec exec; + exec.SetChildTestMainFunction("SimpleMultiprocess"); + exec.Run(); +}; + + +CRASHPAD_CHILD_TEST_MAIN(SimpleMultiprocessReturnsNonZero) { + return 123; +} + +class TestMultiprocessExecEmpty final : public MultiprocessExec { + public: + TestMultiprocessExecEmpty() = default; + ~TestMultiprocessExecEmpty() = default; + + private: + void MultiprocessParent() override {} + + DISALLOW_COPY_AND_ASSIGN(TestMultiprocessExecEmpty); +}; + +TEST(MultiprocessExec, MultiprocessExecSimpleChildReturnsNonZero) { + TestMultiprocessExecEmpty exec; + exec.SetChildTestMainFunction("SimpleMultiprocessReturnsNonZero"); + exec.SetExpectedChildTermination( + Multiprocess::TerminationReason::kTerminationNormal, 123); + exec.Run(); +}; + +#if !defined(OS_WIN) + +CRASHPAD_CHILD_TEST_MAIN(BuiltinTrapChild) { + __builtin_trap(); + return EXIT_SUCCESS; +} + +class TestBuiltinTrapTermination final : public MultiprocessExec { + public: + TestBuiltinTrapTermination() { + SetChildTestMainFunction("BuiltinTrapChild"); + SetExpectedChildTerminationBuiltinTrap(); + } + + ~TestBuiltinTrapTermination() = default; + + private: + void MultiprocessParent() override {} + + DISALLOW_COPY_AND_ASSIGN(TestBuiltinTrapTermination); +}; + +TEST(MultiprocessExec, BuiltinTrapTermination) { + TestBuiltinTrapTermination test; + test.Run(); +} + +#endif // !OS_WIN + } // namespace } // namespace test } // namespace crashpad diff --git a/test/multiprocess_exec_test_child.cc b/test/multiprocess_exec_test_child.cc index 011a37d0..8c77015c 100644 --- a/test/multiprocess_exec_test_child.cc +++ b/test/multiprocess_exec_test_child.cc @@ -15,19 +15,17 @@ #include #include #include -#include #include #include -#if defined(__APPLE__) || defined(__linux__) -#define OS_POSIX 1 -#elif defined(_WIN32) -#define OS_WIN 1 -#endif +#include "base/logging.h" +#include "build/build_config.h" #if defined(OS_POSIX) +#if !defined(OS_FUCHSIA) #include +#endif // !OS_FUCHSIA #include #elif defined(OS_WIN) #include @@ -35,18 +33,26 @@ int main(int argc, char* argv[]) { #if defined(OS_POSIX) + +#if defined(OS_FUCHSIA) + // getrlimit() is not implemented on Fuchsia. By construction, the child only + // receieves specific fds that it's given, but check low values as mild + // verification. + int last_fd = 1024; +#else + rlimit rlimit_nofile; + if (getrlimit(RLIMIT_NOFILE, &rlimit_nofile) != 0) { + LOG(FATAL) << "getrlimit"; + } + int last_fd = static_cast(rlimit_nofile.rlim_cur); +#endif // OS_FUCHSIA + // Make sure that there’s nothing open at any FD higher than 3. All FDs other // than stdin, stdout, and stderr should have been closed prior to or at // exec(). - rlimit rlimit_nofile; - if (getrlimit(RLIMIT_NOFILE, &rlimit_nofile) != 0) { - abort(); - } - for (int fd = STDERR_FILENO + 1; - fd < static_cast(rlimit_nofile.rlim_cur); - ++fd) { + for (int fd = STDERR_FILENO + 1; fd < last_fd; ++fd) { if (close(fd) == 0 || errno != EBADF) { - abort(); + LOG(FATAL) << "close"; } } @@ -54,14 +60,14 @@ int main(int argc, char* argv[]) { char c; ssize_t rv = read(STDIN_FILENO, &c, 1); if (rv != 1 || c != 'z') { - abort(); + LOG(FATAL) << "read"; } // Write a byte to stdout. c = 'Z'; rv = write(STDOUT_FILENO, &c, 1); if (rv != 1) { - abort(); + LOG(FATAL) << "write"; } #elif defined(OS_WIN) // TODO(scottmg): Verify that only the handles we expect to be open, are. @@ -72,7 +78,7 @@ int main(int argc, char* argv[]) { HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); if (!ReadFile(stdin_handle, &c, 1, &bytes_read, nullptr) || bytes_read != 1 || c != 'z') { - abort(); + LOG(FATAL) << "ReadFile"; } // Write a byte to stdout. @@ -81,7 +87,7 @@ int main(int argc, char* argv[]) { if (!WriteFile( GetStdHandle(STD_OUTPUT_HANDLE), &c, 1, &bytes_written, nullptr) || bytes_written != 1) { - abort(); + LOG(FATAL) << "WriteFile"; } #endif // OS_POSIX diff --git a/test/multiprocess_exec_win.cc b/test/multiprocess_exec_win.cc index f3a10c42..38978186 100644 --- a/test/multiprocess_exec_win.cc +++ b/test/multiprocess_exec_win.cc @@ -57,12 +57,16 @@ void Multiprocess::Run() { CloseHandle(info_->process_info.hProcess); } -Multiprocess::~Multiprocess() { - delete info_; +void Multiprocess::SetExpectedChildTermination(TerminationReason reason, + int code) { + EXPECT_EQ(info_, nullptr) + << "SetExpectedChildTermination() must be called before Run()"; + reason_ = reason; + code_ = code; } -void Multiprocess::PreFork() { - NOTREACHED(); +Multiprocess::~Multiprocess() { + delete info_; } FileHandle Multiprocess::ReadPipeHandle() const { @@ -166,5 +170,9 @@ void MultiprocessExec::MultiprocessChild() { &info()->process_info)); } +ProcessType MultiprocessExec::ChildProcess() { + return info()->process_info.hProcess; +} + } // namespace test } // namespace crashpad diff --git a/test/multiprocess_posix.cc b/test/multiprocess_posix.cc index d6796dda..2e0c3856 100644 --- a/test/multiprocess_posix.cc +++ b/test/multiprocess_posix.cc @@ -27,9 +27,15 @@ #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/stringprintf.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" #include "util/misc/scoped_forbid_return.h" +#include "util/posix/signals.h" + +#if defined(OS_MACOSX) +#include "test/mac/exception_swallower.h" +#endif namespace crashpad { namespace test { @@ -67,6 +73,16 @@ void Multiprocess::Run() { ASSERT_NO_FATAL_FAILURE(PreFork()); +#if defined(OS_MACOSX) + // If the child is expected to crash, set up an exception swallower to swallow + // the exception instead of allowing it to be seen by the system’s crash + // reporter. + std::unique_ptr exception_swallower; + if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) { + exception_swallower.reset(new ExceptionSwallower()); + } +#endif // OS_MACOSX + pid_t pid = fork(); ASSERT_GE(pid, 0) << ErrnoMessage("fork"); @@ -108,7 +124,7 @@ void Multiprocess::Run() { strsignal(code), WCOREDUMP(status) ? " (core dumped)" : ""); } else { - FAIL() << "Unknown termination reason"; + FAIL() << base::StringPrintf("Unknown termination reason 0x%x", status); } if (reason_ == kTerminationNormal) { @@ -123,16 +139,36 @@ void Multiprocess::Run() { ADD_FAILURE() << message; } } else { +#if defined(OS_MACOSX) + if (exception_swallower.get()) { + ExceptionSwallower::SwallowExceptions(); + } +#elif defined(OS_LINUX) || defined(OS_ANDROID) + if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) { + Signals::InstallDefaultHandler(code_); + } +#endif // OS_MACOSX + RunChild(); } } void Multiprocess::SetExpectedChildTermination(TerminationReason reason, int code) { + EXPECT_EQ(info_, nullptr) + << "SetExpectedChildTermination() must be called before Run()"; reason_ = reason; code_ = code; } +void Multiprocess::SetExpectedChildTerminationBuiltinTrap() { +#if defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_MIPS_FAMILY) + SetExpectedChildTermination(kTerminationSignal, SIGTRAP); +#else + SetExpectedChildTermination(kTerminationSignal, SIGILL); +#endif +} + Multiprocess::~Multiprocess() { } diff --git a/test/multiprocess_posix_test.cc b/test/multiprocess_posix_test.cc index 44894e8c..94891477 100644 --- a/test/multiprocess_posix_test.cc +++ b/test/multiprocess_posix_test.cc @@ -20,7 +20,7 @@ #include "base/macros.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/file/file_io.h" namespace crashpad { diff --git a/test/process_type.cc b/test/process_type.cc new file mode 100644 index 00000000..0e3c9e6d --- /dev/null +++ b/test/process_type.cc @@ -0,0 +1,37 @@ +// 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/process_type.h" + +#if defined(OS_FUCHSIA) +#include +#elif defined(OS_POSIX) +#include +#endif + +namespace crashpad { +namespace test { + +ProcessType GetSelfProcess() { +#if defined(OS_FUCHSIA) + return zx_process_self(); +#elif defined(OS_POSIX) + return getpid(); +#elif defined(OS_WIN) + return GetCurrentProcess(); +#endif +} + +} // namespace test +} // namespace crashpad diff --git a/test/process_type.h b/test/process_type.h new file mode 100644 index 00000000..a25b1222 --- /dev/null +++ b/test/process_type.h @@ -0,0 +1,48 @@ +// 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_PROCESS_TYPE_H_ +#define CRASHPAD_TEST_PROCESS_TYPE_H_ + +#include "build/build_config.h" + +#if defined(OS_FUCHSIA) +#include +#elif defined(OS_POSIX) +#include +#elif defined(OS_WIN) +#include +#endif + +namespace crashpad { +namespace test { + +#if defined(OS_FUCHSIA) +using ProcessType = zx_handle_t; +#elif defined(OS_POSIX) || DOXYGEN +//! \brief Alias for platform-specific type to represent a process. +using ProcessType = pid_t; +#elif defined(OS_WIN) +using ProcessType = HANDLE; +#else +#error Port. +#endif + +//! \brief Get a ProcessType representing the current process. +ProcessType GetSelfProcess(); + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_TEST_PROCESS_TYPE_H_ diff --git a/test/scoped_module_handle.h b/test/scoped_module_handle.h index 3863cad3..9909d1c8 100644 --- a/test/scoped_module_handle.h +++ b/test/scoped_module_handle.h @@ -43,7 +43,7 @@ class ScopedModuleHandle { using ModuleHandle = HMODULE; static void* LookUpSymbol(ModuleHandle handle, const char* symbol_name) { - return GetProcAddress(handle, symbol_name); + return reinterpret_cast(GetProcAddress(handle, symbol_name)); } #endif @@ -59,6 +59,9 @@ class ScopedModuleHandle { explicit ScopedModuleHandle(ModuleHandle handle); ~ScopedModuleHandle(); + //! \return The module handle being managed. + ModuleHandle get() const { return handle_; } + //! \return `true` if this object manages a valid loadable module handle. bool valid() const { return handle_ != nullptr; } diff --git a/test/test.gyp b/test/test.gyp index 4b389334..29737088 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -22,7 +22,6 @@ 'type': 'static_library', 'dependencies': [ '../compat/compat.gyp:crashpad_compat', - '../snapshot/snapshot.gyp:crashpad_snapshot', '../third_party/gtest/gtest.gyp:gtest', '../third_party/mini_chromium/mini_chromium.gyp:base', '../util/util.gyp:crashpad_util', @@ -37,15 +36,19 @@ 'file.h', 'filesystem.cc', 'filesystem.h', - 'gtest_death_check.h', + 'gtest_death.h', 'gtest_disabled.cc', 'gtest_disabled.h', 'hex_string.cc', 'hex_string.h', 'linux/fake_ptrace_connection.cc', 'linux/fake_ptrace_connection.h', + 'linux/get_tls.cc', + 'linux/get_tls.h', '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', @@ -53,10 +56,13 @@ 'main_arguments.cc', 'main_arguments.h', 'multiprocess.h', + 'multiprocess_exec.cc', 'multiprocess_exec.h', 'multiprocess_exec_posix.cc', 'multiprocess_exec_win.cc', 'multiprocess_posix.cc', + 'process_type.cc', + 'process_type.h', 'scoped_module_handle.cc', 'scoped_module_handle.h', 'scoped_temp_dir.cc', @@ -81,6 +87,10 @@ }, 'conditions': [ ['OS=="mac"', { + 'dependencies': [ + '../handler/handler.gyp:crashpad_handler_lib', + '../snapshot/snapshot.gyp:crashpad_snapshot', + ], 'link_settings': { 'libraries': [ '$(SDKROOT)/usr/lib/libbsm.dylib', diff --git a/test/test_paths.cc b/test/test_paths.cc index ce180a37..50165e43 100644 --- a/test/test_paths.cc +++ b/test/test_paths.cc @@ -43,6 +43,20 @@ bool IsTestDataRoot(const base::FilePath& candidate) { } 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 + if (!IsTestDataRoot(asset_path)) { + LOG(WARNING) << "Test data root seems invalid, continuing anyway"; + } + return asset_path; +#else // defined(OS_FUCHSIA) #if !defined(OS_WIN) const char* environment_value = getenv("CRASHPAD_TEST_DATA_ROOT"); #else // defined(OS_WIN) @@ -88,6 +102,7 @@ base::FilePath TestDataRootInternal() { } return base::FilePath(base::FilePath::kCurrentDirectory); +#endif // defined(OS_FUCHSIA) } #if defined(OS_WIN) && defined(ARCH_CPU_64_BITS) @@ -113,24 +128,36 @@ base::FilePath Output32BitDirectory() { base::FilePath TestPaths::Executable() { 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"); +#endif return executable_path; } // static base::FilePath TestPaths::ExpectedExecutableBasename( const base::FilePath::StringType& name) { -#if defined(CRASHPAD_IN_CHROMIUM) +#if defined(OS_FUCHSIA) + // Apps in Fuchsia packages are always named "app". + return base::FilePath("app"); +#else // OS_FUCHSIA +#if defined(CRASHPAD_IS_IN_CHROMIUM) base::FilePath::StringType executable_name( FILE_PATH_LITERAL("crashpad_tests")); -#else // CRASHPAD_IN_CHROMIUM +#else // CRASHPAD_IS_IN_CHROMIUM base::FilePath::StringType executable_name(name); -#endif // CRASHPAD_IN_CHROMIUM +#endif // CRASHPAD_IS_IN_CHROMIUM #if defined(OS_WIN) executable_name += FILE_PATH_LITERAL(".exe"); #endif // OS_WIN return base::FilePath(executable_name); +#endif // OS_FUCHSIA } // static @@ -162,9 +189,9 @@ base::FilePath TestPaths::BuildArtifact( base::FilePath::StringType test_name = FILE_PATH_LITERAL("crashpad_") + module + FILE_PATH_LITERAL("_test"); -#if !defined(CRASHPAD_IN_CHROMIUM) +#if !defined(CRASHPAD_IS_IN_CHROMIUM) && !defined(OS_FUCHSIA) CHECK(Executable().BaseName().RemoveFinalExtension().value() == test_name); -#endif // !CRASHPAD_IN_CHROMIUM +#endif // !CRASHPAD_IS_IN_CHROMIUM base::FilePath::StringType extension; switch (file_type) { @@ -174,6 +201,17 @@ base::FilePath TestPaths::BuildArtifact( case FileType::kExecutable: #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; @@ -183,6 +221,26 @@ base::FilePath TestPaths::BuildArtifact( #else // OS_WIN extension = FILE_PATH_LITERAL(".so"); #endif // OS_WIN + +#if defined(OS_FUCHSIA) + // TODO(scottmg): .so files are currently deployed into /boot/lib, where + // they'll be found (without a path) by the loader. Application packaging + // infrastructure is in progress, so this will likely change again in the + // future. + directory = base::FilePath(); +#endif + break; + + 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")); +#endif + extension = FILE_PATH_LITERAL(".pem"); break; } diff --git a/test/test_paths.h b/test/test_paths.h index 5540f77d..89ca4851 100644 --- a/test/test_paths.h +++ b/test/test_paths.h @@ -39,6 +39,9 @@ class TestPaths { //! \brief `.dll` will be used on Windows, and `.so` will be used on other //! platforms. kLoadableModule, + + //! \brief `.pem` used for all platforms. + kCertificate, }; //! \brief The architecture of the file requested of BuildArtifact(). diff --git a/test/test_test.gyp b/test/test_test.gyp index a31650da..3c6d7066 100644 --- a/test/test_test.gyp +++ b/test/test_test.gyp @@ -48,6 +48,9 @@ { 'target_name': 'crashpad_test_test_multiprocess_exec_test_child', 'type': 'executable', + 'dependencies': [ + '../third_party/mini_chromium/mini_chromium.gyp:base', + ], 'sources': [ 'multiprocess_exec_test_child.cc', ], diff --git a/third_party/apple_cctools/README.crashpad b/third_party/apple_cctools/README.crashpad deleted file mode 100644 index 43b08618..00000000 --- a/third_party/apple_cctools/README.crashpad +++ /dev/null @@ -1,44 +0,0 @@ -Name: Apple cctools -Short Name: cctools -URL: https://opensource.apple.com/source/cctools/ -URL: https://opensource.apple.com/tarballs/cctools/ -Version: 855 (from Xcode 5.1) -License: APSL 2.0 -License File: cctools/APPLE_LICENSE -Security Critical: no - -Description: -cctools contains portions of Apple’s compiler toolchain, including common tools -like ar, as, nm, strings, and strip, and platform-specific tools like lipo and -otool. It also contains support libraries such as libmacho, which contains -interfaces for dealing with Mach-O images. - -libmacho is available on macOS as a runtime library that is part of libSystem, -but versions of libmacho included in operating system versions prior to Mac OS X -10.7 did not include the getsectiondata() and getsegmentdata() functions. This -library is present here to provide implementations of these functions for -systems that do not have them. - -Crashpad code is not expected to use this library directly. It should use the -getsectiondata() and getsegmentdata() wrappers in compat, which will use -system-provided implementations if present at runtime, and will otherwise fall -back to the implementations in this library. - -Local Modifications: - - Only cctools/APPLE_LICENSE, cctools/libmacho/getsecbyname.c, and - cctools/include/mach-o/getsect.h are included. - - getsecbyname.c and getsect.h have been trimmed to remove everything other - than the getsectiondata() and getsegmentdata() functions. The #include guards - in getsect.h have been made unique. - - getsectiondata() is renamed to crashpad_getsectiondata(), and - getsegmentdata() is renamed to crashpad_getsegmentdata(). - - These functions are only declared and defined if the deployment target is - older than 10.7. This library is not needed otherwise, because in that case, - the system always provides implementations in runtime libraries. - - Originally, each of these two functions were implemented twice: once for - 32-bit code and once for 64-bit code. Aside from the types and constants - used, the two implementations were completely identical. This has been - simplified to have a shared implementation that relies on local typedefs and - constants being defined properly. This change was only made in - getsecbyname.c. getsect.h was not changed to avoid leaking new definitions - beyond this header. diff --git a/third_party/apple_cctools/cctools/APPLE_LICENSE b/third_party/apple_cctools/cctools/APPLE_LICENSE deleted file mode 100644 index fe81a60c..00000000 --- a/third_party/apple_cctools/cctools/APPLE_LICENSE +++ /dev/null @@ -1,367 +0,0 @@ -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/apple_cctools/cctools/include/mach-o/getsect.h b/third_party/apple_cctools/cctools/include/mach-o/getsect.h deleted file mode 100644 index 639b8061..00000000 --- a/third_party/apple_cctools/cctools/include/mach-o/getsect.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2004 Apple Computer, 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 CRASHPAD_THIRD_PARTY_APPLE_CCTOOLS_CCTOOLS_INCLUDE_MACH_O_GETSECT_H_ -#define CRASHPAD_THIRD_PARTY_APPLE_CCTOOLS_CCTOOLS_INCLUDE_MACH_O_GETSECT_H_ - -#include - -#if !defined(MAC_OS_X_VERSION_10_7) || \ - MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#ifndef __LP64__ -/* - * Runtime interfaces for 32-bit Mach-O programs. - */ -extern uint8_t *crashpad_getsectiondata( - const struct mach_header *mhp, - const char *segname, - const char *sectname, - unsigned long *size); - -extern uint8_t *crashpad_getsegmentdata( - const struct mach_header *mhp, - const char *segname, - unsigned long *size); - -#else /* defined(__LP64__) */ -/* - * Runtime interfaces for 64-bit Mach-O programs. - */ -extern uint8_t *crashpad_getsectiondata( - const struct mach_header_64 *mhp, - const char *segname, - const char *sectname, - unsigned long *size); - -extern uint8_t *crashpad_getsegmentdata( - const struct mach_header_64 *mhp, - const char *segname, - unsigned long *size); - -#endif /* defined(__LP64__) */ - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 */ - -#endif /* CRASHPAD_THIRD_PARTY_APPLE_CCTOOLS_CCTOOLS_INCLUDE_MACH_O_GETSECT_H_ */ diff --git a/third_party/apple_cctools/cctools/libmacho/getsecbyname.c b/third_party/apple_cctools/cctools/libmacho/getsecbyname.c deleted file mode 100644 index 1db1cbec..00000000 --- a/third_party/apple_cctools/cctools/libmacho/getsecbyname.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 1999 Apple Computer, 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@ - */ - -#include "third_party/apple_cctools/cctools/include/mach-o/getsect.h" - -#if !defined(MAC_OS_X_VERSION_10_7) || \ - MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 - -#include - -#ifndef __LP64__ -typedef struct mach_header mach_header_32_64; -typedef struct segment_command segment_command_32_64; -typedef struct section section_32_64; -#define LC_SEGMENT_32_64 LC_SEGMENT -#else /* defined(__LP64__) */ -typedef struct mach_header_64 mach_header_32_64; -typedef struct segment_command_64 segment_command_32_64; -typedef struct section_64 section_32_64; -#define LC_SEGMENT_32_64 LC_SEGMENT_64 -#endif /* defined(__LP64__) */ - -/* - * This routine returns the a pointer to the section contents of the named - * section in the named segment if it exists in the image pointed to by the - * mach header. Otherwise it returns zero. - */ - -uint8_t * -crashpad_getsectiondata( -const mach_header_32_64 *mhp, -const char *segname, -const char *sectname, -unsigned long *size) -{ - segment_command_32_64 *sgp, *zero; - section_32_64 *sp, *find; - uint32_t i, j; - - zero = 0; - find = 0; - sp = 0; - sgp = (segment_command_32_64 *) - ((char *)mhp + sizeof(mach_header_32_64)); - for(i = 0; i < mhp->ncmds; i++){ - if(sgp->cmd == LC_SEGMENT_32_64){ - if(zero == 0 && sgp->fileoff == 0 && sgp->nsects != 0){ - zero = sgp; - if(find != 0) - goto done; - } - if(find == 0 && - strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0){ - sp = (section_32_64 *)((char *)sgp + - sizeof(segment_command_32_64)); - for(j = 0; j < sgp->nsects; j++){ - if(strncmp(sp->sectname, sectname, - sizeof(sp->sectname)) == 0 && - strncmp(sp->segname, segname, - sizeof(sp->segname)) == 0){ - find = sp; - if(zero != 0) - goto done; - } - sp = (section_32_64 *)((char *)sp + - sizeof(section_32_64)); - } - } - } - sgp = (segment_command_32_64 *)((char *)sgp + sgp->cmdsize); - } - return(0); -done: - *size = sp->size; - return((uint8_t *)((uintptr_t)mhp - zero->vmaddr + sp->addr)); -} - -uint8_t * -crashpad_getsegmentdata( -const mach_header_32_64 *mhp, -const char *segname, -unsigned long *size) -{ - segment_command_32_64 *sgp, *zero, *find; - uint32_t i; - - zero = 0; - find = 0; - sgp = (segment_command_32_64 *) - ((char *)mhp + sizeof(mach_header_32_64)); - for(i = 0; i < mhp->ncmds; i++){ - if(sgp->cmd == LC_SEGMENT_32_64){ - if(zero == 0 && sgp->fileoff == 0 && sgp->nsects != 0){ - zero = sgp; - if(find != 0) - goto done; - } - if(find == 0 && - strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0){ - find = sgp; - if(zero != 0) - goto done; - } - } - sgp = (segment_command_32_64 *)((char *)sgp + sgp->cmdsize); - } - return(0); -done: - *size = sgp->vmsize; - return((uint8_t *)((uintptr_t)mhp - zero->vmaddr + sgp->vmaddr)); -} - -#endif /* MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 */ diff --git a/third_party/cpp-httplib/BUILD.gn b/third_party/cpp-httplib/BUILD.gn new file mode 100644 index 00000000..be2a6d74 --- /dev/null +++ b/third_party/cpp-httplib/BUILD.gn @@ -0,0 +1,21 @@ +# 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. + +source_set("cpp-httplib") { + testonly = true + include_dirs = [ "cpp-httplib" ] + sources = [ + "cpp-httplib/httplib.h", + ] +} diff --git a/third_party/cpp-httplib/README.crashpad b/third_party/cpp-httplib/README.crashpad new file mode 100644 index 00000000..cfb476e0 --- /dev/null +++ b/third_party/cpp-httplib/README.crashpad @@ -0,0 +1,15 @@ +Name: cpp-httplib +Short Name: cpp-httplib +URL: https://github.com/yhirose/cpp-httplib +Revision: 5b3187e2f9e77c672063d49a1167bbb563da023e +License: MIT +License File: cpp-httplib/LICENSE +Security Critical: no (test only) + +Description: +A C++11 header-only HTTP library. + +Local Modifications: +- Exclude test/ and example/ subdirs. +- Patch httplib.h to use #include "third_party/zlib/zlib_crashpad.h" instead of + . diff --git a/third_party/cpp-httplib/cpp-httplib/LICENSE b/third_party/cpp-httplib/cpp-httplib/LICENSE new file mode 100644 index 00000000..3e5ed359 --- /dev/null +++ b/third_party/cpp-httplib/cpp-httplib/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 yhirose + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/third_party/cpp-httplib/cpp-httplib/README.md b/third_party/cpp-httplib/cpp-httplib/README.md new file mode 100644 index 00000000..84685b4a --- /dev/null +++ b/third_party/cpp-httplib/cpp-httplib/README.md @@ -0,0 +1,209 @@ +cpp-httplib +=========== + +A C++11 header-only HTTP library. + +It's extremely easy to setup. Just include **httplib.h** file in your code! + +Inspired by [Sinatra](http://www.sinatrarb.com/) and [express](https://github.com/visionmedia/express). + +Server Example +-------------- + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Method Chain + +```cpp +svr.Get("/get", [](const auto& req, auto& res) { + res.set_content("get", "text/plain"); + }) + .Post("/post", [](const auto& req, auto& res) { + res.set_content(req.body(), "text/plain"); + }) + .listen("localhost", 1234); +``` + +### Static File Server + +```cpp +svr.set_base_dir("./www"); +``` + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error Handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + const char* fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1")); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + auto body = req.body.substr(file.offset, file.length)); +}) +``` + +Client Example +-------------- + +### GET + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + auto res = cli.Get("/hi"); + if (res && res->status == 200) { + std::cout << res->body << std::endl; + } +} +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params["name"] = "john"; +params["note"] = "coder"; +auto res = cli.Post("/post", params); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Connection Timeout + +```c++ +httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds +``` +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +std::shared_ptr res = + cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)((len/total)*100)); + } +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23). + +### Range + +```cpp +httplib::Client cli("httpbin.org", 80); + +// 'Range: bytes=1-10' +httplib::Headers headers = { httplib::make_range_header(1, 10) }; + +auto res = cli.Get("/range/32", headers); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +OpenSSL Support +--------------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT + +SSLServer svr("./cert.pem", "./key.pem"); + +SSLClient cli("localhost", 8080); +``` + +Zlib Support +------------ + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. + +The server applies gzip compression to the following MIME type contents: + + * all text types + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +License +------- + +MIT license (© 2018 Yuji Hirose) diff --git a/third_party/cpp-httplib/cpp-httplib/httplib.h b/third_party/cpp-httplib/cpp-httplib/httplib.h new file mode 100644 index 00000000..dadab1d8 --- /dev/null +++ b/third_party/cpp-httplib/cpp-httplib/httplib.h @@ -0,0 +1,2335 @@ +// +// httplib.h +// +// Copyright (c) 2017 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef _CPPHTTPLIB_HTTPLIB_H_ +#define _CPPHTTPLIB_HTTPLIB_H_ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf_s +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG)==S_IFREG) +#endif +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR) +#endif + +#include +#include +#include + +#undef min +#undef max + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif + +typedef SOCKET socket_t; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int socket_t; +#define INVALID_SOCKET (-1) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include "third_party/zlib/zlib_crashpad.h" +#endif + +/* + * Configuration + */ +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 + +namespace httplib +{ + +namespace detail { + +struct ci { + bool operator() (const std::string & s1, const std::string & s2) const { + return std::lexicographical_compare( + s1.begin(), s1.end(), + s2.begin(), s2.end(), + [](char c1, char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +} // namespace detail + +enum class HttpVersion { v1_0 = 0, v1_1 }; + +typedef std::multimap Headers; + +template +std::pair make_range_header(uint64_t value, Args... args); + +typedef std::multimap Params; +typedef std::smatch Match; +typedef std::function Progress; + +struct MultipartFile { + std::string filename; + std::string content_type; + size_t offset = 0; + size_t length = 0; +}; +typedef std::multimap MultipartFiles; + +struct Request { + std::string version; + std::string method; + std::string target; + std::string path; + Headers headers; + std::string body; + Params params; + MultipartFiles files; + Match matches; + + Progress progress; + + bool has_header(const char* key) const; + std::string get_header_value(const char* key) const; + void set_header(const char* key, const char* val); + + bool has_param(const char* key) const; + std::string get_param_value(const char* key) const; + + bool has_file(const char* key) const; + MultipartFile get_file_value(const char* key) const; +}; + +struct Response { + std::string version; + int status; + Headers headers; + std::string body; + + bool has_header(const char* key) const; + std::string get_header_value(const char* key) const; + void set_header(const char* key, const char* val); + + void set_redirect(const char* uri); + void set_content(const char* s, size_t n, const char* content_type); + void set_content(const std::string& s, const char* content_type); + + Response() : status(-1) {} +}; + +class Stream { +public: + virtual ~Stream() {} + virtual int read(char* ptr, size_t size) = 0; + virtual int write(const char* ptr, size_t size1) = 0; + virtual int write(const char* ptr) = 0; + virtual std::string get_remote_addr() = 0; + + template + void write_format(const char* fmt, const Args& ...args); +}; + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock); + virtual ~SocketStream(); + + virtual int read(char* ptr, size_t size); + virtual int write(const char* ptr, size_t size); + virtual int write(const char* ptr); + virtual std::string get_remote_addr(); + +private: + socket_t sock_; +}; + +class Server { +public: + typedef std::function Handler; + typedef std::function Logger; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server& Get(const char* pattern, Handler handler); + Server& Post(const char* pattern, Handler handler); + + Server& Put(const char* pattern, Handler handler); + Server& Delete(const char* pattern, Handler handler); + Server& Options(const char* pattern, Handler handler); + + bool set_base_dir(const char* path); + + void set_error_handler(Handler handler); + void set_logger(Logger logger); + + void set_keep_alive_max_count(size_t count); + + int bind_to_any_port(const char* host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const char* host, int port, int socket_flags = 0); + + bool is_running() const; + void stop(); + +protected: + bool process_request(Stream& strm, bool last_connection, bool& connection_close); + + size_t keep_alive_max_count_; + +private: + typedef std::vector> Handlers; + + socket_t create_server_socket(const char* host, int port, int socket_flags) const; + int bind_internal(const char* host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request& req, Response& res); + bool handle_file_request(Request& req, Response& res); + bool dispatch_request(Request& req, Response& res, Handlers& handlers); + + bool parse_request_line(const char* s, Request& req); + void write_response(Stream& strm, bool last_connection, const Request& req, Response& res); + + virtual bool read_and_close_socket(socket_t sock); + + bool is_running_; + socket_t svr_sock_; + std::string base_dir_; + Handlers get_handlers_; + Handlers post_handlers_; + Handlers put_handlers_; + Handlers delete_handlers_; + Handlers options_handlers_; + Handler error_handler_; + Logger logger_; + + // TODO: Use thread pool... + std::mutex running_threads_mutex_; + int running_threads_; +}; + +class Client { +public: + Client( + const char* host, + int port = 80, + size_t timeout_sec = 300); + + virtual ~Client(); + + virtual bool is_valid() const; + + std::shared_ptr Get(const char* path, Progress progress = nullptr); + std::shared_ptr Get(const char* path, const Headers& headers, Progress progress = nullptr); + + std::shared_ptr Head(const char* path); + std::shared_ptr Head(const char* path, const Headers& headers); + + std::shared_ptr Post(const char* path, const std::string& body, const char* content_type); + std::shared_ptr Post(const char* path, const Headers& headers, const std::string& body, const char* content_type); + + std::shared_ptr Post(const char* path, const Params& params); + std::shared_ptr Post(const char* path, const Headers& headers, const Params& params); + + std::shared_ptr Put(const char* path, const std::string& body, const char* content_type); + std::shared_ptr Put(const char* path, const Headers& headers, const std::string& body, const char* content_type); + + std::shared_ptr Delete(const char* path); + std::shared_ptr Delete(const char* path, const Headers& headers); + + std::shared_ptr Options(const char* path); + std::shared_ptr Options(const char* path, const Headers& headers); + + bool send(Request& req, Response& res); + +protected: + bool process_request(Stream& strm, Request& req, Response& res, bool& connection_close); + + const std::string host_; + const int port_; + size_t timeout_sec_; + const std::string host_and_port_; + +private: + socket_t create_client_socket() const; + bool read_response_line(Stream& strm, Response& res); + void write_request(Stream& strm, Request& req); + + virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL* ssl); + virtual ~SSLSocketStream(); + + virtual int read(char* ptr, size_t size); + virtual int write(const char* ptr, size_t size); + virtual int write(const char* ptr); + virtual std::string get_remote_addr(); + +private: + socket_t sock_; + SSL* ssl_; +}; + +class SSLServer : public Server { +public: + SSLServer( + const char* cert_path, const char* private_key_path); + + virtual ~SSLServer(); + + virtual bool is_valid() const; + +private: + virtual bool read_and_close_socket(socket_t sock); + + SSL_CTX* ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : public Client { +public: + SSLClient( + const char* host, + int port = 80, + size_t timeout_sec = 300); + + virtual ~SSLClient(); + + virtual bool is_valid() const; + +private: + virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); + + SSL_CTX* ctx_; + std::mutex ctx_mutex_; +}; +#endif + +/* + * Implementation + */ +namespace detail { + +template +void split(const char* b, const char* e, char d, Fn fn) +{ + int i = 0; + int beg = 0; + + while (e ? (b + i != e) : (b[i] != '\0')) { + if (b[i] == d) { + fn(&b[beg], &b[i]); + beg = i + 1; + } + i++; + } + + if (i) { + fn(&b[beg], &b[i]); + } +} + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream& strm, char* fixed_buffer, size_t fixed_buffer_size) + : strm_(strm) + , fixed_buffer_(fixed_buffer) + , fixed_buffer_size_(fixed_buffer_size) { + } + + const char* ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } + } + + bool getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0; ; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { + break; + } + } + + return true; + } + +private: + void append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } + } + + Stream& strm_; + char* fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_; + std::string glowable_buffer_; +}; + +inline int close_socket(socket_t sock) +{ +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +inline int select_read(socket_t sock, size_t sec, size_t usec) +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = sec; + tv.tv_usec = usec; + + return select(sock + 1, &fds, NULL, NULL, &tv); +} + +inline bool wait_until_socket_is_ready(socket_t sock, size_t sec, size_t usec) +{ + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = sec; + tv.tv_usec = usec; + + if (select(sock + 1, &fdsr, &fdsw, &fdse, &tv) < 0) { + return false; + } else if (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw)) { + int error = 0; + socklen_t len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0 || error) { + return false; + } + } else { + return false; + } + + return true; +} + +template +inline bool read_and_close_socket(socket_t sock, size_t keep_alive_max_count, T callback) +{ + bool ret = false; + + if (keep_alive_max_count > 0) { + auto count = keep_alive_max_count; + while (count > 0 && + detail::select_read(sock, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { + SocketStream strm(sock); + auto last_connection = count == 1; + auto connection_close = false; + + ret = callback(strm, last_connection, connection_close); + if (!ret || connection_close) { + break; + } + + count--; + } + } else { + SocketStream strm(sock); + auto dummy_connection_close = false; + ret = callback(strm, true, dummy_connection_close); + } + + close_socket(sock); + return ret; +} + +inline int shutdown_socket(socket_t sock) +{ +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const char* host, int port, Fn fn, int socket_flags = 0) +{ +#ifdef _WIN32 +#define SO_SYNCHRONOUS_NONALERT 0x20 +#define SO_OPENTYPE 0x7008 + + int opt = SO_SYNCHRONOUS_NONALERT; + setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt)); +#endif + + // Get address info + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = socket_flags; + hints.ai_protocol = 0; + + auto service = std::to_string(port); + + if (getaddrinfo(host, service.c_str(), &hints, &result)) { + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock == INVALID_SOCKET) { + continue; + } + + // Make 'reuse address' option available + int yes = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); + + // bind or connect + if (fn(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) +{ +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() +{ +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline std::string get_remote_addr(socket_t sock) { + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + + if (!getpeername(sock, (struct sockaddr*)&addr, &len)) { + char ipstr[NI_MAXHOST]; + + if (!getnameinfo((struct sockaddr*)&addr, len, + ipstr, sizeof(ipstr), nullptr, 0, NI_NUMERICHOST)) { + return ipstr; + } + } + + return std::string(); +} + +inline bool is_file(const std::string& path) +{ + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +} + +inline bool is_dir(const std::string& path) +{ + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string& path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { + return false; + } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline void read_file(const std::string& path, std::string& out) +{ + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], size); +} + +inline std::string file_extension(const std::string& path) +{ + std::smatch m; + auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, pat)) { + return m[1].str(); + } + return std::string(); +} + +inline const char* find_content_type(const std::string& path) +{ + auto ext = file_extension(path); + if (ext == "txt") { + return "text/plain"; + } else if (ext == "html") { + return "text/html"; + } else if (ext == "css") { + return "text/css"; + } else if (ext == "jpeg" || ext == "jpg") { + return "image/jpg"; + } else if (ext == "png") { + return "image/png"; + } else if (ext == "gif") { + return "image/gif"; + } else if (ext == "svg") { + return "image/svg+xml"; + } else if (ext == "ico") { + return "image/x-icon"; + } else if (ext == "json") { + return "application/json"; + } else if (ext == "pdf") { + return "application/pdf"; + } else if (ext == "js") { + return "application/javascript"; + } else if (ext == "xml") { + return "application/xml"; + } else if (ext == "xhtml") { + return "application/xhtml+xml"; + } + return nullptr; +} + +inline const char* status_message(int status) +{ + switch (status) { + case 200: return "OK"; + case 400: return "Bad Request"; + case 404: return "Not Found"; + case 415: return "Unsupported Media Type"; + default: + case 500: return "Internal Server Error"; + } +} + +inline const char* get_header_value(const Headers& headers, const char* key, const char* def) +{ + auto it = headers.find(key); + if (it != headers.end()) { + return it->second.c_str(); + } + return def; +} + +inline int get_header_value_int(const Headers& headers, const char* key, int def) +{ + auto it = headers.find(key); + if (it != headers.end()) { + return std::stoi(it->second); + } + return def; +} + +inline bool read_headers(Stream& strm, Headers& headers) +{ + static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)"); + + const auto bufsiz = 2048; + char buf[bufsiz]; + + stream_line_reader reader(strm, buf, bufsiz); + + for (;;) { + if (!reader.getline()) { + return false; + } + if (!strcmp(reader.ptr(), "\r\n")) { + break; + } + std::cmatch m; + if (std::regex_match(reader.ptr(), m, re)) { + auto key = std::string(m[1]); + auto val = std::string(m[2]); + headers.emplace(key, val); + } + } + + return true; +} + +inline bool read_content_with_length(Stream& strm, std::string& out, size_t len, Progress progress) +{ + out.assign(len, 0); + size_t r = 0; + while (r < len){ + auto n = strm.read(&out[r], len - r); + if (n <= 0) { + return false; + } + + r += n; + + if (progress) { + progress(r, len); + } + } + + return true; +} + +inline bool read_content_without_length(Stream& strm, std::string& out) +{ + for (;;) { + char byte; + auto n = strm.read(&byte, 1); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + out += byte; + } + + return true; +} + +inline bool read_content_chunked(Stream& strm, std::string& out) +{ + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader reader(strm, buf, bufsiz); + + if (!reader.getline()) { + return false; + } + + auto chunk_len = std::stoi(reader.ptr(), 0, 16); + + while (chunk_len > 0){ + std::string chunk; + if (!read_content_with_length(strm, chunk, chunk_len, nullptr)) { + return false; + } + + if (!reader.getline()) { + return false; + } + + if (strcmp(reader.ptr(), "\r\n")) { + break; + } + + out += chunk; + + if (!reader.getline()) { + return false; + } + + chunk_len = std::stoi(reader.ptr(), 0, 16); + } + + if (chunk_len == 0) { + // Reader terminator after chunks + if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) + return false; + } + + return true; +} + +template +bool read_content(Stream& strm, T& x, Progress progress = Progress()) +{ + auto len = get_header_value_int(x.headers, "Content-Length", 0); + + if (len) { + return read_content_with_length(strm, x.body, len, progress); + } else { + const auto& encoding = get_header_value(x.headers, "Transfer-Encoding", ""); + + if (!strcasecmp(encoding, "chunked")) { + return read_content_chunked(strm, x.body); + } else { + return read_content_without_length(strm, x.body); + } + } + + return true; +} + +template +inline void write_headers(Stream& strm, const T& info) +{ + for (const auto& x: info.headers) { + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + } + strm.write("\r\n"); +} + +inline std::string encode_url(const std::string& s) +{ + std::string result; + + for (auto i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "+"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + case ':': result += "%3A"; break; + case ';': result += "%3B"; break; + default: + if (s[i] < 0) { + result += '%'; + char hex[4]; + size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", (unsigned char)s[i]); + assert(len == 2); + result.append(hex, len); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline bool is_hex(char c, int& v) +{ + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string& s, int i, int cnt, int& val) +{ + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { + return false; + } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline size_t to_utf8(int code, char* buff) +{ + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = (0xC0 | ((code >> 6) & 0x1F)); + buff[1] = (0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = (0xE0 | ((code >> 12) & 0xF)); + buff[1] = (0x80 | ((code >> 6) & 0x3F)); + buff[2] = (0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = (0xE0 | ((code >> 12) & 0xF)); + buff[1] = (0x80 | ((code >> 6) & 0x3F)); + buff[2] = (0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = (0xF0 | ((code >> 18) & 0x7)); + buff[1] = (0x80 | ((code >> 12) & 0x3F)); + buff[2] = (0x80 | ((code >> 6) & 0x3F)); + buff[3] = (0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +inline std::string decode_url(const std::string& s) +{ + std::string result; + + for (int i = 0; s[i]; i++) { + if (s[i] == '%') { + if (s[i + 1] && s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { + result.append(buff, len); + } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += val; + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void parse_query_text(const std::string& s, Params& params) +{ + split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) { + std::string key; + std::string val; + split(b, e, '=', [&](const char* b, const char* e) { + if (key.empty()) { + key.assign(b, e); + } else { + val.assign(b, e); + } + }); + params.emplace(key, decode_url(val)); + }); +} + +inline bool parse_multipart_boundary(const std::string& content_type, std::string& boundary) +{ + auto pos = content_type.find("boundary="); + if (pos == std::string::npos) { + return false; + } + + boundary = content_type.substr(pos + 9); + return true; +} + +inline bool parse_multipart_formdata( + const std::string& boundary, const std::string& body, MultipartFiles& files) +{ + static std::string dash = "--"; + static std::string crlf = "\r\n"; + + static std::regex re_content_type( + "Content-Type: (.*?)", std::regex_constants::icase); + + static std::regex re_content_disposition( + "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?", + std::regex_constants::icase); + + auto dash_boundary = dash + boundary; + + auto pos = body.find(dash_boundary); + if (pos != 0) { + return false; + } + + pos += dash_boundary.size(); + + auto next_pos = body.find(crlf, pos); + if (next_pos == std::string::npos) { + return false; + } + + pos = next_pos + crlf.size(); + + while (pos < body.size()) { + next_pos = body.find(crlf, pos); + if (next_pos == std::string::npos) { + return false; + } + + std::string name; + MultipartFile file; + + auto header = body.substr(pos, (next_pos - pos)); + + while (pos != next_pos) { + std::smatch m; + if (std::regex_match(header, m, re_content_type)) { + file.content_type = m[1]; + } else if (std::regex_match(header, m, re_content_disposition)) { + name = m[1]; + file.filename = m[2]; + } + + pos = next_pos + crlf.size(); + + next_pos = body.find(crlf, pos); + if (next_pos == std::string::npos) { + return false; + } + + header = body.substr(pos, (next_pos - pos)); + } + + pos = next_pos + crlf.size(); + + next_pos = body.find(crlf + dash_boundary, pos); + + if (next_pos == std::string::npos) { + return false; + } + + file.offset = pos; + file.length = next_pos - pos; + + pos = next_pos + crlf.size() + dash_boundary.size(); + + next_pos = body.find(crlf, pos); + if (next_pos == std::string::npos) { + return false; + } + + files.emplace(name, file); + + pos = next_pos + crlf.size(); + } + + return true; +} + +inline std::string to_lower(const char* beg, const char* end) +{ + std::string out; + auto it = beg; + while (it != end) { + out += ::tolower(*it); + it++; + } + return out; +} + +inline void make_range_header_core(std::string&) {} + +template +inline void make_range_header_core(std::string& field, uint64_t value) +{ + if (!field.empty()) { + field += ", "; + } + field += std::to_string(value) + "-"; +} + +template +inline void make_range_header_core(std::string& field, uint64_t value1, uint64_t value2, Args... args) +{ + if (!field.empty()) { + field += ", "; + } + field += std::to_string(value1) + "-" + std::to_string(value2); + make_range_header_core(field, args...); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline bool can_compress(const std::string& content_type) { + return !content_type.find("text/") || + content_type == "image/svg+xml" || + content_type == "application/javascript" || + content_type == "application/json" || + content_type == "application/xml" || + content_type == "application/xhtml+xml"; +} + +inline void compress(std::string& content) +{ + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) { + return; + } + + strm.avail_in = content.size(); + strm.next_in = (Bytef *)content.data(); + + std::string compressed; + + const auto bufsiz = 16384; + char buff[bufsiz]; + do { + strm.avail_out = bufsiz; + strm.next_out = (Bytef *)buff; + deflate(&strm, Z_FINISH); + compressed.append(buff, bufsiz - strm.avail_out); + } while (strm.avail_out == 0); + + content.swap(compressed); + + deflateEnd(&strm); +} + +inline void decompress(std::string& content) +{ + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value to ensure + // that any gzip stream can be decoded. The offset of 16 specifies that the stream + // to decompress will be formatted with a gzip wrapper. + auto ret = inflateInit2(&strm, 16 + 15); + if (ret != Z_OK) { + return; + } + + strm.avail_in = content.size(); + strm.next_in = (Bytef *)content.data(); + + std::string decompressed; + + const auto bufsiz = 16384; + char buff[bufsiz]; + do { + strm.avail_out = bufsiz; + strm.next_out = (Bytef *)buff; + inflate(&strm, Z_NO_FLUSH); + decompressed.append(buff, bufsiz - strm.avail_out); + } while (strm.avail_out == 0); + + content.swap(decompressed); + + inflateEnd(&strm); +} +#endif + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + WSAStartup(0x0002, &wsaData); + } + + ~WSInit() { + WSACleanup(); + } +}; + +static WSInit wsinit_; +#endif + +} // namespace detail + +// Header utilities +template +inline std::pair make_range_header(uint64_t value, Args... args) +{ + std::string field; + detail::make_range_header_core(field, value, args...); + field.insert(0, "bytes="); + return std::make_pair("Range", field); +} + +// Request implementation +inline bool Request::has_header(const char* key) const +{ + return headers.find(key) != headers.end(); +} + +inline std::string Request::get_header_value(const char* key) const +{ + return detail::get_header_value(headers, key, ""); +} + +inline void Request::set_header(const char* key, const char* val) +{ + headers.emplace(key, val); +} + +inline bool Request::has_param(const char* key) const +{ + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const char* key) const +{ + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return std::string(); +} + +inline bool Request::has_file(const char* key) const +{ + return files.find(key) != files.end(); +} + +inline MultipartFile Request::get_file_value(const char* key) const +{ + auto it = files.find(key); + if (it != files.end()) { + return it->second; + } + return MultipartFile(); +} + +// Response implementation +inline bool Response::has_header(const char* key) const +{ + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const char* key) const +{ + return detail::get_header_value(headers, key, ""); +} + +inline void Response::set_header(const char* key, const char* val) +{ + headers.emplace(key, val); +} + +inline void Response::set_redirect(const char* url) +{ + set_header("Location", url); + status = 302; +} + +inline void Response::set_content(const char* s, size_t n, const char* content_type) +{ + body.assign(s, n); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string& s, const char* content_type) +{ + body = s; + set_header("Content-Type", content_type); +} + +// Rstream implementation +template +inline void Stream::write_format(const char* fmt, const Args& ...args) +{ + const auto bufsiz = 2048; + char buf[bufsiz]; + +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); +#else + auto n = snprintf(buf, bufsiz - 1, fmt, args...); +#endif + if (n > 0) { + if (n >= bufsiz - 1) { + std::vector glowable_buf(bufsiz); + + while (n >= static_cast(glowable_buf.size() - 1)) { + glowable_buf.resize(glowable_buf.size() * 2); +#if defined(_MSC_VER) && _MSC_VER < 1900 + n = _snprintf_s(&glowable_buf[0], glowable_buf.size(), glowable_buf.size() - 1, fmt, args...); +#else + n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...); +#endif + } + write(&glowable_buf[0], n); + } else { + write(buf, n); + } + } +} + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock): sock_(sock) +{ +} + +inline SocketStream::~SocketStream() +{ +} + +inline int SocketStream::read(char* ptr, size_t size) +{ + return recv(sock_, ptr, size, 0); +} + +inline int SocketStream::write(const char* ptr, size_t size) +{ + return send(sock_, ptr, size, 0); +} + +inline int SocketStream::write(const char* ptr) +{ + return write(ptr, strlen(ptr)); +} + +inline std::string SocketStream::get_remote_addr() { + return detail::get_remote_addr(sock_); +} + +// HTTP server implementation +inline Server::Server() + : keep_alive_max_count_(5) + , is_running_(false) + , svr_sock_(INVALID_SOCKET) + , running_threads_(0) +{ +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() +{ +} + +inline Server& Server::Get(const char* pattern, Handler handler) +{ + get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return *this; +} + +inline Server& Server::Post(const char* pattern, Handler handler) +{ + post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return *this; +} + +inline Server& Server::Put(const char* pattern, Handler handler) +{ + put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return *this; +} + +inline Server& Server::Delete(const char* pattern, Handler handler) +{ + delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return *this; +} + +inline Server& Server::Options(const char* pattern, Handler handler) +{ + options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + return *this; +} + +inline bool Server::set_base_dir(const char* path) +{ + if (detail::is_dir(path)) { + base_dir_ = path; + return true; + } + return false; +} + +inline void Server::set_error_handler(Handler handler) +{ + error_handler_ = handler; +} + +inline void Server::set_logger(Logger logger) +{ + logger_ = logger; +} + +inline void Server::set_keep_alive_max_count(size_t count) +{ + keep_alive_max_count_ = count; +} + +inline int Server::bind_to_any_port(const char* host, int socket_flags) +{ + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + return listen_internal(); +} + +inline bool Server::listen(const char* host, int port, int socket_flags) +{ + if (bind_internal(host, port, socket_flags) < 0) + return false; + return listen_internal(); +} + +inline bool Server::is_running() const +{ + return is_running_; +} + +inline void Server::stop() +{ + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + detail::shutdown_socket(svr_sock_); + detail::close_socket(svr_sock_); + svr_sock_ = INVALID_SOCKET; + } +} + +inline bool Server::parse_request_line(const char* s, Request& req) +{ + static std::regex re("(GET|HEAD|POST|PUT|DELETE|OPTIONS) (([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n"); + + std::cmatch m; + if (std::regex_match(s, m, re)) { + req.version = std::string(m[4]); + req.method = std::string(m[1]); + req.target = std::string(m[2]); + req.path = detail::decode_url(m[3]); + + // Parse query text + auto len = std::distance(m[4].first, m[4].second); + if (len > 0) { + detail::parse_query_text(m[4], req.params); + } + + return true; + } + + return false; +} + +inline void Server::write_response(Stream& strm, bool last_connection, const Request& req, Response& res) +{ + assert(res.status != -1); + + if (400 <= res.status && error_handler_) { + error_handler_(req, res); + } + + // Response line + strm.write_format("HTTP/1.1 %d %s\r\n", + res.status, + detail::status_message(res.status)); + + // Headers + if (last_connection || + req.version == "HTTP/1.0" || + req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } + + if (!res.body.empty()) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0 + const auto& encodings = req.get_header_value("Accept-Encoding"); + if (encodings.find("gzip") != std::string::npos && + detail::can_compress(res.get_header_value("Content-Type"))) { + detail::compress(res.body); + res.set_header("Content-Encoding", "gzip"); + } +#endif + + if (!res.has_header("Content-Type")) { + res.set_header("Content-Type", "text/plain"); + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length.c_str()); + } + + detail::write_headers(strm, res); + + // Body + if (!res.body.empty() && req.method != "HEAD") { + strm.write(res.body.c_str(), res.body.size()); + } + + // Log + if (logger_) { + logger_(req, res); + } +} + +inline bool Server::handle_file_request(Request& req, Response& res) +{ + if (!base_dir_.empty() && detail::is_valid_path(req.path)) { + std::string path = base_dir_ + req.path; + + if (!path.empty() && path.back() == '/') { + path += "index.html"; + } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = detail::find_content_type(path); + if (type) { + res.set_header("Content-Type", type); + } + res.status = 200; + return true; + } + } + + return false; +} + +inline socket_t Server::create_server_socket(const char* host, int port, int socket_flags) const +{ + return detail::create_socket(host, port, + [](socket_t sock, struct addrinfo& ai) -> bool { + if (::bind(sock, ai.ai_addr, ai.ai_addrlen)) { + return false; + } + if (::listen(sock, 5)) { // Listen through 5 channels + return false; + } + return true; + }, socket_flags); +} + +inline int Server::bind_internal(const char* host, int port, int socket_flags) +{ + if (!is_valid()) { + return -1; + } + + svr_sock_ = create_server_socket(host, port, socket_flags); + if (svr_sock_ == INVALID_SOCKET) { + return -1; + } + + if (port == 0) { + struct sockaddr_storage address; + socklen_t len = sizeof(address); + if (getsockname(svr_sock_, reinterpret_cast(&address), &len) == -1) { + return -1; + } + if (address.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&address)->sin_port); + } else if (address.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&address)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() +{ + auto ret = true; + + is_running_ = true; + + for (;;) { + auto val = detail::select_read(svr_sock_, 0, 100000); + + if (val == 0) { // Timeout + if (svr_sock_ == INVALID_SOCKET) { + // The server socket was closed by 'stop' method. + break; + } + continue; + } + + socket_t sock = accept(svr_sock_, NULL, NULL); + + if (sock == INVALID_SOCKET) { + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + // TODO: Use thread pool... + std::thread([=]() { + { + std::lock_guard guard(running_threads_mutex_); + running_threads_++; + } + + read_and_close_socket(sock); + + { + std::lock_guard guard(running_threads_mutex_); + running_threads_--; + } + }).detach(); + } + + // TODO: Use thread pool... + for (;;) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::lock_guard guard(running_threads_mutex_); + if (!running_threads_) { + break; + } + } + + is_running_ = false; + + return ret; +} + +inline bool Server::routing(Request& req, Response& res) +{ + if (req.method == "GET" && handle_file_request(req, res)) { + return true; + } + + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } + return false; +} + +inline bool Server::dispatch_request(Request& req, Response& res, Handlers& handlers) +{ + for (const auto& x: handlers) { + const auto& pattern = x.first; + const auto& handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline bool Server::process_request(Stream& strm, bool last_connection, bool& connection_close) +{ + const auto bufsiz = 2048; + char buf[bufsiz]; + + detail::stream_line_reader reader(strm, buf, bufsiz); + + // Connection has been closed on client + if (!reader.getline()) { + return false; + } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + // Request line and headers + if (!parse_request_line(reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { + res.status = 400; + write_response(strm, last_connection, req, res); + return true; + } + + auto ret = true; + if (req.get_header_value("Connection") == "close") { + // ret = false; + connection_close = true; + } + + req.set_header("REMOTE_ADDR", strm.get_remote_addr().c_str()); + + // Body + if (req.method == "POST" || req.method == "PUT") { + if (!detail::read_content(strm, req)) { + res.status = 400; + write_response(strm, last_connection, req, res); + return ret; + } + + const auto& content_type = req.get_header_value("Content-Type"); + + if (req.get_header_value("Content-Encoding") == "gzip") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + detail::decompress(req.body); +#else + res.status = 415; + write_response(strm, last_connection, req, res); + return ret; +#endif + } + + if (!content_type.find("application/x-www-form-urlencoded")) { + detail::parse_query_text(req.body, req.params); + } else if(!content_type.find("multipart/form-data")) { + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary) || + !detail::parse_multipart_formdata(boundary, req.body, req.files)) { + res.status = 400; + write_response(strm, last_connection, req, res); + return ret; + } + } + } + + if (routing(req, res)) { + if (res.status == -1) { + res.status = 200; + } + } else { + res.status = 404; + } + + write_response(strm, last_connection, req, res); + return ret; +} + +inline bool Server::is_valid() const +{ + return true; +} + +inline bool Server::read_and_close_socket(socket_t sock) +{ + return detail::read_and_close_socket( + sock, + keep_alive_max_count_, + [this](Stream& strm, bool last_connection, bool& connection_close) { + return process_request(strm, last_connection, connection_close); + }); +} + +// HTTP client implementation +inline Client::Client( + const char* host, int port, size_t timeout_sec) + : host_(host) + , port_(port) + , timeout_sec_(timeout_sec) + , host_and_port_(host_ + ":" + std::to_string(port_)) +{ +} + +inline Client::~Client() +{ +} + +inline bool Client::is_valid() const +{ + return true; +} + +inline socket_t Client::create_client_socket() const +{ + return detail::create_socket(host_.c_str(), port_, + [=](socket_t sock, struct addrinfo& ai) -> bool { + detail::set_nonblocking(sock, true); + + auto ret = connect(sock, ai.ai_addr, ai.ai_addrlen); + if (ret < 0) { + if (detail::is_connection_error() || + !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) { + detail::close_socket(sock); + return false; + } + } + + detail::set_nonblocking(sock, false); + return true; + }); +} + +inline bool Client::read_response_line(Stream& strm, Response& res) +{ + const auto bufsiz = 2048; + char buf[bufsiz]; + + detail::stream_line_reader reader(strm, buf, bufsiz); + + if (!reader.getline()) { + return false; + } + + const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .+\r\n"); + + std::cmatch m; + if (std::regex_match(reader.ptr(), m, re)) { + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + } + + return true; +} + +inline bool Client::send(Request& req, Response& res) +{ + if (req.path.empty()) { + return false; + } + + auto sock = create_client_socket(); + if (sock == INVALID_SOCKET) { + return false; + } + + return read_and_close_socket(sock, req, res); +} + +inline void Client::write_request(Stream& strm, Request& req) +{ + auto path = detail::encode_url(req.path); + + // Request line + strm.write_format("%s %s HTTP/1.1\r\n", + req.method.c_str(), + path.c_str()); + + // Headers + req.set_header("Host", host_and_port_.c_str()); + + if (!req.has_header("Accept")) { + req.set_header("Accept", "*/*"); + } + + if (!req.has_header("User-Agent")) { + req.set_header("User-Agent", "cpp-httplib/0.2"); + } + + // TODO: Support KeepAlive connection + // if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + // } + + if (!req.body.empty()) { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } + + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length.c_str()); + } + + detail::write_headers(strm, req); + + // Body + if (!req.body.empty()) { + if (req.get_header_value("Content-Type") == "application/x-www-form-urlencoded") { + auto str = detail::encode_url(req.body); + strm.write(str.c_str(), str.size()); + } else { + strm.write(req.body.c_str(), req.body.size()); + } + } +} + +inline bool Client::process_request(Stream& strm, Request& req, Response& res, bool& connection_close) +{ + // Send request + write_request(strm, req); + + // Receive response and headers + if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { + return false; + } + + if (res.get_header_value("Connection") == "close" || res.version == "HTTP/1.0") { + connection_close = true; + } + + // Body + if (req.method != "HEAD") { + if (!detail::read_content(strm, res, req.progress)) { + return false; + } + + if (res.get_header_value("Content-Encoding") == "gzip") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + detail::decompress(res.body); +#else + return false; +#endif + } + } + + return true; +} + +inline bool Client::read_and_close_socket(socket_t sock, Request& req, Response& res) +{ + return detail::read_and_close_socket( + sock, + 0, + [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { + return process_request(strm, req, res, connection_close); + }); +} + +inline std::shared_ptr Client::Get(const char* path, Progress progress) +{ + return Get(path, Headers(), progress); +} + +inline std::shared_ptr Client::Get(const char* path, const Headers& headers, Progress progress) +{ + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = progress; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +inline std::shared_ptr Client::Head(const char* path) +{ + return Head(path, Headers()); +} + +inline std::shared_ptr Client::Head(const char* path, const Headers& headers) +{ + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +inline std::shared_ptr Client::Post( + const char* path, const std::string& body, const char* content_type) +{ + return Post(path, Headers(), body, content_type); +} + +inline std::shared_ptr Client::Post( + const char* path, const Headers& headers, const std::string& body, const char* content_type) +{ + Request req; + req.method = "POST"; + req.headers = headers; + req.path = path; + + req.headers.emplace("Content-Type", content_type); + req.body = body; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +inline std::shared_ptr Client::Post(const char* path, const Params& params) +{ + return Post(path, Headers(), params); +} + +inline std::shared_ptr Client::Post(const char* path, const Headers& headers, const Params& params) +{ + std::string query; + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { + query += "&"; + } + query += it->first; + query += "="; + query += it->second; + } + + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline std::shared_ptr Client::Put( + const char* path, const std::string& body, const char* content_type) +{ + return Put(path, Headers(), body, content_type); +} + +inline std::shared_ptr Client::Put( + const char* path, const Headers& headers, const std::string& body, const char* content_type) +{ + Request req; + req.method = "PUT"; + req.headers = headers; + req.path = path; + + req.headers.emplace("Content-Type", content_type); + req.body = body; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +inline std::shared_ptr Client::Delete(const char* path) +{ + return Delete(path, Headers()); +} + +inline std::shared_ptr Client::Delete(const char* path, const Headers& headers) +{ + Request req; + req.method = "DELETE"; + req.path = path; + req.headers = headers; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +inline std::shared_ptr Client::Options(const char* path) +{ + return Options(path, Headers()); +} + +inline std::shared_ptr Client::Options(const char* path, const Headers& headers) +{ + Request req; + req.method = "OPTIONS"; + req.path = path; + req.headers = headers; + + auto res = std::make_shared(); + + return send(req, *res) ? res : nullptr; +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline bool read_and_close_socket_ssl( + socket_t sock, size_t keep_alive_max_count, + // TODO: OpenSSL 1.0.2 occasionally crashes... + // The upcoming 1.1.0 is going to be thread safe. + SSL_CTX* ctx, std::mutex& ctx_mutex, + U SSL_connect_or_accept, V setup, + T callback) +{ + SSL* ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + + ssl = SSL_new(ctx); + if (!ssl) { + return false; + } + } + + auto bio = BIO_new_socket(sock, BIO_NOCLOSE); + SSL_set_bio(ssl, bio, bio); + + setup(ssl); + + SSL_connect_or_accept(ssl); + + bool ret = false; + + if (keep_alive_max_count > 0) { + auto count = keep_alive_max_count; + while (count > 0 && + detail::select_read(sock, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { + SSLSocketStream strm(sock, ssl); + auto last_connection = count == 1; + auto connection_close = false; + + ret = callback(strm, last_connection, connection_close); + if (!ret || connection_close) { + break; + } + + count--; + } + } else { + SSLSocketStream strm(sock, ssl); + auto dummy_connection_close = false; + ret = callback(strm, true, dummy_connection_close); + } + + SSL_shutdown(ssl); + + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + + close_socket(sock); + + return ret; +} + +class SSLInit { +public: + SSLInit() { + SSL_load_error_strings(); + SSL_library_init(); + } +}; + +static SSLInit sslinit_; + +} // namespace detail + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL* ssl) + : sock_(sock), ssl_(ssl) +{ +} + +inline SSLSocketStream::~SSLSocketStream() +{ +} + +inline int SSLSocketStream::read(char* ptr, size_t size) +{ + return SSL_read(ssl_, ptr, size); +} + +inline int SSLSocketStream::write(const char* ptr, size_t size) +{ + return SSL_write(ssl_, ptr, size); +} + +inline int SSLSocketStream::write(const char* ptr) +{ + return write(ptr, strlen(ptr)); +} + +inline std::string SSLSocketStream::get_remote_addr() { + return detail::get_remote_addr(sock_); +} + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path) +{ + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); + // EC_KEY_free(ecdh); + + if (SSL_CTX_use_certificate_file(ctx_, cert_path, SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() +{ + if (ctx_) { + SSL_CTX_free(ctx_); + } +} + +inline bool SSLServer::is_valid() const +{ + return ctx_; +} + +inline bool SSLServer::read_and_close_socket(socket_t sock) +{ + return detail::read_and_close_socket_ssl( + sock, + keep_alive_max_count_, + ctx_, ctx_mutex_, + SSL_accept, + [](SSL* /*ssl*/) {}, + [this](Stream& strm, bool last_connection, bool& connection_close) { + return process_request(strm, last_connection, connection_close); + }); +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const char* host, int port, size_t timeout_sec) + : Client(host, port, timeout_sec) +{ + ctx_ = SSL_CTX_new(SSLv23_client_method()); +} + +inline SSLClient::~SSLClient() +{ + if (ctx_) { + SSL_CTX_free(ctx_); + } +} + +inline bool SSLClient::is_valid() const +{ + return ctx_; +} + +inline bool SSLClient::read_and_close_socket(socket_t sock, Request& req, Response& res) +{ + return is_valid() && detail::read_and_close_socket_ssl( + sock, 0, + ctx_, ctx_mutex_, + SSL_connect, + [&](SSL* ssl) { + SSL_set_tlsext_host_name(ssl, host_.c_str()); + }, + [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { + return process_request(strm, req, res, connection_close); + }); +} +#endif + +} // namespace httplib + +#endif + +// vim: et ts=4 sw=4 cin cino={1s ff=unix diff --git a/third_party/fuchsia/README.crashpad b/third_party/fuchsia/README.crashpad new file mode 100644 index 00000000..8bf0a914 --- /dev/null +++ b/third_party/fuchsia/README.crashpad @@ -0,0 +1,3 @@ +This directory is a placeholder for Fuchsia tools that will be downloaded by +CIPD (https://github.com/luci/luci-go/tree/master/cipd). The CIPD update happens +as part of gclient runhooks. diff --git a/third_party/apple_cctools/apple_cctools.gyp b/third_party/getopt/BUILD.gn similarity index 62% rename from third_party/apple_cctools/apple_cctools.gyp rename to third_party/getopt/BUILD.gn index 7e0dfd43..da27624f 100644 --- a/third_party/apple_cctools/apple_cctools.gyp +++ b/third_party/getopt/BUILD.gn @@ -12,23 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -{ - 'targets': [ - { - 'target_name': 'apple_cctools', - 'type': 'static_library', - 'include_dirs': [ - '../..', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - '../..', - ], - }, - 'sources': [ - 'cctools/include/mach-o/getsect.h', - 'cctools/libmacho/getsecbyname.c', - ], - }, - ], +static_library("getopt") { + sources = [ + "getopt.cc", + "getopt.h", + ] } diff --git a/third_party/gtest/BUILD.gn b/third_party/gtest/BUILD.gn new file mode 100644 index 00000000..db5f4a56 --- /dev/null +++ b/third_party/gtest/BUILD.gn @@ -0,0 +1,404 @@ +# 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. + +import("../../build/crashpad_buildconfig.gni") +import("../../build/test.gni") + +if (crashpad_is_in_chromium) { + group("gtest") { + testonly = true + public_deps = [ + "//testing/gtest", + ] + } + group("gmock") { + testonly = true + public_deps = [ + "//testing/gmock", + ] + } +} else if (crashpad_is_in_fuchsia) { + group("gtest") { + testonly = true + public_deps = [ + "//third_party/googletest:gtest", + ] + } + group("gmock") { + testonly = true + public_deps = [ + "//third_party/googletest:gmock", + ] + } +} else if (crashpad_is_standalone) { + config("gtest_private_config") { + visibility = [ ":*" ] + include_dirs = [ "gtest/googletest" ] + defines = [ "GUNIT_NO_GOOGLE3=1" ] + } + + config("gtest_public_config") { + include_dirs = [ "gtest/googletest/include" ] + } + + static_library("gtest") { + testonly = true + sources = [ + "gtest/googletest/include/gtest/gtest-death-test.h", + "gtest/googletest/include/gtest/gtest-message.h", + "gtest/googletest/include/gtest/gtest-param-test.h", + "gtest/googletest/include/gtest/gtest-printers.h", + "gtest/googletest/include/gtest/gtest-spi.h", + "gtest/googletest/include/gtest/gtest-test-part.h", + "gtest/googletest/include/gtest/gtest-typed-test.h", + "gtest/googletest/include/gtest/gtest.h", + "gtest/googletest/include/gtest/gtest_pred_impl.h", + "gtest/googletest/include/gtest/gtest_prod.h", + "gtest/googletest/include/gtest/internal/custom/gtest-port.h", + "gtest/googletest/include/gtest/internal/custom/gtest-printers.h", + "gtest/googletest/include/gtest/internal/custom/gtest.h", + "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-port.cc", + "gtest/googletest/src/gtest-printers.cc", + "gtest/googletest/src/gtest-test-part.cc", + "gtest/googletest/src/gtest-typed-test.cc", + "gtest/googletest/src/gtest.cc", + ] + sources -= [ "gtest/googletest/src/gtest-all.cc" ] + public_configs = [ ":gtest_public_config" ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] + } + + static_library("gtest_main") { + # Tests outside of this file should use ../../test:gtest_main instead. + visibility = [ ":*" ] + + testonly = true + sources = [ + "gtest/googletest/src/gtest_main.cc", + ] + deps = [ + ":gtest", + ] + } + + 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/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_unittest.cc", + "gtest/googletest/test/production.cc", + "gtest/googletest/test/production.h", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ":gtest_main", + ] + + if (crashpad_is_win) { + cflags = [ "/wd4702" ] # unreachable code + } + } + + test("gtest_environment_test") { + sources = [ + "gtest/googletest/test/gtest_environment_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] + } + + test("gtest_listener_test") { + sources = [ + "gtest/googletest/test/gtest-listener_test.cc", + ] + deps = [ + ":gtest", + ] + } + + test("gtest_no_test") { + sources = [ + "gtest/googletest/test/gtest_no_test_unittest.cc", + ] + deps = [ + ":gtest", + ] + } + + 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", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] + + if (crashpad_is_clang) { + cflags_cc = [ + # For gtest/googlemock/test/gmock-matchers_test.cc’s + # Unstreamable::value_. + "-Wno-unused-private-field", + ] + } + } + + test("gtest_premature_exit_test") { + sources = [ + "gtest/googletest/test/gtest_premature_exit_test.cc", + ] + deps = [ + ":gtest", + ] + } + + test("gtest_repeat_test") { + sources = [ + "gtest/googletest/test/gtest_repeat_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] + } + + test("gtest_sole_header_test") { + sources = [ + "gtest/googletest/test/gtest_sole_header_test.cc", + ] + deps = [ + ":gtest", + ":gtest_main", + ] + } + + test("gtest_stress_test") { + sources = [ + "gtest/googletest/test/gtest_stress_test.cc", + ] + configs += [ ":gtest_private_config" ] + deps = [ + ":gtest", + ] + } + + test("gtest_unittest_api_test") { + sources = [ + "gtest/googletest/test/gtest-unittest-api_test.cc", + ] + deps = [ + ":gtest", + ] + } + + group("gtest_all_tests") { + testonly = true + deps = [ + ":gtest_all_test", + ":gtest_environment_test", + ":gtest_listener_test", + ":gtest_no_test", + ":gtest_param_test", + ":gtest_premature_exit_test", + ":gtest_repeat_test", + ":gtest_sole_header_test", + ":gtest_stress_test", + ":gtest_unittest_api_test", + ] + } + + config("gmock_private_config") { + visibility = [ ":*" ] + include_dirs = [ "gtest/googlemock" ] + } + + config("gmock_public_config") { + include_dirs = [ "gtest/googlemock/include" ] + + if (crashpad_is_clang) { + cflags_cc = [ + # The MOCK_METHODn() macros do not specify “override”, which triggers + # this warning in users: “error: 'Method' overrides a member function + # but is not marked 'override' + # [-Werror,-Winconsistent-missing-override]”. Suppress these warnings + # until https://github.com/google/googletest/issues/533 is fixed. + "-Wno-inconsistent-missing-override", + ] + } + } + + static_library("gmock") { + testonly = true + sources = [ + "gtest/googlemock/include/gmock/gmock-actions.h", + "gtest/googlemock/include/gmock/gmock-cardinalities.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-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/src/gmock-all.cc", + "gtest/googlemock/src/gmock-cardinalities.cc", + "gtest/googlemock/src/gmock-internal-utils.cc", + "gtest/googlemock/src/gmock-matchers.cc", + "gtest/googlemock/src/gmock-spec-builders.cc", + "gtest/googlemock/src/gmock.cc", + ] + sources -= [ "gtest/googlemock/src/gmock-all.cc" ] + public_configs = [ ":gmock_public_config" ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gtest", + ] + } + + static_library("gmock_main") { + # Tests outside of this file should use ../../test:gmock_main instead. + visibility = [ ":*" ] + testonly = true + sources = [ + "gtest/googlemock/src/gmock_main.cc", + ] + deps = [ + ":gmock", + ":gtest", + ] + } + + test("gmock_all_test") { + sources = [ + "gtest/googlemock/test/gmock-actions_test.cc", + "gtest/googlemock/test/gmock-cardinalities_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-spec-builders_test.cc", + "gtest/googlemock/test/gmock_test.cc", + ] + configs += [ + ":gmock_private_config", + ":gtest_private_config", + ] + deps = [ + ":gmock", + ":gmock_main", + ":gtest", + ] + + if (crashpad_is_clang) { + cflags_cc = [ + # For gtest/googlemock/test/gmock-matchers_test.cc’s + # testing::gmock_matchers_test::Unprintable::c_. + "-Wno-unused-private-field", + ] + } + } + + test("gmock_link_test") { + sources = [ + "gtest/googlemock/test/gmock_link2_test.cc", + "gtest/googlemock/test/gmock_link_test.cc", + "gtest/googlemock/test/gmock_link_test.h", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gmock", + ":gmock_main", + ":gtest", + ] + } + + test("gmock_stress_test") { + sources = [ + "gtest/googlemock/test/gmock_stress_test.cc", + ] + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + ] + configs += [ ":gmock_private_config" ] + deps = [ + ":gmock", + ":gtest", + ] + } + + group("gmock_all_tests") { + testonly = true + deps = [ + ":gmock_all_test", + ":gmock_link_test", + ":gmock_stress_test", + ] + } +} diff --git a/third_party/gtest/gmock.gyp b/third_party/gtest/gmock.gyp index a6600708..d53c925f 100644 --- a/third_party/gtest/gmock.gyp +++ b/third_party/gtest/gmock.gyp @@ -92,22 +92,19 @@ # triggers this warning in users: “error: 'Method' overrides a # member function but is not marked 'override' # [-Werror,-Winconsistent-missing-override]”. Suppress these - # warnings, and add -Wno-unknown-warning-option because only - # recent versions of clang (trunk r220703 and later, version - # 3.6 and later) recognize it. + # warnings until https://github.com/google/googletest/issues/533 is + # fixed. 'conditions': [ ['OS=="mac"', { 'xcode_settings': { 'WARNING_CFLAGS': [ '-Wno-inconsistent-missing-override', - '-Wno-unknown-warning-option', ], }, }], ['OS=="linux" or OS=="android"', { 'cflags': [ '-Wno-inconsistent-missing-override', - '-Wno-unknown-warning-option', ], }], ], @@ -171,6 +168,26 @@ '<(gmock_dir)/test/gmock-spec-builders_test.cc', '<(gmock_dir)/test/gmock_test.cc', ], + 'conditions': [ + ['clang!=0', { + # For gtest/googlemock/test/gmock-matchers_test.cc’s + # Unstreamable::value_. + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'WARNING_CFLAGS': [ + '-Wno-unused-private-field', + ], + }, + }], + ['OS=="linux" or OS=="android"', { + 'cflags': [ + '-Wno-unused-private-field', + ], + }], + ], + }], + ], }, { 'target_name': 'gmock_link_test', diff --git a/third_party/gtest/gtest.gyp b/third_party/gtest/gtest.gyp index ad458936..5d93feb5 100644 --- a/third_party/gtest/gtest.gyp +++ b/third_party/gtest/gtest.gyp @@ -40,6 +40,21 @@ 'cflags!': [ '-Wexit-time-destructors', ], + + 'conditions': [ + ['OS=="android" and android_api_level!="" and android_api_level<24', { + 'defines!': [ + # Although many system interfaces are available to 32-bit code with + # 64-bit off_t at API 21, the routines in are not until API + # 24. gtest doesn’t make use of these functions directly, but can + # reach them indirectly via the C++ standard library. Disable 64-bit + # off_t prior to API 24 so that these uses can work. Since nothing + # dependent on the size of off_t should escape gtest’s own API, this + # should be safe even in a program that otherwise uses a 64-bit off_t. + '_FILE_OFFSET_BITS=64', + ], + }], + ], }, 'targets': [ @@ -201,6 +216,26 @@ '<(gtest_dir)/test/gtest-param-test_test.cc', '<(gtest_dir)/test/gtest-param-test_test.h', ], + 'conditions': [ + ['clang!=0', { + # For gtest/googlemock/test/gmock-matchers_test.cc’s + # Unstreamable::value_. + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'WARNING_CFLAGS': [ + '-Wno-unused-private-field', + ], + }, + }], + ['OS=="linux" or OS=="android"', { + 'cflags': [ + '-Wno-unused-private-field', + ], + }], + ], + }], + ], }, { 'target_name': 'gtest_premature_exit_test', diff --git a/third_party/libfuzzer/BUILD.gn b/third_party/libfuzzer/BUILD.gn new file mode 100644 index 00000000..ac815dad --- /dev/null +++ b/third_party/libfuzzer/BUILD.gn @@ -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. + +source_set("libfuzzer") { + if (crashpad_use_libfuzzer) { + sources = [ + "src/FuzzerClangCounters.cpp", + "src/FuzzerCrossOver.cpp", + "src/FuzzerDriver.cpp", + "src/FuzzerExtFunctionsDlsym.cpp", + "src/FuzzerExtFunctionsDlsymWin.cpp", + "src/FuzzerExtFunctionsWeak.cpp", + "src/FuzzerExtFunctionsWeakAlias.cpp", + "src/FuzzerExtraCounters.cpp", + "src/FuzzerIO.cpp", + "src/FuzzerIOPosix.cpp", + "src/FuzzerIOWindows.cpp", + "src/FuzzerLoop.cpp", + "src/FuzzerMain.cpp", + "src/FuzzerMerge.cpp", + "src/FuzzerMutate.cpp", + "src/FuzzerSHA1.cpp", + "src/FuzzerShmemPosix.cpp", + "src/FuzzerShmemWindows.cpp", + "src/FuzzerTracePC.cpp", + "src/FuzzerUtil.cpp", + "src/FuzzerUtilDarwin.cpp", + "src/FuzzerUtilLinux.cpp", + "src/FuzzerUtilPosix.cpp", + "src/FuzzerUtilWindows.cpp", + ] + + configs -= [ + "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors", + "//build:crashpad_fuzzer_flags", + ] + } +} diff --git a/third_party/linux/README.crashpad b/third_party/linux/README.crashpad new file mode 100644 index 00000000..8bf0a914 --- /dev/null +++ b/third_party/linux/README.crashpad @@ -0,0 +1,3 @@ +This directory is a placeholder for Fuchsia tools that will be downloaded by +CIPD (https://github.com/luci/luci-go/tree/master/cipd). The CIPD update happens +as part of gclient runhooks. diff --git a/third_party/mini_chromium/BUILD.gn b/third_party/mini_chromium/BUILD.gn new file mode 100644 index 00000000..e11d0111 --- /dev/null +++ b/third_party/mini_chromium/BUILD.gn @@ -0,0 +1,37 @@ +# 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. + +import("../../build/crashpad_buildconfig.gni") + +group("base") { + if (crashpad_is_in_chromium) { + public_deps = [ + "//base", + ] + } else if (crashpad_is_standalone || crashpad_is_in_fuchsia) { + public_deps = [ + "mini_chromium/base", + ] + } +} + +group("base_test_support") { + testonly = true + + if (crashpad_is_in_chromium) { + public_deps = [ + "//base/test:test_support", + ] + } +} diff --git a/third_party/mini_chromium/README.crashpad b/third_party/mini_chromium/README.crashpad index 1fa3c4d9..37d507bf 100644 --- a/third_party/mini_chromium/README.crashpad +++ b/third_party/mini_chromium/README.crashpad @@ -8,7 +8,7 @@ Security Critical: yes Description: mini_chromium is a small collection of useful low-level (“base”) routines from -the Chromium open-source project at http://www.chromium.org/. Chromium is +the Chromium open-source project at https://www.chromium.org/Home. Chromium is large, sprawling, full of dependencies, and a web browser. mini_chromium is small, self-contained, and a library. mini_chromium is especially useful as a dependency of other code that wishes to use Chromium’s base routines. diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn new file mode 100644 index 00000000..0723ba3e --- /dev/null +++ b/third_party/zlib/BUILD.gn @@ -0,0 +1,121 @@ +# 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. + +import("../../build/crashpad_buildconfig.gni") + +if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) { + zlib_source = "external" +} else if (!crashpad_is_win && !crashpad_is_fuchsia) { + zlib_source = "system" +} else { + zlib_source = "embedded" +} + +config("zlib_config") { + if (zlib_source == "external") { + defines = [ "CRASHPAD_ZLIB_SOURCE_EXTERNAL" ] + } else if (zlib_source == "system") { + defines = [ "CRASHPAD_ZLIB_SOURCE_SYSTEM" ] + } else if (zlib_source == "embedded") { + defines = [ "CRASHPAD_ZLIB_SOURCE_EMBEDDED" ] + include_dirs = [ "zlib" ] + } +} + +if (zlib_source == "external") { + group("zlib") { + public_configs = [ ":zlib_config" ] + public_deps = [ + "//third_party/zlib", + ] + } +} else if (zlib_source == "system") { + source_set("zlib") { + public_configs = [ ":zlib_config" ] + libs = [ "z" ] + } +} else if (zlib_source == "embedded") { + static_library("zlib") { + sources = [ + "zlib/adler32.c", + "zlib/compress.c", + "zlib/crc32.c", + "zlib/crc32.h", + "zlib/deflate.c", + "zlib/deflate.h", + "zlib/gzclose.c", + "zlib/gzguts.h", + "zlib/gzlib.c", + "zlib/gzread.c", + "zlib/gzwrite.c", + "zlib/infback.c", + "zlib/inffast.c", + "zlib/inffast.h", + "zlib/inffixed.h", + "zlib/inflate.c", + "zlib/inflate.h", + "zlib/inftrees.c", + "zlib/inftrees.h", + "zlib/names.h", + "zlib/trees.c", + "zlib/trees.h", + "zlib/uncompr.c", + "zlib/zconf.h", + "zlib/zlib.h", + "zlib/zlib_crashpad.h", + "zlib/zutil.c", + "zlib/zutil.h", + ] + + cflags = [] + defines = [ "HAVE_STDARG_H" ] + public_configs = [ ":zlib_config" ] + + if (crashpad_is_win) { + cflags += [ + "/wd4131", # uses old-style declarator + "/wd4244", # conversion from 't1' to 't2', possible loss of data + "/wd4245", # conversion from 't1' to 't2', signed/unsigned mismatch + "/wd4267", # conversion from 'size_t' to 't', possible loss of data + "/wd4324", # structure was padded due to alignment specifier + "/wd4702", # unreachable code + ] + } else { + defines += [ + "HAVE_HIDDEN", + "HAVE_UNISTD_H", + ] + } + + if (current_cpu == "x86" || current_cpu == "x64") { + sources += [ + "zlib/crc_folding.c", + "zlib/fill_window_sse.c", + "zlib/x86.c", + "zlib/x86.h", + ] + if (!crashpad_is_win || crashpad_is_clang) { + cflags += [ + "-msse4.2", + "-mpclmul", + ] + } + if (crashpad_is_clang) { + cflags += [ "-Wno-incompatible-pointer-types" ] + } + } else { + sources += [ "zlib/simd_stub.c" ] + } + } +} diff --git a/third_party/zlib/README.crashpad b/third_party/zlib/README.crashpad index 83f47e31..8a9533d3 100644 --- a/third_party/zlib/README.crashpad +++ b/third_party/zlib/README.crashpad @@ -1,6 +1,6 @@ Name: zlib Short Name: zlib -URL: http://zlib.net/ +URL: https://zlib.net/ Revision: See zlib/README.chromium License: zlib License File: zlib/LICENSE diff --git a/third_party/zlib/zlib.gyp b/third_party/zlib/zlib.gyp index b6a6bc91..df26cc23 100644 --- a/third_party/zlib/zlib.gyp +++ b/third_party/zlib/zlib.gyp @@ -13,18 +13,28 @@ # limitations under the License. { - 'variables': { - 'conditions': [ - # Use the system zlib by default where available, as it is on most - # platforms. Windows does not have a system zlib, so use “embedded” which - # directs the build to use the source code in the zlib subdirectory. - ['OS!="win"', { - 'zlib_source%': 'system', - }, { - 'zlib_source%': 'embedded', - }], - ], - }, + 'includes': [ + '../../build/crashpad_dependencies.gypi', + ], + 'conditions': [ + ['1==1', { # Defer processing until crashpad_dependencies is set + 'variables': { + 'conditions': [ + ['crashpad_dependencies=="external"', { + 'zlib_source%': 'external', + }, 'OS!="win"', { + # Use the system zlib by default where available, as it is on most + # platforms. Windows does not have a system zlib, so use “embedded” + # which directs the build to use the source code in the zlib + # subdirectory. + 'zlib_source%': 'system', + }, { + 'zlib_source%': 'embedded', + }], + ], + }, + }], + ], 'targets': [ { 'target_name': 'zlib', @@ -141,6 +151,17 @@ }], ], }], + ['zlib_source=="external"', { + 'type': 'none', + 'direct_dependent_settings': { + 'defines': [ + 'CRASHPAD_ZLIB_SOURCE_EXTERNAL', + ], + }, + 'dependencies': [ + '../../../../zlib/zlib.gyp:zlib', + ], + }], ], }, ], diff --git a/third_party/zlib/zlib_crashpad.h b/third_party/zlib/zlib_crashpad.h index 2ab542e0..fd497a85 100644 --- a/third_party/zlib/zlib_crashpad.h +++ b/third_party/zlib/zlib_crashpad.h @@ -19,12 +19,11 @@ // available at any other location in the source tree. It will #include the // proper depending on how the build has been configured. -#if defined(CRASHPAD_ZLIB_SOURCE_SYSTEM) +#if defined(CRASHPAD_ZLIB_SOURCE_SYSTEM) || \ + defined(CRASHPAD_ZLIB_SOURCE_EXTERNAL) #include #elif defined(CRASHPAD_ZLIB_SOURCE_EMBEDDED) #include "third_party/zlib/zlib/zlib.h" -#elif defined(CRASHPAD_ZLIB_SOURCE_CHROMIUM) -#include "third_party/zlib/zlib.h" #else #error Unknown zlib source #endif diff --git a/tools/BUILD.gn b/tools/BUILD.gn new file mode 100644 index 00000000..cd1e95f4 --- /dev/null +++ b/tools/BUILD.gn @@ -0,0 +1,166 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +source_set("tool_support") { + sources = [ + "tool_support.cc", + "tool_support.h", + ] + + public_configs = [ "..:crashpad_config" ] + + deps = [ + "../third_party/mini_chromium:base", + ] +} + +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), + ] + } + + 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", + ] + + deps = [ + ":tool_support", + "../client", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } +} + +if (crashpad_is_mac) { + crashpad_executable("catch_exception_tool") { + sources = [ + "mac/catch_exception_tool.cc", + ] + + deps = [ + ":tool_support", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } + + crashpad_executable("exception_port_tool") { + 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", + ] + ldflags = [ + "-sectcreate", + "__TEXT", + "__info_plist", + rebase_path(inputs[0], root_build_dir), + ] + + deps = [ + ":tool_support", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } + + crashpad_executable("on_demand_service_tool") { + sources = [ + "mac/on_demand_service_tool.mm", + ] + + libs = [ + "CoreFoundation.framework", + "Foundation.framework", + ] + + deps = [ + ":tool_support", + "../compat", + "../third_party/mini_chromium:base", + "../util", + ] + } +} diff --git a/tools/crashpad_database_util.cc b/tools/crashpad_database_util.cc index cc21698b..b4c2a15b 100644 --- a/tools/crashpad_database_util.cc +++ b/tools/crashpad_database_util.cc @@ -584,16 +584,13 @@ int DatabaseUtilMain(int argc, char* argv[]) { file_reader = std::move(file_path_reader); } - CrashReportDatabase::NewReport* new_report; + std::unique_ptr new_report; CrashReportDatabase::OperationStatus status = database->PrepareNewCrashReport(&new_report); if (status != CrashReportDatabase::kNoError) { return EXIT_FAILURE; } - CrashReportDatabase::CallErrorWritingCrashReport - call_error_writing_crash_report(database.get(), new_report); - char buf[4096]; FileOperationResult read_result; do { @@ -601,16 +598,13 @@ int DatabaseUtilMain(int argc, char* argv[]) { if (read_result < 0) { return EXIT_FAILURE; } - if (read_result > 0 && - !LoggingWriteFile(new_report->handle, buf, read_result)) { + if (read_result > 0 && !new_report->Writer()->Write(buf, read_result)) { return EXIT_FAILURE; } } while (read_result > 0); - call_error_writing_crash_report.Disarm(); - UUID uuid; - status = database->FinishedWritingCrashReport(new_report, &uuid); + status = database->FinishedWritingCrashReport(std::move(new_report), &uuid); if (status != CrashReportDatabase::kNoError) { return EXIT_FAILURE; } diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index 956af81b..2cdaed27 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -29,9 +29,12 @@ #include "util/posix/drop_privileges.h" #include "util/stdlib/string_number_conversion.h" +#if defined(OS_POSIX) +#include +#endif + #if defined(OS_MACOSX) #include -#include #include "base/mac/scoped_mach_port.h" #include "snapshot/mac/process_snapshot_mac.h" @@ -42,6 +45,13 @@ #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 namespace crashpad { @@ -154,6 +164,12 @@ int GenerateDumpMain(int argc, char* argv[]) { 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()) { @@ -171,6 +187,11 @@ int GenerateDumpMain(int argc, char* argv[]) { if (options.suspend) { suspend.reset(new ScopedProcessSuspend(process.get())); } +#elif defined(OS_FUCHSIA) + std::unique_ptr suspend; + if (options.suspend) { + suspend.reset(new ScopedTaskSuspend(task.get())); + } #endif // OS_MACOSX #if defined(OS_MACOSX) @@ -188,6 +209,17 @@ int GenerateDumpMain(int argc, char* argv[]) { 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; + if (!process_snapshot.Initialize(nullptr)) { + return EXIT_FAILURE; + } #endif // OS_MACOSX FileWriter file_writer; diff --git a/tools/mac/run_with_crashpad.cc b/tools/run_with_crashpad.cc similarity index 80% rename from tools/mac/run_with_crashpad.cc rename to tools/run_with_crashpad.cc index 39095145..5ef0edb6 100644 --- a/tools/mac/run_with_crashpad.cc +++ b/tools/run_with_crashpad.cc @@ -25,11 +25,20 @@ #include "base/files/file_path.h" #include "base/logging.h" +#include "build/build_config.h" #include "client/crashpad_client.h" #include "tools/tool_support.h" #include "util/stdlib/map_insert.h" #include "util/string/split_string.h" +#if defined(OS_FUCHSIA) +#include +#include +#include + +#include "base/fuchsia/fuchsia_logging.h" +#endif + namespace crashpad { namespace { @@ -38,6 +47,13 @@ void Usage(const std::string& me) { "Usage: %s [OPTION]... COMMAND [ARG]...\n" "Start a Crashpad handler and have it handle crashes from COMMAND.\n" "\n" +#if defined(OS_FUCHSIA) +"COMMAND is run via fdio_spawn, so must be a qualified path to the subprocess to\n" +"be executed.\n" +#else +"COMMAND is run via execvp() so the PATH will be searched.\n" +#endif +"\n" " -h, --handler=HANDLER invoke HANDLER instead of crashpad_handler\n" " --annotation=KEY=VALUE passed to the handler as an --annotation argument\n" " --database=PATH passed to the handler as its --database argument\n" @@ -173,11 +189,44 @@ int RunWithCrashpadMain(int argc, char* argv[]) { return kExitFailure; } +#if defined(OS_FUCHSIA) + // Fuchsia doesn't implement execvp(), launch with fdio_spawn here. + zx_handle_t child = ZX_HANDLE_INVALID; + zx_status_t status = fdio_spawn( + ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv, &child); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "fdio_spawn failed"; + return status == ZX_ERR_IO ? kExitExecENOENT : kExitExecFailure; + } + + zx_signals_t observed; + status = zx_object_wait_one( + child, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &observed); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_wait_one"; + return kExitExecFailure; + } + if (!(observed & ZX_TASK_TERMINATED)) { + LOG(ERROR) << "did not observe ZX_TASK_TERMINATED"; + return kExitExecFailure; + } + + zx_info_process_t proc_info; + status = zx_object_get_info( + child, ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr, nullptr); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info"; + return kExitExecFailure; + } + + return proc_info.return_code; +#else // Using the remaining arguments, start a new program with the new exception // port in effect. execvp(argv[0], argv); PLOG(ERROR) << "execvp " << argv[0]; return errno == ENOENT ? kExitExecENOENT : kExitExecFailure; +#endif } } // namespace diff --git a/tools/mac/run_with_crashpad.md b/tools/run_with_crashpad.md similarity index 93% rename from tools/mac/run_with_crashpad.md rename to tools/run_with_crashpad.md index c87cb150..9758f9af 100644 --- a/tools/mac/run_with_crashpad.md +++ b/tools/run_with_crashpad.md @@ -32,10 +32,10 @@ setting an exception port referencing the handler. Then, executes _COMMAND_ along with any arguments specified (_ARG…_) with the new exception port in effect. -The exception port is configured to receive exceptions of type `EXC_CRASH`, -`EXC_RESOURCE`, and `EXC_GUARD`. The exception behavior is configured as -`EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The thread state flavor is -set to `MACHINE_THREAD_STATE`. +On macOS, the exception port is configured to receive exceptions of type +`EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD`. The exception behavior is +configured as `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The thread +state flavor is set to `MACHINE_THREAD_STATE`. Programs that use the Crashpad client library directly will not normally use this tool. This tool exists to allow programs that are unaware of Crashpad to be diff --git a/tools/tools.gyp b/tools/tools.gyp index 3639b531..d2ab29aa 100644 --- a/tools/tools.gyp +++ b/tools/tools.gyp @@ -200,7 +200,7 @@ '..', ], 'sources': [ - 'mac/run_with_crashpad.cc', + 'run_with_crashpad.cc', ], }, ], diff --git a/util/BUILD.gn b/util/BUILD.gn new file mode 100644 index 00000000..03220c9e --- /dev/null +++ b/util/BUILD.gn @@ -0,0 +1,696 @@ +# 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. + +import("../build/crashpad_buildconfig.gni") + +declare_args() { + use_boringssl_for_http_transport_socket = crashpad_is_fuchsia +} + +if (crashpad_is_mac) { + if (crashpad_is_in_chromium) { + import("//build/config/sysroot.gni") + } else { + import("//third_party/mini_chromium/mini_chromium/build/sysroot.gni") + } + + 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", + ] + + outputs = [ + "$target_gen_dir/mach/{{source_name_part}}User.c", + "$target_gen_dir/mach/{{source_name_part}}Server.c", + "$target_gen_dir/mach/{{source_name_part}}.h", + "$target_gen_dir/mach/{{source_name_part}}Server.h", + ] + + args = [ "{{source}}" ] + args += rebase_path(outputs, root_build_dir) + if (crashpad_is_in_chromium) { + if (!use_system_xcode) { + args += [ + "--developer-dir", + hermetic_xcode_path, + ] + } + } + if (sysroot != "") { + args += [ + "--sdk", + sysroot, + ] + } + args += [ + "--include", + rebase_path("../compat/mac", root_build_dir), + ] + } +} + +static_library("util") { + sources = [ + "file/delimited_file_reader.cc", + "file/delimited_file_reader.h", + "file/directory_reader.h", + "file/file_io.cc", + "file/file_io.h", + "file/file_reader.cc", + "file/file_reader.h", + "file/file_seeker.cc", + "file/file_seeker.h", + "file/file_writer.cc", + "file/file_writer.h", + "file/filesystem.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/as_underlying_type.h", + "misc/capture_context.h", + "misc/clock.h", + "misc/elf_note_types.h", + "misc/from_pointer_cast.h", + "misc/implicit_cast.h", + "misc/initialization_state.h", + "misc/initialization_state_dcheck.cc", + "misc/initialization_state_dcheck.h", + "misc/lexing.cc", + "misc/lexing.h", + "misc/metrics.cc", + "misc/metrics.h", + "misc/paths.h", + "misc/pdb_structures.cc", + "misc/pdb_structures.h", + "misc/random_string.cc", + "misc/random_string.h", + "misc/range_set.cc", + "misc/range_set.h", + "misc/reinterpret_bytes.cc", + "misc/reinterpret_bytes.h", + "misc/scoped_forbid_return.cc", + "misc/scoped_forbid_return.h", + "misc/symbolic_constants_common.h", + "misc/time.cc", + "misc/time.h", + "misc/tri_state.h", + "misc/uuid.cc", + "misc/uuid.h", + "misc/zlib.cc", + "misc/zlib.h", + "net/http_body.cc", + "net/http_body.h", + "net/http_body_gzip.cc", + "net/http_body_gzip.h", + "net/http_headers.h", + "net/http_multipart_builder.cc", + "net/http_multipart_builder.h", + "net/http_transport.cc", + "net/http_transport.h", + "net/url.cc", + "net/url.h", + "numeric/checked_address_range.cc", + "numeric/checked_address_range.h", + "numeric/checked_range.h", + "numeric/checked_vm_address_range.h", + "numeric/in_range_cast.h", + "numeric/int128.h", + "numeric/safe_assignment.h", + "stdlib/aligned_allocator.cc", + "stdlib/aligned_allocator.h", + "stdlib/map_insert.h", + "stdlib/objc.h", + "stdlib/string_number_conversion.cc", + "stdlib/string_number_conversion.h", + "stdlib/strlcpy.cc", + "stdlib/strlcpy.h", + "stdlib/strnlen.cc", + "stdlib/strnlen.h", + "stdlib/thread_safe_vector.h", + "string/split_string.cc", + "string/split_string.h", + "synchronization/semaphore.h", + "thread/stoppable.h", + "thread/thread.cc", + "thread/thread.h", + "thread/thread_log_messages.cc", + "thread/thread_log_messages.h", + "thread/worker_thread.cc", + "thread/worker_thread.h", + ] + + if (crashpad_is_posix || crashpad_is_fuchsia) { + sources += [ + "file/directory_reader_posix.cc", + "file/file_io_posix.cc", + "file/filesystem_posix.cc", + "misc/clock_posix.cc", + "posix/close_stdio.cc", + "posix/close_stdio.h", + "posix/scoped_dir.cc", + "posix/scoped_dir.h", + "posix/scoped_mmap.cc", + "posix/scoped_mmap.h", + "posix/signals.cc", + "posix/signals.h", + "synchronization/semaphore_posix.cc", + "thread/thread_posix.cc", + ] + + if (!crashpad_is_fuchsia) { + sources += [ + "posix/close_multiple.cc", + "posix/close_multiple.h", + "posix/double_fork_and_exec.cc", + "posix/double_fork_and_exec.h", + "posix/drop_privileges.cc", + "posix/drop_privileges.h", + "posix/process_info.h", + + # These map signals to and from strings. While Fuchsia defines some of + # the common SIGx defines, signals are never raised on Fuchsia, so + # there's need to include this mapping code. + "posix/symbolic_constants_posix.cc", + "posix/symbolic_constants_posix.h", + ] + } + } + + 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", + "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", + "mach/exc_client_variants.h", + "mach/exc_server_variants.cc", + "mach/exc_server_variants.h", + "mach/exception_behaviors.cc", + "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", + "mach/notify_server.cc", + "mach/notify_server.h", + "mach/scoped_task_suspend.cc", + "mach/scoped_task_suspend.h", + "mach/symbolic_constants_mach.cc", + "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", + ] + sources += get_target_outputs(":mig") + } + + deps = [] + + if (crashpad_is_linux || crashpad_is_fuchsia) { + sources += [ "net/http_transport_socket.cc" ] + if (use_boringssl_for_http_transport_socket) { + defines = [ "CRASHPAD_USE_BORINGSSL" ] + + if (crashpad_is_in_fuchsia) { + deps += [ "//third_party/boringssl" ] + } else { + libs = [ + "crypto", + "ssl", + ] + } + } + } else if (crashpad_is_android) { + sources += [ "net/http_transport_none.cc" ] + } + + if (crashpad_is_linux || crashpad_is_android) { + set_sources_assignment_filter([]) + sources += [ + "linux/address_types.h", + "linux/auxiliary_vector.cc", + "linux/auxiliary_vector.h", + "linux/checked_linux_address_range.h", + "linux/direct_ptrace_connection.cc", + "linux/direct_ptrace_connection.h", + "linux/exception_handler_client.cc", + "linux/exception_handler_client.h", + "linux/exception_handler_protocol.cc", + "linux/exception_handler_protocol.h", + "linux/exception_information.h", + "linux/memory_map.cc", + "linux/memory_map.h", + "linux/proc_stat_reader.cc", + "linux/proc_stat_reader.h", + "linux/ptrace_broker.cc", + "linux/ptrace_broker.h", + "linux/ptrace_client.cc", + "linux/ptrace_client.h", + "linux/ptrace_connection.h", + "linux/ptracer.cc", + "linux/ptracer.h", + "linux/scoped_pr_set_ptracer.cc", + "linux/scoped_pr_set_ptracer.h", + "linux/scoped_ptrace_attach.cc", + "linux/scoped_ptrace_attach.h", + "linux/thread_info.cc", + "linux/thread_info.h", + "linux/traits.h", + "misc/capture_context_linux.S", + "misc/paths_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", + ] + } + + if (crashpad_is_win) { + sources += [ + "file/directory_reader_win.cc", + "file/file_io_win.cc", + "file/filesystem_win.cc", + "misc/clock_win.cc", + "misc/paths_win.cc", + "misc/time_win.cc", + "net/http_transport_win.cc", + "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/critical_section_with_debug_info.cc", + "win/critical_section_with_debug_info.h", + "win/exception_handler_server.cc", + "win/exception_handler_server.h", + "win/get_function.cc", + "win/get_function.h", + "win/get_module_information.cc", + "win/get_module_information.h", + "win/handle.cc", + "win/handle.h", + "win/initial_client_data.cc", + "win/initial_client_data.h", + "win/module_version.cc", + "win/module_version.h", + "win/nt_internals.cc", + "win/nt_internals.h", + "win/ntstatus_logging.cc", + "win/ntstatus_logging.h", + "win/process_info.cc", + "win/process_info.h", + "win/process_structs.h", + "win/registration_protocol_win.cc", + "win/registration_protocol_win.h", + "win/safe_terminate_process.h", + "win/scoped_handle.cc", + "win/scoped_handle.h", + "win/scoped_local_alloc.cc", + "win/scoped_local_alloc.h", + "win/scoped_process_suspend.cc", + "win/scoped_process_suspend.h", + "win/scoped_set_event.cc", + "win/scoped_set_event.h", + "win/session_end_watcher.cc", + "win/session_end_watcher.h", + "win/termination_codes.h", + "win/xp_compat.h", + ] + + # There's no ml.exe yet in cross builds, so provide broken-but-not-asm + # versions of the functions defined in .asm files. + # + # CaptureContext() in capture_context_broken.cc just calls CHECK(false). + # SafeTerminateProcess() in safe_terminate_process.cc just calls regular + # TerminateProcess() without the protection against broken third-party + # patching of TerminateProcess(). + # + # 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", + ] + } else { + sources += [ + "misc/capture_context_broken.cc", + "win/safe_terminate_process_broken.cc", + ] + } + } + + if (crashpad_is_fuchsia) { + sources += [ + "fuchsia/koid_utilities.cc", + "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", + "process/process_memory_fuchsia.h", + ] + } + + public_configs = [ "..:crashpad_config" ] + + # Include generated files starting with "util". + include_dirs = [ "$root_gen_dir/third_party/crashpad/crashpad" ] + + public_deps = [ + "../compat", + ] + + deps += [ + "../third_party/mini_chromium:base", + "../third_party/zlib", + ] + + if (crashpad_is_mac) { + libs = [ + "bsm", + "CoreFoundation.framework", + "Foundation.framework", + "IOKit.framework", + ] + deps += [ ":mig" ] + include_dirs += [ "$root_build_dir/gen" ] + } + + if (crashpad_is_win) { + libs = [ + "user32.lib", + + # TODO(jperaza): version.lib is needed for Windows 7 compatibility. + # mincore.lib may be linked against instead when targeting Windows 8+. + "version.lib", + + "winhttp.lib", + ] + + cflags = [ "/wd4201" ] # nonstandard extension used: nameless struct/union. + + if (current_cpu == "x86") { + 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_android) { + crashpad_executable("http_transport_test_server") { + testonly = true + sources = [ + "net/http_transport_test_server.cc", + ] + + deps = [ + ":util", + "../third_party/cpp-httplib", + "../third_party/mini_chromium:base", + "../third_party/zlib", + "../tools:tool_support", + ] + + if (crashpad_is_standalone) { + remove_configs = [ "//third_party/mini_chromium/mini_chromium/build:Wexit_time_destructors" ] + } + + if (crashpad_is_win) { + libs = [ "ws2_32.lib" ] + } + + if (use_boringssl_for_http_transport_socket) { + data_deps = [ + ":generate_test_server_key", + ] + defines = [ "CRASHPAD_USE_BORINGSSL" ] + + if (crashpad_is_in_fuchsia) { + deps += [ "//third_party/boringssl" ] + } else { + libs = [ + "crypto", + "ssl", + ] + } + } + } +} + +source_set("util_test") { + testonly = true + + sources = [ + "file/delimited_file_reader_test.cc", + "file/directory_reader_test.cc", + "file/file_io_test.cc", + "file/file_reader_test.cc", + "file/filesystem_test.cc", + "file/string_file_test.cc", + "misc/arraysize_unsafe_test.cc", + "misc/capture_context_test.cc", + "misc/capture_context_test_util.h", + "misc/clock_test.cc", + "misc/from_pointer_cast_test.cc", + "misc/initialization_state_dcheck_test.cc", + "misc/initialization_state_test.cc", + "misc/paths_test.cc", + "misc/random_string_test.cc", + "misc/range_set_test.cc", + "misc/reinterpret_bytes_test.cc", + "misc/scoped_forbid_return_test.cc", + "misc/time_test.cc", + "misc/uuid_test.cc", + "net/http_body_gzip_test.cc", + "net/http_body_test.cc", + "net/http_body_test_util.cc", + "net/http_body_test_util.h", + "net/http_multipart_builder_test.cc", + "net/url_test.cc", + "numeric/checked_address_range_test.cc", + "numeric/checked_range_test.cc", + "numeric/in_range_cast_test.cc", + "numeric/int128_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", + "string/split_string_test.cc", + "synchronization/semaphore_test.cc", + "thread/thread_log_messages_test.cc", + "thread/thread_test.cc", + "thread/worker_thread_test.cc", + ] + + if (!crashpad_is_android) { + # Android requires an HTTPTransport implementation. + sources += [ "net/http_transport_test.cc" ] + } + + if (crashpad_is_posix || crashpad_is_fuchsia) { + if (!crashpad_is_fuchsia) { + sources += [ + "posix/process_info_test.cc", + "posix/signals_test.cc", + "posix/symbolic_constants_posix_test.cc", + ] + } + sources += [ "posix/scoped_mmap_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/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", + ] + } + + 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/ptrace_broker_test.cc", + "linux/ptracer_test.cc", + "linux/scoped_ptrace_attach_test.cc", + "misc/capture_context_test_util_linux.cc", + ] + } + + if (crashpad_is_fuchsia) { + 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", + "win/command_line_test.cc", + "win/critical_section_with_debug_info_test.cc", + "win/exception_handler_server_test.cc", + "win/get_function_test.cc", + "win/handle_test.cc", + "win/initial_client_data_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", + ] + } + + data = [ + "net/testdata/", + ] + + deps = [ + ":util", + "../client", + "../compat", + "../test", + "../third_party/gtest:gmock", + "../third_party/gtest:gtest", + "../third_party/mini_chromium:base", + "../third_party/zlib", + ] + + if (!crashpad_is_android) { + data_deps = [ + ":http_transport_test_server", + ] + + if (use_boringssl_for_http_transport_socket) { + defines = [ "CRASHPAD_USE_BORINGSSL" ] + } + } + + if (crashpad_is_mac) { + libs = [ "Foundation.framework" ] + } + + if (crashpad_is_win) { + libs = [ + "rpcrt4.lib", + "dbghelp.lib", + ] + data_deps += [ + ":crashpad_util_test_process_info_test_child", + ":crashpad_util_test_safe_terminate_process_test_child", + ] + } +} + +if (crashpad_is_win) { + crashpad_executable("crashpad_util_test_process_info_test_child") { + testonly = true + 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", + ] + } +} diff --git a/util/file/file_io.cc b/util/file/file_io.cc index f8a66304..55a6c5f1 100644 --- a/util/file/file_io.cc +++ b/util/file/file_io.cc @@ -156,16 +156,11 @@ void CheckedReadFileAtEOF(FileHandle file) { } } -bool LoggingReadEntireFile(const base::FilePath& path, std::string* contents) { - ScopedFileHandle handle(LoggingOpenFileForRead(path)); - if (!handle.is_valid()) { - return false; - } - +bool LoggingReadToEOF(FileHandle file, std::string* contents) { char buffer[4096]; FileOperationResult rv; std::string local_contents; - while ((rv = ReadFile(handle.get(), buffer, sizeof(buffer))) > 0) { + while ((rv = ReadFile(file, buffer, sizeof(buffer))) > 0) { DCHECK_LE(static_cast(rv), sizeof(buffer)); local_contents.append(buffer, rv); } @@ -177,6 +172,15 @@ bool LoggingReadEntireFile(const base::FilePath& path, std::string* contents) { return true; } +bool LoggingReadEntireFile(const base::FilePath& path, std::string* contents) { + ScopedFileHandle handle(LoggingOpenFileForRead(path)); + if (!handle.is_valid()) { + return false; + } + + return LoggingReadToEOF(handle.get(), contents); +} + void CheckedCloseFile(FileHandle file) { CHECK(LoggingCloseFile(file)); } diff --git a/util/file/file_io.h b/util/file/file_io.h index 044a0a69..797db682 100644 --- a/util/file/file_io.h +++ b/util/file/file_io.h @@ -318,7 +318,13 @@ void CheckedWriteFile(FileHandle file, const void* buffer, size_t size); //! \sa ReadFile void CheckedReadFileAtEOF(FileHandle file); -//! brief Wraps LoggingOpenFileForRead() and ReadFile() reading the entire file +//! \brief Wraps ReadFile() to read from the current file position to the end of +//! the file into \a contents. +//! +//! \return `true` on success, or `false` with a message logged. +bool LoggingReadToEOF(FileHandle file, std::string* contents); + +//! \brief Wraps LoggingOpenFileForRead() and ReadFile() reading the entire file //! into \a contents. //! //! \return `true` on success, or `false` with a message logged. @@ -404,6 +410,11 @@ FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path, FileWriteMode mode, FilePermissions permissions); +// Fuchsia does not currently support any sort of file locking. See +// https://crashpad.chromium.org/bug/196 and +// https://crashpad.chromium.org/bug/217. +#if !defined(OS_FUCHSIA) + //! \brief Locks the given \a file using `flock()` on POSIX or `LockFileEx()` on //! Windows. //! @@ -433,6 +444,8 @@ bool LoggingLockFile(FileHandle file, FileLocking locking); //! \return `true` on success, or `false` and a message will be logged. bool LoggingUnlockFile(FileHandle file); +#endif // !OS_FUCHSIA + //! \brief Wraps `lseek()` or `SetFilePointerEx()`. Logs an error if the //! operation fails. //! diff --git a/util/file/file_io_posix.cc b/util/file/file_io_posix.cc index 60a36996..f3116169 100644 --- a/util/file/file_io_posix.cc +++ b/util/file/file_io_posix.cc @@ -25,6 +25,7 @@ #include "base/files/file_path.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" namespace crashpad { @@ -66,7 +67,12 @@ FileHandle OpenFileForOutput(int rdwr_or_wronly, const base::FilePath& path, FileWriteMode mode, FilePermissions permissions) { +#if defined(OS_FUCHSIA) + // O_NOCTTY is invalid on Fuchsia, and O_CLOEXEC isn't necessary. + int flags = 0; +#else int flags = O_NOCTTY | O_CLOEXEC; +#endif DCHECK(rdwr_or_wronly & (O_RDWR | O_WRONLY)); DCHECK_EQ(rdwr_or_wronly & ~(O_RDWR | O_WRONLY), 0); @@ -109,8 +115,12 @@ FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) { } FileHandle OpenFileForRead(const base::FilePath& path) { - return HANDLE_EINTR( - open(path.value().c_str(), O_RDONLY | O_NOCTTY | O_CLOEXEC)); + int flags = O_RDONLY; +#if !defined(OS_FUCHSIA) + // O_NOCTTY is invalid on Fuchsia, and O_CLOEXEC isn't necessary. + flags |= O_NOCTTY | O_CLOEXEC; +#endif + return HANDLE_EINTR(open(path.value().c_str(), flags)); } FileHandle OpenFileForWrite(const base::FilePath& path, @@ -147,6 +157,8 @@ FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path, return fd; } +#if !defined(OS_FUCHSIA) + bool LoggingLockFile(FileHandle file, FileLocking locking) { int operation = (locking == FileLocking::kShared) ? LOCK_SH : LOCK_EX; int rv = HANDLE_EINTR(flock(file, operation)); @@ -160,6 +172,8 @@ bool LoggingUnlockFile(FileHandle file) { return rv == 0; } +#endif // !OS_FUCHSIA + FileOffset LoggingSeekFile(FileHandle file, FileOffset offset, int whence) { off_t rv = lseek(file, offset, whence); PLOG_IF(ERROR, rv < 0) << "lseek"; diff --git a/util/file/file_io_test.cc b/util/file/file_io_test.cc index fdcf7e9a..4446c320 100644 --- a/util/file/file_io_test.cc +++ b/util/file/file_io_test.cc @@ -523,6 +523,11 @@ TEST(FileIO, FileShareMode_Write_Write) { FileShareModeTest(ReadOrWrite::kWrite, ReadOrWrite::kWrite); } +// Fuchsia does not currently support any sort of file locking. See +// https://crashpad.chromium.org/bug/196 and +// https://crashpad.chromium.org/bug/217. +#if !defined(OS_FUCHSIA) + TEST(FileIO, MultipleSharedLocks) { ScopedTempDir temp_dir; base::FilePath shared_file = @@ -648,6 +653,8 @@ TEST(FileIO, SharedVsExclusives) { LockingTest(FileLocking::kShared, FileLocking::kExclusive); } +#endif // !OS_FUCHSIA + TEST(FileIO, FileSizeByHandle) { EXPECT_EQ(LoggingFileSizeByHandle(kInvalidFileHandle), -1); diff --git a/util/file/filesystem.h b/util/file/filesystem.h index acdd8d81..27a4d1de 100644 --- a/util/file/filesystem.h +++ b/util/file/filesystem.h @@ -15,12 +15,21 @@ #ifndef CRASHPAD_UTIL_FILE_FILESYSTEM_H_ #define CRASHPAD_UTIL_FILE_FILESYSTEM_H_ -#include "base/files/file_path.h" +#include +#include "base/files/file_path.h" #include "util/file/file_io.h" namespace crashpad { +//! \brief Determines the modification time for a file, directory, or symbolic +//! link, logging a message on failure. +//! +//! \param[in] path The file to get the modification time for. +//! \param[out] mtime The modification time as seconds since the POSIX Epoch. +//! \return `true` on success. `false` on failure with a message logged. +bool FileModificationTime(const base::FilePath& path, timespec* mtime); + //! \brief Creates a directory, logging a message on failure. //! //! \param[in] path The path to the directory to create. diff --git a/util/file/filesystem_posix.cc b/util/file/filesystem_posix.cc index a35db1cd..c2234dae 100644 --- a/util/file/filesystem_posix.cc +++ b/util/file/filesystem_posix.cc @@ -21,9 +21,29 @@ #include #include "base/logging.h" +#include "build/build_config.h" namespace crashpad { +bool FileModificationTime(const base::FilePath& path, timespec* mtime) { + struct stat st; + if (lstat(path.value().c_str(), &st) != 0) { + PLOG(ERROR) << "lstat " << path.value(); + return false; + } + +#if defined(OS_MACOSX) + *mtime = st.st_mtimespec; +#elif defined(OS_ANDROID) + // This is needed to compile with traditional NDK headers. + mtime->tv_sec = st.st_mtime; + mtime->tv_nsec = st.st_mtime_nsec; +#else + *mtime = st.st_mtim; +#endif + return true; +} + bool LoggingCreateDirectory(const base::FilePath& path, FilePermissions permissions, bool may_reuse) { @@ -45,6 +65,14 @@ bool LoggingCreateDirectory(const base::FilePath& path, 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(); diff --git a/util/file/filesystem_test.cc b/util/file/filesystem_test.cc index 7e28892e..3430c3f9 100644 --- a/util/file/filesystem_test.cc +++ b/util/file/filesystem_test.cc @@ -14,17 +14,133 @@ #include "util/file/filesystem.h" +#include + #include "base/logging.h" #include "build/build_config.h" #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" namespace crashpad { namespace test { namespace { +bool CurrentTime(timespec* now) { +#if defined(OS_MACOSX) + timeval now_tv; + int res = gettimeofday(&now_tv, nullptr); + if (res != 0) { + EXPECT_EQ(res, 0) << ErrnoMessage("gettimeofday"); + return false; + } + TimevalToTimespec(now_tv, now); + return true; +#elif defined(OS_POSIX) + int res = clock_gettime(CLOCK_REALTIME, now); + if (res != 0) { + EXPECT_EQ(res, 0) << ErrnoMessage("clock_gettime"); + return false; + } + return true; +#else + int res = timespec_get(now, TIME_UTC); + if (res != TIME_UTC) { + EXPECT_EQ(res, TIME_UTC); + return false; + } + return true; +#endif +} + +TEST(Filesystem, FileModificationTime) { + timespec expected_time_start, expected_time_end; + + ASSERT_TRUE(CurrentTime(&expected_time_start)); + ScopedTempDir temp_dir; + ASSERT_TRUE(CurrentTime(&expected_time_end)); + + timespec dir_mtime; + ASSERT_TRUE(FileModificationTime(temp_dir.path(), &dir_mtime)); + EXPECT_GE(dir_mtime.tv_sec, expected_time_start.tv_sec - 2); + EXPECT_LE(dir_mtime.tv_sec, expected_time_end.tv_sec + 2); + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + ASSERT_TRUE(CurrentTime(&expected_time_start)); + ASSERT_TRUE(CreateFile(file)); + ASSERT_TRUE(CurrentTime(&expected_time_end)); + + timespec file_mtime; + ASSERT_TRUE(FileModificationTime(file, &file_mtime)); + EXPECT_GE(file_mtime.tv_sec, expected_time_start.tv_sec - 2); + EXPECT_LE(file_mtime.tv_sec, expected_time_end.tv_sec + 2); + + timespec file_mtime_again; + ASSERT_TRUE(FileModificationTime(file, &file_mtime_again)); + EXPECT_EQ(file_mtime.tv_sec, file_mtime_again.tv_sec); + EXPECT_EQ(file_mtime.tv_nsec, file_mtime_again.tv_nsec); + + timespec mtime; + EXPECT_FALSE(FileModificationTime(base::FilePath(), &mtime)); + EXPECT_FALSE(FileModificationTime( + temp_dir.path().Append(FILE_PATH_LITERAL("notafile")), &mtime)); +} + +#if !defined(OS_FUCHSIA) + +TEST(Filesystem, FileModificationTime_SymbolicLinks) { + if (!CanCreateSymbolicLinks()) { + DISABLED_TEST(); + } + + ScopedTempDir temp_dir; + + const base::FilePath dir_link( + temp_dir.path().Append(FILE_PATH_LITERAL("dir_link"))); + + timespec expected_time_start, expected_time_end; + ASSERT_TRUE(CurrentTime(&expected_time_start)); + ASSERT_TRUE(CreateSymbolicLink(temp_dir.path(), dir_link)); + ASSERT_TRUE(CurrentTime(&expected_time_end)); + + timespec mtime, mtime2; + ASSERT_TRUE(FileModificationTime(temp_dir.path(), &mtime)); + mtime.tv_sec -= 100; + ASSERT_TRUE(SetFileModificationTime(temp_dir.path(), mtime)); + ASSERT_TRUE(FileModificationTime(temp_dir.path(), &mtime2)); + EXPECT_GE(mtime2.tv_sec, mtime.tv_sec - 2); + EXPECT_LE(mtime2.tv_sec, mtime.tv_sec + 2); + + ASSERT_TRUE(FileModificationTime(dir_link, &mtime)); + EXPECT_GE(mtime.tv_sec, expected_time_start.tv_sec - 2); + EXPECT_LE(mtime.tv_sec, expected_time_end.tv_sec + 2); + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + ASSERT_TRUE(CreateFile(file)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + + ASSERT_TRUE(CurrentTime(&expected_time_start)); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + ASSERT_TRUE(CurrentTime(&expected_time_end)); + + ASSERT_TRUE(FileModificationTime(file, &mtime)); + mtime.tv_sec -= 100; + ASSERT_TRUE(SetFileModificationTime(file, mtime)); + ASSERT_TRUE(FileModificationTime(file, &mtime2)); + EXPECT_GE(mtime2.tv_sec, mtime.tv_sec - 2); + EXPECT_LE(mtime2.tv_sec, mtime.tv_sec + 2); + + ASSERT_TRUE(FileModificationTime(link, &mtime)); + EXPECT_GE(mtime.tv_sec, expected_time_start.tv_sec - 2); + EXPECT_LE(mtime.tv_sec, expected_time_end.tv_sec + 2); +} + +#endif // !OS_FUCHSIA + TEST(Filesystem, CreateDirectory) { ScopedTempDir temp_dir; @@ -257,7 +373,6 @@ TEST(Filesystem, RemoveFile) { EXPECT_FALSE(LoggingRemoveFile(base::FilePath())); ScopedTempDir temp_dir; - EXPECT_FALSE(LoggingRemoveFile(temp_dir.path())); base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); EXPECT_FALSE(LoggingRemoveFile(file)); @@ -268,7 +383,6 @@ TEST(Filesystem, RemoveFile) { base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); ASSERT_TRUE( LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); - EXPECT_FALSE(LoggingRemoveFile(dir)); EXPECT_TRUE(LoggingRemoveFile(file)); EXPECT_FALSE(PathExists(file)); @@ -317,8 +431,16 @@ TEST(Filesystem, RemoveDirectory) { EXPECT_FALSE(LoggingRemoveDirectory(file)); ASSERT_TRUE(CreateFile(file)); +#if !defined(OS_FUCHSIA) + // The current implementation of Fuchsia's rmdir() simply calls unlink(), and + // unlink() works on all FS objects. This is incorrect as + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html says + // "The directory shall be removed only if it is an empty directory." and "If + // the directory is not an empty directory, rmdir() shall fail and set errno + // to [EEXIST] or [ENOTEMPTY]." Upstream bug: US-400. EXPECT_FALSE(LoggingRemoveDirectory(file)); EXPECT_FALSE(LoggingRemoveDirectory(dir)); +#endif ASSERT_TRUE(LoggingRemoveFile(file)); EXPECT_TRUE(LoggingRemoveDirectory(dir)); diff --git a/util/file/filesystem_win.cc b/util/file/filesystem_win.cc index b874ed8c..a465eacc 100644 --- a/util/file/filesystem_win.cc +++ b/util/file/filesystem_win.cc @@ -14,10 +14,12 @@ #include "util/file/filesystem.h" +#include #include #include "base/logging.h" #include "base/strings/utf_string_conversions.h" +#include "util/misc/time.h" namespace crashpad { @@ -50,6 +52,35 @@ bool LoggingRemoveDirectoryImpl(const base::FilePath& path) { } // namespace +bool FileModificationTime(const base::FilePath& path, timespec* mtime) { + DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT; + if (IsDirectory(path, true)) { + // required for directory handles + flags |= FILE_FLAG_BACKUP_SEMANTICS; + } + + ScopedFileHandle handle( + ::CreateFile(path.value().c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + flags, + nullptr)); + if (!handle.is_valid()) { + PLOG(ERROR) << "CreateFile " << base::UTF16ToUTF8(path.value()); + return false; + } + + FILETIME file_mtime; + if (!GetFileTime(handle.get(), nullptr, nullptr, &file_mtime)) { + PLOG(ERROR) << "GetFileTime " << base::UTF16ToUTF8(path.value()); + return false; + } + *mtime = FiletimeToTimespecEpoch(file_mtime); + return true; +} + bool LoggingCreateDirectory(const base::FilePath& path, FilePermissions permissions, bool may_reuse) { diff --git a/util/fuchsia/koid_utilities.cc b/util/fuchsia/koid_utilities.cc new file mode 100644 index 00000000..0d44f4af --- /dev/null +++ b/util/fuchsia/koid_utilities.cc @@ -0,0 +1,157 @@ +// 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/fuchsia/koid_utilities.h" + +#include + +#include + +#include "base/files/file_path.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "util/file/file_io.h" + +namespace crashpad { + +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(); + } + return base::ScopedZxHandle(root_job); +} + +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; + } + } + + // 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; +} + +} // namespace + +std::vector GetChildKoids(zx_handle_t parent, + zx_object_info_topic_t child_kind) { + size_t actual = 0; + size_t available = 0; + std::vector result(100); + + // 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); + // If the buffer is too small (even zero), the result is still ZX_OK, not + // ZX_ERR_BUFFER_TOO_SMALL. + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info"; + break; + } + + if (actual == available) { + break; + } + + // Resize to the expected number next time, with a bit of slop to handle the + // race between here and the next request. + result.resize(available + 10); + } + + result.resize(actual); + return result; +} + +std::vector GetChildHandles(zx_handle_t parent, + zx_object_info_topic_t type) { + auto koids = GetChildKoids(parent, type); + return GetHandlesForChildKoids(parent, koids); +} + +std::vector GetHandlesForChildKoids( + zx_handle_t parent, + const std::vector& koids) { + std::vector result; + result.reserve(koids.size()); + for (zx_koid_t koid : koids) { + result.emplace_back(GetChildHandleByKoid(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_koid_t GetKoidForHandle(zx_handle_t 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); + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_object_get_info"; + return ZX_HANDLE_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 new file mode 100644 index 00000000..5bcea105 --- /dev/null +++ b/util/fuchsia/koid_utilities.h @@ -0,0 +1,95 @@ +// 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_KOID_UTILITIES_H_ +#define CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_ + +#include +#include + +#include + +#include "base/fuchsia/scoped_zx_handle.h" + +namespace crashpad { + +//! \brief Get a list of child koids for a parent handle. +//! +//! For example, the list of processes in jobs, or the list of threads in a +//! process. +//! +//! \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 A vector of the koids representing the child objects. +//! +//! \sa GetChildHandles +std::vector GetChildKoids(zx_handle_t 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 GetChildHandles( + zx_handle_t parent, + zx_object_info_topic_t child_kind); + +//! \brief Convert a list of koids that are all children of a particular object +//! into 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 GetHandlesForChildKoids( + zx_handle_t parent, + const std::vector& koids); + +//! \brief Retrieve the child of a parent handle, 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); + +//! \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); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_ diff --git a/util/fuchsia/scoped_task_suspend.cc b/util/fuchsia/scoped_task_suspend.cc new file mode 100644 index 00000000..ca0fd564 --- /dev/null +++ b/util/fuchsia/scoped_task_suspend.cc @@ -0,0 +1,81 @@ +// 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/fuchsia/scoped_task_suspend.h" + +#include +#include +#include + +#include + +#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 { + +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(); + } + + 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); +} + +} // 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"; + } +} + +ScopedTaskSuspend::~ScopedTaskSuspend() = default; + +} // namespace crashpad diff --git a/util/fuchsia/scoped_task_suspend.h b/util/fuchsia/scoped_task_suspend.h new file mode 100644 index 00000000..f817364d --- /dev/null +++ b/util/fuchsia/scoped_task_suspend.h @@ -0,0 +1,55 @@ +// 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_SCOPED_TASK_SUSPEND_H_ +#define CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_ + +#include + +#include + +#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. +//! +//! 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(); + + private: + // Could be one (for a thread) or many (for every process in a thread). + std::vector suspend_tokens_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspend); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_ diff --git a/util/fuchsia/system_exception_port_key.h b/util/fuchsia/system_exception_port_key.h new file mode 100644 index 00000000..0bbae690 --- /dev/null +++ b/util/fuchsia/system_exception_port_key.h @@ -0,0 +1,27 @@ +// 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/linux/auxiliary_vector.cc b/util/linux/auxiliary_vector.cc index 143ab6e3..d3d5ebdf 100644 --- a/util/linux/auxiliary_vector.cc +++ b/util/linux/auxiliary_vector.cc @@ -20,6 +20,7 @@ #include "base/files/file_path.h" #include "base/logging.h" #include "util/file/file_reader.h" +#include "util/file/string_file.h" #include "util/stdlib/map_insert.h" namespace crashpad { @@ -28,18 +29,22 @@ AuxiliaryVector::AuxiliaryVector() : values_() {} AuxiliaryVector::~AuxiliaryVector() {} -bool AuxiliaryVector::Initialize(pid_t pid, bool is_64_bit) { - return is_64_bit ? Read(pid) : Read(pid); +bool AuxiliaryVector::Initialize(PtraceConnection* connection) { + return connection->Is64Bit() ? Read(connection) + : Read(connection); } template -bool AuxiliaryVector::Read(pid_t pid) { +bool AuxiliaryVector::Read(PtraceConnection* connection) { char path[32]; - snprintf(path, sizeof(path), "/proc/%d/auxv", pid); - FileReader aux_file; - if (!aux_file.Open(base::FilePath(path))) { + snprintf(path, sizeof(path), "/proc/%d/auxv", connection->GetProcessID()); + + std::string contents; + if (!connection->ReadFileContents(base::FilePath(path), &contents)) { return false; } + StringFile aux_file; + aux_file.SetString(contents); ULong type; ULong value; diff --git a/util/linux/auxiliary_vector.h b/util/linux/auxiliary_vector.h index 65d0e3cf..a6a44c48 100644 --- a/util/linux/auxiliary_vector.h +++ b/util/linux/auxiliary_vector.h @@ -21,6 +21,7 @@ #include "base/logging.h" #include "base/macros.h" +#include "util/linux/ptrace_connection.h" #include "util/misc/reinterpret_bytes.h" namespace crashpad { @@ -32,16 +33,15 @@ class AuxiliaryVector { ~AuxiliaryVector(); //! \brief Initializes this object with the auxiliary vector for the process - //! with process ID \a pid. + //! connected via \a connection. //! //! This method must be called successfully prior to calling any other method //! in this class. //! - //! \param[in] pid The process ID of a target process. - //! \param[in] is_64_bit Whether the target process is 64-bit. + //! \param[in] connection A connection to the target process. //! //! \return `true` on success, `false` on failure with a message logged. - bool Initialize(pid_t pid, bool is_64_bit); + bool Initialize(PtraceConnection* connection); //! \brief Retrieve a value from the vector. //! @@ -64,7 +64,7 @@ class AuxiliaryVector { private: template - bool Read(pid_t pid); + bool Read(PtraceConnection* connection); DISALLOW_COPY_AND_ASSIGN(AuxiliaryVector); }; diff --git a/util/linux/auxiliary_vector_test.cc b/util/linux/auxiliary_vector_test.cc index e40eb33a..d4cfcb74 100644 --- a/util/linux/auxiliary_vector_test.cc +++ b/util/linux/auxiliary_vector_test.cc @@ -20,9 +20,13 @@ #include +#include "base/bit_cast.h" #include "base/macros.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" +#include "test/linux/fake_ptrace_connection.h" +#include "test/main_arguments.h" #include "test/multiprocess.h" #include "util/linux/address_types.h" #include "util/linux/memory_map.h" @@ -30,25 +34,32 @@ #include "util/numeric/int128.h" #include "util/process/process_memory_linux.h" +#if !defined(OS_ANDROID) +// TODO(jperaza): This symbol isn't defined when building in chromium for +// Android. There may be another symbol to use. extern "C" { -extern void _start(); +#if defined(ARCH_CPU_MIPS_FAMILY) +#define START_SYMBOL __start +#else +#define START_SYMBOL _start +#endif +extern void START_SYMBOL(); } // extern "C" +#endif namespace crashpad { namespace test { namespace { void TestAgainstCloneOrSelf(pid_t pid) { -#if defined(ARCH_CPU_64_BITS) - constexpr bool am_64_bit = true; -#else - constexpr bool am_64_bit = false; -#endif + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(pid)); + AuxiliaryVector aux; - ASSERT_TRUE(aux.Initialize(pid, am_64_bit)); + ASSERT_TRUE(aux.Initialize(&connection)); MemoryMap mappings; - ASSERT_TRUE(mappings.Initialize(pid)); + ASSERT_TRUE(mappings.Initialize(&connection)); LinuxVMAddress phdrs; ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs)); @@ -62,9 +73,11 @@ void TestAgainstCloneOrSelf(pid_t pid) { ASSERT_TRUE(aux.GetValue(AT_BASE, &interp_base)); EXPECT_TRUE(mappings.FindMapping(interp_base)); +#if !defined(OS_ANDROID) LinuxVMAddress entry_addr; ASSERT_TRUE(aux.GetValue(AT_ENTRY, &entry_addr)); - EXPECT_EQ(entry_addr, FromPointerCast(_start)); + EXPECT_EQ(entry_addr, FromPointerCast(START_SYMBOL)); +#endif uid_t uid; ASSERT_TRUE(aux.GetValue(AT_UID, &uid)); @@ -116,7 +129,7 @@ void TestAgainstCloneOrSelf(pid_t pid) { ASSERT_TRUE(aux.GetValue(AT_EXECFN, &filename_addr)); std::string filename; ASSERT_TRUE(memory.ReadCStringSizeLimited(filename_addr, 4096, &filename)); - EXPECT_TRUE(filename.find("crashpad_util_test") != std::string::npos); + EXPECT_TRUE(filename.find(GetMainArguments()[0]) != std::string::npos); #endif // AT_EXECFN int ignore; @@ -160,13 +173,11 @@ class AuxVecTester : public AuxiliaryVector { }; TEST(AuxiliaryVector, SignedBit) { -#if defined(ARCH_CPU_64_BITS) - constexpr bool am_64_bit = true; -#else - constexpr bool am_64_bit = false; -#endif + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + AuxVecTester aux; - ASSERT_TRUE(aux.Initialize(getpid(), am_64_bit)); + ASSERT_TRUE(&connection); constexpr uint64_t type = 0x0000000012345678; constexpr int32_t neg1_32 = -1; diff --git a/util/linux/direct_ptrace_connection.cc b/util/linux/direct_ptrace_connection.cc index 78092bb9..a3df425f 100644 --- a/util/linux/direct_ptrace_connection.cc +++ b/util/linux/direct_ptrace_connection.cc @@ -16,13 +16,16 @@ #include +#include "util/file/file_io.h" + namespace crashpad { DirectPtraceConnection::DirectPtraceConnection() : PtraceConnection(), attachments_(), + memory_(), pid_(-1), - ptracer_(), + ptracer_(/* can_log= */ true), initialized_() {} DirectPtraceConnection::~DirectPtraceConnection() {} @@ -35,6 +38,10 @@ bool DirectPtraceConnection::Initialize(pid_t pid) { } pid_ = pid; + if (!memory_.Initialize(pid)) { + return false; + } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } @@ -63,4 +70,15 @@ bool DirectPtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) { return ptracer_.GetThreadInfo(tid, info); } +bool DirectPtraceConnection::ReadFileContents(const base::FilePath& path, + std::string* contents) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return LoggingReadEntireFile(path, contents); +} + +ProcessMemory* DirectPtraceConnection::Memory() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &memory_; +} + } // namespace crashpad diff --git a/util/linux/direct_ptrace_connection.h b/util/linux/direct_ptrace_connection.h index f1d7ab5f..53594ef9 100644 --- a/util/linux/direct_ptrace_connection.h +++ b/util/linux/direct_ptrace_connection.h @@ -25,6 +25,7 @@ #include "util/linux/ptracer.h" #include "util/linux/scoped_ptrace_attach.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory_linux.h" namespace crashpad { @@ -52,9 +53,13 @@ class DirectPtraceConnection : public PtraceConnection { bool Attach(pid_t tid) override; bool Is64Bit() override; bool GetThreadInfo(pid_t tid, ThreadInfo* info) override; + bool ReadFileContents(const base::FilePath& path, + std::string* contents) override; + ProcessMemory* Memory() override; private: std::vector> attachments_; + ProcessMemoryLinux memory_; pid_t pid_; Ptracer ptracer_; InitializationStateDcheck initialized_; diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc new file mode 100644 index 00000000..6333351c --- /dev/null +++ b/util/linux/exception_handler_client.cc @@ -0,0 +1,166 @@ +// 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 "util/linux/exception_handler_client.h" + +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "util/file/file_io.h" +#include "util/linux/ptrace_broker.h" +#include "util/posix/signals.h" + +namespace crashpad { + +ExceptionHandlerClient::ExceptionHandlerClient(int sock) + : server_sock_(sock), ptracer_(-1), can_set_ptracer_(true) {} + +ExceptionHandlerClient::~ExceptionHandlerClient() = default; + +int ExceptionHandlerClient::RequestCrashDump(const ClientInformation& info) { + int status = SendCrashDumpRequest(info); + if (status != 0) { + return status; + } + return WaitForCrashDumpComplete(); +} + +int ExceptionHandlerClient::SetPtracer(pid_t pid) { + if (ptracer_ == pid) { + return 0; + } + + if (!can_set_ptracer_) { + return EPERM; + } + + if (prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0) { + return 0; + } + return errno; +} + +void ExceptionHandlerClient::SetCanSetPtracer(bool can_set_ptracer) { + can_set_ptracer_ = can_set_ptracer; +} + +int ExceptionHandlerClient::SendCrashDumpRequest( + const ClientInformation& info) { + ClientToServerMessage message; + message.type = ClientToServerMessage::kCrashDumpRequest; + message.client_info = info; + + 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; + + 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(CMSG_DATA(cmsg)) = creds; + + if (HANDLE_EINTR(sendmsg(server_sock_, &msg, MSG_NOSIGNAL)) < 0) { + PLOG(ERROR) << "sendmsg"; + return errno; + } + + return 0; +} + +int ExceptionHandlerClient::WaitForCrashDumpComplete() { + 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: { + Signals::InstallDefaultHandler(SIGCHLD); + + pid_t pid = fork(); + if (pid <= 0) { + Errno error = pid < 0 ? errno : 0; + if (!WriteFile(server_sock_, &error, sizeof(error))) { + return errno; + } + } + + if (pid < 0) { + continue; + } + + if (pid == 0) { +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif // ARCH_CPU_64_BITS + + PtraceBroker broker(server_sock_, getppid(), am_64_bit); + _exit(broker.Run()); + } + + int status = 0; + pid_t child = HANDLE_EINTR(waitpid(pid, &status, 0)); + DCHECK_EQ(child, pid); + + if (child == pid && status != 0) { + return status; + } + continue; + } + + case ServerToClientMessage::kTypeSetPtracer: { + Errno result = SetPtracer(message.pid); + if (!WriteFile(server_sock_, &result, sizeof(result))) { + return errno; + } + continue; + } + + case ServerToClientMessage::kTypeCrashDumpComplete: + case ServerToClientMessage::kTypeCrashDumpFailed: + return 0; + } + + DCHECK(false); + } + + return errno; +} + +} // namespace crashpad diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h new file mode 100644 index 00000000..a60b0656 --- /dev/null +++ b/util/linux/exception_handler_client.h @@ -0,0 +1,68 @@ +// 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_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ +#define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ + +#include + +#include "base/macros.h" +#include "util/linux/exception_handler_protocol.h" + +namespace crashpad { + +//! A client for an ExceptionHandlerServer +class ExceptionHandlerClient { + public: + //! \brief Constructs this object. + //! + //! \param[in] sock A socket connected to an ExceptionHandlerServer. + explicit ExceptionHandlerClient(int sock); + + ~ExceptionHandlerClient(); + + //! \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); + + //! \brief Uses `prctl(PR_SET_PTRACER, ...)` to set the process with + //! process ID \a pid as the ptracer for this process. + //! + //! \param[in] pid The process ID of the process to be set as this process' + //! ptracer. + //! \return 0 on success or an error code on failure. + int SetPtracer(pid_t pid); + + //! \brief Enables or disables SetPtracer(). + //! \param[in] can_set_ptracer Whether SetPtracer should be enabled. + void SetCanSetPtracer(bool can_set_ptracer); + + private: + int SendCrashDumpRequest(const ClientInformation& info); + int WaitForCrashDumpComplete(); + + int server_sock_; + pid_t ptracer_; + bool can_set_ptracer_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerClient); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ diff --git a/util/linux/exception_handler_protocol.cc b/util/linux/exception_handler_protocol.cc new file mode 100644 index 00000000..3feca698 --- /dev/null +++ b/util/linux/exception_handler_protocol.cc @@ -0,0 +1,25 @@ +// 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/exception_handler_protocol.h" + +namespace crashpad { + +ClientInformation::ClientInformation() + : exception_information_address(0), sanitization_information_address(0) {} + +ClientToServerMessage::ClientToServerMessage() + : version(kVersion), type(kCrashDumpRequest), client_info() {} + +} // namespace crashpad diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h new file mode 100644 index 00000000..4ba1b5d0 --- /dev/null +++ b/util/linux/exception_handler_protocol.h @@ -0,0 +1,96 @@ +// 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_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ +#define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ + +#include +#include +#include + +#include "util/file/file_io.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +#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 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 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 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; + }; +}; + +//! \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 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; + + //! \brief The handler's process ID. Valid for kTypeSetPtracer. + pid_t pid; +}; + +#pragma pack(pop) + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ diff --git a/util/linux/exception_information.h b/util/linux/exception_information.h new file mode 100644 index 00000000..d7dbe529 --- /dev/null +++ b/util/linux/exception_information.h @@ -0,0 +1,45 @@ +// 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_UTIL_LINUX_EXCEPTION_INFORMATION_H_ +#define CRASHPAD_UTIL_LINUX_EXCEPTION_INFORMATION_H_ + +#include + +#include "util/linux/address_types.h" + +namespace crashpad { + +#pragma pack(push, 1) + +//! \brief Structure read out of the client process by the crash handler when an +//! exception occurs. +struct ExceptionInformation { + //! \brief The address of the `siginfo_t` passed to the signal handler in the + //! crashed process. + LinuxVMAddress siginfo_address; + + //! \brief The address of the `ucontext_t` passed to the signal handler in the + //! crashed process. + LinuxVMAddress context_address; + + //! \brief The thread ID of the thread which received the signal. + pid_t thread_id; +}; + +#pragma pack(pop) + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_EXCEPTION_INFORMATION_H_ diff --git a/util/linux/memory_map.cc b/util/linux/memory_map.cc index cb7bfe1e..a274790e 100644 --- a/util/linux/memory_map.cc +++ b/util/linux/memory_map.cc @@ -36,7 +36,7 @@ namespace { // 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 -bool LocalStringToNumber(const base::StringPiece& string, Type* number) { +bool LocalStringToNumber(const std::string& string, Type* number) { static_assert(sizeof(Type) == sizeof(int) || sizeof(Type) == sizeof(int64_t), "Unexpected Type size"); @@ -102,10 +102,20 @@ ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader, LOG(ERROR) << "format error"; return ParseResult::kError; } - if (end_address <= start_address) { + if (end_address < start_address) { LOG(ERROR) << "format error"; return ParseResult::kError; } + // Skip zero-length mappings. + if (end_address == start_address) { + std::string rest_of_line; + if (maps_file_reader->GetLine(&rest_of_line) != + DelimitedFileReader::Result::kSuccess) { + LOG(ERROR) << "format error"; + return ParseResult::kError; + } + return ParseResult::kSuccess; + } // TODO(jperaza): set bitness properly #if defined(ARCH_CPU_64_BITS) @@ -221,7 +231,7 @@ bool MemoryMap::Mapping::Equals(const Mapping& other) const { shareable == other.shareable; } -bool MemoryMap::Initialize(pid_t pid) { +bool MemoryMap::Initialize(PtraceConnection* connection) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); // If the maps file is not read atomically, entries can be read multiple times @@ -235,8 +245,8 @@ bool MemoryMap::Initialize(pid_t pid) { do { std::string contents; char path[32]; - snprintf(path, sizeof(path), "/proc/%d/maps", pid); - if (!LoggingReadEntireFile(base::FilePath(path), &contents)) { + snprintf(path, sizeof(path), "/proc/%d/maps", connection->GetProcessID()); + if (!connection->ReadFileContents(base::FilePath(path), &contents)) { return false; } diff --git a/util/linux/memory_map.h b/util/linux/memory_map.h index 6e5b40c8..c8276ba6 100644 --- a/util/linux/memory_map.h +++ b/util/linux/memory_map.h @@ -22,6 +22,7 @@ #include "util/linux/address_types.h" #include "util/linux/checked_linux_address_range.h" +#include "util/linux/ptrace_connection.h" #include "util/misc/initialization_state_dcheck.h" namespace crashpad { @@ -54,15 +55,15 @@ class MemoryMap { ~MemoryMap(); //! \brief Initializes this object with information about the mapped memory - //! regions in the process whose ID is \a pid. + //! regions in the process connected via \a connection. //! //! This method must be called successfully prior to calling any other method //! in this class. This method may only be called once. //! - //! \param[in] pid The process ID to obtain information for. + //! \param[in] connection A connection to the process create a map for. //! //! \return `true` on success, `false` on failure with a message logged. - bool Initialize(pid_t pid); + bool Initialize(PtraceConnection* connection); //! \return The Mapping containing \a address or `nullptr` if no match is //! found. The caller does not take ownership of this object. It is scoped diff --git a/util/linux/memory_map_test.cc b/util/linux/memory_map_test.cc index 8578a7e5..a056589a 100644 --- a/util/linux/memory_map_test.cc +++ b/util/linux/memory_map_test.cc @@ -26,10 +26,11 @@ #include "gtest/gtest.h" #include "test/errors.h" #include "test/file.h" +#include "test/linux/fake_ptrace_connection.h" #include "test/multiprocess.h" #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" -#include "util/linux/scoped_ptrace_attach.h" +#include "util/linux/direct_ptrace_connection.h" #include "util/misc/clock.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/scoped_mmap.h" @@ -46,8 +47,12 @@ TEST(MemoryMap, SelfBasic) { MAP_SHARED | MAP_ANON, -1, 0)); + + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; - ASSERT_TRUE(map.Initialize(getpid())); + ASSERT_TRUE(map.Initialize(&connection)); auto stack_address = FromPointerCast(&map); const MemoryMap::Mapping* mapping = map.FindMapping(stack_address); @@ -118,11 +123,11 @@ class MapChildTest : public Multiprocess { std::string mapped_file_name(path_length, std::string::value_type()); CheckedReadFileExactly(ReadPipeHandle(), &mapped_file_name[0], path_length); - ScopedPtraceAttach attachment; - attachment.ResetAttach(ChildPID()); + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildPID())); MemoryMap map; - ASSERT_TRUE(map.Initialize(ChildPID())); + ASSERT_TRUE(map.Initialize(&connection)); const MemoryMap::Mapping* mapping = map.FindMapping(code_address); ASSERT_TRUE(mapping); @@ -268,8 +273,11 @@ TEST(MemoryMap, SelfLargeMapFile) { ASSERT_NO_FATAL_FAILURE( InitializeMappings(&mappings, kNumMappings, page_size)); + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; - ASSERT_TRUE(map.Initialize(getpid())); + ASSERT_TRUE(map.Initialize(&connection)); ExpectMappings( map, mappings.addr_as(), kNumMappings, page_size); @@ -292,11 +300,11 @@ class MapRunningChildTest : public Multiprocess { // Let the child get back to its work SleepNanoseconds(1000); - ScopedPtraceAttach attachment; - attachment.ResetAttach(ChildPID()); + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildPID())); MemoryMap map; - ASSERT_TRUE(map.Initialize(ChildPID())); + ASSERT_TRUE(map.Initialize(&connection)); // We should at least find the original mappings. The extra mappings may // or not be found depending on scheduling. @@ -349,8 +357,11 @@ TEST(MemoryMap, MapRunningChild) { // file. The second page should not. void ExpectFindFileMmapStart(LinuxVMAddress mapping_start, LinuxVMSize page_size) { + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; - ASSERT_TRUE(map.Initialize(getpid())); + ASSERT_TRUE(map.Initialize(&connection)); auto mapping1 = map.FindMapping(mapping_start); ASSERT_TRUE(mapping1); @@ -391,8 +402,11 @@ TEST(MemoryMap, FindFileMmapStart) { // Basic { + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; - ASSERT_TRUE(map.Initialize(getpid())); + ASSERT_TRUE(map.Initialize(&connection)); auto mapping1 = map.FindMapping(mapping_start); ASSERT_TRUE(mapping1); diff --git a/util/linux/proc_stat_reader.cc b/util/linux/proc_stat_reader.cc index b4cc3ffd..b1dfe94f 100644 --- a/util/linux/proc_stat_reader.cc +++ b/util/linux/proc_stat_reader.cc @@ -22,27 +22,12 @@ #include "base/logging.h" #include "util/file/file_io.h" #include "util/misc/lexing.h" +#include "util/misc/time.h" namespace crashpad { namespace { -void SubtractTimespec(const timespec& t1, - const timespec& t2, - timespec* result) { - result->tv_sec = t1.tv_sec - t2.tv_sec; - result->tv_nsec = t1.tv_nsec - t2.tv_nsec; - if (result->tv_nsec < 0) { - result->tv_sec -= 1; - result->tv_nsec += static_cast(1E9); - } -} - -void TimespecToTimeval(const timespec& ts, timeval* tv) { - tv->tv_sec = ts.tv_sec; - tv->tv_usec = ts.tv_nsec / 1000; -} - long GetClockTicksPerSecond() { long clock_ticks_per_s = sysconf(_SC_CLK_TCK); if (clock_ticks_per_s <= 0) { diff --git a/util/linux/ptrace_broker.cc b/util/linux/ptrace_broker.cc new file mode 100644 index 00000000..e9d30686 --- /dev/null +++ b/util/linux/ptrace_broker.cc @@ -0,0 +1,359 @@ +// 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 "util/linux/ptrace_broker.h" + +#include +#include +#include +#include + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace crashpad { + +namespace { + +size_t FormatPID(char* buffer, pid_t pid) { + DCHECK_GE(pid, 0); + + char pid_buf[16]; + size_t length = 0; + do { + DCHECK_LT(length, sizeof(pid_buf)); + + pid_buf[length] = '0' + pid % 10; + pid /= 10; + ++length; + } while (pid > 0); + + for (size_t index = 0; index < length; ++index) { + buffer[index] = pid_buf[length - index - 1]; + } + + return length; +} + +} // namespace + +PtraceBroker::PtraceBroker(int sock, pid_t pid, bool is_64_bit) + : ptracer_(is_64_bit, /* can_log= */ false), + file_root_(file_root_buffer_), + attachments_(nullptr), + attach_count_(0), + attach_capacity_(0), + memory_file_(), + sock_(sock), + memory_pid_(pid), + tried_opening_mem_file_(false) { + AllocateAttachments(); + + static constexpr char kProc[] = "/proc/"; + size_t root_length = strlen(kProc); + memcpy(file_root_buffer_, kProc, root_length); + + if (pid >= 0) { + root_length += FormatPID(file_root_buffer_ + root_length, pid); + DCHECK_LT(root_length, sizeof(file_root_buffer_)); + file_root_buffer_[root_length] = '/'; + ++root_length; + } + + DCHECK_LT(root_length, sizeof(file_root_buffer_)); + file_root_buffer_[root_length] = '\0'; +} + +PtraceBroker::~PtraceBroker() = default; + +void PtraceBroker::SetFileRoot(const char* new_root) { + DCHECK_EQ(new_root[strlen(new_root) - 1], '/'); + memory_pid_ = -1; + file_root_ = new_root; +} + +int PtraceBroker::Run() { + int result = RunImpl(); + ReleaseAttachments(); + return result; +} + +bool PtraceBroker::AllocateAttachments() { + constexpr size_t page_size = 4096; + constexpr size_t alloc_size = + (sizeof(ScopedPtraceAttach) + page_size - 1) & ~(page_size - 1); + void* alloc = sbrk(alloc_size); + if (reinterpret_cast(alloc) == -1) { + return false; + } + + if (attachments_ == nullptr) { + attachments_ = reinterpret_cast(alloc); + } + + attach_capacity_ += alloc_size / sizeof(ScopedPtraceAttach); + return true; +} + +void PtraceBroker::ReleaseAttachments() { + for (size_t index = 0; index < attach_count_; ++index) { + attachments_[index].Reset(); + } +} + +int PtraceBroker::RunImpl() { + while (true) { + Request request = {}; + if (!ReadFileExactly(sock_, &request, sizeof(request))) { + return errno; + } + + if (request.version != Request::kVersion) { + return EINVAL; + } + + switch (request.type) { + case Request::kTypeAttach: { + ScopedPtraceAttach* attach; + ScopedPtraceAttach stack_attach; + bool attach_on_stack = false; + + if (attach_capacity_ > attach_count_ || AllocateAttachments()) { + attach = new (&attachments_[attach_count_]) ScopedPtraceAttach; + } else { + attach = &stack_attach; + attach_on_stack = true; + } + + Bool status = kBoolFalse; + if (attach->ResetAttach(request.tid)) { + status = kBoolTrue; + if (!attach_on_stack) { + ++attach_count_; + } + } + + if (!WriteFile(sock_, &status, sizeof(status))) { + return errno; + } + + if (status == kBoolFalse) { + Errno error = errno; + if (!WriteFile(sock_, &error, sizeof(error))) { + return errno; + } + } + + if (attach_on_stack && status == kBoolTrue) { + return RunImpl(); + } + continue; + } + + case Request::kTypeIs64Bit: { + Bool is_64_bit = ptracer_.Is64Bit() ? kBoolTrue : kBoolFalse; + if (!WriteFile(sock_, &is_64_bit, sizeof(is_64_bit))) { + return errno; + } + continue; + } + + case Request::kTypeGetThreadInfo: { + GetThreadInfoResponse response; + response.success = ptracer_.GetThreadInfo(request.tid, &response.info) + ? kBoolTrue + : kBoolFalse; + + if (!WriteFile(sock_, &response, sizeof(response))) { + return errno; + } + + if (response.success == kBoolFalse) { + Errno error = errno; + if (!WriteFile(sock_, &error, sizeof(error))) { + return errno; + } + } + continue; + } + + case Request::kTypeReadFile: { + ScopedFileHandle handle; + int result = ReceiveAndOpenFilePath(request.path.path_length, &handle); + if (result != 0) { + return result; + } + + if (!handle.is_valid()) { + continue; + } + + result = SendFileContents(handle.get()); + if (result != 0) { + return result; + } + continue; + } + + case Request::kTypeReadMemory: { + int result = + SendMemory(request.tid, request.iov.base, request.iov.size); + if (result != 0) { + return result; + } + continue; + } + + case Request::kTypeExit: + return 0; + } + + DCHECK(false); + return EINVAL; + } +} + +int PtraceBroker::SendError(Errno err) { + return WriteFile(sock_, &err, sizeof(err)) ? 0 : errno; +} + +int PtraceBroker::SendReadError(ReadError error) { + int32_t rv = -1; + return WriteFile(sock_, &rv, sizeof(rv)) && + WriteFile(sock_, &error, sizeof(error)) + ? 0 + : errno; +} + +int PtraceBroker::SendOpenResult(OpenResult result) { + return WriteFile(sock_, &result, sizeof(result)) ? 0 : errno; +} + +int PtraceBroker::SendFileContents(FileHandle handle) { + char buffer[4096]; + int32_t rv; + do { + rv = ReadFile(handle, buffer, sizeof(buffer)); + + if (rv < 0) { + return SendReadError(static_cast(errno)); + } + + if (!WriteFile(sock_, &rv, sizeof(rv))) { + return errno; + } + + if (rv > 0) { + if (!WriteFile(sock_, buffer, static_cast(rv))) { + return errno; + } + } + } while (rv > 0); + + return 0; +} + +void PtraceBroker::TryOpeningMemFile() { + if (tried_opening_mem_file_) { + return; + } + tried_opening_mem_file_ = true; + + if (memory_pid_ < 0) { + return; + } + + char mem_path[32]; + size_t root_length = strlen(file_root_buffer_); + static constexpr char kMem[] = "mem"; + + DCHECK_LT(root_length + strlen(kMem) + 1, sizeof(mem_path)); + memcpy(mem_path, file_root_buffer_, root_length); + // Include the trailing NUL. + memcpy(mem_path + root_length, kMem, strlen(kMem) + 1); + memory_file_.reset( + HANDLE_EINTR(open(mem_path, O_RDONLY | O_CLOEXEC | O_NOCTTY))); +} + +int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) { + if (memory_pid_ >= 0 && pid != memory_pid_) { + return SendReadError(kReadErrorAccessDenied); + } + + TryOpeningMemFile(); + auto read_memory = [this, pid](VMAddress address, size_t size, char* buffer) { + return this->memory_file_.is_valid() + ? HANDLE_EINTR( + pread64(this->memory_file_.get(), buffer, size, address)) + : this->ptracer_.ReadUpTo(pid, address, size, buffer); + }; + + char buffer[4096]; + while (size > 0) { + size_t to_read = std::min(size, VMSize{sizeof(buffer)}); + + int32_t bytes_read = read_memory(address, to_read, buffer); + + if (bytes_read < 0) { + return SendReadError(static_cast(errno)); + } + + if (!WriteFile(sock_, &bytes_read, sizeof(bytes_read))) { + return errno; + } + + if (bytes_read == 0) { + return 0; + } + + if (!WriteFile(sock_, buffer, bytes_read)) { + return errno; + } + + size -= bytes_read; + address += bytes_read; + } + return 0; +} + +int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length, + ScopedFileHandle* handle) { + char path[std::max(4096, PATH_MAX)]; + + if (path_length >= sizeof(path)) { + return SendOpenResult(kOpenResultTooLong); + } + + if (!ReadFileExactly(sock_, path, path_length)) { + return errno; + } + path[path_length] = '\0'; + + if (strncmp(path, file_root_, strlen(file_root_)) != 0) { + return SendOpenResult(kOpenResultAccessDenied); + } + + ScopedFileHandle local_handle( + HANDLE_EINTR(open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY))); + if (!local_handle.is_valid()) { + return SendOpenResult(static_cast(errno)); + } + + handle->reset(local_handle.release()); + return SendOpenResult(kOpenResultSuccess); +} + +} // namespace crashpad diff --git a/util/linux/ptrace_broker.h b/util/linux/ptrace_broker.h new file mode 100644 index 00000000..2e5decdf --- /dev/null +++ b/util/linux/ptrace_broker.h @@ -0,0 +1,213 @@ +// 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_UTIL_LINUX_PTRACE_BROKER_H_ +#define CRASHPAD_UTIL_LINUX_PTRACE_BROKER_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "util/file/file_io.h" +#include "util/linux/exception_handler_protocol.h" +#include "util/linux/ptrace_connection.h" +#include "util/linux/ptracer.h" +#include "util/linux/scoped_ptrace_attach.h" +#include "util/linux/thread_info.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +//! \brief Implements a PtraceConnection over a socket. +//! +//! This class is the server half of the connection. The broker should be run +//! in a process with `ptrace` capabilities for the target process and may run +//! in a compromised context. +class PtraceBroker { + public: +#pragma pack(push, 1) + //! \brief A request sent to a PtraceBroker from a PtraceClient. + struct Request { + static constexpr uint16_t kVersion = 1; + + //! \brief The version number for this Request. + uint16_t version = kVersion; + + //! \brief The type of request to serve. + enum Type : uint16_t { + //! \brief `ptrace`-attach the specified thread ID. Responds with + //! kBoolTrue on success, otherwise kBoolFalse, followed by an Errno. + kTypeAttach, + + //! \brief Responds with kBoolTrue if the target process is 64-bit. + //! Otherwise, kBoolFalse. + kTypeIs64Bit, + + //! \brief Responds with a GetThreadInfoResponse containing a ThreadInfo + //! for the specified thread ID. If an error occurs, + //! GetThreadInfoResponse::success is set to kBoolFalse and is + //! followed by an Errno. + kTypeGetThreadInfo, + + //! \brief Reads memory from the attached process. The data is returned in + //! a series of messages. Each message begins with an int32_t + //! indicating the number of bytes read, 0 for end-of-file, or -1 for + //! errors, followed by a ReadError. On success the bytes read follow. + kTypeReadMemory, + + //! \brief Read a file's contents. 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, 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. + kTypeReadFile, + + //! \brief Causes the broker to return from Run(), detaching all attached + //! threads. Does not respond. + kTypeExit + } type; + + //! \brief The thread ID associated with this request. Valid for kTypeAttach, + //! kTypeGetThreadInfo, and kTypeReadMemory. + pid_t tid; + + union { + //! \brief Specifies the memory region to read for a kTypeReadMemory + //! request. + struct { + //! \brief The base address of the memory region. + VMAddress base; + + //! \brief The size of the memory region. + VMSize size; + } iov; + + //! \brief Specifies the file path to read for a kTypeReadFile request. + struct { + //! \brief The number of bytes in #path. The path should not include a + //! `NUL`-terminator. + VMSize path_length; + + //! \brief The file path to read. + char path[]; + } path; + }; + }; + + //! \brief A result used in operations that accept paths. + //! + //! Positive values of this enum are reserved for sending errno values. + enum OpenResult : int32_t { + //! \brief Access to the path is denied. + kOpenResultAccessDenied = -2, + + //! \brief The path name is too long. + kOpenResultTooLong = -1, + + //! \brief The file was successfully opened. + kOpenResultSuccess = 0, + }; + + //! \brief A result used in operations that read from memory or files. + //! + //! Positive values of this enum are reserved for sending errno values. + enum ReadError : int32_t { + //! \brief Access to this data is denied. + kReadErrorAccessDenied = -1, + }; + + //! \brief The response sent for a Request with type kTypeGetThreadInfo. + struct GetThreadInfoResponse { + //! \brief Information about the specified thread. Only valid if #success + //! is kBoolTrue. + ThreadInfo info; + + //! \brief Specifies the success or failure of this call. + Bool success; + }; +#pragma pack(pop) + + //! \brief Constructs this object. + //! + //! \param[in] sock A socket on which to read requests from a connected + //! PtraceClient. Does not take ownership of the socket. + //! \param[in] pid The process ID of the process the broker is expected to + //! trace. Setting this value exends the default file root to + //! "/proc/[pid]/" and enables memory reading via /proc/[pid]/mem. The + //! broker will deny any requests to read memory from processes whose + //! processID is not \a pid. If pid is -1, the broker will serve requests + //! to read memory from any process it is able to via `ptrace PEEKDATA`. + //! \param[in] is_64_bit Whether this broker should be configured to trace a + //! 64-bit process. + PtraceBroker(int sock, pid_t pid, bool is_64_bit); + + ~PtraceBroker(); + + //! \brief Restricts the broker to serving the contents of files under \a + //! root. + //! + //! If this method is not called, the broker defaults to only serving files + //! under "/proc/" or "/proc/[pid]/" if a pid was set. + //! + //! Calling this function disables reading from a memory file if one has not + //! already been opened. + //! + //! \param[in] root A NUL-terminated c-string containing the path to the new + //! root. \a root must not be `nullptr`, must end in a '/', and the caller + //! should ensure that \a root remains valid for the lifetime of the + //! broker. + void SetFileRoot(const char* root); + + //! \brief Begin serving requests on the configured socket. + //! + //! This method returns when a PtraceBrokerRequest with type kTypeExit is + //! received or an error is encountered on the socket. + //! + //! This method calls `sbrk`, which may break other memory management tools, + //! such as `malloc`. + //! + //! \return 0 if Run() exited due to an exit request. Otherwise an error code. + int Run(); + + private: + bool AllocateAttachments(); + void ReleaseAttachments(); + int RunImpl(); + int SendError(Errno err); + int SendReadError(ReadError err); + int SendOpenResult(OpenResult result); + int SendFileContents(FileHandle handle); + void TryOpeningMemFile(); + int SendMemory(pid_t pid, VMAddress address, VMSize size); + int ReceiveAndOpenFilePath(VMSize path_length, ScopedFileHandle* handle); + + char file_root_buffer_[32]; + Ptracer ptracer_; + const char* file_root_; + ScopedPtraceAttach* attachments_; + size_t attach_count_; + size_t attach_capacity_; + ScopedFileHandle memory_file_; + int sock_; + pid_t memory_pid_; + bool tried_opening_mem_file_; + + DISALLOW_COPY_AND_ASSIGN(PtraceBroker); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PTRACE_BROKER_H_ diff --git a/util/linux/ptrace_broker_test.cc b/util/linux/ptrace_broker_test.cc new file mode 100644 index 00000000..9ed1973b --- /dev/null +++ b/util/linux/ptrace_broker_test.cc @@ -0,0 +1,279 @@ +// 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 "util/linux/ptrace_broker.h" + +#include +#include +#include +#include + +#include + +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/filesystem.h" +#include "test/linux/get_tls.h" +#include "test/multiprocess.h" +#include "test/scoped_temp_dir.h" +#include "util/file/file_io.h" +#include "util/linux/ptrace_client.h" +#include "util/posix/scoped_mmap.h" +#include "util/synchronization/semaphore.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { +namespace { + +class ScopedTimeoutThread : public Thread { + public: + ScopedTimeoutThread() : join_sem_(0) {} + ~ScopedTimeoutThread() { EXPECT_TRUE(JoinWithTimeout(5.0)); } + + protected: + void ThreadMain() override { join_sem_.Signal(); } + + private: + bool JoinWithTimeout(double timeout) { + if (!join_sem_.TimedWait(timeout)) { + return false; + } + Join(); + return true; + } + + Semaphore join_sem_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTimeoutThread); +}; + +class RunBrokerThread : public ScopedTimeoutThread { + public: + RunBrokerThread(PtraceBroker* broker) + : ScopedTimeoutThread(), broker_(broker) {} + + ~RunBrokerThread() {} + + private: + void ThreadMain() override { + EXPECT_EQ(broker_->Run(), 0); + ScopedTimeoutThread::ThreadMain(); + } + + PtraceBroker* broker_; + + DISALLOW_COPY_AND_ASSIGN(RunBrokerThread); +}; + +class BlockOnReadThread : public ScopedTimeoutThread { + public: + BlockOnReadThread(int readfd, int writefd) + : ScopedTimeoutThread(), readfd_(readfd), writefd_(writefd) {} + + ~BlockOnReadThread() {} + + private: + void ThreadMain() override { + pid_t pid = syscall(SYS_gettid); + LoggingWriteFile(writefd_, &pid, sizeof(pid)); + + LinuxVMAddress tls = GetTLS(); + LoggingWriteFile(writefd_, &tls, sizeof(tls)); + + CheckedReadFileAtEOF(readfd_); + ScopedTimeoutThread::ThreadMain(); + } + + int readfd_; + int writefd_; + + DISALLOW_COPY_AND_ASSIGN(BlockOnReadThread); +}; + +class SameBitnessTest : public Multiprocess { + public: + SameBitnessTest() : Multiprocess(), mapping_() {} + ~SameBitnessTest() {} + + protected: + void PreFork() override { + ASSERT_NO_FATAL_FAILURE(Multiprocess::PreFork()); + + size_t page_size = getpagesize(); + ASSERT_TRUE(mapping_.ResetMmap(nullptr, + page_size * 3, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + ASSERT_TRUE(mapping_.ResetAddrLen(mapping_.addr(), page_size * 2)); + + auto buffer = mapping_.addr_as(); + for (size_t index = 0; index < mapping_.len(); ++index) { + buffer[index] = index % 256; + } + } + + private: + void BrokerTests(bool set_broker_pid, + LinuxVMAddress child1_tls, + LinuxVMAddress child2_tls, + pid_t child2_tid, + const base::FilePath& file_dir, + const base::FilePath& test_file, + const std::string& expected_file_contents) { + int socks[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0); + ScopedFileHandle broker_sock(socks[0]); + ScopedFileHandle client_sock(socks[1]); + +#if defined(ARCH_CPU_64_BITS) + constexpr bool am_64_bit = true; +#else + constexpr bool am_64_bit = false; +#endif // ARCH_CPU_64_BITS + + PtraceBroker broker( + broker_sock.get(), set_broker_pid ? ChildPID() : -1, am_64_bit); + RunBrokerThread broker_thread(&broker); + broker_thread.Start(); + + PtraceClient client; + ASSERT_TRUE(client.Initialize( + client_sock.get(), ChildPID(), /* try_direct_memory= */ false)); + + EXPECT_EQ(client.GetProcessID(), ChildPID()); + EXPECT_TRUE(client.Attach(child2_tid)); + EXPECT_EQ(client.Is64Bit(), am_64_bit); + + ThreadInfo info1; + ASSERT_TRUE(client.GetThreadInfo(ChildPID(), &info1)); + EXPECT_EQ(info1.thread_specific_data_address, child1_tls); + + ThreadInfo info2; + ASSERT_TRUE(client.GetThreadInfo(child2_tid, &info2)); + EXPECT_EQ(info2.thread_specific_data_address, child2_tls); + + ProcessMemory* memory = client.Memory(); + ASSERT_TRUE(memory); + + auto buffer = std::make_unique(mapping_.len()); + ASSERT_TRUE(memory->Read( + mapping_.addr_as(), mapping_.len(), buffer.get())); + auto expected_buffer = mapping_.addr_as(); + for (size_t index = 0; index < mapping_.len(); ++index) { + EXPECT_EQ(buffer[index], expected_buffer[index]); + } + + char first; + ASSERT_TRUE( + memory->Read(mapping_.addr_as(), sizeof(first), &first)); + EXPECT_EQ(first, expected_buffer[0]); + + char last; + ASSERT_TRUE(memory->Read(mapping_.addr_as() + mapping_.len() - 1, + sizeof(last), + &last)); + EXPECT_EQ(last, expected_buffer[mapping_.len() - 1]); + + char unmapped; + EXPECT_FALSE(memory->Read(mapping_.addr_as() + mapping_.len(), + sizeof(unmapped), + &unmapped)); + + std::string file_root = file_dir.value() + '/'; + broker.SetFileRoot(file_root.c_str()); + + std::string file_contents; + ASSERT_TRUE(client.ReadFileContents(test_file, &file_contents)); + EXPECT_EQ(file_contents, expected_file_contents); + + ScopedTempDir temp_dir2; + base::FilePath test_file2(temp_dir2.path().Append("test_file2")); + ASSERT_TRUE(CreateFile(test_file2)); + EXPECT_FALSE(client.ReadFileContents(test_file2, &file_contents)); + } + + void MultiprocessParent() override { + LinuxVMAddress child1_tls; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child1_tls, sizeof(child1_tls))); + + pid_t child2_tid; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child2_tid, sizeof(child2_tid))); + + LinuxVMAddress child2_tls; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &child2_tls, sizeof(child2_tls))); + + ScopedTempDir temp_dir; + base::FilePath file_path(temp_dir.path().Append("test_file")); + std::string expected_file_contents; + { + expected_file_contents.resize(4097); + for (size_t i = 0; i < expected_file_contents.size(); ++i) { + expected_file_contents[i] = static_cast(i % 256); + } + ScopedFileHandle handle( + LoggingOpenFileForWrite(file_path, + FileWriteMode::kCreateOrFail, + FilePermissions::kWorldReadable)); + ASSERT_TRUE(LoggingWriteFile(handle.get(), + expected_file_contents.data(), + expected_file_contents.size())); + } + + BrokerTests(true, + child1_tls, + child2_tls, + child2_tid, + temp_dir.path(), + file_path, + expected_file_contents); + BrokerTests(false, + child1_tls, + child2_tls, + child2_tid, + temp_dir.path(), + file_path, + expected_file_contents); + } + + void MultiprocessChild() override { + LinuxVMAddress tls = GetTLS(); + ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &tls, sizeof(tls))); + + BlockOnReadThread thread(ReadPipeHandle(), WritePipeHandle()); + thread.Start(); + + CheckedReadFileAtEOF(ReadPipeHandle()); + } + + ScopedMmap mapping_; + + DISALLOW_COPY_AND_ASSIGN(SameBitnessTest); +}; + +TEST(PtraceBroker, SameBitness) { + SameBitnessTest test; + test.Run(); +} + +// TODO(jperaza): Test against a process with different bitness. + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/linux/ptrace_client.cc b/util/linux/ptrace_client.cc new file mode 100644 index 00000000..b5ccebaf --- /dev/null +++ b/util/linux/ptrace_client.cc @@ -0,0 +1,310 @@ +// 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 "util/linux/ptrace_client.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "util/file/file_io.h" +#include "util/linux/ptrace_broker.h" +#include "util/process/process_memory_linux.h" + +namespace crashpad { + +namespace { + +bool ReceiveAndLogError(int sock, const std::string& operation) { + Errno error; + if (!LoggingReadFileExactly(sock, &error, sizeof(error))) { + return false; + } + errno = error; + PLOG(ERROR) << operation; + return true; +} + +bool ReceiveAndLogReadError(int sock, const std::string& operation) { + PtraceBroker::ReadError err; + if (!LoggingReadFileExactly(sock, &err, sizeof(err))) { + return false; + } + switch (err) { + case PtraceBroker::kReadErrorAccessDenied: + LOG(ERROR) << operation << " access denied"; + return true; + default: + if (err <= 0) { + LOG(ERROR) << operation << " invalid error " << err; + DCHECK(false); + return false; + } + errno = err; + PLOG(ERROR) << operation; + return true; + } +} + +bool AttachImpl(int sock, pid_t tid) { + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeAttach; + request.tid = tid; + if (!LoggingWriteFile(sock, &request, sizeof(request))) { + return false; + } + + Bool success; + if (!LoggingReadFileExactly(sock, &success, sizeof(success))) { + return false; + } + + if (success != kBoolTrue) { + ReceiveAndLogError(sock, "PtraceBroker Attach"); + return false; + } + + return true; +} + +} // namespace + +PtraceClient::PtraceClient() + : PtraceConnection(), + memory_(), + sock_(kInvalidFileHandle), + pid_(-1), + is_64_bit_(false), + initialized_() {} + +PtraceClient::~PtraceClient() { + if (sock_ != kInvalidFileHandle) { + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeExit; + LoggingWriteFile(sock_, &request, sizeof(request)); + } +} + +bool PtraceClient::Initialize(int sock, pid_t pid, bool try_direct_memory) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + sock_ = sock; + pid_ = pid; + + if (!AttachImpl(sock_, pid_)) { + return false; + } + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeIs64Bit; + request.tid = pid_; + + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + Bool is_64_bit; + if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) { + return false; + } + is_64_bit_ = is_64_bit == kBoolTrue; + + if (try_direct_memory) { + auto direct_mem = std::make_unique(); + if (direct_mem->Initialize(pid)) { + memory_.reset(direct_mem.release()); + } + } + if (!memory_) { + memory_ = std::make_unique(this); + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +pid_t PtraceClient::GetProcessID() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return pid_; +} + +bool PtraceClient::Attach(pid_t tid) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return AttachImpl(sock_, tid); +} + +bool PtraceClient::Is64Bit() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return is_64_bit_; +} + +bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeGetThreadInfo; + request.tid = tid; + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + PtraceBroker::GetThreadInfoResponse response; + if (!LoggingReadFileExactly(sock_, &response, sizeof(response))) { + return false; + } + + if (response.success == kBoolTrue) { + *info = response.info; + return true; + } + + ReceiveAndLogError(sock_, "PtraceBroker GetThreadInfo"); + return false; +} + +bool PtraceClient::ReadFileContents(const base::FilePath& path, + std::string* contents) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeReadFile; + request.path.path_length = path.value().size(); + + if (!LoggingWriteFile(sock_, &request, sizeof(request)) || + !SendFilePath(path.value().c_str(), request.path.path_length)) { + return false; + } + + std::string local_contents; + int32_t read_result; + do { + if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) { + return false; + } + + if (read_result < 0) { + ReceiveAndLogReadError(sock_, "ReadFileContents"); + return false; + } + + if (read_result > 0) { + size_t old_length = local_contents.size(); + local_contents.resize(old_length + read_result); + if (!LoggingReadFileExactly( + sock_, &local_contents[old_length], read_result)) { + return false; + } + } + } while (read_result > 0); + + contents->swap(local_contents); + return true; +} + +ProcessMemory* PtraceClient::Memory() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return memory_.get(); +} + +PtraceClient::BrokeredMemory::BrokeredMemory(PtraceClient* client) + : ProcessMemory(), client_(client) {} + +PtraceClient::BrokeredMemory::~BrokeredMemory() = default; + +ssize_t PtraceClient::BrokeredMemory::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + return client_->ReadUpTo(address, size, buffer); +} + +ssize_t PtraceClient::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + char* buffer_c = reinterpret_cast(buffer); + + PtraceBroker::Request request; + request.type = PtraceBroker::Request::kTypeReadMemory; + request.tid = pid_; + request.iov.base = address; + request.iov.size = size; + + if (!LoggingWriteFile(sock_, &request, sizeof(request))) { + return false; + } + + ssize_t total_read = 0; + while (size > 0) { + int32_t bytes_read; + if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) { + return -1; + } + + if (bytes_read < 0) { + ReceiveAndLogReadError(sock_, "PtraceBroker ReadMemory"); + return -1; + } + + if (bytes_read == 0) { + return total_read; + } + + if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) { + return -1; + } + + size -= bytes_read; + buffer_c += bytes_read; + total_read += bytes_read; + } + + return total_read; +} + +bool PtraceClient::SendFilePath(const char* path, size_t length) { + if (!LoggingWriteFile(sock_, path, length)) { + return false; + } + + PtraceBroker::OpenResult result; + if (!LoggingReadFileExactly(sock_, &result, sizeof(result))) { + return false; + } + + switch (result) { + case PtraceBroker::kOpenResultAccessDenied: + LOG(ERROR) << "Broker Open: access denied"; + return false; + + case PtraceBroker::kOpenResultTooLong: + LOG(ERROR) << "Broker Open: path too long"; + return false; + + case PtraceBroker::kOpenResultSuccess: + return true; + + default: + if (result < 0) { + LOG(ERROR) << "Broker Open: invalid result " << result; + DCHECK(false); + } else { + errno = result; + PLOG(ERROR) << "Broker Open"; + } + return false; + } +} + +} // namespace crashpad diff --git a/util/linux/ptrace_client.h b/util/linux/ptrace_client.h new file mode 100644 index 00000000..a7abc0cf --- /dev/null +++ b/util/linux/ptrace_client.h @@ -0,0 +1,95 @@ +// 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_UTIL_LINUX_PTRACE_CLIENT_H_ +#define CRASHPAD_UTIL_LINUX_PTRACE_CLIENT_H_ + +#include + +#include + +#include "base/macros.h" +#include "util/linux/ptrace_connection.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory.h" + +namespace crashpad { + +//! \brief Implements a PtraceConnection over a socket. +//! +//! This class forms the client half of the connection and is typically used +//! when the current process does not have `ptrace` capabilities on the target +//! process. It should be created with a socket connected to a PtraceBroker. +class PtraceClient : public PtraceConnection { + public: + PtraceClient(); + ~PtraceClient(); + + //! \brief Initializes this object. + //! + //! This method must be successfully called before any other method in this + //! class. + //! + //! \param[in] sock A socket connected to a PtraceBroker. Does not take + //! ownership of the socket. + //! \param[in] pid The process ID of the process to form a PtraceConnection + //! with. + //! \param[in] try_direct_memory If `true` the client will attempt to support + //! memory reading operations by directly acessing the target process' + //! /proc/[pid]/mem file. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(int sock, pid_t pid, bool try_direct_memory = true); + + // PtraceConnection: + + pid_t GetProcessID() override; + bool Attach(pid_t tid) override; + bool Is64Bit() override; + bool GetThreadInfo(pid_t tid, ThreadInfo* info) override; + bool ReadFileContents(const base::FilePath& path, + std::string* contents) override; + ProcessMemory* Memory() override; + + private: + class BrokeredMemory : public ProcessMemory { + public: + explicit BrokeredMemory(PtraceClient* client); + ~BrokeredMemory(); + + ssize_t ReadUpTo(VMAddress address, + size_t size, + void* buffer) const override; + + private: + PtraceClient* client_; + + DISALLOW_COPY_AND_ASSIGN(BrokeredMemory); + }; + + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const; + bool SendFilePath(const char* path, size_t length); + + std::unique_ptr memory_; + int sock_; + pid_t pid_; + bool is_64_bit_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(PtraceClient); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PTRACE_CLIENT_H_ diff --git a/util/linux/ptrace_connection.h b/util/linux/ptrace_connection.h index d09cd20d..86dc2dd9 100644 --- a/util/linux/ptrace_connection.h +++ b/util/linux/ptrace_connection.h @@ -17,7 +17,11 @@ #include +#include + +#include "base/files/file_path.h" #include "util/linux/thread_info.h" +#include "util/process/process_memory.h" namespace crashpad { @@ -45,6 +49,21 @@ class PtraceConnection { //! \param[out] info Information about the thread. //! \return `true` on success. `false` on failure with a message logged. virtual bool GetThreadInfo(pid_t tid, ThreadInfo* info) = 0; + + //! \brief Reads the entire contents of a file. + //! + //! \param[in] path The path of the file to read. + //! \param[out] contents The file contents, valid if this method returns + //! `true`. + //! \return `true` on success. `false` on failure with a message logged. + virtual bool ReadFileContents(const base::FilePath& path, + std::string* contents) = 0; + + //! \brief Returns a memory reader for the connected process. + //! + //! 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; }; } // namespace crashpad diff --git a/util/linux/ptracer.cc b/util/linux/ptracer.cc index fcbaedc0..c6c92299 100644 --- a/util/linux/ptracer.cc +++ b/util/linux/ptracer.cc @@ -14,6 +14,7 @@ #include "util/linux/ptracer.h" +#include #include #include #include @@ -34,38 +35,43 @@ namespace { #if defined(ARCH_CPU_X86_FAMILY) template -bool GetRegisterSet(pid_t tid, int set, Destination* dest) { +bool GetRegisterSet(pid_t tid, int set, Destination* dest, bool can_log) { iovec iov; iov.iov_base = dest; iov.iov_len = sizeof(*dest); if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast(set), &iov) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != sizeof(*dest)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; } -bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { - return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave); +bool GetFloatingPointRegisters32(pid_t tid, + FloatContext* context, + bool can_log) { + return GetRegisterSet(tid, NT_PRXFPREG, &context->f32.fxsave, can_log); } -bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { - return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave); +bool GetFloatingPointRegisters64(pid_t tid, + FloatContext* context, + bool can_log) { + return GetRegisterSet(tid, NT_PRFPREG, &context->f64.fxsave, can_log); } bool GetThreadArea32(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { size_t index = (context.t32.xgs & 0xffff) >> 3; user_desc desc; if (ptrace( PTRACE_GET_THREAD_AREA, tid, reinterpret_cast(index), &desc) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } @@ -75,7 +81,8 @@ bool GetThreadArea32(pid_t tid, bool GetThreadArea64(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { *address = context.t64.fs_base; return true; } @@ -98,17 +105,21 @@ bool GetThreadArea64(pid_t tid, // TODO(mark): Once helpers to interpret the kernel version are available, add // a DCHECK to ensure that the kernel is older than 3.5. -bool GetGeneralPurposeRegistersLegacy(pid_t tid, ThreadContext* context) { +bool GetGeneralPurposeRegistersLegacy(pid_t tid, + ThreadContext* context, + bool can_log) { if (ptrace(PTRACE_GETREGS, tid, nullptr, &context->t32) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } return true; } -bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegistersLegacy(pid_t tid, + FloatContext* context, + bool can_log) { if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } context->f32.have_fpregs = true; @@ -119,7 +130,7 @@ bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { // These registers are optional on 32-bit ARM cpus break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { @@ -138,7 +149,9 @@ bool GetFloatingPointRegistersLegacy(pid_t tid, FloatContext* context) { constexpr size_t kArmVfpSize = 32 * 8 + 4; // Target is 32-bit -bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegisters32(pid_t tid, + FloatContext* context, + bool can_log) { context->f32.have_fpregs = false; context->f32.have_vfp = false; @@ -151,19 +164,19 @@ bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { switch (errno) { #if defined(ARCH_CPU_ARMEL) case EIO: - return GetFloatingPointRegistersLegacy(tid, context); + return GetFloatingPointRegistersLegacy(tid, context, can_log); #endif // ARCH_CPU_ARMEL case EINVAL: // A 32-bit process running on a 64-bit CPU doesn't have this register // set. It should have a VFP register set instead. break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { if (iov.iov_len != sizeof(context->f32.fpregs)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } context->f32.have_fpregs = true; @@ -179,36 +192,38 @@ bool GetFloatingPointRegisters32(pid_t tid, FloatContext* context) { // VFP may not be present on 32-bit ARM cpus. break; default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } } else { if (iov.iov_len != kArmVfpSize && iov.iov_len != sizeof(context->f32.vfp)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } context->f32.have_vfp = true; } if (!(context->f32.have_fpregs || context->f32.have_vfp)) { - LOG(ERROR) << "Unable to collect registers"; + LOG_IF(ERROR, can_log) << "Unable to collect registers"; return false; } return true; } -bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { +bool GetFloatingPointRegisters64(pid_t tid, + FloatContext* context, + bool can_log) { iovec iov; iov.iov_base = context; iov.iov_len = sizeof(*context); if (ptrace( PTRACE_GETREGSET, tid, reinterpret_cast(NT_PRFPREG), &iov) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != sizeof(context->f64)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; @@ -216,11 +231,12 @@ bool GetFloatingPointRegisters64(pid_t tid, FloatContext* context) { bool GetThreadArea32(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { #if defined(ARCH_CPU_ARMEL) void* result; if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } *address = FromPointerCast(result); @@ -228,34 +244,163 @@ bool GetThreadArea32(pid_t tid, #else // TODO(jperaza): it doesn't look like there is a way for a 64-bit ARM process // to get the thread area for a 32-bit ARM process with ptrace. - LOG(WARNING) << "64-bit ARM cannot trace TLS area for a 32-bit process"; + LOG_IF(WARNING, can_log) + << "64-bit ARM cannot trace TLS area for a 32-bit process"; return false; #endif // ARCH_CPU_ARMEL } bool GetThreadArea64(pid_t tid, const ThreadContext& context, - LinuxVMAddress* address) { + LinuxVMAddress* address, + bool can_log) { iovec iov; iov.iov_base = address; iov.iov_len = sizeof(*address); if (ptrace( PTRACE_GETREGSET, tid, reinterpret_cast(NT_ARM_TLS), &iov) != 0) { - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return false; } if (iov.iov_len != 8) { - LOG(ERROR) << "address size mismatch"; + LOG_IF(ERROR, can_log) << "address size mismatch"; return false; } return true; } +#elif defined(ARCH_CPU_MIPS_FAMILY) +// PTRACE_GETREGSET, introduced in Linux 2.6.34 (2225a122ae26), requires kernel +// support enabled by HAVE_ARCH_TRACEHOOK. This has been set for x86 (including +// x86_64) since Linux 2.6.28 (99bbc4b1e677a), but for MIPS only since +// Linux 3.13 (c0ff3c53d4f99). Older Linux kernels support PTRACE_GETREGS, +// and PTRACE_GETFPREGS instead, which don't allow checking the size of data +// copied. Also, PTRACE_GETREGS assumes register size of 64 bits even for 32 bit +// MIPS CPU (contrary to PTRACE_GETREGSET behavior), so we need buffer +// structure here. + +bool GetGeneralPurposeRegistersLegacy(pid_t tid, + ThreadContext* context, + bool can_log) { + ThreadContext context_buffer; + if (ptrace(PTRACE_GETREGS, tid, nullptr, &context_buffer.t64) != 0) { + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } +// Bitness of target process can't be determined through ptrace here, so we +// assume target process has the same as current process, making cross-bit +// ptrace unsupported on MIPS for kernels older than 3.13 +#if defined(ARCH_CPU_MIPSEL) +#define THREAD_CONTEXT_FIELD t32 +#elif defined(ARCH_CPU_MIPS64EL) +#define THREAD_CONTEXT_FIELD t64 +#endif + for (size_t reg = 0; reg < 32; ++reg) { + context->THREAD_CONTEXT_FIELD.regs[reg] = context_buffer.t64.regs[reg]; + } + context->THREAD_CONTEXT_FIELD.lo = context_buffer.t64.lo; + context->THREAD_CONTEXT_FIELD.hi = context_buffer.t64.hi; + context->THREAD_CONTEXT_FIELD.cp0_epc = context_buffer.t64.cp0_epc; + context->THREAD_CONTEXT_FIELD.cp0_badvaddr = context_buffer.t64.cp0_badvaddr; + context->THREAD_CONTEXT_FIELD.cp0_status = context_buffer.t64.cp0_status; + context->THREAD_CONTEXT_FIELD.cp0_cause = context_buffer.t64.cp0_cause; +#undef THREAD_CONTEXT_FIELD + return true; +} + +bool GetFloatingPointRegistersLegacy(pid_t tid, + FloatContext* context, + bool can_log) { + if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) { + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } + return true; +} + +bool GetFloatingPointRegisters32(pid_t tid, + FloatContext* context, + bool can_log) { + iovec iov; + iov.iov_base = &context->f32.fpregs; + iov.iov_len = sizeof(context->f32.fpregs); + if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f32.fpregs) != 0) { + switch (errno) { + case EINVAL: + // fp may not be present + break; + case EIO: + return GetFloatingPointRegistersLegacy(tid, context, can_log); + default: + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } + } + return true; +} + +bool GetFloatingPointRegisters64(pid_t tid, + FloatContext* context, + bool can_log) { + iovec iov; + iov.iov_base = &context->f64.fpregs; + iov.iov_len = sizeof(context->f64.fpregs); + if (ptrace(PTRACE_GETFPREGS, tid, nullptr, &context->f64.fpregs) != 0) { + switch (errno) { + case EINVAL: + // fp may not be present + break; + case EIO: + return GetFloatingPointRegistersLegacy(tid, context, can_log); + default: + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } + } + return true; +} + +bool GetThreadArea32(pid_t tid, + const ThreadContext& context, + LinuxVMAddress* address, + bool can_log) { +#if defined(ARCH_CPU_MIPSEL) + void* result; + if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) { + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } + *address = FromPointerCast(result); + return true; +#else + return false; +#endif +} + +bool GetThreadArea64(pid_t tid, + const ThreadContext& context, + LinuxVMAddress* address, + bool can_log) { + void* result; +#if defined(ARCH_CPU_MIPSEL) + if (ptrace(PTRACE_GET_THREAD_AREA_3264, tid, nullptr, &result) != 0) { +#else + if (ptrace(PTRACE_GET_THREAD_AREA, tid, nullptr, &result) != 0) { +#endif + PLOG_IF(ERROR, can_log) << "ptrace"; + return false; + } + *address = FromPointerCast(result); + return true; +} + #else #error Port. #endif // ARCH_CPU_X86_FAMILY -size_t GetGeneralPurposeRegistersAndLength(pid_t tid, ThreadContext* context) { +size_t GetGeneralPurposeRegistersAndLength(pid_t tid, + ThreadContext* context, + bool can_log) { iovec iov; iov.iov_base = context; iov.iov_len = sizeof(*context); @@ -263,33 +408,37 @@ size_t GetGeneralPurposeRegistersAndLength(pid_t tid, ThreadContext* context) { PTRACE_GETREGSET, tid, reinterpret_cast(NT_PRSTATUS), &iov) != 0) { switch (errno) { -#if defined(ARCH_CPU_ARMEL) +#if defined(ARCH_CPU_ARMEL) || defined(ARCH_CPU_MIPS_FAMILY) case EIO: - if (GetGeneralPurposeRegistersLegacy(tid, context)) { - return sizeof(context->t32); - } + return GetGeneralPurposeRegistersLegacy(tid, context, can_log) + ? sizeof(context->t32) + : 0; #endif // ARCH_CPU_ARMEL default: - PLOG(ERROR) << "ptrace"; + PLOG_IF(ERROR, can_log) << "ptrace"; return 0; } } return iov.iov_len; } -bool GetGeneralPurposeRegisters32(pid_t tid, ThreadContext* context) { - if (GetGeneralPurposeRegistersAndLength(tid, context) != +bool GetGeneralPurposeRegisters32(pid_t tid, + ThreadContext* context, + bool can_log) { + if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != sizeof(context->t32)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; } -bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context) { - if (GetGeneralPurposeRegistersAndLength(tid, context) != +bool GetGeneralPurposeRegisters64(pid_t tid, + ThreadContext* context, + bool can_log) { + if (GetGeneralPurposeRegistersAndLength(tid, context, can_log) != sizeof(context->t64)) { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log) << "Unexpected registers size"; return false; } return true; @@ -297,9 +446,11 @@ bool GetGeneralPurposeRegisters64(pid_t tid, ThreadContext* context) { } // namespace -Ptracer::Ptracer() : is_64_bit_(false), initialized_() {} +Ptracer::Ptracer(bool can_log) + : is_64_bit_(false), can_log_(can_log), initialized_() {} -Ptracer::Ptracer(bool is_64_bit) : is_64_bit_(is_64_bit) { +Ptracer::Ptracer(bool is_64_bit, bool can_log) + : is_64_bit_(is_64_bit), can_log_(can_log), initialized_() { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); INITIALIZATION_STATE_SET_VALID(initialized_); } @@ -310,13 +461,13 @@ bool Ptracer::Initialize(pid_t pid) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); ThreadContext context; - size_t length = GetGeneralPurposeRegistersAndLength(pid, &context); + size_t length = GetGeneralPurposeRegistersAndLength(pid, &context, can_log_); if (length == sizeof(context.t64)) { is_64_bit_ = true; } else if (length == sizeof(context.t32)) { is_64_bit_ = false; } else { - LOG(ERROR) << "Unexpected registers size"; + LOG_IF(ERROR, can_log_) << "Unexpected registers size"; return false; } @@ -333,16 +484,94 @@ bool Ptracer::GetThreadInfo(pid_t tid, ThreadInfo* info) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (is_64_bit_) { - return GetGeneralPurposeRegisters64(tid, &info->thread_context) && - GetFloatingPointRegisters64(tid, &info->float_context) && - GetThreadArea64( - tid, info->thread_context, &info->thread_specific_data_address); + return GetGeneralPurposeRegisters64(tid, &info->thread_context, can_log_) && + GetFloatingPointRegisters64(tid, &info->float_context, can_log_) && + GetThreadArea64(tid, + info->thread_context, + &info->thread_specific_data_address, + can_log_); } - return GetGeneralPurposeRegisters32(tid, &info->thread_context) && - GetFloatingPointRegisters32(tid, &info->float_context) && - GetThreadArea32( - tid, info->thread_context, &info->thread_specific_data_address); + return GetGeneralPurposeRegisters32(tid, &info->thread_context, can_log_) && + GetFloatingPointRegisters32(tid, &info->float_context, can_log_) && + GetThreadArea32(tid, + info->thread_context, + &info->thread_specific_data_address, + can_log_); +} + +ssize_t Ptracer::ReadUpTo(pid_t pid, + LinuxVMAddress address, + size_t size, + char* buffer) { + size_t bytes_read = 0; + while (size > 0) { + errno = 0; + + if (size >= sizeof(long)) { + *reinterpret_cast(buffer) = + ptrace(PTRACE_PEEKDATA, pid, address, nullptr); + + if (errno == EIO) { + ssize_t last_bytes = ReadLastBytes(pid, address, size, buffer); + return last_bytes >= 0 ? bytes_read + last_bytes : -1; + } + + if (errno != 0) { + PLOG_IF(ERROR, can_log_) << "ptrace"; + return -1; + } + + size -= sizeof(long); + buffer += sizeof(long); + address += sizeof(long); + bytes_read += sizeof(long); + } else { + long word = ptrace(PTRACE_PEEKDATA, pid, address, nullptr); + + if (errno == 0) { + memcpy(buffer, reinterpret_cast(&word), size); + return bytes_read + size; + } + + if (errno == EIO) { + ssize_t last_bytes = ReadLastBytes(pid, address, size, buffer); + return last_bytes >= 0 ? bytes_read + last_bytes : -1; + } + + PLOG_IF(ERROR, can_log_); + return -1; + } + } + + return bytes_read; +} + +// Handles an EIO by reading at most size bytes from address into buffer if +// address was within a word of a possible page boundary, by aligning to read +// the last word of the page and extracting the desired bytes. +ssize_t Ptracer::ReadLastBytes(pid_t pid, + LinuxVMAddress address, + size_t size, + char* buffer) { + LinuxVMAddress aligned = ((address + 4095) & ~4095) - sizeof(long); + if (aligned >= address || aligned == address - sizeof(long)) { + PLOG_IF(ERROR, can_log_) << "ptrace"; + return -1; + } + DCHECK_GT(aligned, address - sizeof(long)); + + errno = 0; + long word = ptrace(PTRACE_PEEKDATA, pid, aligned, nullptr); + if (errno != 0) { + PLOG_IF(ERROR, can_log_) << "ptrace"; + return -1; + } + + size_t bytes_read = address - aligned; + size_t last_bytes = std::min(sizeof(long) - bytes_read, size); + memcpy(buffer, reinterpret_cast(&word) + bytes_read, last_bytes); + return last_bytes; } } // namespace crashpad diff --git a/util/linux/ptracer.h b/util/linux/ptracer.h index e47f6577..33eeb3ae 100644 --- a/util/linux/ptracer.h +++ b/util/linux/ptracer.h @@ -35,13 +35,16 @@ class Ptracer { //! \brief Constructs this object with a pre-determined bitness. //! //! \param[in] is_64_bit `true` if this object is to be configured for 64-bit. - explicit Ptracer(bool is_64_bit); + //! \param[in] can_log Whether methods in this class can log error messages. + Ptracer(bool is_64_bit, bool can_log); //! \brief Constructs this object without a pre-determined bitness. //! //! Initialize() must be successfully called before making any other calls on //! this object. - Ptracer(); + //! + //! \param[in] can_log Whether methods in this class can log error messages. + explicit Ptracer(bool can_log); ~Ptracer(); @@ -49,7 +52,8 @@ class Ptracer { //! ID is \a pid. //! //! \param[in] pid The process ID of the process to initialize with. - //! \return `true` on success. `false` on failure with a message logged. + //! \return `true` on success. `false` on failure with a message logged, if + //! enabled. bool Initialize(pid_t pid); //! \brief Return `true` if this object is configured for 64-bit. @@ -63,11 +67,36 @@ class Ptracer { //! //! \param[in] tid The thread ID of the thread to collect information for. //! \param[out] info A ThreadInfo for the thread. - //! \return `true` on success. `false` on failure with a message logged. + //! \return `true` on success. `false` on failure with a message logged, if + //! enabled. bool GetThreadInfo(pid_t tid, ThreadInfo* info); + //! \brief Uses `ptrace` to read memory from the process with process ID \a + //! pid, up to a maximum number of bytes. + //! + //! The target process should already be attached before calling this method. + //! \see ScopedPtraceAttach + //! + //! \param[in] pid The process ID whose memory to read. + //! \param[in] address The base address of the region to read. + //! \param[in] size The size of the memory region to read. \a buffer must be + //! at least this size. + //! \param[out] buffer The buffer to fill with the data read. + //! \return the number of bytes read, 0 if there are no more bytes to read, or + //! -1 on failure with a message logged if logging is enabled. + ssize_t ReadUpTo(pid_t pid, + LinuxVMAddress address, + size_t size, + char* buffer); + private: + ssize_t ReadLastBytes(pid_t pid, + LinuxVMAddress address, + size_t size, + char* buffer); + bool is_64_bit_; + bool can_log_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(Ptracer); diff --git a/util/linux/ptracer_test.cc b/util/linux/ptracer_test.cc index b32e258a..9a759850 100644 --- a/util/linux/ptracer_test.cc +++ b/util/linux/ptracer_test.cc @@ -16,10 +16,10 @@ #include "build/build_config.h" #include "gtest/gtest.h" +#include "test/linux/get_tls.h" #include "test/multiprocess.h" #include "util/file/file_io.h" #include "util/linux/scoped_ptrace_attach.h" -#include "util/misc/from_pointer_cast.h" namespace crashpad { namespace test { @@ -45,7 +45,7 @@ class SameBitnessTest : public Multiprocess { ScopedPtraceAttach attach; ASSERT_TRUE(attach.ResetAttach(ChildPID())); - Ptracer ptracer(am_64_bit); + Ptracer ptracer(am_64_bit, /* can_log= */ true); EXPECT_EQ(ptracer.Is64Bit(), am_64_bit); @@ -60,24 +60,7 @@ class SameBitnessTest : public Multiprocess { } void MultiprocessChild() override { - LinuxVMAddress expected_tls; -#if defined(ARCH_CPU_ARMEL) - // 0xffff0fe0 is the address of the kernel user helper __kuser_get_tls(). - auto kuser_get_tls = reinterpret_cast(0xffff0fe0); - expected_tls = FromPointerCast(kuser_get_tls()); -#elif defined(ARCH_CPU_ARM64) - // Linux/aarch64 places the tls address in system register tpidr_el0. - asm("mrs %0, tpidr_el0" : "=r"(expected_tls)); -#elif defined(ARCH_CPU_X86) - uint32_t expected_tls_32; - asm("movl %%gs:0x0, %0" : "=r"(expected_tls_32)); - expected_tls = expected_tls_32; -#elif defined(ARCH_CPU_X86_64) - asm("movq %%fs:0x0, %0" : "=r"(expected_tls)); -#else -#error Port. -#endif // ARCH_CPU_ARMEL - + LinuxVMAddress expected_tls = GetTLS(); CheckedWriteFile(WritePipeHandle(), &expected_tls, sizeof(expected_tls)); CheckedReadFileAtEOF(ReadPipeHandle()); diff --git a/util/linux/scoped_pr_set_ptracer.cc b/util/linux/scoped_pr_set_ptracer.cc new file mode 100644 index 00000000..c7aeefc6 --- /dev/null +++ b/util/linux/scoped_pr_set_ptracer.cc @@ -0,0 +1,37 @@ +// 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 "util/linux/scoped_pr_set_ptracer.h" + +#include +#include + +#include "base/logging.h" + +namespace crashpad { + +ScopedPrSetPtracer::ScopedPrSetPtracer(pid_t pid, bool may_log) + : success_(false), may_log_(may_log) { + success_ = prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0; + PLOG_IF(ERROR, !success_ && may_log && errno != EINVAL) << "prctl"; +} + +ScopedPrSetPtracer::~ScopedPrSetPtracer() { + if (success_) { + int res = prctl(PR_SET_PTRACER, 0, 0, 0, 0); + PLOG_IF(ERROR, res != 0 && may_log_) << "prctl"; + } +} + +} // namespace crashpad diff --git a/util/linux/scoped_pr_set_ptracer.h b/util/linux/scoped_pr_set_ptracer.h new file mode 100644 index 00000000..2bc8677b --- /dev/null +++ b/util/linux/scoped_pr_set_ptracer.h @@ -0,0 +1,51 @@ +// 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_UTIL_LINUX_SCOPED_PR_SET_PTRACER_H_ +#define CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_PTRACER_H_ + +#include + +#include "base/macros.h" + +namespace crashpad { + +class ScopedPrSetPtracer { + public: + //! \brief Uses `PR_SET_PTRACER` to set \a pid as the caller's ptracer. + //! + //! `PR_SET_PTRACER` is only supported if the Yama Linux security module (LSM) + //! is enabled. Otherwise, `prctl(PR_SET_PTRACER, ...)` fails with `EINVAL`. + //! See linux-4.9.20/security/yama/yama_lsm.c yama_task_prctl() and + //! linux-4.9.20/kernel/sys.c [sys_]prctl(). + //! + //! An error message will be logged on failure only if \a may_log is `true` + //! and `prctl` does not fail with `EINVAL`; + //! + //! \param[in] pid The process ID of the process to make the caller's ptracer. + //! \param[in] may_log if `true`, this class may log error messages. + ScopedPrSetPtracer(pid_t pid, bool may_log); + + ~ScopedPrSetPtracer(); + + private: + bool success_; + bool may_log_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPrSetPtracer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_SCOPED_PR_SET_PTRACER_H_ diff --git a/util/linux/scoped_ptrace_attach_test.cc b/util/linux/scoped_ptrace_attach_test.cc index 99072e4c..d009e682 100644 --- a/util/linux/scoped_ptrace_attach_test.cc +++ b/util/linux/scoped_ptrace_attach_test.cc @@ -15,7 +15,6 @@ #include "util/linux/scoped_ptrace_attach.h" #include -#include #include #include @@ -23,44 +22,19 @@ #include "test/errors.h" #include "test/multiprocess.h" #include "util/file/file_io.h" +#include "util/linux/scoped_pr_set_ptracer.h" namespace crashpad { namespace test { namespace { -class ScopedPrSetPtracer { - public: - explicit ScopedPrSetPtracer(pid_t pid) { - // PR_SET_PTRACER is only supported if the Yama Linux security module (LSM) - // is enabled. Otherwise, this prctl() call fails with EINVAL. See - // linux-4.9.20/security/yama/yama_lsm.c yama_task_prctl() and - // linux-4.9.20/kernel/sys.c [sys_]prctl(). - // - // If Yama is not enabled, the default ptrace restrictions should be - // sufficient for these tests. - // - // If Yama is enabled, then /proc/sys/kernel/yama/ptrace_scope must be 0 - // (YAMA_SCOPE_DISABLED, in which case this prctl() is not necessary) or 1 - // (YAMA_SCOPE_RELATIONAL) for these tests to succeed. If it is 2 - // (YAMA_SCOPE_CAPABILITY) then the test requires CAP_SYS_PTRACE, and if it - // is 3 (YAMA_SCOPE_NO_ATTACH), these tests will fail. - success_ = prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0; - if (!success_) { - EXPECT_EQ(errno, EINVAL) << ErrnoMessage("prctl"); - } - } - - ~ScopedPrSetPtracer() { - if (success_) { - EXPECT_EQ(prctl(PR_SET_PTRACER, 0, 0, 0, 0), 0) << ErrnoMessage("prctl"); - } - } - - private: - bool success_; - - DISALLOW_COPY_AND_ASSIGN(ScopedPrSetPtracer); -}; +// If Yama is not enabled, the default ptrace restrictions should be +// sufficient for these tests. +// +// If Yama is enabled, then /proc/sys/kernel/yama/ptrace_scope must be 0 +// (YAMA_SCOPE_DISABLED) or 1 (YAMA_SCOPE_RELATIONAL) for these tests to +// succeed. If it is 2 (YAMA_SCOPE_CAPABILITY) then the test requires +// CAP_SYS_PTRACE, and if it is 3 (YAMA_SCOPE_NO_ATTACH), these tests will fail. class AttachTest : public Multiprocess { public: @@ -101,7 +75,7 @@ class AttachToChildTest : public AttachTest { } void MultiprocessChild() override { - ScopedPrSetPtracer set_ptracer(getppid()); + ScopedPrSetPtracer set_ptracer(getppid(), /* may_log= */ true); char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); @@ -124,7 +98,7 @@ class AttachToParentResetTest : public AttachTest { private: void MultiprocessParent() override { - ScopedPrSetPtracer set_ptracer(ChildPID()); + ScopedPrSetPtracer set_ptracer(ChildPID(), /* may_log= */ true); char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); @@ -166,7 +140,7 @@ class AttachToParentDestructorTest : public AttachTest { private: void MultiprocessParent() override { - ScopedPrSetPtracer set_ptracer(ChildPID()); + ScopedPrSetPtracer set_ptracer(ChildPID(), /* may_log= */ true); char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); diff --git a/util/linux/thread_info.h b/util/linux/thread_info.h index 4208be67..5b55c24a 100644 --- a/util/linux/thread_info.h +++ b/util/linux/thread_info.h @@ -67,6 +67,18 @@ union ThreadContext { uint32_t pc; uint32_t cpsr; uint32_t orig_r0; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Reflects output format of static int gpr32_get(), defined in + // arch/mips/kernel/ptrace.c in kernel source + uint32_t padding0_[6]; + uint32_t regs[32]; + uint32_t lo; + uint32_t hi; + uint32_t cp0_epc; + uint32_t cp0_badvaddr; + uint32_t cp0_status; + uint32_t cp0_cause; + uint32_t padding1_; #else #error Port. #endif // ARCH_CPU_X86_FAMILY @@ -110,6 +122,16 @@ union ThreadContext { uint64_t sp; uint64_t pc; uint64_t pstate; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Reflects output format of static int gpr64_get(), defined in + // arch/mips/kernel/ptrace.c in kernel source + uint64_t regs[32]; + uint64_t lo; + uint64_t hi; + uint64_t cp0_epc; + uint64_t cp0_badvaddr; + uint64_t cp0_status; + uint64_t cp0_cause; #else #error Port. #endif // ARCH_CPU_X86_FAMILY @@ -119,15 +141,19 @@ union ThreadContext { using NativeThreadContext = user_regs_struct; #elif defined(ARCH_CPU_ARMEL) using NativeThreadContext = user_regs; +#elif defined(ARCH_CPU_MIPS_FAMILY) +// No appropriate NativeThreadsContext type available for MIPS #else #error Port. #endif // ARCH_CPU_X86_FAMILY || ARCH_CPU_ARM64 +#if !defined(ARCH_CPU_MIPS_FAMILY) #if defined(ARCH_CPU_32_BITS) static_assert(sizeof(t32_t) == sizeof(NativeThreadContext), "Size mismatch"); #else // ARCH_CPU_64_BITS static_assert(sizeof(t64_t) == sizeof(NativeThreadContext), "Size mismatch"); #endif // ARCH_CPU_32_BITS +#endif // !ARCH_CPU_MIPS_FAMILY }; static_assert(std::is_standard_layout::value, "Not standard layout"); @@ -176,13 +202,22 @@ union FloatContext { } fpregs; // Reflects user_vfp in sys/user.h. - struct vfp { + struct vfp_t { uint64_t fpregs[32]; uint32_t fpscr; } vfp; bool have_fpregs; bool have_vfp; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Reflects data format filled by ptrace_getfpregs() in + // arch/mips/kernel/ptrace.c + struct { + float _fp_fregs; + unsigned int _fp_pad; + } fpregs[32]; + uint32_t fpcsr; + uint32_t fpu_id; #else #error Port. #endif // ARCH_CPU_X86_FAMILY @@ -211,6 +246,12 @@ union FloatContext { uint32_t fpsr; uint32_t fpcr; uint8_t padding[8]; +#elif defined(ARCH_CPU_MIPS_FAMILY) + // Reflects data format filled by ptrace_getfpregs() in + // arch/mips/kernel/ptrace.c + double fpregs[32]; + uint32_t fpcsr; + uint32_t fpu_id; #else #error Port. #endif // ARCH_CPU_X86_FAMILY @@ -232,9 +273,13 @@ union FloatContext { "Size mismatch"); #elif defined(ARCH_CPU_ARMEL) static_assert(sizeof(f32_t::fpregs) == sizeof(user_fpregs), "Size mismatch"); +#if !defined(__GLIBC__) static_assert(sizeof(f32_t::vfp) == sizeof(user_vfp), "Size mismatch"); +#endif #elif defined(ARCH_CPU_ARM64) static_assert(sizeof(f64) == sizeof(user_fpsimd_struct), "Size mismatch"); +#elif defined(ARCH_CPU_MIPS_FAMILY) +// No appropriate floating point context native type for available MIPS. #else #error Port. #endif // ARCH_CPU_X86 diff --git a/util/linux/traits.h b/util/linux/traits.h index ca957681..dc5463d8 100644 --- a/util/linux/traits.h +++ b/util/linux/traits.h @@ -26,6 +26,7 @@ struct Traits32 { using ULong = uint32_t; using Clock = Long; using Size = uint32_t; + using Char_64Only = Nothing; using ULong_32Only = ULong; using ULong_64Only = Nothing; using UInteger32_64Only = Nothing; @@ -38,6 +39,7 @@ struct Traits64 { using ULong = uint64_t; using Clock = Long; using Size = uint64_t; + using Char_64Only = char; using ULong_32Only = Nothing; using ULong_64Only = ULong; using UInteger32_64Only = uint32_t; diff --git a/util/mac/mac_util.cc b/util/mac/mac_util.cc index 7c51cc2e..5f79701b 100644 --- a/util/mac/mac_util.cc +++ b/util/mac/mac_util.cc @@ -66,9 +66,9 @@ int DarwinMajorVersion() { // base::OperatingSystemVersionNumbers calls Gestalt(), which is a // higher-level function than is needed. It might perform unnecessary // operations. On 10.6, it was observed to be able to spawn threads (see - // http://crbug.com/53200). It might also read files or perform other blocking - // operations. Actually, nobody really knows for sure just what Gestalt() - // might do, or what it might be taught to do in the future. + // https://crbug.com/53200). It might also read files or perform other + // blocking operations. Actually, nobody really knows for sure just what + // Gestalt() might do, or what it might be taught to do in the future. // // uname(), on the other hand, is implemented as a simple series of sysctl() // system calls to obtain the relevant data from the kernel. The data is diff --git a/util/mach/child_port.defs b/util/mach/child_port.defs index b227f00f..b0cf982d 100644 --- a/util/mach/child_port.defs +++ b/util/mach/child_port.defs @@ -18,14 +18,14 @@ // child_port provides an interface for port rights to be transferred between // tasks. Its expected usage is for processes to be able to pass port rights // across IPC boundaries. A child process may wish to give its parent a copy of -// of a send right to its own task port, or a parent process may wish to give a +// a send right to its own task port, or a parent process may wish to give a // receive right to a child process that implements a server. // -// This Mach subsystem defines the lowest-level interface for these rights to -// be transferred. Most users will not user this interface directly, but will -// use ChildPortHandshake, which builds on this interface by providing client -// and server implementations, along with a protocol for establishing -// communication in a parent-child process relationship. +// This Mach subsystem defines the lowest-level interface for these rights to be +// transferred. Most users will not user this interface directly, but will use +// ChildPortHandshake, which builds on this interface by providing client and +// server implementations, along with a protocol for establishing communication +// in a parent-child process relationship. subsystem child_port 10011; serverprefix handle_; diff --git a/util/mach/child_port_server.cc b/util/mach/child_port_server.cc index 9a34d431..a3223028 100644 --- a/util/mach/child_port_server.cc +++ b/util/mach/child_port_server.cc @@ -18,25 +18,6 @@ #include "util/mach/child_portServer.h" #include "util/mach/mach_message.h" -extern "C" { - -// This function is not used, and is in fact obsoleted by the other -// functionality implemented in this file. The standard MIG-generated -// child_port_server() (in child_portServer.c) server dispatch routine usable -// with the standard mach_msg_server() function calls out to this function. -// child_port_server() is unused and is replaced by the more flexible -// ChildPortServer, but the linker still needs to see this function definition. - -kern_return_t handle_child_port_check_in(child_port_server_t server, - child_port_token_t token, - mach_port_t port, - mach_msg_type_name_t right_type) { - NOTREACHED(); - return KERN_FAILURE; -} - -} // extern "C" - namespace { // There is no predefined constant for this. diff --git a/util/mach/composite_mach_message_server.h b/util/mach/composite_mach_message_server.h index fe3446e0..785af4dc 100644 --- a/util/mach/composite_mach_message_server.h +++ b/util/mach/composite_mach_message_server.h @@ -29,7 +29,7 @@ namespace crashpad { //! simultaneous use in a single MachMessageServer::Run() call. //! //! This class implements a MachMessageServer::Interface that contains other -//! other MachMessageServer::Interface objects. +//! MachMessageServer::Interface objects. //! //! In some situations, it may be desirable for a Mach message server to handle //! messages from distinct MIG subsystems with distinct @@ -59,9 +59,9 @@ class CompositeMachMessageServer : public MachMessageServer::Interface { //! \copydoc MachMessageServer::Interface::MachMessageServerFunction() //! //! This implementation forwards the message to an appropriate handler added - //! by AddHandler() on the basis of the \a in request message’s message ID. - //! If no appropriate handler exists, the \a out reply message is treated as - //! a `mig_reply_error_t`, its return code is set to `MIG_BAD_ID`, and `false` + //! by AddHandler() on the basis of the \a in request message’s message ID. If + //! no appropriate handler exists, the \a out reply message is treated as a + //! `mig_reply_error_t`, its return code is set to `MIG_BAD_ID`, and `false` //! is returned. bool MachMessageServerFunction(const mach_msg_header_t* in, mach_msg_header_t* out, diff --git a/util/mach/composite_mach_message_server_test.cc b/util/mach/composite_mach_message_server_test.cc index 74e1707a..d45eca0b 100644 --- a/util/mach/composite_mach_message_server_test.cc +++ b/util/mach/composite_mach_message_server_test.cc @@ -18,7 +18,7 @@ #include "base/strings/stringprintf.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "util/mach/mach_message.h" namespace crashpad { diff --git a/util/mach/exc_client_variants.h b/util/mach/exc_client_variants.h index 3fa06a0b..8f52ed53 100644 --- a/util/mach/exc_client_variants.h +++ b/util/mach/exc_client_variants.h @@ -44,7 +44,7 @@ namespace crashpad { //! //! \a thread and \a task are only used when \a behavior indicates that the //! exception message will carry identity information, when it has the value -//! value `EXCEPTION_DEFAULT` or `EXCEPTION_STATE_IDENTITY`, possibly with +//! `EXCEPTION_DEFAULT` or `EXCEPTION_STATE_IDENTITY`, possibly with //! `MACH_EXCEPTION_CODES` also set. In other cases, these parameters are unused //! and may be set to `THREAD_NULL` and `TASK_NULL`, respectively. //! diff --git a/util/mach/exc_server_variants.cc b/util/mach/exc_server_variants.cc index ff918065..e7174ac7 100644 --- a/util/mach/exc_server_variants.cc +++ b/util/mach/exc_server_variants.cc @@ -30,98 +30,6 @@ #include "util/mach/mach_excServer.h" #include "util/mach/mach_message.h" -extern "C" { - -// These six functions are not used, and are in fact obsoleted by the other -// functionality implemented in this file. The standard MIG-generated exc_server -// (in excServer.c) and mach_exc_server (in mach_excServer.c) server dispatch -// routines usable with the standard mach_msg_server() function call out to -// these functions. exc_server() and mach_exc_server() are unused and are -// replaced by the more flexible ExcServer and MachExcServer, but the linker -// still needs to see these six function definitions. - -kern_return_t catch_exception_raise(exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t catch_exception_raise_state( - exception_handler_t exception_port, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - thread_state_t old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t catch_exception_raise_state_identity( - exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - thread_state_t old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t catch_mach_exception_raise(exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - mach_exception_data_t code, - mach_msg_type_number_t code_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t catch_mach_exception_raise_state( - exception_handler_t exception_port, - exception_type_t exception, - mach_exception_data_t code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - thread_state_t old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t catch_mach_exception_raise_state_identity( - exception_handler_t exception_port, - thread_t thread, - task_t task, - exception_type_t exception, - mach_exception_data_t code, - mach_msg_type_number_t code_count, - thread_state_flavor_t* flavor, - thread_state_t old_state, - mach_msg_type_number_t old_state_count, - thread_state_t new_state, - mach_msg_type_number_t* new_state_count) { - NOTREACHED(); - return KERN_FAILURE; -} - -} // extern "C" - namespace crashpad { namespace { diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc index 358cd1ec..5e67bfe3 100644 --- a/util/mach/exc_server_variants_test.cc +++ b/util/mach/exc_server_variants_test.cc @@ -968,7 +968,10 @@ class TestExcServerVariants : public MachMultiprocess, behavior_(behavior), flavor_(flavor), state_count_(state_count), - handled_(false) {} + handled_(false) { + // This is how the __builtin_trap() in MachMultiprocessChild() appears. + SetExpectedChildTermination(kTerminationSignal, SIGILL); + } // UniversalMachExcServer::Interface: @@ -1013,7 +1016,6 @@ class TestExcServerVariants : public MachMultiprocess, if (exception == EXC_CRASH && code_count >= 1) { int signal; ExcCrashRecoverOriginalException(code[0], nullptr, &signal); - SetExpectedChildTermination(kTerminationSignal, signal); } const bool has_state = ExceptionBehaviorHasState(behavior); diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc index 224217bf..af9cc726 100644 --- a/util/mach/exception_ports_test.cc +++ b/util/mach/exception_ports_test.cc @@ -132,7 +132,12 @@ class TestExceptionPorts : public MachMultiprocess, set_on_(set_on), set_type_(set_type), who_crashes_(who_crashes), - handled_(false) {} + handled_(false) { + if (who_crashes_ != kNobodyCrashes) { + // This is how the __builtin_trap() in Child::Crash() appears. + SetExpectedChildTermination(kTerminationSignal, SIGILL); + } + } SetOn set_on() const { return set_on_; } SetType set_type() const { return set_type_; } @@ -190,8 +195,6 @@ class TestExceptionPorts : public MachMultiprocess, // The child crashed with __builtin_trap(), which shows up as SIGILL. EXPECT_EQ(signal, SIGILL); - - SetExpectedChildTermination(kTerminationSignal, signal); } EXPECT_EQ(AuditPIDFromMachMessageTrailer(trailer), 0); diff --git a/util/mach/mig.py b/util/mach/mig.py index 992f3e1a..9833c8c5 100755 --- a/util/mach/mig.py +++ b/util/mach/mig.py @@ -73,6 +73,18 @@ def FixServerImplementation(implementation): # 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 \n' + contents + file.seek(0) file.truncate() file.write(contents) diff --git a/util/mach/notify_server.cc b/util/mach/notify_server.cc index 2dbdf73e..711529b1 100644 --- a/util/mach/notify_server.cc +++ b/util/mach/notify_server.cc @@ -18,46 +18,6 @@ #include "util/mach/mach_message.h" #include "util/mach/notifyServer.h" -extern "C" { - -// These five functions are not used, and are in fact obsoleted by the other -// functionality implemented in this file. The standard MIG-generated -// notify_server() (in notifyServer.c) server dispatch routine usable with the -// standard mach_msg_server() function calls out to this function. -// notify_server() is unused and is replaced by the more flexible NotifyServer, -// but the linker still needs to see these five function definitions. - -kern_return_t do_mach_notify_port_deleted(notify_port_t notify, - mach_port_name_t name) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t do_mach_notify_port_destroyed(notify_port_t notify, - mach_port_t rights) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t do_mach_notify_no_senders(notify_port_t notify, - mach_port_mscount_t mscount) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t do_mach_notify_send_once(notify_port_t notify) { - NOTREACHED(); - return KERN_FAILURE; -} - -kern_return_t do_mach_notify_dead_name(notify_port_t notify, - mach_port_name_t name) { - NOTREACHED(); - return KERN_FAILURE; -} - -} // extern "C" - namespace { // The MIG-generated __MIG_check__Request__*() functions are not declared as diff --git a/util/mach/symbolic_constants_mach.cc b/util/mach/symbolic_constants_mach.cc index b7296be2..8f95915e 100644 --- a/util/mach/symbolic_constants_mach.cc +++ b/util/mach/symbolic_constants_mach.cc @@ -239,7 +239,8 @@ bool StringToException(const base::StringPiece& string, } if (options & kAllowNumber) { - return StringToNumber(string, reinterpret_cast(exception)); + return StringToNumber(std::string(string.data(), string.length()), + reinterpret_cast(exception)); } return false; @@ -352,7 +353,7 @@ bool StringToExceptionMask(const base::StringPiece& string, } if (options & kAllowNumber) { - return StringToNumber(string, + return StringToNumber(std::string(string.data(), string.length()), reinterpret_cast(exception_mask)); } @@ -452,7 +453,8 @@ bool StringToExceptionBehavior(const base::StringPiece& string, if (options & kAllowNumber) { exception_behavior_t temp_behavior; - if (!StringToNumber(sp, reinterpret_cast(&temp_behavior))) { + if (!StringToNumber(std::string(sp.data(), sp.length()), + reinterpret_cast(&temp_behavior))) { return false; } build_behavior |= temp_behavior; @@ -539,7 +541,8 @@ bool StringToThreadStateFlavor(const base::StringPiece& string, } if (options & kAllowNumber) { - return StringToNumber(string, reinterpret_cast(flavor)); + return StringToNumber(std::string(string.data(), string.length()), + reinterpret_cast(flavor)); } return false; diff --git a/util/misc/address_types.h b/util/misc/address_types.h index bfce35fc..870938d3 100644 --- a/util/misc/address_types.h +++ b/util/misc/address_types.h @@ -27,6 +27,8 @@ #include "util/win/address_types.h" #elif defined(OS_LINUX) || defined(OS_ANDROID) #include "util/linux/address_types.h" +#elif defined(OS_FUCHSIA) +#include #else #error "Unhandled OS type" #endif @@ -58,6 +60,11 @@ using VMSize = WinVMSize; using VMAddress = LinuxVMAddress; using VMSize = LinuxVMSize; +#elif defined(OS_FUCHSIA) + +using VMAddress = zx_vaddr_t; +using VMSize = size_t; + #endif //! \brief Type used to represent an offset from a VMAddress, potentially diff --git a/util/misc/as_underlying_type.h b/util/misc/as_underlying_type.h index 8afd0ef3..ba673ae5 100644 --- a/util/misc/as_underlying_type.h +++ b/util/misc/as_underlying_type.h @@ -24,7 +24,8 @@ namespace crashpad { //! \param[in] from The value to be casted. //! \return \a from casted to its underlying type. template -typename std::underlying_type::type AsUnderlyingType(From from) { +constexpr typename std::underlying_type::type AsUnderlyingType( + From from) { return static_cast::type>(from); } diff --git a/util/misc/capture_context.h b/util/misc/capture_context.h new file mode 100644 index 00000000..541589df --- /dev/null +++ b/util/misc/capture_context.h @@ -0,0 +1,84 @@ +// 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_MISC_CAPTURE_CONTEXT_H_ +#define CRASHPAD_UTIL_MISC_CAPTURE_CONTEXT_H_ + +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#include +#elif defined(OS_WIN) +#include +#elif defined(OS_LINUX) || defined(OS_ANDROID) +#include +#elif defined(OS_FUCHSIA) +#include +#endif // OS_MACOSX + +namespace crashpad { + +#if defined(OS_MACOSX) +#if defined(ARCH_CPU_X86_FAMILY) +using NativeCPUContext = x86_thread_state; +#endif +#elif defined(OS_WIN) +using NativeCPUContext = CONTEXT; +#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) +using NativeCPUContext = ucontext_t; +#endif // OS_MACOSX + +//! \brief Saves the CPU context. +//! +//! The CPU context will be captured as accurately and completely as possible, +//! containing an atomic snapshot at the point of this function’s return. This +//! function does not modify any registers. +//! +//! This function is a replacement for `RtlCaptureContext()` and `getcontext()` +//! which contain bugs and/or limitations. +//! +//! On 32-bit x86, `RtlCaptureContext()` requires that `ebp` be used as a frame +//! pointer, and returns `ebp`, `esp`, and `eip` out of sync with the other +//! registers. Both the 32-bit x86 and 64-bit x86_64 versions of +//! `RtlCaptureContext()` capture only the state of the integer registers, +//! ignoring floating-point and vector state. +//! +//! \param[out] cpu_context The structure to store the context in. +//! +//! \note The ABI may require that this function's argument is passed by +//! register, preventing this fuction from saving the original value of that +//! register. This occurs in the following circumstances: +//! +//! OS | Architecture | Register +//! --------------------|--------------|--------- +//! Win | x86_64 | `%%rcx` +//! macOS/Linux/Fuchsia | x86_64 | `%%rdi` +//! Linux | ARM/ARM64 | `r0`/`x0` +//! Linux | MIPS/MIPS64 | `$a0` +//! +//! Additionally, the value `LR` on ARM/ARM64 will be the return address of +//! this function. +//! +//! If the value of these register prior to calling this function are needed +//! they must be obtained separately prior to calling this function. For +//! example: +//! \code +//! uint64_t rdi; +//! asm("movq %%rdi, %0" : "=m"(rdi)); +//! \endcode +void CaptureContext(NativeCPUContext* cpu_context); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_CAPTURE_CONTEXT_H_ diff --git a/util/win/capture_context_broken.cc b/util/misc/capture_context_broken.cc similarity index 90% rename from util/win/capture_context_broken.cc rename to util/misc/capture_context_broken.cc index 4a641515..ab7a5974 100644 --- a/util/win/capture_context_broken.cc +++ b/util/misc/capture_context_broken.cc @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/win/capture_context.h" +#include "util/misc/capture_context.h" #include "base/logging.h" namespace crashpad { -void CaptureContext(CONTEXT* context) { +void CaptureContext(NativeCPUContext* context) { // Don't use this file in production. CHECK(false) << "Don't use this! For cross builds only. See https://crbug.com/762167."; diff --git a/util/misc/capture_context_fuchsia.S b/util/misc/capture_context_fuchsia.S new file mode 100644 index 00000000..8e5c0cbe --- /dev/null +++ b/util/misc/capture_context_fuchsia.S @@ -0,0 +1,173 @@ +// 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. + +// namespace crashpad { +// void CaptureContext(ucontext_t* context); +// } // namespace crashpad + +#define CAPTURECONTEXT_SYMBOL _ZN8crashpad14CaptureContextEP8ucontext + + .text + .globl CAPTURECONTEXT_SYMBOL +#if defined(__x86_64__) + .balign 16, 0x90 +#elif defined(__aarch64__) + .balign 4, 0x0 +#endif + +CAPTURECONTEXT_SYMBOL: + +#if defined(__x86_64__) + + .cfi_startproc + + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + + // Note that 16-byte stack alignment is not maintained because this function + // does not call out to any other. + + // pushfq first, because some instructions (but probably none used here) + // affect %rflags. %rflags will be in -8(%rbp). + pushfq + + // General-purpose registers whose values haven’t changed can be captured + // directly. + movq %r8, 0x28(%rdi) // context->uc_mcontext.r8 + movq %r9, 0x30(%rdi) // context->uc_mcontext.r9 + movq %r10, 0x38(%rdi) // context->uc_mcontext.r10 + movq %r11, 0x40(%rdi) // context->uc_mcontext.r11 + movq %r12, 0x48(%rdi) // context->uc_mcontext.r12 + movq %r13, 0x50(%rdi) // context->uc_mcontext.r13 + movq %r14, 0x58(%rdi) // context->uc_mcontext.r14 + movq %r15, 0x60(%rdi) // context->uc_mcontext.r15 + + // Because of the calling convention, there’s no way to recover the value of + // the caller’s %rdi as it existed prior to calling this function. This + // function captures a snapshot of the register state at its return, which + // involves %rdi containing a pointer to its first argument. Callers that + // require the value of %rdi prior to calling this function should obtain it + // separately. For example: + // uint64_t rdi; + // asm("movq %%rdi, %0" : "=m"(rdi)); + movq %rdi, 0x68(%rdi) // context->uc_mcontext.rdi + + movq %rsi, 0x70(%rdi) // context->uc_mcontext.rsi + + // Use %r8 as a scratch register now that it has been saved. + // The original %rbp was saved on the stack in this function’s prologue. + movq (%rbp), %r8 + movq %r8, 0x78(%rdi) // context->uc_mcontext.rbp + + // Save the remaining general-purpose registers. + movq %rbx, 0x80(%rdi) // context->uc_mcontext.rbx + movq %rdx, 0x88(%rdi) // context->uc_mcontext.rdx + movq %rax, 0x90(%rdi) // context->uc_mcontext.rax + movq %rcx, 0x98(%rdi) // context->uc_mcontext.rcx + + // %rsp was saved in %rbp in this function’s prologue, but the caller’s %rsp + // is 16 more than this value: 8 for the original %rbp saved on the stack in + // this function’s prologue, and 8 for the return address saved on the stack + // by the call instruction that reached this function. + leaq 16(%rbp), %r8 + movq %r8, 0xa0(%rdi) // context->uc_mcontext.rsp + + // The return address saved on the stack used by the call of this function is + // likely more useful than the current RIP here. + movq 8(%rbp), %r8 + movq %r8, 0xa8(%rdi) // context->uc_mcontext.rip + + // The original %rflags was saved on the stack above. + movq -8(%rbp), %r8 + movq %r8, 0xb0(%rdi) // context->uc_mcontext.eflags + + // Save the segment registers + movw %cs, 0xb8(%rdi) // context->uc_mcontext.cs + movw %gs, 0xba(%rdi) // context->uc_mcontext.gs + movw %fs, 0xbc(%rdi) // context->uc_mcontext.fs + + xorw %ax, %ax + movw %ax, 0xbe(%rdi) // context->uc_mcontext.padding + + // Zero out the remainder of the unused pseudo-registers + xorq %r8, %r8 + movq %r8, 0xc0(%rdi) // context->uc_mcontext.err + movq %r8, 0xc8(%rdi) // context->uc_mcontext.trapno + movq %r8, 0xd0(%rdi) // context->uc_mcontext.oldmask + movq %r8, 0xd8(%rdi) // context->uc_mcontext.cr2 + + // Clean up by restoring clobbered registers, even those considered volatile + // by the ABI, so that the captured context represents the state at this + // function’s exit. + movq 0x90(%rdi), %rax + movq 0x28(%rdi), %r8 + + // TODO(scottmg): save floating-point registers. + + popfq + + popq %rbp + + ret + + .cfi_endproc + +#elif defined(__aarch64__) + + // Zero out fault_address, which is unused. + str x31, [x0, #0xb0] // context->uc_mcontext.fault_address + + // Save general purpose registers in context->uc_mcontext.regs[i]. + // The original x0 can't be recovered. + stp x0, x1, [x0, #0xb8] + stp x2, x3, [x0, #0xc8] + stp x4, x5, [x0, #0xd8] + stp x6, x7, [x0, #0xe8] + stp x8, x9, [x0, #0xf8] + stp x10, x11, [x0, #0x108] + stp x12, x13, [x0, #0x118] + stp x14, x15, [x0, #0x128] + stp x16, x17, [x0, #0x138] + stp x18, x19, [x0, #0x148] + stp x20, x21, [x0, #0x158] + stp x22, x23, [x0, #0x168] + stp x24, x25, [x0, #0x178] + stp x26, x27, [x0, #0x188] + stp x28, x29, [x0, #0x198] + + // The original LR can't be recovered. + str LR, [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 + + // NZCV, pstate, and CPSR are synonyms. + 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. + + ret + +#endif // __x86_64__ diff --git a/util/misc/capture_context_linux.S b/util/misc/capture_context_linux.S new file mode 100644 index 00000000..42999a90 --- /dev/null +++ b/util/misc/capture_context_linux.S @@ -0,0 +1,424 @@ +// 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. + +// namespace crashpad { +// void CaptureContext(ucontext_t* context); +// } // namespace crashpad + +// The type name for a ucontext_t varies by libc implementation and version. +// Bionic and glibc 2.25 typedef ucontext_t from struct ucontext. glibc 2.26+ +// typedef ucontext_t from struct ucontext_t. Alias the symbol names to maintain +// compatibility with both possibilities. +#define CAPTURECONTEXT_SYMBOL _ZN8crashpad14CaptureContextEP10ucontext_t +#define CAPTURECONTEXT_SYMBOL2 _ZN8crashpad14CaptureContextEP8ucontext + + .text + .globl CAPTURECONTEXT_SYMBOL + .globl CAPTURECONTEXT_SYMBOL2 +#if defined(__i386__) || defined(__x86_64__) + .balign 16, 0x90 +#elif defined(__arm__) || defined(__aarch64__) || defined(__mips__) + .balign 4, 0x0 +#endif + +CAPTURECONTEXT_SYMBOL: +CAPTURECONTEXT_SYMBOL2: + +#if defined(__i386__) + + .cfi_startproc + + pushl %ebp + .cfi_def_cfa_offset 8 + .cfi_offset %ebp, -8 + movl %esp, %ebp + .cfi_def_cfa_register %ebp + + // Note that 16-byte stack alignment is not maintained because this function + // does not call out to any other. + + // pushfl first, because some instructions (but probably none used here) + // affect %eflags. %eflags will be in -4(%ebp). + pushfl + + // Save the original value of %eax, and use %eax to hold the ucontext_t* + // argument. The original value of %eax will be in -8(%ebp). + pushl %eax + movl 8(%ebp), %eax + + // Save the original value of %ecx, and use %ecx as a scratch register. + pushl %ecx + + // The segment registers are 16 bits wide, but mcontext_t declares them + // as unsigned 32-bit values, so zero the top half. + xorl %ecx, %ecx + movw %gs, %cx + movl %ecx, 0x14(%eax) // context->uc_mcontext.xgs + movw %fs, %cx + movl %ecx, 0x18(%eax) // context->uc_mcontext.xfs + movw %es, %cx + movl %ecx, 0x1c(%eax) // context->uc_mcontext.xes + movw %ds, %cx + movl %ecx, 0x20(%eax) // context->uc_mcontext.xds + + // General-purpose registers whose values haven’t changed can be captured + // directly. + movl %edi, 0x24(%eax) // context->uc_mcontext.edi + movl %esi, 0x28(%eax) // context->uc_mcontext.esi + + // The original %ebp was saved on the stack in this function’s prologue. + movl (%ebp), %ecx + movl %ecx, 0x2c(%eax) // context->uc_mcontext.ebp + + // %esp was saved in %ebp in this function’s prologue, but the caller’s %esp + // is 8 more than this value: 4 for the original %ebp saved on the stack in + // this function’s prologue, and 4 for the return address saved on the stack + // by the call instruction that reached this function. + leal 8(%ebp), %ecx + movl %ecx, 0x30(%eax) // context->uc_mcontext.esp + + // More general-purpose registers + movl %ebx, 0x34(%eax) // context->uc_mcontext.ebx + movl %edx, 0x38(%eax) // context->uc_mcontext.edx + + // The original %ecx was saved on the stack above. + movl -12(%ebp), %ecx + movl %ecx, 0x3c(%eax) // context->uc_mcontext.ecx + + // The original %eax was saved on the stack above. + movl -8(%ebp), %ecx + movl %ecx, 0x40(%eax) // context->uc_mcontext.eax + + // trapno and err are unused so zero them out. + xorl %ecx, %ecx + movl %ecx, 0x44(%eax) // context->uc_mcontext.trapno + movl %ecx, 0x48(%eax) // context->uc_mcontext.err + + // %eip can’t be accessed directly, but the return address saved on the stack + // by the call instruction that reached this function can be used. + movl 4(%ebp), %ecx + movl %ecx, 0x4c(%eax) // context->uc_mcontext.eip + + // More segment registers + xorl %ecx, %ecx + movw %cs, %cx + movl %ecx, 0x50(%eax) // context->uc_mcontext.xcs + + // The original %eflags was saved on the stack above. + movl -4(%ebp), %ecx + movl %ecx, 0x54(%eax) // context->uc_mcontext.eflags + + // uesp is unused so zero it out. + xorl %ecx, %ecx + movl %ecx, 0x58(%eax) // context->uc_mcontext.uesp + + // The last segment register. + movw %ss, %cx + movl %ecx, 0x5c(%eax) // context->uc_mcontext.xss + + // TODO(jperaza): save floating-point registers. + xorl %ecx, %ecx + movl %ecx, 0x60(%eax) // context->uc_mcontext.fpregs + + // Clean up by restoring clobbered registers, even those considered volatile + // by the ABI, so that the captured context represents the state at this + // function’s exit. + popl %ecx + popl %eax + popfl + + popl %ebp + + ret + + .cfi_endproc + +#elif defined(__x86_64__) + + .cfi_startproc + + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + + // Note that 16-byte stack alignment is not maintained because this function + // does not call out to any other. + + // pushfq first, because some instructions (but probably none used here) + // affect %rflags. %rflags will be in -8(%rbp). + pushfq + + // General-purpose registers whose values haven’t changed can be captured + // directly. + movq %r8, 0x28(%rdi) // context->uc_mcontext.r8 + movq %r9, 0x30(%rdi) // context->uc_mcontext.r9 + movq %r10, 0x38(%rdi) // context->uc_mcontext.r10 + movq %r11, 0x40(%rdi) // context->uc_mcontext.r11 + movq %r12, 0x48(%rdi) // context->uc_mcontext.r12 + movq %r13, 0x50(%rdi) // context->uc_mcontext.r13 + movq %r14, 0x58(%rdi) // context->uc_mcontext.r14 + movq %r15, 0x60(%rdi) // context->uc_mcontext.r15 + + // Because of the calling convention, there’s no way to recover the value of + // the caller’s %rdi as it existed prior to calling this function. This + // function captures a snapshot of the register state at its return, which + // involves %rdi containing a pointer to its first argument. Callers that + // require the value of %rdi prior to calling this function should obtain it + // separately. For example: + // uint64_t rdi; + // asm("movq %%rdi, %0" : "=m"(rdi)); + movq %rdi, 0x68(%rdi) // context->uc_mcontext.rdi + + movq %rsi, 0x70(%rdi) // context->uc_mcontext.rsi + + // Use %r8 as a scratch register now that it has been saved. + // The original %rbp was saved on the stack in this function’s prologue. + movq (%rbp), %r8 + movq %r8, 0x78(%rdi) // context->uc_mcontext.rbp + + // Save the remaining general-purpose registers. + movq %rbx, 0x80(%rdi) // context->uc_mcontext.rbx + movq %rdx, 0x88(%rdi) // context->uc_mcontext.rdx + movq %rax, 0x90(%rdi) // context->uc_mcontext.rax + movq %rcx, 0x98(%rdi) // context->uc_mcontext.rcx + + // %rsp was saved in %rbp in this function’s prologue, but the caller’s %rsp + // is 16 more than this value: 8 for the original %rbp saved on the stack in + // this function’s prologue, and 8 for the return address saved on the stack + // by the call instruction that reached this function. + leaq 16(%rbp), %r8 + movq %r8, 0xa0(%rdi) // context->uc_mcontext.rsp + + // %rip can’t be accessed directly, but the return address saved on the stack + // by the call instruction that reached this function can be used. + movq 8(%rbp), %r8 + movq %r8, 0xa8(%rdi) // context->uc_mcontext.rip + + // The original %rflags was saved on the stack above. + movq -8(%rbp), %r8 + movq %r8, 0xb0(%rdi) // context->uc_mcontext.eflags + + // Save the segment registers + movw %cs, 0xb8(%rdi) // context->uc_mcontext.cs + movw %gs, 0xba(%rdi) // context->uc_mcontext.gs + movw %fs, 0xbc(%rdi) // context->uc_mcontext.fs + + xorw %ax, %ax + movw %ax, 0xbe(%rdi) // context->uc_mcontext.padding + + // Zero out the remainder of the unused pseudo-registers + xorq %r8, %r8 + movq %r8, 0xc0(%rdi) // context->uc_mcontext.err + movq %r8, 0xc8(%rdi) // context->uc_mcontext.trapno + movq %r8, 0xd0(%rdi) // context->uc_mcontext.oldmask + movq %r8, 0xd8(%rdi) // context->uc_mcontext.cr2 + + // TODO(jperaza): save floating-point registers. + movq %r8, 0xe0(%rdi) // context->uc_mcontext.fpregs + + // Clean up by restoring clobbered registers, even those considered volatile + // by the ABI, so that the captured context represents the state at this + // function’s exit. + movq 0x90(%rdi), %rax + movq 0x28(%rdi), %r8 + + popfq + + popq %rbp + + ret + + .cfi_endproc + +#elif defined(__arm__) + + // The original r0 can't be recovered. + str r0, [r0, #0x20] + + // Now advance r0 to point to the register array. + add r0, r0, #0x24 + + // Save registers r1-r12 at context->uc_mcontext.regs[i]. + stm r0, {r1-r12} + + // 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 + str SP, [r0, #0x54] // context->uc_mcontext.sp + + // The original LR can't be recovered. + str LR, [r0, #0x58] // context->uc_mcontext.lr + + // The link register holds the return address for this function. + str LR, [r0, #0x5c] // context->uc_mcontext.pc + + // Use r1 as a scratch register. + + // CPSR is a deprecated synonym for APSR. + mrs r1, APSR + str r1, [r0, #0x60] // context->uc_mcontext.cpsr + + // Zero out unused fields. + mov r1, #0x0 + str r1, [r0, #0x14] // context->uc_mcontext.trap_no + str r1, [r0, #0x18] // context->uc_mcontext.error_code + str r1, [r0, #0x1c] // context->uc_mcontext.oldmask + str r1, [r0, #0x64] // context->uc_mcontext.fault_address + + // Restore r1. + ldr r1, [r0, #0x24] + + // TODO(jperaza): save floating-point registers. + + mov PC, LR + +#elif defined(__aarch64__) + + // Zero out fault_address, which is unused. + str xzr, [x0, #0xb0] // context->uc_mcontext.fault_address + + // Save general purpose registers in context->uc_mcontext.regs[i]. + // The original x0 can't be recovered. + stp x0, x1, [x0, #0xb8] + stp x2, x3, [x0, #0xc8] + stp x4, x5, [x0, #0xd8] + stp x6, x7, [x0, #0xe8] + stp x8, x9, [x0, #0xf8] + stp x10, x11, [x0, #0x108] + stp x12, x13, [x0, #0x118] + stp x14, x15, [x0, #0x128] + stp x16, x17, [x0, #0x138] + stp x18, x19, [x0, #0x148] + stp x20, x21, [x0, #0x158] + stp x22, x23, [x0, #0x168] + stp x24, x25, [x0, #0x178] + stp x26, x27, [x0, #0x188] + stp x28, x29, [x0, #0x198] + + // The original LR can't be recovered. + str LR, [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 + + // NZCV, pstate, and CPSR are synonyms. + 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. + + ret +#elif defined(__mips__) + .set noat + +#if _MIPS_SIM == _ABIO32 +#define STORE sw +#define MCONTEXT_FPREG_SIZE 4 +#define MCONTEXT_PC_OFFSET 32 +#else +#define STORE sd +#define MCONTEXT_FPREG_SIZE 8 +#define MCONTEXT_PC_OFFSET 616 +#endif + +#define MCONTEXT_REG_SIZE 8 +#define MCONTEXT_GREGS_OFFSET 40 +#define MCONTEXT_FPREGS_OFFSET 296 + + // Value of register 0 is always 0. + // Registers 26 and 27 are reserved for kernel, and shouldn't be used. + STORE $1, (1 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $2, (2 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $3, (3 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $4, (4 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $5, (5 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $6, (6 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $7, (7 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $8, (8 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $9, (9 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $10, (10 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $11, (11 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $12, (12 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $13, (13 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $14, (14 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $15, (15 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $16, (16 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $17, (17 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $18, (18 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $19, (19 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $20, (20 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $21, (21 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $22, (22 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $23, (23 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $24, (24 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $25, (25 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $28, (28 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $29, (29 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $30, (30 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $31, (31 * MCONTEXT_REG_SIZE + MCONTEXT_GREGS_OFFSET)($a0) + STORE $31, (MCONTEXT_PC_OFFSET)($a0) + +#ifdef __mips_hard_float + s.d $f0, (0 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f2, (2 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f4, (4 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f6, (6 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f8, (8 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f10, (10 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f12, (12 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f14, (14 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f16, (16 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f18, (18 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f20, (20 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f22, (22 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f24, (24 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f26, (26 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f28, (28 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f30, (30 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) +#if _MIPS_SIM != _ABIO32 + s.d $f1, (1 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f3, (3 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f5, (5 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f7, (7 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f9, (9 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f11, (11 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f13, (13 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f15, (15 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f17, (17 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f19, (19 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f21, (21 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f23, (23 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f25, (25 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f27, (27 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f29, (29 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) + s.d $f31, (31 * MCONTEXT_FPREG_SIZE + MCONTEXT_FPREGS_OFFSET)($a0) +#endif // _MIPS_SIM != _ABIO32 +#endif // __mips_hard_float + + jr $ra + + .set at +#endif // __i386__ diff --git a/client/capture_context_mac.S b/util/misc/capture_context_mac.S similarity index 99% rename from client/capture_context_mac.S rename to util/misc/capture_context_mac.S index 942d8413..39c6ca6a 100644 --- a/client/capture_context_mac.S +++ b/util/misc/capture_context_mac.S @@ -22,7 +22,7 @@ .section __TEXT,__text,regular,pure_instructions .private_extern CAPTURECONTEXT_SYMBOL .globl CAPTURECONTEXT_SYMBOL - .align 4, 0x90 + .balign 16, 0x90 CAPTURECONTEXT_SYMBOL: #if defined(__i386__) diff --git a/util/misc/capture_context_test.cc b/util/misc/capture_context_test.cc new file mode 100644 index 00000000..1117789d --- /dev/null +++ b/util/misc/capture_context_test.cc @@ -0,0 +1,104 @@ +// 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/misc/capture_context.h" + +#include + +#include + +#include "gtest/gtest.h" +#include "util/misc/address_sanitizer.h" +#include "util/misc/capture_context_test_util.h" + +namespace crashpad { +namespace test { +namespace { + +#if defined(OS_FUCHSIA) +// Fuchsia uses -fsanitize=safe-stack by default, which splits local variables +// and the call stack into separate regions (see +// https://clang.llvm.org/docs/SafeStack.html). Because this test would like to +// 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 + +void TestCaptureContext() { + NativeCPUContext context_1; + CaptureContext(&context_1); + + { + SCOPED_TRACE("context_1"); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1)); + } + + // The program counter reference value is this function’s address. The + // captured program counter should be slightly greater than or equal to the + // 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 + // likely fail. + const uintptr_t kReferencePC = + reinterpret_cast(TestCaptureContext); + EXPECT_PRED2([](uintptr_t actual, + uintptr_t reference) { return actual - reference < 64u; }, + pc, + kReferencePC); +#endif // !defined(ADDRESS_SANITIZER) + + // Declare sp and context_2 here because all local variables need to be + // declared before computing the stack pointer reference value, so that the + // reference value can be the lowest value possible. + uintptr_t sp; + NativeCPUContext context_2; + + // 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. + const uintptr_t kReferenceSP = + std::min(std::min(reinterpret_cast(&context_1), + reinterpret_cast(&context_2)), + std::min(reinterpret_cast(&pc), + reinterpret_cast(&sp))); + sp = StackPointerFromContext(context_1); + EXPECT_PRED2([](uintptr_t actual, + uintptr_t reference) { return reference - actual < 768u; }, + sp, + kReferenceSP); + + // Capture the context again, expecting that the stack pointer stays the same + // and the program counter increases. Strictly speaking, there’s no guarantee + // that these conditions will hold, although they do for known compilers even + // under typical optimization. + CaptureContext(&context_2); + + { + SCOPED_TRACE("context_2"); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2)); + } + + EXPECT_EQ(StackPointerFromContext(context_2), sp); + EXPECT_GT(ProgramCounterFromContext(context_2), pc); +} + +TEST(CaptureContext, CaptureContext) { + ASSERT_NO_FATAL_FAILURE(TestCaptureContext()); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/misc/capture_context_test_util.h b/util/misc/capture_context_test_util.h new file mode 100644 index 00000000..5a5ff7d5 --- /dev/null +++ b/util/misc/capture_context_test_util.h @@ -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/misc/capture_context.h" + +#include + +namespace crashpad { +namespace test { + +//! \brief Sanity check conditions that should be true for any NativeCPUContext +//! produced by CaptureContext(). +//! +//! If the context structure has fields that tell whether it’s valid, such as +//! magic numbers or size fields, sanity-checks those fields for validity with +//! fatal gtest assertions. For other fields, where it’s possible to reason +//! about their validity based solely on their contents, sanity-checks via +//! nonfatal gtest assertions. +//! +//! \param[in] context The context to check. +void SanityCheckContext(const NativeCPUContext& context); + +//! \brief Return the value of the program counter from a NativeCPUContext. +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context); + +//! \brief Return the value of the stack pointer from a NativeCPUContext. +uintptr_t StackPointerFromContext(const NativeCPUContext& context); + +} // namespace test +} // namespace crashpad diff --git a/util/misc/capture_context_test_util_fuchsia.cc b/util/misc/capture_context_test_util_fuchsia.cc new file mode 100644 index 00000000..7f9210ed --- /dev/null +++ b/util/misc/capture_context_test_util_fuchsia.cc @@ -0,0 +1,59 @@ +// 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/misc/capture_context_test_util.h" + +#include "base/logging.h" +#include "gtest/gtest.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { + +#if defined(ARCH_CPU_X86_64) +static_assert(offsetof(NativeCPUContext, uc_mcontext) == 0x28, + "unexpected mcontext offset"); +static_assert(offsetof(NativeCPUContext, uc_mcontext.gregs[REG_RSP]) == 0xa0, + "unexpected rsp offset"); +static_assert(offsetof(NativeCPUContext, uc_mcontext.gregs[REG_RIP]) == 0xa8, + "unexpected rip offset"); +#endif // ARCH_CPU_X86_64 + +void SanityCheckContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + EXPECT_EQ(context.uc_mcontext.gregs[REG_RDI], + FromPointerCast(&context)); +#elif defined(ARCH_CPU_ARM64) + EXPECT_EQ(context.uc_mcontext.regs[0], FromPointerCast(&context)); +#endif +} + +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RIP]; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.pc; +#endif +} + +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RSP]; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.sp; +#endif +} + +} // namespace test +} // namespace crashpad diff --git a/util/misc/capture_context_test_util_linux.cc b/util/misc/capture_context_test_util_linux.cc new file mode 100644 index 00000000..9fc5db28 --- /dev/null +++ b/util/misc/capture_context_test_util_linux.cc @@ -0,0 +1,71 @@ +// 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/misc/capture_context_test_util.h" + +#include "base/logging.h" +#include "gtest/gtest.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { + +void SanityCheckContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + // TODO(jperaza): fpregs is nullptr until CaptureContext() supports capturing + // floating point context. + EXPECT_EQ(context.uc_mcontext.fpregs, nullptr); +#elif defined(ARCH_CPU_X86_64) + EXPECT_EQ(context.uc_mcontext.gregs[REG_RDI], + FromPointerCast(&context)); + EXPECT_EQ(context.uc_mcontext.fpregs, nullptr); +#elif defined(ARCH_CPU_ARMEL) + EXPECT_EQ(context.uc_mcontext.arm_r0, FromPointerCast(&context)); +#elif defined(ARCH_CPU_ARM64) + EXPECT_EQ(context.uc_mcontext.regs[0], FromPointerCast(&context)); +#elif defined(ARCH_CPU_MIPS_FAMILY) + EXPECT_EQ(context.uc_mcontext.gregs[4], FromPointerCast(&context)); +#endif +} + +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + return context.uc_mcontext.gregs[REG_EIP]; +#elif defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RIP]; +#elif defined(ARCH_CPU_ARMEL) + return context.uc_mcontext.arm_pc; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.pc; +#elif defined(ARCH_CPU_MIPS_FAMILY) + return context.uc_mcontext.pc; +#endif +} + +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + return context.uc_mcontext.gregs[REG_ESP]; +#elif defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RSP]; +#elif defined(ARCH_CPU_ARMEL) + return context.uc_mcontext.arm_sp; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.sp; +#elif defined(ARCH_CPU_MIPS_FAMILY) + return context.uc_mcontext.gregs[29]; +#endif +} + +} // namespace test +} // namespace crashpad diff --git a/util/misc/capture_context_test_util_mac.cc b/util/misc/capture_context_test_util_mac.cc new file mode 100644 index 00000000..afe09163 --- /dev/null +++ b/util/misc/capture_context_test_util_mac.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 "util/misc/capture_context_test_util.h" + +#include "gtest/gtest.h" +#include "util/misc/implicit_cast.h" + +namespace crashpad { +namespace test { + +void SanityCheckContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + ASSERT_EQ(implicit_cast(context.tsh.flavor), + implicit_cast(x86_THREAD_STATE32)); + ASSERT_EQ(implicit_cast(context.tsh.count), + implicit_cast(x86_THREAD_STATE32_COUNT)); +#elif defined(ARCH_CPU_X86_64) + ASSERT_EQ(implicit_cast(context.tsh.flavor), + implicit_cast(x86_THREAD_STATE64)); + ASSERT_EQ(implicit_cast(context.tsh.count), + implicit_cast(x86_THREAD_STATE64_COUNT)); +#endif + +#if defined(ARCH_CPU_X86_FAMILY) +// The segment registers are only capable of storing 16-bit quantities, but +// the context structure provides native integer-width fields for them. Ensure +// that the high bits are all clear. +// +// Many bit positions in the flags register are reserved and will always read +// a known value. Most reserved bits are always 0, but bit 1 is always 1. +// Check that the reserved bits are all set to their expected values. Note +// that the set of reserved bits may be relaxed over time with newer CPUs, and +// that this test may need to be changed to reflect these developments. The +// current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel +// Software Developer’s Manual, Volume 1: Basic Architecture (253665-051), +// 3.4.3 “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume +// 2: System Programming (24593-3.24), 3.1.6 “RFLAGS Register”. +#if defined(ARCH_CPU_X86) + EXPECT_EQ(context.uts.ts32.__cs & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__ds & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__es & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__fs & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__gs & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__ss & ~0xffff, 0u); + EXPECT_EQ(context.uts.ts32.__eflags & 0xffc0802a, 2u); +#elif defined(ARCH_CPU_X86_64) + EXPECT_EQ(context.uts.ts64.__cs & ~UINT64_C(0xffff), 0u); + EXPECT_EQ(context.uts.ts64.__fs & ~UINT64_C(0xffff), 0u); + EXPECT_EQ(context.uts.ts64.__gs & ~UINT64_C(0xffff), 0u); + EXPECT_EQ(context.uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a), 2u); +#endif +#endif +} + +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + return context.uts.ts32.__eip; +#elif defined(ARCH_CPU_X86_64) + return context.uts.ts64.__rip; +#endif +} + +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86) + return context.uts.ts32.__esp; +#elif defined(ARCH_CPU_X86_64) + return context.uts.ts64.__rsp; +#endif +} + +} // namespace test +} // namespace crashpad diff --git a/util/win/capture_context_test.cc b/util/misc/capture_context_test_util_win.cc similarity index 53% rename from util/win/capture_context_test.cc rename to util/misc/capture_context_test_util_win.cc index 292e4749..239beacd 100644 --- a/util/win/capture_context_test.cc +++ b/util/misc/capture_context_test_util_win.cc @@ -1,4 +1,4 @@ -// Copyright 2015 The Crashpad Authors. All rights reserved. +// 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. @@ -12,40 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/win/capture_context.h" - -#include -#include - -#include +#include "util/misc/capture_context_test_util.h" #include "base/macros.h" -#include "build/build_config.h" #include "gtest/gtest.h" namespace crashpad { namespace test { -namespace { -// If the context structure has fields that tell whether it’s valid, such as -// magic numbers or size fields, sanity-checks those fields for validity with -// fatal gtest assertions. For other fields, where it’s possible to reason about -// their validity based solely on their contents, sanity-checks via nonfatal -// gtest assertions. -void SanityCheckContext(const CONTEXT& context) { +void SanityCheckContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - constexpr uint32_t must_have = CONTEXT_i386 | - CONTEXT_CONTROL | - CONTEXT_INTEGER | - CONTEXT_SEGMENTS | + constexpr uint32_t must_have = CONTEXT_i386 | CONTEXT_CONTROL | + CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT; ASSERT_EQ(context.ContextFlags & must_have, must_have); constexpr uint32_t may_have = CONTEXT_EXTENDED_REGISTERS; ASSERT_EQ(context.ContextFlags & ~(must_have | may_have), 0u); #elif defined(ARCH_CPU_X86_64) - ASSERT_EQ(context.ContextFlags, - static_cast(CONTEXT_AMD64 | CONTEXT_CONTROL | - CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT)); + ASSERT_EQ( + context.ContextFlags, + static_cast(CONTEXT_AMD64 | CONTEXT_CONTROL | CONTEXT_INTEGER | + CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT)); #endif #if defined(ARCH_CPU_X86_FAMILY) @@ -107,8 +94,7 @@ void SanityCheckContext(const CONTEXT& context) { #endif } -// A CPU-independent function to return the program counter. -uintptr_t ProgramCounterFromContext(const CONTEXT& context) { +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) return context.Eip; #elif defined(ARCH_CPU_X86_64) @@ -116,8 +102,7 @@ uintptr_t ProgramCounterFromContext(const CONTEXT& context) { #endif } -// A CPU-independent function to return the stack pointer. -uintptr_t StackPointerFromContext(const CONTEXT& context) { +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) return context.Esp; #elif defined(ARCH_CPU_X86_64) @@ -125,56 +110,5 @@ uintptr_t StackPointerFromContext(const CONTEXT& context) { #endif } -void TestCaptureContext() { - CONTEXT context_1; - CaptureContext(&context_1); - - { - SCOPED_TRACE("context_1"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1)); - } - - // The program counter reference value is this function’s address. The - // captured program counter should be slightly greater than or equal to the - // reference program counter. - uintptr_t pc = ProgramCounterFromContext(context_1); - - // Declare sp and context_2 here because all local variables need to be - // declared before computing the stack pointer reference value, so that the - // reference value can be the lowest value possible. - uintptr_t sp; - CONTEXT context_2; - - // 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. - const uintptr_t kReferenceSP = - std::min(std::min(reinterpret_cast(&context_1), - reinterpret_cast(&context_2)), - std::min(reinterpret_cast(&pc), - reinterpret_cast(&sp))); - sp = StackPointerFromContext(context_1); - EXPECT_LT(kReferenceSP - sp, 512u); - - // Capture the context again, expecting that the stack pointer stays the same - // and the program counter increases. Strictly speaking, there’s no guarantee - // that these conditions will hold, although they do for known compilers even - // under typical optimization. - CaptureContext(&context_2); - - { - SCOPED_TRACE("context_2"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2)); - } - - EXPECT_EQ(StackPointerFromContext(context_2), sp); - EXPECT_GT(ProgramCounterFromContext(context_2), pc); -} - -TEST(CaptureContextWin, CaptureContext) { - ASSERT_NO_FATAL_FAILURE(TestCaptureContext()); -} - -} // namespace } // namespace test } // namespace crashpad diff --git a/util/win/capture_context.asm b/util/misc/capture_context_win.asm similarity index 100% rename from util/win/capture_context.asm rename to util/misc/capture_context_win.asm diff --git a/util/misc/elf_note_types.h b/util/misc/elf_note_types.h new file mode 100644 index 00000000..77c9043b --- /dev/null +++ b/util/misc/elf_note_types.h @@ -0,0 +1,34 @@ +// 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_MISC_ELF_NOTE_TYPES_H_ +#define CRASHPAD_UTIL_MISC_ELF_NOTE_TYPES_H_ + +// This header defines types of ELF "notes" that are embedded sections. These +// can be read by ElfImageReader in the snapshot library, and are created in +// client modules. All notes used by Crashpad use the name "Crashpad" and one of +// the types defined here. Note that this file is #included into .S files, so +// must be relatively plain (no C++ features). + +#define CRASHPAD_ELF_NOTE_NAME "Crashpad" + +// Used by ElfImageReader for testing purposes. +#define CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST 1 + +// Used by the client library to stash a pointer to the CrashpadInfo structure +// for retrieval by the module snapshot. 'OFNI' == 0x4f464e49 which appears as +// "INFO" in readelf -x. +#define CRASHPAD_ELF_NOTE_TYPE_CRASHPAD_INFO 0x4f464e49 + +#endif // CRASHPAD_UTIL_MISC_ELF_NOTE_TYPES_H_ diff --git a/util/misc/from_pointer_cast_test.cc b/util/misc/from_pointer_cast_test.cc index b961abba..1a6850ff 100644 --- a/util/misc/from_pointer_cast_test.cc +++ b/util/misc/from_pointer_cast_test.cc @@ -21,7 +21,7 @@ #include "build/build_config.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { @@ -233,27 +233,28 @@ TEST(FromPointerCast, ToNarrowInteger) { TEST(FromPointerCastDeathTest, ToNarrowInteger) { if (sizeof(int) < sizeof(void*)) { - EXPECT_DEATH(FromPointerCast( - reinterpret_cast(static_cast( - std::numeric_limits::max() + 1ull))), - ""); - EXPECT_DEATH(FromPointerCast( - reinterpret_cast(static_cast( - std::numeric_limits::max() + 1ull))), - ""); + EXPECT_DEATH_CHECK( + FromPointerCast(reinterpret_cast(static_cast( + std::numeric_limits::max() + 1ull))), + ""); + EXPECT_DEATH_CHECK( + FromPointerCast( + reinterpret_cast(static_cast( + std::numeric_limits::max() + 1ull))), + ""); } // int and unsigned int may not be narrower than a pointer, so also test short // and unsigned short. - EXPECT_DEATH(FromPointerCast( - reinterpret_cast(static_cast( - std::numeric_limits::max() + 1u))), - ""); - EXPECT_DEATH(FromPointerCast( - reinterpret_cast(static_cast( - std::numeric_limits::max() + 1u))), - ""); + EXPECT_DEATH_CHECK( + FromPointerCast(reinterpret_cast(static_cast( + std::numeric_limits::max() + 1u))), + ""); + EXPECT_DEATH_CHECK(FromPointerCast( + reinterpret_cast(static_cast( + std::numeric_limits::max() + 1u))), + ""); } } // namespace diff --git a/util/misc/initialization_state_dcheck_test.cc b/util/misc/initialization_state_dcheck_test.cc index 2c407675..f954d004 100644 --- a/util/misc/initialization_state_dcheck_test.cc +++ b/util/misc/initialization_state_dcheck_test.cc @@ -21,7 +21,7 @@ #include "base/logging.h" #include "base/memory/free_deleter.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { diff --git a/util/misc/metrics.cc b/util/misc/metrics.cc index e2bd9f0b..7d191f71 100644 --- a/util/misc/metrics.cc +++ b/util/misc/metrics.cc @@ -14,8 +14,8 @@ #include "util/misc/metrics.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" -#include "base/metrics/sparse_histogram.h" #include "build/build_config.h" #if defined(OS_MACOSX) @@ -26,6 +26,8 @@ #define METRICS_OS_NAME "Android" #elif defined(OS_LINUX) #define METRICS_OS_NAME "Linux" +#elif defined(OS_FUCHSIA) +#define METRICS_OS_NAME "Fuchsia" #endif namespace crashpad { @@ -59,8 +61,7 @@ void Metrics::CrashReportPending(PendingReportReason reason) { } // static -void Metrics::CrashReportSize(FileHandle file) { - const FileOffset size = LoggingFileSizeByHandle(file); +void Metrics::CrashReportSize(FileOffset size) { UMA_HISTOGRAM_CUSTOM_COUNTS( "Crashpad.CrashReportSize", size, 0, 20 * 1024 * 1024, 50); } @@ -89,8 +90,8 @@ void Metrics::ExceptionCaptureResult(CaptureResult result) { // static void Metrics::ExceptionCode(uint32_t exception_code) { - UMA_HISTOGRAM_SPARSE_SLOWLY("Crashpad.ExceptionCode." METRICS_OS_NAME, - static_cast(exception_code)); + base::UmaHistogramSparse("Crashpad.ExceptionCode." METRICS_OS_NAME, + static_cast(exception_code)); } // static @@ -107,7 +108,7 @@ void Metrics::HandlerLifetimeMilestone(LifetimeMilestone milestone) { // static void Metrics::HandlerCrashed(uint32_t exception_code) { - UMA_HISTOGRAM_SPARSE_SLOWLY( + base::UmaHistogramSparse( "Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME, static_cast(exception_code)); } diff --git a/util/misc/metrics.h b/util/misc/metrics.h index b4bea910..3e9337a9 100644 --- a/util/misc/metrics.h +++ b/util/misc/metrics.h @@ -50,7 +50,7 @@ class Metrics { //! \brief Reports the size of a crash report file in bytes. Should be called //! when a new report is written to disk. - static void CrashReportSize(FileHandle file); + static void CrashReportSize(FileOffset size); //! \brief Reports on a crash upload attempt, and if it succeeded. static void CrashUploadAttempted(bool successful); @@ -119,6 +119,22 @@ class Metrics { //! \brief There was a database error in attempt to complete the report. kFinishedWritingCrashReportFailed = 7, + //! \brief An attempt to directly `ptrace` the target failed. + //! + //! This value is only used on Linux/Android. + kDirectPtraceFailed = 8, + + //! \brief An attempt to `ptrace` via a PtraceBroker failed. + //! + //! This value is only used on Linux/Android. + kBrokeredPtraceFailed = 9, + + //! \brief Sanitization was requested but could not be initialized. + kSanitizationInitializationFailed = 10, + + //! \brief Sanitization caused this crash dump to be skipped. + kSkippedDueToSanitization = 11, + //! \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 new file mode 100644 index 00000000..09084483 --- /dev/null +++ b/util/misc/paths_fuchsia.cc @@ -0,0 +1,34 @@ +// 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 "util/misc/paths.h" + +#include +#include +#include + +#include "base/logging.h" + +namespace crashpad { + +// 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 + // . + *path = base::FilePath("/pkg/bin/app"); + return true; +} + +} // namespace crashpad diff --git a/util/misc/pdb_structures.h b/util/misc/pdb_structures.h index 218a7632..d0cc9a32 100644 --- a/util/misc/pdb_structures.h +++ b/util/misc/pdb_structures.h @@ -30,10 +30,9 @@ namespace crashpad { //! //! For more information about this structure and format, see Matching -//! Debug Information, PDB Files, and Undocumented -//! Windows 2000 Secrets, Windows 2000 Debugging Support/Microsoft Symbol -//! File Internals/CodeView Subsections. +//! Debug Information, PDB Files, and Undocumented Windows 2000 +//! Secrets, Windows 2000 Debugging Support/Microsoft Symbol File +//! Internals/CodeView Subsections. //! //! \sa IMAGE_DEBUG_MISC struct CodeViewRecordPDB20 { diff --git a/util/misc/range_set.cc b/util/misc/range_set.cc new file mode 100644 index 00000000..5a7040fe --- /dev/null +++ b/util/misc/range_set.cc @@ -0,0 +1,55 @@ +// 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/misc/range_set.h" + +#include + +namespace crashpad { + +RangeSet::RangeSet() = default; + +RangeSet::~RangeSet() = default; + +void RangeSet::Insert(VMAddress base, VMSize size) { + if (!size) { + return; + } + + VMAddress last = base + size - 1; + + auto overlapping_range = ranges_.lower_bound(base); +#define OVERLAPPING_RANGES_BASE overlapping_range->second +#define OVERLAPPING_RANGES_LAST overlapping_range->first + while (overlapping_range != ranges_.end() && + OVERLAPPING_RANGES_BASE <= last) { + base = std::min(base, OVERLAPPING_RANGES_BASE); + last = std::max(last, OVERLAPPING_RANGES_LAST); + auto tmp = overlapping_range; + ++overlapping_range; + ranges_.erase(tmp); + } +#undef OVERLAPPING_RANGES_BASE +#undef OVERLAPPING_RANGES_LAST + + ranges_[last] = base; +} + +bool RangeSet::Contains(VMAddress address) const { + auto range_above_address = ranges_.lower_bound(address); + return range_above_address != ranges_.end() && + range_above_address->second <= address; +} + +} // namespace crashpad diff --git a/util/misc/range_set.h b/util/misc/range_set.h new file mode 100644 index 00000000..573705ec --- /dev/null +++ b/util/misc/range_set.h @@ -0,0 +1,51 @@ +// 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_MISC_RANGE_SET_H_ +#define CRASHPAD_UTIL_MISC_RANGE_SET_H_ + +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +//! \brief A set of VMAddress ranges. +class RangeSet { + public: + RangeSet(); + ~RangeSet(); + + //! \brief Inserts a range into the set. + //! + //! \param[in] base The low address of the range. + //! \param[in] size The size of the range. + void Insert(VMAddress base, VMSize size); + + //! \brief Returns `true` if \a address falls within a range in this set. + bool Contains(VMAddress address) const; + + private: + // Keys are the highest address in the range. Values are the base address of + // the range. Overlapping ranges are merged on insertion. Adjacent ranges may + // be merged. + std::map ranges_; + + DISALLOW_COPY_AND_ASSIGN(RangeSet); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_RANGE_SET_H_ diff --git a/util/misc/range_set_test.cc b/util/misc/range_set_test.cc new file mode 100644 index 00000000..d5a3cd14 --- /dev/null +++ b/util/misc/range_set_test.cc @@ -0,0 +1,116 @@ +// 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/misc/range_set.h" + +#include + +#include + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "util/misc/address_types.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { +namespace { + +void ExpectRangeIsContained(const RangeSet& ranges, + VMAddress base, + VMSize size) { + for (VMAddress addr = base; addr < base + size; ++addr) { + SCOPED_TRACE(base::StringPrintf("0x%" PRIx64 " in range 0x%" PRIx64 + ":0x%" PRIx64, + addr, + base, + base + size)); + EXPECT_TRUE(ranges.Contains(addr)); + } +} + +TEST(RangeSet, Basic) { + RangeSet ranges; + auto base = FromPointerCast(&ranges); + VMSize size = sizeof(ranges); + ranges.Insert(base, size); + ExpectRangeIsContained(ranges, base, size); + EXPECT_FALSE(ranges.Contains(base - 1)); + EXPECT_FALSE(ranges.Contains(base + size)); +} + +TEST(RangeSet, ZeroSizedRange) { + RangeSet ranges; + auto addr = FromPointerCast(&ranges); + ranges.Insert(addr, 0); + EXPECT_FALSE(ranges.Contains(addr)); +} + +TEST(RangeSet, DuplicateRanges) { + RangeSet ranges; + auto base = FromPointerCast(&ranges); + VMSize size = sizeof(ranges); + ranges.Insert(base, size); + ranges.Insert(base, size); + ExpectRangeIsContained(ranges, base, size); +} + +TEST(RangeSet, OverlappingRanges) { + RangeSet ranges; + ranges.Insert(37, 16); + ranges.Insert(9, 9); + ranges.Insert(17, 42); + + EXPECT_TRUE(ranges.Contains(9)); + EXPECT_TRUE(ranges.Contains(17)); + EXPECT_TRUE(ranges.Contains(36)); + EXPECT_TRUE(ranges.Contains(37)); + EXPECT_TRUE(ranges.Contains(52)); + EXPECT_TRUE(ranges.Contains(58)); +} + +TEST(RangeSet, SubRangeInLargeRange) { + constexpr size_t kBufferSize = 2 << 22; + auto buf = std::make_unique(kBufferSize); + + RangeSet ranges; + auto addr = FromPointerCast(buf.get()); + + ranges.Insert(addr, kBufferSize); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); + + ranges.Insert(addr, kBufferSize / 2); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize / 2 - 1)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); +} + +TEST(RangeSet, LargeOverlappingRanges) { + constexpr size_t kBufferSize = 2 << 23; + auto buf = std::make_unique(kBufferSize); + + RangeSet ranges; + auto addr = FromPointerCast(buf.get()); + + ranges.Insert(addr, 3 * kBufferSize / 4); + ranges.Insert(addr + kBufferSize / 4, 3 * kBufferSize / 4); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/misc/scoped_forbid_return_test.cc b/util/misc/scoped_forbid_return_test.cc index 4f73ea58..06cb5494 100644 --- a/util/misc/scoped_forbid_return_test.cc +++ b/util/misc/scoped_forbid_return_test.cc @@ -16,7 +16,7 @@ #include "base/compiler_specific.h" #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { diff --git a/util/misc/time.cc b/util/misc/time.cc new file mode 100644 index 00000000..549b26ad --- /dev/null +++ b/util/misc/time.cc @@ -0,0 +1,54 @@ +// 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 "util/misc/time.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +void AddTimespec(const timespec& ts1, const timespec& ts2, timespec* result) { + result->tv_sec = ts1.tv_sec + ts2.tv_sec; + result->tv_nsec = ts1.tv_nsec + ts2.tv_nsec; + if (result->tv_nsec >= long{kNanosecondsPerSecond}) { + ++result->tv_sec; + result->tv_nsec -= kNanosecondsPerSecond; + } +} + +void SubtractTimespec(const timespec& t1, + const timespec& t2, + timespec* result) { + result->tv_sec = t1.tv_sec - t2.tv_sec; + result->tv_nsec = t1.tv_nsec - t2.tv_nsec; + if (result->tv_nsec < 0) { + result->tv_sec -= 1; + result->tv_nsec += kNanosecondsPerSecond; + } +} + +bool TimespecToTimeval(const timespec& ts, timeval* tv) { + tv->tv_usec = ts.tv_nsec / 1000; + + // timespec::tv_sec and timeval::tv_sec should generally both be of type + // time_t, however, on Windows, timeval::tv_sec is declared as a long, which + // may be smaller than a time_t. + return AssignIfInRange(&tv->tv_sec, ts.tv_sec); +} + +void TimevalToTimespec(const timeval& tv, timespec* ts) { + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; +} + +} // namespace crashpad diff --git a/util/misc/time.h b/util/misc/time.h new file mode 100644 index 00000000..aabe8e64 --- /dev/null +++ b/util/misc/time.h @@ -0,0 +1,74 @@ +// 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_UTIL_MISC_TIME_H_ +#define CRASHPAD_UTIL_MISC_TIME_H_ + +#include +#include +#include + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace crashpad { + +constexpr uint64_t kNanosecondsPerSecond = static_cast(1E9); + +//! \brief Add `timespec` \a ts1 and \a ts2 and return the result in \a result. +void AddTimespec(const timespec& ts1, const timespec& ts2, timespec* result); + +//! \brief Subtract `timespec` \a ts2 from \a ts1 and return the result in \a +//! result. +void SubtractTimespec(const timespec& ts1, + const timespec& ts2, + timespec* result); + +//! \brief Convert the timespec \a ts to a timeval \a tv. +//! \return `true` if the assignment is possible without truncation. +bool TimespecToTimeval(const timespec& ts, timeval* tv); + +//! \brief Convert the timeval \a tv to a timespec \a ts. +void TimevalToTimespec(const timeval& tv, timespec* ts); + +#if defined(OS_WIN) || DOXYGEN + +//! \brief Convert a `timespec` to a Windows `FILETIME`, converting from POSIX +//! epoch to Windows epoch. +FILETIME TimespecToFiletimeEpoch(const timespec& ts); + +//! \brief Convert a Windows `FILETIME` to `timespec`, converting from Windows +//! epoch to POSIX epoch. +timespec FiletimeToTimespecEpoch(const FILETIME& filetime); + +//! \brief Convert Windows `FILETIME` to `timeval`, converting from Windows +//! epoch to POSIX epoch. +timeval FiletimeToTimevalEpoch(const FILETIME& filetime); + +//! \brief Convert Windows `FILETIME` to `timeval`, treating the values as +//! an interval of elapsed time. +timeval FiletimeToTimevalInterval(const FILETIME& filetime); + +//! \brief Similar to POSIX `gettimeofday()`, gets the current system time in +//! UTC. +void GetTimeOfDay(timeval* tv); + +#endif // OS_WIN + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_TIME_H_ diff --git a/util/misc/time_test.cc b/util/misc/time_test.cc new file mode 100644 index 00000000..87e28faa --- /dev/null +++ b/util/misc/time_test.cc @@ -0,0 +1,132 @@ +// 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 "util/misc/time.h" + +#include + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Time, TimespecArithmetic) { + timespec ts1, ts2, result; + ts1.tv_sec = ts2.tv_sec = 1; + ts1.tv_nsec = ts2.tv_nsec = kNanosecondsPerSecond / 2; + AddTimespec(ts1, ts2, &result); + EXPECT_EQ(result.tv_sec, 3); + EXPECT_EQ(result.tv_nsec, 0); + + ts1.tv_sec = 2; + ts1.tv_nsec = 0; + ts2.tv_sec = 1; + ts2.tv_nsec = 1; + SubtractTimespec(ts1, ts2, &result); + EXPECT_EQ(result.tv_sec, 0); + EXPECT_EQ(result.tv_nsec, long{kNanosecondsPerSecond - 1}); +} + +TEST(Time, TimeConversions) { + // On July 30th, 2014 at 9:15 PM GMT+0, the Crashpad git repository was born. + // (nanoseconds are approximate) + constexpr timespec kCrashpadBirthdate = { + /* .tv_sec= */ 1406754914, + /* .tv_nsec= */ 32487 + }; + + timeval timeval_birthdate; + ASSERT_TRUE(TimespecToTimeval(kCrashpadBirthdate, &timeval_birthdate)); + EXPECT_EQ(timeval_birthdate.tv_sec, kCrashpadBirthdate.tv_sec); + EXPECT_EQ(timeval_birthdate.tv_usec, kCrashpadBirthdate.tv_nsec / 1000); + + timespec timespec_birthdate; + TimevalToTimespec(timeval_birthdate, ×pec_birthdate); + EXPECT_EQ(timespec_birthdate.tv_sec, kCrashpadBirthdate.tv_sec); + EXPECT_EQ(timespec_birthdate.tv_nsec, + kCrashpadBirthdate.tv_nsec - (kCrashpadBirthdate.tv_nsec % 1000)); + + constexpr timespec kEndOfTime = { + /* .tv_sec= */ std::numeric_limits::max(), + /* .tv_nsec= */ 0 + }; + + timeval end_of_timeval; + if (std::numeric_limits::max() > + std::numeric_limits::max()) { + EXPECT_FALSE(TimespecToTimeval(kEndOfTime, &end_of_timeval)); + } else { + EXPECT_TRUE(TimespecToTimeval(kEndOfTime, &end_of_timeval)); + } + +#if defined(OS_WIN) + constexpr uint64_t kBirthdateFiletimeIntervals = 130512285140000324; + FILETIME filetime_birthdate; + filetime_birthdate.dwLowDateTime = 0xffffffff & kBirthdateFiletimeIntervals; + filetime_birthdate.dwHighDateTime = kBirthdateFiletimeIntervals >> 32; + + FILETIME filetime = TimespecToFiletimeEpoch(kCrashpadBirthdate); + EXPECT_EQ(filetime.dwLowDateTime, filetime_birthdate.dwLowDateTime); + EXPECT_EQ(filetime.dwHighDateTime, filetime_birthdate.dwHighDateTime); + + timespec_birthdate = FiletimeToTimespecEpoch(filetime_birthdate); + EXPECT_EQ(timespec_birthdate.tv_sec, kCrashpadBirthdate.tv_sec); + EXPECT_EQ(timespec_birthdate.tv_nsec, + kCrashpadBirthdate.tv_nsec - kCrashpadBirthdate.tv_nsec % 100); + + timeval_birthdate = FiletimeToTimevalEpoch(filetime_birthdate); + EXPECT_EQ(timeval_birthdate.tv_sec, kCrashpadBirthdate.tv_sec); + EXPECT_EQ(timeval_birthdate.tv_usec, kCrashpadBirthdate.tv_nsec / 1000); + + FILETIME elapsed_filetime; + elapsed_filetime.dwLowDateTime = 0; + elapsed_filetime.dwHighDateTime = 0; + timeval elapsed_timeval = FiletimeToTimevalInterval(elapsed_filetime); + EXPECT_EQ(elapsed_timeval.tv_sec, 0); + EXPECT_EQ(elapsed_timeval.tv_usec, 0); + + elapsed_filetime.dwLowDateTime = 9; + elapsed_timeval = FiletimeToTimevalInterval(elapsed_filetime); + EXPECT_EQ(elapsed_timeval.tv_sec, 0); + EXPECT_EQ(elapsed_timeval.tv_usec, 0); + + elapsed_filetime.dwLowDateTime = 10; + elapsed_timeval = FiletimeToTimevalInterval(elapsed_filetime); + EXPECT_EQ(elapsed_timeval.tv_sec, 0); + EXPECT_EQ(elapsed_timeval.tv_usec, 1); + + elapsed_filetime.dwHighDateTime = 1; + elapsed_filetime.dwLowDateTime = 0; + elapsed_timeval = FiletimeToTimevalInterval(elapsed_filetime); + EXPECT_EQ(elapsed_timeval.tv_sec, 429); + EXPECT_EQ(elapsed_timeval.tv_usec, 496729); +#endif // OS_WIN +} + +#if defined(OS_WIN) + +TEST(Time, GetTimeOfDay) { + timeval t; + GetTimeOfDay(&t); + time_t approx_now = time(nullptr); + EXPECT_GE(approx_now, t.tv_sec); + EXPECT_LT(approx_now - 100, t.tv_sec); +} + +#endif // OS_WIN + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/win/time.cc b/util/misc/time_win.cc similarity index 54% rename from util/win/time.cc rename to util/misc/time_win.cc index f3c2360a..d9c4156b 100644 --- a/util/win/time.cc +++ b/util/misc/time_win.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/win/time.h" +#include "util/misc/time.h" #include @@ -23,11 +23,22 @@ namespace crashpad { namespace { constexpr uint64_t kMicrosecondsPerSecond = static_cast(1E6); +constexpr uint64_t kNanosecondsPerFiletimeInterval = static_cast(100); +constexpr uint64_t kFiletimeIntervalsPerSecond = + kNanosecondsPerSecond / kNanosecondsPerFiletimeInterval; +constexpr uint64_t kFiletimeIntervalsPerMicrosecond = + kFiletimeIntervalsPerSecond / kMicrosecondsPerSecond; + +// Windows epoch is 1601-01-01, and FILETIME ticks are 100 nanoseconds. +// 1601 to 1970 is 369 years + 89 leap days = 134774 days * 86400 seconds per +// day. It's not entirely clear, but it appears that these are solar seconds, +// not SI seconds, so there are no leap seconds to be considered. +constexpr uint64_t kNumSecondsFrom1601To1970 = (369 * 365 + 89) * 86400ULL; uint64_t FiletimeToMicroseconds(const FILETIME& filetime) { uint64_t t = (static_cast(filetime.dwHighDateTime) << 32) | filetime.dwLowDateTime; - return t / 10; // 100 nanosecond intervals to microseconds. + return t / kFiletimeIntervalsPerMicrosecond; } timeval MicrosecondsToTimeval(uint64_t microseconds) { @@ -39,14 +50,31 @@ timeval MicrosecondsToTimeval(uint64_t microseconds) { } // namespace +FILETIME TimespecToFiletimeEpoch(const timespec& ts) { + uint64_t intervals = + (kNumSecondsFrom1601To1970 + ts.tv_sec) * kFiletimeIntervalsPerSecond + + ts.tv_nsec / kNanosecondsPerFiletimeInterval; + FILETIME filetime; + filetime.dwLowDateTime = intervals & 0xffffffff; + filetime.dwHighDateTime = intervals >> 32; + return filetime; +} + +timespec FiletimeToTimespecEpoch(const FILETIME& filetime) { + uint64_t intervals = + (uint64_t{filetime.dwHighDateTime} << 32) | filetime.dwLowDateTime; + timespec result; + result.tv_sec = + (intervals / kFiletimeIntervalsPerSecond) - kNumSecondsFrom1601To1970; + result.tv_nsec = + static_cast(intervals % kFiletimeIntervalsPerSecond) * + kNanosecondsPerFiletimeInterval; + return result; +} + timeval FiletimeToTimevalEpoch(const FILETIME& filetime) { uint64_t microseconds = FiletimeToMicroseconds(filetime); - // Windows epoch is 1601-01-01, and FILETIME ticks are 100 nanoseconds. - // 1601 to 1970 is 369 years + 89 leap days = 134774 days * 86400 seconds per - // day. It's not entirely clear, but it appears that these are solar seconds, - // not SI seconds, so there are no leap seconds to be considered. - constexpr uint64_t kNumSecondsFrom1601To1970 = (369 * 365 + 89) * 86400ULL; DCHECK_GE(microseconds, kNumSecondsFrom1601To1970 * kMicrosecondsPerSecond); microseconds -= kNumSecondsFrom1601To1970 * kMicrosecondsPerSecond; return MicrosecondsToTimeval(microseconds); diff --git a/util/misc/uuid.cc b/util/misc/uuid.cc index 6cf777f5..ffd49708 100644 --- a/util/misc/uuid.cc +++ b/util/misc/uuid.cc @@ -84,15 +84,21 @@ bool UUID::InitializeFromString(const base::StringPiece& string) { return true; } +bool UUID::InitializeFromString(const base::StringPiece16& string) { + return InitializeFromString(UTF16ToUTF8(string)); +} + bool UUID::InitializeWithNew() { #if defined(OS_MACOSX) uuid_t uuid; uuid_generate(uuid); InitializeFromBytes(uuid); return true; -#elif defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID) - // Linux does not provide a UUID generator in a widely-available system - // library. uuid_generate() from libuuid is not available everywhere. +#elif defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID) || \ + defined(OS_FUCHSIA) + // Linux, Android, and Fuchsia do not provide a UUID generator in a + // widely-available system library. On Linux and Android, uuid_generate() + // from libuuid is not available everywhere. // On Windows, do not use UuidCreate() to avoid a dependency on rpcrt4, so // that this function is usable early in DllMain(). base::RandBytes(this, sizeof(*this)); diff --git a/util/misc/uuid.h b/util/misc/uuid.h index 4e5884e2..af801222 100644 --- a/util/misc/uuid.h +++ b/util/misc/uuid.h @@ -63,6 +63,7 @@ struct UUID { //! been initialized with the data. `false` if the string could not be //! parsed, with the object state untouched. bool InitializeFromString(const base::StringPiece& string); + bool InitializeFromString(const base::StringPiece16& string); //! \brief Initializes the %UUID using a standard system facility to generate //! the value. diff --git a/util/misc/uuid_test.cc b/util/misc/uuid_test.cc index 72b8216b..c05c5c1b 100644 --- a/util/misc/uuid_test.cc +++ b/util/misc/uuid_test.cc @@ -214,6 +214,31 @@ TEST(UUID, FromString) { // Mixed case. uuid.InitializeFromString("5762C15D-50b5-4171-a2e9-7429C9EC6CAB"); EXPECT_EQ(uuid.ToString(), "5762c15d-50b5-4171-a2e9-7429c9ec6cab"); + + // Test accepting a StringPiece16. + // clang-format off + static constexpr base::char16 kChar16UUID[] = { + 'f', '3', '2', 'e', '5', 'b', 'd', 'c', '-', + '2', '6', '8', '1', '-', + '4', 'c', '7', '3', '-', + 'a', '4', 'e', '6', '-', + '3', '3', '3', 'f', 'f', 'd', '3', '3', 'b', '3', '3', '3', + }; + // clang-format on + EXPECT_TRUE(uuid.InitializeFromString( + base::StringPiece16(kChar16UUID, arraysize(kChar16UUID)))); + EXPECT_EQ(uuid.ToString(), "f32e5bdc-2681-4c73-a4e6-333ffd33b333"); + +#if defined(OS_WIN) + // Test accepting a StringPiece16 via L"" literals on Windows. + EXPECT_TRUE( + uuid.InitializeFromString(L"F32E5BDC-2681-4C73-A4E6-444FFD44B444")); + EXPECT_EQ(uuid.ToString(), "f32e5bdc-2681-4c73-a4e6-444ffd44b444"); + + EXPECT_TRUE( + uuid.InitializeFromString(L"5762C15D-50b5-4171-a2e9-5555C5EC5CAB")); + EXPECT_EQ(uuid.ToString(), "5762c15d-50b5-4171-a2e9-5555c5ec5cab"); +#endif // OS_WIN } #if defined(OS_WIN) diff --git a/util/net/generate_test_server_key.py b/util/net/generate_test_server_key.py new file mode 100755 index 00000000..6e6340e3 --- /dev/null +++ b/util/net/generate_test_server_key.py @@ -0,0 +1,28 @@ +#!/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 subprocess + +# 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')) diff --git a/util/net/http_body_gzip_test.cc b/util/net/http_body_gzip_test.cc index 85887502..9dafb373 100644 --- a/util/net/http_body_gzip_test.cc +++ b/util/net/http_body_gzip_test.cc @@ -85,7 +85,7 @@ void TestGzipDeflateInflate(const std::string& string) { // 8-byte trailer. constexpr size_t kGzipHeaderSize = 18; - // Per http://www.zlib.net/zlib_tech.html, in the worst case, zlib will store + // Per https://zlib.net/zlib_tech.html, in the worst case, zlib will store // uncompressed data as-is, at an overhead of 5 bytes per 16384-byte block. // Zero-length input will “compress” to a 2-byte zlib stream. Add the overhead // of the gzip wrapper, assuming no optional fields are present. diff --git a/util/net/http_multipart_builder_test.cc b/util/net/http_multipart_builder_test.cc index 08806c37..dab9e7e7 100644 --- a/util/net/http_multipart_builder_test.cc +++ b/util/net/http_multipart_builder_test.cc @@ -19,7 +19,7 @@ #include #include "gtest/gtest.h" -#include "test/gtest_death_check.h" +#include "test/gtest_death.h" #include "test/test_paths.h" #include "util/net/http_body.h" #include "util/net/http_body_test_util.h" diff --git a/util/net/http_transport.cc b/util/net/http_transport.cc index 5b1f611e..7ee381bd 100644 --- a/util/net/http_transport.cc +++ b/util/net/http_transport.cc @@ -52,4 +52,8 @@ void HTTPTransport::SetTimeout(double timeout) { timeout_ = timeout; } +void HTTPTransport::SetRootCACertificatePath(const base::FilePath& cert) { + root_ca_certificate_path_ = cert; +} + } // namespace crashpad diff --git a/util/net/http_transport.h b/util/net/http_transport.h index f9948aa2..f91a5561 100644 --- a/util/net/http_transport.h +++ b/util/net/http_transport.h @@ -18,6 +18,7 @@ #include #include +#include "base/files/file_path.h" #include "base/macros.h" #include "util/net/http_headers.h" @@ -71,6 +72,16 @@ class HTTPTransport { //! \param[in] timeout The request timeout, in seconds. void SetTimeout(double timeout); + //! \brief Sets a certificate file to be used in lieu of the system CA cert + //! bundle. + //! + //! This is exposed primarily for testing with a self-signed certificate, and + //! it isn't necessary to set it in normal use. + //! + //! \param[in] cert The filename of a file in PEM format containing the CA + //! cert to be used for TLS connections. + void SetRootCACertificatePath(const base::FilePath& cert); + //! \brief Performs the HTTP request with the configured parameters and waits //! for the execution to complete. //! @@ -90,10 +101,14 @@ class HTTPTransport { const HTTPHeaders& headers() const { return headers_; } HTTPBodyStream* body_stream() const { return body_stream_.get(); } double timeout() const { return timeout_; } + const base::FilePath& root_ca_certificate_path() const { + return root_ca_certificate_path_; + } private: std::string url_; std::string method_; + base::FilePath root_ca_certificate_path_; HTTPHeaders headers_; std::unique_ptr body_stream_; double timeout_; diff --git a/util/net/http_transport_libcurl.cc b/util/net/http_transport_libcurl.cc deleted file mode 100644 index c16a593d..00000000 --- a/util/net/http_transport_libcurl.cc +++ /dev/null @@ -1,402 +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 "util/net/http_transport.h" - -#include -#include -#include - -#include -#include - -#include "base/logging.h" -#include "base/numerics/safe_math.h" -#include "base/scoped_generic.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/stringprintf.h" -#include "build/build_config.h" -#include "package.h" -#include "util/net/http_body.h" -#include "util/numeric/safe_assignment.h" - -namespace crashpad { - -namespace { - -std::string UserAgent() { - std::string user_agent = base::StringPrintf( - "%s/%s %s", PACKAGE_NAME, PACKAGE_VERSION, curl_version()); - - utsname os; - if (uname(&os) != 0) { - PLOG(WARNING) << "uname"; - } else { - // Match the architecture name that would be used by the kernel, so that the - // strcmp() below can omit the kernel’s architecture name if it’s the same - // as the user process’ architecture. On Linux, these names are normally - // defined in each architecture’s Makefile as UTS_MACHINE, but can be - // overridden in architecture-specific configuration as COMPAT_UTS_MACHINE. - // See linux-4.9.17/arch/*/Makefile and - // linux-4.9.17/arch/*/include/asm/compat.h. In turn, on some systems, these - // names are further overridden or refined in early kernel startup code by - // modifying the string returned by linux-4.9.17/include/linux/utsname.h - // init_utsname() as noted. -#if defined(ARCH_CPU_X86) - // linux-4.9.17/arch/x86/kernel/cpu/bugs.c check_bugs() sets the first digit - // to 4, 5, or 6, but no higher. -#if defined(__i686__) - static constexpr char arch[] = "i686"; -#elif defined(__i586__) - static constexpr char arch[] = "i586"; -#elif defined(__i486__) - static constexpr char arch[] = "i486"; -#else - static constexpr char arch[] = "i386"; -#endif -#elif defined(ARCH_CPU_X86_64) - static constexpr char arch[] = "x86_64"; -#elif defined(ARCH_CPU_ARMEL) - // linux-4.9.17/arch/arm/kernel/setup.c setup_processor() bases the string - // on the ARM processor name and a character identifying little- or - // big-endian. The processor name comes from a definition in - // arch/arm/mm/proc-*.S. -#if defined(__ARM_ARCH_4T__) - static constexpr char arch[] = "armv4t" -#elif defined(__ARM_ARCH_5TEJ__) - static constexpr char arch[] = "armv5tej" -#elif defined(__ARM_ARCH_5TE__) - static constexpr char arch[] = "armv5te" -#elif defined(__ARM_ARCH_5T__) - static constexpr char arch[] = "armv5t" -#elif defined(__ARM_ARCH_7M__) - static constexpr char arch[] = "armv7m" -#else - // Most ARM architectures fall into here, including all profile variants of - // armv6, armv7, armv8, with one exception, armv7m, handled above. - // xstr(__ARM_ARCH) will be the architecture revision number, such as 6, 7, - // or 8. -#define xstr(s) str(s) -#define str(s) #s - static constexpr char arch[] = "armv" xstr(__ARM_ARCH) -#undef str -#undef xstr -#endif -#if defined(ARCH_CPU_LITTLE_ENDIAN) - "l"; -#elif defined(ARCH_CPU_BIG_ENDIAN) - "b"; -#endif -#elif defined(ARCH_CPU_ARM64) - // ARM64 uses aarch64 or aarch64_be as directed by ELF_PLATFORM. See - // linux-4.9.17/arch/arm64/kernel/setup.c setup_arch(). -#if defined(ARCH_CPU_LITTLE_ENDIAN) - static constexpr char arch[] = "aarch64"; -#elif defined(ARCH_CPU_BIG_ENDIAN) - static constexpr char arch[] = "aarch64_be"; -#endif -#elif defined(ARCH_CPU_MIPSEL) - static constexpr char arch[] = "mips"; -#elif defined(ARCH_CPU_MIPS64EL) - static constexpr char arch[] = "mips64"; -#else -#error Port -#endif - - user_agent.append( - base::StringPrintf(" %s/%s (%s", os.sysname, os.release, arch)); - if (strcmp(arch, os.machine) != 0) { - user_agent.append(base::StringPrintf("; %s", os.machine)); - } - user_agent.append(1, ')'); - } - - return user_agent; -} - -std::string CurlErrorMessage(CURLcode curl_err, const std::string& base) { - return base::StringPrintf( - "%s: %s (%d)", base.c_str(), curl_easy_strerror(curl_err), curl_err); -} - -struct ScopedCURLTraits { - static CURL* InvalidValue() { return nullptr; } - static void Free(CURL* curl) { - if (curl) { - curl_easy_cleanup(curl); - } - } -}; -using ScopedCURL = base::ScopedGeneric; - -class CurlSList { - public: - CurlSList() : list_(nullptr) {} - ~CurlSList() { - if (list_) { - curl_slist_free_all(list_); - } - } - - curl_slist* get() const { return list_; } - - bool Append(const char* data) { - curl_slist* list = curl_slist_append(list_, data); - if (!list_) { - list_ = list; - } - return list != nullptr; - } - - private: - curl_slist* list_; - - DISALLOW_COPY_AND_ASSIGN(CurlSList); -}; - -class ScopedClearString { - public: - explicit ScopedClearString(std::string* string) : string_(string) {} - - ~ScopedClearString() { - if (string_) { - string_->clear(); - } - } - - void Disarm() { string_ = nullptr; } - - private: - std::string* string_; - - DISALLOW_COPY_AND_ASSIGN(ScopedClearString); -}; - -class HTTPTransportLibcurl final : public HTTPTransport { - public: - HTTPTransportLibcurl(); - ~HTTPTransportLibcurl() override; - - // HTTPTransport: - bool ExecuteSynchronously(std::string* response_body) override; - - private: - static size_t ReadRequestBody(char* buffer, - size_t size, - size_t nitems, - void* userdata); - static size_t WriteResponseBody(char* buffer, - size_t size, - size_t nitems, - void* userdata); - - DISALLOW_COPY_AND_ASSIGN(HTTPTransportLibcurl); -}; - -HTTPTransportLibcurl::HTTPTransportLibcurl() : HTTPTransport() {} - -HTTPTransportLibcurl::~HTTPTransportLibcurl() {} - -bool HTTPTransportLibcurl::ExecuteSynchronously(std::string* response_body) { - DCHECK(body_stream()); - - response_body->clear(); - - // curl_easy_init() will do this on the first call if it hasn’t been done yet, - // but not in a thread-safe way as is done here. - static CURLcode curl_global_init_err = []() { - return curl_global_init(CURL_GLOBAL_DEFAULT); - }(); - if (curl_global_init_err != CURLE_OK) { - LOG(ERROR) << CurlErrorMessage(curl_global_init_err, "curl_global_init"); - return false; - } - - CurlSList curl_headers; - ScopedCURL curl(curl_easy_init()); - if (!curl.get()) { - LOG(ERROR) << "curl_easy_init"; - return false; - } - -// These macros wrap the repetitive “try something, log an error and return -// false on failure” pattern. Macros are convenient because the log messages -// will point to the correct line number, which can help pinpoint a problem when -// there are as many calls to these functions as there are here. -#define TRY_CURL_EASY_SETOPT(curl, option, parameter) \ - do { \ - CURLcode curl_err = curl_easy_setopt((curl), (option), (parameter)); \ - if (curl_err != CURLE_OK) { \ - LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_setopt"); \ - return false; \ - } \ - } while (false) -#define TRY_CURL_SLIST_APPEND(slist, data) \ - do { \ - if (!(slist).Append(data)) { \ - LOG(ERROR) << "curl_slist_append"; \ - return false; \ - } \ - } while (false) - - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_USERAGENT, UserAgent().c_str()); - - // Accept and automatically decode any encoding that libcurl understands. - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_ACCEPT_ENCODING, ""); - - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_URL, url().c_str()); - - constexpr int kMillisecondsPerSecond = 1E3; - TRY_CURL_EASY_SETOPT(curl.get(), - CURLOPT_TIMEOUT_MS, - static_cast(timeout() * kMillisecondsPerSecond)); - - // If the request body size is known ahead of time, a Content-Length header - // field will be present. Store that to use as CURLOPT_POSTFIELDSIZE_LARGE, - // which will both set the Content-Length field in the request header and - // inform libcurl of the request body size. Otherwise, use Transfer-Encoding: - // chunked, which does not require advance knowledge of the request body size. - bool chunked = true; - size_t content_length; - for (const auto& pair : headers()) { - if (pair.first == kContentLength) { - chunked = !base::StringToSizeT(pair.second, &content_length); - DCHECK(!chunked); - } else { - TRY_CURL_SLIST_APPEND(curl_headers, - (pair.first + ": " + pair.second).c_str()); - } - } - - if (method() == "POST") { - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_POST, 1l); - - // By default when sending a POST request, libcurl includes an “Expect: - // 100-continue” header field. Althogh this header is specified in HTTP/1.1 - // (RFC 2616 §8.2.3, RFC 7231 §5.1.1), even collection servers that claim to - // speak HTTP/1.1 may not respond to it. When sending this header field, - // libcurl will wait for one second for the server to respond with a “100 - // Continue” status before continuing to transmit the request body. This - // delay is avoided by telling libcurl not to send this header field at all. - // The drawback is that certain HTTP error statuses may not be received - // until after substantial amounts of data have been sent to the server. - TRY_CURL_SLIST_APPEND(curl_headers, "Expect:"); - - if (chunked) { - TRY_CURL_SLIST_APPEND(curl_headers, "Transfer-Encoding: chunked"); - } else { - curl_off_t content_length_curl; - if (!AssignIfInRange(&content_length_curl, content_length)) { - LOG(ERROR) << base::StringPrintf("Content-Length %zu too large", - content_length); - return false; - } - TRY_CURL_EASY_SETOPT( - curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, content_length_curl); - } - } else if (method() != "GET") { - // Untested. - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_CUSTOMREQUEST, method().c_str()); - } - - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get()); - - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READFUNCTION, ReadRequestBody); - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READDATA, this); - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEFUNCTION, WriteResponseBody); - TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEDATA, response_body); - -#undef TRY_CURL_EASY_SETOPT -#undef TRY_CURL_SLIST_APPEND - - // If a partial response body is received and then a failure occurs, ensure - // that response_body is cleared. - ScopedClearString clear_response_body(response_body); - - // Do it. - CURLcode curl_err = curl_easy_perform(curl.get()); - if (curl_err != CURLE_OK) { - LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_perform"); - return false; - } - - long status; - curl_err = curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &status); - if (curl_err != CURLE_OK) { - LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_getinfo"); - return false; - } - - if (status != 200) { - LOG(ERROR) << base::StringPrintf("HTTP status %ld", status); - return false; - } - - // The response body is complete. Don’t clear it. - clear_response_body.Disarm(); - - return true; -} - -// static -size_t HTTPTransportLibcurl::ReadRequestBody(char* buffer, - size_t size, - size_t nitems, - void* userdata) { - HTTPTransportLibcurl* self = - reinterpret_cast(userdata); - - // This libcurl callback mimics the silly stdio-style fread() interface: size - // and nitems have been separated and must be multiplied. - base::CheckedNumeric checked_len = base::CheckMul(size, nitems); - size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); - - // Limit the read to what can be expressed in a FileOperationResult. - len = std::min( - len, - static_cast(std::numeric_limits::max())); - - FileOperationResult bytes_read = self->body_stream()->GetBytesBuffer( - reinterpret_cast(buffer), len); - if (bytes_read < 0) { - return CURL_READFUNC_ABORT; - } - - return bytes_read; -} - -// static -size_t HTTPTransportLibcurl::WriteResponseBody(char* buffer, - size_t size, - size_t nitems, - void* userdata) { - std::string* response_body = reinterpret_cast(userdata); - - // This libcurl callback mimics the silly stdio-style fread() interface: size - // and nitems have been separated and must be multiplied. - base::CheckedNumeric checked_len = base::CheckMul(size, nitems); - size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); - - response_body->append(buffer, len); - return len; -} - -} // namespace - -// static -std::unique_ptr HTTPTransport::Create() { - return std::unique_ptr(new HTTPTransportLibcurl()); -} - -} // namespace crashpad diff --git a/util/net/http_transport_none.cc b/util/net/http_transport_none.cc new file mode 100644 index 00000000..1c08c1f1 --- /dev/null +++ b/util/net/http_transport_none.cc @@ -0,0 +1,26 @@ +// 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 "util/net/http_transport.h" + +#include "base/logging.h" + +namespace crashpad { + +std::unique_ptr HTTPTransport::Create() { + NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 + return std::unique_ptr(); +} + +} // namespace crashpad diff --git a/util/net/http_transport_socket.cc b/util/net/http_transport_socket.cc new file mode 100644 index 00000000..f0e2dc14 --- /dev/null +++ b/util/net/http_transport_socket.cc @@ -0,0 +1,584 @@ +// 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/net/http_transport.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/numerics/safe_conversions.h" +#include "base/posix/eintr_wrapper.h" +#include "base/scoped_generic.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "util/file/file_io.h" +#include "util/net/http_body.h" +#include "util/net/url.h" +#include "util/stdlib/string_number_conversion.h" +#include "util/string/split_string.h" + +#if defined(CRASHPAD_USE_BORINGSSL) +#include +#endif + +namespace crashpad { + +namespace { + +constexpr const char kCRLFTerminator[] = "\r\n"; + +class HTTPTransportSocket final : public HTTPTransport { + public: + HTTPTransportSocket() = default; + ~HTTPTransportSocket() override = default; + + bool ExecuteSynchronously(std::string* response_body) override; + + private: + DISALLOW_COPY_AND_ASSIGN(HTTPTransportSocket); +}; + +struct ScopedAddrinfoTraits { + static addrinfo* InvalidValue() { return nullptr; } + static void Free(addrinfo* ai) { freeaddrinfo(ai); } +}; +using ScopedAddrinfo = + base::ScopedGeneric; + +class Stream { + public: + virtual ~Stream() = default; + + virtual bool LoggingWrite(const void* data, size_t size) = 0; + virtual bool LoggingRead(void* data, size_t size) = 0; + virtual bool LoggingReadToEOF(std::string* contents) = 0; +}; + +class FdStream : public Stream { + public: + explicit FdStream(int fd) : fd_(fd) { CHECK(fd_ >= 0); } + + bool LoggingWrite(const void* data, size_t size) override { + return LoggingWriteFile(fd_, data, size); + } + + bool LoggingRead(void* data, size_t size) override { + return LoggingReadFileExactly(fd_, data, size); + } + + bool LoggingReadToEOF(std::string* result) override{ + return crashpad::LoggingReadToEOF(fd_, result); + } + + private: + int fd_; + + DISALLOW_COPY_AND_ASSIGN(FdStream); +}; + +#if defined(CRASHPAD_USE_BORINGSSL) +class SSLStream : public Stream { + public: + SSLStream() = default; + + bool Initialize(const base::FilePath& root_cert_path, + int sock, + const std::string& hostname) { + SSL_library_init(); + + ctx_.reset(SSL_CTX_new(TLS_method())); + if (!ctx_.is_valid()) { + LOG(ERROR) << "SSL_CTX_new"; + return false; + } + + if (SSL_CTX_set_min_proto_version(ctx_.get(), TLS1_2_VERSION) <= 0) { + LOG(ERROR) << "SSL_CTX_set_min_proto_version"; + return false; + } + + SSL_CTX_set_verify(ctx_.get(), SSL_VERIFY_PEER, nullptr); + SSL_CTX_set_verify_depth(ctx_.get(), 5); + + if (!root_cert_path.empty()) { + if (SSL_CTX_load_verify_locations( + ctx_.get(), root_cert_path.value().c_str(), nullptr) <= 0) { + LOG(ERROR) << "SSL_CTX_load_verify_locations"; + return false; + } + } else { +#if defined(OS_LINUX) + if (SSL_CTX_load_verify_locations( + ctx_.get(), nullptr, "/etc/ssl/certs") <= 0) { + LOG(ERROR) << "SSL_CTX_load_verify_locations"; + return false; + } +#elif defined(OS_FUCHSIA) + if (SSL_CTX_load_verify_locations( + ctx_.get(), "/config/ssl/cert.pem", nullptr) <= 0) { + LOG(ERROR) << "SSL_CTX_load_verify_locations"; + return false; + } +#else +#error cert store location +#endif + } + + ssl_.reset(SSL_new(ctx_.get())); + if (!ssl_.is_valid()) { + LOG(ERROR) << "SSL_new"; + return false; + } + + BIO* bio = BIO_new_socket(sock, BIO_NOCLOSE); + if (!bio) { + LOG(ERROR) << "BIO_new_socket"; + return false; + } + + // SSL_set_bio() takes ownership of |bio|. + SSL_set_bio(ssl_.get(), bio, bio); + + if (SSL_set_tlsext_host_name(ssl_.get(), hostname.c_str()) == 0) { + LOG(ERROR) << "SSL_set_tlsext_host_name"; + return false; + } + + if (SSL_connect(ssl_.get()) <= 0) { + LOG(ERROR) << "SSL_connect"; + return false; + } + + return true; + } + + bool LoggingWrite(const void* data, size_t size) override { + return SSL_write(ssl_.get(), data, size) != 0; + } + + bool LoggingRead(void* data, size_t size) override { + return SSL_read(ssl_.get(), data, size) != 0; + } + + bool LoggingReadToEOF(std::string* contents) override { + contents->clear(); + char buffer[4096]; + FileOperationResult rv; + while ((rv = SSL_read(ssl_.get(), buffer, sizeof(buffer))) > 0) { + DCHECK_LE(static_cast(rv), sizeof(buffer)); + contents->append(buffer, rv); + } + if (rv < 0) { + LOG(ERROR) << "SSL_read"; + contents->clear(); + return false; + } + return true; + } + + private: + struct ScopedSSLCTXTraits { + static SSL_CTX* InvalidValue() { return nullptr; } + static void Free(SSL_CTX* ctx) { SSL_CTX_free(ctx); } + }; + using ScopedSSLCTX = base::ScopedGeneric; + + struct ScopedSSLTraits { + static SSL* InvalidValue() { return nullptr; } + static void Free(SSL* ssl) { + SSL_shutdown(ssl); + SSL_free(ssl); + } + }; + using ScopedSSL = base::ScopedGeneric; + + ScopedSSLCTX ctx_; + ScopedSSL ssl_; + + DISALLOW_COPY_AND_ASSIGN(SSLStream); +}; +#endif + +bool WaitUntilSocketIsReady(int sock) { + pollfd pollfds; + pollfds.fd = sock; + pollfds.events = POLLIN | POLLPRI | POLLOUT; + constexpr int kTimeoutMS = 1000; + int ret = HANDLE_EINTR(poll(&pollfds, 1, kTimeoutMS)); + if (ret < 0) { + PLOG(ERROR) << "poll"; + return false; + } else if (ret == 1) { + if (pollfds.revents & POLLERR) { + int err; + socklen_t err_len = sizeof(err); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) != 0) { + PLOG(ERROR) << "getsockopt"; + } else { + errno = err; + PLOG(ERROR) << "POLLERR"; + } + return false; + } + if (pollfds.revents & POLLHUP) { + return false; + } + return (pollfds.revents & POLLIN) != 0 || (pollfds.revents & POLLOUT) != 0; + } + + // Timeout. + return false; +} + +class ScopedSetNonblocking { + public: + explicit ScopedSetNonblocking(int sock) : sock_(sock) { + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) { + PLOG(ERROR) << "fcntl"; + sock_ = -1; + return; + } + + if (fcntl(sock_, F_SETFL, flags | O_NONBLOCK) < 0) { + PLOG(ERROR) << "fcntl"; + sock_ = -1; + } + } + + ~ScopedSetNonblocking() { + if (sock_ >= 0) { + int flags = fcntl(sock_, F_GETFL, 0); + if (flags < 0) { + PLOG(ERROR) << "fcntl"; + return; + } + + if (fcntl(sock_, F_SETFL, flags & (~O_NONBLOCK)) < 0) { + PLOG(ERROR) << "fcntl"; + } + } + } + + private: + int sock_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSetNonblocking); +}; + +base::ScopedFD CreateSocket(const std::string& hostname, + const std::string& port) { + addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + + addrinfo* addrinfo_raw; + if (getaddrinfo(hostname.c_str(), port.c_str(), &hints, &addrinfo_raw) < 0) { + PLOG(ERROR) << "getaddrinfo"; + return base::ScopedFD(); + } + ScopedAddrinfo addrinfo(addrinfo_raw); + + for (const auto* ap = addrinfo.get(); ap; ap = ap->ai_next) { + base::ScopedFD result( + socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol)); + if (!result.is_valid()) { + continue; + } + + { + // Set socket to non-blocking to avoid hanging for a long time if the + // network is down. + ScopedSetNonblocking nonblocking(result.get()); + + if (HANDLE_EINTR(connect(result.get(), ap->ai_addr, ap->ai_addrlen)) < + 0) { + if (errno != EINPROGRESS) { + PLOG(ERROR) << "connect"; + } else if (WaitUntilSocketIsReady(result.get())) { + return result; + } + return base::ScopedFD(); + } + + return result; + } + } + + return base::ScopedFD(); +} + +bool WriteRequest(Stream* stream, + const std::string& method, + const std::string& resource, + const HTTPHeaders& headers, + HTTPBodyStream* body_stream) { + std::string request_line = base::StringPrintf( + "%s %s HTTP/1.0\r\n", method.c_str(), resource.c_str()); + if (!stream->LoggingWrite(request_line.data(), request_line.size())) + return false; + + // Write headers, and determine if Content-Length has been specified. + bool chunked = true; + size_t content_length = 0; + for (const auto& header : headers) { + std::string header_str = base::StringPrintf( + "%s: %s\r\n", header.first.c_str(), header.second.c_str()); + if (header.first == kContentLength) { + chunked = !base::StringToSizeT(header.second, &content_length); + DCHECK(!chunked); + } + + if (!stream->LoggingWrite(header_str.data(), header_str.size())) + return false; + } + + // If no Content-Length, then encode as chunked, so add that header too. + if (chunked) { + static constexpr const char kTransferEncodingChunked[] = + "Transfer-Encoding: chunked\r\n"; + if (!stream->LoggingWrite(kTransferEncodingChunked, + strlen(kTransferEncodingChunked))) { + return false; + } + } + + if (!stream->LoggingWrite(kCRLFTerminator, strlen(kCRLFTerminator))) { + return false; + } + + FileOperationResult data_bytes; + do { + constexpr size_t kCRLFSize = arraysize(kCRLFTerminator) - 1; + struct __attribute__((packed)) { + char size[8]; + char crlf[2]; + uint8_t data[32 * 1024 + kCRLFSize]; + } buf; + static_assert( + sizeof(buf) == sizeof(buf.size) + sizeof(buf.crlf) + sizeof(buf.data), + "buf should not have padding"); + + // Read a block of data. + data_bytes = + body_stream->GetBytesBuffer(buf.data, sizeof(buf.data) - kCRLFSize); + if (data_bytes == -1) { + return false; + } + DCHECK_GE(data_bytes, 0); + DCHECK_LE(static_cast(data_bytes), sizeof(buf.data) - kCRLFSize); + + void* write_start; + size_t write_size; + + if (chunked) { + // Chunked encoding uses the entirety of buf. buf.size is presented in + // hexadecimal without any leading "0x". The terminating CR and LF will be + // 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(data_bytes)); + DCHECK_GE(rv, 0); + DCHECK_EQ(static_cast(rv), sizeof(buf.size)); + DCHECK_NE(buf.size[sizeof(buf.size) - 1], '\0'); + + memcpy(&buf.crlf[0], kCRLFTerminator, kCRLFSize); + memcpy(&buf.data[data_bytes], kCRLFTerminator, kCRLFSize); + + // Skip leading zeroes in the chunk size. + size_t size_len; + for (size_len = sizeof(buf.size); size_len > 1; --size_len) { + if (buf.size[sizeof(buf.size) - size_len] != '0') { + break; + } + } + + write_start = buf.crlf - size_len; + write_size = size_len + sizeof(buf.crlf) + data_bytes + kCRLFSize; + } else { + // When not using chunked encoding, only use buf.data. + write_start = buf.data; + write_size = data_bytes; + } + + // write_size will be 0 at EOF in non-chunked mode. Skip the write in that + // case. In contrast, at EOF in chunked mode, a zero-length chunk must be + // sent to signal EOF. This will happen when processing the EOF indicated by + // a 0 return from body_stream()->GetBytesBuffer() above. + if (write_size != 0) { + if (!stream->LoggingWrite(write_start, write_size)) + return false; + } + } while (data_bytes > 0); + + return true; +} + +bool ReadLine(Stream* stream, std::string* line) { + line->clear(); + for (;;) { + char byte; + if (!stream->LoggingRead(&byte, 1)) { + return false; + } + + line->append(&byte, 1); + if (byte == '\n') + return true; + } +} + +bool StartsWith(const std::string& str, const char* with, size_t len) { + return str.compare(0, len, with) == 0; +} + +bool ReadResponseLine(Stream* stream) { + std::string response_line; + if (!ReadLine(stream, &response_line)) { + 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)); +} + +bool ReadResponseHeaders(Stream* stream, HTTPHeaders* headers) { + for (;;) { + std::string line; + if (!ReadLine(stream, &line)) { + return false; + } + + if (line == kCRLFTerminator) { + return true; + } + + std::string left, right; + if (!SplitStringFirst(line, ':', &left, &right)) { + LOG(ERROR) << "SplitStringFirst"; + return false; + } + DCHECK_EQ(right[right.size() - 1], '\n'); + DCHECK_EQ(right[right.size() - 2], '\r'); + DCHECK_EQ(right[0], ' '); + DCHECK_NE(right[1], ' '); + right = right.substr(1, right.size() - 3); + (*headers)[left] = right; + } +} + +bool ReadContentChunked(Stream* stream, std::string* body) { + // TODO(scottmg): https://crashpad.chromium.org/bug/196. + LOG(ERROR) << "TODO(scottmg): chunked response read"; + return false; +} + +bool ReadResponse(Stream* stream, std::string* response_body) { + response_body->clear(); + + if (!ReadResponseLine(stream)) { + return false; + } + + HTTPHeaders response_headers; + if (!ReadResponseHeaders(stream, &response_headers)) { + return false; + } + + auto it = response_headers.find("Content-Length"); + size_t len = 0; + if (it != response_headers.end()) { + if (!base::StringToSizeT(it->second, &len)) { + LOG(ERROR) << "invalid Content-Length"; + return false; + } + } + + if (len) { + response_body->resize(len, 0); + return stream->LoggingRead(&(*response_body)[0], len); + } + + it = response_headers.find("Transfer-Encoding"); + bool chunked = false; + if (it != response_headers.end() && it->second == "chunked") { + chunked = true; + } + + return chunked ? ReadContentChunked(stream, response_body) + : stream->LoggingReadToEOF(response_body); +} + +bool HTTPTransportSocket::ExecuteSynchronously(std::string* response_body) { + std::string scheme, hostname, port, resource; + if (!CrackURL(url(), &scheme, &hostname, &port, &resource)) { + return false; + } + +#if !defined(CRASHPAD_USE_BORINGSSL) + CHECK(scheme == "http"); +#endif + + base::ScopedFD sock(CreateSocket(hostname, port)); + if (!sock.is_valid()) { + return false; + } + +#if defined(CRASHPAD_USE_BORINGSSL) + std::unique_ptr stream; + if (scheme == "https") { + auto ssl_stream = std::make_unique(); + if (!ssl_stream->Initialize( + root_ca_certificate_path(), sock.get(), hostname)) { + LOG(ERROR) << "SSLStream Initialize"; + return false; + } + stream = std::move(ssl_stream); + } else { + stream = std::make_unique(sock.get()); + } +#else // CRASHPAD_USE_BORINGSSL + std::unique_ptr stream(std::make_unique(sock.get())); +#endif // CRASHPAD_USE_BORINGSSL + + if (!WriteRequest( + stream.get(), method(), resource, headers(), body_stream())) { + return false; + } + + if (!ReadResponse(stream.get(), response_body)) { + return false; + } + + return true; +} + +} // namespace + +// static +std::unique_ptr HTTPTransport::Create() { + return std::unique_ptr(new HTTPTransportSocket); +} + +} // namespace crashpad diff --git a/util/net/http_transport_test.cc b/util/net/http_transport_test.cc index fc02ba19..7b5f41df 100644 --- a/util/net/http_transport_test.cc +++ b/util/net/http_transport_test.cc @@ -42,12 +42,23 @@ namespace crashpad { namespace test { namespace { +#if defined(OS_WIN) +std::string ToUTF8IfWin(const base::string16& x) { + return base::UTF16ToUTF8(x); +} +#else +std::string ToUTF8IfWin(const std::string& x) { + return x; +} +#endif + class HTTPTransportTestFixture : public MultiprocessExec { public: using RequestValidator = void(*)(HTTPTransportTestFixture*, const std::string&); - HTTPTransportTestFixture(const HTTPHeaders& headers, + HTTPTransportTestFixture(const base::FilePath::StringType& scheme, + const HTTPHeaders& headers, std::unique_ptr body_stream, uint16_t http_response_code, RequestValidator request_validator) @@ -55,20 +66,33 @@ class HTTPTransportTestFixture : public MultiprocessExec { headers_(headers), body_stream_(std::move(body_stream)), response_code_(http_response_code), - request_validator_(request_validator) { - base::FilePath server_path = TestPaths::TestDataRoot().Append( - FILE_PATH_LITERAL("util/net/http_transport_test_server.py")); -#if defined(OS_POSIX) - SetChildCommand(server_path, nullptr); -#elif defined(OS_WIN) - // Explicitly invoke a shell and python so that python can be found in the - // path, and run the test script. - std::vector args; - args.push_back("/c"); - args.push_back("python"); - args.push_back(base::UTF16ToUTF8(server_path.value())); - SetChildCommand(base::FilePath(_wgetenv(L"COMSPEC")), &args); -#endif // OS_POSIX + request_validator_(request_validator), + cert_(), + scheme_and_host_() { + base::FilePath server_path = TestPaths::Executable().DirName().Append( + FILE_PATH_LITERAL("http_transport_test_server") +#if defined(OS_WIN) + FILE_PATH_LITERAL(".exe") +#endif + ); + + if (ToUTF8IfWin(scheme) == "http") { + scheme_and_host_ = "http://localhost"; + SetChildCommand(server_path, nullptr); + } else { + std::vector args; + cert_ = TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"), + FILE_PATH_LITERAL("cert"), + TestPaths::FileType::kCertificate); + args.push_back(ToUTF8IfWin(cert_.value())); + args.emplace_back(ToUTF8IfWin( + TestPaths::BuildArtifact(FILE_PATH_LITERAL("util"), + FILE_PATH_LITERAL("key"), + TestPaths::FileType::kCertificate) + .value())); + SetChildCommand(server_path, &args); + scheme_and_host_ = "https://localhost"; + } } const HTTPHeaders& headers() { return headers_; } @@ -100,7 +124,12 @@ class HTTPTransportTestFixture : public MultiprocessExec { // Now execute the HTTP request. std::unique_ptr transport(HTTPTransport::Create()); transport->SetMethod("POST"); - transport->SetURL(base::StringPrintf("http://127.0.0.1:%d/upload", port)); + + if (!cert_.empty()) { + transport->SetRootCACertificatePath(cert_); + } + transport->SetURL( + base::StringPrintf("%s:%d/upload", scheme_and_host_.c_str(), port)); for (const auto& pair : headers_) { transport->SetHeader(pair.first, pair.second); } @@ -134,6 +163,8 @@ class HTTPTransportTestFixture : public MultiprocessExec { std::unique_ptr body_stream_; uint16_t response_code_; RequestValidator request_validator_; + base::FilePath cert_; + std::string scheme_and_host_; }; constexpr char kMultipartFormData[] = "multipart/form-data"; @@ -215,7 +246,10 @@ void ValidFormData(HTTPTransportTestFixture* fixture, EXPECT_EQ(request.substr(body_start), expected); } -TEST(HTTPTransport, ValidFormData) { +class HTTPTransport + : public testing::TestWithParam {}; + +TEST_P(HTTPTransport, ValidFormData) { HTTPMultipartBuilder builder; builder.SetFormData("key1", "test"); builder.SetFormData("key2", "--abcdefg123"); @@ -223,12 +257,12 @@ TEST(HTTPTransport, ValidFormData) { HTTPHeaders headers; builder.PopulateContentHeaders(&headers); - HTTPTransportTestFixture test( + HTTPTransportTestFixture test(GetParam(), headers, builder.GetBodyStream(), 200, &ValidFormData); test.Run(); } -TEST(HTTPTransport, ValidFormData_Gzip) { +TEST_P(HTTPTransport, ValidFormData_Gzip) { HTTPMultipartBuilder builder; builder.SetGzipEnabled(true); builder.SetFormData("key1", "test"); @@ -237,8 +271,8 @@ TEST(HTTPTransport, ValidFormData_Gzip) { HTTPHeaders headers; builder.PopulateContentHeaders(&headers); - HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200, - &ValidFormData); + HTTPTransportTestFixture test( + GetParam(), headers, builder.GetBodyStream(), 200, &ValidFormData); test.Run(); } @@ -251,11 +285,11 @@ void ErrorResponse(HTTPTransportTestFixture* fixture, EXPECT_EQ(content_type, kTextPlain); } -TEST(HTTPTransport, ErrorResponse) { +TEST_P(HTTPTransport, ErrorResponse) { HTTPMultipartBuilder builder; HTTPHeaders headers; headers[kContentType] = kTextPlain; - HTTPTransportTestFixture test(headers, builder.GetBodyStream(), + HTTPTransportTestFixture test(GetParam(), headers, builder.GetBodyStream(), 404, &ErrorResponse); test.Run(); } @@ -279,7 +313,7 @@ void UnchunkedPlainText(HTTPTransportTestFixture* fixture, EXPECT_EQ(request.substr(body_start + 2), kTextBody); } -TEST(HTTPTransport, UnchunkedPlainText) { +TEST_P(HTTPTransport, UnchunkedPlainText) { std::unique_ptr body_stream( new StringHTTPBodyStream(kTextBody)); @@ -287,12 +321,13 @@ TEST(HTTPTransport, UnchunkedPlainText) { headers[kContentType] = kTextPlain; headers[kContentLength] = base::StringPrintf("%" PRIuS, strlen(kTextBody)); - HTTPTransportTestFixture test( + HTTPTransportTestFixture test(GetParam(), headers, std::move(body_stream), 200, &UnchunkedPlainText); test.Run(); } -void RunUpload33k(bool has_content_length) { +void RunUpload33k(const base::FilePath::StringType& 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 @@ -309,6 +344,7 @@ void RunUpload33k(bool has_content_length) { base::StringPrintf("%" PRIuS, request_string.size()); } HTTPTransportTestFixture test( + scheme, headers, std::move(body_stream), 200, @@ -319,15 +355,31 @@ void RunUpload33k(bool has_content_length) { test.Run(); } -TEST(HTTPTransport, Upload33k) { - RunUpload33k(true); +TEST_P(HTTPTransport, Upload33k) { + RunUpload33k(GetParam(), true); } -TEST(HTTPTransport, Upload33k_LengthUnknown) { +TEST_P(HTTPTransport, Upload33k_LengthUnknown) { // The same as Upload33k, but without declaring Content-Length ahead of time. - RunUpload33k(false); + RunUpload33k(GetParam(), false); } +#if defined(CRASHPAD_USE_BORINGSSL) +// 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"))); +#else +INSTANTIATE_TEST_CASE_P(HTTPTransport, + HTTPTransport, + testing::Values(FILE_PATH_LITERAL("http"))); +#endif + } // namespace } // namespace test } // namespace crashpad diff --git a/util/net/http_transport_test_server.cc b/util/net/http_transport_test_server.cc new file mode 100644 index 00000000..a3bcc6a5 --- /dev/null +++ b/util/net/http_transport_test_server.cc @@ -0,0 +1,134 @@ +// 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. + +// A one-shot testing webserver. +// +// When invoked, this server will write a short integer to stdout, indicating on +// which port the server is listening. It will then read one integer from stdin, +// indicating the response code to be sent in response to a request. It also +// reads 16 characters from stdin, which, after having "\r\n" appended, will +// form the response body in a successful response (one with code 200). The +// server will process one HTTP request, deliver the prearranged response to the +// client, and write the entire request to stdout. It will then terminate. + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "tools/tool_support.h" +#include "util/file/file_io.h" + +#if COMPILER_MSVC +#pragma warning(push) +#pragma warning(disable: 4244 4245 4267 4702) +#endif + +#if defined(CRASHPAD_USE_BORINGSSL) +#define CPPHTTPLIB_OPENSSL_SUPPORT +#endif + +#define CPPHTTPLIB_ZLIB_SUPPORT +#include "third_party/cpp-httplib/cpp-httplib/httplib.h" + +#if COMPILER_MSVC +#pragma warning(pop) +#endif + +namespace crashpad { +namespace { + +int HttpTransportTestServerMain(int argc, char* argv[]) { + std::unique_ptr server; + if (argc == 1) { + server.reset(new httplib::Server); +#if defined(CRASHPAD_USE_BORINGSSL) + } else if (argc == 3) { + server.reset(new httplib::SSLServer(argv[1], argv[2])); +#endif + } else { + LOG(ERROR) << "usage: http_transport_test_server [cert.pem key.pem]"; + return 1; + } + + + if (!server->is_valid()) { + LOG(ERROR) << "server creation failed"; + return 1; + } + + server->set_keep_alive_max_count(1); + + uint16_t response_code; + char response[16]; + + std::string to_stdout; + + server->Post("/upload", + [&response, &response_code, &server, &to_stdout]( + const httplib::Request& req, httplib::Response& res) { + res.status = response_code; + if (response_code == 200) { + res.set_content(std::string(response, 16) + "\r\n", + "text/plain"); + } else { + res.set_content("error", "text/plain"); + } + + to_stdout += "POST /upload HTTP/1.0\r\n"; + for (const auto& h : req.headers) { + to_stdout += base::StringPrintf( + "%s: %s\r\n", h.first.c_str(), h.second.c_str()); + } + to_stdout += "\r\n"; + to_stdout += req.body; + + server->stop(); + }); + + uint16_t port = + base::checked_cast(server->bind_to_any_port("localhost")); + + CheckedWriteFile( + StdioFileHandle(StdioStream::kStandardOutput), &port, sizeof(port)); + + CheckedReadFileExactly(StdioFileHandle(StdioStream::kStandardInput), + &response_code, + sizeof(response_code)); + + CheckedReadFileExactly(StdioFileHandle(StdioStream::kStandardInput), + &response, + sizeof(response)); + + server->listen_after_bind(); + + LoggingWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + to_stdout.data(), + to_stdout.size()); + + return 0; +} + +} // namespace +} // namespace crashpad + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) +int main(int argc, char* argv[]) { + return crashpad::HttpTransportTestServerMain(argc, argv); +} +#elif defined(OS_WIN) +int wmain(int argc, wchar_t* argv[]) { + return crashpad::ToolSupport::Wmain( + argc, argv, crashpad::HttpTransportTestServerMain); +} +#endif // OS_POSIX diff --git a/util/net/http_transport_test_server.py b/util/net/http_transport_test_server.py deleted file mode 100755 index 7ea15719..00000000 --- a/util/net/http_transport_test_server.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -# 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. - -"""A one-shot testing webserver. - -When invoked, this server will write a short integer to stdout, indiciating on -which port the server is listening. It will then read one integer from stdin, -indiciating the response code to be sent in response to a request. It also reads -16 characters from stdin, which, after having "\r\n" appended, will form the -response body in a successful response (one with code 200). The server will -process one HTTP request, deliver the prearranged response to the client, and -write the entire request to stdout. It will then terminate. - -This server is written in Python since it provides a simple HTTP stack, and -because parsing chunked encoding is safer and easier in a memory-safe language. -This could easily have been written in C++ instead. -""" - -import BaseHTTPServer -import struct -import sys -import zlib - -class BufferedReadFile(object): - """A File-like object that stores all read contents into a buffer.""" - - def __init__(self, real_file): - self.file = real_file - self.buffer = "" - - def read(self, size=-1): - buf = self.file.read(size) - self.buffer += buf - return buf - - def readline(self, size=-1): - buf = self.file.readline(size) - self.buffer += buf - return buf - - def flush(self): - self.file.flush() - - def close(self): - self.file.close() - - -class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - # Everything to be written to stdout is collected into this string. It can’t - # be written to stdout until after the HTTP transaction is complete, because - # stdout is a pipe being read by a test program that’s also the HTTP client. - # The test program expects to complete the entire HTTP transaction before it - # even starts reading this script’s stdout. If the stdout pipe buffer fills up - # during an HTTP transaction, deadlock would result. - raw_request = '' - - response_code = 500 - response_body = '' - - def handle_one_request(self): - # Wrap the rfile in the buffering file object so that the raw header block - # can be written to stdout after it is parsed. - self.rfile = BufferedReadFile(self.rfile) - BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self) - - def do_POST(self): - RequestHandler.raw_request = self.rfile.buffer - self.rfile.buffer = '' - - if self.headers.get('Transfer-Encoding', '').lower() == 'chunked': - if 'Content-Length' in self.headers: - raise AssertionError - body = self.handle_chunked_encoding() - else: - length = int(self.headers.get('Content-Length', -1)) - body = self.rfile.read(length) - - if self.headers.get('Content-Encoding', '').lower() == 'gzip': - # 15 is the value of |wbits|, which should be at the maximum possible - # value to ensure that any gzip stream can be decoded. The offset of 16 - # specifies that the stream to decompress will be formatted with a gzip - # wrapper. - body = zlib.decompress(body, 16 + 15) - - RequestHandler.raw_request += body - - self.send_response(self.response_code) - self.end_headers() - if self.response_code == 200: - self.wfile.write(self.response_body) - self.wfile.write('\r\n') - - def handle_chunked_encoding(self): - """This parses a "Transfer-Encoding: Chunked" body in accordance with - RFC 7230 §4.1. This returns the result as a string. - """ - body = '' - chunk_size = self.read_chunk_size() - while chunk_size > 0: - # Read the body. - data = self.rfile.read(chunk_size) - chunk_size -= len(data) - body += data - - # Finished reading this chunk. - if chunk_size == 0: - # Read through any trailer fields. - trailer_line = self.rfile.readline() - while trailer_line.strip() != '': - trailer_line = self.rfile.readline() - - # Read the chunk size. - chunk_size = self.read_chunk_size() - return body - - def read_chunk_size(self): - # Read the whole line, including the \r\n. - chunk_size_and_ext_line = self.rfile.readline() - # Look for a chunk extension. - chunk_size_end = chunk_size_and_ext_line.find(';') - if chunk_size_end == -1: - # No chunk extensions; just encounter the end of line. - chunk_size_end = chunk_size_and_ext_line.find('\r') - if chunk_size_end == -1: - self.send_response(400) # Bad request. - return -1 - return int(chunk_size_and_ext_line[:chunk_size_end], base=16) - - def log_request(self, code='-', size='-'): - # The default implementation logs these to sys.stderr, which is just noise. - pass - - -def Main(): - if sys.platform == 'win32': - import os, msvcrt - msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) - - # Start the server. - server = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), RequestHandler) - - # Write the port as an unsigned short to the parent process. - sys.stdout.write(struct.pack('=H', server.server_address[1])) - sys.stdout.flush() - - # Read the desired test response code as an unsigned short and the desired - # response body as a 16-byte string from the parent process. - RequestHandler.response_code, RequestHandler.response_body = \ - struct.unpack('=H16s', sys.stdin.read(struct.calcsize('=H16s'))) - - # Handle the request. - server.handle_request() - - # Share the entire request with the test program, which will validate it. - sys.stdout.write(RequestHandler.raw_request) - sys.stdout.flush() - -if __name__ == '__main__': - Main() diff --git a/util/net/http_transport_win.cc b/util/net/http_transport_win.cc index dc3eeeb7..18d343cc 100644 --- a/util/net/http_transport_win.cc +++ b/util/net/http_transport_win.cc @@ -169,8 +169,7 @@ bool HTTPTransportWin::ExecuteSynchronously(std::string* response_body) { url_components.dwUrlPathLength = 1; url_components.dwExtraInfoLength = 1; std::wstring url_wide(base::UTF8ToUTF16(url())); - // dwFlags = ICU_REJECT_USERPWD fails on XP. See "Community Additions" at: - // https://msdn.microsoft.com/en-us/library/aa384092.aspx + // dwFlags = ICU_REJECT_USERPWD fails on XP. if (!WinHttpCrackUrl( url_wide.c_str(), 0, 0, &url_components)) { LOG(ERROR) << WinHttpMessage("WinHttpCrackUrl"); diff --git a/util/net/url.cc b/util/net/url.cc index 6309a8f7..9ce88e93 100644 --- a/util/net/url.cc +++ b/util/net/url.cc @@ -16,6 +16,7 @@ #include +#include "base/logging.h" #include "base/strings/stringprintf.h" namespace crashpad { @@ -41,4 +42,51 @@ std::string URLEncode(const std::string& url) { return encoded; } +bool CrackURL(const std::string& url, + std::string* scheme, + std::string* host, + std::string* port, + std::string* rest) { + std::string result_scheme; + std::string result_port; + + size_t host_start; + static constexpr const char kHttp[] = "http://"; + static constexpr const char kHttps[] = "https://"; + if (url.compare(0, strlen(kHttp), kHttp) == 0) { + result_scheme = "http"; + result_port = "80"; + host_start = strlen(kHttp); + } else if (url.compare(0, strlen(kHttps), kHttps) == 0) { + result_scheme = "https"; + result_port = "443"; + host_start = strlen(kHttps); + } else { + LOG(ERROR) << "expecting http or https"; + return false; + } + + // Find the start of the resource. + size_t resource_start = url.find('/', host_start); + if (resource_start == std::string::npos) { + LOG(ERROR) << "no resource component"; + return false; + } + + scheme->swap(result_scheme); + port->swap(result_port); + std::string host_and_possible_port = + url.substr(host_start, resource_start - host_start); + size_t colon = host_and_possible_port.find(':'); + if (colon == std::string::npos) { + *host = host_and_possible_port; + } else { + *host = host_and_possible_port.substr(0, colon); + *port = host_and_possible_port.substr(colon + 1); + } + + *rest = url.substr(resource_start); + return true; +} + } // namespace crashpad diff --git a/util/net/url.h b/util/net/url.h index 8817571f..f83d436c 100644 --- a/util/net/url.h +++ b/util/net/url.h @@ -26,6 +26,25 @@ namespace crashpad { //! \return The encoded string. std::string URLEncode(const std::string& url); +//! \brief Crack a URL into component parts. +//! +//! This is not a general function, and works only on the limited style of URLs +//! that are expected to be used by HTTPTransport::SetURL(). +//! +//! \param[in] url The URL to crack. +//! \param[out] scheme The request scheme, either http or https. +//! \param[out] host The hostname. +//! \param[out] port The port. +//! \param[out] rest The remainder of the URL (both resource and URL params). +//! \return `true` on success in which case all output parameters will be filled +//! out, or `false` on failure, in which case the output parameters will be +//! unmodified and an error will be logged. +bool CrackURL(const std::string& url, + std::string* scheme, + std::string* host, + std::string* port, + std::string* rest); + } // namespace crashpad #endif // CRASHPAD_UTIL_NET_URL_H_ diff --git a/util/net/url_test.cc b/util/net/url_test.cc index d9376671..4f076413 100644 --- a/util/net/url_test.cc +++ b/util/net/url_test.cc @@ -42,6 +42,91 @@ TEST(URLEncode, SimpleAddress) { "3Dvalue"); } +TEST(CrackURL, Unsupported) { + std::string scheme, host, port, rest; + + // Not HTTP. + EXPECT_FALSE(CrackURL("file://stuff/things", &scheme, &host, &port, &rest)); + + // No resource. + EXPECT_FALSE(CrackURL("file://stuff", &scheme, &host, &port, &rest)); + EXPECT_FALSE(CrackURL("http://stuff", &scheme, &host, &port, &rest)); + EXPECT_FALSE(CrackURL("https://stuff", &scheme, &host, &port, &rest)); +} + +TEST(CrackURL, UnsupportedDoesNotModifiedOutArgs) { + std::string scheme, host, port, rest; + + scheme = "scheme"; + host = "host"; + port = "port"; + rest = "rest"; + + // Bad scheme. + EXPECT_FALSE(CrackURL("file://stuff/things", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "scheme"); + EXPECT_EQ(host, "host"); + EXPECT_EQ(port, "port"); + EXPECT_EQ(rest, "rest"); + + scheme = "scheme"; + host = "host"; + port = "port"; + rest = "rest"; + + // No resource. + EXPECT_FALSE(CrackURL("http://stuff", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "scheme"); + EXPECT_EQ(host, "host"); + EXPECT_EQ(port, "port"); + EXPECT_EQ(rest, "rest"); +} + +TEST(CrackURL, BasicWithDefaultPort) { + std::string scheme, host, port, rest; + + ASSERT_TRUE(CrackURL("http://stuff/things", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "http"); + EXPECT_EQ(host, "stuff"); + EXPECT_EQ(port, "80"); + EXPECT_EQ(rest, "/things"); + + ASSERT_TRUE(CrackURL("https://stuff/things", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "https"); + EXPECT_EQ(host, "stuff"); + EXPECT_EQ(port, "443"); + EXPECT_EQ(rest, "/things"); +} + +TEST(CrackURL, BasicWithExplicitPort) { + std::string scheme, host, port, rest; + + ASSERT_TRUE( + CrackURL("http://stuff:999/things", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "http"); + EXPECT_EQ(host, "stuff"); + EXPECT_EQ(port, "999"); + EXPECT_EQ(rest, "/things"); + + ASSERT_TRUE( + CrackURL("https://stuff:1010/things", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "https"); + EXPECT_EQ(host, "stuff"); + EXPECT_EQ(port, "1010"); + EXPECT_EQ(rest, "/things"); +} + +TEST(CrackURL, WithURLParams) { + std::string scheme, host, port, rest; + + ASSERT_TRUE(CrackURL( + "http://stuff:999/things?blah=stuff:3", &scheme, &host, &port, &rest)); + EXPECT_EQ(scheme, "http"); + EXPECT_EQ(host, "stuff"); + EXPECT_EQ(port, "999"); + EXPECT_EQ(rest, "/things?blah=stuff:3"); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/util/numeric/checked_address_range.cc b/util/numeric/checked_address_range.cc index 17f77618..b2784fc0 100644 --- a/util/numeric/checked_address_range.cc +++ b/util/numeric/checked_address_range.cc @@ -23,6 +23,8 @@ #include "util/win/address_types.h" #elif defined(OS_LINUX) || defined(OS_ANDROID) #include "util/linux/address_types.h" +#elif defined(OS_FUCHSIA) +#include #endif // OS_MACOSX namespace crashpad { @@ -129,6 +131,8 @@ template class CheckedAddressRangeGeneric; template class CheckedAddressRangeGeneric; #elif defined(OS_LINUX) || defined(OS_ANDROID) template class CheckedAddressRangeGeneric; +#elif defined(OS_FUCHSIA) +template class CheckedAddressRangeGeneric; #endif // OS_MACOSX } // namespace internal diff --git a/util/posix/double_fork_and_exec.cc b/util/posix/double_fork_and_exec.cc new file mode 100644 index 00000000..df74f709 --- /dev/null +++ b/util/posix/double_fork_and_exec.cc @@ -0,0 +1,146 @@ +// 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 "util/posix/double_fork_and_exec.h" + +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/stringprintf.h" +#include "util/posix/close_multiple.h" + +namespace crashpad { + +bool DoubleForkAndExec(const std::vector& argv, + int preserve_fd, + bool use_path, + void (*child_function)()) { + // 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 + // so in the child or grandchild process. + std::vector argv_c; + argv_c.reserve(argv.size() + 1); + for (const std::string& argument : argv) { + argv_c.push_back(argument.c_str()); + } + argv_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 + // parent process ID becomes 1. This relieves the parent and child of the + // responsibility to reap the grandchild with waitpid() or similar. The + // grandchild is expected to outlive the parent process, so the parent + // shouldn’t be concerned with reaping it. This approach means that accidental + // early termination of the handler process will not result in a zombie + // process. + pid_t pid = fork(); + if (pid < 0) { + PLOG(ERROR) << "fork"; + return false; + } + + if (pid == 0) { + // Child process. + + if (child_function) { + child_function(); + } + + // Call setsid(), creating a new process group and a new session, both led + // by this process. The new process group has no controlling terminal. This + // disconnects it from signals generated by the parent process’ terminal. + // + // setsid() is done in the child instead of the grandchild so that the + // grandchild will not be a session leader. If it were a session leader, an + // accidental open() of a terminal device without O_NOCTTY would make that + // terminal the controlling terminal. + // + // It’s not desirable for the grandchild to have a controlling terminal. The + // grandchild manages its own lifetime, such as by monitoring clients on its + // own and exiting when it loses all clients and when it deems it + // appropraite to do so. It may serve clients in different process groups or + // sessions than its original client, and receiving signals intended for its + // original client’s process group could be harmful in that case. + PCHECK(setsid() != -1) << "setsid"; + + pid = fork(); + if (pid < 0) { + PLOG(FATAL) << "fork"; + } + + if (pid > 0) { + // Child process. + + // _exit() instead of exit(), because fork() was called. + _exit(EXIT_SUCCESS); + } + + // Grandchild process. + + CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd); + + // &argv_c[0] is a pointer to a pointer to const char data, but because of + // how C (not C++) works, execvp() wants a pointer to a const pointer to + // char data. It modifies neither the data nor the pointers, so the + // const_cast is safe. + char* const* argv_for_execv = const_cast(&argv_c[0]); + + if (use_path) { + execvp(argv_for_execv[0], argv_for_execv); + PLOG(FATAL) << "execvp " << argv_for_execv[0]; + } + + execv(argv_for_execv[0], argv_for_execv); + PLOG(FATAL) << "execv " << argv_for_execv[0]; + } + + // waitpid() for the child, so that it does not become a zombie process. The + // child normally exits quickly. + // + // Failures from this point on may result in the accumulation of a zombie, but + // should not be considered fatal. Log only warnings, but don’t treat these + // failures as a failure of the function overall. + int status; + pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0)); + if (wait_pid == -1) { + PLOG(WARNING) << "waitpid"; + return true; + } + DCHECK_EQ(wait_pid, pid); + + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + LOG(WARNING) << base::StringPrintf( + "intermediate process terminated by signal %d (%s)%s", + sig, + strsignal(sig), + WCOREDUMP(status) ? " (core dumped)" : ""); + } else if (!WIFEXITED(status)) { + LOG(WARNING) << base::StringPrintf( + "intermediate process: unknown termination 0x%x", status); + } else if (WEXITSTATUS(status) != EXIT_SUCCESS) { + LOG(WARNING) << "intermediate process exited with code " + << WEXITSTATUS(status); + } + + return true; +} + +} // namespace crashpad diff --git a/util/posix/double_fork_and_exec.h b/util/posix/double_fork_and_exec.h new file mode 100644 index 00000000..df340d07 --- /dev/null +++ b/util/posix/double_fork_and_exec.h @@ -0,0 +1,67 @@ +// 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_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_ +#define CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_ + +#include +#include + +namespace crashpad { + +//! \brief Executes a (grand-)child process. +//! +//! The grandchild process will be started through the +//! double-`fork()`-and-`execv()` pattern. This allows the grandchild to fully +//! disassociate from the parent. The grandchild will not be a member of the +//! parent’s process group or session and will not have a controlling terminal, +//! providing isolation from signals not intended for it. The grandchild’s +//! parent process, in terms of the process tree hierarchy, will be the process +//! with process ID 1, relieving any other process of the responsibility to reap +//! it via `waitpid()`. Aside from the three file descriptors associated with +//! the standard input/output streams and any file descriptor passed in \a +//! preserve_fd, the grandchild will not inherit any file descriptors from the +//! parent process. +//! +//! \param[in] argv The argument vector to start the grandchild process with. +//! `argv[0]` is used as the path to the executable. +//! \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` +//! if no additional file descriptors are to be inherited. +//! \param[in] use_path Whether to consult the `PATH` environment variable when +//! requested to start an executable at a non-absolute path. If `false`, +//! `execv()`, which does not consult `PATH`, will be used. If `true`, +//! `execvp()`, which does consult `PATH`, will be used. +//! \param[in] child_function If not `nullptr`, this function will be called in +//! the intermediate child process, prior to the second `fork()`. Take note +//! that this function will run in the context of a forked process, and must +//! be safe for that purpose. +//! +//! \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. +//! Failures in the intermediate child or grandchild processes cannot be +//! reported in the return value, and are addressed by logging a message and +//! terminating. The caller assumes the responsibility for detecting such +//! failures, for example, by observing a failure to perform a successful +//! handshake with the grandchild process. +bool DoubleForkAndExec(const std::vector& argv, + int preserve_fd, + bool use_path, + void (*child_function)()); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_ diff --git a/util/posix/scoped_mmap_test.cc b/util/posix/scoped_mmap_test.cc index 33807295..7312b849 100644 --- a/util/posix/scoped_mmap_test.cc +++ b/util/posix/scoped_mmap_test.cc @@ -22,6 +22,7 @@ #include "base/rand_util.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" +#include "test/gtest_death.h" namespace crashpad { namespace test { @@ -118,7 +119,7 @@ TEST(ScopedMmapDeathTest, Destructor) { cookie.SetUp(mapping.addr_as()); } - EXPECT_DEATH(cookie.Check(), ""); + EXPECT_DEATH_CRASH(cookie.Check(), ""); } TEST(ScopedMmapDeathTest, Reset) { @@ -135,7 +136,7 @@ TEST(ScopedMmapDeathTest, Reset) { ASSERT_TRUE(mapping.Reset()); - EXPECT_DEATH(cookie.Check(), ""); + EXPECT_DEATH_CRASH(cookie.Check(), ""); } TEST(ScopedMmapDeathTest, ResetAddrLen_Shrink) { @@ -164,8 +165,8 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_Shrink) { EXPECT_EQ(cookies[1].Observed(), cookies[1].Expected()); - EXPECT_DEATH(cookies[0].Check(), ""); - EXPECT_DEATH(cookies[2].Check(), ""); + EXPECT_DEATH_CRASH(cookies[0].Check(), ""); + EXPECT_DEATH_CRASH(cookies[2].Check(), ""); } TEST(ScopedMmap, ResetAddrLen_Grow) { @@ -230,7 +231,7 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_MoveDownAndGrow) { EXPECT_EQ(cookies[0].Observed(), cookies[0].Expected()); EXPECT_EQ(cookies[1].Observed(), cookies[1].Expected()); - EXPECT_DEATH(cookies[2].Check(), ""); + EXPECT_DEATH_CRASH(cookies[2].Check(), ""); } TEST(ScopedMmapDeathTest, ResetAddrLen_MoveUpAndShrink) { @@ -262,8 +263,8 @@ TEST(ScopedMmapDeathTest, ResetAddrLen_MoveUpAndShrink) { EXPECT_EQ(cookies[2].Observed(), cookies[2].Expected()); - EXPECT_DEATH(cookies[0].Check(), ""); - EXPECT_DEATH(cookies[1].Check(), ""); + EXPECT_DEATH_CRASH(cookies[0].Check(), ""); + EXPECT_DEATH_CRASH(cookies[1].Check(), ""); } TEST(ScopedMmapDeathTest, ResetMmap) { @@ -289,7 +290,7 @@ TEST(ScopedMmapDeathTest, ResetMmap) { EXPECT_NE(mapping.addr(), MAP_FAILED); EXPECT_EQ(mapping.len(), kPageSize); - EXPECT_DEATH(cookie.Check(), ""); + EXPECT_DEATH_CRASH(cookie.Check(), ""); } TEST(ScopedMmapDeathTest, Mprotect) { @@ -306,7 +307,7 @@ TEST(ScopedMmapDeathTest, Mprotect) { ASSERT_TRUE(mapping.Mprotect(PROT_READ)); - EXPECT_DEATH(*addr = 0, ""); + EXPECT_DEATH_CRASH(*addr = 0, ""); ASSERT_TRUE(mapping.Mprotect(PROT_READ | PROT_WRITE)); EXPECT_EQ(*addr, 1); diff --git a/util/posix/signals.cc b/util/posix/signals.cc index d79d53ab..63764ab8 100644 --- a/util/posix/signals.cc +++ b/util/posix/signals.cc @@ -138,6 +138,15 @@ bool Signals::InstallHandler(int sig, return true; } +// static +bool Signals::InstallDefaultHandler(int sig) { + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + return sigaction(sig, &action, nullptr) == 0; +} + // static bool Signals::InstallCrashHandlers(Handler handler, int flags, diff --git a/util/posix/signals.h b/util/posix/signals.h index 36d33cd6..dc550594 100644 --- a/util/posix/signals.h +++ b/util/posix/signals.h @@ -24,6 +24,9 @@ namespace crashpad { //! \brief Utilities for handling POSIX signals. class Signals { public: + //! \brief A signal number used by Crashpad to simulate signals. + static constexpr int kSimulatedSigno = -1; + //! \brief The type used for `struct sigaction::sa_sigaction`. using Handler = void (*)(int, siginfo_t*, void*); @@ -85,6 +88,14 @@ class Signals { int flags, struct sigaction* old_action); + //! \brief Installs `SIG_DFL` for the signal \a sig. + //! + //! \param[in] sig The signal to set the default action for. + //! + //! \return `true` on success, `false` on failure with errno set. No message + //! is logged. + static bool InstallDefaultHandler(int sig); + //! \brief Installs a new signal handler for all signals associated with //! crashes. //! diff --git a/util/posix/symbolic_constants_posix.cc b/util/posix/symbolic_constants_posix.cc index 51cc583b..8008ffb6 100644 --- a/util/posix/symbolic_constants_posix.cc +++ b/util/posix/symbolic_constants_posix.cc @@ -65,6 +65,39 @@ constexpr const char* kSignalNames[] = { "USR1", "USR2", #elif defined(OS_LINUX) || defined(OS_ANDROID) +#if defined(ARCH_CPU_MIPS_FAMILY) + "HUP", + "INT", + "QUIT", + "ILL", + "TRAP", + "ABRT", + "EMT", + "FPE", + "KILL", + "BUS", + "SEGV", + "SYS", + "PIPE", + "ALRM", + "TERM", + "USR1", + "USR2", + "CHLD", + "PWR", + "WINCH", + "URG", + "IO", + "STOP", + "TSTP", + "CONT", + "TTIN", + "TTOU", + "VTALRM", + "PROF", + "XCPU", + "XFSZ", +#else // sed -Ene 's/^#define[[:space:]]SIG([[:alnum:]]+)[[:space:]]+[[:digit:]]{1,2}([[:space:]]|$).*/ "\1",/p' // /usr/include/asm-generic/signal.h // and fix up by removing SIGIOT, SIGLOST, SIGUNUSED, and SIGRTMIN. @@ -99,6 +132,7 @@ constexpr const char* kSignalNames[] = { "IO", "PWR", "SYS", +#endif // defined(ARCH_CPU_MIPS_FAMILY) #endif }; #if defined(OS_LINUX) || defined(OS_ANDROID) @@ -161,7 +195,7 @@ bool StringToSignal(const base::StringPiece& string, } if (options & kAllowNumber) { - return StringToNumber(string, signal); + return StringToNumber(std::string(string.data(), string.length()), signal); } return false; diff --git a/util/posix/symbolic_constants_posix_test.cc b/util/posix/symbolic_constants_posix_test.cc index 8478b7bc..32c1d434 100644 --- a/util/posix/symbolic_constants_posix_test.cc +++ b/util/posix/symbolic_constants_posix_test.cc @@ -67,8 +67,10 @@ constexpr struct { {SIGINFO, "SIGINFO", "INFO"}, #elif defined(OS_LINUX) || defined(OS_ANDROID) {SIGPWR, "SIGPWR", "PWR"}, +#if !defined(ARCH_CPU_MIPS_FAMILY) {SIGSTKFLT, "SIGSTKFLT", "STKFLT"}, #endif +#endif }; // If |expect| is nullptr, the conversion is expected to fail. If |expect| is diff --git a/util/process/process_memory.cc b/util/process/process_memory.cc new file mode 100644 index 00000000..6bc00101 --- /dev/null +++ b/util/process/process_memory.cc @@ -0,0 +1,78 @@ +// 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.h" + +#include "base/logging.h" + +namespace crashpad { + +bool ProcessMemory::Read(VMAddress address, size_t size, void* buffer) const { + char* buffer_c = static_cast(buffer); + while (size > 0) { + ssize_t bytes_read = ReadUpTo(address, size, buffer_c); + if (bytes_read < 0) { + return false; + } + if (bytes_read == 0) { + LOG(ERROR) << "short read"; + return false; + } + DCHECK_LE(static_cast(bytes_read), size); + size -= bytes_read; + address += bytes_read; + buffer_c += bytes_read; + } + return true; +} + +bool ProcessMemory::ReadCStringInternal(VMAddress address, + bool has_size, + size_t size, + std::string* string) const { + string->clear(); + + char buffer[4096]; + do { + size_t read_size; + if (has_size) { + read_size = std::min(sizeof(buffer), size); + } else { + read_size = sizeof(buffer); + } + + ssize_t bytes_read = ReadUpTo(address, read_size, buffer); + if (bytes_read < 0) { + return false; + } + if (bytes_read == 0) { + break; + } + + char* nul = static_cast(memchr(buffer, '\0', bytes_read)); + if (nul != nullptr) { + string->append(buffer, nul - buffer); + return true; + } + string->append(buffer, bytes_read); + + address += bytes_read; + size -= bytes_read; + } while (!has_size || size > 0); + + LOG(ERROR) << "unterminated string"; + return false; +} + +} // namespace crashpad diff --git a/util/process/process_memory.h b/util/process/process_memory.h index c046fc6e..d67c3828 100644 --- a/util/process/process_memory.h +++ b/util/process/process_memory.h @@ -40,7 +40,7 @@ class ProcessMemory { //! //! \return `true` on success, with \a buffer filled appropriately. `false` on //! failure, with a message logged. - virtual bool Read(VMAddress address, size_t size, void* buffer) const = 0; + bool Read(VMAddress address, size_t size, void* buffer) const; //! \brief Reads a `NUL`-terminated C string from the target process into a //! string in the current process. @@ -78,11 +78,28 @@ class ProcessMemory { return ReadCStringInternal(address, true, size, string); } + virtual ~ProcessMemory() = default; + protected: ProcessMemory() = default; - ~ProcessMemory() = default; private: + //! \brief Copies memory from the target process into a caller-provided buffer + //! in the current process, up to a maximum number of bytes. + //! + //! \param[in] address The address, in the target process' address space, of + //! the memory region to copy. + //! \param[in] size The maximum 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 + //! process' memory will be copied. + //! + //! \return the number of bytes copied, 0 if there is no more data to read, or + //! -1 on failure with a message logged. + virtual ssize_t ReadUpTo(VMAddress address, + size_t size, + void* buffer) const = 0; + //! \brief Reads a `NUL`-terminated C string from the target process into a //! string in the current process. //! @@ -102,7 +119,7 @@ class ProcessMemory { virtual bool ReadCStringInternal(VMAddress address, bool has_size, size_t size, - std::string* string) const = 0; + std::string* string) const; }; } // namespace crashpad diff --git a/util/process/process_memory_fuchsia.cc b/util/process/process_memory_fuchsia.cc new file mode 100644 index 00000000..212e1c6f --- /dev/null +++ b/util/process/process_memory_fuchsia.cc @@ -0,0 +1,56 @@ +// 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_fuchsia.h" + +#include + +#include + +#include "base/logging.h" +#include "base/fuchsia/fuchsia_logging.h" + +namespace crashpad { + +ProcessMemoryFuchsia::ProcessMemoryFuchsia() + : ProcessMemory(), process_(), initialized_() {} + +ProcessMemoryFuchsia::~ProcessMemoryFuchsia() {} + +bool ProcessMemoryFuchsia::Initialize(zx_handle_t process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + process_ = process; + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +ssize_t ProcessMemoryFuchsia::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK_LE(size, size_t{std::numeric_limits::max()}); + + size_t actual; + zx_status_t status = + zx_process_read_memory(process_, address, buffer, size, &actual); + + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "zx_process_read_memory"; + return -1; + } + + return actual; +} + +} // namespace crashpad diff --git a/util/process/process_memory_fuchsia.h b/util/process/process_memory_fuchsia.h new file mode 100644 index 00000000..e6cdd3e8 --- /dev/null +++ b/util/process/process_memory_fuchsia.h @@ -0,0 +1,56 @@ +// 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_FUCHSIA_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_ + +#include + +#include + +#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 Fuchsia process. +class ProcessMemoryFuchsia final : public ProcessMemory { + public: + ProcessMemoryFuchsia(); + ~ProcessMemoryFuchsia(); + + //! \brief Initializes this object to read the memory of a process by handle. + //! + //! This method must be called successfully prior to calling any other method + //! in this class. + //! + //! \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); + + private: + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; + + zx_handle_t process_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMemoryFuchsia); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_FUCHSIA_H_ diff --git a/util/process/process_memory_linux.cc b/util/process/process_memory_linux.cc index f294ee34..1fc3c05f 100644 --- a/util/process/process_memory_linux.cc +++ b/util/process/process_memory_linux.cc @@ -20,6 +20,7 @@ #include #include +#include #include "base/logging.h" #include "base/posix/eintr_wrapper.h" @@ -27,11 +28,12 @@ namespace crashpad { ProcessMemoryLinux::ProcessMemoryLinux() - : ProcessMemory(), mem_fd_(), pid_(-1) {} + : ProcessMemory(), mem_fd_(), pid_(-1), initialized_() {} ProcessMemoryLinux::~ProcessMemoryLinux() {} bool ProcessMemoryLinux::Initialize(pid_t pid) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); pid_ = pid; char path[32]; snprintf(path, sizeof(path), "/proc/%d/mem", pid_); @@ -40,75 +42,23 @@ bool ProcessMemoryLinux::Initialize(pid_t pid) { PLOG(ERROR) << "open"; return false; } + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } -bool ProcessMemoryLinux::Read(VMAddress address, - size_t size, - void* buffer) const { +ssize_t ProcessMemoryLinux::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); DCHECK(mem_fd_.is_valid()); + DCHECK_LE(size, size_t{std::numeric_limits::max()}); - char* buffer_c = static_cast(buffer); - while (size > 0) { - ssize_t bytes_read = - HANDLE_EINTR(pread64(mem_fd_.get(), buffer_c, size, address)); - if (bytes_read < 0) { - PLOG(ERROR) << "pread64"; - return false; - } - if (bytes_read == 0) { - LOG(ERROR) << "unexpected eof"; - return false; - } - DCHECK_LE(static_cast(bytes_read), size); - size -= bytes_read; - address += bytes_read; - buffer_c += bytes_read; + ssize_t bytes_read = + HANDLE_EINTR(pread64(mem_fd_.get(), buffer, size, address)); + if (bytes_read < 0) { + PLOG(ERROR) << "pread64"; } - return true; -} - -bool ProcessMemoryLinux::ReadCStringInternal(VMAddress address, - bool has_size, - size_t size, - std::string* string) const { - DCHECK(mem_fd_.is_valid()); - - string->clear(); - - char buffer[4096]; - do { - size_t read_size; - if (has_size) { - read_size = std::min(sizeof(buffer), size); - } else { - read_size = sizeof(buffer); - } - ssize_t bytes_read; - bytes_read = - HANDLE_EINTR(pread64(mem_fd_.get(), buffer, read_size, address)); - if (bytes_read < 0) { - PLOG(ERROR) << "pread64"; - return false; - } - if (bytes_read == 0) { - break; - } - DCHECK_LE(static_cast(bytes_read), read_size); - - char* nul = static_cast(memchr(buffer, '\0', bytes_read)); - if (nul != nullptr) { - string->append(buffer, nul - buffer); - return true; - } - string->append(buffer, bytes_read); - - address += bytes_read; - size -= bytes_read; - } while (!has_size || size > 0); - - LOG(ERROR) << "unterminated string"; - return false; + return bytes_read; } } // namespace crashpad diff --git a/util/process/process_memory_linux.h b/util/process/process_memory_linux.h index e03e251f..4fe008bb 100644 --- a/util/process/process_memory_linux.h +++ b/util/process/process_memory_linux.h @@ -22,6 +22,7 @@ #include "base/files/scoped_file.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 { @@ -43,16 +44,12 @@ class ProcessMemoryLinux final : public ProcessMemory { //! \return `true` on success, `false` on failure with a message logged. bool Initialize(pid_t pid); - bool Read(VMAddress address, size_t size, void* buffer) const override; - private: - bool ReadCStringInternal(VMAddress address, - bool has_size, - size_t size, - std::string* string) const override; + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; base::ScopedFD mem_fd_; pid_t pid_; + InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessMemoryLinux); }; diff --git a/util/process/process_memory_native.h b/util/process/process_memory_native.h new file mode 100644 index 00000000..19fa805a --- /dev/null +++ b/util/process/process_memory_native.h @@ -0,0 +1,34 @@ +// 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 "build/build_config.h" + +#if defined(OS_FUCHSIA) +#include "util/process/process_memory_fuchsia.h" +#elif defined(OS_LINUX) || defined(OS_ANDROID) +#include "util/process/process_memory_linux.h" +#endif + +namespace crashpad { + +#if defined(OS_FUCHSIA) || DOXYGEN +//! \brief Alias for platform-specific native implementation of ProcessMemory. +using ProcessMemoryNative = ProcessMemoryFuchsia; +#elif defined(OS_LINUX) || defined(OS_ANDROID) +using ProcessMemoryNative = ProcessMemoryLinux; +#else +#error Port. +#endif + +} // namespace crashpad diff --git a/util/process/process_memory_range_test.cc b/util/process/process_memory_range_test.cc index c4cab7e0..1c785068 100644 --- a/util/process/process_memory_range_test.cc +++ b/util/process/process_memory_range_test.cc @@ -22,7 +22,14 @@ #include "build/build_config.h" #include "gtest/gtest.h" #include "util/misc/from_pointer_cast.h" + +#if defined(OS_FUCHSIA) +#include + +#include "util/process/process_memory_fuchsia.h" +#else #include "util/process/process_memory_linux.h" +#endif namespace crashpad { namespace test { @@ -34,6 +41,11 @@ struct TestObject { } 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; @@ -43,6 +55,7 @@ TEST(ProcessMemoryRange, Basic) { ProcessMemoryLinux memory; ASSERT_TRUE(memory.Initialize(pid)); +#endif // OS_FUCHSIA ProcessMemoryRange range; ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); diff --git a/util/process/process_memory_test.cc b/util/process/process_memory_test.cc index 5f631237..741e58d6 100644 --- a/util/process/process_memory_test.cc +++ b/util/process/process_memory_test.cc @@ -23,92 +23,117 @@ #include "gtest/gtest.h" #include "test/errors.h" #include "test/multiprocess.h" +#include "test/multiprocess_exec.h" +#include "test/process_type.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_linux.h" +#include "util/process/process_memory_native.h" namespace crashpad { namespace test { namespace { -class TargetProcessTest : public Multiprocess { +void DoChildReadTestSetup(size_t* region_size, + std::unique_ptr* region) { + *region_size = 4 * getpagesize(); + region->reset(new char[*region_size]); + for (size_t index = 0; index < *region_size; ++index) { + (*region)[index] = index % 256; + } +} + +CRASHPAD_CHILD_TEST_MAIN(ReadTestChild) { + size_t region_size; + std::unique_ptr region; + DoChildReadTestSetup(®ion_size, ®ion); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + CheckedWriteFile(out, ®ion_size, sizeof(region_size)); + VMAddress address = FromPointerCast(region.get()); + CheckedWriteFile(out, &address, sizeof(address)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadTest : public MultiprocessExec { public: - TargetProcessTest() : Multiprocess() {} - ~TargetProcessTest() {} - - void RunAgainstSelf() { DoTest(getpid()); } - - void RunAgainstForked() { Run(); } - - private: - void MultiprocessParent() override { DoTest(ChildPID()); } - - void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } - - virtual void DoTest(pid_t pid) = 0; - - DISALLOW_COPY_AND_ASSIGN(TargetProcessTest); -}; - -class ReadTest : public TargetProcessTest { - public: - ReadTest() - : TargetProcessTest(), - page_size_(getpagesize()), - region_size_(4 * page_size_), - region_(new char[region_size_]) { - for (size_t index = 0; index < region_size_; ++index) { - region_[index] = index % 256; - } + ReadTest() : MultiprocessExec() { + SetChildTestMainFunction("ReadTestChild"); } - private: - void DoTest(pid_t pid) override { - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); + void RunAgainstSelf() { + size_t region_size; + std::unique_ptr region; + DoChildReadTestSetup(®ion_size, ®ion); + DoTest(GetSelfProcess(), + region_size, + FromPointerCast(region.get())); + } - VMAddress address = FromPointerCast(region_.get()); - std::unique_ptr result(new char[region_size_]); + void RunAgainstChild() { Run(); } + + private: + void MultiprocessParent() override { + size_t region_size; + VMAddress region; + ASSERT_TRUE( + ReadFileExactly(ReadPipeHandle(), ®ion_size, sizeof(region_size))); + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), ®ion, sizeof(region))); + DoTest(ChildProcess(), region_size, region); + } + + void DoTest(ProcessType process, size_t region_size, VMAddress address) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + + std::unique_ptr result(new char[region_size]); // Ensure that the entire region can be read. - ASSERT_TRUE(memory.Read(address, region_size_, result.get())); - EXPECT_EQ(memcmp(region_.get(), result.get(), region_size_), 0); + ASSERT_TRUE(memory.Read(address, region_size, result.get())); + for (size_t i = 0; i < region_size; ++i) { + EXPECT_EQ(result[i], static_cast(i % 256)); + } // Ensure that a read of length 0 succeeds and doesn’t touch the result. - memset(result.get(), '\0', region_size_); + memset(result.get(), '\0', region_size); ASSERT_TRUE(memory.Read(address, 0, result.get())); - for (size_t i = 0; i < region_size_; ++i) { + for (size_t i = 0; i < region_size; ++i) { EXPECT_EQ(result[i], 0); } // Ensure that a read starting at an unaligned address works. - ASSERT_TRUE(memory.Read(address + 1, region_size_ - 1, result.get())); - EXPECT_EQ(memcmp(region_.get() + 1, result.get(), region_size_ - 1), 0); + ASSERT_TRUE(memory.Read(address + 1, region_size - 1, result.get())); + for (size_t i = 0; i < region_size - 1; ++i) { + EXPECT_EQ(result[i], static_cast((i + 1) % 256)); + } // Ensure that a read ending at an unaligned address works. - ASSERT_TRUE(memory.Read(address, region_size_ - 1, result.get())); - EXPECT_EQ(memcmp(region_.get(), result.get(), region_size_ - 1), 0); + ASSERT_TRUE(memory.Read(address, region_size - 1, result.get())); + for (size_t i = 0; i < region_size - 1; ++i) { + EXPECT_EQ(result[i], static_cast(i % 256)); + } // Ensure that a read starting and ending at unaligned addresses works. - ASSERT_TRUE(memory.Read(address + 1, region_size_ - 2, result.get())); - EXPECT_EQ(memcmp(region_.get() + 1, result.get(), region_size_ - 2), 0); + ASSERT_TRUE(memory.Read(address + 1, region_size - 2, result.get())); + for (size_t i = 0; i < region_size - 2; ++i) { + EXPECT_EQ(result[i], static_cast((i + 1) % 256)); + } // Ensure that a read of exactly one page works. - ASSERT_TRUE(memory.Read(address + page_size_, page_size_, result.get())); - EXPECT_EQ(memcmp(region_.get() + page_size_, result.get(), page_size_), 0); + size_t page_size = 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) { + EXPECT_EQ(result[i], static_cast((i + page_size) % 256)); + } // Ensure that reading exactly a single byte works. result[1] = 'J'; ASSERT_TRUE(memory.Read(address + 2, 1, result.get())); - EXPECT_EQ(result[0], region_[2]); + EXPECT_EQ(result[0], 2); EXPECT_EQ(result[1], 'J'); } - const size_t page_size_; - const size_t region_size_; - std::unique_ptr region_; - DISALLOW_COPY_AND_ASSIGN(ReadTest); }; @@ -117,96 +142,161 @@ TEST(ProcessMemory, ReadSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadForked) { +TEST(ProcessMemory, ReadChild) { ReadTest test; - test.RunAgainstForked(); -} - -bool ReadCString(const ProcessMemory& memory, - const char* pointer, - std::string* result) { - return memory.ReadCString(FromPointerCast(pointer), result); -} - -bool ReadCStringSizeLimited(const ProcessMemory& memory, - const char* pointer, - size_t size, - std::string* result) { - return memory.ReadCStringSizeLimited( - FromPointerCast(pointer), size, result); + test.RunAgainstChild(); } constexpr char kConstCharEmpty[] = ""; constexpr char kConstCharShort[] = "A short const char[]"; -class ReadCStringTest : public TargetProcessTest { +#define SHORT_LOCAL_STRING "A short local variable char[]" + +std::string MakeLongString() { + std::string long_string; + const size_t kStringLongSize = 4 * getpagesize(); + for (size_t index = 0; index < kStringLongSize; ++index) { + long_string.push_back((index % 255) + 1); + } + EXPECT_EQ(long_string.size(), kStringLongSize); + return long_string; +} + +void DoChildCStringReadTestSetup(const char** const_empty, + const char** const_short, + const char** local_empty, + const char** local_short, + std::string* long_string) { + *const_empty = kConstCharEmpty; + *const_short = kConstCharShort; + *local_empty = ""; + *local_short = SHORT_LOCAL_STRING; + *long_string = MakeLongString(); +} + +CRASHPAD_CHILD_TEST_MAIN(ReadCStringTestChild) { + const char* const_empty; + const char* const_short; + const char* local_empty; + const char* local_short; + std::string long_string; + DoChildCStringReadTestSetup( + &const_empty, &const_short, &local_empty, &local_short, &long_string); + const auto write_address = [](const char* p) { + VMAddress address = FromPointerCast(p); + CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), + &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)); + return 0; +} + +class ReadCStringTest : public MultiprocessExec { public: ReadCStringTest(bool limit_size) - : TargetProcessTest(), - member_char_empty_(""), - member_char_short_("A short member char[]"), - limit_size_(limit_size) { - const size_t kStringLongSize = 4 * getpagesize(); - for (size_t index = 0; index < kStringLongSize; ++index) { - string_long_.push_back((index % 255) + 1); - } - EXPECT_EQ(string_long_.size(), kStringLongSize); + : MultiprocessExec(), limit_size_(limit_size) { + SetChildTestMainFunction("ReadCStringTestChild"); } + void RunAgainstSelf() { + const char* const_empty; + const char* const_short; + const char* local_empty; + const char* local_short; + std::string long_string; + DoChildCStringReadTestSetup( + &const_empty, &const_short, &local_empty, &local_short, &long_string); + DoTest(GetSelfProcess(), + FromPointerCast(const_empty), + FromPointerCast(const_short), + FromPointerCast(local_empty), + FromPointerCast(local_short), + FromPointerCast(long_string.c_str())); + } + void RunAgainstChild() { Run(); } + private: - void DoTest(pid_t pid) override { - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); + void MultiprocessParent() override { +#define DECLARE_AND_READ_ADDRESS(name) \ + VMAddress name; \ + ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &name, sizeof(name))); + DECLARE_AND_READ_ADDRESS(const_empty_address); + DECLARE_AND_READ_ADDRESS(const_short_address); + DECLARE_AND_READ_ADDRESS(local_empty_address); + DECLARE_AND_READ_ADDRESS(local_short_address); + DECLARE_AND_READ_ADDRESS(long_string_address); +#undef DECLARE_AND_READ_ADDRESS + + DoTest(ChildProcess(), + const_empty_address, + const_short_address, + local_empty_address, + local_short_address, + long_string_address); + } + + void DoTest(ProcessType process, + VMAddress const_empty_address, + VMAddress const_short_address, + VMAddress local_empty_address, + VMAddress local_short_address, + VMAddress long_string_address) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); std::string result; if (limit_size_) { - ASSERT_TRUE(ReadCStringSizeLimited( - memory, kConstCharEmpty, arraysize(kConstCharEmpty), &result)); + ASSERT_TRUE(memory.ReadCStringSizeLimited( + const_empty_address, arraysize(kConstCharEmpty), &result)); EXPECT_EQ(result, kConstCharEmpty); - ASSERT_TRUE(ReadCStringSizeLimited( - memory, kConstCharShort, arraysize(kConstCharShort), &result)); + ASSERT_TRUE(memory.ReadCStringSizeLimited( + const_short_address, arraysize(kConstCharShort), &result)); EXPECT_EQ(result, kConstCharShort); - EXPECT_FALSE(ReadCStringSizeLimited( - memory, kConstCharShort, arraysize(kConstCharShort) - 1, &result)); + EXPECT_FALSE(memory.ReadCStringSizeLimited( + const_short_address, arraysize(kConstCharShort) - 1, &result)); - ASSERT_TRUE(ReadCStringSizeLimited( - memory, member_char_empty_, strlen(member_char_empty_) + 1, &result)); - EXPECT_EQ(result, member_char_empty_); + ASSERT_TRUE( + memory.ReadCStringSizeLimited(local_empty_address, 1, &result)); + EXPECT_EQ(result, ""); - ASSERT_TRUE(ReadCStringSizeLimited( - memory, member_char_short_, strlen(member_char_short_) + 1, &result)); - EXPECT_EQ(result, member_char_short_); - EXPECT_FALSE(ReadCStringSizeLimited( - memory, member_char_short_, strlen(member_char_short_), &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)); - ASSERT_TRUE(ReadCStringSizeLimited( - memory, string_long_.c_str(), string_long_.size() + 1, &result)); - EXPECT_EQ(result, string_long_); - EXPECT_FALSE(ReadCStringSizeLimited( - memory, string_long_.c_str(), string_long_.size(), &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(ReadCString(memory, kConstCharEmpty, &result)); + ASSERT_TRUE(memory.ReadCString(const_empty_address, &result)); EXPECT_EQ(result, kConstCharEmpty); - ASSERT_TRUE(ReadCString(memory, kConstCharShort, &result)); + ASSERT_TRUE(memory.ReadCString(const_short_address, &result)); EXPECT_EQ(result, kConstCharShort); - ASSERT_TRUE(ReadCString(memory, member_char_empty_, &result)); - EXPECT_EQ(result, member_char_empty_); + ASSERT_TRUE(memory.ReadCString(local_empty_address, &result)); + EXPECT_EQ(result, ""); - ASSERT_TRUE(ReadCString(memory, member_char_short_, &result)); - EXPECT_EQ(result, member_char_short_); + ASSERT_TRUE(memory.ReadCString(local_short_address, &result)); + EXPECT_EQ(result, SHORT_LOCAL_STRING); - ASSERT_TRUE(ReadCString(memory, string_long_.c_str(), &result)); - EXPECT_EQ(result, string_long_); + ASSERT_TRUE(memory.ReadCString(long_string_address, &result)); + EXPECT_EQ(result, MakeLongString()); } } - std::string string_long_; - const char* member_char_empty_; - const char* member_char_short_; const bool limit_size_; DISALLOW_COPY_AND_ASSIGN(ReadCStringTest); @@ -217,9 +307,9 @@ TEST(ProcessMemory, ReadCStringSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadCStringForked) { +TEST(ProcessMemory, ReadCStringChild) { ReadCStringTest test(/* limit_size= */ false); - test.RunAgainstForked(); + test.RunAgainstChild(); } TEST(ProcessMemory, ReadCStringSizeLimitedSelf) { @@ -227,56 +317,96 @@ TEST(ProcessMemory, ReadCStringSizeLimitedSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadCStringSizeLimitedForked) { +TEST(ProcessMemory, ReadCStringSizeLimitedChild) { ReadCStringTest test(/* limit_size= */ true); - test.RunAgainstForked(); + test.RunAgainstChild(); } -class ReadUnmappedTest : public TargetProcessTest { - public: - ReadUnmappedTest() - : TargetProcessTest(), - page_size_(getpagesize()), - region_size_(2 * page_size_), - result_(new char[region_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(); - for (size_t index = 0; index < region_size_; ++index) { - region[index] = index % 256; - } - - EXPECT_TRUE(pages_.ResetAddrLen(region, page_size_)); +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(); + + char* region = pages->addr_as(); + for (size_t index = 0; index < *region_size; ++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, ®ion_size); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + CheckedWriteFile(out, &address, sizeof(address)); + CheckedWriteFile(out, &page_size, sizeof(page_size)); + CheckedWriteFile(out, ®ion_size, sizeof(region_size)); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadUnmappedTest : public MultiprocessExec { + public: + ReadUnmappedTest() : MultiprocessExec() { + SetChildTestMainFunction("ReadUnmappedChildMain"); + } + + void RunAgainstSelf() { + ScopedMmap pages; + VMAddress address = 0; + size_t page_size, region_size; + DoReadUnmappedChildMainSetup(&pages, &address, &page_size, ®ion_size); + DoTest(GetSelfProcess(), address, page_size, region_size); + } + + void RunAgainstChild() { Run(); } + private: - void DoTest(pid_t pid) override { - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); - - VMAddress page_addr1 = pages_.addr_as(); - VMAddress page_addr2 = page_addr1 + page_size_; - - EXPECT_TRUE(memory.Read(page_addr1, page_size_, 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_addr2 - 1, 2, result_.get())); + void MultiprocessParent() 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(), ®ion_size, sizeof(region_size))); + DoTest(ChildProcess(), address, page_size, region_size); } - ScopedMmap pages_; - const size_t page_size_; - const size_t region_size_; - std::unique_ptr result_; + void DoTest(ProcessType process, + VMAddress address, + size_t page_size, + size_t region_size) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + + VMAddress page_addr1 = address; + VMAddress page_addr2 = page_addr1 + page_size; + + std::unique_ptr result(new char[region_size]); + EXPECT_TRUE(memory.Read(page_addr1, page_size, 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_addr2 - 1, 2, result.get())); + } DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest); }; @@ -287,90 +417,158 @@ TEST(ProcessMemory, ReadUnmappedSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadUnmappedForked) { +TEST(ProcessMemory, ReadUnmappedChild) { ReadUnmappedTest test; ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstForked(); + test.RunAgainstChild(); } -class ReadCStringUnmappedTest : public TargetProcessTest { +constexpr size_t kChildProcessStringLength = 10; + +class StringDataInChildProcess { + public: + // This constructor only makes sense in the child process. + explicit StringDataInChildProcess(const char* cstring) + : address_(FromPointerCast(cstring)) { + memcpy(expected_value_, cstring, kChildProcessStringLength + 1); + } + + void Write(FileHandle out) { + CheckedWriteFile(out, &address_, sizeof(address_)); + CheckedWriteFile(out, &expected_value_, sizeof(expected_value_)); + } + + static StringDataInChildProcess Read(FileHandle in) { + StringDataInChildProcess str; + EXPECT_TRUE(ReadFileExactly(in, &str.address_, sizeof(str.address_))); + EXPECT_TRUE( + ReadFileExactly(in, &str.expected_value_, sizeof(str.expected_value_))); + return str; + } + + VMAddress address() const { return address_; } + std::string expected_value() const { return expected_value_; } + + private: + StringDataInChildProcess() : address_(0), expected_value_() {} + + VMAddress address_; + char expected_value_[kChildProcessStringLength + 1]; +}; + +void DoCStringUnmappedTestSetup( + ScopedMmap* pages, + std::vector* 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(); + for (size_t index = 0; index < region_size; ++index) { + region[index] = 1 + index % 255; + } + + // A string at the start of the mapped region + char* string1 = region; + string1[kChildProcessStringLength] = '\0'; + + // A string near the end of the mapped region + char* string2 = region + page_size - 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'; + + // A string entirely in the unmapped region + char* string4 = region + page_size + 10; + string4[kChildProcessStringLength] = '\0'; + + 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)); +} + +CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) { + ScopedMmap pages; + std::vector strings; + DoCStringUnmappedTestSetup(&pages, &strings); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + strings[0].Write(out); + strings[1].Write(out); + strings[2].Write(out); + strings[3].Write(out); + CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); + return 0; +} + +class ReadCStringUnmappedTest : public MultiprocessExec { public: ReadCStringUnmappedTest(bool limit_size) - : TargetProcessTest(), - page_size_(getpagesize()), - region_size_(2 * page_size_), - limit_size_(limit_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(); - for (size_t index = 0; index < region_size_; ++index) { - region[index] = 1 + index % 255; - } - - // A string at the start of the mapped region - string1_ = region; - string1_[expected_length_] = '\0'; - - // A string near the end of the mapped region - string2_ = region + page_size_ - expected_length_ * 2; - string2_[expected_length_] = '\0'; - - // A string that crosses from the mapped into the unmapped region - string3_ = region + page_size_ - expected_length_ + 1; - string3_[expected_length_] = '\0'; - - // A string entirely in the unmapped region - string4_ = region + page_size_ + 10; - string4_[expected_length_] = '\0'; - - result_.reserve(expected_length_ + 1); - - EXPECT_TRUE(pages_.ResetAddrLen(region, page_size_)); + : MultiprocessExec(), limit_size_(limit_size) { + SetChildTestMainFunction("ReadCStringUnmappedChildMain"); } + void RunAgainstSelf() { + ScopedMmap pages; + std::vector strings; + DoCStringUnmappedTestSetup(&pages, &strings); + DoTest(GetSelfProcess(), strings); + } + + void RunAgainstChild() { Run(); } + private: - void DoTest(pid_t pid) { - ProcessMemoryLinux memory; - ASSERT_TRUE(memory.Initialize(pid)); + void MultiprocessParent() override { + std::vector 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); + } + + void DoTest(ProcessType process, + const std::vector& strings) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(process)); + + std::string result; + result.reserve(kChildProcessStringLength + 1); if (limit_size_) { - ASSERT_TRUE(ReadCStringSizeLimited( - memory, string1_, expected_length_ + 1, &result_)); - EXPECT_EQ(result_, string1_); - ASSERT_TRUE(ReadCStringSizeLimited( - memory, string2_, expected_length_ + 1, &result_)); - EXPECT_EQ(result_, string2_); - EXPECT_FALSE(ReadCStringSizeLimited( - memory, string3_, expected_length_ + 1, &result_)); - EXPECT_FALSE(ReadCStringSizeLimited( - memory, string4_, expected_length_ + 1, &result_)); + ASSERT_TRUE(memory.ReadCStringSizeLimited( + strings[0].address(), kChildProcessStringLength + 1, &result)); + EXPECT_EQ(result, strings[0].expected_value()); + ASSERT_TRUE(memory.ReadCStringSizeLimited( + strings[1].address(), kChildProcessStringLength + 1, &result)); + EXPECT_EQ(result, strings[1].expected_value()); + EXPECT_FALSE(memory.ReadCStringSizeLimited( + strings[2].address(), kChildProcessStringLength + 1, &result)); + EXPECT_FALSE(memory.ReadCStringSizeLimited( + strings[3].address(), kChildProcessStringLength + 1, &result)); } else { - ASSERT_TRUE(ReadCString(memory, string1_, &result_)); - EXPECT_EQ(result_, string1_); - ASSERT_TRUE(ReadCString(memory, string2_, &result_)); - EXPECT_EQ(result_, string2_); - EXPECT_FALSE(ReadCString(memory, string3_, &result_)); - EXPECT_FALSE(ReadCString(memory, string4_, &result_)); + ASSERT_TRUE(memory.ReadCString(strings[0].address(), &result)); + EXPECT_EQ(result, strings[0].expected_value()); + ASSERT_TRUE(memory.ReadCString(strings[1].address(), &result)); + EXPECT_EQ(result, strings[1].expected_value()); + EXPECT_FALSE(memory.ReadCString(strings[2].address(), &result)); + EXPECT_FALSE(memory.ReadCString(strings[3].address(), &result)); } } - std::string result_; - ScopedMmap pages_; - const size_t page_size_; - const size_t region_size_; - static const size_t expected_length_ = 10; - char* string1_; - char* string2_; - char* string3_; - char* string4_; const bool limit_size_; DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest); @@ -382,10 +580,10 @@ TEST(ProcessMemory, ReadCStringUnmappedSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadCStringUnmappedForked) { +TEST(ProcessMemory, ReadCStringUnmappedChild) { ReadCStringUnmappedTest test(/* limit_size= */ false); ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstForked(); + test.RunAgainstChild(); } TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) { @@ -394,10 +592,10 @@ TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedSelf) { test.RunAgainstSelf(); } -TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedForked) { +TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedChild) { ReadCStringUnmappedTest test(/* limit_size= */ true); ASSERT_FALSE(testing::Test::HasFailure()); - test.RunAgainstForked(); + test.RunAgainstChild(); } } // namespace diff --git a/util/stdlib/aligned_allocator.cc b/util/stdlib/aligned_allocator.cc index 9aedb299..797a3ac4 100644 --- a/util/stdlib/aligned_allocator.cc +++ b/util/stdlib/aligned_allocator.cc @@ -18,7 +18,7 @@ #include "build/build_config.h" -#if defined(OS_POSIX) +#if defined(OS_POSIX) || defined(_LIBCPP_STD_VER) #include #elif defined(OS_WIN) #include @@ -31,7 +31,7 @@ namespace { // library to do so. This works even if C++ exceptions are disabled, causing // program termination if uncaught. void ThrowBadAlloc() { -#if defined(OS_POSIX) +#if defined(OS_POSIX) || defined(_LIBCPP_STD_VER) // This works with both libc++ and libstdc++. std::__throw_bad_alloc(); #elif defined(OS_WIN) diff --git a/util/stdlib/aligned_allocator_test.cc b/util/stdlib/aligned_allocator_test.cc index d52dcfa2..1c16dcc5 100644 --- a/util/stdlib/aligned_allocator_test.cc +++ b/util/stdlib/aligned_allocator_test.cc @@ -18,6 +18,7 @@ #include "base/compiler_specific.h" #include "gtest/gtest.h" +#include "test/gtest_death.h" #if defined(OS_WIN) #include @@ -110,7 +111,7 @@ void BadAlignmentTest() { } TEST(AlignedAllocatorDeathTest, BadAlignment) { - ASSERT_DEATH(BadAlignmentTest(), ""); + ASSERT_DEATH_CRASH(BadAlignmentTest(), ""); } } // namespace diff --git a/util/stdlib/objc.h b/util/stdlib/objc.h index 0b44bbab..faaa4538 100644 --- a/util/stdlib/objc.h +++ b/util/stdlib/objc.h @@ -22,7 +22,7 @@ // In order for the @NO and @YES literals to work, NO and YES must be defined as // __objc_no and __objc_yes. See -// http://llvm.org/releases/3.1/tools/clang/docs/ObjectiveCLiterals.html . +// https://clang.llvm.org/docs/ObjectiveCLiterals.html. // // NO and YES are defined properly for this purpose in the 10.8 SDK, but not in // earlier SDKs. Because this code is never expected to be compiled with a diff --git a/util/stdlib/string_number_conversion.cc b/util/stdlib/string_number_conversion.cc index c3352bee..859a8333 100644 --- a/util/stdlib/string_number_conversion.cc +++ b/util/stdlib/string_number_conversion.cc @@ -116,7 +116,7 @@ struct StringToUnsignedInt64Traits }; template -bool StringToIntegerInternal(const base::StringPiece& string, +bool StringToIntegerInternal(const std::string& string, typename Traits::IntType* number) { using IntType = typename Traits::IntType; using LongType = typename Traits::LongType; @@ -127,14 +127,6 @@ bool StringToIntegerInternal(const base::StringPiece& string, return false; } - if (string[string.length()] != '\0') { - // The implementations use the C standard library’s conversion routines, - // which rely on the strings having a trailing NUL character. std::string - // will NUL-terminate. - std::string terminated_string(string.data(), string.length()); - return StringToIntegerInternal(terminated_string, number); - } - errno = 0; char* end; LongType result = Traits::Convert(string.data(), &end, 0); @@ -152,19 +144,19 @@ bool StringToIntegerInternal(const base::StringPiece& string, namespace crashpad { -bool StringToNumber(const base::StringPiece& string, int* number) { +bool StringToNumber(const std::string& string, int* number) { return StringToIntegerInternal(string, number); } -bool StringToNumber(const base::StringPiece& string, unsigned int* number) { +bool StringToNumber(const std::string& string, unsigned int* number) { return StringToIntegerInternal(string, number); } -bool StringToNumber(const base::StringPiece& string, int64_t* number) { +bool StringToNumber(const std::string& string, int64_t* number) { return StringToIntegerInternal(string, number); } -bool StringToNumber(const base::StringPiece& string, uint64_t* number) { +bool StringToNumber(const std::string& string, uint64_t* number) { return StringToIntegerInternal(string, number); } diff --git a/util/stdlib/string_number_conversion.h b/util/stdlib/string_number_conversion.h index b7bdcce8..b5f1d44a 100644 --- a/util/stdlib/string_number_conversion.h +++ b/util/stdlib/string_number_conversion.h @@ -15,7 +15,7 @@ #ifndef CRASHPAD_UTIL_STDLIB_STRING_NUMBER_CONVERSION_H_ #define CRASHPAD_UTIL_STDLIB_STRING_NUMBER_CONVERSION_H_ -#include "base/strings/string_piece.h" +#include namespace crashpad { @@ -54,10 +54,10 @@ namespace crashpad { //! allow arbitrary bases based on whether the string begins with a prefix //! indicating its base. The functions here are provided for situations //! where such prefix recognition is desirable. -bool StringToNumber(const base::StringPiece& string, int* number); -bool StringToNumber(const base::StringPiece& string, unsigned int* number); -bool StringToNumber(const base::StringPiece& string, int64_t* number); -bool StringToNumber(const base::StringPiece& string, uint64_t* number); +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); //! \} } // namespace crashpad diff --git a/util/stdlib/string_number_conversion_test.cc b/util/stdlib/string_number_conversion_test.cc index dd17c00d..d855c8d7 100644 --- a/util/stdlib/string_number_conversion_test.cc +++ b/util/stdlib/string_number_conversion_test.cc @@ -114,13 +114,9 @@ TEST(StringNumberConversion, StringToInt) { // is split to avoid MSVC warning: // "decimal digit terminates octal escape sequence". static constexpr char input[] = "6\000" "6"; - base::StringPiece input_string(input, arraysize(input) - 1); + std::string input_string(input, arraysize(input) - 1); int output; EXPECT_FALSE(StringToNumber(input_string, &output)); - - // Ensure that a NUL is not required at the end of the string. - EXPECT_TRUE(StringToNumber(base::StringPiece("66", 1), &output)); - EXPECT_EQ(output, 6); } TEST(StringNumberConversion, StringToUnsignedInt) { @@ -212,13 +208,9 @@ TEST(StringNumberConversion, StringToUnsignedInt) { // is split to avoid MSVC warning: // "decimal digit terminates octal escape sequence". static constexpr char input[] = "6\000" "6"; - base::StringPiece input_string(input, arraysize(input) - 1); + std::string input_string(input, arraysize(input) - 1); unsigned int output; EXPECT_FALSE(StringToNumber(input_string, &output)); - - // Ensure that a NUL is not required at the end of the string. - EXPECT_TRUE(StringToNumber(base::StringPiece("66", 1), &output)); - EXPECT_EQ(output, 6u); } TEST(StringNumberConversion, StringToInt64) { diff --git a/util/stdlib/strnlen.cc b/util/stdlib/strnlen.cc index 3016bf63..a238728f 100644 --- a/util/stdlib/strnlen.cc +++ b/util/stdlib/strnlen.cc @@ -17,7 +17,7 @@ #if defined(OS_MACOSX) && 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 OSX 10.7+ to suppress a +// Redeclare a method only available on Mac OS X 10.7 and later to suppress a // -Wpartial-availability warning. extern "C" { size_t strnlen(const char* string, size_t max_length); diff --git a/util/synchronization/semaphore.h b/util/synchronization/semaphore.h index 49479639..dc3000b0 100644 --- a/util/synchronization/semaphore.h +++ b/util/synchronization/semaphore.h @@ -23,6 +23,9 @@ #include #elif defined(OS_WIN) #include +#elif defined(OS_ANDROID) +#include +#include #else #include #endif @@ -77,6 +80,10 @@ class Semaphore { dispatch_semaphore_t semaphore_; #elif defined(OS_WIN) HANDLE semaphore_; +#elif defined(OS_ANDROID) + std::condition_variable cv_; + std::mutex mutex_; + int value_; #else sem_t semaphore_; #endif diff --git a/util/synchronization/semaphore_posix.cc b/util/synchronization/semaphore_posix.cc index f6e8bea1..3875f3f4 100644 --- a/util/synchronization/semaphore_posix.cc +++ b/util/synchronization/semaphore_posix.cc @@ -18,25 +18,51 @@ #include #include +#include + #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "util/misc/time.h" namespace crashpad { -#if !defined(OS_MACOSX) +#if defined(OS_ANDROID) -namespace { +Semaphore::Semaphore(int value) : cv_(), mutex_(), value_(value) {} -void AddTimespec(const timespec& ts1, const timespec& ts2, timespec* result) { - result->tv_sec = ts1.tv_sec + ts2.tv_sec; - result->tv_nsec = ts1.tv_nsec + ts2.tv_nsec; - if (result->tv_nsec > static_cast(1E9)) { - ++result->tv_sec; - result->tv_nsec -= static_cast(1E9); - } +Semaphore::~Semaphore() = default; + +void Semaphore::Wait() { + std::unique_lock lock(mutex_); + cv_.wait(lock, [this] { return this->value_ > 0; }); + --value_; } -} // namespace +bool Semaphore::TimedWait(double seconds) { + DCHECK_GE(seconds, 0.0); + + if (isinf(seconds)) { + Wait(); + return true; + } + + std::unique_lock lock(mutex_); + if (!cv_.wait_for(lock, std::chrono::duration(seconds), [this] { + return this->value_ > 0; + })) { + return false; + } + --value_; + return true; +} + +void Semaphore::Signal() { + std::lock_guard lock(mutex_); + ++value_; + cv_.notify_one(); +} + +#elif !defined(OS_MACOSX) Semaphore::Semaphore(int value) { PCHECK(sem_init(&semaphore_, 0, value) == 0) << "sem_init"; @@ -77,6 +103,6 @@ void Semaphore::Signal() { PCHECK(sem_post(&semaphore_) == 0) << "sem_post"; } -#endif +#endif // OS_ANDROID } // namespace crashpad diff --git a/util/synchronization/semaphore_test.cc b/util/synchronization/semaphore_test.cc index e1f3648c..10f7546d 100644 --- a/util/synchronization/semaphore_test.cc +++ b/util/synchronization/semaphore_test.cc @@ -41,7 +41,10 @@ TEST(Semaphore, TimedWait) { TEST(Semaphore, TimedWaitTimeout) { Semaphore semaphore(0); - EXPECT_FALSE(semaphore.TimedWait(0.01)); // 10ms + semaphore.Signal(); + constexpr double kTenMs = 0.01; + EXPECT_TRUE(semaphore.TimedWait(kTenMs)); + EXPECT_FALSE(semaphore.TimedWait(kTenMs)); } TEST(Semaphore, TimedWaitInfinite_0) { diff --git a/util/thread/stoppable.h b/util/thread/stoppable.h new file mode 100644 index 00000000..e7a51277 --- /dev/null +++ b/util/thread/stoppable.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_UTIL_THREAD_STOPPABLE_H_ +#define CRASHPAD_UTIL_THREAD_STOPPABLE_H_ + +#include "base/macros.h" + +namespace crashpad { + +//! \brief An interface for operations that may be Started and Stopped. +class Stoppable { + public: + virtual ~Stoppable() = default; + + //! \brief Starts the operation. + virtual void Start() = 0; + + //! \brief Stops the operation. + virtual void Stop() = 0; + + protected: + Stoppable() = default; +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_THREAD_STOPPABLE_H_ diff --git a/util/thread/worker_thread_test.cc b/util/thread/worker_thread_test.cc index 9b11fef1..4e4fbf9a 100644 --- a/util/thread/worker_thread_test.cc +++ b/util/thread/worker_thread_test.cc @@ -71,7 +71,18 @@ TEST(WorkerThread, DoWork) { thread.Stop(); EXPECT_FALSE(thread.is_running()); - EXPECT_GE(1 * kNanosecondsPerSecond, ClockMonotonicNanoseconds() - start); +// Fuchsia's scheduler is very antagonistic. The assumption that the two work +// items complete in some particular amount of time is strictly incorrect, but +// also somewhat useful. The expected time "should" be ~40-50ms with a work +// interval of 0.05s, but on Fuchsia, 1200ms was observed. So, on Fuchsia, use a +// much larger timeout. See https://crashpad.chromium.org/bug/231. +#if defined(OS_FUCHSIA) + constexpr uint64_t kUpperBoundTime = 10; +#else + constexpr uint64_t kUpperBoundTime = 1; +#endif + EXPECT_GE(kUpperBoundTime * kNanosecondsPerSecond, + ClockMonotonicNanoseconds() - start); } TEST(WorkerThread, StopBeforeDoWork) { diff --git a/util/util.gyp b/util/util.gyp index 2b463c62..1ffb752c 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -58,13 +58,24 @@ 'linux/checked_address_range.h', 'linux/direct_ptrace_connection.cc', 'linux/direct_ptrace_connection.h', + 'linux/exception_handler_client.cc', + 'linux/exception_handler_client.h', + 'linux/exception_handler_protocol.cc', + 'linux/exception_handler_protocol.h', + 'linux/exception_information.h', 'linux/memory_map.cc', 'linux/memory_map.h', 'linux/proc_stat_reader.cc', 'linux/proc_stat_reader.h', + 'linux/ptrace_broker.cc', + 'linux/ptrace_broker.h', + 'linux/ptrace_client.cc', + 'linux/ptrace_client.h' 'linux/ptrace_connection.h', 'linux/ptracer.cc', 'linux/ptracer.h', + 'linux/scoped_pr_set_ptracer.cc', + 'linux/scoped_pr_set_ptracer.h', 'linux/scoped_ptrace_attach.cc', 'linux/scoped_ptrace_attach.h', 'linux/thread_info.cc', @@ -117,10 +128,15 @@ 'misc/address_types.h', 'misc/arraysize_unsafe.h', 'misc/as_underlying_type.h', + 'misc/capture_context.h', + 'misc/capture_context_linux.S', + 'misc/capture_context_mac.S', + 'misc/capture_context_win.asm', 'misc/clock.h', 'misc/clock_mac.cc', 'misc/clock_posix.cc', 'misc/clock_win.cc', + 'misc/elf_note_types.h', 'misc/from_pointer_cast.h', 'misc/implicit_cast.h', 'misc/initialization_state.h', @@ -138,11 +154,16 @@ 'misc/pdb_structures.h', 'misc/random_string.cc', 'misc/random_string.h', + 'misc/range_set.cc', + 'misc/range_set.h', 'misc/reinterpret_bytes.cc', 'misc/reinterpret_bytes.h', 'misc/scoped_forbid_return.cc', 'misc/scoped_forbid_return.h', 'misc/symbolic_constants_common.h', + 'misc/time.cc', + 'misc/time.h', + 'misc/time_win.cc', 'misc/tri_state.h', 'misc/uuid.cc', 'misc/uuid.h', @@ -157,8 +178,8 @@ 'net/http_multipart_builder.h', 'net/http_transport.cc', 'net/http_transport.h', - 'net/http_transport_libcurl.cc', 'net/http_transport_mac.mm', + 'net/http_transport_none.cc', 'net/http_transport_win.cc', 'net/url.cc', 'net/url.h', @@ -175,6 +196,8 @@ 'posix/close_stdio.h', 'posix/drop_privileges.cc', 'posix/drop_privileges.h', + 'posix/double_fork_and_exec.cc', + 'posix/double_fork_and_exec.h', 'posix/process_info.h', 'posix/process_info_linux.cc', 'posix/process_info_mac.cc', @@ -186,9 +209,11 @@ 'posix/signals.h', 'posix/symbolic_constants_posix.cc', 'posix/symbolic_constants_posix.h', + 'process/process_memory.cc', 'process/process_memory.h', 'process/process_memory_linux.cc', 'process/process_memory_linux.h', + 'process/process_memory_native.h', 'process/process_memory_range.cc', 'process/process_memory_range.h', 'stdlib/aligned_allocator.cc', @@ -208,6 +233,7 @@ 'synchronization/semaphore_posix.cc', 'synchronization/semaphore_win.cc', 'synchronization/semaphore.h', + 'thread/stoppable.h', 'thread/thread.cc', 'thread/thread.h', 'thread/thread_log_messages.cc', @@ -217,8 +243,6 @@ 'thread/worker_thread.cc', 'thread/worker_thread.h', 'win/address_types.h', - 'win/capture_context.asm', - 'win/capture_context.h', 'win/checked_win_address_range.h', 'win/command_line.cc', 'win/command_line.h', @@ -253,11 +277,11 @@ 'win/scoped_local_alloc.h', 'win/scoped_process_suspend.cc', 'win/scoped_process_suspend.h', + 'win/scoped_set_event.cc', + 'win/scoped_set_event.h', 'win/session_end_watcher.cc', 'win/session_end_watcher.h', 'win/termination_codes.h', - 'win/time.cc', - 'win/time.h', 'win/xp_compat.h', ], 'conditions': [ @@ -325,6 +349,8 @@ '$(SDKROOT)/usr/lib/libbsm.dylib', ], }, + }, { # else: OS!=mac + 'sources!': [ 'misc/capture_context_mac.S' ], }], ['OS=="win"', { 'link_settings': { @@ -354,19 +380,22 @@ ], }, { # else: OS!="win" 'sources!': [ - 'win/capture_context.asm', + 'misc/capture_context_win.asm', 'win/safe_terminate_process.asm', ], }], ['OS=="linux"', { - 'link_settings': { - 'libraries': [ - '-lcurl', - ], - }, + 'sources': [ + 'net/http_transport_socket.cc', + ], }, { # else: OS!="linux" 'sources!': [ - 'net/http_transport_libcurl.cc', + 'misc/capture_context_linux.S', + ], + }], + ['OS!="android"', { + 'sources!': [ + 'net/http_transport_none.cc', ], }], ['OS!="linux" and OS!="android"', { @@ -379,6 +408,7 @@ ['OS=="android"', { 'sources/': [ ['include', '^linux/'], + ['include', '^misc/capture_context_linux\\.S$'], ['include', '^misc/paths_linux\\.cc$'], ['include', '^posix/process_info_linux\\.cc$'], ['include', '^process/process_memory_linux\\.cc$'], diff --git a/util/util_test.gyp b/util/util_test.gyp index 152c981a..ff3559e3 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -44,6 +44,7 @@ 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', 'linux/proc_stat_reader_test.cc', + 'linux/ptrace_broker_test.cc', 'linux/ptracer_test.cc', 'linux/scoped_ptrace_attach_test.cc', 'mac/launchd_test.mm', @@ -66,6 +67,11 @@ 'mach/symbolic_constants_mach_test.cc', 'mach/task_memory_test.cc', 'misc/arraysize_unsafe_test.cc', + 'misc/capture_context_test.cc', + 'misc/capture_context_test_util.h', + 'misc/capture_context_test_util_linux.cc', + 'misc/capture_context_test_util_mac.cc', + 'misc/capture_context_test_util_win.cc', 'misc/clock_test.cc', 'misc/from_pointer_cast_test.cc', 'misc/initialization_state_dcheck_test.cc', @@ -73,7 +79,9 @@ 'misc/paths_test.cc', 'misc/scoped_forbid_return_test.cc', 'misc/random_string_test.cc', + 'misc/range_set_test.cc', 'misc/reinterpret_bytes_test.cc', + 'misc/time_test.cc', 'misc/uuid_test.cc', 'net/http_body_gzip_test.cc', 'net/http_body_test.cc', @@ -103,7 +111,6 @@ 'thread/thread_log_messages_test.cc', 'thread/thread_test.cc', 'thread/worker_thread_test.cc', - 'win/capture_context_test.cc', 'win/command_line_test.cc', 'win/critical_section_with_debug_info_test.cc', 'win/exception_handler_server_test.cc', @@ -115,7 +122,6 @@ 'win/safe_terminate_process_test.cc', 'win/scoped_process_suspend_test.cc', 'win/session_end_watcher_test.cc', - 'win/time_test.cc', ], 'conditions': [ ['OS=="mac"', { @@ -155,12 +161,41 @@ ['OS=="android"', { 'sources/': [ ['include', '^linux/'], + ['include', '^misc/capture_context_test_util_linux\\.cc$'], ], }], ], }, ], 'conditions': [ + ['OS!="android"', { + 'targets': [ + { + 'target_name': 'http_transport_test_server', + 'type': 'executable', + 'dependencies': [ + '../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', + ], + 'sources': [ + 'net/http_transport_test_server.cc', + ], + 'include_dirs': [ + '..', + ], + 'xcode_settings': { + 'WARNING_CFLAGS!': [ + '-Wexit-time-destructors', + ], + }, + 'cflags!': [ + '-Wexit-time-destructors', + ], + }, + ], + }], ['OS=="win"', { 'targets': [ { @@ -169,18 +204,6 @@ 'sources': [ 'win/process_info_test_child.cc', ], - # Set an unusually high load address to make sure that the main - # executable still appears as the first element in - # ProcessInfo::Modules(). - 'msvs_settings': { - 'VCLinkerTool': { - 'AdditionalOptions': [ - '/BASE:0x78000000', - ], - 'RandomizedBaseAddress': '1', # /DYNAMICBASE:NO. - 'FixedBaseAddress': '2', # /FIXED. - }, - }, }, { 'target_name': 'crashpad_util_test_safe_terminate_process_test_child', diff --git a/util/win/capture_context.h b/util/win/capture_context.h deleted file mode 100644 index 2f501f80..00000000 --- a/util/win/capture_context.h +++ /dev/null @@ -1,47 +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_CLIENT_CAPTURE_CONTEXT_WIN_H_ -#define CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_ - -#include - -namespace crashpad { - -//! \brief Saves the CPU context. -//! -//! The CPU context will be captured as accurately and completely as possible, -//! containing an atomic snapshot at the point of this function’s return. This -//! function does not modify any registers. -//! -//! This function captures all integer registers as well as the floating-point -//! and vector (SSE) state. It does not capture debug registers, which are -//! inaccessible by user code. -//! -//! This function is a replacement for `RtlCaptureContext()`, which contains -//! bugs and limitations. On 32-bit x86, `RtlCaptureContext()` requires that -//! `ebp` be used as a frame pointer, and returns `ebp`, `esp`, and `eip` out of -//! sync with the other registers. Both the 32-bit x86 and 64-bit x86_64 -//! versions of `RtlCaptureContext()` capture only the state of the integer -//! registers, ignoring floating-point and vector state. -//! -//! \param[out] context The structure to store the context in. -//! -//! \note On x86_64, the value for `rcx` will be populated with the address of -//! this function’s argument, as mandated by the ABI. -void CaptureContext(CONTEXT* context); - -} // namespace crashpad - -#endif // CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_ diff --git a/util/win/command_line.cc b/util/win/command_line.cc index 930c83c8..c015eed7 100644 --- a/util/win/command_line.cc +++ b/util/win/command_line.cc @@ -19,7 +19,7 @@ namespace crashpad { // Ref: -// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx +// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ void AppendCommandLineArgument(const std::wstring& argument, std::wstring* command_line) { if (!command_line->empty()) { diff --git a/util/win/command_line_test.cc b/util/win/command_line_test.cc index e7761f12..025ef8a7 100644 --- a/util/win/command_line_test.cc +++ b/util/win/command_line_test.cc @@ -53,7 +53,7 @@ void AppendCommandLineArgumentTest(size_t argc, const wchar_t* const argv[]) { TEST(CommandLine, AppendCommandLineArgument) { // Most of these test cases come from - // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx, + // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/, // which was also a reference for the implementation of // AppendCommandLineArgument(). diff --git a/util/win/exception_handler_server.h b/util/win/exception_handler_server.h index 69f760d2..994cdba4 100644 --- a/util/win/exception_handler_server.h +++ b/util/win/exception_handler_server.h @@ -38,8 +38,6 @@ class ExceptionHandlerServer { public: class Delegate { public: - virtual ~Delegate(); - //! \brief Called when the server has created the named pipe connection //! points and is ready to service requests. virtual void ExceptionHandlerServerStarted() = 0; @@ -60,6 +58,9 @@ class ExceptionHandlerServer { HANDLE process, WinVMAddress exception_information_address, WinVMAddress debug_critical_section_address) = 0; + + protected: + ~Delegate(); }; //! \brief Constructs the exception handling server. diff --git a/util/win/exception_handler_server_test.cc b/util/win/exception_handler_server_test.cc index 6f880516..ce316774 100644 --- a/util/win/exception_handler_server_test.cc +++ b/util/win/exception_handler_server_test.cc @@ -56,7 +56,7 @@ class RunServerThread : public Thread { class TestDelegate : public ExceptionHandlerServer::Delegate { public: explicit TestDelegate(HANDLE server_ready) : server_ready_(server_ready) {} - ~TestDelegate() override {} + ~TestDelegate() {} void ExceptionHandlerServerStarted() override { SetEvent(server_ready_); diff --git a/util/win/handle.h b/util/win/handle.h index 8a630690..951302e2 100644 --- a/util/win/handle.h +++ b/util/win/handle.h @@ -24,7 +24,7 @@ namespace crashpad { //! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t //! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even //! in 64-bit processes on 64-bit operating systems. See Interprocess +//! href="https://msdn.microsoft.com/library/aa384203.aspx">Interprocess //! Communication Between 32-bit and 64-bit Applications. //! //! This function safely converts a kernel `HANDLE` to an `int` similarly to a @@ -45,7 +45,7 @@ int HandleToInt(HANDLE handle); //! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t //! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even //! in 64-bit processes on 64-bit operating systems. See Interprocess +//! href="https://msdn.microsoft.com/library/aa384203.aspx">Interprocess //! Communication Between 32-bit and 64-bit Applications. //! //! This function safely convert an `int` to a kernel `HANDLE` similarly to a diff --git a/util/win/nt_internals.h b/util/win/nt_internals.h index a14678fc..dad04056 100644 --- a/util/win/nt_internals.h +++ b/util/win/nt_internals.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef CRASHPAD_UTIL_WIN_NT_INTERNALS_H_ +#define CRASHPAD_UTIL_WIN_NT_INTERNALS_H_ + #include #include @@ -76,7 +79,7 @@ NTSTATUS NtSuspendProcess(HANDLE handle); NTSTATUS NtResumeProcess(HANDLE handle); -// From https://msdn.microsoft.com/en-us/library/cc678403(v=vs.85).aspx. +// From https://msdn.microsoft.com/library/cc678403.aspx. template struct RTL_UNLOAD_EVENT_TRACE { typename Traits::Pointer BaseAddress; @@ -92,3 +95,5 @@ void RtlGetUnloadEventTraceEx(ULONG** element_size, void** event_trace); } // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_NT_INTERNALS_H_ diff --git a/util/win/process_info.cc b/util/win/process_info.cc index 3f005791..cd6bcd3e 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -269,36 +269,15 @@ bool ReadProcessData(HANDLE process, return false; process_types::LDR_DATA_TABLE_ENTRY ldr_data_table_entry; - - // Include the first module in the memory order list to get our the main - // executable's name, as it's not included in initialization order below. - if (!ReadStruct(process, - static_cast( - peb_ldr_data.InMemoryOrderModuleList.Flink) - - offsetof(process_types::LDR_DATA_TABLE_ENTRY, - InMemoryOrderLinks), - &ldr_data_table_entry)) { - return false; - } ProcessInfo::Module module; - if (!ReadUnicodeString( - process, ldr_data_table_entry.FullDllName, &module.name)) { - return false; - } - module.dll_base = ldr_data_table_entry.DllBase; - module.size = ldr_data_table_entry.SizeOfImage; - module.timestamp = ldr_data_table_entry.TimeDateStamp; - process_info->modules_.push_back(module); // Walk the PEB LDR structure (doubly-linked list) to get the list of loaded // modules. We use this method rather than EnumProcessModules to get the - // modules in initialization order rather than memory order. - typename Traits::Pointer last = - peb_ldr_data.InInitializationOrderModuleList.Blink; - for (typename Traits::Pointer cur = - peb_ldr_data.InInitializationOrderModuleList.Flink; - ; - cur = ldr_data_table_entry.InInitializationOrderLinks.Flink) { + // modules in load order rather than memory order. Notably, this includes the + // main executable as the first element. + typename Traits::Pointer last = peb_ldr_data.InLoadOrderModuleList.Blink; + for (typename Traits::Pointer cur = peb_ldr_data.InLoadOrderModuleList.Flink;; + cur = ldr_data_table_entry.InLoadOrderLinks.Flink) { // |cur| is the pointer to the LIST_ENTRY embedded in the // LDR_DATA_TABLE_ENTRY, in the target process's address space. So we need // to read from the target, and also offset back to the beginning of the @@ -306,14 +285,14 @@ bool ReadProcessData(HANDLE process, if (!ReadStruct(process, static_cast(cur) - offsetof(process_types::LDR_DATA_TABLE_ENTRY, - InInitializationOrderLinks), + InLoadOrderLinks), &ldr_data_table_entry)) { break; } // TODO(scottmg): Capture Checksum, etc. too? if (!ReadUnicodeString( process, ldr_data_table_entry.FullDllName, &module.name)) { - break; + module.name = L"???"; } module.dll_base = ldr_data_table_entry.DllBase; module.size = ldr_data_table_entry.SizeOfImage; diff --git a/util/win/process_info.h b/util/win/process_info.h index be968f92..58921c56 100644 --- a/util/win/process_info.h +++ b/util/win/process_info.h @@ -72,7 +72,7 @@ class ProcessInfo { //! \brief The `ACCESS_MASK` for the handle in this process. //! //! See - //! http://blogs.msdn.com/b/openspecification/archive/2010/04/01/about-the-access-mask-structure.aspx + //! https://blogs.msdn.microsoft.com/openspecification/2010/04/01/about-the-access_mask-structure/ //! for more information. uint32_t granted_access; @@ -188,9 +188,9 @@ class ProcessInfo { // the presumed alignment and emits SSE instructions that require aligned // storage. clang-cl should relax (unfortunately), but in the mean time, this // provides aligned storage. See https://crbug.com/564691 and - // http://llvm.org/PR25779. + // https://llvm.org/PR25779. // - // TODO(mark): Remove this workaround when http://llvm.org/PR25779 is fixed + // TODO(mark): Remove this workaround when https://llvm.org/PR25779 is fixed // and the fix is present in the clang-cl that compiles this code. MemoryBasicInformation64Vector memory_info_; diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index 709536b9..c7abdb6f 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -180,11 +180,18 @@ void TestOtherProcess(TestPaths::Architecture architecture) { // lz32.dll is an uncommonly-used-but-always-available module that the test // binary manually loads. static constexpr wchar_t kLz32dllName[] = L"\\lz32.dll"; - ASSERT_GE(modules.back().name.size(), wcslen(kLz32dllName)); - EXPECT_EQ(modules.back().name.substr(modules.back().name.size() - - wcslen(kLz32dllName)), + auto& lz32 = modules[modules.size() - 2]; + ASSERT_GE(lz32.name.size(), wcslen(kLz32dllName)); + EXPECT_EQ(lz32.name.substr(lz32.name.size() - wcslen(kLz32dllName)), kLz32dllName); + // Note that the test code corrupts the PEB MemoryOrder list, whereas + // ProcessInfo::Modules() retrieves the module names via the PEB LoadOrder + // list. These are expected to point to the same strings, but theoretically + // could be separate. + auto& corrupted = modules.back(); + EXPECT_EQ(corrupted.name, L"???"); + VerifyAddressInInCodePage(process_info, code_address); } diff --git a/util/win/process_info_test_child.cc b/util/win/process_info_test_child.cc index c587f5b8..1de1e8b2 100644 --- a/util/win/process_info_test_child.cc +++ b/util/win/process_info_test_child.cc @@ -13,10 +13,26 @@ // limitations under the License. #include -#include #include +#include +#include #include #include +#include + +namespace { + +bool UnicodeStringEndsWithCaseInsensitive(const UNICODE_STRING& us, + const wchar_t* ends_with) { + const size_t len = wcslen(ends_with); + // Recall that UNICODE_STRING.Length is in bytes, not characters. + const size_t us_len_in_chars = us.Length / sizeof(wchar_t); + if (us_len_in_chars < len) + return false; + return _wcsnicmp(&us.Buffer[us_len_in_chars - len], ends_with, len) == 0; +} + +} // namespace // A simple binary to be loaded and inspected by ProcessInfo. int wmain(int argc, wchar_t** argv) { @@ -29,10 +45,49 @@ int wmain(int argc, wchar_t** argv) { abort(); // Load an unusual module (that we don't depend upon) so we can do an - // existence check. + // existence check. It's also important that these DLLs don't depend on + // any other DLLs, otherwise there'll be additional modules in the list, which + // the test expects not to be there. if (!LoadLibrary(L"lz32.dll")) abort(); + // Load another unusual module so we can destroy its FullDllName field in the + // PEB to test corrupted name reads. + static constexpr wchar_t kCorruptableDll[] = L"kbdurdu.dll"; + if (!LoadLibrary(kCorruptableDll)) + abort(); + + // Find and corrupt the buffer pointer to the name in the PEB. + HINSTANCE ntdll = GetModuleHandle(L"ntdll.dll"); + decltype(NtQueryInformationProcess)* nt_query_information_process = + reinterpret_cast( + GetProcAddress(ntdll, "NtQueryInformationProcess")); + if (!nt_query_information_process) + abort(); + + PROCESS_BASIC_INFORMATION pbi; + if (nt_query_information_process(GetCurrentProcess(), + ProcessBasicInformation, + &pbi, + sizeof(pbi), + nullptr) < 0) { + abort(); + } + + PEB_LDR_DATA* ldr = pbi.PebBaseAddress->Ldr; + LIST_ENTRY* head = &ldr->InMemoryOrderModuleList; + LIST_ENTRY* next = head->Flink; + while (next != head) { + LDR_DATA_TABLE_ENTRY* entry = + CONTAINING_RECORD(next, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (UnicodeStringEndsWithCaseInsensitive(entry->FullDllName, + kCorruptableDll)) { + // Corrupt the pointer to the name. + entry->FullDllName.Buffer = 0; + } + next = next->Flink; + } + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); if (out == INVALID_HANDLE_VALUE) abort(); diff --git a/util/win/process_structs.h b/util/win/process_structs.h index 66bc7f5b..d9b5b2fa 100644 --- a/util/win/process_structs.h +++ b/util/win/process_structs.h @@ -289,7 +289,7 @@ struct PEB { template struct NT_TIB { union { - // See https://msdn.microsoft.com/en-us/library/dn424783.aspx. + // See https://msdn.microsoft.com/library/dn424783.aspx. typename Traits::Pointer Wow64Teb; struct { typename Traits::Pointer ExceptionList; @@ -306,7 +306,7 @@ struct NT_TIB { }; }; -// See https://msdn.microsoft.com/en-us/library/gg750647.aspx. +// See https://msdn.microsoft.com/library/gg750647.aspx. template struct CLIENT_ID { typename Traits::Pointer UniqueProcess; @@ -314,8 +314,8 @@ struct CLIENT_ID { }; // This is a partial definition of the TEB, as we do not currently use many -// fields of it. See http://www.nirsoft.net/kernel_struct/vista/TEB.html, and -// the (arch-specific) definition of _TEB in winternl.h. +// fields of it. See https://nirsoft.net/kernel_struct/vista/TEB.html, and the +// (arch-specific) definition of _TEB in winternl.h. template struct TEB { NT_TIB NtTib; @@ -334,7 +334,7 @@ struct TEB { typename Traits::Pointer TlsExpansionSlots; }; -// See https://msdn.microsoft.com/en-us/library/gg750724.aspx. +// See https://msdn.microsoft.com/library/gg750724.aspx. template struct SYSTEM_THREAD_INFORMATION { union { @@ -400,7 +400,7 @@ struct VM_COUNTERS { SIZE_T PrivateUsage; }; -// See http://undocumented.ntinternals.net/source/usermode/undocumented%20functions/system%20information/structures/system_process_information.html +// https://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/System%20Information/Structures/SYSTEM_PROCESS_INFORMATION.html template struct SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; @@ -436,7 +436,7 @@ struct SYSTEM_PROCESS_INFORMATION { SYSTEM_THREAD_INFORMATION Threads[1]; }; -// http://undocumented.ntinternals.net/source/usermode/structures/thread_basic_information.html +// https://undocumented.ntinternals.net/source/usermode/structures/THREAD_BASIC_INFORMATION.html template struct THREAD_BASIC_INFORMATION { union { diff --git a/util/win/registration_protocol_win.cc b/util/win/registration_protocol_win.cc index dc31e7d4..4fb536d9 100644 --- a/util/win/registration_protocol_win.cc +++ b/util/win/registration_protocol_win.cc @@ -137,7 +137,7 @@ const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { #pragma pack(push, 1) static constexpr struct SecurityDescriptorBlob { - // See https://msdn.microsoft.com/en-us/library/cc230366.aspx. + // See https://msdn.microsoft.com/library/cc230366.aspx. SECURITY_DESCRIPTOR_RELATIVE sd_rel; struct { ACL acl; diff --git a/util/win/time_test.cc b/util/win/scoped_set_event.cc similarity index 65% rename from util/win/time_test.cc rename to util/win/scoped_set_event.cc index ad0771e3..7fb16474 100644 --- a/util/win/time_test.cc +++ b/util/win/scoped_set_event.cc @@ -12,23 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/win/time.h" +#include "util/win/scoped_set_event.h" -#include "gtest/gtest.h" +#include "base/logging.h" namespace crashpad { -namespace test { -namespace { -TEST(Time, Reasonable) { - timeval t; - GetTimeOfDay(&t); - // Assume that time's time_t return is seconds from 1970. - time_t approx_now = time(nullptr); - EXPECT_GE(approx_now, t.tv_sec); - EXPECT_LT(approx_now - 100, t.tv_sec); +ScopedSetEvent::ScopedSetEvent(HANDLE event) : event_(event) { + DCHECK(event_); +} + +ScopedSetEvent::~ScopedSetEvent() { + if (event_) { + Set(); + } +} + +bool ScopedSetEvent::Set() { + bool rv = !!SetEvent(event_); + if (!rv) { + PLOG(ERROR) << "SetEvent"; + } + event_ = nullptr; + return rv; } -} // namespace -} // namespace test } // namespace crashpad diff --git a/util/win/scoped_set_event.h b/util/win/scoped_set_event.h new file mode 100644 index 00000000..82a1b314 --- /dev/null +++ b/util/win/scoped_set_event.h @@ -0,0 +1,48 @@ +// 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_UTIL_WIN_SCOPED_SET_EVENT_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_SET_EVENT_H_ + +#include + +#include "base/macros.h" + +namespace crashpad { + +//! \brief Calls `SetEvent()` on destruction at latest. +//! +//! Does not assume ownership of the event handle. Use ScopedKernelHANDLE for +//! ownership. +class ScopedSetEvent { + public: + explicit ScopedSetEvent(HANDLE event); + ~ScopedSetEvent(); + + //! \brief Calls `SetEvent()` immediately. + //! + //! `SetEvent()` will not be called on destruction. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Set(); + + private: + HANDLE event_; // weak + + DISALLOW_COPY_AND_ASSIGN(ScopedSetEvent); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_SET_EVENT_H_ diff --git a/util/win/session_end_watcher.cc b/util/win/session_end_watcher.cc index e795f22a..1d470899 100644 --- a/util/win/session_end_watcher.cc +++ b/util/win/session_end_watcher.cc @@ -16,6 +16,7 @@ #include "base/logging.h" #include "base/scoped_generic.h" +#include "util/win/scoped_set_event.h" extern "C" { extern IMAGE_DOS_HEADER __ImageBase; @@ -25,21 +26,6 @@ namespace crashpad { namespace { -class ScopedSetEvent { - public: - explicit ScopedSetEvent(HANDLE event) : event_(event) {} - ~ScopedSetEvent() { - if (!SetEvent(event_)) { - PLOG(ERROR) << "SetEvent"; - } - } - - private: - HANDLE event_; - - DISALLOW_COPY_AND_ASSIGN(ScopedSetEvent); -}; - // ScopedWindowClass and ScopedWindow operate on ATOM* and HWND*, respectively, // instead of ATOM and HWND, so that the actual storage can exist as a local // variable or a member variable, and the scoper can be responsible for diff --git a/util/win/time.h b/util/win/time.h deleted file mode 100644 index 7cc0094f..00000000 --- a/util/win/time.h +++ /dev/null @@ -1,36 +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_UTIL_WIN_TIME_H_ -#define CRASHPAD_UTIL_WIN_TIME_H_ - -#include -#include - -namespace crashpad { - -//! \brief Convert Windows `FILETIME` to `timeval`, converting from Windows -//! epoch to POSIX epoch. -timeval FiletimeToTimevalEpoch(const FILETIME& filetime); - -//! \brief Convert Windows `FILETIME` to `timeval`, treating the values as -//! an interval of elapsed time. -timeval FiletimeToTimevalInterval(const FILETIME& filetime); - -//! \brief Similar to POSIX gettimeofday(), gets the current system time in UTC. -void GetTimeOfDay(timeval* tv); - -} // namespace crashpad - -#endif // CRASHPAD_UTIL_WIN_TIME_H_ diff --git a/util/win/xp_compat.h b/util/win/xp_compat.h index 7c62e41b..1d4d4a0b 100644 --- a/util/win/xp_compat.h +++ b/util/win/xp_compat.h @@ -24,14 +24,14 @@ enum { //! //! Requesting `PROCESS_ALL_ACCESS` with the value defined when building //! against a Vista+ SDK results in `ERROR_ACCESS_DENIED` when running on XP. - //! See https://msdn.microsoft.com/en-ca/library/windows/desktop/ms684880.aspx + //! See https://msdn.microsoft.com/library/ms684880.aspx. kXPProcessAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF, //! \brief This is the XP-suitable value of `THREAD_ALL_ACCESS`. //! //! Requesting `THREAD_ALL_ACCESS` with the value defined when building //! against a Vista+ SDK results in `ERROR_ACCESS_DENIED` when running on XP. - //! See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769.aspx + //! See https://msdn.microsoft.com/library/ms686769.aspx. kXPThreadAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3FF, };